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