From 26d6011ec5abe1280362f331913cd7d1e174c552 Mon Sep 17 00:00:00 2001 From: Stoyan Kirov Date: Wed, 24 Apr 2024 17:50:24 +0300 Subject: [PATCH] Rust implementation for some Cairo types --- 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 + 6 files changed, 835 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/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;