From 0a4f0bec9b0a2568288fc26e29bc7129cfb9d89b Mon Sep 17 00:00:00 2001 From: jesse Date: Wed, 24 Sep 2025 00:47:36 +0800 Subject: [PATCH 01/13] add variant NoneForFuzz --- api/types/src/transaction.rs | 1 + aptos-move/aptos-vm/src/aptos_vm.rs | 80 +++++++++++ .../src/transaction_filter.rs | 2 + types/src/transaction/authenticator.rs | 128 +++++++++++------- 4 files changed, 161 insertions(+), 50 deletions(-) diff --git a/api/types/src/transaction.rs b/api/types/src/transaction.rs index afd6db5befea3..94b8046dadf5a 100755 --- a/api/types/src/transaction.rs +++ b/api/types/src/transaction.rs @@ -2331,6 +2331,7 @@ impl From for TransactionSignature { .into(), ), SingleSender { sender } => Self::SingleSender(sender.into()), + NoneForFuzz => Self::NoAccountSignature(NoAccountSignature), } } } diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index c7da85249c10f..a4175de638c05 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -392,6 +392,86 @@ impl AptosVM { self.move_vm.env.clone() } + /// Execute a single transaction payload (EntryFunction or Script) without signature, + /// nonce, or gas checks. Intended for fuzzing and simulation environments. + pub fn execute_user_payload_no_checking( + &self, + resolver: &impl AptosMoveResolver, + code_storage: &impl move_vm_runtime::CodeStorage, + payload: &aptos_types::transaction::TransactionPayload, + ) -> anyhow::Result<( + aptos_types::write_set::WriteSet, + Vec, + )> { + let mut session = self.new_session(resolver, SessionId::void(), None); + + let mut gas = UnmeteredGasMeter; + let storage = TraversalStorage::new(); + let mut traversal = TraversalContext::new(&storage); + + match payload { + TransactionPayload::EntryFunction(entry) => { + let module_id = entry.module().clone(); + let func_name = entry.function(); + let ty_args = entry.ty_args().to_vec(); + let args: Vec<&[u8]> = entry.args().iter().map(|v| v.as_slice()).collect(); + + session + .execute_function_bypass_visibility( + &module_id, + func_name, + ty_args, + args, + &mut gas, + &mut traversal, + code_storage, + ) + .map_err(|e| anyhow::anyhow!("entry function execution failed: {e:?}"))?; + }, + TransactionPayload::Script(script) => { + let mv_args: Vec = + script.args().iter().cloned().map(MoveValue::from).collect(); + let args: Vec> = serialize_values(&mv_args); + + move_vm_runtime::dispatch_loader!(code_storage, loader, { + let function = loader + .load_script( + &move_vm_runtime::LegacyLoaderConfig::unmetered(), + &mut gas, + &mut traversal, + &script.code(), + &script.ty_args().to_vec(), + ) + .map_err(|e| anyhow::anyhow!("load_script failed: {e:?}"))?; + + session + .execute_loaded_function(function, args, &mut gas, &mut traversal, &loader) + .map_err(|e| anyhow::anyhow!("script execute failed: {e:?}"))?; + }); + }, + _ => anyhow::bail!("Unsupported payload type for no-check execution"), + } + + let change_set = session + .finish( + &aptos_vm_types::storage::change_set_configs::ChangeSetConfigs:: + unlimited_at_gas_feature_version(0), + code_storage, + ) + .map_err(|e| anyhow::anyhow!("session finish failed: {e:?}"))?; + + let storage_change_set = change_set + .try_combine_into_storage_change_set( + aptos_vm_types::module_write_set::ModuleWriteSet::empty(), + ) + .map_err(|e| anyhow::anyhow!("convert change set failed: {e:?}"))?; + + let write_set = storage_change_set.write_set().clone(); + let events = storage_change_set.events().to_vec(); + + Ok((write_set, events)) + } + /// Sets execution concurrency level when invoked the first time. pub fn set_concurrency_level_once(mut concurrency_level: usize) { concurrency_level = min(concurrency_level, num_cpus::get()); diff --git a/crates/aptos-transaction-filters/src/transaction_filter.rs b/crates/aptos-transaction-filters/src/transaction_filter.rs index 18bfcbb3f7029..64850051df111 100644 --- a/crates/aptos-transaction-filters/src/transaction_filter.rs +++ b/crates/aptos-transaction-filters/src/transaction_filter.rs @@ -436,6 +436,7 @@ fn matches_transaction_authenticator_address( ) -> bool { // Match all variants explicitly to ensure future enum changes are caught during compilation match signed_transaction.authenticator_ref() { + TransactionAuthenticator::NoneForFuzz => false, TransactionAuthenticator::Ed25519 { .. } | TransactionAuthenticator::MultiEd25519 { .. } => false, TransactionAuthenticator::MultiAgent { @@ -477,6 +478,7 @@ fn matches_transaction_authenticator_public_key( ) -> bool { // Match all variants explicitly to ensure future enum changes are caught during compilation match signed_transaction.authenticator_ref() { + TransactionAuthenticator::NoneForFuzz => false, TransactionAuthenticator::Ed25519 { public_key, .. } => { compare_ed25519_public_key(public_key, any_public_key) }, diff --git a/types/src/transaction/authenticator.rs b/types/src/transaction/authenticator.rs index a16ab68b491ea..cb38a47bea52b 100644 --- a/types/src/transaction/authenticator.rs +++ b/types/src/transaction/authenticator.rs @@ -100,6 +100,9 @@ pub enum TransactionAuthenticator { SingleSender { sender: AccountAuthenticator, }, + /// No authentication, intended only for fuzzing/testing flows. + /// Skips all signature checks. + NoneForFuzz, } impl TransactionAuthenticator { @@ -157,6 +160,11 @@ impl TransactionAuthenticator { Self::SingleSender { sender } } + /// Create a no-authenticator variant for fuzzing. Bypasses all verification. + pub fn none_for_fuzz() -> Self { + Self::NoneForFuzz + } + /// Return Ok if all AccountAuthenticator's public keys match their signatures, Err otherwise pub fn verify(&self, raw_txn: &RawTransaction) -> Result<()> { let num_sigs: usize = self.sender().number_of_signatures() @@ -169,6 +177,7 @@ impl TransactionAuthenticator { return Err(Error::new(AuthenticationError::MaxSignaturesExceeded)); } match self { + Self::NoneForFuzz => Ok(()), Self::Ed25519 { public_key, signature, @@ -244,6 +253,7 @@ impl TransactionAuthenticator { pub fn sender(&self) -> AccountAuthenticator { match self { + Self::NoneForFuzz => AccountAuthenticator::NoAccountAuthenticator, Self::Ed25519 { public_key, signature, @@ -260,7 +270,10 @@ impl TransactionAuthenticator { pub fn secondary_signer_addresses(&self) -> Vec { match self { - Self::Ed25519 { .. } | Self::MultiEd25519 { .. } | Self::SingleSender { .. } => { + Self::NoneForFuzz + | Self::Ed25519 { .. } + | Self::MultiEd25519 { .. } + | Self::SingleSender { .. } => { vec![] }, Self::FeePayer { @@ -278,7 +291,10 @@ impl TransactionAuthenticator { pub fn secondary_signers(&self) -> Vec { match self { - Self::Ed25519 { .. } | Self::MultiEd25519 { .. } | Self::SingleSender { .. } => { + Self::NoneForFuzz + | Self::Ed25519 { .. } + | Self::MultiEd25519 { .. } + | Self::SingleSender { .. } => { vec![] }, Self::FeePayer { @@ -297,7 +313,8 @@ impl TransactionAuthenticator { pub fn fee_payer_address(&self) -> Option { match self { - Self::Ed25519 { .. } + Self::NoneForFuzz + | Self::Ed25519 { .. } | Self::MultiEd25519 { .. } | Self::MultiAgent { .. } | Self::SingleSender { .. } => None, @@ -313,7 +330,8 @@ impl TransactionAuthenticator { pub fn fee_payer_signer(&self) -> Option { match self { - Self::Ed25519 { .. } + Self::NoneForFuzz + | Self::Ed25519 { .. } | Self::MultiEd25519 { .. } | Self::MultiAgent { .. } | Self::SingleSender { .. } => None, @@ -330,7 +348,8 @@ impl TransactionAuthenticator { pub fn all_signers(&self) -> Vec { match self { // This is to ensure that any new TransactionAuthenticator variant must update this function. - Self::Ed25519 { .. } + Self::NoneForFuzz + | Self::Ed25519 { .. } | Self::MultiEd25519 { .. } | Self::MultiAgent { .. } | Self::FeePayer { .. } @@ -399,6 +418,9 @@ impl TransactionAuthenticator { impl fmt::Display for TransactionAuthenticator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::NoneForFuzz => { + write!(f, "TransactionAuthenticator[scheme: NoneForFuzz]") + }, Self::Ed25519 { .. } => { write!( f, @@ -1718,52 +1740,52 @@ mod tests { let signed_txn = SignedTransaction::new_single_sender(raw_txn.clone(), account_auth); signed_txn.verify_signature().unwrap_err(); - let mk_auth_01 = MultiKeyAuthenticator::new(multi_key.clone(), vec![ - (0, signature0.clone()), - (1, signature1.clone()), - ]) + let mk_auth_01 = MultiKeyAuthenticator::new( + multi_key.clone(), + vec![(0, signature0.clone()), (1, signature1.clone())], + ) .unwrap(); let single_key_authenticators = mk_auth_01.to_single_key_authenticators().unwrap(); - assert_eq!(single_key_authenticators, vec![ - sender0_auth.clone(), - sender1_auth.clone() - ]); + assert_eq!( + single_key_authenticators, + vec![sender0_auth.clone(), sender1_auth.clone()] + ); let account_auth = AccountAuthenticator::multi_key(mk_auth_01); let signed_txn = SignedTransaction::new_single_sender(raw_txn.clone(), account_auth); signed_txn.verify_signature().unwrap(); - let mk_auth_02 = MultiKeyAuthenticator::new(multi_key.clone(), vec![ - (0, signature0.clone()), - (2, signature1.clone()), - ]) + let mk_auth_02 = MultiKeyAuthenticator::new( + multi_key.clone(), + vec![(0, signature0.clone()), (2, signature1.clone())], + ) .unwrap(); let single_key_authenticators = mk_auth_02.to_single_key_authenticators().unwrap(); - assert_eq!(single_key_authenticators, vec![ - sender0_auth.clone(), - sender1_auth.clone() - ]); + assert_eq!( + single_key_authenticators, + vec![sender0_auth.clone(), sender1_auth.clone()] + ); let account_auth = AccountAuthenticator::multi_key(mk_auth_02); let signed_txn = SignedTransaction::new_single_sender(raw_txn.clone(), account_auth); signed_txn.verify_signature().unwrap(); - let mk_auth_12 = MultiKeyAuthenticator::new(multi_key.clone(), vec![ - (1, signature1.clone()), - (2, signature1.clone()), - ]) + let mk_auth_12 = MultiKeyAuthenticator::new( + multi_key.clone(), + vec![(1, signature1.clone()), (2, signature1.clone())], + ) .unwrap(); let single_key_authenticators = mk_auth_12.to_single_key_authenticators().unwrap(); - assert_eq!(single_key_authenticators, vec![ - sender1_auth.clone(), - sender1_auth.clone() - ]); + assert_eq!( + single_key_authenticators, + vec![sender1_auth.clone(), sender1_auth.clone()] + ); let account_auth = AccountAuthenticator::multi_key(mk_auth_12); let signed_txn = SignedTransaction::new_single_sender(raw_txn.clone(), account_auth); signed_txn.verify_signature().unwrap(); - MultiKeyAuthenticator::new(multi_key.clone(), vec![ - (0, signature0.clone()), - (0, signature0.clone()), - ]) + MultiKeyAuthenticator::new( + multi_key.clone(), + vec![(0, signature0.clone()), (0, signature0.clone())], + ) .unwrap_err(); } @@ -2021,10 +2043,10 @@ mod tests { let second_sender0_auth = AccountAuthenticator::single_key(second_sender0_sk_auth.clone()); let second_sender1_auth = AccountAuthenticator::single_key(second_sender1_sk_auth.clone()); let fee_payer_multi_key_auth = AccountAuthenticator::multi_key( - MultiKeyAuthenticator::new(multi_key.clone(), vec![ - (0, fee_payer0_sig.clone()), - (1, fee_payer1_sig.clone()), - ]) + MultiKeyAuthenticator::new( + multi_key.clone(), + vec![(0, fee_payer0_sig.clone()), (1, fee_payer1_sig.clone())], + ) .unwrap(), ); @@ -2037,21 +2059,27 @@ mod tests { ); let authenticators = txn_auth.all_signers(); - assert_eq!(authenticators, vec![ - sender_auth, - second_sender0_auth, - second_sender1_auth, - fee_payer_multi_key_auth - ]); + assert_eq!( + authenticators, + vec![ + sender_auth, + second_sender0_auth, + second_sender1_auth, + fee_payer_multi_key_auth + ] + ); let single_key_authenticators = txn_auth.to_single_key_authenticators().unwrap(); - assert_eq!(single_key_authenticators, vec![ - sender_sk_auth, - second_sender0_sk_auth, - second_sender1_sk_auth, - fee_payer0_sk_auth, - fee_payer1_sk_auth - ]); + assert_eq!( + single_key_authenticators, + vec![ + sender_sk_auth, + second_sender0_sk_auth, + second_sender1_sk_auth, + fee_payer0_sk_auth, + fee_payer1_sk_auth + ] + ); } #[test] From 98e5aee7c764898a550d354869f4b580e0ee653f Mon Sep 17 00:00:00 2001 From: jesse Date: Thu, 25 Sep 2025 20:15:44 +0800 Subject: [PATCH 02/13] support contextual execute --- aptos-move/aptos-vm/src/aptos_vm.rs | 58 +++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index a4175de638c05..3cd86eccc0d5a 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -391,19 +391,69 @@ impl AptosVM { pub fn environment(&self) -> AptosEnvironment { self.move_vm.env.clone() } - - /// Execute a single transaction payload (EntryFunction or Script) without signature, - /// nonce, or gas checks. Intended for fuzzing and simulation environments. + + /// If `sender` is provided, creates a session with transaction context that allows + /// Move code to access `transaction_context::sender()` and related functions. + /// If `sender` is None, uses void session for performance. pub fn execute_user_payload_no_checking( &self, resolver: &impl AptosMoveResolver, code_storage: &impl move_vm_runtime::CodeStorage, payload: &aptos_types::transaction::TransactionPayload, + sender: Option, ) -> anyhow::Result<( aptos_types::write_set::WriteSet, Vec, )> { - let mut session = self.new_session(resolver, SessionId::void(), None); + let (session_id, user_context) = if let Some(sender_addr) = sender { + // Get sequence number + let sequence_number = match resolver.get_resource( + &sender_addr, + &aptos_types::account_config::AccountResource::struct_tag(), + ) { + Ok(Some(resource_bytes)) => { + match bcs::from_bytes::(&resource_bytes) { + Ok(account_resource) => account_resource.sequence_number(), + Err(_) => 0, // Default to 0 if deserialization fails + } + }, + _ => 0, + }; + + // Create UserTransactionContext for enhanced testing + let user_ctx = aptos_types::transaction::user_transaction_context::UserTransactionContext::new( + sender_addr, // sender + vec![], // secondary_signers + sender_addr, // gas_payer (same as sender) + 1_000_000, // max_gas_amount (1M units) + 0, // gas_unit_price (0 octas) + self.chain_id().id(), // chain_id + None, // entry_function_payload + None, // multisig_payload + None, // transaction_index + ); + + // Create SessionId::Txn with sender info + let script_hash = match payload { + aptos_types::transaction::TransactionPayload::Script(script) => { + // For scripts, calculate the actual hash to maintain semantic correctness + aptos_crypto::HashValue::sha3_256_of(script.code()).to_vec() + }, + _ => vec![], // EntryFunction and others use empty hash + }; + + let session_id = SessionId::Txn { + sender: sender_addr, + sequence_number, + script_hash, + }; + + (session_id, Some(user_ctx)) + } else { + (SessionId::void(), None) + }; + + let mut session = self.new_session(resolver, session_id, user_context); let mut gas = UnmeteredGasMeter; let storage = TraversalStorage::new(); From 36a5d5c80f3bd3baae74031adeb07ec717fad8cc Mon Sep 17 00:00:00 2001 From: jesse Date: Thu, 25 Sep 2025 20:34:20 +0800 Subject: [PATCH 03/13] fix resolve --- aptos-move/aptos-vm/src/aptos_vm.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 3cd86eccc0d5a..fe70f5eea86ff 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -407,11 +407,13 @@ impl AptosVM { )> { let (session_id, user_context) = if let Some(sender_addr) = sender { // Get sequence number - let sequence_number = match resolver.get_resource( + let sequence_number = match resolver.get_resource_bytes_with_metadata_and_layout( &sender_addr, &aptos_types::account_config::AccountResource::struct_tag(), + &[], + None, ) { - Ok(Some(resource_bytes)) => { + Ok((Some(resource_bytes), _)) => { match bcs::from_bytes::(&resource_bytes) { Ok(account_resource) => account_resource.sequence_number(), Err(_) => 0, // Default to 0 if deserialization fails From 6bcfc7585bf87775e4372c86333d29c5ae71de9f Mon Sep 17 00:00:00 2001 From: jesse Date: Fri, 26 Sep 2025 00:23:34 +0800 Subject: [PATCH 04/13] fix entryFunc args dealing --- aptos-move/aptos-vm/src/aptos_vm.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index fe70f5eea86ff..44a93832da4d8 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -466,7 +466,15 @@ impl AptosVM { let module_id = entry.module().clone(); let func_name = entry.function(); let ty_args = entry.ty_args().to_vec(); - let args: Vec<&[u8]> = entry.args().iter().map(|v| v.as_slice()).collect(); + // If a sender was provided (or even if not), inject a signer argument in front. + // This is required for most Aptos entry functions which take a signer/&signer as + // the first parameter. When sender is None, default to ZERO address. + let signer_addr = sender.unwrap_or(move_core_types::account_address::AccountAddress::ZERO); + let mut arg_bytes: Vec> = move_core_types::value::serialize_values(&vec![ + move_core_types::value::MoveValue::Signer(signer_addr), + ]); + arg_bytes.extend(entry.args().iter().cloned()); + let args: Vec<&[u8]> = arg_bytes.iter().map(|v| v.as_slice()).collect(); session .execute_function_bypass_visibility( From 4c7fa4041df43c68f6cea007461e9d8f26a5d529 Mon Sep 17 00:00:00 2001 From: jesse Date: Sat, 27 Sep 2025 01:54:19 +0800 Subject: [PATCH 05/13] ret code --- aptos-move/aptos-vm/src/aptos_vm.rs | 33 +++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 44a93832da4d8..469a635fd603a 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -401,10 +401,10 @@ impl AptosVM { code_storage: &impl move_vm_runtime::CodeStorage, payload: &aptos_types::transaction::TransactionPayload, sender: Option, - ) -> anyhow::Result<( + ) -> Result<( aptos_types::write_set::WriteSet, Vec, - )> { + ), move_core_types::vm_status::VMStatus> { let (session_id, user_context) = if let Some(sender_addr) = sender { // Get sequence number let sequence_number = match resolver.get_resource_bytes_with_metadata_and_layout( @@ -486,7 +486,7 @@ impl AptosVM { &mut traversal, code_storage, ) - .map_err(|e| anyhow::anyhow!("entry function execution failed: {e:?}"))?; + .map_err(|e| e.into_vm_status())?; }, TransactionPayload::Script(script) => { let mv_args: Vec = @@ -502,14 +502,20 @@ impl AptosVM { &script.code(), &script.ty_args().to_vec(), ) - .map_err(|e| anyhow::anyhow!("load_script failed: {e:?}"))?; + .map_err(|e| e.into_vm_status())?; session .execute_loaded_function(function, args, &mut gas, &mut traversal, &loader) - .map_err(|e| anyhow::anyhow!("script execute failed: {e:?}"))?; + .map_err(|e| e.into_vm_status())?; + }); + }, + _ => { + return Err(move_core_types::vm_status::VMStatus::Error { + status_code: move_core_types::vm_status::StatusCode::UNKNOWN_STATUS, + sub_status: None, + message: Some("Unsupported payload type for no-check execution".to_string()), }); }, - _ => anyhow::bail!("Unsupported payload type for no-check execution"), } let change_set = session @@ -518,13 +524,22 @@ impl AptosVM { unlimited_at_gas_feature_version(0), code_storage, ) - .map_err(|e| anyhow::anyhow!("session finish failed: {e:?}"))?; + .map_err(|e| e.into_vm_status())?; - let storage_change_set = change_set + let storage_change_set = match change_set .try_combine_into_storage_change_set( aptos_vm_types::module_write_set::ModuleWriteSet::empty(), ) - .map_err(|e| anyhow::anyhow!("convert change set failed: {e:?}"))?; + { + Ok(cs) => cs, + Err(_e) => { + return Err(move_core_types::vm_status::VMStatus::Error { + status_code: move_core_types::vm_status::StatusCode::UNKNOWN_STATUS, + sub_status: None, + message: Some("convert change set failed".to_string()), + }); + } + }; let write_set = storage_change_set.write_set().clone(); let events = storage_change_set.events().to_vec(); From 0899856e93d3d00972b9ded73e455186b0f363a2 Mon Sep 17 00:00:00 2001 From: jesse Date: Wed, 1 Oct 2025 00:07:28 +0800 Subject: [PATCH 06/13] program counter --- aptos-move/aptos-vm/src/aptos_vm.rs | 32 ++++++++++++ .../move/move-vm/runtime/src/tracing.rs | 49 ++++++++++++------- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 469a635fd603a..06d48bcce7815 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -140,6 +140,7 @@ use move_vm_runtime::{ InstantiatedFunctionLoader, LegacyLoaderConfig, ModuleStorage, RuntimeEnvironment, ScriptLoader, WithRuntimeEnvironment, }; +use move_vm_runtime::tracing::{begin_pc_capture, end_pc_capture_take}; use move_vm_types::gas::{DependencyKind, GasMeter, UnmeteredGasMeter}; use num_cpus; use once_cell::sync::OnceCell; @@ -149,6 +150,7 @@ use std::{ marker::Sync, sync::Arc, }; +use std::sync::atomic::{AtomicU64, Ordering}; static EXECUTION_CONCURRENCY_LEVEL: OnceCell = OnceCell::new(); static NUM_EXECUTION_SHARD: OnceCell = OnceCell::new(); @@ -547,6 +549,36 @@ impl AptosVM { Ok((write_set, events)) } + /// Execute without checks and always return (Result<(WriteSet, Events), VMStatus>, pc_index_sequence). + /// The index sequence contains the instruction offsets (pc) observed during execution. + pub fn execute_user_payload_no_checking_with_counter( + &self, + resolver: &impl AptosMoveResolver, + code_storage: &impl move_vm_runtime::CodeStorage, + payload: &aptos_types::transaction::TransactionPayload, + sender: Option, + ) -> ( + Result< + ( + aptos_types::write_set::WriteSet, + Vec, + ), + move_core_types::vm_status::VMStatus, + >, + Vec, + ) { + // Run the underlying no-check execution while capturing pc into thread-local buffer. + begin_pc_capture(); + let result = self.execute_user_payload_no_checking( + resolver, + code_storage, + payload, + sender, + ); + let pcs = end_pc_capture_take(); + (result, pcs) + } + /// Sets execution concurrency level when invoked the first time. pub fn set_concurrency_level_once(mut concurrency_level: usize) { concurrency_level = min(concurrency_level, num_cpus::get()); diff --git a/third_party/move/move-vm/runtime/src/tracing.rs b/third_party/move/move-vm/runtime/src/tracing.rs index dfb1884afc588..fe1b70d5a76a5 100644 --- a/third_party/move/move-vm/runtime/src/tracing.rs +++ b/third_party/move/move-vm/runtime/src/tracing.rs @@ -4,14 +4,13 @@ #[cfg(any(debug_assertions, feature = "debugging"))] use crate::debug::DebugContext; -#[cfg(any(debug_assertions, feature = "debugging"))] use crate::{interpreter::InterpreterDebugInterface, loader::LoadedFunction, RuntimeEnvironment}; -#[cfg(any(debug_assertions, feature = "debugging"))] use ::{ move_binary_format::file_format::Bytecode, move_vm_types::values::Locals, once_cell::sync::Lazy, std::{ + cell::RefCell, env, fs::{File, OpenOptions}, io::Write, @@ -19,18 +18,14 @@ use ::{ }, }; -#[cfg(any(debug_assertions, feature = "debugging"))] const MOVE_VM_TRACING_ENV_VAR_NAME: &str = "MOVE_VM_TRACE"; -#[cfg(any(debug_assertions, feature = "debugging"))] const MOVE_VM_STEPPING_ENV_VAR_NAME: &str = "MOVE_VM_STEP"; -#[cfg(any(debug_assertions, feature = "debugging"))] static FILE_PATH: Lazy = Lazy::new(|| { env::var(MOVE_VM_TRACING_ENV_VAR_NAME).unwrap_or_else(|_| "move_vm_trace.trace".to_string()) }); -#[cfg(any(debug_assertions, feature = "debugging"))] pub static TRACING_ENABLED: Lazy = Lazy::new(|| env::var(MOVE_VM_TRACING_ENV_VAR_NAME).is_ok()); @@ -38,24 +33,36 @@ pub static TRACING_ENABLED: Lazy = static DEBUGGING_ENABLED: Lazy = Lazy::new(|| env::var(MOVE_VM_STEPPING_ENV_VAR_NAME).is_ok()); -#[cfg(any(debug_assertions, feature = "debugging"))] pub static LOGGING_FILE_WRITER: Lazy>> = Lazy::new(|| { let file = OpenOptions::new() .create(true) .append(true) .open(&*FILE_PATH) .unwrap(); - Mutex::new(std::io::BufWriter::with_capacity( - 4096 * 1024, /* 4096KB */ - file, - )) + Mutex::new(std::io::BufWriter::with_capacity(4096 * 1024, file)) }); +// Thread-local, in-memory pc capture support. +thread_local! { + static TL_PC_CAPTURE_ENABLED: RefCell = RefCell::new(false); + static TL_PC_BUFFER: RefCell> = RefCell::new(Vec::new()); +} + +/// Begin capturing program counters for the current thread. +pub fn begin_pc_capture() { + TL_PC_CAPTURE_ENABLED.with(|e| *e.borrow_mut() = true); + TL_PC_BUFFER.with(|buf| buf.borrow_mut().clear()); +} + +/// Stop capturing and return the captured program counters for the current thread. +pub fn end_pc_capture_take() -> Vec { + TL_PC_CAPTURE_ENABLED.with(|e| *e.borrow_mut() = false); + TL_PC_BUFFER.with(|buf| std::mem::take(&mut *buf.borrow_mut())) +} + #[cfg(any(debug_assertions, feature = "debugging"))] static DEBUG_CONTEXT: Lazy> = Lazy::new(|| Mutex::new(DebugContext::new())); -// Only include in debug builds -#[cfg(any(debug_assertions, feature = "debugging"))] pub(crate) fn trace( function: &LoadedFunction, locals: &Locals, @@ -64,17 +71,25 @@ pub(crate) fn trace( runtime_environment: &RuntimeEnvironment, interpreter: &dyn InterpreterDebugInterface, ) { + // Always attempt to capture into thread-local buffer when enabled. + TL_PC_CAPTURE_ENABLED.with(|enabled| { + if *enabled.borrow() { + TL_PC_BUFFER.with(|buf| buf.borrow_mut().push(pc as u32)); + } + }); + if *TRACING_ENABLED { - let buf_writer = &mut *LOGGING_FILE_WRITER.lock().unwrap(); - buf_writer + let writer = &mut *LOGGING_FILE_WRITER.lock().unwrap(); + writer .write_fmt(format_args!( "{},{}\n", function.name_as_pretty_string(), pc, )) .unwrap(); - buf_writer.flush().unwrap(); + writer.flush().unwrap(); } + #[cfg(any(debug_assertions, feature = "debugging"))] if *DEBUGGING_ENABLED { DEBUG_CONTEXT.lock().unwrap().debug_loop( function, @@ -90,8 +105,6 @@ pub(crate) fn trace( #[macro_export] macro_rules! trace { ($function_desc:expr, $locals:expr, $pc:expr, $instr:tt, $resolver:expr, $interp:expr) => { - // Only include this code in debug releases - #[cfg(any(debug_assertions, feature = "debugging"))] $crate::tracing::trace(&$function_desc, $locals, $pc, &$instr, $resolver, $interp) }; } From 41406ab53835bfb1343e007df6957d18a4ea139d Mon Sep 17 00:00:00 2001 From: jesse Date: Wed, 1 Oct 2025 15:37:59 +0800 Subject: [PATCH 07/13] define errorkind --- aptos-move/aptos-vm/src/aptos_vm.rs | 82 ++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 06d48bcce7815..dad81c2ea59de 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -142,6 +142,7 @@ use move_vm_runtime::{ }; use move_vm_runtime::tracing::{begin_pc_capture, end_pc_capture_take}; use move_vm_types::gas::{DependencyKind, GasMeter, UnmeteredGasMeter}; +use move_core_types::vm_status as move_vm_status; use num_cpus; use once_cell::sync::OnceCell; use std::{ @@ -151,6 +152,7 @@ use std::{ sync::Arc, }; use std::sync::atomic::{AtomicU64, Ordering}; +use std::panic::{catch_unwind, AssertUnwindSafe}; static EXECUTION_CONCURRENCY_LEVEL: OnceCell = OnceCell::new(); static NUM_EXECUTION_SHARD: OnceCell = OnceCell::new(); @@ -158,6 +160,17 @@ static NUM_PROOF_READING_THREADS: OnceCell = OnceCell::new(); static DISCARD_FAILED_BLOCKS: OnceCell = OnceCell::new(); static PROCESSED_TRANSACTIONS_DETAILED_COUNTERS: OnceCell = OnceCell::new(); +/// Outcome classification for fuzzing/external consumers. +#[derive(Debug, Clone, Copy)] +pub enum ExecOutcomeKind { + Ok, + MoveAbort(u64), + OutOfGas, + InvariantViolation, + OtherError, + Panic, +} + macro_rules! deprecated_module_bundle { () => { VMStatus::error( @@ -407,6 +420,11 @@ impl AptosVM { aptos_types::write_set::WriteSet, Vec, ), move_core_types::vm_status::VMStatus> { + // Guard against Rust-level panics in the VM dispatch path + let inner = || -> Result<( + aptos_types::write_set::WriteSet, + Vec, + ), move_core_types::vm_status::VMStatus> { let (session_id, user_context) = if let Some(sender_addr) = sender { // Get sequence number let sequence_number = match resolver.get_resource_bytes_with_metadata_and_layout( @@ -547,6 +565,14 @@ impl AptosVM { let events = storage_change_set.events().to_vec(); Ok((write_set, events)) + }; + match catch_unwind(AssertUnwindSafe(inner)) { + Ok(res) => res, + Err(_) => Err(move_core_types::vm_status::VMStatus::error( + move_core_types::vm_status::StatusCode::UNKNOWN_STATUS, + Some("panic in execute_user_payload_no_checking".to_string()), + )), + } } /// Execute without checks and always return (Result<(WriteSet, Events), VMStatus>, pc_index_sequence). @@ -566,17 +592,59 @@ impl AptosVM { move_core_types::vm_status::VMStatus, >, Vec, + ExecOutcomeKind, ) { // Run the underlying no-check execution while capturing pc into thread-local buffer. begin_pc_capture(); - let result = self.execute_user_payload_no_checking( - resolver, - code_storage, - payload, - sender, - ); + let result = catch_unwind(AssertUnwindSafe(|| { + self.execute_user_payload_no_checking( + resolver, + code_storage, + payload, + sender, + ) + })); let pcs = end_pc_capture_take(); - (result, pcs) + + match result { + Ok(exec_res) => { + let kind = match &exec_res { + Ok(_) => ExecOutcomeKind::Ok, + Err(vm_status) => match vm_status { + move_vm_status::VMStatus::Executed => ExecOutcomeKind::Ok, + move_vm_status::VMStatus::MoveAbort(_, code) => ExecOutcomeKind::MoveAbort(*code), + move_vm_status::VMStatus::ExecutionFailure { status_code, .. } => { + match status_code { + move_vm_status::StatusCode::OUT_OF_GAS => ExecOutcomeKind::OutOfGas, + _ => match status_code.status_type() { + move_vm_status::StatusType::InvariantViolation => ExecOutcomeKind::InvariantViolation, + _ => ExecOutcomeKind::OtherError, + }, + } + } + move_vm_status::VMStatus::Error { status_code, .. } => match status_code { + move_vm_status::StatusCode::OUT_OF_GAS => ExecOutcomeKind::OutOfGas, + _ => match status_code.status_type() { + move_vm_status::StatusType::InvariantViolation => ExecOutcomeKind::InvariantViolation, + _ => ExecOutcomeKind::OtherError, + }, + }, + }, + }; + (exec_res, pcs, kind) + } + Err(_) => { + // Rust-level panic during execution + ( + Err(move_core_types::vm_status::VMStatus::error( + move_core_types::vm_status::StatusCode::UNKNOWN_STATUS, + Some("panic in execute_user_payload_no_checking".to_string()), + )), + pcs, + ExecOutcomeKind::Panic, + ) + } + } } /// Sets execution concurrency level when invoked the first time. From 2de5e7d07a89bb486128e379c810e42a20bc7704 Mon Sep 17 00:00:00 2001 From: jesse Date: Thu, 9 Oct 2025 22:01:17 +0800 Subject: [PATCH 08/13] with shift tracing --- aptos-move/aptos-vm/src/aptos_vm.rs | 138 ++++++++++-------- .../move/move-vm/runtime/src/interpreter.rs | 6 +- .../move/move-vm/runtime/src/tracing.rs | 86 ++++++++++- 3 files changed, 170 insertions(+), 60 deletions(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index dad81c2ea59de..9bf891a8976d2 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -140,7 +140,7 @@ use move_vm_runtime::{ InstantiatedFunctionLoader, LegacyLoaderConfig, ModuleStorage, RuntimeEnvironment, ScriptLoader, WithRuntimeEnvironment, }; -use move_vm_runtime::tracing::{begin_pc_capture, end_pc_capture_take}; +use move_vm_runtime::tracing::{begin_pc_capture, begin_shift_capture, end_pc_capture_take, end_shift_capture_take}; use move_vm_types::gas::{DependencyKind, GasMeter, UnmeteredGasMeter}; use move_core_types::vm_status as move_vm_status; use num_cpus; @@ -416,11 +416,18 @@ impl AptosVM { code_storage: &impl move_vm_runtime::CodeStorage, payload: &aptos_types::transaction::TransactionPayload, sender: Option, - ) -> Result<( - aptos_types::write_set::WriteSet, - Vec, - ), move_core_types::vm_status::VMStatus> { + ) -> ( + Result<( + aptos_types::write_set::WriteSet, + Vec, + ), move_core_types::vm_status::VMStatus>, + Vec, + Vec, + ExecOutcomeKind, + ) { // Guard against Rust-level panics in the VM dispatch path + begin_pc_capture(); + begin_shift_capture(); let inner = || -> Result<( aptos_types::write_set::WriteSet, Vec, @@ -566,49 +573,12 @@ impl AptosVM { Ok((write_set, events)) }; - match catch_unwind(AssertUnwindSafe(inner)) { - Ok(res) => res, - Err(_) => Err(move_core_types::vm_status::VMStatus::error( - move_core_types::vm_status::StatusCode::UNKNOWN_STATUS, - Some("panic in execute_user_payload_no_checking".to_string()), - )), - } - } - - /// Execute without checks and always return (Result<(WriteSet, Events), VMStatus>, pc_index_sequence). - /// The index sequence contains the instruction offsets (pc) observed during execution. - pub fn execute_user_payload_no_checking_with_counter( - &self, - resolver: &impl AptosMoveResolver, - code_storage: &impl move_vm_runtime::CodeStorage, - payload: &aptos_types::transaction::TransactionPayload, - sender: Option, - ) -> ( - Result< - ( - aptos_types::write_set::WriteSet, - Vec, - ), - move_core_types::vm_status::VMStatus, - >, - Vec, - ExecOutcomeKind, - ) { - // Run the underlying no-check execution while capturing pc into thread-local buffer. - begin_pc_capture(); - let result = catch_unwind(AssertUnwindSafe(|| { - self.execute_user_payload_no_checking( - resolver, - code_storage, - payload, - sender, - ) - })); + let result = catch_unwind(AssertUnwindSafe(inner)); let pcs = end_pc_capture_take(); - + let shifts = end_shift_capture_take(); match result { - Ok(exec_res) => { - let kind = match &exec_res { + Ok(res) => { + let kind = match &res { Ok(_) => ExecOutcomeKind::Ok, Err(vm_status) => match vm_status { move_vm_status::VMStatus::Executed => ExecOutcomeKind::Ok, @@ -631,22 +601,74 @@ impl AptosVM { }, }, }; - (exec_res, pcs, kind) - } - Err(_) => { - // Rust-level panic during execution - ( - Err(move_core_types::vm_status::VMStatus::error( - move_core_types::vm_status::StatusCode::UNKNOWN_STATUS, - Some("panic in execute_user_payload_no_checking".to_string()), - )), - pcs, - ExecOutcomeKind::Panic, - ) + (res, pcs, shifts, kind) } + Err(_) => ( + Err(move_core_types::vm_status::VMStatus::error( + move_core_types::vm_status::StatusCode::UNKNOWN_STATUS, + Some("panic in execute_user_payload_no_checking".to_string()), + )), + pcs, + shifts, + ExecOutcomeKind::Panic, + ), } } + /// Execute without checks and always return (Result<(WriteSet, Events), VMStatus>, pc_index_sequence). + /// The index sequence contains the instruction offsets (pc) observed during execution. + pub fn execute_user_payload_no_checking_with_counter( + &self, + resolver: &impl AptosMoveResolver, + code_storage: &impl move_vm_runtime::CodeStorage, + payload: &aptos_types::transaction::TransactionPayload, + sender: Option, + ) -> ( + Result< + ( + aptos_types::write_set::WriteSet, + Vec, + ), + move_core_types::vm_status::VMStatus, + >, + Vec, + ExecOutcomeKind, + ) { + let (res, pcs, _shifts, kind) = self.execute_user_payload_no_checking( + resolver, + code_storage, + payload, + sender, + ); + (res, pcs, kind) + } + + /// Execute without checks and return shift events captured during execution. + pub fn execute_user_payload_no_checking_with_shift_trace( + &self, + resolver: &impl AptosMoveResolver, + code_storage: &impl move_vm_runtime::CodeStorage, + payload: &aptos_types::transaction::TransactionPayload, + sender: Option, + ) -> ( + Result< + ( + aptos_types::write_set::WriteSet, + Vec, + ), + move_core_types::vm_status::VMStatus, + >, + Vec, + ) { + let (res, _pcs, shifts, _kind) = self.execute_user_payload_no_checking( + resolver, + code_storage, + payload, + sender, + ); + (res, shifts) + } + /// Sets execution concurrency level when invoked the first time. pub fn set_concurrency_level_once(mut concurrency_level: usize) { concurrency_level = min(concurrency_level, num_cpus::get()); diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index fca8fd66bd6ca..b4170cb15b715 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -22,7 +22,9 @@ use crate::{ loader::traits::Loader, ty_depth_checker::TypeDepthChecker, ty_layout_converter::LayoutConverter, }, - trace, LoadedFunction, RuntimeEnvironment, + trace, + tracing::{record_shift_event, ShiftOp}, + LoadedFunction, RuntimeEnvironment, }; use fail::fail_point; use move_binary_format::{ @@ -2452,6 +2454,7 @@ impl Frame { gas_meter.charge_simple_instr(S::Shl)?; let rhs = interpreter.operand_stack.pop_as::()?; let lhs = interpreter.operand_stack.pop_as::()?; + record_shift_event(&self.function, self.pc, ShiftOp::Shl, &lhs, rhs); interpreter .operand_stack .push(lhs.shl_checked(rhs)?.into_value())?; @@ -2460,6 +2463,7 @@ impl Frame { gas_meter.charge_simple_instr(S::Shr)?; let rhs = interpreter.operand_stack.pop_as::()?; let lhs = interpreter.operand_stack.pop_as::()?; + record_shift_event(&self.function, self.pc, ShiftOp::Shr, &lhs, rhs); interpreter .operand_stack .push(lhs.shr_checked(rhs)?.into_value())?; diff --git a/third_party/move/move-vm/runtime/src/tracing.rs b/third_party/move/move-vm/runtime/src/tracing.rs index fe1b70d5a76a5..561ea01305b16 100644 --- a/third_party/move/move-vm/runtime/src/tracing.rs +++ b/third_party/move/move-vm/runtime/src/tracing.rs @@ -7,7 +7,7 @@ use crate::debug::DebugContext; use crate::{interpreter::InterpreterDebugInterface, loader::LoadedFunction, RuntimeEnvironment}; use ::{ move_binary_format::file_format::Bytecode, - move_vm_types::values::Locals, + move_vm_types::values::{IntegerValue, Locals}, once_cell::sync::Lazy, std::{ cell::RefCell, @@ -48,6 +48,90 @@ thread_local! { static TL_PC_BUFFER: RefCell> = RefCell::new(Vec::new()); } +// Thread-local, in-memory shift event capture support. +thread_local! { + static TL_SHIFT_CAPTURE_ENABLED: RefCell = RefCell::new(false); + static TL_SHIFT_BUFFER: RefCell> = RefCell::new(Vec::new()); +} + +#[derive(Clone, Copy, Debug)] +pub enum ShiftOp { + Shl, + Shr, +} + +#[derive(Clone, Debug)] +pub struct ShiftEvent { + pub function: String, + pub pc: u16, + pub op: ShiftOp, + pub lhs: String, + pub rhs: u8, + pub lost_high_bits: bool, +} + +/// Begin capturing shift operations for the current thread. +pub fn begin_shift_capture() { + TL_SHIFT_CAPTURE_ENABLED.with(|e| *e.borrow_mut() = true); + TL_SHIFT_BUFFER.with(|buf| buf.borrow_mut().clear()); +} + +/// Stop capturing and return the captured shift events for the current thread. +pub fn end_shift_capture_take() -> Vec { + TL_SHIFT_CAPTURE_ENABLED.with(|e| *e.borrow_mut() = false); + TL_SHIFT_BUFFER.with(|buf| std::mem::take(&mut *buf.borrow_mut())) +} + +pub(crate) fn record_shift_event( + function: &LoadedFunction, + pc: u16, + op: ShiftOp, + lhs: &IntegerValue, + rhs: u8, +) { + TL_SHIFT_CAPTURE_ENABLED.with(|enabled| { + if *enabled.borrow() { + let event = ShiftEvent { + function: function.name_as_pretty_string(), + pc, + op, + lhs: format_integer_value(lhs), + rhs, + lost_high_bits: match op { ShiftOp::Shl => shl_loses_high_bits(lhs, rhs), ShiftOp::Shr => false }, + }; + TL_SHIFT_BUFFER.with(|buf| buf.borrow_mut().push(event)); + } + }); +} + +fn format_integer_value(v: &IntegerValue) -> String { + match v { + IntegerValue::U8(x) => x.to_string(), + IntegerValue::U16(x) => x.to_string(), + IntegerValue::U32(x) => x.to_string(), + IntegerValue::U64(x) => x.to_string(), + IntegerValue::U128(x) => x.to_string(), + IntegerValue::U256(x) => format!("{}", x), + } +} + +fn shl_loses_high_bits(v: &IntegerValue, n: u8) -> bool { + if n == 0 { return false; } + match v { + IntegerValue::U8(x) => if n < 8 { (*x >> (8 - n)) != 0 } else { false }, + IntegerValue::U16(x) => if n < 16 { (*x >> (16 - n)) != 0 } else { false }, + IntegerValue::U32(x) => if n < 32 { (*x >> (32 - n)) != 0 } else { false }, + IntegerValue::U64(x) => if n < 64 { (*x >> (64 - n)) != 0 } else { false }, + IntegerValue::U128(x) => if n < 128 { (*x >> (128 - n)) != 0 } else { false }, + IntegerValue::U256(x) => { + // compare as string against zero after shifting right by (256-n) + let shift: u8 = (256u32.saturating_sub(n as u32)) as u8; + let shifted = format!("{}", *x >> shift); + shifted != "0" + }, + } +} + /// Begin capturing program counters for the current thread. pub fn begin_pc_capture() { TL_PC_CAPTURE_ENABLED.with(|e| *e.borrow_mut() = true); From 8efd25bc8a02c25dba3b277842f7f1cd8c4e949f Mon Sep 17 00:00:00 2001 From: jesse Date: Mon, 3 Nov 2025 16:42:57 +0800 Subject: [PATCH 09/13] tracing with module_id and function_name --- aptos-move/aptos-vm/src/aptos_vm.rs | 10 +++++++- .../move/move-vm/runtime/src/tracing.rs | 24 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 9bf891a8976d2..4ad9766867bf9 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -151,7 +151,6 @@ use std::{ marker::Sync, sync::Arc, }; -use std::sync::atomic::{AtomicU64, Ordering}; use std::panic::{catch_unwind, AssertUnwindSafe}; static EXECUTION_CONCURRENCY_LEVEL: OnceCell = OnceCell::new(); @@ -160,6 +159,15 @@ static NUM_PROOF_READING_THREADS: OnceCell = OnceCell::new(); static DISCARD_FAILED_BLOCKS: OnceCell = OnceCell::new(); static PROCESSED_TRANSACTIONS_DETAILED_COUNTERS: OnceCell = OnceCell::new(); +/// Fuzzer sender address containing "FUZZER_SENDER" in ASCII +/// 0x000000000000000000000000000000000000000046555A5A45525F53454E444552 +pub const FUZZER_SENDER: AccountAddress = AccountAddress::new([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x46, 0x55, 0x5A, 0x5A, 0x45, 0x52, // "FUZZER" + 0x5F, 0x53, 0x45, 0x4E, 0x44, 0x45, 0x52, 0x00, // "_SENDE\0" +]); + /// Outcome classification for fuzzing/external consumers. #[derive(Debug, Clone, Copy)] pub enum ExecOutcomeKind { diff --git a/third_party/move/move-vm/runtime/src/tracing.rs b/third_party/move/move-vm/runtime/src/tracing.rs index 561ea01305b16..ea75b09d1b83f 100644 --- a/third_party/move/move-vm/runtime/src/tracing.rs +++ b/third_party/move/move-vm/runtime/src/tracing.rs @@ -158,7 +158,29 @@ pub(crate) fn trace( // Always attempt to capture into thread-local buffer when enabled. TL_PC_CAPTURE_ENABLED.with(|enabled| { if *enabled.borrow() { - TL_PC_BUFFER.with(|buf| buf.borrow_mut().push(pc as u32)); + // Compute a global PC index that is stable across functions by + // hashing module address, module name, function name, and local pc. + fn hash32(data: &[u8]) -> u32 { + let mut hash: u32 = 0x811C9DC5; // FNV-1a 32-bit offset basis + for &b in data { + hash ^= b as u32; + hash = hash.wrapping_mul(0x01000193); + } + hash + } + + let module_id = function.module_or_script_id(); + let func_name = function.name_id(); + + // Build bytes: address || module_name || function_name || pc + let mut bytes = Vec::with_capacity(32 + module_id.name().as_str().len() + func_name.as_str().len() + 4); + bytes.extend_from_slice(module_id.address().as_ref()); + bytes.extend_from_slice(module_id.name().as_str().as_bytes()); + bytes.extend_from_slice(func_name.as_str().as_bytes()); + bytes.extend_from_slice(&(pc as u32).to_le_bytes()); + let global_pc = hash32(&bytes); + + TL_PC_BUFFER.with(|buf| buf.borrow_mut().push(global_pc)); } }); From e9a1d181c58cb9d80c3db4d1072c400591647041 Mon Sep 17 00:00:00 2001 From: jesse Date: Tue, 4 Nov 2025 22:11:41 +0800 Subject: [PATCH 10/13] add aip-102 impl from runtianz/aptos_intent --- Cargo.lock | 97 +++- Cargo.toml | 2 + api/Cargo.toml | 1 + ..._test_intent_execution_and_simulation.json | 53 ++ api/src/tests/transactions_test.rs | 68 +++ api/test-context/src/test_context.rs | 6 + api/types/Cargo.toml | 1 + api/types/src/convert.rs | 66 ++- api/types/src/transaction.rs | 19 + aptos-move/aptos-intent/Cargo.toml | 37 ++ .../aptos-intent-npm/dist/aptos_intent.d.ts | 132 +++++ .../aptos-intent/aptos-intent-npm/entry.js | 5 + .../aptos-intent-npm/package.json | 38 ++ .../aptos-intent-npm/rollup.config.mjs | 18 + aptos-move/aptos-intent/index.js | 79 +++ aptos-move/aptos-intent/package.json | 19 + aptos-move/aptos-intent/src/builder.rs | 478 +++++++++++++++++ aptos-move/aptos-intent/src/codegen.rs | 505 ++++++++++++++++++ aptos-move/aptos-intent/src/decompiler.rs | 235 ++++++++ aptos-move/aptos-intent/src/lib.rs | 61 +++ aptos-move/aptos-intent/src/tests/mod.rs | 233 ++++++++ aptos-move/aptos-intent/webpack.config.js | 32 ++ aptos-move/aptos-resource-viewer/src/lib.rs | 11 +- .../indexer-grpc-fullnode/src/convert.rs | 20 + .../aptos/transaction/v1/transaction.proto | 6 + protos/rust/src/pb/aptos.transaction.v1.rs | 13 +- .../rust/src/pb/aptos.transaction.v1.serde.rs | 109 ++++ .../tools/move-resource-viewer/src/lib.rs | 39 ++ 28 files changed, 2377 insertions(+), 6 deletions(-) create mode 100644 api/goldens/aptos_api__tests__transactions_test__test_intent_execution_and_simulation.json create mode 100644 aptos-move/aptos-intent/Cargo.toml create mode 100644 aptos-move/aptos-intent/aptos-intent-npm/dist/aptos_intent.d.ts create mode 100644 aptos-move/aptos-intent/aptos-intent-npm/entry.js create mode 100644 aptos-move/aptos-intent/aptos-intent-npm/package.json create mode 100644 aptos-move/aptos-intent/aptos-intent-npm/rollup.config.mjs create mode 100644 aptos-move/aptos-intent/index.js create mode 100644 aptos-move/aptos-intent/package.json create mode 100644 aptos-move/aptos-intent/src/builder.rs create mode 100644 aptos-move/aptos-intent/src/codegen.rs create mode 100644 aptos-move/aptos-intent/src/decompiler.rs create mode 100644 aptos-move/aptos-intent/src/lib.rs create mode 100644 aptos-move/aptos-intent/src/tests/mod.rs create mode 100644 aptos-move/aptos-intent/webpack.config.js diff --git a/Cargo.lock b/Cargo.lock index a230dc6ff443c..b89baf6143f92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -484,6 +484,7 @@ dependencies = [ "aptos-gas-meter", "aptos-gas-schedule", "aptos-global-constants", + "aptos-intent", "aptos-logger", "aptos-mempool", "aptos-metrics-core", @@ -590,6 +591,7 @@ dependencies = [ "anyhow", "aptos-config", "aptos-crypto", + "aptos-intent", "aptos-logger", "aptos-openapi", "aptos-resource-viewer", @@ -2657,6 +2659,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "aptos-intent" +version = "0.1.0" +dependencies = [ + "anyhow", + "aptos-types", + "bcs 0.1.4", + "bytes", + "e2e-move-tests", + "getrandom 0.2.11", + "hex", + "move-binary-format", + "move-bytecode-verifier", + "move-core-types", + "reqwest 0.12.5", + "reqwest-wasm", + "serde", + "serde_bytes", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "aptos-jellyfish-merkle" version = "0.1.0" @@ -10366,6 +10391,22 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.6" @@ -15608,7 +15649,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.28", "hyper-rustls 0.24.2", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -15647,19 +15688,24 @@ dependencies = [ "async-compression", "base64 0.22.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "http-body-util", "hyper 1.4.1", "hyper-rustls 0.27.2", + "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -15672,7 +15718,9 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", + "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.0", "tokio-util 0.7.10", "tower-service", @@ -15722,6 +15770,44 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "reqwest-wasm" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fdca8934fccbd5aec3e208bdb5a76107ce8690aa51c6c292c31f21541b52b9" +dependencies = [ + "base64 0.13.1", + "bytes", + "encoding_rs", + "futures", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.10.1", +] + [[package]] name = "retain_mut" version = "0.1.9" @@ -19548,6 +19634,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index dfb46881ce48d..56bd6b3f1051c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "aptos-move/aptos-gas-profiling", "aptos-move/aptos-gas-schedule", "aptos-move/aptos-gas-schedule-updator", + "aptos-move/aptos-intent", "aptos-move/aptos-memory-usage-tracker", "aptos-move/aptos-native-interface", "aptos-move/aptos-release-builder", @@ -363,6 +364,7 @@ aptos-github-client = { path = "crates/aptos-github-client" } aptos-global-constants = { path = "config/global-constants" } aptos-id-generator = { path = "crates/aptos-id-generator" } aptos-indexer = { path = "crates/indexer" } +aptos-intent = { path = "aptos-move/aptos-intent" } aptos-indexer-grpc-cache-worker = { path = "ecosystem/indexer-grpc/indexer-grpc-cache-worker" } aptos-indexer-grpc-data-service = { path = "ecosystem/indexer-grpc/indexer-grpc-data-service" } aptos-indexer-grpc-data-service-v2 = { path = "ecosystem/indexer-grpc/indexer-grpc-data-service-v2" } diff --git a/api/Cargo.toml b/api/Cargo.toml index 3be5a7dd2add5..3335e642fe7c0 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -55,6 +55,7 @@ aptos-cached-packages = { workspace = true } aptos-framework = { workspace = true } aptos-gas-meter = { workspace = true } aptos-gas-schedule = { workspace = true, features = ["testing"] } +aptos-intent = { workspace = true } aptos-move-stdlib = { workspace = true } aptos-proptest-helpers = { workspace = true } aptos-transaction-filters = { workspace = true, features = ["fuzzing"] } diff --git a/api/goldens/aptos_api__tests__transactions_test__test_intent_execution_and_simulation.json b/api/goldens/aptos_api__tests__transactions_test__test_intent_execution_and_simulation.json new file mode 100644 index 0000000000000..5082146f573b7 --- /dev/null +++ b/api/goldens/aptos_api__tests__transactions_test__test_intent_execution_and_simulation.json @@ -0,0 +1,53 @@ +{ + "hash": "", + "sender": "0x34bf7e2d17674feb234371a7ea58efd715f0e56ba20ebf13789480d9d643afaf", + "sequence_number": "0", + "max_gas_amount": "100000000", + "gas_unit_price": "0", + "expiration_timestamp_secs": "18446744073709551615", + "payload": { + "intent_calls": [ + { + "function": "0x1::coin::withdraw", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "10" + ] + }, + { + "function": "0x1::coin::coin_to_fungible_asset", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + { + "call_idx": 0, + "return_idx": 0, + "operation_type": "Move" + } + ] + }, + { + "function": "0x1::primary_fungible_store::deposit", + "type_arguments": [], + "arguments": [ + "0x34bf7e2d17674feb234371a7ea58efd715f0e56ba20ebf13789480d9d643afaf", + { + "call_idx": 1, + "return_idx": 0, + "operation_type": "Move" + } + ] + } + ], + "type": "intent_payload" + }, + "signature": { + "public_key": "0xd5a781494d2bf1a174ddffde1e02cb8881cff6dab70e61cbdef393deac0ce639", + "signature": "0xaa3e6ddb3762601932363df9bada2f70b2e00881f3b78a1af66a36f03be7f17ebddff68825800afee07bc7bfdb8231777a20b1bb6ce0056fe7093b4dfe06e20b", + "type": "ed25519_signature" + }, + "replay_protection_nonce": null +} diff --git a/api/src/tests/transactions_test.rs b/api/src/tests/transactions_test.rs index af49fa9887839..2885ec3096fd9 100644 --- a/api/src/tests/transactions_test.rs +++ b/api/src/tests/transactions_test.rs @@ -2579,3 +2579,71 @@ fn gen_string(len: u64) -> String { fn build_path(path: &str) -> String { format!("/v1/transactions{}", path) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_intent_execution_and_simulation() { + use aptos_intent::{BatchArgumentWASM, BatchedFunctionCallBuilder}; + use move_core_types::value::MoveValue; + + let mut context = new_test_context(current_function_name!()); + let account = context.gen_account(); + let user_account_tx = context.create_user_account(&account).await; + context.commit_block(&vec![user_account_tx]).await; + + let mut builder = BatchedFunctionCallBuilder::single_signer(); + let url = context.poem_url(); + builder.load_module(url.clone(), "0x1::coin".to_string()).await.unwrap(); + builder.load_module(url, "0x1::primary_fungible_store".to_string()).await.unwrap(); + + let mut returns_1 = builder + .add_batched_call( + "0x1::coin".to_string(), + "withdraw".to_string(), + vec!["0x1::aptos_coin::AptosCoin".to_string()], + vec![ + BatchArgumentWASM::new_signer(0), + BatchArgumentWASM::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), + ], + ) + .unwrap(); + let mut returns_2 = builder + .add_batched_call( + "0x1::coin".to_string(), + "coin_to_fungible_asset".to_string(), + vec!["0x1::aptos_coin::AptosCoin".to_string()], + vec![returns_1.pop().unwrap()], + ) + .unwrap(); + builder + .add_batched_call( + "0x1::primary_fungible_store".to_string(), + "deposit".to_string(), + vec![], + vec![ + BatchArgumentWASM::new_bytes( + MoveValue::Address(account.address()) + .simple_serialize() + .unwrap(), + ), + returns_2.pop().unwrap(), + ], + ) + .unwrap(); + + let script = builder.generate_batched_calls().unwrap(); + + let txn = account.sign_with_transaction_builder( + context + .transaction_factory() + .payload(aptos_types::transaction::TransactionPayload::Script(bcs::from_bytes::