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::{eip7702, hardfork::SpecId, U256};
17use primitives::{Address, HashMap, HashSet, StorageKey};
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 let mut map: HashMap<Address, HashSet<StorageKey>> = HashMap::default();
57 for item in access_list {
58 map.entry(*item.address())
59 .or_default()
60 .extend(item.storage_slots().map(|key| U256::from_be_bytes(key.0)));
61 }
62 journal.warm_access_list(map);
63 }
64 }
65
66 Ok(())
67}
68
69#[inline]
71pub fn validate_account_nonce_and_code_with_components(
72 caller_info: &AccountInfo,
73 tx: impl Transaction,
74 cfg: impl Cfg,
75) -> Result<(), InvalidTransaction> {
76 validate_account_nonce_and_code(
77 caller_info,
78 tx.nonce(),
79 cfg.is_eip3607_disabled(),
80 cfg.is_nonce_check_disabled(),
81 )
82}
83
84#[inline]
86pub fn validate_account_nonce_and_code(
87 caller_info: &AccountInfo,
88 tx_nonce: u64,
89 is_eip3607_disabled: bool,
90 is_nonce_check_disabled: bool,
91) -> Result<(), InvalidTransaction> {
92 if !is_eip3607_disabled {
96 let bytecode = match caller_info.code.as_ref() {
97 Some(code) => code,
98 None => &Bytecode::default(),
99 };
100 if !bytecode.is_empty() && !bytecode.is_eip7702() {
103 return Err(InvalidTransaction::RejectCallerWithCode);
104 }
105 }
106
107 if !is_nonce_check_disabled {
109 let tx = tx_nonce;
110 let state = caller_info.nonce;
111 match tx.cmp(&state) {
112 Ordering::Greater => {
113 return Err(InvalidTransaction::NonceTooHigh { tx, state });
114 }
115 Ordering::Less => {
116 return Err(InvalidTransaction::NonceTooLow { tx, state });
117 }
118 _ => {}
119 }
120 }
121 Ok(())
122}
123
124#[inline]
128pub fn calculate_caller_fee(
129 balance: U256,
130 tx: impl Transaction,
131 block: impl Block,
132 cfg: impl Cfg,
133) -> Result<U256, InvalidTransaction> {
134 let basefee = block.basefee() as u128;
135 let blob_price = block.blob_gasprice().unwrap_or_default();
136 let is_balance_check_disabled = cfg.is_balance_check_disabled();
137
138 if !is_balance_check_disabled {
139 tx.ensure_enough_balance(balance)?;
140 }
141
142 let effective_balance_spending = tx
143 .effective_balance_spending(basefee, blob_price)
144 .expect("effective balance is always smaller than max balance so it can't overflow");
145
146 let gas_balance_spending = effective_balance_spending - tx.value();
147
148 let mut new_balance = balance.saturating_sub(gas_balance_spending);
150
151 if is_balance_check_disabled {
152 new_balance = new_balance.max(tx.value());
154 }
155
156 Ok(new_balance)
157}
158
159#[inline]
161pub fn validate_against_state_and_deduct_caller<
162 CTX: ContextTr,
163 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
164>(
165 context: &mut CTX,
166) -> Result<(), ERROR> {
167 let (block, tx, cfg, journal, _, _) = context.all_mut();
168
169 let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
171
172 validate_account_nonce_and_code_with_components(&caller.info, tx, cfg)?;
173
174 let new_balance = calculate_caller_fee(*caller.balance(), tx, block, cfg)?;
175
176 caller.set_balance(new_balance);
177 if tx.kind().is_call() {
178 caller.bump_nonce();
179 }
180 Ok(())
181}
182
183#[inline]
185pub fn apply_eip7702_auth_list<
186 CTX: ContextTr,
187 ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
188>(
189 context: &mut CTX,
190) -> Result<u64, ERROR> {
191 let tx = context.tx();
192 if tx.tx_type() != TransactionType::Eip7702 {
194 return Ok(0);
195 }
196
197 let chain_id = context.cfg().chain_id();
198 let (tx, journal) = context.tx_journal_mut();
199
200 let mut refunded_accounts = 0;
201 for authorization in tx.authorization_list() {
202 let auth_chain_id = authorization.chain_id();
204 if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
205 continue;
206 }
207
208 if authorization.nonce() == u64::MAX {
210 continue;
211 }
212
213 let Some(authority) = authorization.authority() else {
216 continue;
217 };
218
219 let mut authority_acc = journal.load_account_with_code_mut(authority)?;
222
223 if let Some(bytecode) = &authority_acc.info.code {
225 if !bytecode.is_empty() && !bytecode.is_eip7702() {
227 continue;
228 }
229 }
230
231 if authorization.nonce() != authority_acc.info.nonce {
233 continue;
234 }
235
236 if !(authority_acc.is_empty() && authority_acc.is_loaded_as_not_existing_not_touched()) {
238 refunded_accounts += 1;
239 }
240
241 authority_acc.delegate(authorization.address());
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}