Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uniswap V2 WETH-USDC swap example #1353

Merged
merged 11 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/revm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
278 changes: 278 additions & 0 deletions examples/uniswap_v2_usdc_swap.rs
Original file line number Diff line number Diff line change
@@ -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<AlloyDB<Http<Client>, Ethereum, Arc<RootProvider<Http<Client>>>>>;

#[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<U256> {
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 = <U256>::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<U256> {
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 = <U256>::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),
..
} => <bool>::abi_decode(&value, false)?,
result => return Err(anyhow!("'transfer' execution failed: {result:?}")),
};

if !success {
return Err(anyhow!("'transfer' failed"));
}

Ok(())
}
Loading