diff --git a/Cargo.lock b/Cargo.lock index d7dcdf72b0..4d75a02769 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2853,8 +2853,10 @@ dependencies = [ name = "revm" version = "8.0.0" dependencies = [ + "alloy-primitives", "alloy-provider", "alloy-rpc-types", + "alloy-sol-types", "alloy-transport", "alloy-transport-http", "anyhow", diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 84dad5cc15..1f1f40806a 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -46,6 +46,8 @@ alloy-rpc-types = {git = "https://github.com/alloy-rs/alloy.git", optional = tru alloy-transport = {git = "https://github.com/alloy-rs/alloy.git", optional = true, default-features = false } [dev-dependencies] +alloy-primitives = { version = "0.7.0", default-features = false } +alloy-sol-types = { version = "0.7.0", default-features = false } ethers-contract = { version = "2.0.14", default-features = false } anyhow = "1.0.82" criterion = "0.5" diff --git a/examples/uniswap_v2_usdc_swap.rs b/examples/uniswap_v2_usdc_swap.rs index 008b0c0fa2..0a1ca30825 100644 --- a/examples/uniswap_v2_usdc_swap.rs +++ b/examples/uniswap_v2_usdc_swap.rs @@ -1,19 +1,14 @@ -use ethers_contract::BaseContract; -use ethers_core::{ - abi, - abi::parse_abi, - types::{Bytes, H160, U256}, - utils::to_checksum, -}; +use alloy_primitives::{address, Address, U256}; +use alloy_sol_types::{sol, SolCall, SolValue}; use ethers_providers::{Http, Provider}; use revm::{ db::{CacheDB, EmptyDB, EmptyDBTyped, EthersDB}, primitives::{ - address, keccak256, AccountInfo, ExecutionResult, Output, TransactTo, U256 as rU256, + keccak256, AccountInfo, Bytes as rBytes, ExecutionResult, Output, TransactTo, U256 as rU256, }, Database, Evm, }; -use revm_precompile::Address; +use std::ops::Div; use std::{convert::Infallible, sync::Arc}; #[tokio::main] @@ -27,91 +22,72 @@ async fn main() -> anyhow::Result<()> { let mut cache_db = CacheDB::new(EmptyDB::default()); // Random empty account - let account: H160 = "0x18B06aaF27d44B756FCF16Ca20C1f183EB49111f" - .parse() - .unwrap(); - let weth: H160 = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - .parse() - .unwrap(); - let usdc: H160 = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - .parse() - .unwrap(); - let usdc_weth_pair: H160 = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc" - .parse() - .unwrap(); - let uniswap_v2_router: H160 = "0x7a250d5630b4cf539739df2c5dacb4c659f2488d" - .parse() - .unwrap(); + let account = address!("18B06aaF27d44B756FCF16Ca20C1f183EB49111f"); + + let weth = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let usdc = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + let usdc_weth_pair = address!("B4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"); + let uniswap_v2_router = address!("7a250d5630b4cf539739df2c5dacb4c659f2488d"); // USDC uses a proxy pattern so we have to fetch implementation address let usdc_impl_slot: rU256 = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" .parse() .unwrap(); - let usdc_impl_raw = ethersdb.storage(to_address(usdc), usdc_impl_slot).unwrap(); - let usdc_impl: H160 = format!("{:x}", usdc_impl_raw)[24..].parse().unwrap(); + let usdc_impl_raw = ethersdb.storage(usdc, usdc_impl_slot).unwrap(); + let usdc_impl: Address = format!("{:x}", usdc_impl_raw)[24..].parse().unwrap(); // populate basic data for addr in [weth, usdc, usdc_weth_pair, uniswap_v2_router, usdc_impl] { - let addr = to_address(addr); let acc_info = ethersdb.basic(addr).unwrap().unwrap(); cache_db.insert_account_info(addr, acc_info); } cache_db - .insert_account_storage(to_address(usdc), usdc_impl_slot, usdc_impl_raw) + .insert_account_storage(usdc, usdc_impl_slot, usdc_impl_raw) .unwrap(); // populate WETH balance for USDC-WETH pair let weth_balance_slot = U256::from(3); - let pair_weth_balance_slot = keccak256(abi::encode(&[ - abi::Token::Address(usdc_weth_pair), - abi::Token::Uint(weth_balance_slot), - ])); + let pair_weth_balance_slot = keccak256((usdc_weth_pair, weth_balance_slot).abi_encode()); let value = ethersdb - .storage(to_address(weth), pair_weth_balance_slot.into()) + .storage(weth, pair_weth_balance_slot.into()) .unwrap(); cache_db - .insert_account_storage(to_address(weth), pair_weth_balance_slot.into(), value) + .insert_account_storage(weth, pair_weth_balance_slot.into(), value) .unwrap(); // populate USDC balance for USDC-WETH pair let usdc_balance_slot = U256::from(9); - let pair_usdc_balance_slot = keccak256(abi::encode(&[ - abi::Token::Address(usdc_weth_pair), - abi::Token::Uint(usdc_balance_slot), - ])); + let pair_usdc_balance_slot = keccak256((usdc_weth_pair, usdc_balance_slot).abi_encode()); let value = ethersdb - .storage(to_address(usdc), pair_usdc_balance_slot.into()) + .storage(usdc, pair_usdc_balance_slot.into()) .unwrap(); cache_db - .insert_account_storage(to_address(usdc), pair_usdc_balance_slot.into(), value) + .insert_account_storage(usdc, pair_usdc_balance_slot.into(), value) .unwrap(); // give our test account some fake WETH and ETH let one_ether = rU256::from(1_000_000_000_000_000_000u128); - let hashed_acc_balance_slot = keccak256(abi::encode(&[ - abi::Token::Address(account), - abi::Token::Uint(weth_balance_slot), - ])); + let hashed_acc_balance_slot = keccak256((account, weth_balance_slot).abi_encode()); cache_db - .insert_account_storage(to_address(weth), hashed_acc_balance_slot.into(), one_ether) + .insert_account_storage(weth, hashed_acc_balance_slot.into(), one_ether) .unwrap(); let acc_info = AccountInfo { nonce: 0_u64, balance: one_ether, - code_hash: keccak256(Bytes::new()), + code_hash: keccak256(rBytes::new()), code: None, }; - cache_db.insert_account_info(to_address(account), acc_info); + cache_db.insert_account_info(account, acc_info); // populate UniswapV2 pair slots - let usdc_weth_pair_address = to_address(usdc_weth_pair); + let usdc_weth_pair_address = usdc_weth_pair; let pair_acc_info = ethersdb.basic(usdc_weth_pair_address).unwrap().unwrap(); cache_db.insert_account_info(usdc_weth_pair_address, pair_acc_info); for i in 0..=12 { @@ -131,7 +107,7 @@ async fn main() -> anyhow::Result<()> { let (reserve0, reserve1) = get_reserves(usdc_weth_pair, &mut cache_db).await?; - let amount_in = U256::from_dec_str("100000000000000000").unwrap(); // 1/10 ETH + let amount_in = one_ether.div(rU256::from(10)); // calculate USDC amount out let amount_out = get_amount_out(amount_in, reserve1, reserve0, &mut cache_db).await?; @@ -159,23 +135,23 @@ async fn main() -> anyhow::Result<()> { } async fn balance_of( - token: H160, - address: H160, + token: Address, + address: Address, cache_db: &mut CacheDB>, ) -> anyhow::Result { - let abi = BaseContract::from(parse_abi(&[ - "function balanceOf(address) public returns (uint256)", - ])?); + sol! { + function balanceOf(address account) public returns (uint256); + } - let encoded_balance = abi.encode("balanceOf", address)?; + let encoded = balanceOfCall { account: address }.abi_encode(); let mut evm = Evm::builder() .with_db(cache_db) .modify_tx_env(|tx| { // 0x1 because calling USDC proxy from zero address fails tx.caller = address!("0000000000000000000000000000000000000001"); - tx.transact_to = TransactTo::Call(to_address(token)); - tx.data = encoded_balance.0.into(); + tx.transact_to = TransactTo::Call(token); + tx.data = encoded.into(); tx.value = rU256::from(0); }) .build(); @@ -191,7 +167,11 @@ async fn balance_of( result => panic!("'balance_of' execution failed: {result:?}"), }; - let balance: u128 = abi.decode_output("balanceOf", value)?; + let balance: u128 = match ::abi_decode(&value, false) { + Ok(balance) => balance, + Err(e) => panic!("'balance_of' decode failed: {:?}", e), + }; + Ok(balance) } @@ -201,21 +181,24 @@ async fn get_amount_out( reserve_out: U256, cache_db: &mut CacheDB>, ) -> anyhow::Result { - let uniswap_v2_router: H160 = "0x7a250d5630b4cf539739df2c5dacb4c659f2488d" - .parse() - .unwrap(); - let router_abi = BaseContract::from(parse_abi(&[ - "function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut)" - ])?); + let uniswap_v2_router = address!("7a250d5630b4cf539739df2c5dacb4c659f2488d"); + sol! { + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + } - let encoded = router_abi.encode("getAmountOut", (amount_in, reserve_in, reserve_out))?; + let encoded = getAmountOutCall { + amountIn: amount_in, + reserveIn: reserve_in, + reserveOut: reserve_out, + } + .abi_encode(); let mut evm = Evm::builder() .with_db(cache_db) .modify_tx_env(|tx| { tx.caller = address!("0000000000000000000000000000000000000000"); - tx.transact_to = TransactTo::Call(to_address(uniswap_v2_router)); - tx.data = encoded.0.into(); + tx.transact_to = TransactTo::Call(uniswap_v2_router); + tx.data = encoded.into(); tx.value = rU256::from(0); }) .build(); @@ -231,25 +214,30 @@ async fn get_amount_out( result => panic!("'get_amount_out' execution failed: {result:?}"), }; - let amount_out: u128 = router_abi.decode_output("getAmountOut", value)?; + let amount_out: u128 = match ::abi_decode(&value, false) { + Ok(amount_out) => amount_out, + Err(e) => panic!("'get_amount_out' decode failed: {:?}", e), + }; + Ok(U256::from(amount_out)) } async fn get_reserves( - pair_address: H160, + pair_address: Address, cache_db: &mut CacheDB>, ) -> anyhow::Result<(U256, U256)> { - let abi = BaseContract::from(parse_abi(&[ - "function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)" - ])?); + sol! { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + } + + let encoded = getReservesCall {}.abi_encode(); - let encoded = abi.encode("getReserves", ())?; let mut evm = Evm::builder() .with_db(cache_db) .modify_tx_env(|tx| { tx.caller = address!("0000000000000000000000000000000000000000"); - tx.transact_to = TransactTo::Call(to_address(pair_address)); - tx.data = encoded.0.into(); + tx.transact_to = TransactTo::Call(pair_address); + tx.data = encoded.into(); tx.value = rU256::from(0); }) .build(); @@ -265,35 +253,43 @@ async fn get_reserves( result => panic!("'get_reserves' execution failed: {result:?}"), }; - let (reserve0, reserve1, _): (u128, u128, u32) = abi.decode_output("getReserves", value)?; + let (reserve0, reserve1): (u128, u128) = match <(u128, u128, u32)>::abi_decode(&value, false) { + Ok((reserve0, reserve1, _)) => (reserve0, reserve1), + Err(e) => panic!("'get_reserves' decode failed: {:?}", e), + }; + Ok((U256::from(reserve0), U256::from(reserve1))) } async fn swap( - from: H160, - pool_address: H160, - target: H160, + from: Address, + pool_address: Address, + target: Address, amount_out: U256, is_token0: bool, cache_db: &mut CacheDB>, ) -> anyhow::Result<()> { - let abi = BaseContract::from(parse_abi(&[ - "function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external", - ])?); - - let from = to_address(from); - let pool_address = to_address(pool_address); + sol! { + function swap(uint amount0Out, uint amount1Out, address target, bytes callback) external; + } let amount0_out = if is_token0 { amount_out } else { U256::from(0) }; let amount1_out = if is_token0 { U256::from(0) } else { amount_out }; - let encoded = abi.encode("swap", (amount0_out, amount1_out, target, Bytes::new()))?; + let encoded = swapCall { + amount0Out: amount0_out, + amount1Out: amount1_out, + target, + callback: rBytes::new(), + } + .abi_encode(); + let mut evm = Evm::builder() .with_db(cache_db) .modify_tx_env(|tx| { tx.caller = from; tx.transact_to = TransactTo::Call(pool_address); - tx.data = encoded.0.into(); + tx.data = encoded.into(); tx.value = rU256::from(0); }) .build(); @@ -309,26 +305,24 @@ async fn swap( } async fn transfer( - from: H160, - to: H160, + from: Address, + to: Address, amount: U256, - token: H160, + token: Address, cache_db: &mut CacheDB>, ) -> anyhow::Result<()> { - let abi = BaseContract::from(parse_abi(&[ - "function transfer(address to, uint amount) returns (bool)", - ])?); - - let from = to_address(from); + sol! { + function transfer(address to, uint amount) external returns (bool); + } - let encoded = abi.encode("transfer", (to, amount))?; + let encoded = transferCall { to, amount }.abi_encode(); let mut evm = Evm::builder() .with_db(cache_db) .modify_tx_env(|tx| { tx.caller = from; - tx.transact_to = TransactTo::Call(to_address(token)); - tx.data = encoded.0.into(); + tx.transact_to = TransactTo::Call(token); + tx.data = encoded.into(); tx.value = rU256::from(0); }) .build(); @@ -339,7 +333,10 @@ async fn transfer( output: Output::Call(value), .. } => { - let success: bool = abi.decode_output("transfer", value)?; + let success: bool = match ::abi_decode(&value, false) { + Ok(balance) => balance, + Err(e) => panic!("'transfer' decode failed: {:?}", e), + }; success } result => panic!("'transfer' execution failed: {result:?}"), @@ -351,7 +348,3 @@ async fn transfer( Ok(()) } - -fn to_address(h160: H160) -> Address { - Address::parse_checksummed(to_checksum(&h160, None), None).unwrap() -}