//! Integration tests for transaction tracing and asset tracking //! //! This test module verifies the transaction simulation and asset tracing functionality //! across different scenarios: //! //! # Test Coverage //! - Historical state access at different block heights //! - Different caller types (EOA vs Contract addresses) //! - Different inspector configurations //! - Contract deployment and interaction //! - Error handling and revert scenarios //! - Multicall transaction batching //! - State changes between transactions //! //! # Test Infrastructure //! - Uses Ankr's public RPC endpoint //! - Requires multi-threaded tokio runtime //! - Tests both successful and failure cases //! //! # Note on Historical State Access //! The tests include scenarios for accessing historical state, but success depends //! on the RPC node's capabilities: //! - Recent blocks: May succeed on regular nodes //! - Old blocks: Requires archive node access use revm_trace::{ TransactionProcessor, traits::Database, types::TxKind, create_evm_ws, create_evm_with_inspector, utils::error_utils::parse_custom_error, BlockEnv, SimulationBatch, SimulationTx, TxInspector }; use alloy::{ eips::BlockNumberOrTag, primitives::{address, hex, Address, U256}, providers::{Provider, ProviderBuilder, WsConnect}, sol, sol_types::SolCall }; /// Helper function to get block environment from HTTP RPC async fn get_block_env(http_url: &str,block_number:Option<u64>) -> BlockEnv { let provider = ProviderBuilder::new() .on_http(http_url.parse().unwrap()); if let Some(block_number) = block_number { let block_info = provider.get_block_by_number(BlockNumberOrTag::Number(block_number),false).await.unwrap().unwrap(); BlockEnv { number: block_number, timestamp: block_info.header.timestamp } } else { let latest_block = provider.get_block_number().await.unwrap(); let block_info = provider.get_block_by_number(BlockNumberOrTag::Number(latest_block),false).await.unwrap().unwrap(); BlockEnv { number: latest_block, timestamp: block_info.header.timestamp } } } /// Helper function to get block environment from WebSocket RPC async fn get_block_env_ws(ws_url: &str, block_number: Option<u64>) -> BlockEnv { let provider = ProviderBuilder::new() .on_ws(WsConnect::new(ws_url)).await.unwrap(); if let Some(block_number) = block_number { let block_info = provider.get_block_by_number(BlockNumberOrTag::Number(block_number),false).await.unwrap().unwrap(); BlockEnv { number: block_number, timestamp: block_info.header.timestamp } } else { let latest_block = provider.get_block_number().await.unwrap(); let block_info = provider.get_block_by_number(BlockNumberOrTag::Number(latest_block),false).await.unwrap().unwrap(); BlockEnv { number: latest_block, timestamp: block_info.header.timestamp } } } // Test contract definitions using alloy-sol macro sol! { contract OwnerDemo { address public owner; address public revert_address; constructor() { owner = msg.sender; } function setOwner(address _owner) public { require(msg.sender == owner, "Only the owner can set the owner"); owner = _owner; } function setRevertDemo(address _revert_address) public { revert_address = _revert_address; } function revert_demo() public { RevertDemo(revert_address).revert_demo(); } function revert_demo_multi() public { // catch first call try RevertDemo(revert_address).revert_demo() { } catch Error(string memory /*reason*/) { // catch revert error } catch (bytes memory /*lowLevelData*/) { // catch other errors } // second call will cause actual revert RevertDemo(revert_address).revert_demo(); } } contract RevertDemo { function revert_demo() public { this.nested_revert(); } function nested_revert() public { revert("Revert demo"); } } } const ETH_RPC_URL: &str = "https://rpc.ankr.com/eth"; const SENDER: Address = address!("3ee18B2214AFF97000D974cf647E7C347E8fa585"); const CAFE_ADDRESS: Address = address!("cafe00000000000000000000000000000000face"); const DEAD_ADDRESS: Address = address!("deAD00000000000000000000000000000000dEAd"); const OWNER_DEMO_BYTECODE:&str = "0x608060405234801561001057600080fd5b50600080546001600160a01b031916331790556103ae806100326000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c806313af40351461006757806315bb76871461008f5780633d39ef1f146100b55780635e56f344146100bd5780638da5cb5b146100c5578063f106e187146100e9575b600080fd5b61008d6004803603602081101561007d57600080fd5b50356001600160a01b03166100f1565b005b61008d600480360360208110156100a557600080fd5b50356001600160a01b0316610172565b61008d610194565b61008d610244565b6100cd6102ae565b604080516001600160a01b039092168252519081900360200190f35b6100cd6102bd565b6000546001600160a01b03163314610150576040805162461bcd60e51b815260206004820181905260248201527f4f6e6c7920746865206f776e65722063616e2073657420746865206f776e6572604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b0392909216919091179055565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b600160009054906101000a90046001600160a01b03166001600160a01b0316635e56f3446040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156101e457600080fd5b505af19250505080156101f5575060015b610244576102016102d2565b8061020c5750610212565b50610244565b3d80801561023c576040519150601f19603f3d011682016040523d82523d6000602084013e610241565b606091505b50505b600160009054906101000a90046001600160a01b03166001600160a01b0316635e56f3446040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561029457600080fd5b505af11580156102a8573d6000803e3d6000fd5b50505050565b6000546001600160a01b031681565b6001546001600160a01b031681565b60e01c90565b600060443d10156102e257610375565b600481823e6308c379a06102f682516102cc565b1461030057610375565b6040513d600319016004823e80513d67ffffffffffffffff81602484011181841117156103305750505050610375565b8284019250825191508082111561034a5750505050610375565b503d8301602082840101111561036257505050610375565b601f01601f191681016020016040529150505b9056fea2646970667358221220577efd69e9b6bd0aef315ca8b576c73ea45e4fdd661c80354676892187cee1dd64736f6c63430007060033"; const REVERT_DEMO_BYTECODE:&str = "0x608060405234801561001057600080fd5b50610109806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80635e56f344146037578063a814827114603f575b600080fd5b603d6045565b005b603d6098565b306001600160a01b031663a81482716040518163ffffffff1660e01b8152600401600060405180830381600087803b158015607f57600080fd5b505af11580156092573d6000803e3d6000fd5b50505050565b6040805162461bcd60e51b815260206004820152600b60248201526a5265766572742064656d6f60a81b604482015290519081900360640190fdfea2646970667358221220ec2b7033a5b157556e539f3bcae34ab87defd9acac77633153af96a8be1644b364736f6c63430007060033"; /// Test nested revert handling with try-catch mechanism /// /// Verifies: /// - Proper handling of nested contract calls /// - Try-catch error handling /// - Error propagation in multicall context /// - Trace address tracking #[tokio::test(flavor = "multi_thread")] async fn test_nested_revert_with_try_catch() { let inspector = TxInspector::new(); let mut evm: revm_trace::evm::TraceEvm<'_, alloy::transports::http::Http<alloy::transports::http::Client>, alloy::providers::RootProvider<alloy::transports::http::Http<alloy::transports::http::Client>>, TxInspector> = create_evm_with_inspector(ETH_RPC_URL,inspector).await.unwrap(); let block_env = get_block_env(ETH_RPC_URL, None).await; // get current nonce to calculate contract address let current_account = evm.db_mut().basic(SENDER).unwrap().unwrap(); let nonce = current_account.nonce; let revert_demo_address = SENDER.create(nonce); let owner_demo_address = SENDER.create(nonce + 1); // 1. deploy RevertDemo contract let tx0 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: hex::decode(REVERT_DEMO_BYTECODE).unwrap().into(), }; // 2. deploy OwnerDemo contract let tx1 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: hex::decode(OWNER_DEMO_BYTECODE).unwrap().into(), }; // 3. call setRevertDemo to set revert_address let data = OwnerDemo::setRevertDemoCall{ _revert_address: revert_demo_address, }.abi_encode(); let tx2 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(owner_demo_address), value: U256::ZERO, data: data.into(), }; // 4. call revert_demo_multi to trigger two calls let data = OwnerDemo::revert_demo_multiCall{}.abi_encode(); let tx3 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(owner_demo_address), value: U256::ZERO, data: data.into(), }; // execute all transactions let results = evm.process_transactions(SimulationBatch { block_env, is_stateful: true, transactions: vec![tx0, tx1, tx2, tx3], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); // verify results assert_eq!(results.len(), 4, "Each tx should have an ExecutionResult"); // verify transaction failed assert!(!results[3].0.is_success(), "Tx should be failed"); // verify error info (error from second call) match &results[3].0.output() { Some(output) => { let reason = parse_custom_error(output).unwrap(); assert_eq!(reason, "Revert demo", "Should have correct revert reason"); }, _ => panic!("Expected revert failure"), } // verify call chain let top_traces = &results[3].1.call_trace; assert!(top_traces.is_some(), "Tx should have one top-level traces"); let top_traces = top_traces.as_ref().unwrap(); assert!(top_traces.trace_address.is_empty(), "Top-level trace should have empty trace_address"); assert_eq!(top_traces.subtraces.len(), 2, "Top-level trace should have two subtraces"); // verify first call (catched by try-catch) let first_subtrace = &top_traces.subtraces[0]; assert_eq!(first_subtrace.trace_address, vec![0], "First subtrace should have trace_address [0]"); assert!(!first_subtrace.status.is_success(), "First subtrace should fail"); // verify last call (cause actual revert) let last_trace = &top_traces.subtraces[1]; assert_eq!(last_trace.trace_address, vec![1], "Last call should have trace_address [1]"); assert!(!last_trace.status.is_success(), "Last call should fail"); // verify final call (caught by try-catch) let final_subtrace = &last_trace.subtraces[0]; assert_eq!(final_subtrace.trace_address, vec![1,0], "Final subtrace should have trace_address [1,0]"); assert!(!final_subtrace.status.is_success(), "Final subtrace should fail"); assert!(final_subtrace.error_origin, "Final subtrace should be error origin"); // verify error trace let error_trace_address = results[3].1.error_trace_address.as_ref().unwrap(); assert_eq!(*error_trace_address, vec![1,0], "Error trace should be from the second call"); } /// Test nested revert handling in multicall context /// /// Verifies: /// - Error propagation in nested calls /// - Trace address tracking /// - Error origin identification #[tokio::test(flavor = "multi_thread")] async fn test_nested_revert_with_multicall() { let inspector = TxInspector::new(); let mut evm = create_evm_with_inspector(ETH_RPC_URL,inspector).await.unwrap(); let block_env = get_block_env(ETH_RPC_URL, None).await; // get current nonce to calculate contract address let current_account = evm.db_mut().basic(SENDER).unwrap().unwrap(); let nonce = current_account.nonce; let revert_demo_address = SENDER.create(nonce); let owner_demo_address = SENDER.create(nonce + 1); // 1. deploy ReverDemo contract let tx0 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: hex::decode(REVERT_DEMO_BYTECODE).unwrap().into(), }; // 2. deploy OwnerDemo contract let tx1 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: hex::decode(OWNER_DEMO_BYTECODE).unwrap().into(), }; // 3. call setRevertDemo to set revert_address let data = OwnerDemo::setRevertDemoCall{ _revert_address: revert_demo_address, }.abi_encode(); let tx2 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(owner_demo_address), value: U256::ZERO, data: data.into(), }; // 4. call revert_demo to trigger nested call failure let data = OwnerDemo::revert_demoCall{}.abi_encode(); let tx3 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(owner_demo_address), value: U256::ZERO, data: data.into(), }; // execute all transactions let results = evm.process_transactions(SimulationBatch { block_env, is_stateful: true, transactions: vec![tx0, tx1, tx2, tx3], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); // verify results assert_eq!(results.len(), 4, "Each tx should have one execution result"); // verify transaction failed assert!(!results[3].0.is_success(), "Tx3 should be failed"); // verify error info match &results[3].0.output() { Some(output) => { let reason = parse_custom_error(output).unwrap(); assert_eq!(reason, "Revert demo", "Should have correct revert reason"); }, _ => panic!("Expected revert failure"), } // verify call chain let top_traces = &results[3].1.call_trace; assert!(top_traces.is_some(), "Tx should have one top-level traces"); let top_traces = top_traces.as_ref().unwrap(); assert!(top_traces.trace_address.is_empty(), "Top-level trace should have empty trace_address"); assert_eq!(top_traces.subtraces.len(), 1, "Top-level trace should have two subtraces"); let error_trace_address = results[3].1.error_trace_address.as_ref().unwrap(); assert_eq!(*error_trace_address, vec![0,0], "Error trace should be the latest call"); let mid_trace = &top_traces.subtraces[0]; assert_eq!(mid_trace.trace_address, vec![0], "Mid trace should have trace_address [0]"); assert!(!mid_trace.status.is_success(), "Mid trace should be failed"); assert!(!mid_trace.error_origin, "Mid trace should not be error origin"); let final_trace = &mid_trace.subtraces[0]; assert_eq!(final_trace.trace_address, vec![0,0], "Final trace should have trace_address [0,0]"); assert!(!final_trace.status.is_success(), "Final trace should be failed"); assert!(final_trace.error_origin, "Final trace should be error origin"); } /// Test nested revert handling without multicall /// /// Verifies: /// - Individual transaction execution /// - Error handling in standalone context /// - Trace collection and verification #[tokio::test(flavor = "multi_thread")] async fn test_nested_revert_without_multicall() { let inspector = TxInspector::new(); let mut evm = create_evm_with_inspector(ETH_RPC_URL,inspector).await.unwrap(); let block_env = get_block_env(ETH_RPC_URL, None).await; // get current nonce to calculate contract address let current_account = evm.db_mut().basic(SENDER).unwrap().unwrap(); let nonce = current_account.nonce; let revert_demo_address = SENDER.create(nonce); let owner_demo_address = SENDER.create(nonce + 1); // 1. deploy ReverDemo contract let tx0 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: hex::decode(REVERT_DEMO_BYTECODE).unwrap().into(), }; // 2. deploy OwnerDemo contract let tx1 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: hex::decode(OWNER_DEMO_BYTECODE).unwrap().into(), }; // 3. call setRevertDemo to set revert_address let data = OwnerDemo::setRevertDemoCall{ _revert_address: revert_demo_address, }.abi_encode(); let tx2 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(owner_demo_address), value: U256::ZERO, data: data.into(), }; // 4. call revert_demo to trigger nested call failure let data = OwnerDemo::revert_demoCall{}.abi_encode(); let tx3 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(owner_demo_address), value: U256::ZERO, data: data.into(), }; // execute all transactions let results = evm.process_transactions(SimulationBatch { block_env, is_stateful: true, transactions: vec![tx0, tx1, tx2, tx3], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); // verify results assert_eq!(results.len(), 4, "Should have results for all four transactions"); // verify first three calls succeed assert!(results[0].0.is_success(), "ReverDemo deployment should succeed"); assert!(results[1].0.is_success(), "OwnerDemo deployment should succeed"); assert!(results[2].0.is_success(), "setRevertDemo call should succeed"); // verify last call failed assert!(!results[3].0.is_success(), "revert_demo call should fail"); // verify error info match &results[3].0.output() { Some(output) => { let reason = parse_custom_error(output).unwrap(); assert_eq!(reason, "Revert demo", "Should have correct revert reason"); }, _ => panic!("Expected revert failure"), } // verify call chain let top_trace = &results[3].1.call_trace.as_ref().unwrap(); assert_eq!(top_trace.subtraces.len(), 1, "Should have one subtrace"); assert_eq!(top_trace.from, SENDER); assert_eq!(top_trace.to, owner_demo_address); assert!(!top_trace.status.is_success(), "Top trace should be failed"); assert!(top_trace.trace_address.is_empty(), "Top trace should have empty trace_address"); assert!(!top_trace.error_origin, "Top trace should not be error origin"); let sub_trace = &top_trace.subtraces[0]; assert_eq!(sub_trace.from, owner_demo_address); assert_eq!(sub_trace.to, revert_demo_address); assert_eq!(sub_trace.trace_address, vec![0], "Subtrace should have trace_address [0]"); assert!(!sub_trace.status.is_success(), "Subtrace should be failed"); assert!(!sub_trace.error_origin, "Subtrace should not be error origin"); let final_trace = &sub_trace.subtraces[0]; assert_eq!(final_trace.from, revert_demo_address); assert_eq!(final_trace.to, revert_demo_address); assert_eq!(final_trace.trace_address, vec![0,0], "Subtrace should have trace_address [0]"); assert!(!final_trace.status.is_success(), "Subtrace should be failed"); assert!(final_trace.error_origin, "Subtrace should be error origin"); // verify error trace let error_trace = results[3].1.error_trace_address.as_ref().unwrap(); assert_eq!(*error_trace, vec![0,0], "Error_trace should be same as final_trace"); } /// Test multicall execution with error handling /// /// Verifies: /// - Batch transaction processing /// - Error handling in multicall context /// - Transaction ordering and state changes #[tokio::test(flavor = "multi_thread")] async fn test_multicall_with_error() { let inspector = TxInspector::new(); let mut evm = create_evm_with_inspector(ETH_RPC_URL,inspector).await.unwrap(); let block_env = get_block_env(ETH_RPC_URL, None).await; // get current nonce to calculate contract address let current_account = evm.db_mut().basic(SENDER).unwrap().unwrap(); let nonce = current_account.nonce; let expected_contract_address = SENDER.create(nonce); // 1. deploy OwnerDemo contract let tx0 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: hex::decode(OWNER_DEMO_BYTECODE).unwrap().into(), }; // 2. non-owner attempt to set owner (will fail) let data = OwnerDemo::setOwnerCall { _owner: DEAD_ADDRESS, } .abi_encode(); let tx1 = SimulationTx { caller: CAFE_ADDRESS, transact_to: TxKind::Call(expected_contract_address), value: U256::ZERO, data: data.clone().into(), }; // 3. owner set new owner transaction (will succeed) let tx2 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(expected_contract_address), value: U256::ZERO, data: data.clone().into(), }; // execute batch transactions let results = evm.process_transactions(SimulationBatch { block_env, is_stateful: true, transactions: vec![tx0, tx1, tx2], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); // verify results assert_eq!(results.len(), 3, "Each tx should have one execution result"); let result = &results[1].0; match result.output() { Some(output) => { let reason = parse_custom_error(output).unwrap(); assert_eq!(reason, "Only the owner can set the owner", "Should fail with correct revert reason"); }, _ => panic!("Expected revert failure"), } // verify error trace let error_trace = results[1].1.call_trace.as_ref().unwrap(); assert_eq!(error_trace.from , CAFE_ADDRESS, "Error should come from CAFE_ADDRESS call"); assert_eq!(error_trace.to, expected_contract_address, "Error should be in contract call"); assert!(error_trace.trace_address.is_empty(), "Error should be in the top transaction"); } /// Test contract creation and deployment /// /// Verifies: /// - Contract deployment process /// - Address prediction /// - Creation trace collection #[tokio::test(flavor = "multi_thread")] async fn test_create_contract() { let inspector = TxInspector::new(); let mut evm = create_evm_with_inspector(ETH_RPC_URL,inspector).await.unwrap(); let block_env = get_block_env(ETH_RPC_URL, None).await; let sender = address!("b20a608c624Ca5003905aA834De7156C68b2E1d0"); let current_account = evm.db_mut().basic(sender).unwrap().unwrap(); let nonce = current_account.nonce; let expected_contract_address = sender.create(nonce); let data = hex::decode(OWNER_DEMO_BYTECODE).unwrap(); let tx0 = SimulationTx { caller: sender, transact_to: TxKind::Create, value: U256::ZERO, data: data.into(), }; let results = evm.process_transactions(SimulationBatch { block_env: block_env.clone(), is_stateful: false, transactions: vec![tx0], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); assert_eq!(results.len(), 1, "Should have results for one transaction"); let result = &results[0].0; assert!(result.is_success(), "Contract creation should succeed"); // verify contract creation output let call_trace = &results[0].1.call_trace.as_ref().unwrap(); assert_eq!(call_trace.from, sender, "Creator should match"); assert_eq!(call_trace.to, expected_contract_address, "Contract address should match"); } #[tokio::test(flavor = "multi_thread")] async fn test_stateful_and_stateless_call_trace() { let inspector = TxInspector::new(); let mut evm = create_evm_with_inspector(ETH_RPC_URL,inspector).await.unwrap(); let block_env = get_block_env(ETH_RPC_URL, None).await; let sender = address!("b20a608c624Ca5003905aA834De7156C68b2E1d0"); let current_account = evm.db_mut().basic(sender).unwrap().unwrap(); let nonce = current_account.nonce; let expected_contract_address = sender.create(nonce); let next_contract_address = sender.create(nonce + 1); let data = hex::decode(OWNER_DEMO_BYTECODE).unwrap(); let tx0 = SimulationTx { caller: sender, transact_to: TxKind::Create, value: U256::ZERO, data: data.clone().into(), }; let tx1 = SimulationTx { caller: sender, transact_to: TxKind::Create, value: U256::ZERO, data: data.into(), }; let results = evm.process_transactions(SimulationBatch { block_env: block_env.clone(), is_stateful: false, transactions: vec![tx0.clone(), tx1.clone()], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); assert_eq!(results.len(), 2, "Should have results for two transactions"); assert!(results[0].0.is_success(), "Contract creation should succeed"); assert!(results[1].0.is_success(), "setOwner should succeed"); let deploy_call_tx0 = results[0].1.call_trace.as_ref().unwrap(); assert_eq!(deploy_call_tx0.from, sender, "Creator should match"); assert_eq!(deploy_call_tx0.to, expected_contract_address, "Contract address should match"); let deploy_call_tx1 = results[1].1.call_trace.as_ref().unwrap(); assert_eq!(deploy_call_tx1.from, sender, "Creator should match"); assert_eq!(deploy_call_tx1.to, expected_contract_address, "Contract address should match"); let results = evm.process_transactions(SimulationBatch { block_env: block_env.clone(), is_stateful: true, transactions: vec![tx0.clone(), tx1.clone()], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); assert_eq!(results.len(), 2, "Should have results for two transactions"); assert!(results[0].0.is_success(), "Contract creation should succeed"); assert!(results[1].0.is_success(), "setOwner should succeed"); let deploy_call_tx0 = results[0].1.call_trace.as_ref().unwrap(); assert_eq!(deploy_call_tx0.from, sender, "Creator should match"); assert_eq!(deploy_call_tx0.to, expected_contract_address, "Contract address should match"); let deploy_call_tx1 = results[1].1.call_trace.as_ref().unwrap(); assert_eq!(deploy_call_tx1.from, sender, "Creator should match"); assert_eq!(deploy_call_tx1.to, next_contract_address, "Contract address should match"); } #[tokio::test(flavor = "multi_thread")] async fn test_wth_ws() { let ws_rpc_url = std::env::var("WS_RPC_URL").unwrap(); let inspector = TxInspector::new(); let mut evm = create_evm_ws(&ws_rpc_url,inspector).await.unwrap(); let block_env = get_block_env_ws(&ws_rpc_url, None).await; // check initial state let cafe_balance_before = evm.db_mut().basic(CAFE_ADDRESS).unwrap().unwrap().balance; let dead_balance_before = evm.db_mut().basic(DEAD_ADDRESS).unwrap().unwrap().balance; assert_eq!(cafe_balance_before, U256::ZERO, "CAFE initial balance should be 0"); assert_eq!(dead_balance_before, U256::ZERO, "DEAD initial balance should be 0"); // define transfer amounts let transfer1_amount = U256::from(100000000000000000u64); // 0.1 ETH let transfer2_amount = U256::from(60000000000000000u64); // 0.06 ETH let txs = SimulationBatch { block_env, is_stateful: true, transactions: vec![ SimulationTx { caller: SENDER, transact_to: TxKind::Call(CAFE_ADDRESS), value: transfer1_amount, data: vec![].into(), }, SimulationTx { caller: CAFE_ADDRESS, transact_to: TxKind::Call(DEAD_ADDRESS), value: transfer2_amount, data: vec![].into(), }, ], }; let results = evm.process_transactions(txs).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); assert_eq!(results.len(), 2, "Should have results for both transactions"); // verify first tx let result0 = &results[0]; assert!(result0.0.is_success(), "First tx should succeed"); let transfer1 = &result0.1.asset_transfers[0]; assert_eq!(transfer1.from, SENDER); assert_eq!(transfer1.to, Some(CAFE_ADDRESS)); assert_eq!(transfer1.value, transfer1_amount); assert!(transfer1.is_native_token()); // verify second transfer let result1 = &results[1]; assert!(result1.0.is_success(), "Second tx should succeed"); let transfer2 = &result1.1.asset_transfers[0]; assert_eq!(transfer2.from, CAFE_ADDRESS); assert_eq!(transfer2.to, Some(DEAD_ADDRESS)); assert_eq!(transfer2.value, transfer2_amount); assert!(transfer2.is_native_token()); // verify final state let cafe_balance_after = evm.db_mut().basic(CAFE_ADDRESS).unwrap().unwrap().balance; let dead_balance_after = evm.db_mut().basic(DEAD_ADDRESS).unwrap().unwrap().balance; // calculate expected balance let expected_cafe_balance = transfer1_amount - transfer2_amount; assert_eq!(cafe_balance_after, expected_cafe_balance, "CAFE should have 0.04 ETH left"); assert_eq!(dead_balance_after, transfer2_amount, "DEAD should have received 0.06 ETH"); }