//! EVM Trace Example using EIP-3155 Inspector //! //! This example demonstrates: //! - Using the built-in EIP-3155 tracer //! - Deploying a contract and calling its methods //! - Tracking execution steps with detailed gas and opcode information //! - Verifying contract state after deployment use revm_trace::{ TransactionProcessor, traits::Database, types::TxKind, inspectors::TracerEip3155, create_evm_with_inspector, SimulationBatch, SimulationTx, }; use anyhow::Result; use alloy::{ primitives::{address, hex, Address, U256}, sol, sol_types::SolCall }; mod common; use common::get_block_env; // Define a simple contract that stores its deployer's address sol! { contract OwnerDemo { address private _owner; constructor() { _owner = msg.sender; } function getOwner() public view returns (address) { return _owner; } } } // Contract bytecode generated from the above Solidity code const BYTECODE:&str = "0x6080604052348015600f57600080fd5b50600080546001600160a01b031916331790556094806100306000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063893d20e814602d575b600080fd5b6033604f565b604080516001600160a01b039092168252519081900360200190f35b6000546001600160a01b03169056fea26469706673582212207e07a4e6666a33a6ee2fea8782ac8bcd42996a5130bb22b4353dbb5ea87bd4ee64736f6c63430007060033"; const ETH_RPC_URL: &str = "https://rpc.ankr.com/eth"; const SENDER: Address = address!("3ee18B2214AFF97000D974cf647E7C347E8fa585"); #[tokio::main] async fn main() -> Result<()> { // Initialize EVM with EIP-3155 tracer that outputs to stdout let inspector = TracerEip3155::new(Box::new(std::io::stdout())); let mut evm = create_evm_with_inspector(ETH_RPC_URL, inspector).await.unwrap(); // Calculate the contract address that will be created let current_account = evm.db_mut().basic(SENDER).unwrap().unwrap(); let nonce = current_account.nonce; let owner_demo_address = SENDER.create(nonce); let block_env = get_block_env(ETH_RPC_URL, None).await.unwrap(); let data = hex::decode(BYTECODE).unwrap(); // Transaction 1: Deploy the contract let tx0 = SimulationTx { caller: SENDER, transact_to: TxKind::Create, value: U256::ZERO, data: data.clone().into(), }; // Transaction 2: Call getOwner() on the deployed contract let tx1 = SimulationTx { caller: SENDER, transact_to: TxKind::Call(owner_demo_address), value: U256::ZERO, data: OwnerDemo::getOwnerCall{}.abi_encode().into(), }; // Process both transactions with state preservation let results = evm.process_transactions(SimulationBatch { block_env, is_stateful: true, transactions: vec![tx0,tx1], }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>(); let result = results[1].0.output().unwrap(); let owner = Address::from_slice(&result[12..32]); println!("Owner: {:?}", owner); assert_eq!(owner, SENDER, "Owner should be the deployer"); Ok(()) } // Example output trace for a single opcode: // { // "pc": 0, // Program counter // "op": 96, // Opcode (PUSH1) // "gas": "0xfff...", // Remaining gas // "gasCost": "0x3", // Cost of this operation // "stack": [], // Current stack contents // "depth": 1, // Call depth // "returnData": "0x", // Return data buffer // "refund": "0x0", // Gas refund counter // "memSize": "0", // Memory size // "opName": "PUSH1" // Operation name // }