revm/handler/mainnet/
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::{
6    precompile::PrecompileSpecId,
7    primitives::{
8        db::Database,
9        eip7702, Account, Bytecode, EVMError, Env, Spec,
10        SpecId::{CANCUN, PRAGUE, SHANGHAI},
11        TxKind, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256,
12    },
13    Context, ContextPrecompiles,
14};
15
16/// Main precompile load
17#[inline]
18pub fn load_precompiles<SPEC: Spec, DB: Database>() -> ContextPrecompiles<DB> {
19    ContextPrecompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID))
20}
21
22/// Main load handle
23#[inline]
24pub fn load_accounts<SPEC: Spec, EXT, DB: Database>(
25    context: &mut Context<EXT, DB>,
26) -> Result<(), EVMError<DB::Error>> {
27    // set journaling state flag.
28    context.evm.journaled_state.set_spec_id(SPEC::SPEC_ID);
29
30    // load coinbase
31    // EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm
32    if SPEC::enabled(SHANGHAI) {
33        let coinbase = context.evm.inner.env.block.coinbase;
34        context
35            .evm
36            .journaled_state
37            .warm_preloaded_addresses
38            .insert(coinbase);
39    }
40
41    // Load blockhash storage address
42    // EIP-2935: Serve historical block hashes from state
43    if SPEC::enabled(PRAGUE) {
44        context
45            .evm
46            .journaled_state
47            .warm_preloaded_addresses
48            .insert(BLOCKHASH_STORAGE_ADDRESS);
49    }
50
51    // Load access list
52    context.evm.load_access_list()?;
53    Ok(())
54}
55
56/// Helper function that deducts the caller balance.
57#[inline]
58pub fn deduct_caller_inner<SPEC: Spec>(caller_account: &mut Account, env: &Env) {
59    // Subtract gas costs from the caller's account.
60    // We need to saturate the gas cost to prevent underflow in case that `disable_balance_check` is enabled.
61    let mut gas_cost = U256::from(env.tx.gas_limit).saturating_mul(env.effective_gas_price());
62
63    // EIP-4844
64    if SPEC::enabled(CANCUN) {
65        let data_fee = env.calc_data_fee().expect("already checked");
66        gas_cost = gas_cost.saturating_add(data_fee);
67    }
68
69    // set new caller account balance.
70    caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost);
71
72    // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`.
73    if matches!(env.tx.transact_to, TxKind::Call(_)) {
74        // Nonce is already checked
75        caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
76    }
77
78    // touch account so we know it is changed.
79    caller_account.mark_touch();
80}
81
82/// Deducts the caller balance to the transaction limit.
83#[inline]
84pub fn deduct_caller<SPEC: Spec, EXT, DB: Database>(
85    context: &mut Context<EXT, DB>,
86) -> Result<(), EVMError<DB::Error>> {
87    // load caller's account.
88    let caller_account = context
89        .evm
90        .inner
91        .journaled_state
92        .load_account(context.evm.inner.env.tx.caller, &mut context.evm.inner.db)?;
93
94    // deduct gas cost from caller's account.
95    deduct_caller_inner::<SPEC>(caller_account.data, &context.evm.inner.env);
96
97    Ok(())
98}
99
100/// Apply EIP-7702 auth list and return number gas refund on already created accounts.
101#[inline]
102pub fn apply_eip7702_auth_list<SPEC: Spec, EXT, DB: Database>(
103    context: &mut Context<EXT, DB>,
104) -> Result<u64, EVMError<DB::Error>> {
105    // EIP-7702. Load bytecode to authorized accounts.
106    if !SPEC::enabled(PRAGUE) {
107        return Ok(0);
108    }
109
110    // return if there is no auth list.
111    let Some(authorization_list) = context.evm.inner.env.tx.authorization_list.as_ref() else {
112        return Ok(0);
113    };
114
115    let mut refunded_accounts = 0;
116    for authorization in authorization_list.recovered_iter() {
117        // 1. Verify the chain id is either 0 or the chain's current ID.
118        let chain_id = authorization.chain_id();
119        if chain_id != 0 && chain_id != context.evm.inner.env.cfg.chain_id {
120            continue;
121        }
122
123        // 2. Verify the `nonce` is less than `2**64 - 1`.
124        if authorization.nonce() == u64::MAX {
125            continue;
126        }
127
128        // recover authority and authorized addresses.
129        // 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
130        let Some(authority) = authorization.authority() else {
131            continue;
132        };
133
134        // warm authority account and check nonce.
135        // 4. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).)
136        let mut authority_acc = context
137            .evm
138            .inner
139            .journaled_state
140            .load_code(authority, &mut context.evm.inner.db)?;
141
142        // 5. Verify the code of `authority` is either empty or already delegated.
143        if let Some(bytecode) = &authority_acc.info.code {
144            // if it is not empty and it is not eip7702
145            if !bytecode.is_empty() && !bytecode.is_eip7702() {
146                continue;
147            }
148        }
149
150        // 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`.
151        if authorization.nonce() != authority_acc.info.nonce {
152            continue;
153        }
154
155        // 7. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if `authority` exists in the trie.
156        if !authority_acc.is_empty() {
157            refunded_accounts += 1;
158        }
159
160        // 8. Set the code of `authority` to be `0xef0100 || address`. This is a delegation designation.
161        //  * As a special case, if `address` is `0x0000000000000000000000000000000000000000` do not write the designation. Clear the accounts code and reset the account's code hash to the empty hash `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`.
162        let (bytecode, hash) = if authorization.address.is_zero() {
163            (Bytecode::default(), KECCAK_EMPTY)
164        } else {
165            let bytecode = Bytecode::new_eip7702(authorization.address);
166            let hash = bytecode.hash_slow();
167            (bytecode, hash)
168        };
169        authority_acc.info.code_hash = hash;
170        authority_acc.info.code = Some(bytecode);
171
172        // 9. Increase the nonce of `authority` by one.
173        authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1);
174        authority_acc.mark_touch();
175    }
176
177    let refunded_gas =
178        refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
179
180    Ok(refunded_gas)
181}