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, 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 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 let amount_out = get_amount_out(amount_in, reserve1, reserve0, &mut cache_db).await?;
63
64 transfer(account, usdc_weth_pair, amount_in, weth, &mut cache_db)?;
66
67 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}