example_erc20_gas/
main.rs1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
6
7use alloy_provider::{network::Ethereum, DynProvider, Provider, ProviderBuilder};
8use alloy_sol_types::SolValue;
9use anyhow::Result;
10use exec::transact_erc20evm_commit;
11use revm::{
12 context_interface::{
13 result::{InvalidHeader, InvalidTransaction},
14 ContextTr, JournalTr,
15 },
16 database::{AlloyDB, BlockId, CacheDB},
17 database_interface::WrapDatabaseAsync,
18 primitives::{
19 address, hardfork::SpecId, keccak256, Address, StorageValue, TxKind, KECCAK_EMPTY, U256,
20 },
21 state::AccountInfo,
22 Context, Database, MainBuilder, MainContext,
23};
24
25pub mod exec;
27pub mod handler;
29
30type AlloyCacheDB = CacheDB<WrapDatabaseAsync<AlloyDB<Ethereum, DynProvider>>>;
31
32pub const TOKEN: Address = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48");
35pub const TREASURY: Address = address!("0000000000000000000000000000000000000001");
37
38#[tokio::main]
39async fn main() -> Result<()> {
40 let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
42 let provider = ProviderBuilder::new().connect(rpc_url).await?.erased();
43
44 let alloy_db = WrapDatabaseAsync::new(AlloyDB::new(provider, BlockId::latest())).unwrap();
45 let mut cache_db = CacheDB::new(alloy_db);
46
47 let account = address!("18B06aaF27d44B756FCF16Ca20C1f183EB49111f");
49 let account_to = address!("21a4B6F62E51e59274b6Be1705c7c68781B87C77");
51
52 let hundred_tokens = U256::from(100_000_000_000_000_000u128);
54
55 let balance_slot = erc_address_storage(account);
56 println!("Balance slot: {balance_slot}");
57 cache_db
58 .insert_account_storage(TOKEN, balance_slot, hundred_tokens * StorageValue::from(2))
59 .unwrap();
60 cache_db.insert_account_info(
61 account,
62 AccountInfo {
63 nonce: 0,
64 balance: hundred_tokens * U256::from(2),
65 code_hash: KECCAK_EMPTY,
66 code: None,
67 },
68 );
69
70 let balance_before = balance_of(account, &mut cache_db).unwrap();
71 println!("Balance before: {balance_before}");
72
73 transfer(account, account_to, hundred_tokens, &mut cache_db)?;
76
77 let balance_after = balance_of(account, &mut cache_db)?;
78 println!("Balance after: {balance_after}");
79
80 Ok(())
81}
82
83pub fn token_operation<CTX, ERROR>(
85 context: &mut CTX,
86 sender: Address,
87 recipient: Address,
88 amount: U256,
89) -> Result<(), ERROR>
90where
91 CTX: ContextTr,
92 ERROR: From<InvalidTransaction> + From<InvalidHeader> + From<<CTX::Db as Database>::Error>,
93{
94 let sender_balance_slot = erc_address_storage(sender);
95 let sender_balance = context
96 .journal_mut()
97 .sload(TOKEN, sender_balance_slot)?
98 .data;
99
100 if sender_balance < amount {
101 return Err(ERROR::from(
102 InvalidTransaction::MaxFeePerBlobGasNotSupported,
103 ));
104 }
105 let sender_new_balance = sender_balance.saturating_sub(amount);
107 context
108 .journal_mut()
109 .sstore(TOKEN, sender_balance_slot, sender_new_balance)?;
110
111 let recipient_balance_slot = erc_address_storage(recipient);
113 let recipient_balance = context
114 .journal_mut()
115 .sload(TOKEN, recipient_balance_slot)?
116 .data;
117
118 let recipient_new_balance = recipient_balance.saturating_add(amount);
119 context
120 .journal_mut()
121 .sstore(TOKEN, recipient_balance_slot, recipient_new_balance)?;
122
123 Ok(())
124}
125
126fn balance_of(address: Address, alloy_db: &mut AlloyCacheDB) -> Result<StorageValue> {
127 let slot = erc_address_storage(address);
128 alloy_db.storage(TOKEN, slot).map_err(From::from)
129}
130
131fn transfer(from: Address, to: Address, amount: U256, cache_db: &mut AlloyCacheDB) -> Result<()> {
132 let mut ctx = Context::mainnet()
133 .with_db(cache_db)
134 .modify_cfg_chained(|cfg| {
135 cfg.spec = SpecId::CANCUN;
136 })
137 .modify_tx_chained(|tx| {
138 tx.caller = from;
139 tx.kind = TxKind::Call(to);
140 tx.value = amount;
141 tx.gas_price = 2;
142 })
143 .modify_block_chained(|b| {
144 b.basefee = 1;
145 })
146 .build_mainnet();
147
148 transact_erc20evm_commit(&mut ctx).unwrap();
149
150 Ok(())
151}
152
153pub fn erc_address_storage(address: Address) -> U256 {
157 keccak256((address, U256::from(4)).abi_encode()).into()
158}