1use crate::{EvmTr, PrecompileProvider};
6use bytecode::Bytecode;
7use context_interface::{
8 journaled_state::{account::JournaledAccountTr, JournalTr},
9 result::InvalidTransaction,
10 transaction::{AccessListItemTr, AuthorizationTr, Transaction, TransactionType},
11 Block, Cfg, ContextTr, Database,
12};
13use core::cmp::Ordering;
14use interpreter::InitialAndFloorGas;
15use primitives::{hardfork::SpecId, AddressMap, HashSet, StorageKey, U256};
16use state::AccountInfo;
17
18pub fn load_accounts<
20 EVM: EvmTr<Precompiles: PrecompileProvider<EVM::Context>>,
21 ERROR: From<<<EVM::Context as ContextTr>::Db as Database>::Error>,
22>(
23 evm: &mut EVM,
24) -> Result<(), ERROR> {
25 let (context, precompiles) = evm.ctx_precompiles();
26
27 let gen_spec = context.cfg().spec();
28 let spec = gen_spec.clone().into();
29 context.journal_mut().set_spec_id(spec);
31 let precompiles_changed = precompiles.set_spec(gen_spec);
32 let empty_warmed_precompiles = context.journal_mut().precompile_addresses().is_empty();
33
34 if precompiles_changed || empty_warmed_precompiles {
35 context
38 .journal_mut()
39 .warm_precompiles(precompiles.warm_addresses());
40 }
41
42 if spec.is_enabled_in(SpecId::SHANGHAI) {
45 let coinbase = context.block().beneficiary();
46 context.journal_mut().warm_coinbase_account(coinbase);
47 }
48
49 let (tx, journal) = context.tx_journal_mut();
51 if tx.tx_type() != TransactionType::Legacy {
53 if let Some(access_list) = tx.access_list() {
54 let mut map: AddressMap<HashSet<StorageKey>> = AddressMap::default();
55 for item in access_list {
56 map.entry(*item.address())
57 .or_default()
58 .extend(item.storage_slots().map(|key| U256::from_be_bytes(key.0)));
59 }
60 journal.warm_access_list(map);
61 }
62 }
63
64 Ok(())
65}
66
67#[inline]
69pub fn validate_account_nonce_and_code_with_components(
70 caller_info: &AccountInfo,
71 tx: impl Transaction,
72 cfg: impl Cfg,
73) -> Result<(), InvalidTransaction> {
74 validate_account_nonce_and_code(
75 caller_info,
76 tx.nonce(),
77 cfg.is_eip3607_disabled(),
78 cfg.is_nonce_check_disabled(),
79 )
80}
81
82#[inline]
84pub fn validate_account_nonce_and_code(
85 caller_info: &AccountInfo,
86 tx_nonce: u64,
87 is_eip3607_disabled: bool,
88 is_nonce_check_disabled: bool,
89) -> Result<(), InvalidTransaction> {
90 if !is_eip3607_disabled {
94 let bytecode = match caller_info.code.as_ref() {
95 Some(code) => code,
96 None => &Bytecode::default(),
97 };
98 if !bytecode.is_empty() && !bytecode.is_eip7702() {
101 return Err(InvalidTransaction::RejectCallerWithCode);
102 }
103 }
104
105 if !is_nonce_check_disabled {
107 let tx = tx_nonce;
108 let state = caller_info.nonce;
109 if tx == u64::MAX && state == u64::MAX {
110 return Err(InvalidTransaction::NonceOverflowInTransaction);
111 }
112 match tx.cmp(&state) {
113 Ordering::Greater => {
114 return Err(InvalidTransaction::NonceTooHigh { tx, state });
115 }
116 Ordering::Less => {
117 return Err(InvalidTransaction::NonceTooLow { tx, state });
118 }
119 _ => {}
120 }
121 }
122 Ok(())
123}
124
125#[inline]
129pub fn calculate_caller_fee(
130 balance: U256,
131 tx: impl Transaction,
132 block: impl Block,
133 cfg: impl Cfg,
134) -> Result<U256, InvalidTransaction> {
135 if cfg.is_fee_charge_disabled() {
138 return Ok(balance);
139 }
140
141 let basefee = block.basefee() as u128;
142 let blob_price = block.blob_gasprice().unwrap_or_default();
143 let is_balance_check_disabled = cfg.is_balance_check_disabled();
144
145 if !is_balance_check_disabled {
146 tx.ensure_enough_balance(balance)?;
147 }
148
149 let effective_balance_spending = tx
150 .effective_balance_spending(basefee, blob_price)
151 .expect("effective balance is always smaller than max balance so it can't overflow");
152
153 let gas_balance_spending = effective_balance_spending - tx.value();
154
155 let mut new_balance = balance.saturating_sub(gas_balance_spending);
157
158 if is_balance_check_disabled {
159 new_balance = new_balance.max(tx.value());
161 }
162
163 Ok(new_balance)
164}
165
166#[inline]
168pub fn validate_against_state_and_deduct_caller<
169 CTX: ContextTr,
170 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
171>(
172 context: &mut CTX,
173) -> Result<(), ERROR> {
174 let (block, tx, cfg, journal, _, _) = context.all_mut();
175
176 let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
178
179 validate_account_nonce_and_code_with_components(&caller.account().info, tx, cfg)?;
180
181 let new_balance = calculate_caller_fee(*caller.balance(), tx, block, cfg)?;
182
183 caller.set_balance(new_balance);
184 if tx.kind().is_call() {
185 caller.bump_nonce();
186 }
187 Ok(())
188}
189
190#[inline]
197pub fn apply_eip7702_auth_list<
198 CTX: ContextTr,
199 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
200>(
201 context: &mut CTX,
202 init_and_floor_gas: &mut InitialAndFloorGas,
203) -> Result<u64, ERROR> {
204 let chain_id = context.cfg().chain_id();
205 let is_eip8037 = context.cfg().is_amsterdam_eip8037_enabled();
206 let (tx, journal) = context.tx_journal_mut();
207
208 if tx.tx_type() != TransactionType::Eip7702 {
210 return Ok(0);
211 }
212 let (number_of_refunded_accounts, number_of_refunded_bytecodes) =
213 apply_auth_list::<_, ERROR>(chain_id, tx.authorization_list(), journal, is_eip8037)?;
214
215 let params = context.cfg().gas_params();
216
217 if is_eip8037 {
223 init_and_floor_gas.state_refund += params
224 .tx_eip7702_state_refund(number_of_refunded_accounts, number_of_refunded_bytecodes);
225 }
226
227 let regular_gas_refund = params
228 .tx_eip7702_auth_refund_regular()
229 .saturating_mul(number_of_refunded_accounts);
230
231 Ok(regular_gas_refund)
232}
233
234#[inline]
244pub fn apply_auth_list<
245 JOURNAL: JournalTr,
246 ERROR: From<InvalidTransaction> + From<<JOURNAL::Database as Database>::Error>,
247>(
248 chain_id: u64,
249 auth_list: impl Iterator<Item = impl AuthorizationTr>,
250 journal: &mut JOURNAL,
251 is_eip8037: bool,
252) -> Result<(u64, u64), ERROR> {
253 let mut refunded_accounts = 0;
254 let mut refunded_bytecodes = 0;
255 macro_rules! reject {
261 () => {{
262 if is_eip8037 {
263 refunded_accounts += 1;
264 refunded_bytecodes += 1;
265 }
266 continue;
267 }};
268 }
269 for authorization in auth_list {
270 let auth_chain_id = authorization.chain_id();
272 if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
273 reject!();
274 }
275
276 if authorization.nonce() == u64::MAX {
278 reject!();
279 }
280
281 let Some(authority) = authorization.authority() else {
284 reject!();
285 };
286
287 let mut authority_acc = journal.load_account_with_code_mut(authority)?;
290 let authority_acc_info = &authority_acc.account().info;
291
292 if let Some(bytecode) = &authority_acc_info.code {
294 if !bytecode.is_empty() && !bytecode.is_eip7702() {
296 reject!();
297 }
298 }
299
300 if authorization.nonce() != authority_acc_info.nonce {
302 reject!();
303 }
304
305 let existed = !(authority_acc_info.is_empty()
316 && authority_acc
317 .account()
318 .is_loaded_as_not_existing_not_touched());
319 let delegated_now = !authority_acc_info.is_code_hash_empty_or_zero();
320 let delegated_before_tx = authority_acc
321 .account()
322 .original_info()
323 .code
324 .as_ref()
325 .is_some_and(Bytecode::is_eip7702);
326 let clearing = authorization.address().is_zero();
327
328 if existed {
331 refunded_accounts += 1;
332 }
333
334 if clearing {
336 refunded_bytecodes += 1;
337 if delegated_now && !delegated_before_tx {
340 refunded_bytecodes += 1;
341 }
342 } else if delegated_now || delegated_before_tx {
343 refunded_bytecodes += 1;
344 }
345
346 authority_acc.delegate(authorization.address());
351 }
352
353 Ok((refunded_accounts, refunded_bytecodes))
354}
355
356#[cfg(test)]
357mod tests {
358 use super::validate_account_nonce_and_code;
359 use context_interface::result::InvalidTransaction;
360 use state::AccountInfo;
361
362 #[test]
363 fn rejects_transactions_when_sender_nonce_is_max() {
364 let caller_info = AccountInfo {
365 nonce: u64::MAX,
366 ..AccountInfo::default()
367 };
368
369 let err = validate_account_nonce_and_code(&caller_info, u64::MAX, false, false)
370 .expect_err("nonce-max sender should be rejected before execution");
371
372 assert_eq!(err, InvalidTransaction::NonceOverflowInTransaction);
373 }
374
375 #[test]
376 fn allows_matching_non_max_nonce() {
377 let caller_info = AccountInfo {
378 nonce: 7,
379 ..AccountInfo::default()
380 };
381
382 assert!(validate_account_nonce_and_code(&caller_info, 7, false, false).is_ok());
383 }
384}