revm-trace 4.2.0

High-performance multi-threaded EVM transaction simulator and analyzer with comprehensive tracing capabilities
Documentation
//! ERC20 Token Transfer Simulation Example
//!
//! This example demonstrates how to:
//! - Set up a simulation environment
//! - Encode ERC20 transfer calls
//! - Execute token transfers
//! - Track and verify transfer results
//! - Display transfer information with proper token details

use alloy::{
    primitives::{address, utils::format_units, Address, TxKind, U256},
    sol,
    sol_types::SolCall,
};
use anyhow::Result;
use revm_trace::{
    types::SlotAccessType, utils::erc20_utils::get_token_infos, SimulationBatch, SimulationTx,
    TransactionTrace, TxInspector,
};

#[cfg(not(feature = "foundry-fork"))]
use revm_trace::create_evm_with_tracer;

#[cfg(feature = "foundry-fork")]
use revm_trace::create_shared_evm_with_tracer;

// Define ERC20 interface for transfer function
sol!(
    contract ERC20 {
        function transfer(address to, uint256 amount) external returns (bool);
    }
);

/// Encodes an ERC20 transfer function call
///
/// # Arguments
/// * `to` - The recipient address
/// * `amount` - The amount to transfer
///
/// # Returns
/// The encoded function call as bytes
fn encode_erc20_transfer(to: Address, amount: U256) -> Vec<u8> {
    ERC20::transferCall { to, amount }.abi_encode()
}

const ETH_RPC_URL: &str = "https://eth.llamarpc.com";

#[tokio::main]
async fn main() -> Result<()> {
    #[cfg(not(feature = "foundry-fork"))]
    println!("Using AlloyDB backend for EVM simulation");

    #[cfg(feature = "foundry-fork")]
    println!("Using Foundry fork backend for EVM simulation");

    let inspector = TxInspector::new();

    #[cfg(not(feature = "foundry-fork"))]
    let mut evm = create_evm_with_tracer(ETH_RPC_URL, inspector).await?;

    #[cfg(feature = "foundry-fork")]
    let mut evm = create_shared_evm_with_tracer(ETH_RPC_URL, inspector).await?;
    // USDC proxy contract address
    let usdc = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");

    // Construct transfer call data
    let transfer_data = encode_erc20_transfer(
        address!("34e5dacdc16ff5bcdbdfa66c21a20f46347d86cf "),
        U256::from(1000000), // 1 USDC (6 decimals)
    );
    let tx = SimulationTx {
        caller: address!("0x28C6c06298d514Db089934071355E5743bf21d60"),
        transact_to: TxKind::Call(usdc),
        value: U256::ZERO,
        data: transfer_data.into(),
    };

    let result = &evm
        .trace_transactions(SimulationBatch {
            is_stateful: false,
            transactions: vec![tx],
            overrides: None,
        })
        .into_iter()
        .map(|v| v.unwrap())
        .collect::<Vec<_>>()[0];
    let output = result.0.output().unwrap();
    assert!(
        output.len() == 32 && output[31] == 1,
        "❌ Expected transfer to succeed"
    );
    // print slot changes for debugging
    for (_, change) in result.1.iter() {
        println!("Slot Change on global: {:?}", change);
    }
    // print call_trace's slot changes detail for debugging
    if let Some(call_trace) = result.2.call_trace.as_ref() {
        let slot_changes = call_trace.all_slot_accesses(SlotAccessType::Write);
        for change in slot_changes {
            println!("Slot Change detail: {:?}", change);
        }
    }
    // Print results
    for transfer in &result.2.asset_transfers {
        let token_info = &get_token_infos(&mut evm, &[transfer.token]).unwrap()[0];

        println!(
            "Transfer: {} {} -> {}: {}",
            token_info.symbol,
            transfer.from,
            transfer.to.unwrap(),
            format_units(transfer.value, token_info.decimals).unwrap()
        );
    }

    Ok(())
}