From 63e4dfc50fd577b95b44110f717d9d32c2404e83 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Fri, 9 Aug 2024 19:29:22 +0200 Subject: [PATCH] feat(abi): add support for tvm getters --- nekoton-abi/src/lib.rs | 101 +++++++++++++++++++++++++++-- nekoton-abi/src/tvm.rs | 47 ++++++++++++-- nekoton-utils/src/cell.rs | 19 ++++++ nekoton-utils/src/lib.rs | 1 + nekoton-utils/src/serde_helpers.rs | 22 +------ 5 files changed, 160 insertions(+), 30 deletions(-) diff --git a/nekoton-abi/src/lib.rs b/nekoton-abi/src/lib.rs index ff5b15419..ec6036b48 100644 --- a/nekoton-abi/src/lib.rs +++ b/nekoton-abi/src/lib.rs @@ -65,6 +65,7 @@ use ton_block::{ }; use ton_executor::{BlockchainConfig, OrdinaryTransactionExecutor, TransactionExecutor}; use ton_types::{SliceData, UInt256}; +use ton_vm::executor::BehaviorModifiers; #[cfg(feature = "derive")] pub use { @@ -86,8 +87,8 @@ pub use self::models::*; pub use self::token_packer::*; pub use self::token_unpacker::*; pub use self::tokens_json::*; -pub use self::tvm::BriefBlockchainConfig; -pub use transaction_parser::TransactionParser; +pub use self::transaction_parser::TransactionParser; +pub use self::tvm::{BriefBlockchainConfig, StackItem, VmGetterOutput}; mod abi_helpers; mod code_salt; @@ -100,7 +101,7 @@ mod token_packer; mod token_unpacker; mod tokens_json; pub mod transaction_parser; -mod tvm; +pub mod tvm; pub fn read_function_id(data: &SliceData) -> Result { let mut value: u32 = 0; @@ -582,6 +583,76 @@ impl<'a> ExecutionContext<'a> { ) -> Result { function.run_local_responsible(self.clock, self.account_stuff.clone(), input) } + + pub fn run_getter( + &self, + method_id: &M, + args: &[StackItem], + ) -> Result + where + M: AsGetterMethodId + ?Sized, + { + self.run_getter_ext( + method_id, + args, + &BriefBlockchainConfig::default(), + &Default::default(), + ) + } + + pub fn run_getter_ext( + &self, + method_id: &M, + args: &[StackItem], + config: &BriefBlockchainConfig, + modifier: &BehaviorModifiers, + ) -> Result + where + M: AsGetterMethodId + ?Sized, + { + let BlockStats { + gen_utime, gen_lt, .. + } = get_block_stats(self.clock, None, self.account_stuff.storage.last_trans_lt); + + tvm::call_getter( + gen_utime, + gen_lt, + self.account_stuff, + method_id.as_getter_method_id(), + args, + config, + modifier, + ) + } +} + +pub trait AsGetterMethodId { + fn as_getter_method_id(&self) -> u32; +} + +impl AsGetterMethodId for &T { + fn as_getter_method_id(&self) -> u32 { + T::as_getter_method_id(*self) + } +} + +impl AsGetterMethodId for &mut T { + fn as_getter_method_id(&self) -> u32 { + T::as_getter_method_id(*self) + } +} + +impl AsGetterMethodId for u32 { + fn as_getter_method_id(&self) -> u32 { + *self + } +} + +impl AsGetterMethodId for str { + fn as_getter_method_id(&self) -> u32 { + let crc = crc_16(self.as_bytes()); + crc as u32 | 0x10000 + } } pub trait FunctionExt { @@ -739,7 +810,14 @@ impl<'a> FunctionAbi<'a> { let tvm::ActionPhaseOutput { messages, exit_code: result_code, - } = tvm::call_msg(gen_utime, gen_lt, account_stuff, &msg, config)?; + } = tvm::call_msg( + gen_utime, + gen_lt, + account_stuff, + &msg, + config, + &Default::default(), + )?; let tokens = if let Some(answer_id) = answer_id { messages.map(|messages| { @@ -1172,6 +1250,21 @@ mod tests { assert_eq!(decoded_comment, comment); } + #[test] + fn execute_getter() { + let cell = ton_types::deserialize_tree_of_cells(&mut base64::decode("te6ccgEBAwEA1wACcIAStWnZig414CoO3Ix5SSSgxF+4p0D15b9rxM7Q6hTG2AQNApWGauQIQAABez2Soaga3FkkG3ymAgEAUAAACtJLqS2Krp5U49k0sATqkF/7CPTREi6T4gLBqodDaVGp3w9YHEEA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA==").unwrap().as_slice()).unwrap(); + let state = nekoton_utils::deserialize_account_stuff(cell).unwrap(); + + let res = ExecutionContext { + clock: &SimpleClock, + account_stuff: &state, + } + .run_getter("seqno", &[]) + .unwrap(); + + println!("{res:?}"); + } + #[test] fn test_encode_cell() { let expected = "te6ccgEBAQEAIgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA5"; diff --git a/nekoton-abi/src/tvm.rs b/nekoton-abi/src/tvm.rs index 9f716c014..701662196 100644 --- a/nekoton-abi/src/tvm.rs +++ b/nekoton-abi/src/tvm.rs @@ -7,7 +7,10 @@ use ton_block::{ use ton_types::SliceData; use ton_vm::executor::gas::gas_state::Gas; use ton_vm::stack::integer::IntegerData; -use ton_vm::stack::{savelist::SaveList, Stack, StackItem}; +use ton_vm::stack::{savelist::SaveList, Stack}; + +pub type BehaviorModifiers = ton_vm::executor::BehaviorModifiers; +pub type StackItem = ton_vm::stack::StackItem; #[derive(Debug, Copy, Clone)] pub struct BriefBlockchainConfig { @@ -43,11 +46,12 @@ impl From for BriefBlockchainConfig { pub fn call( utime: u32, lt: u64, - account: &mut AccountStuff, + account: &AccountStuff, stack: Stack, config: &BriefBlockchainConfig, + modifiers: &BehaviorModifiers, ) -> Result<(ton_vm::executor::Engine, i32, bool), ExecutionError> { - let state = match &mut account.storage.state { + let state = match &account.storage.state { ton_block::AccountState::AccountActive { state_init, .. } => Ok(state_init), _ => Err(ExecutionError::AccountIsNotActive), }?; @@ -85,6 +89,7 @@ pub fn call( Some(gas), ); engine.set_signature_id(config.global_id); + engine.modify_behavior(modifiers.clone()); let result = engine.execute(); @@ -112,6 +117,7 @@ pub fn call_msg( account: &mut AccountStuff, msg: &Message, config: &BriefBlockchainConfig, + modifiers: &ton_vm::executor::BehaviorModifiers, ) -> Result { let msg_cell = msg .write_to_new_cell() @@ -135,7 +141,7 @@ pub fn call_msg( .push(StackItem::Slice(msg.body().unwrap_or_default())) // message body .push(function_selector); // function selector - let (engine, exit_code, success) = call(utime, lt, account, stack, config)?; + let (engine, exit_code, success) = call(utime, lt, account, stack, config, modifiers)?; if !success { return Ok(ActionPhaseOutput { messages: None, @@ -166,6 +172,37 @@ pub fn call_msg( }) } +pub fn call_getter( + utime: u32, + lt: u64, + account: &AccountStuff, + method_id: u32, + args: &[ton_vm::stack::StackItem], + config: &BriefBlockchainConfig, + modifiers: &ton_vm::executor::BehaviorModifiers, +) -> Result { + let mut stack = Stack::new(); + for arg in args { + stack.push(arg.clone()); + } + stack.push(ton_vm::int!(method_id)); + + let (mut engine, exit_code, is_ok) = call(utime, lt, account, stack, config, modifiers)?; + + Ok(VmGetterOutput { + stack: engine.withdraw_stack().storage, + exit_code, + is_ok, + }) +} + +#[derive(Debug, Clone)] +pub struct VmGetterOutput { + pub stack: Vec, + pub exit_code: i32, + pub is_ok: bool, +} + fn build_contract_info( address: &MsgAddressInt, balance: &CurrencyCollection, @@ -192,7 +229,7 @@ pub struct ActionPhaseOutput { pub exit_code: i32, } -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Clone, Copy)] pub enum ExecutionError { #[error("Failed to serialize message")] FailedToSerializeMessage, diff --git a/nekoton-utils/src/cell.rs b/nekoton-utils/src/cell.rs index ad2d088b4..5992aa404 100644 --- a/nekoton-utils/src/cell.rs +++ b/nekoton-utils/src/cell.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use ton_block::{Deserializable, MaybeDeserialize}; use ton_types::{BuilderData, Cell, CellType, IBitstring, LevelMask, UInt256}; const EMPTY_CELL_HASH: [u8; 32] = [ @@ -56,3 +57,21 @@ pub fn make_pruned_branch_cell(cell: &Cell, merkle_depth: u8) -> Result { } result.into_cell() } + +pub fn deserialize_account_stuff(cell: Cell) -> Result { + let slice = &mut ton_types::SliceData::load_cell(cell)?; + Ok(ton_block::AccountStuff { + addr: Deserializable::construct_from(slice)?, + storage_stat: Deserializable::construct_from(slice)?, + storage: ton_block::AccountStorage { + last_trans_lt: Deserializable::construct_from(slice)?, + balance: Deserializable::construct_from(slice)?, + state: Deserializable::construct_from(slice)?, + init_code_hash: if slice.remaining_bits() > 0 { + UInt256::read_maybe_from(slice)? + } else { + None + }, + }, + }) +} diff --git a/nekoton-utils/src/lib.rs b/nekoton-utils/src/lib.rs index 01c69d8e2..181e402d0 100644 --- a/nekoton-utils/src/lib.rs +++ b/nekoton-utils/src/lib.rs @@ -55,6 +55,7 @@ pub use self::address::*; pub use self::cell::*; pub use self::clock::*; +pub use self::crc::crc_16; #[cfg(feature = "encryption")] pub use self::encryption::*; pub use self::serde_helpers::*; diff --git a/nekoton-utils/src/serde_helpers.rs b/nekoton-utils/src/serde_helpers.rs index 557b25c9e..b1f28163f 100644 --- a/nekoton-utils/src/serde_helpers.rs +++ b/nekoton-utils/src/serde_helpers.rs @@ -697,8 +697,6 @@ pub mod serde_ton_block { } pub mod serde_account_stuff { - use ton_block::Deserializable; - use super::*; #[inline(always)] @@ -713,28 +711,10 @@ pub mod serde_account_stuff { where D: serde::Deserializer<'de>, { - use ton_block::MaybeDeserialize; - let data = String::deserialize(deserializer)?; let bytes = base64::decode(data).map_err(D::Error::custom)?; ton_types::deserialize_tree_of_cells(&mut bytes.as_slice()) - .and_then(|cell| { - let slice = &mut ton_types::SliceData::load_cell(cell)?; - Ok(ton_block::AccountStuff { - addr: Deserializable::construct_from(slice)?, - storage_stat: Deserializable::construct_from(slice)?, - storage: ton_block::AccountStorage { - last_trans_lt: Deserializable::construct_from(slice)?, - balance: Deserializable::construct_from(slice)?, - state: Deserializable::construct_from(slice)?, - init_code_hash: if slice.remaining_bits() > 0 { - UInt256::read_maybe_from(slice)? - } else { - None - }, - }, - }) - }) + .and_then(crate::deserialize_account_stuff) .map_err(D::Error::custom) } }