example_uniswap_v2_usdc_swap/
main.rs1#![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 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 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 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 let amount_out = get_amount_out(amount_in, reserve1, reserve0, &mut cache_db).await?;
65
66 transfer(account, usdc_weth_pair, amount_in, weth, &mut cache_db)?;
68
69 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(TxEnv {
99 caller: address!("0000000000000000000000000000000000000001"),
101 kind: TxKind::Call(token),
102 data: encoded.into(),
103 value: U256::from(0),
104 ..Default::default()
105 })
106 .unwrap();
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)?;
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().with_db(cache_db).build_mainnet();
140
141 let result = evm
142 .transact(TxEnv {
143 caller: address!("0000000000000000000000000000000000000000"),
144 kind: TxKind::Call(uniswap_v2_router),
145 data: encoded.into(),
146 value: U256::from(0),
147 ..Default::default()
148 })
149 .unwrap();
150
151 let value = match result {
152 ExecutionResult::Success {
153 output: Output::Call(value),
154 ..
155 } => value,
156 result => return Err(anyhow!("'getAmountOut' execution failed: {result:?}")),
157 };
158
159 let amount_out = <U256>::abi_decode(&value)?;
160
161 Ok(amount_out)
162}
163
164fn get_reserves(pair_address: Address, cache_db: &mut AlloyCacheDB) -> Result<(U256, U256)> {
165 sol! {
166 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
167 }
168
169 let encoded = getReservesCall {}.abi_encode();
170
171 let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
172
173 let result = evm
174 .transact(TxEnv {
175 caller: address!("0000000000000000000000000000000000000000"),
176 kind: TxKind::Call(pair_address),
177 data: encoded.into(),
178 value: U256::from(0),
179 ..Default::default()
180 })
181 .unwrap();
182
183 let value = match result {
184 ExecutionResult::Success {
185 output: Output::Call(value),
186 ..
187 } => value,
188 result => return Err(anyhow!("'getReserves' execution failed: {result:?}")),
189 };
190
191 let (reserve0, reserve1, _) = <(U256, U256, u32)>::abi_decode(&value)?;
192
193 Ok((reserve0, reserve1))
194}
195
196fn swap(
197 from: Address,
198 pool_address: Address,
199 target: Address,
200 amount_out: U256,
201 is_token0: bool,
202 cache_db: &mut AlloyCacheDB,
203) -> Result<()> {
204 sol! {
205 function swap(uint amount0Out, uint amount1Out, address target, bytes callback) external;
206 }
207
208 let amount0_out = if is_token0 { amount_out } else { U256::from(0) };
209 let amount1_out = if is_token0 { U256::from(0) } else { amount_out };
210
211 let encoded = swapCall {
212 amount0Out: amount0_out,
213 amount1Out: amount1_out,
214 target,
215 callback: Bytes::new(),
216 }
217 .abi_encode();
218
219 let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
220
221 let tx = TxEnv {
222 caller: from,
223 kind: TxKind::Call(pool_address),
224 data: encoded.into(),
225 value: U256::from(0),
226 nonce: 1,
227 ..Default::default()
228 };
229
230 let ref_tx = evm.transact_commit(tx).unwrap();
231
232 match ref_tx {
233 ExecutionResult::Success { .. } => {}
234 result => return Err(anyhow!("'swap' execution failed: {result:?}")),
235 };
236
237 Ok(())
238}
239
240fn transfer(
241 from: Address,
242 to: Address,
243 amount: U256,
244 token: Address,
245 cache_db: &mut AlloyCacheDB,
246) -> Result<()> {
247 sol! {
248 function transfer(address to, uint amount) external returns (bool);
249 }
250
251 let encoded = transferCall { to, amount }.abi_encode();
252
253 let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
254
255 let tx = TxEnv {
256 caller: from,
257 kind: TxKind::Call(token),
258 data: encoded.into(),
259 value: U256::from(0),
260 ..Default::default()
261 };
262
263 let ref_tx = evm.transact_commit(tx).unwrap();
264 let success: bool = match ref_tx {
265 ExecutionResult::Success {
266 output: Output::Call(value),
267 ..
268 } => <bool>::abi_decode(&value)?,
269 result => return Err(anyhow!("'transfer' execution failed: {result:?}")),
270 };
271
272 if !success {
273 return Err(anyhow!("'transfer' failed"));
274 }
275
276 Ok(())
277}