revm_handler/
pre_execution.rs

1//! Handles related to the main function of the EVM.
2//!
3//! They handle initial setup of the EVM, call loop and the final return of the EVM
4
5use crate::{EvmTr, PrecompileProvider};
6use bytecode::Bytecode;
7use context::journaled_state::account::JournaledAccountTr;
8use context_interface::{
9    journaled_state::JournalTr,
10    result::InvalidTransaction,
11    transaction::{AccessListItemTr, AuthorizationTr, Transaction, TransactionType},
12    Block, Cfg, ContextTr, Database,
13};
14use core::cmp::Ordering;
15use primitives::{eip7702, hardfork::SpecId, Address, HashMap, HashSet, StorageKey, U256};
16use state::AccountInfo;
17
18/// Loads and warms accounts for execution, including precompiles and access list.
19pub 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    // sets eth spec id in journal
30    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        // load new precompile addresses into journal.
36        // When precompiles addresses are changed we reset the warmed hashmap to those new addresses.
37        context
38            .journal_mut()
39            .warm_precompiles(precompiles.warm_addresses().collect());
40    }
41
42    // Load coinbase
43    // EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm
44    if spec.is_enabled_in(SpecId::SHANGHAI) {
45        let coinbase = context.block().beneficiary();
46        context.journal_mut().warm_coinbase_account(coinbase);
47    }
48
49    // Load access list
50    let (tx, journal) = context.tx_journal_mut();
51    // legacy is only tx type that does not have access list.
52    if tx.tx_type() != TransactionType::Legacy {
53        if let Some(access_list) = tx.access_list() {
54            let mut map: HashMap<Address, HashSet<StorageKey>> = HashMap::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/// Validates caller account nonce and code according to EIP-3607.
68#[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/// Validates caller account nonce and code according to EIP-3607.
83#[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    // EIP-3607: Reject transactions from senders with deployed code
91    // This EIP is introduced after london but there was no collision in past
92    // so we can leave it enabled always
93    if !is_eip3607_disabled {
94        let bytecode = match caller_info.code.as_ref() {
95            Some(code) => code,
96            None => &Bytecode::default(),
97        };
98        // Allow EOAs whose code is a valid delegation designation,
99        // i.e. 0xef0100 || address, to continue to originate transactions.
100        if !bytecode.is_empty() && !bytecode.is_eip7702() {
101            return Err(InvalidTransaction::RejectCallerWithCode);
102        }
103    }
104
105    // Check that the transaction's nonce is correct
106    if !is_nonce_check_disabled {
107        let tx = tx_nonce;
108        let state = caller_info.nonce;
109        match tx.cmp(&state) {
110            Ordering::Greater => {
111                return Err(InvalidTransaction::NonceTooHigh { tx, state });
112            }
113            Ordering::Less => {
114                return Err(InvalidTransaction::NonceTooLow { tx, state });
115            }
116            _ => {}
117        }
118    }
119    Ok(())
120}
121
122/// Check maximum possible fee and deduct the effective fee.
123///
124/// Returns new balance.
125#[inline]
126pub fn calculate_caller_fee(
127    balance: U256,
128    tx: impl Transaction,
129    block: impl Block,
130    cfg: impl Cfg,
131) -> Result<U256, InvalidTransaction> {
132    let basefee = block.basefee() as u128;
133    let blob_price = block.blob_gasprice().unwrap_or_default();
134    let is_balance_check_disabled = cfg.is_balance_check_disabled();
135
136    if !is_balance_check_disabled {
137        tx.ensure_enough_balance(balance)?;
138    }
139
140    let effective_balance_spending = tx
141        .effective_balance_spending(basefee, blob_price)
142        .expect("effective balance is always smaller than max balance so it can't overflow");
143
144    let gas_balance_spending = effective_balance_spending - tx.value();
145
146    // new balance
147    let mut new_balance = balance.saturating_sub(gas_balance_spending);
148
149    if is_balance_check_disabled {
150        // Make sure the caller's balance is at least the value of the transaction.
151        new_balance = new_balance.max(tx.value());
152    }
153
154    Ok(new_balance)
155}
156
157/// Validates caller state and deducts transaction costs from the caller's balance.
158#[inline]
159pub fn validate_against_state_and_deduct_caller<
160    CTX: ContextTr,
161    ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
162>(
163    context: &mut CTX,
164) -> Result<(), ERROR> {
165    let (block, tx, cfg, journal, _, _) = context.all_mut();
166
167    // Load caller's account.
168    let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
169
170    validate_account_nonce_and_code_with_components(&caller.account().info, tx, cfg)?;
171
172    let new_balance = calculate_caller_fee(*caller.balance(), tx, block, cfg)?;
173
174    caller.set_balance(new_balance);
175    if tx.kind().is_call() {
176        caller.bump_nonce();
177    }
178    Ok(())
179}
180
181/// Apply EIP-7702 auth list and return number gas refund on already created accounts.
182///
183/// Note that this function will do nothing if the transaction type is not EIP-7702.
184/// If you need to apply auth list for other transaction types, use [`apply_auth_list`] function.
185///
186/// Internally uses [`apply_auth_list`] function.
187#[inline]
188pub fn apply_eip7702_auth_list<
189    CTX: ContextTr,
190    ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
191>(
192    context: &mut CTX,
193) -> Result<u64, ERROR> {
194    let chain_id = context.cfg().chain_id();
195    let (tx, journal) = context.tx_journal_mut();
196
197    // Return if not EIP-7702 transaction.
198    if tx.tx_type() != TransactionType::Eip7702 {
199        return Ok(0);
200    }
201    apply_auth_list(chain_id, tx.authorization_list(), journal)
202}
203
204/// Apply EIP-7702 style auth list and return number gas refund on already created accounts.
205///
206/// It is more granular function from [`apply_eip7702_auth_list`] function as it takes only the list, journal and chain id.
207#[inline]
208pub fn apply_auth_list<
209    JOURNAL: JournalTr,
210    ERROR: From<InvalidTransaction> + From<<JOURNAL::Database as Database>::Error>,
211>(
212    chain_id: u64,
213    auth_list: impl Iterator<Item = impl AuthorizationTr>,
214    journal: &mut JOURNAL,
215) -> Result<u64, ERROR> {
216    let mut refunded_accounts = 0;
217    for authorization in auth_list {
218        // 1. Verify the chain id is either 0 or the chain's current ID.
219        let auth_chain_id = authorization.chain_id();
220        if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
221            continue;
222        }
223
224        // 2. Verify the `nonce` is less than `2**64 - 1`.
225        if authorization.nonce() == u64::MAX {
226            continue;
227        }
228
229        // recover authority and authorized addresses.
230        // 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
231        let Some(authority) = authorization.authority() else {
232            continue;
233        };
234
235        // warm authority account and check nonce.
236        // 4. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).)
237        let mut authority_acc = journal.load_account_with_code_mut(authority)?;
238        let authority_acc_info = &authority_acc.account().info;
239
240        // 5. Verify the code of `authority` is either empty or already delegated.
241        if let Some(bytecode) = &authority_acc_info.code {
242            // if it is not empty and it is not eip7702
243            if !bytecode.is_empty() && !bytecode.is_eip7702() {
244                continue;
245            }
246        }
247
248        // 6. Verify the nonce of `authority` is equal to `nonce`. In case `authority` does not exist in the trie, verify that `nonce` is equal to `0`.
249        if authorization.nonce() != authority_acc_info.nonce {
250            continue;
251        }
252
253        // 7. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if `authority` exists in the trie.
254        if !(authority_acc_info.is_empty()
255            && authority_acc
256                .account()
257                .is_loaded_as_not_existing_not_touched())
258        {
259            refunded_accounts += 1;
260        }
261
262        // 8. Set the code of `authority` to be `0xef0100 || address`. This is a delegation designation.
263        //  * As a special case, if `address` is `0x0000000000000000000000000000000000000000` do not write the designation.
264        //    Clear the accounts code and reset the account's code hash to the empty hash `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`.
265        // 9. Increase the nonce of `authority` by one.
266        authority_acc.delegate(authorization.address());
267    }
268
269    let refunded_gas =
270        refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
271
272    Ok(refunded_gas)
273}