diff --git a/Cargo.lock b/Cargo.lock index d7dcdf72b0..80d0007123 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1604,6 +1604,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.0" @@ -1778,7 +1797,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.25", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -1801,6 +1820,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.4", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -2780,7 +2800,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.25", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", @@ -2818,8 +2838,10 @@ checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" dependencies = [ "base64 0.21.7", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2 0.4.4", "http 1.1.0", "http-body 1.0.0", "http-body-util", @@ -2839,6 +2861,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -2855,6 +2878,7 @@ version = "8.0.0" dependencies = [ "alloy-provider", "alloy-rpc-types", + "alloy-sol-types", "alloy-transport", "alloy-transport-http", "anyhow", @@ -2866,6 +2890,7 @@ dependencies = [ "ethers-core", "ethers-providers", "indicatif", + "reqwest 0.12.2", "revm-interpreter", "revm-precompile", "serde", diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 8510a9edd0..2a517a4a17 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -46,10 +46,12 @@ 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-sol-types = { version = "0.7.0", default-features = false, features = ["std"] } ethers-contract = { version = "2.0.14", default-features = false } anyhow = "1.0.82" criterion = "0.5" indicatif = "0.17" +reqwest = { version = "0.12" } alloy-provider = { git = "https://github.com/alloy-rs/alloy.git", default-features = false, features = ["reqwest"] } # needed for enabling TLS to use HTTPS connections when testing alloy DB @@ -136,6 +138,11 @@ name = "db_by_ref" path = "../../examples/db_by_ref.rs" required-features = ["std", "serde-json"] +[[example]] +name = "uniswap_v2_usdc_swap" +path = "../../examples/uniswap_v2_usdc_swap.rs" +required-features = ["alloydb"] + [[bench]] name = "bench" path = "benches/bench.rs" diff --git a/examples/uniswap_v2_usdc_swap.rs b/examples/uniswap_v2_usdc_swap.rs new file mode 100644 index 0000000000..9171da10ac --- /dev/null +++ b/examples/uniswap_v2_usdc_swap.rs @@ -0,0 +1,278 @@ +use alloy_provider::{network::Ethereum, ProviderBuilder, RootProvider}; +use alloy_sol_types::{sol, SolCall, SolValue}; +use alloy_transport_http::Http; +use anyhow::{anyhow, Result}; +use reqwest::Client; +use revm::{ + db::{AlloyDB, CacheDB}, + primitives::{ + address, keccak256, AccountInfo, Address, Bytes, ExecutionResult, Output, TransactTo, U256, + }, + Evm, +}; +use std::ops::Div; +use std::sync::Arc; + +type AlloyCacheDB = CacheDB, Ethereum, Arc>>>>; + +#[tokio::main] +async fn main() -> Result<()> { + let client = ProviderBuilder::new() + .on_reqwest_http( + "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27" + .parse() + .unwrap(), + ) + .unwrap(); + let client = Arc::new(client); + let mut cache_db = CacheDB::new(AlloyDB::new(client, None)); + + // Random empty account + let account = address!("18B06aaF27d44B756FCF16Ca20C1f183EB49111f"); + + let weth = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let usdc = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + let usdc_weth_pair = address!("B4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"); + + let weth_balance_slot = U256::from(3); + + // give our test account some fake WETH and ETH + let one_ether = U256::from(1_000_000_000_000_000_000u128); + let hashed_acc_balance_slot = keccak256((account, weth_balance_slot).abi_encode()); + cache_db + .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: None, + }; + cache_db.insert_account_info(account, acc_info); + + let acc_weth_balance_before = balance_of(weth, account, &mut cache_db)?; + println!("WETH balance before swap: {}", acc_weth_balance_before); + let acc_usdc_balance_before = balance_of(usdc, account, &mut cache_db)?; + println!("USDC balance before swap: {}", acc_usdc_balance_before); + + let (reserve0, reserve1) = get_reserves(usdc_weth_pair, &mut cache_db)?; + + let amount_in = one_ether.div(U256::from(10)); + + // calculate USDC amount out + let amount_out = get_amount_out(amount_in, reserve1, reserve0, &mut cache_db).await?; + + // transfer WETH to USDC-WETH pair + transfer(account, usdc_weth_pair, amount_in, weth, &mut cache_db)?; + + // execute low-level swap without using UniswapV2 router + swap( + account, + usdc_weth_pair, + account, + amount_out, + true, + &mut cache_db, + )?; + + let acc_weth_balance_after = balance_of(weth, account, &mut cache_db)?; + println!("WETH balance after swap: {}", acc_weth_balance_after); + let acc_usdc_balance_after = balance_of(usdc, account, &mut cache_db)?; + println!("USDC balance after swap: {}", acc_usdc_balance_after); + + Ok(()) +} + +fn balance_of(token: Address, address: Address, cache_db: &mut AlloyCacheDB) -> Result { + sol! { + function balanceOf(address account) public returns (uint256); + } + + 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(token); + tx.data = encoded.into(); + tx.value = U256::from(0); + }) + .build(); + + let ref_tx = evm.transact().unwrap(); + let result = ref_tx.result; + + let value = match result { + ExecutionResult::Success { + output: Output::Call(value), + .. + } => value, + result => return Err(anyhow!("'balanceOf' execution failed: {result:?}")), + }; + + let balance = ::abi_decode(&value, false)?; + + Ok(balance) +} + +async fn get_amount_out( + amount_in: U256, + reserve_in: U256, + reserve_out: U256, + cache_db: &mut AlloyCacheDB, +) -> Result { + let uniswap_v2_router = address!("7a250d5630b4cf539739df2c5dacb4c659f2488d"); + sol! { + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + } + + 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(uniswap_v2_router); + tx.data = encoded.into(); + tx.value = U256::from(0); + }) + .build(); + + let ref_tx = evm.transact().unwrap(); + let result = ref_tx.result; + + let value = match result { + ExecutionResult::Success { + output: Output::Call(value), + .. + } => value, + result => return Err(anyhow!("'getAmountOut' execution failed: {result:?}")), + }; + + let amount_out = ::abi_decode(&value, false)?; + + Ok(amount_out) +} + +fn get_reserves(pair_address: Address, cache_db: &mut AlloyCacheDB) -> Result<(U256, U256)> { + sol! { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + } + + let encoded = getReservesCall {}.abi_encode(); + + let mut evm = Evm::builder() + .with_db(cache_db) + .modify_tx_env(|tx| { + tx.caller = address!("0000000000000000000000000000000000000000"); + tx.transact_to = TransactTo::Call(pair_address); + tx.data = encoded.into(); + tx.value = U256::from(0); + }) + .build(); + + let ref_tx = evm.transact().unwrap(); + let result = ref_tx.result; + + let value = match result { + ExecutionResult::Success { + output: Output::Call(value), + .. + } => value, + result => return Err(anyhow!("'getReserves' execution failed: {result:?}")), + }; + + let (reserve0, reserve1, _) = <(U256, U256, u32)>::abi_decode(&value, false)?; + + Ok((reserve0, reserve1)) +} + +fn swap( + from: Address, + pool_address: Address, + target: Address, + amount_out: U256, + is_token0: bool, + cache_db: &mut AlloyCacheDB, +) -> Result<()> { + 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 = swapCall { + amount0Out: amount0_out, + amount1Out: amount1_out, + target, + callback: Bytes::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.into(); + tx.value = U256::from(0); + }) + .build(); + + let ref_tx = evm.transact_commit().unwrap(); + + match ref_tx { + ExecutionResult::Success { .. } => {} + result => return Err(anyhow!("'swap' execution failed: {result:?}")), + }; + + Ok(()) +} + +fn transfer( + from: Address, + to: Address, + amount: U256, + token: Address, + cache_db: &mut AlloyCacheDB, +) -> Result<()> { + sol! { + function transfer(address to, uint amount) external returns (bool); + } + + 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(token); + tx.data = encoded.into(); + tx.value = U256::from(0); + }) + .build(); + + let ref_tx = evm.transact_commit().unwrap(); + let success: bool = match ref_tx { + ExecutionResult::Success { + output: Output::Call(value), + .. + } => ::abi_decode(&value, false)?, + result => return Err(anyhow!("'transfer' execution failed: {result:?}")), + }; + + if !success { + return Err(anyhow!("'transfer' failed")); + } + + Ok(()) +}