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