diff --git a/app/rust/src/parser/parsed_obj.rs b/app/rust/src/parser/parsed_obj.rs index 55ef45dd..1750f41c 100644 --- a/app/rust/src/parser/parsed_obj.rs +++ b/app/rust/src/parser/parsed_obj.rs @@ -784,4 +784,13 @@ mod test { msg.read(&bytes).unwrap(); ParsedObj::validate(&mut msg).unwrap(); } + + #[test] + fn parse_versioned_contract() { + let input = "8080000000040060dbb32efe0c56e1d418c020f4cb71c556b6a60d0000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000301000000000602107468656e2d677265656e2d6d61636177000004cf3b3b2068656c6c6f2d776f726c6420636f6e74726163740a0a28646566696e652d636f6e7374616e742073656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b51394836445052290a28646566696e652d636f6e7374616e7420726563697069656e742027534d324a365a593438475631455a35563256355242394d5036365357383650594b4b51565838583047290a0a28646566696e652d66756e6769626c652d746f6b656e206e6f76656c2d746f6b656e2d3139290a2866742d6d696e743f206e6f76656c2d746f6b656e2d3139207531322073656e646572290a2866742d7472616e736665723f206e6f76656c2d746f6b656e2d31392075322073656e64657220726563697069656e74290a0a28646566696e652d6e6f6e2d66756e6769626c652d746f6b656e2068656c6c6f2d6e66742075696e74290a0a286e66742d6d696e743f2068656c6c6f2d6e66742075312073656e646572290a286e66742d6d696e743f2068656c6c6f2d6e66742075322073656e646572290a286e66742d7472616e736665723f2068656c6c6f2d6e66742075312073656e64657220726563697069656e74290a0a28646566696e652d7075626c69632028746573742d656d69742d6576656e74290a202028626567696e0a20202020287072696e7420224576656e74212048656c6c6f20776f726c64220a20202020286f6b207531290a2020290a290a0a28626567696e2028746573742d656d69742d6576656e7429290a0a28646566696e652d7075626c69632028746573742d6576656e742d7479706573290a202028626567696e0a2020202028756e777261702d70616e6963202866742d6d696e743f206e6f76656c2d746f6b656e2d313920753320726563697069656e7429290a2020202028756e777261702d70616e696320286e66742d6d696e743f2068656c6c6f2d6e667420753220726563697069656e7429290a2020202028756e777261702d70616e696320287374782d7472616e736665723f207536302074782d73656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b5139483644505229290a2020202028756e777261702d70616e696320287374782d6275726e3f207532302074782d73656e64657229290a20202020286f6b207531290a2020290a290a0a28646566696e652d6d61702073746f7265207b206b65793a20286275666620333229207d207b2076616c75653a20286275666620333229207d290a0a28646566696e652d7075626c696320286765742d76616c756520286b65792028627566662033322929290a202028626567696e0a20202020286d6174636820286d61702d6765743f2073746f7265207b206b65793a206b6579207d290a202020202020656e74727920286f6b20286765742076616c756520656e74727929290a202020202020286572722030290a20202020290a2020290a290a0a28646566696e652d7075626c696320287365742d76616c756520286b65792028627566662033322929202876616c75652028627566662033322929290a202028626567696e0a20202020286d61702d7365742073746f7265207b206b65793a206b6579207d207b2076616c75653a2076616c7565207d290a20202020286f6b207531290a2020290a290a"; + let bytes = hex::decode(input).unwrap(); + let mut msg = ParsedObj::from_bytes(&bytes).unwrap(); + msg.read(&bytes).unwrap(); + ParsedObj::validate(&mut msg).unwrap(); + } } diff --git a/app/rust/src/parser/transaction_payload.rs b/app/rust/src/parser/transaction_payload.rs index 804f670a..a928484f 100644 --- a/app/rust/src/parser/transaction_payload.rs +++ b/app/rust/src/parser/transaction_payload.rs @@ -1,7 +1,9 @@ use core::fmt::Write; use nom::{ - bytes::complete::take, - number::complete::{be_u32, be_u64, le_u8}, + branch::alt, + bytes::complete::{tag, take}, + combinator::{flat_map, map}, + number::complete::{be_u32, be_u64, be_u8, le_u8}, sequence::tuple, }; @@ -19,6 +21,9 @@ 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 @@ -477,6 +482,69 @@ impl<'a> TransactionContractCall<'a> { } } +/// 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 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::parser_unexpected_buffer_end)?; + check_canary!(); + let name = self.contract_name()?; + zxformat::pageString(out_value, name.name(), page_idx) + } + _ => Err(ParserError::parser_value_out_of_range), + } + } +} + /// A transaction that instantiates a smart contract #[repr(C)] #[derive(Clone, PartialEq)] @@ -485,11 +553,22 @@ pub struct TransactionSmartContract<'a>(&'a [u8]); impl<'a> TransactionSmartContract<'a> { #[inline(never)] - fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> { + fn from_bytes(bytes: &'a [u8]) -> Result<(&[u8], Self), ParserError> { check_canary!(); - // we take "ownership" of bytes here because - // it should only contain the contract information and body - Ok((Default::default(), Self(bytes))) + + // 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 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> { @@ -529,6 +608,7 @@ pub enum TransactionPayloadId { TokenTransfer = 0, SmartContract = 1, ContractCall = 2, + VersionedSmartContract = 6, } impl TransactionPayloadId { @@ -537,6 +617,7 @@ impl TransactionPayloadId { 0 => Ok(Self::TokenTransfer), 1 => Ok(Self::SmartContract), 2 => Ok(Self::ContractCall), + 6 => Ok(Self::VersionedSmartContract), _ => Err(ParserError::parser_invalid_transaction_payload), } } @@ -549,6 +630,7 @@ pub enum TransactionPayload<'a> { TokenTransfer(StxTokenTransfer<'a>), SmartContract(TransactionSmartContract<'a>), ContractCall(TransactionContractCall<'a>), + VersionedSmartContract(VersionedSmartContract<'a>), } impl<'a> TransactionPayload<'a> { @@ -568,6 +650,10 @@ impl<'a> TransactionPayload<'a> { let call = TransactionContractCall::from_bytes(id.0)?; (call.0, Self::ContractCall(call.1)) } + TransactionPayloadId::VersionedSmartContract => { + let call = VersionedSmartContract::from_bytes(id.0)?; + (call.0, Self::VersionedSmartContract(call.1)) + } }; Ok(res) } @@ -583,10 +669,15 @@ impl<'a> TransactionPayload<'a> { matches!(self, &Self::ContractCall(_)) } + pub fn is_contract_deploy_payload(&self) -> bool { + matches!(self, &Self::VersionedSmartContract(_)) + } + pub fn contract_name(&'a self) -> Option> { match self { Self::SmartContract(contract) => contract.contract_name().ok(), Self::ContractCall(contract) => contract.contract_name().ok(), + Self::VersionedSmartContract(contract) => contract.contract_name().ok(), _ => None, } } @@ -635,7 +726,7 @@ impl<'a> TransactionPayload<'a> { pub fn num_items(&self) -> u8 { match self { Self::TokenTransfer(_) => 3, - Self::SmartContract(_) => 1, + Self::SmartContract(_) | Self::VersionedSmartContract(_) => 1, Self::ContractCall(ref call) => call.num_items().unwrap_or(CONTRACT_CALL_BASE_ITEMS), } } @@ -659,6 +750,9 @@ impl<'a> TransactionPayload<'a> { Self::ContractCall(ref call) => { call.get_contract_call_items(idx, out_key, out_value, page_idx) } + Self::VersionedSmartContract(ref deploy) => { + deploy.get_contract_items(idx, out_key, out_value, page_idx) + } } } }