//! Uniswap V2 Swap Transaction Simulation Example //! //! This example demonstrates how to: //! - Set up an EVM instance with transaction tracing //! - Encode Uniswap V2 swap function calls //! - Track token transfers during swap execution //! - Format and display swap results in a readable table //! //! Note: This example simulates a swap of ETH -> USDC using the Uniswap V2 router. //! Results may vary based on pool state at different blocks. use std::collections::HashMap; use revm_trace::{ TransactionProcessor, types::{TxKind,TokenInfo}, utils::erc20_utils::get_token_infos, create_evm_with_inspector, SimulationBatch, SimulationTx, TxInspector }; use anyhow::Result; use alloy::{ primitives::{address, Address, U256}, sol, sol_types::SolCall }; use prettytable::{format, Cell, Row, Table}; use colored::*; mod common; use common::get_block_env; // Define Uniswap V2 Router interface for swapping sol! { contract UniswapV2Router { /// Swaps exact amount of ETH for tokens /// @param amountOutMin Minimum amount of tokens to receive /// @param path Array of token addresses defining the swap path /// @param to Address to receive the output tokens /// @param deadline Unix timestamp deadline for the swap function swapExactETHForTokens( uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external payable returns (uint256[] memory amounts); } } /// Formats a U256 amount with proper decimal places /// /// # Arguments /// * `amount` - The amount to format /// * `decimals` - Number of decimal places for the token /// /// # Returns /// Formatted string with proper decimal point placement /// /// # Example /// ``` /// let amount = U256::from(1234567890); /// let formatted = format_amount(amount, 6); /// assert_eq!(formatted, "1234.56789"); /// ``` fn format_amount(amount: U256, decimals: u8) -> String { let mut value = amount.to_string(); if value.len() <= decimals as usize { value.insert_str(0, &"0".repeat(decimals as usize - value.len() + 1)); value.insert(1, '.'); } else { value.insert(value.len() - decimals as usize, '.'); } value .trim_end_matches('0') .trim_end_matches('.') .to_string() } const ETH_RPC_URL: &str = "https://rpc.ankr.com/eth"; #[tokio::main] async fn main() -> Result<()> { // Create EVM instance with transaction tracing let inspector = TxInspector::new(); let mut evm = create_evm_with_inspector(ETH_RPC_URL,inspector).await.unwrap(); println!("{}", "✅ EVM instance created successfully\n".green()); // Get block environment for simulation let block_env = get_block_env(ETH_RPC_URL, None).await.unwrap(); // Configure swap parameters let caller = address!("57757E3D981446D585Af0D9Ae4d7DF6D64647806"); let router = address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D"); let weth = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); let usdc = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); // Display swap configuration println!("Swap Configuration:"); println!("------------------"); println!("Caller: {}", caller); println!("Router: {}", router); println!("Path: WETH -> USDC\n"); // Prepare swap transaction let swap_amount = U256::from(100000000000000000u128); // 0.1 ETH let path = vec![weth, usdc]; let deadline = U256::from(u64::MAX); let data = UniswapV2Router::swapExactETHForTokensCall { amountOutMin: U256::ZERO, path, to: caller, deadline, } .abi_encode(); // Create and execute swap transaction println!("Executing swap of {} ETH...\n", "0.1".bold()); let tx = SimulationTx{ caller, transact_to: TxKind::Call(router), value: swap_amount, data: data.into(), }; // Process transaction and get results let result = evm.process_transactions(SimulationBatch { block_env, transactions: vec![tx], is_stateful: true, }).into_iter().map(|v| v.unwrap()).collect::<Vec<_>>()[0].clone(); // Verify transaction success println!("\nTransaction Result:"); println!("-----------------"); assert!( result.0.is_success(), "⌠Swap failed" ); // Format results in a table let mut table = Table::new(); table.set_format(*format::consts::FORMAT_BOX_CHARS); table.add_row(Row::new(vec![ Cell::new("Token").style_spec("Fb"), Cell::new("From").style_spec("Fb"), Cell::new("To").style_spec("Fb"), Cell::new("Amount").style_spec("Fb"), ])); // Get all unique tokens let mut tokens = vec![]; for transfer in &result.1.asset_transfers { if !tokens.contains(&transfer.token) && transfer.token != Address::ZERO { tokens.push(transfer.token); } } // Get token infos let token_infos = get_token_infos(&mut evm,&tokens, None).unwrap(); let mut token_info_map = HashMap::new(); token_info_map.insert(Address::ZERO, TokenInfo{ name: "Ethereum".to_string(), symbol: "ETH".to_string(), decimals: 18, total_supply: U256::MAX }); for (i,token_info) in token_infos.into_iter().enumerate() { token_info_map.insert(tokens[i], token_info); } // Add transfers to table for transfer in &result.1.asset_transfers { let amount = if let Some(info) = token_info_map.get(&transfer.token) { format_amount(transfer.value, info.decimals) } else { format_amount(transfer.value, 18) // Default to 18 decimals for ETH }; table.add_row(Row::new(vec![ Cell::new( &token_info_map .get(&transfer.token) .map(|i| i.symbol.clone()) .unwrap_or_else(|| "ETH".to_string()), ), Cell::new(&format!("{:.8}...", transfer.from)), Cell::new(&format!("{:.8}...", transfer.to.unwrap())), Cell::new(&amount), ])); } println!("Swap Results:"); println!("------------"); table.printstd(); println!( "\n{}", "✅ All assertions passed successfully!".bold().green() ); println!("Note: This example might fail occasionally due to DEX pool state changes"); println!("Consider using a specific block number or implementing retry logic for more stable results"); Ok(()) }