diff --git a/app/Makefile.version b/app/Makefile.version index 5fbe479b..50b9605a 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -1,3 +1,3 @@ APPVERSION_M=0 APPVERSION_N=24 -APPVERSION_P=4 +APPVERSION_P=5 diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock index 65f630f0..6087fe2e 100644 --- a/app/rust/Cargo.lock +++ b/app/rust/Cargo.lock @@ -71,6 +71,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.9.0" @@ -194,12 +200,29 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "bitflags", + "cfg-if", + "libm", +] + [[package]] name = "libc" version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "lock_api" version = "0.4.7" @@ -329,6 +352,7 @@ dependencies = [ "arrayvec", "base64", "hex", + "lexical-core", "no-std-compat", "nom", "numtoa", diff --git a/app/rust/Cargo.toml b/app/rust/Cargo.toml index 21137082..1f0e96cf 100644 --- a/app/rust/Cargo.toml +++ b/app/rust/Cargo.toml @@ -23,6 +23,9 @@ hex = { version = "0.4", default-features = false } serde-json-core = { version = "0.4.0", default-features = false } serde = { version = "1.0", default-features = false, features = ["derive"] } nom = { version = "7.1.2", default-features = false } +lexical-core = { version = "0.7", features = [ + "libm", +], default-features = false } [dependencies.arrayvec] diff --git a/app/rust/src/parser/ffi.rs b/app/rust/src/parser/ffi.rs index d5cc62ca..2d17fd91 100644 --- a/app/rust/src/parser/ffi.rs +++ b/app/rust/src/parser/ffi.rs @@ -225,9 +225,12 @@ pub unsafe extern "C" fn _last_block_ptr( ) -> u16 { if let Some(tx) = parsed_obj_from_state(tx_t as _).and_then(|obj| obj.transaction()) { let block = tx.last_transaction_block(); + *block_ptr = block.as_ptr(); return block.len() as _; } + + *block_ptr = core::ptr::null_mut(); 0 } diff --git a/app/rust/src/parser/message.rs b/app/rust/src/parser/message.rs index 4790a89a..8ff822c6 100644 --- a/app/rust/src/parser/message.rs +++ b/app/rust/src/parser/message.rs @@ -12,6 +12,7 @@ const BYTE_STRING_HEADER_LEN: usize = "\x17Stacks Signed Message:\n".as_bytes(). const MAX_ASCII_LEN: usize = 270; #[repr(C)] +#[cfg_attr(test, derive(Debug))] pub struct Message<'a>(ByteString<'a>); impl<'a> Message<'a> { @@ -45,9 +46,20 @@ impl<'a> Message<'a> { } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq)] pub struct ByteString<'a>(&'a [u8]); +#[cfg(test)] +impl<'a> core::fmt::Debug for ByteString<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "ByteString(\"")?; + for byte in self.0 { + write!(f, "{:02x}", byte)?; + } + write!(f, "\")") + } +} + impl<'a> ByteString<'a> { pub fn is_msg(data: &'a [u8]) -> bool { Self::contain_header(data) diff --git a/app/rust/src/parser/mod.rs b/app/rust/src/parser/mod.rs index 8531df4e..f3518d58 100644 --- a/app/rust/src/parser/mod.rs +++ b/app/rust/src/parser/mod.rs @@ -5,13 +5,14 @@ mod jwt; mod message; mod parsed_obj; mod parser_common; -mod post_condition; +mod post_conditions; mod principal; mod spending_condition; mod structured_msg; mod transaction; mod transaction_auth; mod transaction_payload; +mod tx_post_conditions; mod utils; mod value; pub use error::ParserError; @@ -20,10 +21,11 @@ pub use jwt::Jwt; pub use message::{ByteString, Message}; pub use parsed_obj::{ParsedObj, Tag}; pub use parser_common::*; -pub use post_condition::{FungibleConditionCode, TransactionPostCondition}; +pub use post_conditions::{FungibleConditionCode, TransactionPostCondition}; pub use principal::*; pub use structured_msg::{Domain, StructuredMsg}; pub use transaction::Transaction; pub use transaction_auth::TransactionAuth; +pub use tx_post_conditions::{PostConditions, TransactionPostConditionMode}; pub use utils::*; pub use value::{Int128, Tuple, UInt128, Value, ValueId}; diff --git a/app/rust/src/parser/parsed_obj.rs b/app/rust/src/parser/parsed_obj.rs index 2fa43f7f..34463a2b 100644 --- a/app/rust/src/parser/parsed_obj.rs +++ b/app/rust/src/parser/parsed_obj.rs @@ -1,5 +1,7 @@ #![allow(non_camel_case_types, non_snake_case, clippy::missing_safety_doc)] +use crate::bolos::c_zemu_log_stack; + use super::{error::ParserError, transaction::Transaction, Message}; use super::{Jwt, StructuredMsg}; @@ -56,6 +58,7 @@ impl<'a> ParsedObj<'a> { } pub fn read(&mut self, data: &'a [u8]) -> Result<(), ParserError> { + c_zemu_log_stack("ParsedObj::read\x00"); if data.is_empty() { return Err(ParserError::NoData); } @@ -65,15 +68,19 @@ impl<'a> ParsedObj<'a> { unsafe { if Message::is_message(data) { + c_zemu_log_stack("Tag::Msg\x00"); self.tag = Tag::Message; self.obj.read_msg(data) } else if Jwt::is_jwt(data) { + c_zemu_log_stack("Tag::Jwt\x00"); self.tag = Tag::Jwt; self.obj.read_jwt(data) } else if StructuredMsg::is_msg(data) { + c_zemu_log_stack("Tag::StructuredMsg\x00"); self.tag = Tag::StructuredMsg; self.obj.read_structured_msg(data) } else { + c_zemu_log_stack("Tag::Transaction\x00"); self.tag = Tag::Transaction; self.obj.read_tx(data) } @@ -99,6 +106,7 @@ impl<'a> ParsedObj<'a> { value: &mut [u8], page_idx: u8, ) -> Result { + c_zemu_log_stack("ParsedObj::get_item\x00"); unsafe { match self.tag { Tag::Transaction => { @@ -259,6 +267,38 @@ impl<'a> Obj<'a> { } } +#[cfg(test)] +impl<'a> core::fmt::Debug for ParsedObj<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut debug_struct = f.debug_struct("ParsedObj"); + debug_struct.field("tag", &self.tag); + + // Safety: We're matching on the tag to ensure we access the correct + // union variant. The union is guaranteed to be initialized with the + // correct variant and that variant won't change during the lifetime + // of the object. + match self.tag { + Tag::Transaction => unsafe { + debug_struct.field("obj", &self.obj.tx); + }, + Tag::Message => unsafe { + debug_struct.field("obj", &self.obj.msg); + }, + Tag::Jwt => unsafe { + debug_struct.field("obj", &self.obj.jwt); + }, + Tag::StructuredMsg => unsafe { + debug_struct.field("obj", &self.obj.structured_msg); + }, + Tag::Invalid => { + debug_struct.field("obj", &""); + } + } + + debug_struct.finish() + } +} + #[cfg(test)] mod test { use serde::{Deserialize, Serialize}; @@ -793,4 +833,17 @@ mod test { msg.read(&bytes).unwrap(); ParsedObj::validate(&mut msg).unwrap(); } + + #[test] + fn test_swap_tx() { + let input = "000000000104009ef3889fd070159edcd8ef88a0ec87cea1592c83000000000000000000000000000f42400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000302000000060002169ef3889fd070159edcd8ef88a0ec87cea1592c830100000000000027100003167c5f674a8fd08efa61dd9b11121e046dd2c892730a756e6976322d636f72650300000000000000000103167c5f674a8fd08efa61dd9b11121e046dd2c892730a756e6976322d636f7265168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e086c6f6e67636f696e0300000000000000000103167c5f674a8fd08efa61dd9b11121e046dd2c892730a756e6976322d636f7265168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e086c6f6e67636f696e0300000000000000000102169ef3889fd070159edcd8ef88a0ec87cea1592c83168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e086c6f6e67636f696e030000000000000000010316402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0f616d6d2d7661756c742d76322d303116402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0a746f6b656e2d616c657804616c65780300000000011c908a02162ec1a2dc2904ebc8b408598116c75e42c51afa2617726f757465722d76656c61722d616c65782d762d312d320d737761702d68656c7065722d6100000007010000000000000000000000000000271001000000000000000000000000011c908a040c00000002016106167c5f674a8fd08efa61dd9b11121e046dd2c892730477737478016206168c5e2f8d25627d6edebeb6d10fa3300f5acc8441086c6f6e67636f696e06167c5f674a8fd08efa61dd9b11121e046dd2c8927312756e6976322d73686172652d6665652d746f0c0000000201610616402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0b746f6b656e2d776c6f6e6701620616402da2c079e5d31d58b9cfc7286d1b1eb2f7834e0a746f6b656e2d616c65780c0000000101610100000000000000000000000005f5e100"; + let bytes = hex::decode(input).unwrap(); + let mut msg = ParsedObj::from_bytes(&bytes).unwrap(); + + msg.read(&bytes).unwrap(); + + ParsedObj::validate(&mut msg).unwrap(); + + std::println!("tx: {:?}", msg); + } } diff --git a/app/rust/src/parser/parser_common.rs b/app/rust/src/parser/parser_common.rs index 05b42dbe..a161d6ea 100644 --- a/app/rust/src/parser/parser_common.rs +++ b/app/rust/src/parser/parser_common.rs @@ -20,11 +20,12 @@ pub const C32_ENCODED_ADDRS_LENGTH: usize = 48; pub const NUM_SUPPORTED_POST_CONDITIONS: usize = 16; pub const SIGNATURE_LEN: usize = 65; pub const PUBKEY_LEN: usize = 33; -pub const TOKEN_TRANSFER_MEMO_LEN: usize = 34; +pub const MEMO_LEN: usize = 34; +pub const AMOUNT_LEN: usize = 8; // A recursion limit use to control ram usage when parsing // contract-call arguments that comes in a transaction -pub const TX_DEPTH_LIMIT: u8 = 3; +pub const TX_DEPTH_LIMIT: u8 = 8; // Use to limit recursion when parsing nested clarity values that comes as part of a structured // message. the limit is higher than the one use when parsing contract-args in transactions diff --git a/app/rust/src/parser/post_condition.rs b/app/rust/src/parser/post_conditions.rs similarity index 74% rename from app/rust/src/parser/post_condition.rs rename to app/rust/src/parser/post_conditions.rs index 834ddf9c..2f108237 100644 --- a/app/rust/src/parser/post_condition.rs +++ b/app/rust/src/parser/post_conditions.rs @@ -5,186 +5,20 @@ use nom::{ number::complete::{be_u64, le_u8}, }; -use super::error::ParserError; - -use super::parser_common::{ - AssetInfo, ContractName, StacksAddress, C32_ENCODED_ADDRS_LENGTH, HASH160_LEN, STX_DECIMALS, - TX_DEPTH_LIMIT, -}; -use crate::parser::value::Value; -use crate::zxformat; - -#[repr(u8)] -#[derive(Clone, PartialEq, Copy)] -pub enum PostConditionPrincipalId { - Origin = 0x01, - Standard = 0x02, - Contract = 0x03, -} - -impl TryFrom for PostConditionPrincipalId { - type Error = ParserError; - - fn try_from(value: u8) -> Result { - let id = match value { - 1 => Self::Origin, - 2 => Self::Standard, - 3 => Self::Contract, - _ => return Err(ParserError::UnexpectedValue), - }; - Ok(id) - } -} - -#[repr(C)] -#[derive(Clone, Copy, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub enum PostConditionPrincipal<'a> { - Origin, - Standard(StacksAddress<'a>), - Contract(StacksAddress<'a>, ContractName<'a>), -} - -impl<'a> PostConditionPrincipal<'a> { - #[inline(never)] - pub fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { - let id = le_u8(bytes)?; - let principal_id = PostConditionPrincipalId::try_from(id.1)?; - match principal_id { - PostConditionPrincipalId::Origin => Ok((id.0, PostConditionPrincipal::Origin)), - PostConditionPrincipalId::Standard => { - let addrs = StacksAddress::from_bytes(id.0)?; - Ok((addrs.0, PostConditionPrincipal::Standard(addrs.1))) - } - PostConditionPrincipalId::Contract => { - let addrs = StacksAddress::from_bytes(id.0)?; - let contract_name = ContractName::from_bytes(addrs.0)?; - Ok(( - contract_name.0, - PostConditionPrincipal::Contract(addrs.1, contract_name.1), - )) - } - } - } - - pub fn read_as_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], &[u8], ParserError> { - let (rem, id) = le_u8(bytes)?; - let principal_id = PostConditionPrincipalId::try_from(id)?; - match principal_id { - PostConditionPrincipalId::Origin => Ok((rem, Default::default())), - PostConditionPrincipalId::Standard => { - // we take 20-byte key hash + 1-byte hash_mode + 1-byte principal_id - let (raw, addr) = take(HASH160_LEN + 2usize)(bytes)?; - Ok((raw, addr)) - } - PostConditionPrincipalId::Contract => { - let (rem, _) = StacksAddress::from_bytes(rem)?; - let (_, name) = ContractName::from_bytes(rem)?; - // we take 20-byte key hash + 1-byte hash_mode + - // contract_name len + 1-byte len + 1-byte principal_id - let total_len = HASH160_LEN + name.len() + 3; - let (rem, contract_bytes) = take(total_len)(bytes)?; - Ok((rem, contract_bytes)) - } - } - } - - pub fn is_origin(&self) -> bool { - matches!(self, Self::Origin) - } - - pub fn is_standard(&self) -> bool { - matches!(self, Self::Standard(..)) - } - - pub fn is_contract(&self) -> bool { - matches!(self, Self::Contract(..)) - } - - pub fn origin_address( - ) -> Result, ParserError> { - let mut output: ArrayVec<[_; C32_ENCODED_ADDRS_LENGTH]> = ArrayVec::new(); - output.try_extend_from_slice(b"Origin".as_ref()).unwrap(); - Ok(output) - } - - pub fn get_principal_address( - &self, - ) -> Result, ParserError> { - match self { - Self::Origin => Self::origin_address(), - Self::Standard(ref address) | Self::Contract(ref address, _) => { - address.encoded_address() - } - } - } +mod fungible; +mod non_fungible; +mod post_condition_principal; +pub use fungible::FungibleConditionCode; +pub use non_fungible::NonfungibleConditionCode; - pub fn get_contract_name(&'a self) -> Option<&'a [u8]> { - match self { - Self::Contract(_, name) => Some(name.name()), - _ => None, - } - } -} +pub use post_condition_principal::{PostConditionPrincipal, PostConditionPrincipalId}; -#[repr(u8)] -#[derive(Clone, PartialEq, Copy)] -#[cfg_attr(test, derive(Debug))] -pub enum FungibleConditionCode { - SentEq = 0x01, - SentGt = 0x02, - SentGe = 0x03, - SentLt = 0x04, - SentLe = 0x05, -} - -impl FungibleConditionCode { - pub fn from_u8(b: u8) -> Option { - match b { - 0x01 => Some(FungibleConditionCode::SentEq), - 0x02 => Some(FungibleConditionCode::SentGt), - 0x03 => Some(FungibleConditionCode::SentGe), - 0x04 => Some(FungibleConditionCode::SentLt), - 0x05 => Some(FungibleConditionCode::SentLe), - _ => None, - } - } - - pub fn to_str(self) -> &'static str { - match self { - FungibleConditionCode::SentEq => "SentEq", - FungibleConditionCode::SentGt => "SentGt", - FungibleConditionCode::SentGe => "SentGe", - FungibleConditionCode::SentLt => "SentLt", - FungibleConditionCode::SentLe => "SentLe", - } - } -} - -#[repr(u8)] -#[derive(Clone, PartialEq, Copy)] -#[cfg_attr(test, derive(Debug))] -pub enum NonfungibleConditionCode { - Sent = 0x10, - NotSent = 0x11, -} - -impl NonfungibleConditionCode { - pub fn from_u8(b: u8) -> Option { - match b { - 0x10 => Some(NonfungibleConditionCode::Sent), - 0x11 => Some(NonfungibleConditionCode::NotSent), - _ => None, - } - } +use super::error::ParserError; - pub fn to_str(self) -> &'static str { - match self { - Self::Sent => "Sent", - Self::NotSent => "NotSent", - } - } -} +use super::parser_common::{AssetInfo, C32_ENCODED_ADDRS_LENGTH, STX_DECIMALS, TX_DEPTH_LIMIT}; +use crate::zxformat; +use crate::{bolos::c_zemu_log_stack, parser::value::Value}; +use lexical_core::Number; #[repr(u8)] #[derive(Clone, PartialEq, Copy)] @@ -345,18 +179,18 @@ impl<'a> TransactionPostCondition<'a> { let mut output = ArrayVec::from([0u8; zxformat::MAX_STR_BUFF_LEN]); let amount = self.tokens_amount()?; - let len = zxformat::u64_to_str(output.as_mut(), amount).ok()? as usize; + let len = zxformat::u64_to_str(output.as_mut(), amount).ok()?.len(); unsafe { output.set_len(len); } Some(output) } - pub fn amount_stx_str(&self) -> Option> { + pub fn amount_stx_str(&self) -> Option> { let amount = self.amount_stx()?; - let mut output = ArrayVec::from([0u8; zxformat::MAX_STR_BUFF_LEN]); - let len = - zxformat::fpu64_to_str_check_test(output.as_mut(), amount, STX_DECIMALS).ok()? as usize; + + let mut output = ArrayVec::from([0u8; u64::FORMATTED_SIZE_DECIMAL]); + let len = zxformat::fpu64_to_str(output.as_mut(), amount, STX_DECIMALS).ok()? as usize; unsafe { output.set_len(len); } @@ -423,6 +257,7 @@ impl<'a> TransactionPostCondition<'a> { page_idx: u8, ) -> Result { let index = display_idx % self.num_items(); + if index == 0 { self.write_principal_address(out_key, out_value, page_idx) } else { @@ -443,6 +278,8 @@ impl<'a> TransactionPostCondition<'a> { out_value: &mut [u8], page_idx: u8, ) -> Result { + c_zemu_log_stack("TransactionPostCondition::get_stx_items\x00"); + let mut writer_key = zxformat::Writer::new(out_key); match self { Self::Stx(..) => match display_idx { @@ -477,6 +314,8 @@ impl<'a> TransactionPostCondition<'a> { out_value: &mut [u8], page_idx: u8, ) -> Result { + c_zemu_log_stack("TransactionPostCondition::get_fungible_items\x00"); + let mut writer_key = zxformat::Writer::new(out_key); match self { Self::Fungible(..) => { @@ -526,6 +365,8 @@ impl<'a> TransactionPostCondition<'a> { out_value: &mut [u8], page_idx: u8, ) -> Result { + c_zemu_log_stack("TransactionPostCondition::get_non_fungible_items\x00"); + let mut writer_key = zxformat::Writer::new(out_key); match self { Self::Nonfungible(..) => { @@ -566,6 +407,8 @@ impl<'a> TransactionPostCondition<'a> { #[cfg(test)] mod test { + use crate::parser::StacksAddress; + use super::*; use std::prelude::v1::*; diff --git a/app/rust/src/parser/post_conditions/fungible.rs b/app/rust/src/parser/post_conditions/fungible.rs new file mode 100644 index 00000000..608f8b66 --- /dev/null +++ b/app/rust/src/parser/post_conditions/fungible.rs @@ -0,0 +1,33 @@ +#[repr(u8)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] +pub enum FungibleConditionCode { + SentEq = 0x01, + SentGt = 0x02, + SentGe = 0x03, + SentLt = 0x04, + SentLe = 0x05, +} + +impl FungibleConditionCode { + pub fn from_u8(b: u8) -> Option { + match b { + 0x01 => Some(FungibleConditionCode::SentEq), + 0x02 => Some(FungibleConditionCode::SentGt), + 0x03 => Some(FungibleConditionCode::SentGe), + 0x04 => Some(FungibleConditionCode::SentLt), + 0x05 => Some(FungibleConditionCode::SentLe), + _ => None, + } + } + + pub fn to_str(self) -> &'static str { + match self { + FungibleConditionCode::SentEq => "SentEq", + FungibleConditionCode::SentGt => "SentGt", + FungibleConditionCode::SentGe => "SentGe", + FungibleConditionCode::SentLt => "SentLt", + FungibleConditionCode::SentLe => "SentLe", + } + } +} diff --git a/app/rust/src/parser/post_conditions/non_fungible.rs b/app/rust/src/parser/post_conditions/non_fungible.rs new file mode 100644 index 00000000..ad1e8368 --- /dev/null +++ b/app/rust/src/parser/post_conditions/non_fungible.rs @@ -0,0 +1,24 @@ +#[repr(u8)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] +pub enum NonfungibleConditionCode { + Sent = 0x10, + NotSent = 0x11, +} + +impl NonfungibleConditionCode { + pub fn from_u8(b: u8) -> Option { + match b { + 0x10 => Some(NonfungibleConditionCode::Sent), + 0x11 => Some(NonfungibleConditionCode::NotSent), + _ => None, + } + } + + pub fn to_str(self) -> &'static str { + match self { + Self::Sent => "Sent", + Self::NotSent => "NotSent", + } + } +} diff --git a/app/rust/src/parser/post_conditions/post_condition_principal.rs b/app/rust/src/parser/post_conditions/post_condition_principal.rs new file mode 100644 index 00000000..a0c01797 --- /dev/null +++ b/app/rust/src/parser/post_conditions/post_condition_principal.rs @@ -0,0 +1,121 @@ +use core::convert::TryFrom; + +use arrayvec::ArrayVec; +use nom::{bytes::complete::take, number::complete::le_u8}; + +use crate::parser::{ + ContractName, ParserError, StacksAddress, C32_ENCODED_ADDRS_LENGTH, HASH160_LEN, +}; + +#[repr(u8)] +#[derive(Clone, PartialEq, Copy)] +pub enum PostConditionPrincipalId { + Origin = 0x01, + Standard = 0x02, + Contract = 0x03, +} + +impl TryFrom for PostConditionPrincipalId { + type Error = ParserError; + + fn try_from(value: u8) -> Result { + let id = match value { + 1 => Self::Origin, + 2 => Self::Standard, + 3 => Self::Contract, + _ => return Err(ParserError::UnexpectedValue), + }; + Ok(id) + } +} + +#[repr(C)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub enum PostConditionPrincipal<'a> { + Origin, + Standard(StacksAddress<'a>), + Contract(StacksAddress<'a>, ContractName<'a>), +} + +impl<'a> PostConditionPrincipal<'a> { + #[inline(never)] + pub fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { + let id = le_u8(bytes)?; + let principal_id = PostConditionPrincipalId::try_from(id.1)?; + match principal_id { + PostConditionPrincipalId::Origin => Ok((id.0, PostConditionPrincipal::Origin)), + PostConditionPrincipalId::Standard => { + let addrs = StacksAddress::from_bytes(id.0)?; + Ok((addrs.0, PostConditionPrincipal::Standard(addrs.1))) + } + PostConditionPrincipalId::Contract => { + let addrs = StacksAddress::from_bytes(id.0)?; + let contract_name = ContractName::from_bytes(addrs.0)?; + Ok(( + contract_name.0, + PostConditionPrincipal::Contract(addrs.1, contract_name.1), + )) + } + } + } + + pub fn read_as_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], &[u8], ParserError> { + let (rem, id) = le_u8(bytes)?; + let principal_id = PostConditionPrincipalId::try_from(id)?; + match principal_id { + PostConditionPrincipalId::Origin => Ok((rem, Default::default())), + PostConditionPrincipalId::Standard => { + // we take 20-byte key hash + 1-byte hash_mode + 1-byte principal_id + let (raw, addr) = take(HASH160_LEN + 2usize)(bytes)?; + Ok((raw, addr)) + } + PostConditionPrincipalId::Contract => { + let (rem, _) = StacksAddress::from_bytes(rem)?; + let (_, name) = ContractName::from_bytes(rem)?; + // we take 20-byte key hash + 1-byte hash_mode + + // contract_name len + 1-byte len + 1-byte principal_id + let total_len = HASH160_LEN + name.len() + 3; + let (rem, contract_bytes) = take(total_len)(bytes)?; + Ok((rem, contract_bytes)) + } + } + } + + pub fn is_origin(&self) -> bool { + matches!(self, Self::Origin) + } + + pub fn is_standard(&self) -> bool { + matches!(self, Self::Standard(..)) + } + + pub fn is_contract(&self) -> bool { + matches!(self, Self::Contract(..)) + } + + pub fn origin_address( + ) -> Result, ParserError> { + let mut output: ArrayVec<[_; C32_ENCODED_ADDRS_LENGTH]> = ArrayVec::new(); + output.try_extend_from_slice(b"Origin".as_ref()).unwrap(); + Ok(output) + } + + pub fn get_principal_address( + &self, + ) -> Result, ParserError> { + match self { + Self::Origin => Self::origin_address(), + Self::Standard(ref address) | Self::Contract(ref address, _) => { + address.encoded_address() + } + } + } + + pub fn get_contract_name(&'a self) -> Option<&'a [u8]> { + match self { + Self::Contract(_, name) => Some(name.name()), + _ => None, + } + } +} diff --git a/app/rust/src/parser/spending_condition.rs b/app/rust/src/parser/spending_condition.rs index 341b19cf..93e9b887 100644 --- a/app/rust/src/parser/spending_condition.rs +++ b/app/rust/src/parser/spending_condition.rs @@ -163,7 +163,7 @@ impl<'a> SpendingConditionSigner<'a> { pub fn nonce_str(&self) -> Result, ParserError> { let mut output = ArrayVec::from([0u8; zxformat::MAX_STR_BUFF_LEN]); let nonce = self.nonce()?; - let len = zxformat::u64_to_str(&mut output[..zxformat::MAX_STR_BUFF_LEN], nonce)? as usize; + let len = zxformat::u64_to_str(&mut output[..zxformat::MAX_STR_BUFF_LEN], nonce)?.len(); unsafe { output.set_len(len); } @@ -174,7 +174,7 @@ impl<'a> SpendingConditionSigner<'a> { pub fn fee_str(&self) -> Result, ParserError> { let mut output = ArrayVec::from([0u8; zxformat::MAX_STR_BUFF_LEN]); let fee = self.fee()?; - let len = zxformat::u64_to_str(output.as_mut(), fee)? as usize; + let len = zxformat::u64_to_str(output.as_mut(), fee)?.len(); unsafe { output.set_len(len); } @@ -404,6 +404,7 @@ impl<'a> MultisigSpendingCondition<'a> { fn clear_as_singlesig(&mut self) { // TODO: check if it involves shrinking // the general transaction buffer + // function is not being called anywhere todo!(); } } diff --git a/app/rust/src/parser/structured_msg.rs b/app/rust/src/parser/structured_msg.rs index b5812a9f..9a8601db 100644 --- a/app/rust/src/parser/structured_msg.rs +++ b/app/rust/src/parser/structured_msg.rs @@ -82,13 +82,12 @@ impl<'a> Domain<'a> { let id = value.value_id(); if id == ValueId::UInt { - // wont panic as this was checked by the parser - let chain_id = value.uint().unwrap(); + let chain_id = value.uint().ok_or(ParserError::UnexpectedValue)?; let num = chain_id.numtoa_str(10, &mut buff).as_bytes(); pageString(out_value, num, page_idx) } else { - let string = value.string_ascii().unwrap(); + let string = value.string_ascii().ok_or(ParserError::UnexpectedValue)?; pageString(out_value, string.content(), page_idx) } diff --git a/app/rust/src/parser/transaction.rs b/app/rust/src/parser/transaction.rs index f95a819d..2867d710 100644 --- a/app/rust/src/parser/transaction.rs +++ b/app/rust/src/parser/transaction.rs @@ -2,25 +2,23 @@ use core::fmt::Write; use nom::{ branch::permutation, bytes::complete::take, - combinator::iterator, number::complete::{be_u32, le_u8}, }; -use arrayvec::ArrayVec; - -use crate::parser::{ - error::ParserError, - parser_common::{ - HashMode, SignerId, TransactionVersion, C32_ENCODED_ADDRS_LENGTH, - NUM_SUPPORTED_POST_CONDITIONS, +use crate::{ + bolos::c_zemu_log_stack, + parser::{ + error::ParserError, + parser_common::{HashMode, SignerId, TransactionVersion, C32_ENCODED_ADDRS_LENGTH}, + transaction_auth::TransactionAuth, + transaction_payload::TransactionPayload, }, - post_condition::TransactionPostCondition, - transaction_auth::TransactionAuth, - transaction_payload::TransactionPayload, }; use crate::{check_canary, zxformat}; +use super::PostConditions; + // In multisig transactions the remainder should contain: // 32-byte previous signer post_sig_hash // 1-byte pubkey type @@ -35,33 +33,6 @@ pub enum TransactionAuthFlags { Sponsored = 0x05, } -#[repr(u8)] -#[derive(Clone, PartialEq, Copy)] -#[cfg_attr(test, derive(Debug))] -pub enum TransactionPostConditionMode { - Allow = 0x01, // allow any other changes not specified - Deny = 0x02, // deny any other changes not specified -} - -impl TransactionPostConditionMode { - #[inline(never)] - fn from_u8(v: u8) -> Option { - match v { - 1 => Some(Self::Allow), - 2 => Some(Self::Deny), - _ => None, - } - } - - #[inline(never)] - fn from_bytes(bytes: &[u8]) -> nom::IResult<&[u8], Self, ParserError> { - let mode = le_u8(bytes)?; - let tx_mode = Self::from_u8(mode.1).ok_or(ParserError::UnexpectedError)?; - check_canary!(); - Ok((mode.0, tx_mode)) - } -} - #[repr(u8)] #[derive(Clone, PartialEq, Copy)] #[cfg_attr(test, derive(Debug))] @@ -91,123 +62,6 @@ impl TransactionAnchorMode { } } -#[repr(C)] -#[derive(Clone, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub struct PostConditions<'a> { - pub(crate) conditions: ArrayVec<[&'a [u8]; NUM_SUPPORTED_POST_CONDITIONS]>, - num_items: u8, - current_idx: u8, -} - -impl<'a> PostConditions<'a> { - #[inline(never)] - fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { - let (raw, len) = be_u32::<_, ParserError>(bytes)?; - if len > NUM_SUPPORTED_POST_CONDITIONS as u32 { - return Err(nom::Err::Error(ParserError::ValueOutOfRange)); - } - let mut conditions: ArrayVec<[&'a [u8]; NUM_SUPPORTED_POST_CONDITIONS]> = ArrayVec::new(); - let mut iter = iterator(raw, TransactionPostCondition::read_as_bytes); - iter.take(len as _).enumerate().for_each(|i| { - conditions.push(i.1); - }); - let res = iter.finish()?; - let num_items = Self::get_num_items(conditions.as_ref()); - check_canary!(); - Ok(( - res.0, - Self { - conditions, - num_items, - current_idx: 0, - }, - )) - } - - fn get_num_items(conditions: &[&[u8]]) -> u8 { - conditions - .iter() - .filter_map(|bytes| TransactionPostCondition::from_bytes(bytes).ok()) - .map(|condition| (condition.1).num_items()) - .sum() - } - - pub fn get_postconditions(&self) -> &[&[u8]] { - self.conditions.as_ref() - } - - pub fn num_items(&self) -> u8 { - self.num_items - } - - #[inline(never)] - fn update_postcondition( - &mut self, - total_items: u8, - display_idx: u8, - ) -> Result { - // map display_idx to our range of items - let in_start = total_items - self.num_items; - let idx = self.map_idx(display_idx, in_start, total_items); - - let limit = self.get_current_limit(); - - // get the current postcondition which is used to - // check if it is time to change to the next/previous postconditions in our list - // and if that is not the case, we use it to get its items - let current_condition = self.current_post_condition()?; - - // before continuing we need to check if the current display_idx - // correspond to the current, next or previous postcondition - // if so, update it - if idx >= (limit + current_condition.num_items()) { - self.current_idx += 1; - // this should not happen - if self.current_idx > self.num_items { - return Err(ParserError::UnexpectedError); - } - } else if idx < limit && idx > 0 { - self.current_idx -= 1; - } - Ok(idx) - } - - #[inline(never)] - pub fn get_items( - &mut self, - display_idx: u8, - out_key: &mut [u8], - out_value: &mut [u8], - page_idx: u8, - num_items: u8, - ) -> Result { - let idx = self.update_postcondition(num_items, display_idx)?; - let current_postcondition = self.current_post_condition()?; - current_postcondition.get_items(idx, out_key, out_value, page_idx) - } - - fn map_idx(&self, display_idx: u8, in_start: u8, in_end: u8) -> u8 { - let slope = self.num_items / (in_end - in_start); - slope * (display_idx - in_start) - } - - fn get_current_limit(&self) -> u8 { - let current = self.current_idx as usize; - self.conditions[..current] - .iter() - .filter_map(|bytes| TransactionPostCondition::from_bytes(bytes).ok()) - .map(|condition| (condition.1).num_items()) - .sum() - } - - fn current_post_condition(&self) -> Result { - TransactionPostCondition::from_bytes(self.conditions[self.current_idx as usize]) - .map_err(|_| ParserError::PostConditionFailed) - .map(|res| res.1) - } -} - pub type TxTuple<'a> = ( TransactionVersion, // version number u32, // chainId @@ -219,6 +73,11 @@ pub type TxTuple<'a> = ( impl<'a> From<(&'a [u8], TxTuple<'a>)> for Transaction<'a> { fn from(raw: (&'a [u8], TxTuple<'a>)) -> Self { + let mut remainder = None; + if !raw.0.is_empty() { + remainder = Some(raw.0); + } + Self { version: (raw.1).0, chain_id: (raw.1).1, @@ -228,7 +87,7 @@ impl<'a> From<(&'a [u8], TxTuple<'a>)> for Transaction<'a> { payload: (raw.1).5, // At this point the signer is unknown signer: SignerId::Invalid, - remainder: raw.0, + remainder, } } } @@ -249,94 +108,106 @@ pub struct Transaction<'a> { // with them, we can construct the pre_sig_hash for the current signer // we would ideally verify it, but we can lend such responsability to the application // which has more resources - // If this is not a multisig transaction, this field should be an empty array - pub remainder: &'a [u8], + // If this is not a multisig transaction, this field should be None + pub remainder: Option<&'a [u8]>, } impl<'a> Transaction<'a> { fn update_remainder(&mut self, data: &'a [u8]) { - self.remainder = data; + if !data.is_empty() { + self.remainder = Some(data); + } else { + self.remainder = None; + } } #[inline(never)] pub fn read(&mut self, data: &'a [u8]) -> Result<(), ParserError> { - self.update_remainder(data); - self.read_header()?; - self.read_auth()?; - self.read_transaction_modes()?; - self.read_post_conditions()?; - self.read_payload()?; + c_zemu_log_stack("Transaction::read\x00"); + let rem = self.read_header(data)?; + let rem = self.read_auth(rem)?; + let rem = self.read_transaction_modes(rem)?; + let rem = self.read_post_conditions(rem)?; + let rem = self.read_payload(rem)?; let is_token_transfer = self.payload.is_token_transfer_payload(); let is_standard_auth = self.transaction_auth.is_standard_auth(); if is_token_transfer && !is_standard_auth { + c_zemu_log_stack("Transaction::invalid_token_transfer!\x00"); return Err(ParserError::InvalidTransactionPayload); } // At this point we do not know who the signer is self.signer = SignerId::Invalid; + + self.remainder = None; + + // set the remainder if this is mutltisig + if self.is_multisig() && !rem.is_empty() { + self.update_remainder(rem); + } + Ok(()) } #[inline(never)] - fn read_header(&mut self) -> Result<(), ParserError> { - let (next_data, version) = TransactionVersion::from_bytes(self.remainder) - .map_err(|_| ParserError::UnexpectedValue)?; + fn read_header(&mut self, data: &'a [u8]) -> Result<&'a [u8], ParserError> { + c_zemu_log_stack("Transaction::read_header\x00"); + let (rem, version) = + TransactionVersion::from_bytes(data).map_err(|_| ParserError::UnexpectedValue)?; - let (next_data, chain_id) = - be_u32::<_, ParserError>(next_data).map_err(|_| ParserError::UnexpectedValue)?; + let (rem, chain_id) = + be_u32::<_, ParserError>(rem).map_err(|_| ParserError::UnexpectedValue)?; self.version = version; self.chain_id = chain_id; check_canary!(); - self.update_remainder(next_data); - - Ok(()) + Ok(rem) } #[inline(never)] - fn read_auth(&mut self) -> Result<(), ParserError> { - let (next_data, auth) = TransactionAuth::from_bytes(self.remainder) - .map_err(|_| ParserError::InvalidAuthType)?; + fn read_auth(&mut self, data: &'a [u8]) -> Result<&'a [u8], ParserError> { + c_zemu_log_stack("Transaction::read_auth\x00"); + let (rem, auth) = + TransactionAuth::from_bytes(data).map_err(|_| ParserError::InvalidAuthType)?; self.transaction_auth = auth; - self.update_remainder(next_data); check_canary!(); - Ok(()) + Ok(rem) } #[inline(never)] - fn read_transaction_modes(&mut self) -> Result<(), ParserError> { + fn read_transaction_modes(&mut self, data: &'a [u8]) -> Result<&'a [u8], ParserError> { + c_zemu_log_stack("Transaction::read_transaction_modes\x00"); // two modes are included here, // anchor mode and postcondition mode - let (raw, _) = take::<_, _, ParserError>(2usize)(self.remainder) + let (rem, _) = take::<_, _, ParserError>(2usize)(data) .map_err(|_| ParserError::UnexpectedBufferEnd)?; - let modes = arrayref::array_ref!(self.remainder, 0, 2); + let modes = arrayref::array_ref!(data, 0, 2); self.transaction_modes = modes; - self.update_remainder(raw); check_canary!(); - Ok(()) + Ok(rem) } #[inline(never)] - fn read_post_conditions(&mut self) -> Result<(), ParserError> { - let (raw, conditions) = PostConditions::from_bytes(self.remainder) - .map_err(|_| ParserError::PostConditionFailed)?; + fn read_post_conditions(&mut self, data: &'a [u8]) -> Result<&'a [u8], ParserError> { + c_zemu_log_stack("Transaction::read_post_conditions\x00"); + let (rem, conditions) = + PostConditions::from_bytes(data).map_err(|_| ParserError::PostConditionFailed)?; self.post_conditions = conditions; - self.update_remainder(raw); check_canary!(); - Ok(()) + Ok(rem) } #[inline(never)] - fn read_payload(&mut self) -> Result<(), ParserError> { - let (raw, payload) = TransactionPayload::from_bytes(self.remainder) + fn read_payload(&mut self, data: &'a [u8]) -> Result<&'a [u8], ParserError> { + c_zemu_log_stack("Transaction::read_payload\x00"); + let (rem, payload) = TransactionPayload::from_bytes(data) .map_err(|_| ParserError::InvalidTransactionPayload)?; self.payload = payload; - self.update_remainder(raw); check_canary!(); - Ok(()) + Ok(rem) } pub fn from_bytes(bytes: &'a [u8]) -> Result { @@ -368,9 +239,11 @@ impl<'a> Transaction<'a> { } pub fn num_items(&self) -> Result { + let num_items_post_conditions = self.post_conditions.num_items(); + // nonce + origin + fee-rate + payload + post-conditions 3u8.checked_add(self.payload.num_items()) - .and_then(|res| res.checked_add(self.post_conditions.num_items)) + .and_then(|res| res.checked_add(num_items_post_conditions)) .ok_or(ParserError::ValueOutOfRange) } @@ -381,6 +254,7 @@ impl<'a> Transaction<'a> { out_value: &mut [u8], page_idx: u8, ) -> Result { + c_zemu_log_stack("Transaction::get_origin_items\x00"); let mut writer_key = zxformat::Writer::new(out_key); #[cfg(test)] @@ -434,8 +308,9 @@ impl<'a> Transaction<'a> { out_value: &mut [u8], page_idx: u8, ) -> Result { + c_zemu_log_stack("Transaction::get_other_items\x00"); let num_items = self.num_items()?; - let post_conditions_items = self.post_conditions.num_items; + let post_conditions_items = self.post_conditions.num_items(); if display_idx >= (num_items - post_conditions_items) { if post_conditions_items == 0 { @@ -461,6 +336,7 @@ impl<'a> Transaction<'a> { out_value: &mut [u8], page_idx: u8, ) -> Result { + c_zemu_log_stack("Transaction::get_item\x00"); if display_idx >= self.num_items()? { return Err(ParserError::DisplayIdxOutOfRange); } @@ -527,21 +403,50 @@ impl<'a> Transaction<'a> { if self.signer != SignerId::Invalid { return ParserError::ParserOk; } + c_zemu_log_stack("Invalid transaction signer\x00"); ParserError::InvalidAuthType } // returns a slice of the last block to be used in the presighash calculation pub fn last_transaction_block(&self) -> &[u8] { - unsafe { - let len = - (self.remainder.as_ptr() as usize - self.transaction_modes.as_ptr() as usize) as _; - core::slice::from_raw_parts(self.transaction_modes.as_ptr(), len) + match self.remainder { + Some(remainder) => { + let remainder_ptr = remainder.as_ptr() as usize; + let tx_modes_ptr = self.transaction_modes.as_ptr() as usize; + + unsafe { + let len = remainder_ptr - tx_modes_ptr; + core::slice::from_raw_parts(self.transaction_modes.as_ptr(), len) + } + } + None => { + // If there's no remainder, return everything from transaction_modes to the end of payload + let payload = self.payload.raw_payload(); + unsafe { + let payload_end = payload.as_ptr().add(payload.len()); + let len = payload_end as usize - self.transaction_modes.as_ptr() as usize; + core::slice::from_raw_parts(self.transaction_modes.as_ptr(), len) + } + } } } + // pub fn last_transaction_block(&self) -> Option<&[u8]> { + // self.remainder.map(|remainder| { + // let remainder_ptr = remainder.as_ptr() as usize; + // let tx_modes_ptr = self.transaction_modes.as_ptr() as usize; + // + // unsafe { + // let len = remainder_ptr - tx_modes_ptr; + // core::slice::from_raw_parts(self.transaction_modes.as_ptr(), len) + // } + // }) + // } pub fn previous_signer_data(&self) -> Option<&[u8]> { - if self.is_multisig() && self.remainder.len() >= MULTISIG_PREVIOUS_SIGNER_DATA_LEN { - return Some(&self.remainder[..MULTISIG_PREVIOUS_SIGNER_DATA_LEN]); + let remainder = self.remainder?; + + if self.is_multisig() && remainder.len() >= MULTISIG_PREVIOUS_SIGNER_DATA_LEN { + return Some(&remainder[..MULTISIG_PREVIOUS_SIGNER_DATA_LEN]); } None } diff --git a/app/rust/src/parser/transaction_payload.rs b/app/rust/src/parser/transaction_payload.rs index 3e85613a..15df6cfb 100644 --- a/app/rust/src/parser/transaction_payload.rs +++ b/app/rust/src/parser/transaction_payload.rs @@ -1,604 +1,21 @@ -use core::fmt::Write; -use nom::{ - branch::alt, - bytes::complete::{tag, take}, - combinator::{flat_map, map}, - number::complete::{be_u32, be_u64, be_u8, le_u8}, - sequence::tuple, +mod arguments; +mod contract_call; +mod smart_contract; +mod token_transfer; +mod versioned_contract; + +use nom::number::complete::le_u8; + +use self::{ + contract_call::{TransactionContractCall, CONTRACT_CALL_BASE_ITEMS}, + smart_contract::TransactionSmartContract, + token_transfer::StxTokenTransfer, + versioned_contract::VersionedSmartContract, }; -use arrayvec::ArrayVec; -use numtoa::NumToA; - -use super::{ - utils::ApduPanic, ClarityName, ContractName, PrincipalData, StacksAddress, - C32_ENCODED_ADDRS_LENGTH, HASH160_LEN, TX_DEPTH_LIMIT, -}; +use super::{ContractName, C32_ENCODED_ADDRS_LENGTH}; use crate::parser::error::ParserError; -use crate::parser::c32; - -use super::value::{Value, ValueId}; -use crate::{check_canary, is_expert_mode, zxformat}; - -// The number of contract call arguments we can handle. -// this can be adjusted, but keep in mind that higher values could -// hit stack overflows issues. -pub const MAX_NUM_ARGS: u32 = 10; - -// The items in contract_call transactions are -// contract_address, contract_name and function_name -pub const CONTRACT_CALL_BASE_ITEMS: u8 = 3; - -pub const MAX_STRING_ASCII_TO_SHOW: usize = 60; - -#[repr(u8)] -#[derive(Clone, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub enum TokenTranferPrincipal { - Standard = 0x05, - Contract = 0x06, -} - -impl TokenTranferPrincipal { - fn from_u8(v: u8) -> Result { - match v { - 5 => Ok(Self::Standard), - 6 => Ok(Self::Contract), - _ => Err(ParserError::InvalidTokenTransferPrincipal), - } - } -} - -#[repr(C)] -#[derive(Clone, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub struct StxTokenTransfer<'a>(&'a [u8]); - -impl<'a> StxTokenTransfer<'a> { - #[inline(never)] - fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { - let id = le_u8(bytes)?; - let (raw, _) = match TokenTranferPrincipal::from_u8(id.1)? { - TokenTranferPrincipal::Standard => PrincipalData::standard_from_bytes(id.0)?, - TokenTranferPrincipal::Contract => PrincipalData::contract_principal_from_bytes(id.0)?, - }; - // Besides principal we take 34-bytes being the MEMO message + 8-bytes amount of stx - let len = bytes.len() - raw.len() + 34 + 8; - let (raw, data) = take(len)(bytes)?; - Ok((raw, Self(data))) - } - - pub fn memo(&self) -> &[u8] { - let at = self.0.len() - 34; - // safe to unwrap as parser checked for proper len - self.0.get(at..).apdu_unwrap() - } - - pub fn amount(&self) -> Result { - let at = self.0.len() - 34 - 8; - let amount = self.0.get(at..).ok_or(ParserError::NoData)?; - be_u64::<_, ParserError>(amount) - .map(|res| res.1) - .map_err(|_| ParserError::UnexpectedBufferEnd) - } - - pub fn raw_address(&self) -> &[u8] { - // Skips the principal-id and hash_mode - // is valid as this was check by the parser - // safe to unwrap as this was checked at parsing - self.0.get(2..22).apdu_unwrap() - } - - pub fn encoded_address( - &self, - ) -> Result, ParserError> { - // Skips the principal-id at [0] and uses hash_mode and the follow 20-bytes - let version = self.0.get(1).ok_or(ParserError::NoData)?; - c32::c32_address( - *version, - self.0.get(2..22).ok_or(ParserError::InvalidAddress)?, - ) - } - - pub fn amount_stx(&self) -> Result, ParserError> { - let mut output = ArrayVec::from([0u8; zxformat::MAX_STR_BUFF_LEN]); - let amount = self.amount()?; - let len = zxformat::u64_to_str(output.as_mut(), amount)? as usize; - unsafe { - output.set_len(len); - } - check_canary!(); - Ok(output) - } - - fn get_token_transfer_items( - &self, - display_idx: u8, - out_key: &mut [u8], - out_value: &mut [u8], - page_idx: u8, - ) -> Result { - let mut writer_key = zxformat::Writer::new(out_key); - - match display_idx { - // Fomatting the amount in stx - 0 => { - writer_key - .write_str("Amount uSTX") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - let amount = self.amount_stx()?; - check_canary!(); - zxformat::pageString(out_value, amount.as_ref(), page_idx) - } - // Recipient address - 1 => { - writer_key - .write_str("To") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - let recipient = self.encoded_address()?; - check_canary!(); - zxformat::pageString(out_value, recipient.as_ref(), page_idx) - } - 2 => { - writer_key - .write_str("Memo") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - check_canary!(); - zxformat::pageString(out_value, self.memo(), page_idx) - } - _ => Err(ParserError::DisplayIdxOutOfRange), - } - } -} - -#[repr(C)] -#[derive(Clone, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub struct Arguments<'a>(&'a [u8]); - -impl<'a> Arguments<'a> { - #[inline(never)] - fn from_bytes(bytes: &'a [u8]) -> Result<(&[u8], Self), nom::Err> { - check_canary!(); - - let (_, num_args) = be_u32::<_, ParserError>(bytes)?; - - if num_args > MAX_NUM_ARGS && !is_expert_mode() { - return Err(ParserError::InvalidTransactionPayload.into()); - } - let (raw, args) = take(bytes.len())(bytes)?; - - // take all bytes as there must not be more data after the arguments - // returning an empty remain data. NOTE: we use take(bytes.len()), to offset - // the remainder bytes as it is used to set the tx.remainder, which is use - // to calculate the last_tx_block during the transaction signing process - Ok((raw, Self(args))) - } - - pub fn num_args(&self) -> Result { - be_u32::<_, ParserError>(self.0) - .map(|res| res.1) - .map_err(|_| ParserError::UnexpectedError) - } - - pub fn argument_at(&self, at: usize) -> Result, ParserError> { - check_canary!(); - - let mut idx = 0; - let num_args = self.num_args()?; - - // skip the first 4-bytes - let mut leftover = &self.0[4..]; - - while idx < num_args as usize { - let (bytes, value) = Value::from_bytes::(leftover) - .map_err(|_| ParserError::InvalidArgumentId)?; - - leftover = bytes; - if idx == at { - return Ok(value); - } - idx += 1; - } - Err(ParserError::DisplayIdxOutOfRange) - } -} - -/// A transaction that calls into a smart contract -#[repr(C)] -#[derive(Clone, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub struct TransactionContractCall<'a>(&'a [u8]); - -impl<'a> TransactionContractCall<'a> { - #[inline(never)] - fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { - let (raw, _) = StacksAddress::from_bytes(bytes)?; - // get contract name and function name. - let (raw2, _) = tuple((ContractName::from_bytes, ClarityName::from_bytes))(raw)?; - let (leftover, _) = Arguments::from_bytes(raw2)?; - let len = bytes.len() - leftover.len(); - let (_, data) = take(len)(bytes)?; - check_canary!(); - Ok((leftover, Self(data))) - } - - pub fn contract_name(&'a self) -> Result, ParserError> { - let at = HASH160_LEN + 1; - ContractName::from_bytes(&self.0[at..]) - .map(|(_, name)| name) - .map_err(|e| e.into()) - } - - pub fn function_name(&self) -> Result<&[u8], ParserError> { - ContractName::from_bytes(&self.0[(HASH160_LEN + 1)..]) - .and_then(|b| ClarityName::from_bytes(b.0)) - .map(|res| (res.1).0) - .map_err(|_| ParserError::UnexpectedError) - } - - pub fn function_args(&self) -> Result, ParserError> { - ContractName::from_bytes(&self.0[(HASH160_LEN + 1)..]) - .and_then(|b| ClarityName::from_bytes(b.0)) - .and_then(|c| Arguments::from_bytes(c.0)) - .map(|res| res.1) - .map_err(|_| ParserError::InvalidArgumentId) - } - - pub fn num_args(&self) -> Result { - self.function_args().and_then(|args| args.num_args()) - } - - #[inline(never)] - pub fn contract_address( - &self, - ) -> Result, ParserError> { - let version = self.0[0]; - c32::c32_address(version, &self.0[1..21]) - } - - // change label if it is a stacking contract call - fn label_stacking_value(&self, key: &mut [u8]) -> Result<(), ParserError> { - let addr = self.contract_address()?; - let addr = addr.as_ref(); - let contract_name = self.contract_name()?; - if (addr == "SP000000000000000000002Q6VF78".as_bytes() - || addr == "ST000000000000000000002AMW42H".as_bytes()) - && contract_name.name() == "pox".as_bytes() - { - let name = self.function_name()?; - if name == "stack-stx".as_bytes() { - key.iter_mut().for_each(|v| *v = 0); - let mut writer = zxformat::Writer::new(key); - writer - .write_str("stacked uSTX") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - } else if name == "delegate-stx".as_bytes() { - key.iter_mut().for_each(|v| *v = 0); - let mut writer = zxformat::Writer::new(key); - writer - .write_str("delegated uSTX") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - } - } - Ok(()) - } - - fn get_contract_call_args( - &'a self, - display_idx: u8, - out_key: &mut [u8], - out_value: &mut [u8], - page_idx: u8, - ) -> Result { - if display_idx < CONTRACT_CALL_BASE_ITEMS { - return Err(ParserError::DisplayIdxOutOfRange); - } - let arg_num = display_idx - CONTRACT_CALL_BASE_ITEMS; - - let args = self.function_args()?; - - let value = args.argument_at(arg_num as _)?; - - { - let mut writer_key = zxformat::Writer::new(out_key); - let mut arg_num_buff = [0u8; 3]; - let arg_num_str = arg_num.numtoa_str(10, &mut arg_num_buff); - - writer_key - .write_str("arg") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - writer_key - .write_str(arg_num_str) - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - } - - // return the value content including the valueID - let payload = value.payload(); - - match value.value_id() { - ValueId::Int => { - let value = value.int().ok_or(ParserError::UnexpectedError)?; - let mut buff = [0u8; 39]; - - zxformat::pageString( - out_value, - value.numtoa_str(10, &mut buff).as_bytes(), - page_idx, - ) - } - ValueId::UInt => { - let value = value.uint().ok_or(ParserError::UnexpectedError)?; - let mut buff = [0u8; 39]; - - if arg_num == 0 { - self.label_stacking_value(out_key)?; - } - - zxformat::pageString( - out_value, - value.numtoa_str(10, &mut buff).as_bytes(), - page_idx, - ) - } - ValueId::BoolTrue => { - zxformat::pageString(out_value, "is bool: true".as_bytes(), page_idx) - } - ValueId::BoolFalse => { - zxformat::pageString(out_value, "is bool: false".as_bytes(), page_idx) - } - ValueId::OptionalNone => { - zxformat::pageString(out_value, "is Option: None".as_bytes(), page_idx) - } - ValueId::OptionalSome => { - zxformat::pageString(out_value, "is Option: Some".as_bytes(), page_idx) - } - ValueId::ResponseOk => { - zxformat::pageString(out_value, "is Result: Ok".as_bytes(), page_idx) - } - ValueId::ResponseErr => { - zxformat::pageString(out_value, "is Result: Err".as_bytes(), page_idx) - } - ValueId::StandardPrincipal => { - let (_, principal) = PrincipalData::standard_from_bytes(payload)?; - let address = principal.encoded_address()?; - zxformat::pageString(out_value, &address[0..address.len()], page_idx) - } - ValueId::ContractPrincipal => { - // holds principal_encoded address + '.' + contract_name - let mut data = [0; C32_ENCODED_ADDRS_LENGTH + ClarityName::MAX_LEN as usize + 1]; - - let (_, principal) = PrincipalData::contract_principal_from_bytes(payload)?; - let address = principal.encoded_address()?; - - // should not fail as this was parsed in previous step - let contract_name = principal.contract_name().apdu_unwrap(); - - data.get_mut(..address.len()) - .apdu_unwrap() - .copy_from_slice(&address[0..address.len()]); - - data[address.len()] = b'.'; - let len = address.len() + 1; - - // wont panic as we reserved enough space. - data.get_mut(len..len + contract_name.len()) - .apdu_unwrap() - .copy_from_slice(contract_name.name()); - - zxformat::pageString(out_value, &data[0..len + contract_name.len()], page_idx) - } - ValueId::Buffer => zxformat::pageString(out_value, "is Buffer".as_bytes(), page_idx), - ValueId::List => zxformat::pageString(out_value, "is List".as_bytes(), page_idx), - ValueId::Tuple => zxformat::pageString(out_value, "is Tuple".as_bytes(), page_idx), - ValueId::StringAscii => { - // 4 bytes encode the length of the string - let len = if payload.len() - 4 > MAX_STRING_ASCII_TO_SHOW { - MAX_STRING_ASCII_TO_SHOW - } else { - payload.len() - }; - zxformat::pageString( - out_value, - &payload[4..len], // omit the first 4-bytes as they are the string length - page_idx, - ) - } - - ValueId::StringUtf8 => { - zxformat::pageString(out_value, "is StringUtf8".as_bytes(), page_idx) - } - } - } - - pub fn num_items(&self) -> Result { - // contract-address, contract-name, function-name - // + the number of arguments - let num_args = self.num_args()? as u8; - num_args - .checked_add(CONTRACT_CALL_BASE_ITEMS) - .ok_or(ParserError::ValueOutOfRange) - } - - fn get_base_items( - &self, - display_idx: u8, - out_key: &mut [u8], - out_value: &mut [u8], - page_idx: u8, - ) -> Result { - if display_idx > CONTRACT_CALL_BASE_ITEMS { - return Err(ParserError::DisplayIdxOutOfRange); - } - let mut writer_key = zxformat::Writer::new(out_key); - match display_idx { - // Contract-address - 0 => { - writer_key - .write_str("Contract address") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - let address = self.contract_address()?; - check_canary!(); - zxformat::pageString(out_value, address.as_ref(), page_idx) - } - // Contract.name - 1 => { - writer_key - .write_str("Contract name") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - let name = self.contract_name()?; - check_canary!(); - zxformat::pageString(out_value, name.name(), page_idx) - } - // Function-name - 2 => { - writer_key - .write_str("Function name") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - let name = self.function_name()?; - check_canary!(); - zxformat::pageString(out_value, name, page_idx) - } - _ => Err(ParserError::DisplayIdxOutOfRange), - } - } - - fn get_contract_call_items( - &self, - display_idx: u8, - out_key: &mut [u8], - out_value: &mut [u8], - page_idx: u8, - ) -> Result { - // display_idx was already normalize - if display_idx < CONTRACT_CALL_BASE_ITEMS { - self.get_base_items(display_idx, out_key, out_value, page_idx) - } else { - self.get_contract_call_args(display_idx, out_key, out_value, page_idx) - } - } -} - -/// A transaction that deploys a versioned smart contract -#[repr(C)] -#[derive(Clone, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub struct VersionedSmartContract<'a>(&'a [u8]); - -impl<'a> VersionedSmartContract<'a> { - #[inline(never)] - fn from_bytes(input: &'a [u8]) -> Result<(&[u8], Self), ParserError> { - check_canary!(); - - // clarity version - // len prefixed contract name - // len prefixed contract code - let parse_tag = alt((tag(&[0x01]), tag(&[0x02]))); - let parse_length_1_byte = map(be_u8, |length| std::cmp::min(length, 128u8) as usize); - let parse_length_4_bytes = flat_map(be_u32, take); - - let mut parser = tuple(( - parse_tag, - flat_map(parse_length_1_byte, take), - parse_length_4_bytes, - )); - let (_, (_, name, code)) = parser(input)?; - - // 1-byte tag, 1-byte name_len, name, 4-byte code_len, code - let total_length = 1 + 1 + name.len() + 4 + code.len(); - let (rem, res) = take(total_length)(input)?; - - Ok((rem, Self(res))) - } - - pub fn contract_name(&'a self) -> Result, ParserError> { - // skip the tag. safe ecause this was checked during parsing - ContractName::from_bytes(&self.0[1..]) - .map(|(_, res)| res) - .map_err(|e| e.into()) - } - - #[inline(never)] - fn get_contract_items( - &self, - display_idx: u8, - out_key: &mut [u8], - out_value: &mut [u8], - page_idx: u8, - ) -> Result { - let mut writer_key = zxformat::Writer::new(out_key); - - match display_idx { - 0 => { - writer_key - .write_str("Contract Name") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - check_canary!(); - let name = self.contract_name()?; - zxformat::pageString(out_value, name.name(), page_idx) - } - _ => Err(ParserError::ValueOutOfRange), - } - } -} - -/// A transaction that instantiates a smart contract -#[repr(C)] -#[derive(Clone, PartialEq)] -#[cfg_attr(test, derive(Debug))] -pub struct TransactionSmartContract<'a>(&'a [u8]); - -impl<'a> TransactionSmartContract<'a> { - #[inline(never)] - fn from_bytes(bytes: &'a [u8]) -> Result<(&[u8], Self), ParserError> { - check_canary!(); - - // len prefixed contract name - // len prefixed contract code - let parse_length_1_byte = map(be_u8, |length| std::cmp::min(length, 128u8) as usize); - let parse_length_4_bytes = flat_map(be_u32, take); - - let mut parser = tuple((flat_map(parse_length_1_byte, take), parse_length_4_bytes)); - let (_, (name, code)) = parser(bytes)?; - - // 1-byte name_len, name, 4-byte code_len, code - let total_length = 1 + name.len() + 4 + code.len(); - let (rem, res) = take(total_length)(bytes)?; - - Ok((rem, Self(res))) - } - - pub fn contract_name(&'a self) -> Result, ParserError> { - ContractName::from_bytes(self.0) - .map(|(_, res)| res) - .map_err(|e| e.into()) - } - - #[inline(never)] - fn get_contract_items( - &self, - display_idx: u8, - out_key: &mut [u8], - out_value: &mut [u8], - page_idx: u8, - ) -> Result { - let mut writer_key = zxformat::Writer::new(out_key); - - match display_idx { - 0 => { - writer_key - .write_str("Contract Name") - .map_err(|_| ParserError::UnexpectedBufferEnd)?; - check_canary!(); - let name = self.contract_name()?; - zxformat::pageString(out_value, name.name(), page_idx) - } - _ => Err(ParserError::ValueOutOfRange), - } - } -} - #[repr(u8)] #[derive(Clone, PartialEq, Copy)] #[cfg_attr(test, derive(Debug))] @@ -753,6 +170,15 @@ impl<'a> TransactionPayload<'a> { } } } + + pub fn raw_payload(&self) -> &'a [u8] { + match self { + Self::TokenTransfer(token) => token.raw_data(), + Self::SmartContract(contract) => contract.raw_data(), + Self::ContractCall(call) => call.raw_data(), + Self::VersionedSmartContract(deploy) => deploy.raw_data(), + } + } } #[cfg(test)] diff --git a/app/rust/src/parser/transaction_payload/arguments.rs b/app/rust/src/parser/transaction_payload/arguments.rs new file mode 100644 index 00000000..e4375ec9 --- /dev/null +++ b/app/rust/src/parser/transaction_payload/arguments.rs @@ -0,0 +1,75 @@ +use nom::{bytes::complete::take, number::complete::be_u32}; + +use crate::{ + check_canary, is_expert_mode, + parser::{ParserError, Value, TX_DEPTH_LIMIT}, +}; + +// The number of contract call arguments we can handle. +// this can be adjusted, but keep in mind that higher values could +// hit stack overflows issues. +pub const MAX_NUM_ARGS: u32 = 10; + +#[repr(C)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub struct Arguments<'a>(&'a [u8]); + +impl<'a> Arguments<'a> { + #[inline(never)] + pub fn from_bytes(bytes: &'a [u8]) -> Result<(&[u8], Self), nom::Err> { + check_canary!(); + + let (mut rem, num_args) = be_u32::<_, ParserError>(bytes)?; + + if num_args > MAX_NUM_ARGS && !is_expert_mode() { + return Err(ParserError::InvalidTransactionPayload.into()); + } + + // Parse all arguments so we can be sure that at runtime when each + // argument is retrieved it does not crashes + for _ in 0..num_args { + let (leftover, _) = Value::from_bytes::(rem) + .map_err(|_| ParserError::InvalidArgumentId)?; + + rem = leftover; + } + + let len = bytes.len() - rem.len(); + let (rem, args) = take(len)(bytes)?; + + // take all bytes as there must not be more data after the arguments + // returning an empty remain data. NOTE: we use take(bytes.len()), to offset + // the remainder bytes as it is used to set the tx.remainder, which is use + // to calculate the last_tx_block during the transaction signing process + Ok((rem, Self(args))) + } + + pub fn num_args(&self) -> Result { + be_u32::<_, ParserError>(self.0) + .map(|res| res.1) + .map_err(|_| ParserError::UnexpectedError) + } + + pub fn argument_at(&self, at: usize) -> Result, ParserError> { + check_canary!(); + + let mut idx = 0; + let num_args = self.num_args()?; + + // skip the first 4-bytes + let mut leftover = &self.0[4..]; + + while idx < num_args as usize { + let (bytes, value) = Value::from_bytes::(leftover) + .map_err(|_| ParserError::InvalidArgumentId)?; + + leftover = bytes; + if idx == at { + return Ok(value); + } + idx += 1; + } + Err(ParserError::DisplayIdxOutOfRange) + } +} diff --git a/app/rust/src/parser/transaction_payload/contract_call.rs b/app/rust/src/parser/transaction_payload/contract_call.rs new file mode 100644 index 00000000..e8aa51dc --- /dev/null +++ b/app/rust/src/parser/transaction_payload/contract_call.rs @@ -0,0 +1,302 @@ +use core::fmt::Write; + +use nom::{bytes::complete::take, sequence::tuple}; +use numtoa::NumToA; + +use crate::{ + check_canary, + parser::{ + c32, transaction_payload::arguments::Arguments, ApduPanic, ClarityName, ContractName, + ParserError, PrincipalData, StacksAddress, ValueId, C32_ENCODED_ADDRS_LENGTH, HASH160_LEN, + }, + zxformat, +}; + +pub const MAX_STRING_ASCII_TO_SHOW: usize = 60; +// The items in contract_call transactions are +// contract_address, contract_name and function_name +pub const CONTRACT_CALL_BASE_ITEMS: u8 = 3; +/// A transaction that calls into a smart contract +#[repr(C)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub struct TransactionContractCall<'a>(&'a [u8]); + +impl<'a> TransactionContractCall<'a> { + #[inline(never)] + pub fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { + let (rem, _) = StacksAddress::from_bytes(bytes)?; + // get contract name and function name. + let (rem, _) = tuple(( + ContractName::from_bytes, + ClarityName::from_bytes, + Arguments::from_bytes, + ))(rem)?; + + let len = bytes.len() - rem.len(); + let (rem, data) = take(len)(bytes)?; + + check_canary!(); + + Ok((rem, Self(data))) + } + + pub fn contract_name(&'a self) -> Result, ParserError> { + let at = HASH160_LEN + 1; + ContractName::from_bytes(&self.0[at..]) + .map(|(_, name)| name) + .map_err(|e| e.into()) + } + + pub fn function_name(&self) -> Result<&[u8], ParserError> { + ContractName::from_bytes(&self.0[(HASH160_LEN + 1)..]) + .and_then(|b| ClarityName::from_bytes(b.0)) + .map(|res| (res.1).0) + .map_err(|_| ParserError::UnexpectedError) + } + + pub fn function_args(&self) -> Result, ParserError> { + ContractName::from_bytes(&self.0[(HASH160_LEN + 1)..]) + .and_then(|b| ClarityName::from_bytes(b.0)) + .and_then(|c| Arguments::from_bytes(c.0)) + .map(|res| res.1) + .map_err(|_| ParserError::InvalidArgumentId) + } + + pub fn num_args(&self) -> Result { + self.function_args().and_then(|args| args.num_args()) + } + + #[inline(never)] + pub fn contract_address( + &self, + ) -> Result, ParserError> { + let version = self.0[0]; + c32::c32_address(version, &self.0[1..21]) + } + + // change label if it is a stacking contract call + fn label_stacking_value(&self, key: &mut [u8]) -> Result<(), ParserError> { + let addr = self.contract_address()?; + let addr = addr.as_ref(); + let contract_name = self.contract_name()?; + if (addr == "SP000000000000000000002Q6VF78".as_bytes() + || addr == "ST000000000000000000002AMW42H".as_bytes()) + && contract_name.name() == "pox".as_bytes() + { + let name = self.function_name()?; + if name == "stack-stx".as_bytes() { + key.iter_mut().for_each(|v| *v = 0); + let mut writer = zxformat::Writer::new(key); + writer + .write_str("stacked uSTX") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + } else if name == "delegate-stx".as_bytes() { + key.iter_mut().for_each(|v| *v = 0); + let mut writer = zxformat::Writer::new(key); + writer + .write_str("delegated uSTX") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + } + } + Ok(()) + } + + fn get_contract_call_args( + &'a self, + display_idx: u8, + out_key: &mut [u8], + out_value: &mut [u8], + page_idx: u8, + ) -> Result { + if display_idx < CONTRACT_CALL_BASE_ITEMS { + return Err(ParserError::DisplayIdxOutOfRange); + } + let arg_num = display_idx - CONTRACT_CALL_BASE_ITEMS; + + let args = self.function_args()?; + + let value = args.argument_at(arg_num as _)?; + + { + let mut writer_key = zxformat::Writer::new(out_key); + let mut arg_num_buff = [0u8; 3]; + let arg_num_str = arg_num.numtoa_str(10, &mut arg_num_buff); + + writer_key + .write_str("arg") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + writer_key + .write_str(arg_num_str) + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + } + + // return the value content including the valueID + let payload = value.payload(); + + match value.value_id() { + ValueId::Int => { + let value = value.int().ok_or(ParserError::UnexpectedError)?; + let mut buff = [0u8; 39]; + + zxformat::pageString( + out_value, + value.numtoa_str(10, &mut buff).as_bytes(), + page_idx, + ) + } + ValueId::UInt => { + let value = value.uint().ok_or(ParserError::UnexpectedError)?; + let mut buff = [0u8; 39]; + + if arg_num == 0 { + self.label_stacking_value(out_key)?; + } + + zxformat::pageString( + out_value, + value.numtoa_str(10, &mut buff).as_bytes(), + page_idx, + ) + } + ValueId::BoolTrue => { + zxformat::pageString(out_value, "is bool: true".as_bytes(), page_idx) + } + ValueId::BoolFalse => { + zxformat::pageString(out_value, "is bool: false".as_bytes(), page_idx) + } + ValueId::OptionalNone => { + zxformat::pageString(out_value, "is Option: None".as_bytes(), page_idx) + } + ValueId::OptionalSome => { + zxformat::pageString(out_value, "is Option: Some".as_bytes(), page_idx) + } + ValueId::ResponseOk => { + zxformat::pageString(out_value, "is Result: Ok".as_bytes(), page_idx) + } + ValueId::ResponseErr => { + zxformat::pageString(out_value, "is Result: Err".as_bytes(), page_idx) + } + ValueId::StandardPrincipal => { + let (_, principal) = PrincipalData::standard_from_bytes(payload)?; + let address = principal.encoded_address()?; + zxformat::pageString(out_value, &address[0..address.len()], page_idx) + } + ValueId::ContractPrincipal => { + // holds principal_encoded address + '.' + contract_name + let mut data = [0; C32_ENCODED_ADDRS_LENGTH + ClarityName::MAX_LEN as usize + 1]; + + let (_, principal) = PrincipalData::contract_principal_from_bytes(payload)?; + let address = principal.encoded_address()?; + + // should not fail as this was parsed in previous step + let contract_name = principal.contract_name().apdu_unwrap(); + + data.get_mut(..address.len()) + .apdu_unwrap() + .copy_from_slice(&address[0..address.len()]); + + data[address.len()] = b'.'; + let len = address.len() + 1; + + // wont panic as we reserved enough space. + data.get_mut(len..len + contract_name.len()) + .apdu_unwrap() + .copy_from_slice(contract_name.name()); + + zxformat::pageString(out_value, &data[0..len + contract_name.len()], page_idx) + } + ValueId::Buffer => zxformat::pageString(out_value, "is Buffer".as_bytes(), page_idx), + ValueId::List => zxformat::pageString(out_value, "is List".as_bytes(), page_idx), + ValueId::Tuple => zxformat::pageString(out_value, "is Tuple".as_bytes(), page_idx), + ValueId::StringAscii => { + // 4 bytes encode the length of the string + let len = if payload.len() - 4 > MAX_STRING_ASCII_TO_SHOW { + MAX_STRING_ASCII_TO_SHOW + } else { + payload.len() + }; + zxformat::pageString( + out_value, + &payload[4..len], // omit the first 4-bytes as they are the string length + page_idx, + ) + } + + ValueId::StringUtf8 => { + zxformat::pageString(out_value, "is StringUtf8".as_bytes(), page_idx) + } + } + } + + pub fn num_items(&self) -> Result { + // contract-address, contract-name, function-name + // + the number of arguments + let num_args = self.num_args()? as u8; + num_args + .checked_add(CONTRACT_CALL_BASE_ITEMS) + .ok_or(ParserError::ValueOutOfRange) + } + + fn get_base_items( + &self, + display_idx: u8, + out_key: &mut [u8], + out_value: &mut [u8], + page_idx: u8, + ) -> Result { + if display_idx > CONTRACT_CALL_BASE_ITEMS { + return Err(ParserError::DisplayIdxOutOfRange); + } + let mut writer_key = zxformat::Writer::new(out_key); + match display_idx { + // Contract-address + 0 => { + writer_key + .write_str("Contract address") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + let address = self.contract_address()?; + check_canary!(); + zxformat::pageString(out_value, address.as_ref(), page_idx) + } + // Contract.name + 1 => { + writer_key + .write_str("Contract name") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + let name = self.contract_name()?; + check_canary!(); + zxformat::pageString(out_value, name.name(), page_idx) + } + // Function-name + 2 => { + writer_key + .write_str("Function name") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + let name = self.function_name()?; + check_canary!(); + zxformat::pageString(out_value, name, page_idx) + } + _ => Err(ParserError::DisplayIdxOutOfRange), + } + } + + pub fn get_contract_call_items( + &self, + display_idx: u8, + out_key: &mut [u8], + out_value: &mut [u8], + page_idx: u8, + ) -> Result { + // display_idx was already normalize + if display_idx < CONTRACT_CALL_BASE_ITEMS { + self.get_base_items(display_idx, out_key, out_value, page_idx) + } else { + self.get_contract_call_args(display_idx, out_key, out_value, page_idx) + } + } + + pub fn raw_data(&self) -> &'a [u8] { + self.0 + } +} diff --git a/app/rust/src/parser/transaction_payload/smart_contract.rs b/app/rust/src/parser/transaction_payload/smart_contract.rs new file mode 100644 index 00000000..c04ea40f --- /dev/null +++ b/app/rust/src/parser/transaction_payload/smart_contract.rs @@ -0,0 +1,74 @@ +use core::fmt::Write; + +use nom::{ + bytes::complete::take, + combinator::{flat_map, map}, + number::complete::{be_u32, be_u8}, + sequence::tuple, +}; + +use crate::{ + check_canary, + parser::{ContractName, ParserError}, + zxformat, +}; + +/// A transaction that instantiates a smart contract +#[repr(C)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub struct TransactionSmartContract<'a>(&'a [u8]); + +impl<'a> TransactionSmartContract<'a> { + #[inline(never)] + pub fn from_bytes(bytes: &'a [u8]) -> Result<(&[u8], Self), ParserError> { + check_canary!(); + + // len prefixed contract name + // len prefixed contract code + let parse_length_1_byte = map(be_u8, |length| std::cmp::min(length, 128u8) as usize); + let parse_length_4_bytes = flat_map(be_u32, take); + + let mut parser = tuple((flat_map(parse_length_1_byte, take), parse_length_4_bytes)); + let (_, (name, code)) = parser(bytes)?; + + // 1-byte name_len, name, 4-byte code_len, code + let total_length = 1 + name.len() + 4 + code.len(); + let (rem, res) = take(total_length)(bytes)?; + + Ok((rem, Self(res))) + } + + pub fn contract_name(&'a self) -> Result, ParserError> { + ContractName::from_bytes(self.0) + .map(|(_, res)| res) + .map_err(|e| e.into()) + } + + #[inline(never)] + pub fn get_contract_items( + &self, + display_idx: u8, + out_key: &mut [u8], + out_value: &mut [u8], + page_idx: u8, + ) -> Result { + let mut writer_key = zxformat::Writer::new(out_key); + + match display_idx { + 0 => { + writer_key + .write_str("Contract Name") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + check_canary!(); + let name = self.contract_name()?; + zxformat::pageString(out_value, name.name(), page_idx) + } + _ => Err(ParserError::ValueOutOfRange), + } + } + + pub fn raw_data(&self) -> &'a [u8] { + self.0 + } +} diff --git a/app/rust/src/parser/transaction_payload/token_transfer.rs b/app/rust/src/parser/transaction_payload/token_transfer.rs new file mode 100644 index 00000000..83e0d3b2 --- /dev/null +++ b/app/rust/src/parser/transaction_payload/token_transfer.rs @@ -0,0 +1,139 @@ +use core::fmt::Write; + +use arrayvec::ArrayVec; +use nom::{ + bytes::complete::take, + number::complete::{be_u64, le_u8}, +}; + +use crate::{ + check_canary, + parser::{ + c32, ApduPanic, ParserError, PrincipalData, AMOUNT_LEN, C32_ENCODED_ADDRS_LENGTH, MEMO_LEN, + }, + zxformat, +}; + +#[repr(u8)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub enum TokenTranferPrincipal { + Standard = 0x05, + Contract = 0x06, +} + +impl TokenTranferPrincipal { + fn from_u8(v: u8) -> Result { + match v { + 5 => Ok(Self::Standard), + 6 => Ok(Self::Contract), + _ => Err(ParserError::InvalidTokenTransferPrincipal), + } + } +} + +#[repr(C)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub struct StxTokenTransfer<'a>(&'a [u8]); + +impl<'a> StxTokenTransfer<'a> { + #[inline(never)] + pub fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { + let id = le_u8(bytes)?; + let (raw, _) = match TokenTranferPrincipal::from_u8(id.1)? { + TokenTranferPrincipal::Standard => PrincipalData::standard_from_bytes(id.0)?, + TokenTranferPrincipal::Contract => PrincipalData::contract_principal_from_bytes(id.0)?, + }; + // Besides principal we take 34-bytes being the MEMO message + 8-bytes amount of stx + let len = bytes.len() - raw.len() + MEMO_LEN + AMOUNT_LEN; + let (raw, data) = take(len)(bytes)?; + Ok((raw, Self(data))) + } + + pub fn memo(&self) -> &[u8] { + let at = self.0.len() - MEMO_LEN; + // safe to unwrap as parser checked for proper len + self.0.get(at..).apdu_unwrap() + } + + pub fn amount(&self) -> Result { + let at = self.0.len() - MEMO_LEN - AMOUNT_LEN; + let amount = self.0.get(at..).ok_or(ParserError::NoData)?; + be_u64::<_, ParserError>(amount) + .map(|res| res.1) + .map_err(|_| ParserError::UnexpectedBufferEnd) + } + + pub fn raw_address(&self) -> &[u8] { + // Skips the principal-id and hash_mode + // is valid as this was check by the parser + // safe to unwrap as this was checked at parsing + self.0.get(2..22).apdu_unwrap() + } + + pub fn encoded_address( + &self, + ) -> Result, ParserError> { + // Skips the principal-id at [0] and uses hash_mode and the follow 20-bytes + let version = self.0.get(1).ok_or(ParserError::NoData)?; + c32::c32_address( + *version, + self.0.get(2..22).ok_or(ParserError::InvalidAddress)?, + ) + } + + pub fn amount_stx(&self) -> Result, ParserError> { + let mut output = ArrayVec::from([0u8; zxformat::MAX_STR_BUFF_LEN]); + let amount = self.amount()?; + let len = zxformat::u64_to_str(output.as_mut(), amount)?.len(); + unsafe { + output.set_len(len); + } + check_canary!(); + Ok(output) + } + + pub fn get_token_transfer_items( + &self, + display_idx: u8, + out_key: &mut [u8], + out_value: &mut [u8], + page_idx: u8, + ) -> Result { + let mut writer_key = zxformat::Writer::new(out_key); + + match display_idx { + // Fomatting the amount in stx + 0 => { + writer_key + .write_str("Amount uSTX") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + let amount = self.amount_stx()?; + check_canary!(); + zxformat::pageString(out_value, amount.as_ref(), page_idx) + } + // Recipient address + 1 => { + writer_key + .write_str("To") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + let recipient = self.encoded_address()?; + check_canary!(); + zxformat::pageString(out_value, recipient.as_ref(), page_idx) + } + 2 => { + writer_key + .write_str("Memo") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + check_canary!(); + zxformat::pageString(out_value, self.memo(), page_idx) + } + _ => Err(ParserError::DisplayIdxOutOfRange), + } + } + + pub fn raw_data(&self) -> &'a [u8] { + self.0 + } +} diff --git a/app/rust/src/parser/transaction_payload/versioned_contract.rs b/app/rust/src/parser/transaction_payload/versioned_contract.rs new file mode 100644 index 00000000..97589d61 --- /dev/null +++ b/app/rust/src/parser/transaction_payload/versioned_contract.rs @@ -0,0 +1,82 @@ +use core::fmt::Write; + +use nom::{ + branch::alt, + bytes::complete::{tag, take}, + combinator::{flat_map, map}, + number::complete::{be_u32, be_u8}, + sequence::tuple, +}; + +use crate::{ + check_canary, + parser::{ContractName, ParserError}, + zxformat, +}; + +/// A transaction that deploys a versioned smart contract +#[repr(C)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub struct VersionedSmartContract<'a>(&'a [u8]); + +impl<'a> VersionedSmartContract<'a> { + #[inline(never)] + pub fn from_bytes(input: &'a [u8]) -> Result<(&[u8], Self), ParserError> { + check_canary!(); + + // clarity version + // len prefixed contract name + // len prefixed contract code + let parse_tag = alt((tag(&[0x01]), tag(&[0x02]))); + let parse_length_1_byte = map(be_u8, |length| std::cmp::min(length, 128u8) as usize); + let parse_length_4_bytes = flat_map(be_u32, take); + + let mut parser = tuple(( + parse_tag, + flat_map(parse_length_1_byte, take), + parse_length_4_bytes, + )); + let (_, (_, name, code)) = parser(input)?; + + // 1-byte tag, 1-byte name_len, name, 4-byte code_len, code + let total_length = 1 + 1 + name.len() + 4 + code.len(); + let (rem, res) = take(total_length)(input)?; + + Ok((rem, Self(res))) + } + + pub fn contract_name(&'a self) -> Result, ParserError> { + // skip the tag. safe ecause this was checked during parsing + ContractName::from_bytes(&self.0[1..]) + .map(|(_, res)| res) + .map_err(|e| e.into()) + } + + #[inline(never)] + pub fn get_contract_items( + &self, + display_idx: u8, + out_key: &mut [u8], + out_value: &mut [u8], + page_idx: u8, + ) -> Result { + let mut writer_key = zxformat::Writer::new(out_key); + + match display_idx { + 0 => { + writer_key + .write_str("Contract Name") + .map_err(|_| ParserError::UnexpectedBufferEnd)?; + check_canary!(); + let name = self.contract_name()?; + zxformat::pageString(out_value, name.name(), page_idx) + } + _ => Err(ParserError::ValueOutOfRange), + } + } + + pub fn raw_data(&self) -> &'a [u8] { + self.0 + } +} diff --git a/app/rust/src/parser/tx_post_conditions.rs b/app/rust/src/parser/tx_post_conditions.rs new file mode 100644 index 00000000..f1cd8c8c --- /dev/null +++ b/app/rust/src/parser/tx_post_conditions.rs @@ -0,0 +1,169 @@ +use arrayvec::ArrayVec; +use nom::{ + combinator::iterator, + number::complete::{be_u32, le_u8}, +}; + +use crate::{bolos::c_zemu_log_stack, check_canary, parser::TransactionPostCondition}; + +use super::{ParserError, NUM_SUPPORTED_POST_CONDITIONS}; + +#[repr(u8)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] +pub enum TransactionPostConditionMode { + Allow = 0x01, // allow any other changes not specified + Deny = 0x02, // deny any other changes not specified +} + +impl TransactionPostConditionMode { + #[inline(never)] + fn from_u8(v: u8) -> Option { + match v { + 1 => Some(Self::Allow), + 2 => Some(Self::Deny), + _ => None, + } + } + + #[inline(never)] + fn from_bytes(bytes: &[u8]) -> nom::IResult<&[u8], Self, ParserError> { + let mode = le_u8(bytes)?; + let tx_mode = Self::from_u8(mode.1).ok_or(ParserError::UnexpectedError)?; + check_canary!(); + Ok((mode.0, tx_mode)) + } +} + +#[repr(C)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub struct PostConditions<'a> { + pub(crate) conditions: ArrayVec<[&'a [u8]; NUM_SUPPORTED_POST_CONDITIONS]>, + // The number of items to display to the user + num_items: u8, + // The number of post_conditions in our list + num_conditions: usize, + current_idx: u8, +} + +impl<'a> PostConditions<'a> { + #[inline(never)] + pub fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { + let (raw, len) = be_u32::<_, ParserError>(bytes)?; + + if len > NUM_SUPPORTED_POST_CONDITIONS as u32 { + return Err(nom::Err::Error(ParserError::ValueOutOfRange)); + } + let mut conditions: ArrayVec<[&'a [u8]; NUM_SUPPORTED_POST_CONDITIONS]> = ArrayVec::new(); + let mut iter = iterator(raw, TransactionPostCondition::read_as_bytes); + iter.take(len as _).enumerate().for_each(|i| { + conditions.push(i.1); + }); + let res = iter.finish()?; + let num_items = Self::get_num_items(&conditions[..len as usize]); + check_canary!(); + Ok(( + res.0, + Self { + conditions, + num_items, + current_idx: 0, + num_conditions: len as usize, + }, + )) + } + + pub fn get_num_items(conditions: &[&[u8]]) -> u8 { + conditions + .iter() + .filter_map(|bytes| TransactionPostCondition::from_bytes(bytes).ok()) + .map(|condition| (condition.1).num_items()) + .sum() + } + + pub fn get_postconditions(&self) -> &[&[u8]] { + self.conditions.as_ref() + } + + pub fn num_conditions(&self) -> usize { + self.num_conditions + } + + #[inline(never)] + fn update_postcondition( + &mut self, + total_items: u8, + display_idx: u8, + ) -> Result { + // map display_idx to our range of items + let in_start = total_items - self.num_items; + let idx = self.map_idx(display_idx, in_start, total_items); + + let limit = self.get_current_limit(); + + // get the current postcondition which is used to + // check if it is time to change to the next/previous postconditions in our list + // and if that is not the case, we use it to get its items + let current_condition = self.current_post_condition()?; + + // before continuing we need to check if the current display_idx + // correspond to the current, next or previous postcondition + // if so, update it + if idx >= (limit + current_condition.num_items()) { + self.current_idx += 1; + // this should not happen + if self.current_idx > self.num_items { + return Err(ParserError::UnexpectedError); + } + } else if idx < limit && idx > 0 { + self.current_idx -= 1; + } + Ok(idx) + } + + #[inline(never)] + pub fn get_items( + &mut self, + display_idx: u8, + out_key: &mut [u8], + out_value: &mut [u8], + page_idx: u8, + num_items: u8, + ) -> Result { + c_zemu_log_stack("PostConditions::get_items\x00"); + + let idx = self.update_postcondition(num_items, display_idx)?; + let current_postcondition = self.current_post_condition()?; + current_postcondition.get_items(idx, out_key, out_value, page_idx) + } + + fn map_idx(&self, display_idx: u8, in_start: u8, in_end: u8) -> u8 { + let slope = self.num_items / (in_end - in_start); + slope * (display_idx - in_start) + } + + fn get_current_limit(&self) -> u8 { + let current = self.current_idx as usize; + self.conditions[..current] + .iter() + .filter_map(|bytes| TransactionPostCondition::from_bytes(bytes).ok()) + .map(|condition| (condition.1).num_items()) + .sum() + } + + fn current_post_condition(&self) -> Result { + let raw_current = self + .conditions + .get(self.current_idx as usize) + .ok_or(ParserError::ValueOutOfRange)?; + + TransactionPostCondition::from_bytes(raw_current) + .map_err(|_| ParserError::PostConditionFailed) + .map(|res| res.1) + } + + pub fn num_items(&self) -> u8 { + self.num_items + } +} diff --git a/app/rust/src/zxformat.rs b/app/rust/src/zxformat.rs index 6f78ea2a..6b133926 100644 --- a/app/rust/src/zxformat.rs +++ b/app/rust/src/zxformat.rs @@ -3,9 +3,7 @@ use core::fmt::{self, Write}; use crate::parser::ParserError; - -#[cfg(not(any(test, fuzzing)))] -use crate::parser::fp_uint64_to_str; +use lexical_core::Number; pub const MAX_STR_BUFF_LEN: usize = 30; @@ -39,59 +37,46 @@ impl<'a> fmt::Write for Writer<'a> { } macro_rules! num_to_str { - ($name: ident, $number: ty) => { - pub fn $name(output: &mut [u8], number: $number) -> Result { - if output.len() < 2 { + // we can use a procedural macro to "attach " the type name to the function name + // but lets do it later. + ($int_type:ty, $_name: ident) => { + pub fn $_name(output: &mut [u8], number: $int_type) -> Result<&mut [u8], ParserError> { + if output.len() < <$int_type>::FORMATTED_SIZE_DECIMAL { return Err(ParserError::UnexpectedBufferEnd); } - let len; - - #[cfg(any(test, fuzzing))] - { - let mut writer = Writer::new(output); - core::write!(writer, "{}", number).map_err(|_| ParserError::UnexpectedBufferEnd)?; + if number == 0 { + output[0] = b'0'; + return Ok(&mut output[..1]); + } - len = writer.offset; + let mut offset = 0; + let mut number = number; + while number != 0 { + let rem = number % 10; + output[offset] = b'0' + rem as u8; + offset += 1; + number /= 10; } - #[cfg(not(any(test, fuzzing)))] - { - // We add this path here because pic issues with the write! trait - // so that it is preferable to use the c implementation when running on - // the device. - unsafe { - len = fp_uint64_to_str( - output.as_mut_ptr() as _, - output.len() as u16, - number as _, - 0, - ) as usize; - } + // swap values + let len = offset; + let mut idx = 0; + while idx < offset { + offset -= 1; + output.swap(idx, offset); + idx += 1; } - Ok(len) + + Ok(&mut output[..len]) } }; } -num_to_str!(u64_to_str, u64); -num_to_str!(i64_to_str, i64); - -/// Fixed point u64 number -/// -/// Converts an u64 number into its fixed point string representation -/// using #decimals padding zeros -/// # Arguments -/// * * `out`: the output buffer where the conversion result is written -/// * `value`: The number to convert to -/// * `decimals`: the number of decimals after the decimal point -/// # Returns -/// The number of bytes written if success or Error otherwise -pub fn fpu64_to_str(out: &mut [u8], value: u64, decimals: u8) -> Result { - let mut temp = [0u8; MAX_STR_BUFF_LEN]; - let len = u64_to_str(temp.as_mut(), value)?; - fpstr_to_str(out, &temp[..len], decimals) -} +num_to_str!(u64, u64_to_str); +num_to_str!(u32, u32_to_str); +num_to_str!(u8, u8_to_str); +num_to_str!(i64, i64_to_str); /// Fixed point u64 number with native/test support /// @@ -105,29 +90,22 @@ pub fn fpu64_to_str(out: &mut [u8], value: u64, decimals: u8) -> Result Result { - let len = fpu64_to_str(out, value, decimals)? as usize; - Ok(len) -} - -/// Fixed point i64 number -/// -/// Converts an u64 number into its fixed point string representation -/// using decimals padding zeros -/// # Arguments -/// * * `out`: the output buffer where the conversion result is written -/// * `value`: The number to convert to -/// * `decimals`: the number of decimals after the decimal point -/// # Returns -/// The number of bytes written if success or Error otherwise -pub fn fpi64_to_str(out: &mut [u8], value: i64, decimals: u8) -> Result { - let mut temp = [0u8; MAX_STR_BUFF_LEN]; - let len = i64_to_str(temp.as_mut(), value)?; - fpstr_to_str(out, &temp[..len], decimals) +pub fn fpu64_to_str(out: &mut [u8], value: u64, decimals: u8) -> Result { + #[cfg(any(test, fuzzing))] + { + let mut temp = [0u8; u64::FORMATTED_SIZE_DECIMAL]; + let value = u64_to_str(temp.as_mut(), value)?; + fpstr_to_str(out, value, decimals) + } + #[cfg(not(any(test, fuzzing)))] + unsafe { + Ok(crate::parser::fp_uint64_to_str( + out.as_mut_ptr() as *mut i8, + out.len() as u16, + value, + decimals, + ) as usize) + } } pub(crate) fn fpstr_to_str( @@ -177,8 +155,11 @@ pub(crate) fn fpstr_to_str( } let fp = in_len - decimals as usize; - let left = str.get(0..fp).unwrap(); - let right = str.get(fp..in_len).unwrap(); + let left = str.get(0..fp).ok_or(ParserError::UnexpectedBufferEnd)?; + let right = str + .get(fp..in_len) + .ok_or(ParserError::UnexpectedBufferEnd)?; + write!(&mut writer, "{}.{}", left, right) .map(|_| writer.offset) .map_err(|_| ParserError::UnexpectedBufferEnd) @@ -226,61 +207,12 @@ mod test { #[test] fn test_u64_to_str() { - let mut output = [0u8; 10]; - assert!(u64_to_str(output.as_mut(), 125_550).is_ok()); - assert_eq!(&output[..6], b"125550"); - // overflow - assert!(u64_to_str(output.as_mut(), 12_521_547_982).is_err()); - } - - #[test] - fn test_i64_to_str() { - let mut output = [0u8; 10]; - assert!(i64_to_str(output.as_mut(), -125_550).is_ok()); - assert_eq!(&output[..7], b"-125550"); + let mut output = [0u8; u64::FORMATTED_SIZE_DECIMAL]; + let value = u64_to_str(output.as_mut(), 125_550).unwrap(); + std::println!("value: {}", core::str::from_utf8(value).unwrap()); + assert_eq!(&value, b"125550"); // overflow - assert!(i64_to_str(output.as_mut(), -1_234_567_890).is_err()); - } - - #[test] - fn test_fpi64_8decimals() { - let mut output = [0u8; 15]; - let len = fpi64_to_str(output.as_mut(), -1_234_567, 8).unwrap(); - let result = core::str::from_utf8(&output[..len]).unwrap(); - assert_eq!(result, "-0.01234567"); - } - - #[test] - fn test_fpi64_10decimals() { - let mut output = [0u8; 15]; - // With 10 decimals - let len = fpi64_to_str(output.as_mut(), -1_234_567, 10).unwrap(); - let result = core::str::from_utf8(&output[..len]).unwrap(); - assert_eq!(result, "-0.0001234567"); - } - - #[test] - fn test_fpi64_0decimals() { - let mut output = [0u8; 15]; - let len = fpi64_to_str(output.as_mut(), -1_234_567, 0).unwrap(); - let result = core::str::from_utf8(&output[..len]).unwrap(); - assert_eq!(result, "-1234567"); - } - - #[test] - fn test_fpi64_4decimals() { - let mut output = [0u8; 15]; - let len = fpi64_to_str(output.as_mut(), -1_234_567, 4).unwrap(); - let result = core::str::from_utf8(&output[..len]).unwrap(); - assert_eq!(result, "-123.4567"); - } - - #[test] - fn test_fpi64_overflow() { - let mut output = [0u8; 5]; - // overflow wit zero decimals - let result = fpi64_to_str(output.as_mut(), -102_123_456, 0); - assert!(result.is_err()); + assert!(u64_to_str(&mut output[..10], 12_521_547_982).is_err()); } #[test] diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00000.png b/tests_zemu/snapshots/fl-contract_call_long_args/00000.png new file mode 100644 index 00000000..7843caae Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00000.png differ diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00001.png b/tests_zemu/snapshots/fl-contract_call_long_args/00001.png new file mode 100644 index 00000000..6fd4a5c9 Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00001.png differ diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00002.png b/tests_zemu/snapshots/fl-contract_call_long_args/00002.png new file mode 100644 index 00000000..c80edadb Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00002.png differ diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00003.png b/tests_zemu/snapshots/fl-contract_call_long_args/00003.png new file mode 100644 index 00000000..0cf3d63d Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00003.png differ diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00004.png b/tests_zemu/snapshots/fl-contract_call_long_args/00004.png new file mode 100644 index 00000000..206c60a1 Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00004.png differ diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00005.png b/tests_zemu/snapshots/fl-contract_call_long_args/00005.png new file mode 100644 index 00000000..7a429ce9 Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00005.png differ diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00006.png b/tests_zemu/snapshots/fl-contract_call_long_args/00006.png new file mode 100644 index 00000000..639a0038 Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00006.png differ diff --git a/tests_zemu/snapshots/fl-contract_call_long_args/00007.png b/tests_zemu/snapshots/fl-contract_call_long_args/00007.png new file mode 100644 index 00000000..25476f9e Binary files /dev/null and b/tests_zemu/snapshots/fl-contract_call_long_args/00007.png differ diff --git a/tests_zemu/snapshots/fl-mainmenu/00004.png b/tests_zemu/snapshots/fl-mainmenu/00004.png index 8da83c6c..bb07351a 100644 Binary files a/tests_zemu/snapshots/fl-mainmenu/00004.png and b/tests_zemu/snapshots/fl-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00000.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00000.png new file mode 100644 index 00000000..b8f5322f Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00000.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00001.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00001.png new file mode 100644 index 00000000..2ec7157e Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00001.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00002.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00002.png new file mode 100644 index 00000000..8952e882 Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00002.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00003.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00003.png new file mode 100644 index 00000000..66b0543b Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00003.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00004.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00004.png new file mode 100644 index 00000000..b73ec806 Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00004.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00005.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00005.png new file mode 100644 index 00000000..f430e460 Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00005.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00006.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00006.png new file mode 100644 index 00000000..55a2232f Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00006.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00007.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00007.png new file mode 100644 index 00000000..482530eb Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00007.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00008.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00008.png new file mode 100644 index 00000000..dcd19bac Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00008.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00009.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00009.png new file mode 100644 index 00000000..b698b16c Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00009.png differ diff --git a/tests_zemu/snapshots/fl-swap_with_post_conditions/00010.png b/tests_zemu/snapshots/fl-swap_with_post_conditions/00010.png new file mode 100644 index 00000000..25476f9e Binary files /dev/null and b/tests_zemu/snapshots/fl-swap_with_post_conditions/00010.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00000.png b/tests_zemu/snapshots/s-contract_call_long_args/00000.png new file mode 100644 index 00000000..3eca54f8 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00000.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00001.png b/tests_zemu/snapshots/s-contract_call_long_args/00001.png new file mode 100644 index 00000000..033f3e4b Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00001.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00002.png b/tests_zemu/snapshots/s-contract_call_long_args/00002.png new file mode 100644 index 00000000..71512699 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00002.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00003.png b/tests_zemu/snapshots/s-contract_call_long_args/00003.png new file mode 100644 index 00000000..5c15e9f9 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00003.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00004.png b/tests_zemu/snapshots/s-contract_call_long_args/00004.png new file mode 100644 index 00000000..14870b0c Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00004.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00005.png b/tests_zemu/snapshots/s-contract_call_long_args/00005.png new file mode 100644 index 00000000..1a9660ff Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00005.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00006.png b/tests_zemu/snapshots/s-contract_call_long_args/00006.png new file mode 100644 index 00000000..b2091a00 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00006.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00007.png b/tests_zemu/snapshots/s-contract_call_long_args/00007.png new file mode 100644 index 00000000..565f9634 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00007.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00008.png b/tests_zemu/snapshots/s-contract_call_long_args/00008.png new file mode 100644 index 00000000..c2e5c5d4 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00008.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00009.png b/tests_zemu/snapshots/s-contract_call_long_args/00009.png new file mode 100644 index 00000000..86c174ca Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00009.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00010.png b/tests_zemu/snapshots/s-contract_call_long_args/00010.png new file mode 100644 index 00000000..ead445f5 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00010.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00011.png b/tests_zemu/snapshots/s-contract_call_long_args/00011.png new file mode 100644 index 00000000..e0c5ceaa Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00011.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00012.png b/tests_zemu/snapshots/s-contract_call_long_args/00012.png new file mode 100644 index 00000000..edf6924c Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00012.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00013.png b/tests_zemu/snapshots/s-contract_call_long_args/00013.png new file mode 100644 index 00000000..94ad78af Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00013.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00014.png b/tests_zemu/snapshots/s-contract_call_long_args/00014.png new file mode 100644 index 00000000..23817237 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00014.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00015.png b/tests_zemu/snapshots/s-contract_call_long_args/00015.png new file mode 100644 index 00000000..5ffe0362 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00015.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00016.png b/tests_zemu/snapshots/s-contract_call_long_args/00016.png new file mode 100644 index 00000000..ea0d2320 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00016.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00017.png b/tests_zemu/snapshots/s-contract_call_long_args/00017.png new file mode 100644 index 00000000..006c26ab Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00017.png differ diff --git a/tests_zemu/snapshots/s-contract_call_long_args/00018.png b/tests_zemu/snapshots/s-contract_call_long_args/00018.png new file mode 100644 index 00000000..fe668c89 Binary files /dev/null and b/tests_zemu/snapshots/s-contract_call_long_args/00018.png differ diff --git a/tests_zemu/snapshots/s-mainmenu/00004.png b/tests_zemu/snapshots/s-mainmenu/00004.png index 222e09f8..0f3fa1e0 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00004.png and b/tests_zemu/snapshots/s-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/s-mainmenu/00010.png b/tests_zemu/snapshots/s-mainmenu/00010.png index 222e09f8..0f3fa1e0 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00010.png and b/tests_zemu/snapshots/s-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00000.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00000.png new file mode 100644 index 00000000..3eca54f8 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00000.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00001.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00001.png new file mode 100644 index 00000000..033f3e4b Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00001.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00002.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00002.png new file mode 100644 index 00000000..71512699 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00002.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00003.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00003.png new file mode 100644 index 00000000..5c15e9f9 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00003.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00004.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00004.png new file mode 100644 index 00000000..14870b0c Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00004.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00005.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00005.png new file mode 100644 index 00000000..1a9660ff Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00005.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00006.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00006.png new file mode 100644 index 00000000..b2091a00 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00006.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00007.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00007.png new file mode 100644 index 00000000..565f9634 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00007.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00008.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00008.png new file mode 100644 index 00000000..c2e5c5d4 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00008.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00009.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00009.png new file mode 100644 index 00000000..86c174ca Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00009.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00010.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00010.png new file mode 100644 index 00000000..ead445f5 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00010.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00011.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00011.png new file mode 100644 index 00000000..e0c5ceaa Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00011.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00012.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00012.png new file mode 100644 index 00000000..edf6924c Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00012.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00013.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00013.png new file mode 100644 index 00000000..94ad78af Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00013.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00014.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00014.png new file mode 100644 index 00000000..23817237 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00014.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00015.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00015.png new file mode 100644 index 00000000..5ffe0362 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00015.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00016.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00016.png new file mode 100644 index 00000000..ea0d2320 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00016.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00017.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00017.png new file mode 100644 index 00000000..6348d452 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00017.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00018.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00018.png new file mode 100644 index 00000000..891a1c8f Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00018.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00019.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00019.png new file mode 100644 index 00000000..fee48896 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00019.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00020.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00020.png new file mode 100644 index 00000000..53d01303 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00020.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00021.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00021.png new file mode 100644 index 00000000..6348d452 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00021.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00022.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00022.png new file mode 100644 index 00000000..891a1c8f Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00022.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00023.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00023.png new file mode 100644 index 00000000..fee48896 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00023.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00024.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00024.png new file mode 100644 index 00000000..53d01303 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00024.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00025.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00025.png new file mode 100644 index 00000000..6348d452 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00025.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00026.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00026.png new file mode 100644 index 00000000..891a1c8f Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00026.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00027.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00027.png new file mode 100644 index 00000000..fee48896 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00027.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00028.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00028.png new file mode 100644 index 00000000..3745958f Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00028.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00029.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00029.png new file mode 100644 index 00000000..006c26ab Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00029.png differ diff --git a/tests_zemu/snapshots/s-swap_with_post_conditions/00030.png b/tests_zemu/snapshots/s-swap_with_post_conditions/00030.png new file mode 100644 index 00000000..fe668c89 Binary files /dev/null and b/tests_zemu/snapshots/s-swap_with_post_conditions/00030.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00000.png b/tests_zemu/snapshots/sp-contract_call_long_args/00000.png new file mode 100644 index 00000000..e51f0f6f Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00000.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00001.png b/tests_zemu/snapshots/sp-contract_call_long_args/00001.png new file mode 100644 index 00000000..f8c60d92 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00001.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00002.png b/tests_zemu/snapshots/sp-contract_call_long_args/00002.png new file mode 100644 index 00000000..b146df2f Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00002.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00003.png b/tests_zemu/snapshots/sp-contract_call_long_args/00003.png new file mode 100644 index 00000000..3007712e Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00003.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00004.png b/tests_zemu/snapshots/sp-contract_call_long_args/00004.png new file mode 100644 index 00000000..bdd52883 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00004.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00005.png b/tests_zemu/snapshots/sp-contract_call_long_args/00005.png new file mode 100644 index 00000000..e4a48c79 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00005.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00006.png b/tests_zemu/snapshots/sp-contract_call_long_args/00006.png new file mode 100644 index 00000000..aade4dfd Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00006.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00007.png b/tests_zemu/snapshots/sp-contract_call_long_args/00007.png new file mode 100644 index 00000000..db320aac Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00007.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00008.png b/tests_zemu/snapshots/sp-contract_call_long_args/00008.png new file mode 100644 index 00000000..1f95cf22 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00008.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00009.png b/tests_zemu/snapshots/sp-contract_call_long_args/00009.png new file mode 100644 index 00000000..b603f272 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00009.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00010.png b/tests_zemu/snapshots/sp-contract_call_long_args/00010.png new file mode 100644 index 00000000..22822ee2 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00010.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00011.png b/tests_zemu/snapshots/sp-contract_call_long_args/00011.png new file mode 100644 index 00000000..0c71d1d9 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00011.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00012.png b/tests_zemu/snapshots/sp-contract_call_long_args/00012.png new file mode 100644 index 00000000..d99fa0b2 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00012.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00013.png b/tests_zemu/snapshots/sp-contract_call_long_args/00013.png new file mode 100644 index 00000000..9d840988 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00013.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00014.png b/tests_zemu/snapshots/sp-contract_call_long_args/00014.png new file mode 100644 index 00000000..35f106cd Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00014.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00015.png b/tests_zemu/snapshots/sp-contract_call_long_args/00015.png new file mode 100644 index 00000000..404a3359 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00015.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00016.png b/tests_zemu/snapshots/sp-contract_call_long_args/00016.png new file mode 100644 index 00000000..1e4be699 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00016.png differ diff --git a/tests_zemu/snapshots/sp-contract_call_long_args/00017.png b/tests_zemu/snapshots/sp-contract_call_long_args/00017.png new file mode 100644 index 00000000..04254665 Binary files /dev/null and b/tests_zemu/snapshots/sp-contract_call_long_args/00017.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00004.png b/tests_zemu/snapshots/sp-mainmenu/00004.png index d8ed08de..66e6ea9b 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00004.png and b/tests_zemu/snapshots/sp-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00010.png b/tests_zemu/snapshots/sp-mainmenu/00010.png index d8ed08de..66e6ea9b 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00010.png and b/tests_zemu/snapshots/sp-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00000.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00000.png new file mode 100644 index 00000000..e51f0f6f Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00000.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00001.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00001.png new file mode 100644 index 00000000..f8c60d92 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00001.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00002.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00002.png new file mode 100644 index 00000000..b146df2f Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00002.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00003.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00003.png new file mode 100644 index 00000000..3007712e Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00003.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00004.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00004.png new file mode 100644 index 00000000..bdd52883 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00004.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00005.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00005.png new file mode 100644 index 00000000..e4a48c79 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00005.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00006.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00006.png new file mode 100644 index 00000000..aade4dfd Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00006.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00007.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00007.png new file mode 100644 index 00000000..db320aac Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00007.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00008.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00008.png new file mode 100644 index 00000000..1f95cf22 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00008.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00009.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00009.png new file mode 100644 index 00000000..b603f272 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00009.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00010.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00010.png new file mode 100644 index 00000000..22822ee2 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00010.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00011.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00011.png new file mode 100644 index 00000000..0c71d1d9 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00011.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00012.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00012.png new file mode 100644 index 00000000..d99fa0b2 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00012.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00013.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00013.png new file mode 100644 index 00000000..9d840988 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00013.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00014.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00014.png new file mode 100644 index 00000000..35f106cd Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00014.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00015.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00015.png new file mode 100644 index 00000000..404a3359 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00015.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00016.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00016.png new file mode 100644 index 00000000..6d9e41f4 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00016.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00017.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00017.png new file mode 100644 index 00000000..ddb64d75 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00017.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00018.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00018.png new file mode 100644 index 00000000..fecc41ed Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00018.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00019.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00019.png new file mode 100644 index 00000000..6d9e41f4 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00019.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00020.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00020.png new file mode 100644 index 00000000..ddb64d75 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00020.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00021.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00021.png new file mode 100644 index 00000000..fecc41ed Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00021.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00022.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00022.png new file mode 100644 index 00000000..6d9e41f4 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00022.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00023.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00023.png new file mode 100644 index 00000000..ddb64d75 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00023.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00024.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00024.png new file mode 100644 index 00000000..fa031397 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00024.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00025.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00025.png new file mode 100644 index 00000000..1e4be699 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00025.png differ diff --git a/tests_zemu/snapshots/sp-swap_with_post_conditions/00026.png b/tests_zemu/snapshots/sp-swap_with_post_conditions/00026.png new file mode 100644 index 00000000..04254665 Binary files /dev/null and b/tests_zemu/snapshots/sp-swap_with_post_conditions/00026.png differ diff --git a/tests_zemu/snapshots/st-contract_call_long_args/00000.png b/tests_zemu/snapshots/st-contract_call_long_args/00000.png new file mode 100644 index 00000000..f2a36133 Binary files /dev/null and b/tests_zemu/snapshots/st-contract_call_long_args/00000.png differ diff --git a/tests_zemu/snapshots/st-contract_call_long_args/00001.png b/tests_zemu/snapshots/st-contract_call_long_args/00001.png new file mode 100644 index 00000000..22737bb3 Binary files /dev/null and b/tests_zemu/snapshots/st-contract_call_long_args/00001.png differ diff --git a/tests_zemu/snapshots/st-contract_call_long_args/00002.png b/tests_zemu/snapshots/st-contract_call_long_args/00002.png new file mode 100644 index 00000000..16c3a4f4 Binary files /dev/null and b/tests_zemu/snapshots/st-contract_call_long_args/00002.png differ diff --git a/tests_zemu/snapshots/st-contract_call_long_args/00003.png b/tests_zemu/snapshots/st-contract_call_long_args/00003.png new file mode 100644 index 00000000..bc70e7ec Binary files /dev/null and b/tests_zemu/snapshots/st-contract_call_long_args/00003.png differ diff --git a/tests_zemu/snapshots/st-contract_call_long_args/00004.png b/tests_zemu/snapshots/st-contract_call_long_args/00004.png new file mode 100644 index 00000000..199c3ff5 Binary files /dev/null and b/tests_zemu/snapshots/st-contract_call_long_args/00004.png differ diff --git a/tests_zemu/snapshots/st-contract_call_long_args/00005.png b/tests_zemu/snapshots/st-contract_call_long_args/00005.png new file mode 100644 index 00000000..bdb9485c Binary files /dev/null and b/tests_zemu/snapshots/st-contract_call_long_args/00005.png differ diff --git a/tests_zemu/snapshots/st-contract_call_long_args/00006.png b/tests_zemu/snapshots/st-contract_call_long_args/00006.png new file mode 100644 index 00000000..dfbde52b Binary files /dev/null and b/tests_zemu/snapshots/st-contract_call_long_args/00006.png differ diff --git a/tests_zemu/snapshots/st-mainmenu/00004.png b/tests_zemu/snapshots/st-mainmenu/00004.png index e868434a..b005528e 100644 Binary files a/tests_zemu/snapshots/st-mainmenu/00004.png and b/tests_zemu/snapshots/st-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00000.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00000.png new file mode 100644 index 00000000..05089157 Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00000.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00001.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00001.png new file mode 100644 index 00000000..9ba06d1b Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00001.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00002.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00002.png new file mode 100644 index 00000000..53d65650 Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00002.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00003.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00003.png new file mode 100644 index 00000000..a9ebe1a8 Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00003.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00004.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00004.png new file mode 100644 index 00000000..c9a629d4 Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00004.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00005.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00005.png new file mode 100644 index 00000000..113de2d3 Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00005.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00006.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00006.png new file mode 100644 index 00000000..df0a2b23 Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00006.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00007.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00007.png new file mode 100644 index 00000000..95bac01f Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00007.png differ diff --git a/tests_zemu/snapshots/st-swap_with_post_conditions/00008.png b/tests_zemu/snapshots/st-swap_with_post_conditions/00008.png new file mode 100644 index 00000000..dfbde52b Binary files /dev/null and b/tests_zemu/snapshots/st-swap_with_post_conditions/00008.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00000.png b/tests_zemu/snapshots/x-contract_call_long_args/00000.png new file mode 100644 index 00000000..e51f0f6f Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00000.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00001.png b/tests_zemu/snapshots/x-contract_call_long_args/00001.png new file mode 100644 index 00000000..f8c60d92 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00001.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00002.png b/tests_zemu/snapshots/x-contract_call_long_args/00002.png new file mode 100644 index 00000000..b146df2f Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00002.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00003.png b/tests_zemu/snapshots/x-contract_call_long_args/00003.png new file mode 100644 index 00000000..3007712e Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00003.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00004.png b/tests_zemu/snapshots/x-contract_call_long_args/00004.png new file mode 100644 index 00000000..bdd52883 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00004.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00005.png b/tests_zemu/snapshots/x-contract_call_long_args/00005.png new file mode 100644 index 00000000..e4a48c79 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00005.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00006.png b/tests_zemu/snapshots/x-contract_call_long_args/00006.png new file mode 100644 index 00000000..aade4dfd Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00006.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00007.png b/tests_zemu/snapshots/x-contract_call_long_args/00007.png new file mode 100644 index 00000000..db320aac Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00007.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00008.png b/tests_zemu/snapshots/x-contract_call_long_args/00008.png new file mode 100644 index 00000000..1f95cf22 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00008.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00009.png b/tests_zemu/snapshots/x-contract_call_long_args/00009.png new file mode 100644 index 00000000..b603f272 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00009.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00010.png b/tests_zemu/snapshots/x-contract_call_long_args/00010.png new file mode 100644 index 00000000..22822ee2 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00010.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00011.png b/tests_zemu/snapshots/x-contract_call_long_args/00011.png new file mode 100644 index 00000000..0c71d1d9 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00011.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00012.png b/tests_zemu/snapshots/x-contract_call_long_args/00012.png new file mode 100644 index 00000000..d99fa0b2 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00012.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00013.png b/tests_zemu/snapshots/x-contract_call_long_args/00013.png new file mode 100644 index 00000000..9d840988 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00013.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00014.png b/tests_zemu/snapshots/x-contract_call_long_args/00014.png new file mode 100644 index 00000000..35f106cd Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00014.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00015.png b/tests_zemu/snapshots/x-contract_call_long_args/00015.png new file mode 100644 index 00000000..404a3359 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00015.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00016.png b/tests_zemu/snapshots/x-contract_call_long_args/00016.png new file mode 100644 index 00000000..1e4be699 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00016.png differ diff --git a/tests_zemu/snapshots/x-contract_call_long_args/00017.png b/tests_zemu/snapshots/x-contract_call_long_args/00017.png new file mode 100644 index 00000000..04254665 Binary files /dev/null and b/tests_zemu/snapshots/x-contract_call_long_args/00017.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00004.png b/tests_zemu/snapshots/x-mainmenu/00004.png index d8ed08de..66e6ea9b 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00004.png and b/tests_zemu/snapshots/x-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00010.png b/tests_zemu/snapshots/x-mainmenu/00010.png index d8ed08de..66e6ea9b 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00010.png and b/tests_zemu/snapshots/x-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00000.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00000.png new file mode 100644 index 00000000..e51f0f6f Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00000.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00001.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00001.png new file mode 100644 index 00000000..f8c60d92 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00001.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00002.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00002.png new file mode 100644 index 00000000..b146df2f Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00002.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00003.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00003.png new file mode 100644 index 00000000..3007712e Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00003.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00004.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00004.png new file mode 100644 index 00000000..bdd52883 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00004.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00005.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00005.png new file mode 100644 index 00000000..e4a48c79 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00005.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00006.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00006.png new file mode 100644 index 00000000..aade4dfd Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00006.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00007.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00007.png new file mode 100644 index 00000000..db320aac Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00007.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00008.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00008.png new file mode 100644 index 00000000..1f95cf22 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00008.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00009.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00009.png new file mode 100644 index 00000000..b603f272 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00009.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00010.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00010.png new file mode 100644 index 00000000..22822ee2 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00010.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00011.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00011.png new file mode 100644 index 00000000..0c71d1d9 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00011.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00012.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00012.png new file mode 100644 index 00000000..d99fa0b2 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00012.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00013.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00013.png new file mode 100644 index 00000000..9d840988 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00013.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00014.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00014.png new file mode 100644 index 00000000..35f106cd Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00014.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00015.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00015.png new file mode 100644 index 00000000..404a3359 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00015.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00016.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00016.png new file mode 100644 index 00000000..6d9e41f4 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00016.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00017.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00017.png new file mode 100644 index 00000000..ddb64d75 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00017.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00018.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00018.png new file mode 100644 index 00000000..fecc41ed Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00018.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00019.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00019.png new file mode 100644 index 00000000..6d9e41f4 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00019.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00020.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00020.png new file mode 100644 index 00000000..ddb64d75 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00020.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00021.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00021.png new file mode 100644 index 00000000..fecc41ed Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00021.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00022.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00022.png new file mode 100644 index 00000000..6d9e41f4 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00022.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00023.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00023.png new file mode 100644 index 00000000..ddb64d75 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00023.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00024.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00024.png new file mode 100644 index 00000000..fa031397 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00024.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00025.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00025.png new file mode 100644 index 00000000..1e4be699 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00025.png differ diff --git a/tests_zemu/snapshots/x-swap_with_post_conditions/00026.png b/tests_zemu/snapshots/x-swap_with_post_conditions/00026.png new file mode 100644 index 00000000..04254665 Binary files /dev/null and b/tests_zemu/snapshots/x-swap_with_post_conditions/00026.png differ diff --git a/tests_zemu/tests/standard.test.ts b/tests_zemu/tests/standard.test.ts index 55ed80cd..614a5a32 100644 --- a/tests_zemu/tests/standard.test.ts +++ b/tests_zemu/tests/standard.test.ts @@ -39,6 +39,18 @@ import { uintCV, stringAsciiCV, stringUtf8CV, + tupleCV, + intCV, + bufferCV, + listCV, + responseOkCV, + responseErrorCV, + someCV, + noneCV, + trueCV, + falseCV, + FungibleConditionCode, + makeStandardSTXPostCondition, } from '@stacks/transactions' import { StacksTestnet } from '@stacks/network' import { AnchorMode } from '@stacks/transactions/src/constants' @@ -57,6 +69,28 @@ const defaultOptions = { jest.setTimeout(180000) +const BIG_TUPLE = tupleCV({ + hello: uintCV(234), + a: intCV(-1), + b: bufferCV(Buffer.from('abcdefgh', 'ascii')), + m: listCV([intCV(-1), intCV(-1), intCV(-1), intCV(-1)]), + result_call: responseOkCV(stringAsciiCV('done')), + error_msg: responseErrorCV(stringUtf8CV('unknown URI')), + nested: someCV(listCV([noneCV(), someCV(intCV(-100))])), + principal: standardPrincipalCV('SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B'), + l: tupleCV({ + a: trueCV(), + b: falseCV(), + }), + contractPrincipal: contractPrincipalCV('SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B', 'test'), + xxxx: tupleCV({ + yyyy: intCV(123), + ddd: tupleCV({ + ggg: uintCV(123), + }), + }), +}) + describe('Standard', function () { test.concurrent.each(models)('can start and stop container', async function (m) { const sim = new Zemu(m.path) @@ -128,7 +162,6 @@ describe('Standard', function () { const response = await app.getAddressAndPubKey("m/5757'/0'/5'/0/0", AddressVersion.MainnetSingleSig) console.log(response) expect(response.returnCode).toEqual(0x9000) - } finally { await sim.close() } @@ -324,7 +357,7 @@ describe('Standard', function () { console.log(pkResponse) expect(pkResponse.returnCode).toEqual(0x9000) expect(pkResponse.errorMessage).toEqual('No errors') - const devicePublicKey = publicKeyFromBuffer(pkResponse.publicKey); + const devicePublicKey = publicKeyFromBuffer(pkResponse.publicKey) const devicePublicKeyString = pkResponse.publicKey.toString('hex') const recipient = standardPrincipalCV('ST2XADQKC3EPZ62QTG5Q2RSPV64JG6KXCND0PHT7F') @@ -389,7 +422,7 @@ describe('Standard', function () { // Add Ledger signature to transaction tx.auth.spendingCondition.fields[1] = createTransactionAuthField(PubKeyEncoding.Compressed, { data: signature.signatureVRS.toString('hex'), - type: StacksMessageType.MessageSignature + type: StacksMessageType.MessageSignature, }) // For full tx validation, use `stacks-inspect decode-tx ` @@ -443,7 +476,7 @@ describe('Standard', function () { console.log(pkResponse) expect(pkResponse.returnCode).toEqual(0x9000) expect(pkResponse.errorMessage).toEqual('No errors') - const devicePublicKey = publicKeyFromBuffer(pkResponse.publicKey); + const devicePublicKey = publicKeyFromBuffer(pkResponse.publicKey) const devicePublicKeyString = pkResponse.publicKey.toString('hex') const recipient = standardPrincipalCV('ST2XADQKC3EPZ62QTG5Q2RSPV64JG6KXCND0PHT7F') @@ -471,7 +504,7 @@ describe('Standard', function () { // @ts-ignore // Use order-independent multisig P2SH // TODO: Replace with constant once support is added in Stacks.js - tx.auth.spendingCondition.hashMode = 5; + tx.auth.spendingCondition.hashMode = 5 const sigHashPreSign = makeSigHashPreSign( tx.signBegin(), @@ -627,6 +660,99 @@ describe('Standard', function () { } }) + test.concurrent.each(models)(`sign_contract_call_long_args`, async function (m) { + const sim = new Zemu(m.path) + const network = new StacksTestnet() + const senderKey = '2cefd4375fcb0b3c0935fcbc53a8cb7c7b9e0af0225581bbee006cf7b1aa0216' + const my_key = '2e64805a5808a8a72df89b4b18d2451f8d5ab5224b4d8c7c36033aee4add3f27f' + const path = "m/44'/5757'/0'/0/0" + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = new StacksApp(sim.getTransport()) + // Get pubkey and check + const pkResponse = await app.getAddressAndPubKey(path, AddressVersion.TestnetSingleSig) + console.log(pkResponse) + expect(pkResponse.returnCode).toEqual(0x9000) + expect(pkResponse.errorMessage).toEqual('No errors') + const devicePublicKey = pkResponse.publicKey.toString('hex') + + const recipient = standardPrincipalCV('ST39RCH114B48GY5E0K2Q4SV28XZMXW4ZZTN8QSS5') + const contract_principal = contractPrincipalCV('ST39RCH114B48GY5E0K2Q4SV28XZMXW4ZZTN8QSS5', 'some-contract-name') + const fee = new BN(10) + const nonce = new BN(0) + const [contract_address, contract_name] = 'SP000000000000000000002Q6VF78.long_args_contract'.split('.') + const txOptions = { + anchorMode: AnchorMode.Any, + contractAddress: contract_address, + contractName: contract_name, + functionName: 'stack-stx', + functionArgs: [ + uintCV(20000), + recipient, + intCV(-2), + someCV(listCV([noneCV(), someCV(intCV(-100))])), + contract_principal, + uintCV(20), + BIG_TUPLE, + bufferCV(Buffer.from('abcdefgh', 'ascii')), + ], + network: network, + fee: fee, + nonce: nonce, + publicKey: devicePublicKey, + } + + const transaction = await makeUnsignedContractCall(txOptions) + const serializeTx = transaction.serialize().toString('hex') + + const blob = Buffer.from(serializeTx, 'hex') + const signatureRequest = app.sign(path, blob) + + // Wait until we are not in the main menu + await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) + + await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-contract_call_long_args`) + + const signature = await signatureRequest + console.log(signature) + + expect(signature.returnCode).toEqual(0x9000) + + // compute postSignHash to verify signature + + const sigHashPreSign = makeSigHashPreSign( + transaction.signBegin(), + // @ts-ignore + transaction.auth.authType, + transaction.auth.spendingCondition?.fee, + transaction.auth.spendingCondition?.nonce, + ) + console.log('sigHashPreSign: ', sigHashPreSign) + const presig_hash = Buffer.from(sigHashPreSign, 'hex') + + const key_t = Buffer.alloc(1) + key_t.writeInt8(0x00) + + const array = [presig_hash, key_t, signature.signatureVRS] + const to_hash = Buffer.concat(array) + const hash = sha512_256(to_hash) + console.log('computed postSignHash: ', hash.toString('hex')) + + // compare hashes + expect(signature.postSignHash.toString('hex')).toEqual(hash.toString('hex')) + + //Verify signature + const ec = new EC('secp256k1') + const signature1 = signature.signatureVRS.toString('hex') + const signature1_obj = { r: signature1.substr(2, 64), s: signature1.substr(66, 64) } + // @ts-ignore + const signature1Ok = ec.verify(presig_hash, signature1_obj, devicePublicKey, 'hex') + expect(signature1Ok).toEqual(true) + } finally { + await sim.close() + } + }) + test.concurrent.each(models)(`sign_message`, async function (m) { const sim = new Zemu(m.path) const senderKey = '2cefd4375fcb0b3c0935fcbc53a8cb7c7b9e0af0225581bbee006cf7b1aa0216' @@ -831,4 +957,111 @@ describe('Standard', function () { await sim.close() } }) + + test.concurrent.each(models)(`sign_contract_and_post_conditions`, async function (m) { + const sim = new Zemu(m.path) + const network = new StacksTestnet() + const senderKey = '2cefd4375fcb0b3c0935fcbc53a8cb7c7b9e0af0225581bbee006cf7b1aa0216' + const my_key = '2e64805a5808a8a72df89b4b18d2451f8d5ab5224b4d8c7c36033aee4add3f27f' + const path = "m/44'/5757'/0'/0/0" + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = new StacksApp(sim.getTransport()) + // Get pubkey and check + const pkResponse = await app.getAddressAndPubKey(path, AddressVersion.TestnetSingleSig) + console.log(pkResponse) + expect(pkResponse.returnCode).toEqual(0x9000) + expect(pkResponse.errorMessage).toEqual('No errors') + const devicePublicKey = pkResponse.publicKey.toString('hex') + + const recipient = standardPrincipalCV('ST39RCH114B48GY5E0K2Q4SV28XZMXW4ZZTN8QSS5') + const contract_principal = contractPrincipalCV('ST39RCH114B48GY5E0K2Q4SV28XZMXW4ZZTN8QSS5', 'some-contract-name') + const fee = new BN(10) + const nonce = new BN(0) + const [contract_address, contract_name] = 'SP000000000000000000002Q6VF78.long_args_contract'.split('.') + + const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE' + const postConditionAddress2 = 'ST39RCH114B48GY5E0K2Q4SV28XZMXW4ZZTN8QSS5' + const postConditionAddress3 = 'ST39RCH114B48GY5E0K2Q4SV28XZMXW4ZZTN8QSS5' + const postConditionCode = FungibleConditionCode.GreaterEqual + const postConditionAmount = new BN(1000000) + const postConditionAmount2 = new BN(1005020) + const postConditions = [ + makeStandardSTXPostCondition(postConditionAddress, postConditionCode, postConditionAmount), + makeStandardSTXPostCondition(postConditionAddress2, postConditionCode, postConditionAmount), + makeStandardSTXPostCondition(postConditionAddress3, postConditionCode, postConditionAmount2), + ] + + const txOptions = { + anchorMode: AnchorMode.Any, + contractAddress: contract_address, + contractName: contract_name, + functionName: 'stack-stx', + functionArgs: [ + uintCV(20000), + recipient, + intCV(-2), + someCV(listCV([noneCV(), someCV(intCV(-100))])), + contract_principal, + uintCV(20), + BIG_TUPLE, + bufferCV(Buffer.from('abcdefgh', 'ascii')), + ], + network: network, + fee: fee, + nonce: nonce, + publicKey: devicePublicKey, + postConditions, + } + + const transaction = await makeUnsignedContractCall(txOptions) + const serializeTx = transaction.serialize().toString('hex') + + const blob = Buffer.from(serializeTx, 'hex') + const signatureRequest = app.sign(path, blob) + + // Wait until we are not in the main menu + await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) + + await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-swap_with_post_conditions`) + + const signature = await signatureRequest + console.log(signature) + + expect(signature.returnCode).toEqual(0x9000) + + // compute postSignHash to verify signature + + const sigHashPreSign = makeSigHashPreSign( + transaction.signBegin(), + // @ts-ignore + transaction.auth.authType, + transaction.auth.spendingCondition?.fee, + transaction.auth.spendingCondition?.nonce, + ) + console.log('sigHashPreSign: ', sigHashPreSign) + const presig_hash = Buffer.from(sigHashPreSign, 'hex') + + const key_t = Buffer.alloc(1) + key_t.writeInt8(0x00) + + const array = [presig_hash, key_t, signature.signatureVRS] + const to_hash = Buffer.concat(array) + const hash = sha512_256(to_hash) + console.log('computed postSignHash: ', hash.toString('hex')) + + // compare hashes + expect(signature.postSignHash.toString('hex')).toEqual(hash.toString('hex')) + + //Verify signature + const ec = new EC('secp256k1') + const signature1 = signature.signatureVRS.toString('hex') + const signature1_obj = { r: signature1.substr(2, 64), s: signature1.substr(66, 64) } + // @ts-ignore + const signature1Ok = ec.verify(presig_hash, signature1_obj, devicePublicKey, 'hex') + expect(signature1Ok).toEqual(true) + } finally { + await sim.close() + } + }) })