example_uniswap_v2_usdc_swap/
main.rs

1//! Example of uniswap getReserves() call emulation.
2#![cfg_attr(not(test), warn(unused_crate_dependencies))]
3
4use alloy_eips::BlockId;
5use alloy_provider::{network::Ethereum, DynProvider, Provider, ProviderBuilder};
6use alloy_sol_types::{sol, SolCall, SolValue};
7use anyhow::{anyhow, Result};
8use database::{AlloyDB, CacheDB};
9use revm::{
10    context_interface::result::{ExecutionResult, Output},
11    database_interface::WrapDatabaseAsync,
12    primitives::{address, keccak256, Address, Bytes, TxKind, U256},
13    state::AccountInfo,
14    Context, ExecuteCommitEvm, ExecuteEvm, MainBuilder, MainContext,
15};
16use std::ops::Div;
17
18type AlloyCacheDB = CacheDB<WrapDatabaseAsync<AlloyDB<Ethereum, DynProvider>>>;
19
20#[tokio::main]
21async fn main() -> Result<()> {
22    // Initialize the Alloy provider and database
23    let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
24    let provider = ProviderBuilder::new().on_builtin(rpc_url).await?.erased();
25
26    let alloy_db = WrapDatabaseAsync::new(AlloyDB::new(provider, BlockId::latest())).unwrap();
27    let mut cache_db = CacheDB::new(alloy_db);
28
29    // Random empty account
30    let account = address!("18B06aaF27d44B756FCF16Ca20C1f183EB49111f");
31
32    let weth = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
33    let usdc = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48");
34    let usdc_weth_pair = address!("B4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc");
35
36    let weth_balance_slot = U256::from(3);
37
38    // Give our test account some fake WETH and ETH
39    let one_ether = U256::from(1_000_000_000_000_000_000u128);
40    let hashed_acc_balance_slot = keccak256((account, weth_balance_slot).abi_encode());
41    cache_db
42        .insert_account_storage(weth, hashed_acc_balance_slot.into(), one_ether)
43        .unwrap();
44
45    let acc_info = AccountInfo {
46        nonce: 0_u64,
47        balance: one_ether,
48        code_hash: keccak256(Bytes::new()),
49        code: None,
50    };
51    cache_db.insert_account_info(account, acc_info);
52
53    let acc_weth_balance_before = balance_of(weth, account, &mut cache_db)?;
54    println!("WETH balance before swap: {}", acc_weth_balance_before);
55    let acc_usdc_balance_before = balance_of(usdc, account, &mut cache_db)?;
56    println!("USDC balance before swap: {}", acc_usdc_balance_before);
57
58    let (reserve0, reserve1) = get_reserves(usdc_weth_pair, &mut cache_db)?;
59
60    let amount_in = one_ether.div(U256::from(10));
61
62    // Calculate USDC amount out
63    let amount_out = get_amount_out(amount_in, reserve1, reserve0, &mut cache_db).await?;
64
65    // Transfer WETH to USDC-WETH pair
66    transfer(account, usdc_weth_pair, amount_in, weth, &mut cache_db)?;
67
68    // Execute low-level swap without using UniswapV2 router
69    swap(
70        account,
71        usdc_weth_pair,
72        account,
73        amount_out,
74        true,
75        &mut cache_db,
76    )?;
77
78    let acc_weth_balance_after = balance_of(weth, account, &mut cache_db)?;
79    println!("WETH balance after swap: {}", acc_weth_balance_after);
80    let acc_usdc_balance_after = balance_of(usdc, account, &mut cache_db)?;
81    println!("USDC balance after swap: {}", acc_usdc_balance_after);
82
83    println!("OK");
84    Ok(())
85}
86
87fn balance_of(token: Address, address: Address, alloy_db: &mut AlloyCacheDB) -> Result<U256> {
88    sol! {
89        function balanceOf(address account) public returns (uint256);
90    }
91
92    let encoded = balanceOfCall { account: address }.abi_encode();
93
94    let mut evm = Context::mainnet()
95        .with_db(alloy_db)
96        .modify_tx_chained(|tx| {
97            // 0x1 because calling USDC proxy from zero address fails
98            tx.caller = address!("0000000000000000000000000000000000000001");
99            tx.kind = TxKind::Call(token);
100            tx.data = encoded.into();
101            tx.value = U256::from(0);
102        })
103        .build_mainnet();
104
105    let ref_tx = evm.transact_previous().unwrap();
106    let result = ref_tx.result;
107
108    let value = match result {
109        ExecutionResult::Success {
110            output: Output::Call(value),
111            ..
112        } => value,
113        result => return Err(anyhow!("'balanceOf' execution failed: {result:?}")),
114    };
115
116    let balance = <U256>::abi_decode(&value, false)?;
117
118    Ok(balance)
119}
120
121async fn get_amount_out(
122    amount_in: U256,
123    reserve_in: U256,
124    reserve_out: U256,
125    cache_db: &mut AlloyCacheDB,
126) -> Result<U256> {
127    let uniswap_v2_router = address!("7a250d5630b4cf539739df2c5dacb4c659f2488d");
128    sol! {
129        function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
130    }
131
132    let encoded = getAmountOutCall {
133        amountIn: amount_in,
134        reserveIn: reserve_in,
135        reserveOut: reserve_out,
136    }
137    .abi_encode();
138
139    let mut evm = Context::mainnet()
140        .with_db(cache_db)
141        .modify_tx_chained(|tx| {
142            tx.caller = address!("0000000000000000000000000000000000000000");
143            tx.kind = TxKind::Call(uniswap_v2_router);
144            tx.data = encoded.into();
145            tx.value = U256::from(0);
146        })
147        .build_mainnet();
148
149    let ref_tx = evm.transact_previous().unwrap();
150    let result = ref_tx.result;
151
152    let value = match result {
153        ExecutionResult::Success {
154            output: Output::Call(value),
155            ..
156        } => value,
157        result => return Err(anyhow!("'getAmountOut' execution failed: {result:?}")),
158    };
159
160    let amount_out = <U256>::abi_decode(&value, false)?;
161
162    Ok(amount_out)
163}
164
165fn get_reserves(pair_address: Address, cache_db: &mut AlloyCacheDB) -> Result<(U256, U256)> {
166    sol! {
167        function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
168    }
169
170    let encoded = getReservesCall {}.abi_encode();
171
172    let mut evm = Context::mainnet()
173        .with_db(cache_db)
174        .modify_tx_chained(|tx| {
175            tx.caller = address!("0000000000000000000000000000000000000000");
176            tx.kind = TxKind::Call(pair_address);
177            tx.data = encoded.into();
178            tx.value = U256::from(0);
179        })
180        .build_mainnet();
181
182    let ref_tx = evm.transact_previous().unwrap();
183    let result = ref_tx.result;
184
185    let value = match result {
186        ExecutionResult::Success {
187            output: Output::Call(value),
188            ..
189        } => value,
190        result => return Err(anyhow!("'getReserves' execution failed: {result:?}")),
191    };
192
193    let (reserve0, reserve1, _) = <(U256, U256, u32)>::abi_decode(&value, false)?;
194
195    Ok((reserve0, reserve1))
196}
197
198fn swap(
199    from: Address,
200    pool_address: Address,
201    target: Address,
202    amount_out: U256,
203    is_token0: bool,
204    cache_db: &mut AlloyCacheDB,
205) -> Result<()> {
206    sol! {
207        function swap(uint amount0Out, uint amount1Out, address target, bytes callback) external;
208    }
209
210    let amount0_out = if is_token0 { amount_out } else { U256::from(0) };
211    let amount1_out = if is_token0 { U256::from(0) } else { amount_out };
212
213    let encoded = swapCall {
214        amount0Out: amount0_out,
215        amount1Out: amount1_out,
216        target,
217        callback: Bytes::new(),
218    }
219    .abi_encode();
220
221    let mut evm = Context::mainnet()
222        .with_db(cache_db)
223        .modify_tx_chained(|tx| {
224            tx.caller = from;
225            tx.kind = TxKind::Call(pool_address);
226            tx.data = encoded.into();
227            tx.value = U256::from(0);
228            tx.nonce = 1;
229        })
230        .build_mainnet();
231
232    let ref_tx = evm.transact_commit_previous().unwrap();
233
234    match ref_tx {
235        ExecutionResult::Success { .. } => {}
236        result => return Err(anyhow!("'swap' execution failed: {result:?}")),
237    };
238
239    Ok(())
240}
241
242fn transfer(
243    from: Address,
244    to: Address,
245    amount: U256,
246    token: Address,
247    cache_db: &mut AlloyCacheDB,
248) -> Result<()> {
249    sol! {
250        function transfer(address to, uint amount) external returns (bool);
251    }
252
253    let encoded = transferCall { to, amount }.abi_encode();
254
255    let mut evm = Context::mainnet()
256        .with_db(cache_db)
257        .modify_tx_chained(|tx| {
258            tx.caller = from;
259            tx.kind = TxKind::Call(token);
260            tx.data = encoded.into();
261            tx.value = U256::from(0);
262        })
263        .build_mainnet();
264
265    let ref_tx = evm.transact_commit_previous().unwrap();
266    let success: bool = match ref_tx {
267        ExecutionResult::Success {
268            output: Output::Call(value),
269            ..
270        } => <bool>::abi_decode(&value, false)?,
271        result => return Err(anyhow!("'transfer' execution failed: {result:?}")),
272    };
273
274    if !success {
275        return Err(anyhow!("'transfer' failed"));
276    }
277
278    Ok(())
279}