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, KECCAK_EMPTY, 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        nonce: 0_u64,
48        balance: one_ether,
49        code_hash: KECCAK_EMPTY,
50        code: None,
51    };
52    cache_db.insert_account_info(account, acc_info);
53
54    let acc_weth_balance_before = balance_of(weth, account, &mut cache_db)?;
55    println!("WETH balance before swap: {acc_weth_balance_before}");
56    let acc_usdc_balance_before = balance_of(usdc, account, &mut cache_db)?;
57    println!("USDC balance before swap: {acc_usdc_balance_before}");
58
59    let (reserve0, reserve1) = get_reserves(usdc_weth_pair, &mut cache_db)?;
60
61    let amount_in = one_ether.div(U256::from(10));
62
63    // Calculate USDC amount out
64    let amount_out = get_amount_out(amount_in, reserve1, reserve0, &mut cache_db).await?;
65
66    // Transfer WETH to USDC-WETH pair
67    transfer(account, usdc_weth_pair, amount_in, weth, &mut cache_db)?;
68
69    // Execute low-level swap without using UniswapV2 router
70    swap(
71        account,
72        usdc_weth_pair,
73        account,
74        amount_out,
75        true,
76        &mut cache_db,
77    )?;
78
79    let acc_weth_balance_after = balance_of(weth, account, &mut cache_db)?;
80    println!("WETH balance after swap: {acc_weth_balance_after}");
81    let acc_usdc_balance_after = balance_of(usdc, account, &mut cache_db)?;
82    println!("USDC balance after swap: {acc_usdc_balance_after}");
83
84    println!("OK");
85    Ok(())
86}
87
88fn balance_of(token: Address, address: Address, alloy_db: &mut AlloyCacheDB) -> Result<U256> {
89    sol! {
90        function balanceOf(address account) public returns (uint256);
91    }
92
93    let encoded = balanceOfCall { account: address }.abi_encode();
94
95    let mut evm = Context::mainnet().with_db(alloy_db).build_mainnet();
96
97    let result = evm
98        .transact_one(
99            TxEnv::builder()
100                .caller(address!("0000000000000000000000000000000000000001"))
101                .kind(TxKind::Call(token))
102                .data(encoded.into())
103                .value(U256::from(0))
104                .build()
105                .unwrap(),
106        )
107        .unwrap();
108
109    let value = match result {
110        ExecutionResult::Success {
111            output: Output::Call(value),
112            ..
113        } => value,
114        result => return Err(anyhow!("'balanceOf' execution failed: {result:?}")),
115    };
116
117    let balance = <U256>::abi_decode(&value)?;
118
119    Ok(balance)
120}
121
122async fn get_amount_out(
123    amount_in: U256,
124    reserve_in: U256,
125    reserve_out: U256,
126    cache_db: &mut AlloyCacheDB,
127) -> Result<U256> {
128    let uniswap_v2_router = address!("7a250d5630b4cf539739df2c5dacb4c659f2488d");
129    sol! {
130        function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
131    }
132
133    let encoded = getAmountOutCall {
134        amountIn: amount_in,
135        reserveIn: reserve_in,
136        reserveOut: reserve_out,
137    }
138    .abi_encode();
139
140    let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
141
142    let result = evm
143        .transact_one(
144            TxEnv::builder()
145                .caller(address!("0000000000000000000000000000000000000000"))
146                .kind(TxKind::Call(uniswap_v2_router))
147                .data(encoded.into())
148                .value(U256::from(0))
149                .build()
150                .unwrap(),
151        )
152        .unwrap();
153
154    let value = match result {
155        ExecutionResult::Success {
156            output: Output::Call(value),
157            ..
158        } => value,
159        result => return Err(anyhow!("'getAmountOut' execution failed: {result:?}")),
160    };
161
162    let amount_out = <U256>::abi_decode(&value)?;
163
164    Ok(amount_out)
165}
166
167fn get_reserves(pair_address: Address, cache_db: &mut AlloyCacheDB) -> Result<(U256, U256)> {
168    sol! {
169        function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
170    }
171
172    let encoded = getReservesCall {}.abi_encode();
173
174    let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
175
176    let result = evm
177        .transact_one(
178            TxEnv::builder()
179                .caller(address!("0000000000000000000000000000000000000000"))
180                .kind(TxKind::Call(pair_address))
181                .data(encoded.into())
182                .value(U256::from(0))
183                .build()
184                .unwrap(),
185        )
186        .unwrap();
187
188    let value = match result {
189        ExecutionResult::Success {
190            output: Output::Call(value),
191            ..
192        } => value,
193        result => return Err(anyhow!("'getReserves' execution failed: {result:?}")),
194    };
195
196    let (reserve0, reserve1, _) = <(U256, U256, u32)>::abi_decode(&value)?;
197
198    Ok((reserve0, reserve1))
199}
200
201fn swap(
202    from: Address,
203    pool_address: Address,
204    target: Address,
205    amount_out: U256,
206    is_token0: bool,
207    cache_db: &mut AlloyCacheDB,
208) -> Result<()> {
209    sol! {
210        function swap(uint amount0Out, uint amount1Out, address target, bytes callback) external;
211    }
212
213    let amount0_out = if is_token0 { amount_out } else { U256::from(0) };
214    let amount1_out = if is_token0 { U256::from(0) } else { amount_out };
215
216    let encoded = swapCall {
217        amount0Out: amount0_out,
218        amount1Out: amount1_out,
219        target,
220        callback: Bytes::new(),
221    }
222    .abi_encode();
223
224    let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
225
226    let tx = TxEnv::builder()
227        .caller(from)
228        .kind(TxKind::Call(pool_address))
229        .data(encoded.into())
230        .value(U256::from(0))
231        .nonce(1)
232        .build()
233        .unwrap();
234
235    let ref_tx = evm.transact_commit(tx).unwrap();
236
237    match ref_tx {
238        ExecutionResult::Success { .. } => {}
239        result => return Err(anyhow!("'swap' execution failed: {result:?}")),
240    };
241
242    Ok(())
243}
244
245fn transfer(
246    from: Address,
247    to: Address,
248    amount: U256,
249    token: Address,
250    cache_db: &mut AlloyCacheDB,
251) -> Result<()> {
252    sol! {
253        function transfer(address to, uint amount) external returns (bool);
254    }
255
256    let encoded = transferCall { to, amount }.abi_encode();
257
258    let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
259
260    let tx = TxEnv::builder()
261        .caller(from)
262        .kind(TxKind::Call(token))
263        .data(encoded.into())
264        .value(U256::from(0))
265        .build()
266        .unwrap();
267
268    let ref_tx = evm.transact_commit(tx).unwrap();
269    let success: bool = match ref_tx {
270        ExecutionResult::Success {
271            output: Output::Call(value),
272            ..
273        } => <bool>::abi_decode(&value)?,
274        result => return Err(anyhow!("'transfer' execution failed: {result:?}")),
275    };
276
277    if !success {
278        return Err(anyhow!("'transfer' failed"));
279    }
280
281    Ok(())
282}