revm_handler/
pre_execution.rs1use crate::{EvmTr, PrecompileProvider};
6use bytecode::Bytecode;
7use context_interface::{
8 journaled_state::JournalTr,
9 result::InvalidTransaction,
10 transaction::{AccessListItemTr, AuthorizationTr, Transaction, TransactionType},
11 Block, Cfg, ContextTr, Database,
12};
13use core::cmp::Ordering;
14use primitives::{eip7702, hardfork::SpecId, Address, HashMap, HashSet, StorageKey, U256};
15use state::AccountInfo;
16
17pub fn load_accounts<
19 EVM: EvmTr<Precompiles: PrecompileProvider<EVM::Context>>,
20 ERROR: From<<<EVM::Context as ContextTr>::Db as Database>::Error>,
21>(
22 evm: &mut EVM,
23) -> Result<(), ERROR> {
24 let (context, precompiles) = evm.ctx_precompiles();
25
26 let gen_spec = context.cfg().spec();
27 let spec = gen_spec.clone().into();
28 context.journal_mut().set_spec_id(spec);
30 let precompiles_changed = precompiles.set_spec(gen_spec);
31 let empty_warmed_precompiles = context.journal_mut().precompile_addresses().is_empty();
32
33 if precompiles_changed || empty_warmed_precompiles {
34 context
37 .journal_mut()
38 .warm_precompiles(precompiles.warm_addresses().collect());
39 }
40
41 if spec.is_enabled_in(SpecId::SHANGHAI) {
44 let coinbase = context.block().beneficiary();
45 context.journal_mut().warm_coinbase_account(coinbase);
46 }
47
48 let (tx, journal) = context.tx_journal_mut();
50 if tx.tx_type() != TransactionType::Legacy {
52 if let Some(access_list) = tx.access_list() {
53 let mut map: HashMap<Address, HashSet<StorageKey>> = HashMap::default();
54 for item in access_list {
55 map.entry(*item.address())
56 .or_default()
57 .extend(item.storage_slots().map(|key| U256::from_be_bytes(key.0)));
58 }
59 journal.warm_access_list(map);
60 }
61 }
62
63 Ok(())
64}
65
66#[inline]
68pub fn validate_account_nonce_and_code_with_components(
69 caller_info: &AccountInfo,
70 tx: impl Transaction,
71 cfg: impl Cfg,
72) -> Result<(), InvalidTransaction> {
73 validate_account_nonce_and_code(
74 caller_info,
75 tx.nonce(),
76 cfg.is_eip3607_disabled(),
77 cfg.is_nonce_check_disabled(),
78 )
79}
80
81#[inline]
83pub fn validate_account_nonce_and_code(
84 caller_info: &AccountInfo,
85 tx_nonce: u64,
86 is_eip3607_disabled: bool,
87 is_nonce_check_disabled: bool,
88) -> Result<(), InvalidTransaction> {
89 if !is_eip3607_disabled {
93 let bytecode = match caller_info.code.as_ref() {
94 Some(code) => code,
95 None => &Bytecode::default(),
96 };
97 if !bytecode.is_empty() && !bytecode.is_eip7702() {
100 return Err(InvalidTransaction::RejectCallerWithCode);
101 }
102 }
103
104 if !is_nonce_check_disabled {
106 let tx = tx_nonce;
107 let state = caller_info.nonce;
108 match tx.cmp(&state) {
109 Ordering::Greater => {
110 return Err(InvalidTransaction::NonceTooHigh { tx, state });
111 }
112 Ordering::Less => {
113 return Err(InvalidTransaction::NonceTooLow { tx, state });
114 }
115 _ => {}
116 }
117 }
118 Ok(())
119}
120
121#[inline]
125pub fn calculate_caller_fee(
126 balance: U256,
127 tx: impl Transaction,
128 block: impl Block,
129 cfg: impl Cfg,
130) -> Result<U256, InvalidTransaction> {
131 let basefee = block.basefee() as u128;
132 let blob_price = block.blob_gasprice().unwrap_or_default();
133 let is_balance_check_disabled = cfg.is_balance_check_disabled();
134
135 if !is_balance_check_disabled {
136 tx.ensure_enough_balance(balance)?;
137 }
138
139 let effective_balance_spending = tx
140 .effective_balance_spending(basefee, blob_price)
141 .expect("effective balance is always smaller than max balance so it can't overflow");
142
143 let gas_balance_spending = effective_balance_spending - tx.value();
144
145 let mut new_balance = balance.saturating_sub(gas_balance_spending);
147
148 if is_balance_check_disabled {
149 new_balance = new_balance.max(tx.value());
151 }
152
153 Ok(new_balance)
154}
155
156#[inline]
158pub fn validate_against_state_and_deduct_caller<
159 CTX: ContextTr,
160 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
161>(
162 context: &mut CTX,
163) -> Result<(), ERROR> {
164 let (block, tx, cfg, journal, _, _) = context.all_mut();
165
166 let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
168
169 validate_account_nonce_and_code_with_components(&caller.info, tx, cfg)?;
170
171 let new_balance = calculate_caller_fee(*caller.balance(), tx, block, cfg)?;
172
173 caller.set_balance(new_balance);
174 if tx.kind().is_call() {
175 caller.bump_nonce();
176 }
177 Ok(())
178}
179
180#[inline]
182pub fn apply_eip7702_auth_list<
183 CTX: ContextTr,
184 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
185>(
186 context: &mut CTX,
187) -> Result<u64, ERROR> {
188 let tx = context.tx();
189 if tx.tx_type() != TransactionType::Eip7702 {
191 return Ok(0);
192 }
193
194 let chain_id = context.cfg().chain_id();
195 let (tx, journal) = context.tx_journal_mut();
196
197 let mut refunded_accounts = 0;
198 for authorization in tx.authorization_list() {
199 let auth_chain_id = authorization.chain_id();
201 if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
202 continue;
203 }
204
205 if authorization.nonce() == u64::MAX {
207 continue;
208 }
209
210 let Some(authority) = authorization.authority() else {
213 continue;
214 };
215
216 let mut authority_acc = journal.load_account_with_code_mut(authority)?;
219
220 if let Some(bytecode) = &authority_acc.info.code {
222 if !bytecode.is_empty() && !bytecode.is_eip7702() {
224 continue;
225 }
226 }
227
228 if authorization.nonce() != authority_acc.info.nonce {
230 continue;
231 }
232
233 if !(authority_acc.is_empty() && authority_acc.is_loaded_as_not_existing_not_touched()) {
235 refunded_accounts += 1;
236 }
237
238 authority_acc.delegate(authorization.address());
243 }
244
245 let refunded_gas =
246 refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
247
248 Ok(refunded_gas)
249}