revm_handler/
pre_execution.rs1use crate::{EvmTr, PrecompileProvider};
6use bytecode::Bytecode;
7use context_interface::transaction::{AccessListItemTr, AuthorizationTr};
8use context_interface::ContextTr;
9use context_interface::{
10 journaled_state::JournalTr,
11 result::InvalidTransaction,
12 transaction::{Transaction, TransactionType},
13 Block, Cfg, Database,
14};
15use core::cmp::Ordering;
16use primitives::StorageKey;
17use primitives::{eip7702, hardfork::SpecId, KECCAK_EMPTY, U256};
18use state::AccountInfo;
19
20pub fn load_accounts<
22 EVM: EvmTr<Precompiles: PrecompileProvider<EVM::Context>>,
23 ERROR: From<<<EVM::Context as ContextTr>::Db as Database>::Error>,
24>(
25 evm: &mut EVM,
26) -> Result<(), ERROR> {
27 let (context, precompiles) = evm.ctx_precompiles();
28
29 let gen_spec = context.cfg().spec();
30 let spec = gen_spec.clone().into();
31 context.journal_mut().set_spec_id(spec);
33 let precompiles_changed = precompiles.set_spec(gen_spec);
34 let empty_warmed_precompiles = context.journal_mut().precompile_addresses().is_empty();
35
36 if precompiles_changed || empty_warmed_precompiles {
37 context
40 .journal_mut()
41 .warm_precompiles(precompiles.warm_addresses().collect());
42 }
43
44 if spec.is_enabled_in(SpecId::SHANGHAI) {
47 let coinbase = context.block().beneficiary();
48 context.journal_mut().warm_coinbase_account(coinbase);
49 }
50
51 let (tx, journal) = context.tx_journal_mut();
53 if tx.tx_type() != TransactionType::Legacy {
55 if let Some(access_list) = tx.access_list() {
56 for item in access_list {
57 journal.warm_account_and_storage(
58 *item.address(),
59 item.storage_slots().map(|i| StorageKey::from_be_bytes(i.0)),
60 )?;
61 }
62 }
63 }
64
65 Ok(())
66}
67
68#[inline]
70pub fn validate_account_nonce_and_code(
71 caller_info: &mut AccountInfo,
72 tx_nonce: u64,
73 is_eip3607_disabled: bool,
74 is_nonce_check_disabled: bool,
75) -> Result<(), InvalidTransaction> {
76 if !is_eip3607_disabled {
80 let bytecode = match caller_info.code.as_ref() {
81 Some(code) => code,
82 None => &Bytecode::default(),
83 };
84 if !bytecode.is_empty() && !bytecode.is_eip7702() {
87 return Err(InvalidTransaction::RejectCallerWithCode);
88 }
89 }
90
91 if !is_nonce_check_disabled {
93 let tx = tx_nonce;
94 let state = caller_info.nonce;
95 match tx.cmp(&state) {
96 Ordering::Greater => {
97 return Err(InvalidTransaction::NonceTooHigh { tx, state });
98 }
99 Ordering::Less => {
100 return Err(InvalidTransaction::NonceTooLow { tx, state });
101 }
102 _ => {}
103 }
104 }
105 Ok(())
106}
107
108#[inline]
112pub fn calculate_caller_fee(
113 balance: U256,
114 tx: impl Transaction,
115 block: impl Block,
116 cfg: impl Cfg,
117) -> Result<U256, InvalidTransaction> {
118 let basefee = block.basefee() as u128;
119 let blob_price = block.blob_gasprice().unwrap_or_default();
120 let is_balance_check_disabled = cfg.is_balance_check_disabled();
121
122 if !is_balance_check_disabled {
123 tx.ensure_enough_balance(balance)?;
124 }
125
126 let effective_balance_spending = tx
127 .effective_balance_spending(basefee, blob_price)
128 .expect("effective balance is always smaller than max balance so it can't overflow");
129
130 let gas_balance_spending = effective_balance_spending - tx.value();
131
132 let mut new_balance = balance.saturating_sub(gas_balance_spending);
134
135 if is_balance_check_disabled {
136 new_balance = new_balance.max(tx.value());
138 }
139
140 Ok(new_balance)
141}
142
143#[inline]
145pub fn validate_against_state_and_deduct_caller<
146 CTX: ContextTr,
147 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
148>(
149 context: &mut CTX,
150) -> Result<(), ERROR> {
151 let (block, tx, cfg, journal, _, _) = context.all_mut();
152
153 let caller_account = journal.load_account_code(tx.caller())?.data;
155
156 validate_account_nonce_and_code(
157 &mut caller_account.info,
158 tx.nonce(),
159 cfg.is_eip3607_disabled(),
160 cfg.is_nonce_check_disabled(),
161 )?;
162
163 let new_balance = calculate_caller_fee(caller_account.info.balance, tx, block, cfg)?;
164
165 let old_balance = caller_account.caller_initial_modification(new_balance, tx.kind().is_call());
166
167 journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call());
168 Ok(())
169}
170
171#[inline]
173pub fn apply_eip7702_auth_list<
174 CTX: ContextTr,
175 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
176>(
177 context: &mut CTX,
178) -> Result<u64, ERROR> {
179 let tx = context.tx();
180 if tx.tx_type() != TransactionType::Eip7702 {
182 return Ok(0);
183 }
184
185 let chain_id = context.cfg().chain_id();
186 let (tx, journal) = context.tx_journal_mut();
187
188 let mut refunded_accounts = 0;
189 for authorization in tx.authorization_list() {
190 let auth_chain_id = authorization.chain_id();
192 if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
193 continue;
194 }
195
196 if authorization.nonce() == u64::MAX {
198 continue;
199 }
200
201 let Some(authority) = authorization.authority() else {
204 continue;
205 };
206
207 let mut authority_acc = journal.load_account_code(authority)?;
210
211 if let Some(bytecode) = &authority_acc.info.code {
213 if !bytecode.is_empty() && !bytecode.is_eip7702() {
215 continue;
216 }
217 }
218
219 if authorization.nonce() != authority_acc.info.nonce {
221 continue;
222 }
223
224 if !(authority_acc.is_empty() && authority_acc.is_loaded_as_not_existing_not_touched()) {
226 refunded_accounts += 1;
227 }
228
229 let address = authorization.address();
233 let (bytecode, hash) = if address.is_zero() {
234 (Bytecode::default(), KECCAK_EMPTY)
235 } else {
236 let bytecode = Bytecode::new_eip7702(address);
237 let hash = bytecode.hash_slow();
238 (bytecode, hash)
239 };
240 authority_acc.info.code_hash = hash;
241 authority_acc.info.code = Some(bytecode);
242
243 authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1);
245 authority_acc.mark_touch();
246 }
247
248 let refunded_gas =
249 refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
250
251 Ok(refunded_gas)
252}