Skip to content

Commit

Permalink
Use alloy primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
pawurb committed Apr 23, 2024
1 parent 73acdcf commit 8241d25
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 103 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/revm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
199 changes: 96 additions & 103 deletions examples/uniswap_v2_usdc_swap.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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 {
Expand All @@ -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?;
Expand Down Expand Up @@ -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<EmptyDBTyped<Infallible>>,
) -> anyhow::Result<u128> {
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();
Expand All @@ -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 <u128>::abi_decode(&value, false) {
Ok(balance) => balance,
Err(e) => panic!("'balance_of' decode failed: {:?}", e),
};

Ok(balance)
}

Expand All @@ -201,21 +181,24 @@ async fn get_amount_out(
reserve_out: U256,
cache_db: &mut CacheDB<EmptyDBTyped<Infallible>>,
) -> anyhow::Result<U256> {
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();
Expand All @@ -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 <u128>::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<EmptyDBTyped<Infallible>>,
) -> 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();
Expand All @@ -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<EmptyDBTyped<Infallible>>,
) -> 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();
Expand All @@ -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<EmptyDBTyped<Infallible>>,
) -> 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();
Expand All @@ -339,7 +333,10 @@ async fn transfer(
output: Output::Call(value),
..
} => {
let success: bool = abi.decode_output("transfer", value)?;
let success: bool = match <bool>::abi_decode(&value, false) {
Ok(balance) => balance,
Err(e) => panic!("'transfer' decode failed: {:?}", e),
};
success
}
result => panic!("'transfer' execution failed: {result:?}"),
Expand All @@ -351,7 +348,3 @@ async fn transfer(

Ok(())
}

fn to_address(h160: H160) -> Address {
Address::parse_checksummed(to_checksum(&h160, None), None).unwrap()
}

0 comments on commit 8241d25

Please sign in to comment.