From 6eabee6677102e451f3fb584125b2e7ceca3cede Mon Sep 17 00:00:00 2001 From: Stoyan Kirov Date: Wed, 24 Apr 2024 17:50:24 +0300 Subject: [PATCH 1/4] feat: Rust implementation for some Cairo types --- Cargo.lock | 117 ++++++ ampd/Cargo.toml | 2 + ampd/src/lib.rs | 27 +- ampd/src/starknet/mod.rs | 1 + ampd/src/starknet/types/array_span.rs | 250 ++++++++++++ ampd/src/starknet/types/byte_array.rs | 566 ++++++++++++++++++++++++++ ampd/src/starknet/types/mod.rs | 2 + 7 files changed, 952 insertions(+), 13 deletions(-) create mode 100644 ampd/src/starknet/mod.rs create mode 100644 ampd/src/starknet/types/array_span.rs create mode 100644 ampd/src/starknet/types/byte_array.rs create mode 100644 ampd/src/starknet/types/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3a8ec7d4d..d9fd12426 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,6 +191,8 @@ dependencies = [ "serde_with 3.3.0", "service-registry", "sha3 0.10.8", + "starknet-core", + "starknet-providers", "sui-json-rpc-types", "sui-types", "tendermint 0.33.0", @@ -888,6 +890,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint 0.4.4", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "bincode" version = "1.3.3" @@ -7124,6 +7138,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_json_pythonic" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62212da9872ca2a0cad0093191ee33753eddff9266cbbc1b4a602d13a3a768db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_path_to_error" version = "0.1.14" @@ -7499,6 +7524,98 @@ dependencies = [ "der 0.7.8", ] +[[package]] +name = "starknet-core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ed286d637e34fb8ae1cd2f9615120ec8ff38d1cffd311ed7fdd497cdd2bd01f" +dependencies = [ + "base64 0.21.4", + "flate2", + "hex", + "serde", + "serde_json", + "serde_json_pythonic", + "serde_with 2.3.3", + "sha3 0.10.8", + "starknet-crypto", + "starknet-ff", +] + +[[package]] +name = "starknet-crypto" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e2c30c01e8eb0fc913c4ee3cf676389fffc1d1182bfe5bb9670e4e72e968064" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint 0.4.4", + "num-integer", + "num-traits", + "rfc6979", + "sha2 0.10.7", + "starknet-crypto-codegen", + "starknet-curve", + "starknet-ff", + "zeroize", +] + +[[package]] +name = "starknet-crypto-codegen" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc159a1934c7be9761c237333a57febe060ace2bc9e3b337a59a37af206d19f" +dependencies = [ + "starknet-curve", + "starknet-ff", + "syn 2.0.37", +] + +[[package]] +name = "starknet-curve" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c383518bb312751e4be80f53e8644034aa99a0afb29d7ac41b89a997db875b" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-ff" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abf1b44ec5b18d87c1ae5f54590ca9d0699ef4dd5b2ffa66fc97f24613ec585" +dependencies = [ + "ark-ff", + "bigdecimal", + "crypto-bigint", + "getrandom", + "hex", + "serde", +] + +[[package]] +name = "starknet-providers" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6abf40ffcbe3b887b4d5cfc8ab73170c816b4aa78d1d4ad59abd3fb3b0f53cd" +dependencies = [ + "async-trait", + "auto_impl", + "ethereum-types", + "flate2", + "log", + "reqwest", + "serde", + "serde_json", + "serde_with 2.3.3", + "starknet-core", + "thiserror", + "url", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index a838e3481..adbaff99c 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -43,6 +43,8 @@ serde_json = { workspace = true } serde_with = "3.2.0" service-registry = { workspace = true } sha3 = { workspace = true } +starknet-core = "0.10.0" +starknet-providers = "0.10.0" sui-json-rpc-types = { git = "https://github.com/mystenlabs/sui", tag = "mainnet-v1.14.2" } sui-types = { git = "https://github.com/mystenlabs/sui", features = ["test-utils"], tag = "mainnet-v1.14.2" } # Need to switch to our own fork of tendermint and tendermint-rpc due to event attribute value being nullable. diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index df1e86142..edf6c3350 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -2,29 +2,28 @@ use std::pin::Pin; use std::time::Duration; use block_height_monitor::BlockHeightMonitor; +use broadcaster::accounts::account; +use broadcaster::Broadcaster; use connection_router_api::ChainName; -use cosmos_sdk_proto::cosmos::{ - auth::v1beta1::query_client::QueryClient, tx::v1beta1::service_client::ServiceClient, -}; +use cosmos_sdk_proto::cosmos::auth::v1beta1::query_client::QueryClient; +use cosmos_sdk_proto::cosmos::tx::v1beta1::service_client::ServiceClient; use error_stack::{report, FutureExt, Result, ResultExt}; +use event_processor::EventHandler; +use events::Event; use evm::finalizer::{pick, Finalization}; use evm::json_rpc::EthereumClient; +use queue::queued_broadcaster::{QueuedBroadcaster, QueuedBroadcasterDriver}; +use state::StateUpdater; use thiserror::Error; +use tofnd::grpc::{MultisigClient, SharableEcdsaClient}; use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::oneshot; use tokio_stream::Stream; use tokio_util::sync::CancellationToken; -use tracing::info; - -use crate::asyncutil::task::{CancellableTask, TaskError, TaskGroup}; -use broadcaster::{accounts::account, Broadcaster}; -use event_processor::EventHandler; -use events::Event; -use queue::queued_broadcaster::{QueuedBroadcaster, QueuedBroadcasterDriver}; -use state::StateUpdater; -use tofnd::grpc::{MultisigClient, SharableEcdsaClient}; +use tracing::{info, warn}; use types::TMAddress; +use crate::asyncutil::task::{CancellableTask, TaskError, TaskGroup}; use crate::config::Config; use crate::state::State; @@ -41,6 +40,7 @@ mod handlers; mod health_check; mod json_rpc; mod queue; +mod starknet; pub mod state; mod sui; mod tm_client; @@ -431,7 +431,8 @@ where broadcaster.run().change_context(Error::Broadcaster) })) .add_task(CancellableTask::create(|_| async move { - // assert: the state updater only stops when all handlers that are updating their states have stopped + // assert: the state updater only stops when all handlers that are updating + // their states have stopped state_tx .send(state_updater.run().await) .map_err(|_| report!(Error::ReturnState)) diff --git a/ampd/src/starknet/mod.rs b/ampd/src/starknet/mod.rs new file mode 100644 index 000000000..cd408564e --- /dev/null +++ b/ampd/src/starknet/mod.rs @@ -0,0 +1 @@ +pub mod types; diff --git a/ampd/src/starknet/types/array_span.rs b/ampd/src/starknet/types/array_span.rs new file mode 100644 index 000000000..9112525ea --- /dev/null +++ b/ampd/src/starknet/types/array_span.rs @@ -0,0 +1,250 @@ +use starknet_core::types::{FieldElement, ValueOutOfRangeError}; +use thiserror::Error; + +/// Applies for bot a cairo Array and a Span +#[derive(Debug)] +pub struct ArraySpan { + pub bytes: Vec, +} + +#[derive(Error, Debug)] +pub enum ArraySpanError { + #[error("Invalid array/span length")] + InvalidLength, + #[error("Failed to parse felt - {0}")] + ParsingFelt(#[from] ValueOutOfRangeError), +} + +impl TryFrom> for ArraySpan { + type Error = ArraySpanError; + + fn try_from(data: Vec) -> Result { + // First element is always the array length. + // We also have to go from `u32` to usize, because + // there's no direct `usize` From impl. + let arr_length: u32 = match data[0].try_into() { + Ok(al) => al, + Err(err) => return Err(ArraySpanError::ParsingFelt(err)), + }; + + // -1 because we have to offset the first element (the length itself) + let is_arr_el_count_valid = usize::try_from(arr_length) + .map(|count| count == data.len() - 1) + .unwrap_or(false); + + if !is_arr_el_count_valid { + return Err(ArraySpanError::InvalidLength); + } + + let bytes_parse: Result, ArraySpanError> = match data.get(1..) { + Some(b) => b, + None => return Err(ArraySpanError::InvalidLength), + } + .to_vec() + .into_iter() + .map(|e| { + let word_count: u8 = match e.try_into() { + Ok(wc) => wc, + Err(err) => return Err(ArraySpanError::ParsingFelt(err)), + }; + + Ok(word_count) + }) + .collect(); + + let bytes = match bytes_parse { + Ok(b) => b, + Err(e) => return Err(e), + }; + + Ok(ArraySpan { bytes }) + } +} + +#[cfg(test)] +mod array_span_tests { + use std::str::FromStr; + + use starknet_core::types::FieldElement; + + use crate::starknet::types::array_span::ArraySpan; + + #[test] + fn try_from_valid_zeros() { + // the string "hello", but FieldElement is bigger than u8::max + let data = vec![FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap()]; + + let array_span = ArraySpan::try_from(data).unwrap(); + assert_eq!(array_span.bytes, Vec::::new()); + } + + #[test] + fn try_from_failed_to_parse_element_to_u8() { + // the string "hello", but FieldElement is bigger than u8::max + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + FieldElement::from_str( + "0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000065", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006f", + ) + .unwrap(), + ]; + + let array_span = ArraySpan::try_from(data); + assert!(array_span.is_err()); + } + + #[test] + fn try_from_failed_to_parse_elements_length_to_u32() { + // the string "hello", but element counte bigger than u32::max + let data = vec![ + FieldElement::from_str( + "0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000068", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000065", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006f", + ) + .unwrap(), + ]; + + let array_span = ArraySpan::try_from(data); + assert!(array_span.is_err()); + } + + #[test] + fn try_from_invalid_number_of_elements() { + // the string "hello", but with only 4 bytes + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000068", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000065", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + ]; + + let array_span = ArraySpan::try_from(data); + assert!(array_span.is_err()); + } + + #[test] + fn try_from_invalid_declared_length() { + // the string "hello", with correct number of bytes, but only 4 declared, + // instead of 5 + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000004", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000068", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000065", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006f", + ) + .unwrap(), + ]; + + let array_span = ArraySpan::try_from(data); + assert!(array_span.is_err()); + } + + #[test] + fn try_from_valid() { + // the string "hello" + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000068", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000065", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006c", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000000000000006f", + ) + .unwrap(), + ]; + + let array_span = ArraySpan::try_from(data).unwrap(); + assert_eq!(array_span.bytes, vec![104, 101, 108, 108, 111]); + } +} diff --git a/ampd/src/starknet/types/byte_array.rs b/ampd/src/starknet/types/byte_array.rs new file mode 100644 index 000000000..f4a6dc264 --- /dev/null +++ b/ampd/src/starknet/types/byte_array.rs @@ -0,0 +1,566 @@ +use itertools::FoldWhile::{Continue, Done}; +use itertools::Itertools; +use starknet_core::types::{FieldElement, ValueOutOfRangeError}; +use starknet_core::utils::parse_cairo_short_string; +use thiserror::Error; + +#[derive(Debug)] +pub struct ByteArray { + /// The data byte array. Contains 31-byte chunks of the byte array. + data: Vec, + /// The bytes that remain after filling the data array with full 31-byte + /// chunks + pending_word: FieldElement, + /// The byte count of the pending_word + pending_word_length: u8, // can't be more than 30 bytes +} + +impl Default for ByteArray { + fn default() -> Self { + Self { + data: Default::default(), + pending_word: Default::default(), + pending_word_length: Default::default(), + } + } +} + +#[derive(Error, Debug)] +pub enum ByteArrayError { + #[error("Invalid byte array - {0}")] + InvalidByteArray(String), + #[error("Failed to parse felt - {0}")] + ParsingFelt(#[from] ValueOutOfRangeError), + #[error("Failed to convert the byte array into a string")] + ToString, +} + +/// The Vec should be the elements representing the ByteArray +/// type as described in this document: +/// https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays +/// +/// ## Example usage +/// +/// ```rust +/// use amd::starknet::types::ByteArray; +/// use std::str::FromStr; +/// use starknet_core::types::FieldElement; +/// +/// let data = vec![ +/// FieldElement::from_str( +/// "0x0000000000000000000000000000000000000000000000000000000000000000", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x00000000000000000000000000000000000000000000000000000068656c6c6f", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x0000000000000000000000000000000000000000000000000000000000000005", +/// ) +/// .unwrap(), +/// ]; +/// +/// let byte_array = ByteArray::try_from(data).unwrap(); +/// assert_eq!("hello", byte_array.is_ok()); +/// ``` +impl TryFrom> for ByteArray { + type Error = ByteArrayError; + + fn try_from(data: Vec) -> Result { + let mut byte_array = ByteArray { + ..Default::default() + }; + + if data.len() < 3 { + return Err(ByteArrayError::InvalidByteArray( + "vec should have minimum 3 elements".to_owned(), + )); + } + + // word count is always the first element + let word_count: u32 = match data[0].try_into() { + Ok(wc) => wc, + Err(err) => return Err(ByteArrayError::ParsingFelt(err)), + }; + + // vec element count should be whatever the word count is + 3 + // the 3 stands for the minimum 3 elements: + // - word count + // - pending_word + // - pendint_word_length + let is_arr_el_count_valid = usize::try_from(word_count + 3) + .map(|count| count == data.len()) + .unwrap_or(false); + if !is_arr_el_count_valid { + return Err(ByteArrayError::InvalidByteArray( + "pre-defined count doesn't match actual 31byte element count".to_owned(), + )); + } + + // pending word byte count is always the last element + let pending_word_length: u8 = match data[data.len() - 1].try_into() { + Ok(bc) => bc, + Err(err) => return Err(ByteArrayError::ParsingFelt(err)), + }; + byte_array.pending_word_length = pending_word_length; + + // pending word is always the next to last element + let pending_word = data[data.len() - 2]; + byte_array.pending_word = pending_word; + + // count bytes, excluding leading zeros + let non_zero_pw_length = pending_word + .to_bytes_be() + .iter() + .fold_while(32, |acc, n| { + if *n == 0 { + Continue(acc - 1) + } else { + Done(acc) + } + }) + .into_inner(); + + let is_pending_word_len_valid = usize::try_from(pending_word_length) + .map(|count| non_zero_pw_length == count) + .unwrap_or(false); + + if !is_pending_word_len_valid { + return Err(ByteArrayError::InvalidByteArray( + "pending_word length doesn't match it's defined length".to_owned(), + )); + } + + if word_count > 0 { + byte_array.data = data[1..data.len() - 2].to_vec(); + } + + Ok(byte_array) + + // TODO: + // - If word count is 0 - convert the pending word to a string + // - If word count > 0: + // - for i=2; i < 2+eventData[1]; i++ + // - cut all leading 0s + // - concatenate all field element hex bytes resulting in + // 31_word_bytes + // - parse felt 1 to u32 and take element parsedFelt+2 which is the + // pending_word + // - parse elelemtn parsedFelt+3 as u8, which is + // pending_word_bytes_length + // - take pending_words_byte_length worth of bytes from the + // pending_word + // - take the pending_word bytes and concatenate them with the + // previous 31_word_bytes + // - Convert those bytes to a string + } +} + +impl ByteArray { + /// Takes the ByteArray struct and tries to parse it as a single string + /// + /// ## Example usage with the string "hello" + /// + /// ```rust + /// use ampd::starknet::types::ByteArray; + /// use std::str::FromStr; + /// use starknet_core::types::FieldElement; + /// + /// let data = vec![ + /// FieldElement::from_str( + /// "0x0000000000000000000000000000000000000000000000000000000000000000", + /// ) + /// .unwrap(), + /// FieldElement::from_str( + /// "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + /// ) + /// .unwrap(), + /// FieldElement::from_str( + /// "0x0000000000000000000000000000000000000000000000000000000000000005", + /// ) + /// .unwrap(), + /// ]; + /// + /// let byte_array = ByteArray::try_from(data).unwrap(); + /// assert_eq!("hello", byte_array.try_to_string().unwrap()); + /// ``` + /// + /// Additional documentation you can find here: + /// https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + pub fn try_to_string(&self) -> Result { + match self + .data + .iter() + .chain(std::iter::once(&self.pending_word)) + .map(|felt| parse_cairo_short_string(felt)) + .collect::>() + { + Ok(s) => Ok(s), + Err(_) => Err(ByteArrayError::ToString), + } + } +} + +#[cfg(test)] +mod byte_array_tests { + use std::str::FromStr; + + use starknet_core::types::FieldElement; + + use crate::starknet::types::byte_array::ByteArray; + + #[test] + fn byte_array_parse_fail_wrong_pending_word_length() { + // Example for a small string (fits in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "hello" + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + // Should be of length 5 bytes, but we put 6 bytes, in order to fail + // the parsing + "0x0000000000000000000000000000000000000000000000000000000000000020", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data); + assert!(byte_array.is_err()); + } + + #[test] + fn byte_array_to_string_error() { + // Example for a small string (fits in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "hello" + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + // Note the 01 in the beginning. This is what causes the parse + // function to error. + FieldElement::from_str( + "0x01000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + // 32(0x20) bytes long pending_word + "0x0000000000000000000000000000000000000000000000000000000000000020", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data).unwrap(); + assert!(byte_array.try_to_string().is_err()); + } + + #[test] + fn byte_array_single_pending_word_only_to_string_valid() { + // Example for a small string (fits in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "hello" + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data).unwrap(); + assert_eq!("hello", byte_array.try_to_string().unwrap()); + } + + #[test] + fn byte_array_to_long_string_valid() { + // Example for a long string (doesn't fit in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "Long long string, a lot more than 31 characters that + // wouldn't even fit in two felts, so we'll have at least two felts and a + // pending word." + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000004", + ) + .unwrap(), + FieldElement::from_str( + "0x00004c6f6e67206c6f6e6720737472696e672c2061206c6f74206d6f72652074", + ) + .unwrap(), + FieldElement::from_str( + "0x000068616e2033312063686172616374657273207468617420776f756c646e27", + ) + .unwrap(), + FieldElement::from_str( + "0x000074206576656e2066697420696e2074776f2066656c74732c20736f207765", + ) + .unwrap(), + FieldElement::from_str( + "0x0000276c6c2068617665206174206c656173742074776f2066656c747320616e", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000006420612070656e64696e6720776f72642e", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000011", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data).unwrap(); + assert_eq!("Long long string, a lot more than 31 characters that wouldn't even fit in two felts, so we'll have at least two felts and a pending word.", byte_array.try_to_string().unwrap()); + } + + #[test] + fn try_from_vec_count_less_then_3() { + let data = vec![FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap()]; + + let byte_array_err = ByteArray::try_from(data); + assert!(byte_array_err.is_err()); + } + + #[test] + fn try_from_non_u32_word_count() { + let data = vec![ + // should be 0, because the message is short + // enough to fit in a single FieldElement + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + ]; + + let byte_array_err = ByteArray::try_from(data); + assert!(byte_array_err.is_err()); + } + #[test] + fn try_from_invalid_byte_array_element_count() { + let data = vec![ + // should be 0, because the message is short + // enough to fit in a single FieldElement + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + ]; + + let byte_array_err = ByteArray::try_from(data); + assert!(byte_array_err.is_err()); + } + + #[test] + fn try_from_non_u8_pending_word_length() { + // Example for a small string (fits in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "hello" + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data); + assert!(byte_array.is_err()); + } + + #[test] + fn try_from_valid_only_pending_word() { + // Example for a small string (fits in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "hello" + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000005", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data).unwrap(); + + assert_eq!(byte_array.data, vec![]); + assert_eq!( + byte_array.pending_word, + FieldElement::from_str( + "0x00000000000000000000000000000000000000000000000000000068656c6c6f", + ) + .unwrap() + ); + assert_eq!(byte_array.pending_word_length, 5); + } + + #[test] + fn try_from_valid_one_big_string_split_in_multiple_data_elements() { + // Example for a long string (doesn't fit in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "Long long string, a lot more than 31 characters that + // wouldn't even fit in two felts, so we'll have at least two felts and a + // pending word." + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000004", + ) + .unwrap(), + FieldElement::from_str( + "0x00004c6f6e67206c6f6e6720737472696e672c2061206c6f74206d6f72652074", + ) + .unwrap(), + FieldElement::from_str( + "0x000068616e2033312063686172616374657273207468617420776f756c646e27", + ) + .unwrap(), + FieldElement::from_str( + "0x000074206576656e2066697420696e2074776f2066656c74732c20736f207765", + ) + .unwrap(), + FieldElement::from_str( + "0x0000276c6c2068617665206174206c656173742074776f2066656c747320616e", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000006420612070656e64696e6720776f72642e", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000011", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data).unwrap(); + + assert_eq!( + byte_array.data, + vec![ + FieldElement::from_str( + "0x00004c6f6e67206c6f6e6720737472696e672c2061206c6f74206d6f72652074", + ) + .unwrap(), + FieldElement::from_str( + "0x000068616e2033312063686172616374657273207468617420776f756c646e27", + ) + .unwrap(), + FieldElement::from_str( + "0x000074206576656e2066697420696e2074776f2066656c74732c20736f207765", + ) + .unwrap(), + FieldElement::from_str( + "0x0000276c6c2068617665206174206c656173742074776f2066656c747320616e", + ) + .unwrap() + ] + ); + assert_eq!( + byte_array.pending_word, + FieldElement::from_str( + "0x0000000000000000000000000000006420612070656e64696e6720776f72642e", + ) + .unwrap() + ); + assert_eq!(byte_array.pending_word_length, 17); + } + + #[test] + fn try_from_valid_one_very_big_string() { + // Example for a long string (doesn't fit in a single felt) taken from here: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays + // + // So this is the string "Long string, more than 31 characters." + let data = vec![ + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) + .unwrap(), + FieldElement::from_str( + "0x004c6f6e6720737472696e672c206d6f7265207468616e203331206368617261", + ) + .unwrap(), + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000063746572732e", + ) + .unwrap(), + FieldElement::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000006", + ) + .unwrap(), + ]; + + let byte_array = ByteArray::try_from(data).unwrap(); + + assert_eq!( + byte_array.data, + vec![FieldElement::from_str( + "0x004c6f6e6720737472696e672c206d6f7265207468616e203331206368617261", + ) + .unwrap()] + ); + assert_eq!( + byte_array.pending_word, + FieldElement::from_str( + "0x000000000000000000000000000000000000000000000000000063746572732e", + ) + .unwrap() + ); + assert_eq!(byte_array.pending_word_length, 6); + } +} diff --git a/ampd/src/starknet/types/mod.rs b/ampd/src/starknet/types/mod.rs new file mode 100644 index 000000000..c74f260f7 --- /dev/null +++ b/ampd/src/starknet/types/mod.rs @@ -0,0 +1,2 @@ +pub mod array_span; +pub mod byte_array; From d637a149d5d1b254fd18bb48f12c849e9865b9fc Mon Sep 17 00:00:00 2001 From: Stoyan Kirov Date: Thu, 25 Apr 2024 13:09:34 +0300 Subject: [PATCH 2/4] fixes on doc tests --- ampd/src/lib.rs | 2 +- ampd/src/starknet/types/array_span.rs | 45 ++++++++++++++- ampd/src/starknet/types/byte_array.rs | 79 +++++++++++---------------- 3 files changed, 77 insertions(+), 49 deletions(-) diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index edf6c3350..d6a552445 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -40,7 +40,7 @@ mod handlers; mod health_check; mod json_rpc; mod queue; -mod starknet; +pub mod starknet; pub mod state; mod sui; mod tm_client; diff --git a/ampd/src/starknet/types/array_span.rs b/ampd/src/starknet/types/array_span.rs index 9112525ea..97a8c6924 100644 --- a/ampd/src/starknet/types/array_span.rs +++ b/ampd/src/starknet/types/array_span.rs @@ -1,7 +1,50 @@ use starknet_core::types::{FieldElement, ValueOutOfRangeError}; use thiserror::Error; -/// Applies for bot a cairo Array and a Span +/// Represents Cairo's Array and Span types. +/// Implements `TryFrom>`, which is the way to create it. +/// +/// ## Example usage with the strging "hello" +/// +/// ```rust +/// use ampd::starknet::types::array_span::ArraySpan; +/// use std::str::FromStr; +/// use starknet_core::types::FieldElement; +/// +/// let data = vec![ +/// FieldElement::from_str( +/// "0x0000000000000000000000000000000000000000000000000000000000000005", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x0000000000000000000000000000000000000000000000000000000000000068", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x0000000000000000000000000000000000000000000000000000000000000065", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x000000000000000000000000000000000000000000000000000000000000006c", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x000000000000000000000000000000000000000000000000000000000000006c", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x000000000000000000000000000000000000000000000000000000000000006f", +/// ) +/// .unwrap(), +/// ]; +/// +/// let array_span = ArraySpan::try_from(data).unwrap(); +/// assert_eq!(array_span.bytes, vec![104, 101, 108, 108, 111]); +/// assert_eq!(String::from_utf8(array_span.bytes).unwrap(), "hello"); +/// ``` +/// +/// For more info: +/// https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays #[derive(Debug)] pub struct ArraySpan { pub bytes: Vec, diff --git a/ampd/src/starknet/types/byte_array.rs b/ampd/src/starknet/types/byte_array.rs index f4a6dc264..811e4f607 100644 --- a/ampd/src/starknet/types/byte_array.rs +++ b/ampd/src/starknet/types/byte_array.rs @@ -4,6 +4,37 @@ use starknet_core::types::{FieldElement, ValueOutOfRangeError}; use starknet_core::utils::parse_cairo_short_string; use thiserror::Error; +/// Represents Cairo's ByteArray type. +/// Implements `TryFrom>`, which is the way to create it. +/// +/// ## Example usage with the strging "hello" +/// +/// ```rust +/// use ampd::starknet::types::byte_array::ByteArray; +/// use std::str::FromStr; +/// use starknet_core::types::FieldElement; +/// +/// let data = vec![ +/// FieldElement::from_str( +/// "0x0000000000000000000000000000000000000000000000000000000000000000", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x00000000000000000000000000000000000000000000000000000068656c6c6f", +/// ) +/// .unwrap(), +/// FieldElement::from_str( +/// "0x0000000000000000000000000000000000000000000000000000000000000005", +/// ) +/// .unwrap(), +/// ]; +/// +/// let byte_array = ByteArray::try_from(data); +/// assert!(byte_array.is_ok()); +/// ``` +/// +/// For more info: +/// https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays #[derive(Debug)] pub struct ByteArray { /// The data byte array. Contains 31-byte chunks of the byte array. @@ -35,35 +66,6 @@ pub enum ByteArrayError { ToString, } -/// The Vec should be the elements representing the ByteArray -/// type as described in this document: -/// https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays -/// -/// ## Example usage -/// -/// ```rust -/// use amd::starknet::types::ByteArray; -/// use std::str::FromStr; -/// use starknet_core::types::FieldElement; -/// -/// let data = vec![ -/// FieldElement::from_str( -/// "0x0000000000000000000000000000000000000000000000000000000000000000", -/// ) -/// .unwrap(), -/// FieldElement::from_str( -/// "0x00000000000000000000000000000000000000000000000000000068656c6c6f", -/// ) -/// .unwrap(), -/// FieldElement::from_str( -/// "0x0000000000000000000000000000000000000000000000000000000000000005", -/// ) -/// .unwrap(), -/// ]; -/// -/// let byte_array = ByteArray::try_from(data).unwrap(); -/// assert_eq!("hello", byte_array.is_ok()); -/// ``` impl TryFrom> for ByteArray { type Error = ByteArrayError; @@ -137,23 +139,6 @@ impl TryFrom> for ByteArray { } Ok(byte_array) - - // TODO: - // - If word count is 0 - convert the pending word to a string - // - If word count > 0: - // - for i=2; i < 2+eventData[1]; i++ - // - cut all leading 0s - // - concatenate all field element hex bytes resulting in - // 31_word_bytes - // - parse felt 1 to u32 and take element parsedFelt+2 which is the - // pending_word - // - parse elelemtn parsedFelt+3 as u8, which is - // pending_word_bytes_length - // - take pending_words_byte_length worth of bytes from the - // pending_word - // - take the pending_word bytes and concatenate them with the - // previous 31_word_bytes - // - Convert those bytes to a string } } @@ -163,7 +148,7 @@ impl ByteArray { /// ## Example usage with the string "hello" /// /// ```rust - /// use ampd::starknet::types::ByteArray; + /// use ampd::starknet::types::byte_array::ByteArray; /// use std::str::FromStr; /// use starknet_core::types::FieldElement; /// From 244060df6407c24d5afa54c4c7df534ed669b51f Mon Sep 17 00:00:00 2001 From: Stoyan Kirov Date: Thu, 25 Apr 2024 18:38:30 +0300 Subject: [PATCH 3/4] Error handling fixes and PR comment fixes --- ampd/src/starknet/types/array_span.rs | 16 ++++------------ ampd/src/starknet/types/byte_array.rs | 14 ++++---------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/ampd/src/starknet/types/array_span.rs b/ampd/src/starknet/types/array_span.rs index 97a8c6924..ffaa0883d 100644 --- a/ampd/src/starknet/types/array_span.rs +++ b/ampd/src/starknet/types/array_span.rs @@ -4,7 +4,7 @@ use thiserror::Error; /// Represents Cairo's Array and Span types. /// Implements `TryFrom>`, which is the way to create it. /// -/// ## Example usage with the strging "hello" +/// ## Example usage with the string "hello" /// /// ```rust /// use ampd::starknet::types::array_span::ArraySpan; @@ -65,10 +65,7 @@ impl TryFrom> for ArraySpan { // First element is always the array length. // We also have to go from `u32` to usize, because // there's no direct `usize` From impl. - let arr_length: u32 = match data[0].try_into() { - Ok(al) => al, - Err(err) => return Err(ArraySpanError::ParsingFelt(err)), - }; + let arr_length = u32::try_from(data[0])?; // -1 because we have to offset the first element (the length itself) let is_arr_el_count_valid = usize::try_from(arr_length) @@ -79,7 +76,7 @@ impl TryFrom> for ArraySpan { return Err(ArraySpanError::InvalidLength); } - let bytes_parse: Result, ArraySpanError> = match data.get(1..) { + let bytes: Result, ArraySpanError> = match data.get(1..) { Some(b) => b, None => return Err(ArraySpanError::InvalidLength), } @@ -95,12 +92,7 @@ impl TryFrom> for ArraySpan { }) .collect(); - let bytes = match bytes_parse { - Ok(b) => b, - Err(e) => return Err(e), - }; - - Ok(ArraySpan { bytes }) + Ok(ArraySpan { bytes: bytes? }) } } diff --git a/ampd/src/starknet/types/byte_array.rs b/ampd/src/starknet/types/byte_array.rs index 811e4f607..8b6082fcf 100644 --- a/ampd/src/starknet/types/byte_array.rs +++ b/ampd/src/starknet/types/byte_array.rs @@ -7,7 +7,7 @@ use thiserror::Error; /// Represents Cairo's ByteArray type. /// Implements `TryFrom>`, which is the way to create it. /// -/// ## Example usage with the strging "hello" +/// ## Example usage with the string "hello" /// /// ```rust /// use ampd::starknet::types::byte_array::ByteArray; @@ -60,7 +60,7 @@ impl Default for ByteArray { pub enum ByteArrayError { #[error("Invalid byte array - {0}")] InvalidByteArray(String), - #[error("Failed to parse felt - {0}")] + #[error("Failed to convert felt - {0}")] ParsingFelt(#[from] ValueOutOfRangeError), #[error("Failed to convert the byte array into a string")] ToString, @@ -81,10 +81,7 @@ impl TryFrom> for ByteArray { } // word count is always the first element - let word_count: u32 = match data[0].try_into() { - Ok(wc) => wc, - Err(err) => return Err(ByteArrayError::ParsingFelt(err)), - }; + let word_count = u32::try_from(data[0])?; // vec element count should be whatever the word count is + 3 // the 3 stands for the minimum 3 elements: @@ -101,10 +98,7 @@ impl TryFrom> for ByteArray { } // pending word byte count is always the last element - let pending_word_length: u8 = match data[data.len() - 1].try_into() { - Ok(bc) => bc, - Err(err) => return Err(ByteArrayError::ParsingFelt(err)), - }; + let pending_word_length = u8::try_from(data[data.len() - 1])?; byte_array.pending_word_length = pending_word_length; // pending word is always the next to last element From 72499d692b88aea6347b4179304118e29a2602e5 Mon Sep 17 00:00:00 2001 From: Stoyan Kirov Date: Mon, 29 Apr 2024 19:07:51 +0300 Subject: [PATCH 4/4] Fix clippy and refactor --- ampd/src/lib.rs | 2 +- ampd/src/starknet/types/array_span.rs | 35 +++++---------- ampd/src/starknet/types/byte_array.rs | 61 +++++++++++++-------------- 3 files changed, 42 insertions(+), 56 deletions(-) diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index d6a552445..da73beadb 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -20,7 +20,7 @@ use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::oneshot; use tokio_stream::Stream; use tokio_util::sync::CancellationToken; -use tracing::{info, warn}; +use tracing::info; use types::TMAddress; use crate::asyncutil::task::{CancellableTask, TaskError, TaskGroup}; diff --git a/ampd/src/starknet/types/array_span.rs b/ampd/src/starknet/types/array_span.rs index ffaa0883d..2aaf27520 100644 --- a/ampd/src/starknet/types/array_span.rs +++ b/ampd/src/starknet/types/array_span.rs @@ -62,35 +62,22 @@ impl TryFrom> for ArraySpan { type Error = ArraySpanError; fn try_from(data: Vec) -> Result { - // First element is always the array length. - // We also have to go from `u32` to usize, because - // there's no direct `usize` From impl. - let arr_length = u32::try_from(data[0])?; + // First element is always the array length, which is a felt (so u8 is enough) + let arr_length = u8::try_from(data[0])?; // -1 because we have to offset the first element (the length itself) - let is_arr_el_count_valid = usize::try_from(arr_length) - .map(|count| count == data.len() - 1) - .unwrap_or(false); - - if !is_arr_el_count_valid { + let arr_length_usize = usize::from(arr_length); + if arr_length_usize != data.len().wrapping_sub(1) { return Err(ArraySpanError::InvalidLength); } - let bytes: Result, ArraySpanError> = match data.get(1..) { - Some(b) => b, - None => return Err(ArraySpanError::InvalidLength), - } - .to_vec() - .into_iter() - .map(|e| { - let word_count: u8 = match e.try_into() { - Ok(wc) => wc, - Err(err) => return Err(ArraySpanError::ParsingFelt(err)), - }; - - Ok(word_count) - }) - .collect(); + let bytes: Result, ArraySpanError> = data + .get(1..) + .ok_or(ArraySpanError::InvalidLength)? + .iter() + .copied() + .map(|e| e.try_into().map_err(ArraySpanError::ParsingFelt)) + .collect(); Ok(ArraySpan { bytes: bytes? }) } diff --git a/ampd/src/starknet/types/byte_array.rs b/ampd/src/starknet/types/byte_array.rs index 8b6082fcf..aa3467ad0 100644 --- a/ampd/src/starknet/types/byte_array.rs +++ b/ampd/src/starknet/types/byte_array.rs @@ -35,7 +35,7 @@ use thiserror::Error; /// /// For more info: /// https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/serialization_of_Cairo_types/#serialization_of_byte_arrays -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ByteArray { /// The data byte array. Contains 31-byte chunks of the byte array. data: Vec, @@ -46,18 +46,10 @@ pub struct ByteArray { pending_word_length: u8, // can't be more than 30 bytes } -impl Default for ByteArray { - fn default() -> Self { - Self { - data: Default::default(), - pending_word: Default::default(), - pending_word_length: Default::default(), - } - } -} - #[derive(Error, Debug)] pub enum ByteArrayError { + #[error("Failed to fetch element from byte array at index")] + OutOfBound, #[error("Invalid byte array - {0}")] InvalidByteArray(String), #[error("Failed to convert felt - {0}")] @@ -70,6 +62,10 @@ impl TryFrom> for ByteArray { type Error = ByteArrayError; fn try_from(data: Vec) -> Result { + // pending word is always the next to last element + let pending_word_index = data.len().wrapping_sub(2); + let last_element_index = data.len().wrapping_sub(1); + let mut byte_array = ByteArray { ..Default::default() }; @@ -80,56 +76,59 @@ impl TryFrom> for ByteArray { )); } - // word count is always the first element - let word_count = u32::try_from(data[0])?; + // word count is always the first element, which is a felt (so u8 is enough) + let word_count = u8::try_from(data[0])?; - // vec element count should be whatever the word count is + 3 + // vec element count should be whatever the word count is + an offset of 3 // the 3 stands for the minimum 3 elements: // - word count // - pending_word // - pendint_word_length - let is_arr_el_count_valid = usize::try_from(word_count + 3) - .map(|count| count == data.len()) - .unwrap_or(false); - if !is_arr_el_count_valid { + let word_count_usize = usize::from(word_count.wrapping_add(3)); + if word_count_usize != data.len() { return Err(ByteArrayError::InvalidByteArray( "pre-defined count doesn't match actual 31byte element count".to_owned(), )); } // pending word byte count is always the last element - let pending_word_length = u8::try_from(data[data.len() - 1])?; + let pending_word_length_felt = data + .get(last_element_index) + .ok_or(ByteArrayError::OutOfBound)?; + let pending_word_length = u8::try_from(*pending_word_length_felt)?; byte_array.pending_word_length = pending_word_length; - // pending word is always the next to last element - let pending_word = data[data.len() - 2]; - byte_array.pending_word = pending_word; + let pending_word = data + .get(pending_word_index) + .ok_or(ByteArrayError::OutOfBound)?; + byte_array.pending_word = *pending_word; // count bytes, excluding leading zeros let non_zero_pw_length = pending_word .to_bytes_be() .iter() - .fold_while(32, |acc, n| { + .fold_while(32, |acc: u8, n| { if *n == 0 { - Continue(acc - 1) + Continue(acc.saturating_sub(1)) } else { Done(acc) } }) .into_inner(); - let is_pending_word_len_valid = usize::try_from(pending_word_length) - .map(|count| non_zero_pw_length == count) - .unwrap_or(false); - - if !is_pending_word_len_valid { + if pending_word_length != non_zero_pw_length { return Err(ByteArrayError::InvalidByteArray( "pending_word length doesn't match it's defined length".to_owned(), )); } if word_count > 0 { - byte_array.data = data[1..data.len() - 2].to_vec(); + let byte_array_data = data + .get(1..pending_word_index) + .ok_or(ByteArrayError::OutOfBound)? + .to_vec(); + + byte_array.data = byte_array_data; } Ok(byte_array) @@ -172,7 +171,7 @@ impl ByteArray { .data .iter() .chain(std::iter::once(&self.pending_word)) - .map(|felt| parse_cairo_short_string(felt)) + .map(parse_cairo_short_string) .collect::>() { Ok(s) => Ok(s),