example_uniswap_get_reserves/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::ProviderBuilder;
6use alloy_sol_types::{sol, SolCall};
7use revm::{
8 context::TxEnv,
9 context_interface::result::{ExecutionResult, Output},
10 database::{AlloyDB, CacheDB},
11 database_interface::{DatabaseRef, EmptyDB, WrapDatabaseAsync},
12 primitives::{address, StorageKey, TxKind, U256},
13 Context, ExecuteEvm, MainBuilder, MainContext,
14};
15
16#[tokio::main]
17async fn main() -> anyhow::Result<()> {
18 // Initialize the Alloy provider and database
19 let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
20 let provider = ProviderBuilder::new().connect(rpc_url).await?;
21
22 let alloy_db = WrapDatabaseAsync::new(AlloyDB::new(provider, BlockId::latest())).unwrap();
23 let cache_db = CacheDB::new(alloy_db);
24
25 // ----------------------------------------------------------- //
26 // Storage slots of UniV2Pair contract //
27 // =========================================================== //
28 // storage[5] = factory: address //
29 // storage[6] = token0: address //
30 // storage[7] = token1: address //
31 // storage[8] = (res0, res1, ts): (uint112, uint112, uint32) //
32 // storage[9] = price0CumulativeLast: uint256 //
33 // storage[10] = price1CumulativeLast: uint256 //
34 // storage[11] = kLast: uint256 //
35 // =========================================================== //
36
37 // Choose slot of storage that you would like to transact with
38 let slot = StorageKey::from(8);
39
40 // ETH/USDT pair on Uniswap V2
41 let pool_address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852");
42
43 // Generate abi for the calldata from the human readable interface
44 sol! {
45 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
46 }
47
48 // Encode abi into Bytes
49 let encoded = getReservesCall::new(()).abi_encode();
50
51 // Query basic properties of an account incl bytecode
52 let acc_info = cache_db.basic_ref(pool_address).unwrap().unwrap();
53
54 // Query value of storage slot at account address
55 let value = cache_db.storage_ref(pool_address, slot).unwrap();
56
57 // Initialise empty in-memory-db
58 let mut cache_db = CacheDB::new(EmptyDB::default());
59
60 // Insert basic account info which was generated via Web3DB with the corresponding address
61 cache_db.insert_account_info(pool_address, acc_info);
62
63 // Insert our pre-loaded storage slot to the corresponding contract key (address) in the DB
64 cache_db
65 .insert_account_storage(pool_address, slot, value)
66 .unwrap();
67
68 // Initialise an empty (default) EVM
69 let mut evm = Context::mainnet().with_db(cache_db).build_mainnet();
70
71 // Execute transaction without writing to the DB
72 let result = evm
73 .transact_one(
74 TxEnv::builder()
75 // fill in missing bits of env struct
76 // change that to whatever caller you want to be
77 .caller(address!("0000000000000000000000000000000000000000"))
78 // account you want to transact with
79 .kind(TxKind::Call(pool_address))
80 // calldata formed via abigen
81 .data(encoded.into())
82 // transaction value in wei
83 .value(U256::from(0))
84 .build()
85 .unwrap(),
86 )
87 .unwrap();
88
89 // Unpack output call enum into raw bytes
90 let value = match result {
91 ExecutionResult::Success {
92 output: Output::Call(value),
93 ..
94 } => value,
95 _ => panic!("Execution failed: {result:?}"),
96 };
97
98 // Decode bytes to reserves + ts via alloy's abi decode
99 let return_vals = getReservesCall::abi_decode_returns(&value)?;
100
101 // Print emulated getReserves() call output
102 println!("Reserve0: {:#?}", return_vals.reserve0);
103 println!("Reserve1: {:#?}", return_vals.reserve1);
104 println!("Timestamp: {:#?}", return_vals.blockTimestampLast);
105
106 Ok(())
107}