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_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}