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