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 bytecode::Bytecode;
6use context_interface::transaction::{AccessListItemTr, AuthorizationTr};
7use context_interface::ContextTr;
8use context_interface::{
9    journaled_state::JournalTr,
10    result::InvalidTransaction,
11    transaction::{Transaction, TransactionType},
12    Block, Cfg, Database,
13};
14use primitives::{eip7702, hardfork::SpecId, KECCAK_EMPTY, U256};
15
16use crate::{EvmTr, PrecompileProvider};
17
18pub fn load_accounts<
19    EVM: EvmTr<Precompiles: PrecompileProvider<EVM::Context>>,
20    ERROR: From<<<EVM::Context as ContextTr>::Db as Database>::Error>,
21>(
22    evm: &mut EVM,
23) -> Result<(), ERROR> {
24    let (context, precompiles) = evm.ctx_precompiles();
25
26    let gen_spec = context.cfg().spec();
27    let spec = gen_spec.clone().into();
28    // sets eth spec id in journal
29    context.journal().set_spec_id(spec);
30    let precompiles_changed = precompiles.set_spec(gen_spec);
31    let empty_warmed_precompiles = context.journal().precompile_addresses().is_empty();
32
33    if precompiles_changed || empty_warmed_precompiles {
34        // load new precompile addresses into journal.
35        // When precompiles addresses are changed we reset the warmed hashmap to those new addresses.
36        context
37            .journal()
38            .warm_precompiles(precompiles.warm_addresses().collect());
39    }
40
41    // Load coinbase
42    // EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm
43    if spec.is_enabled_in(SpecId::SHANGHAI) {
44        let coinbase = context.block().beneficiary();
45        context.journal().warm_account(coinbase);
46    }
47
48    // Load access list
49    let (tx, journal) = context.tx_journal();
50    if let Some(access_list) = tx.access_list() {
51        for item in access_list {
52            let address = item.address();
53            let mut storage = item.storage_slots().peekable();
54            if storage.peek().is_none() {
55                journal.warm_account(*address);
56            } else {
57                journal.warm_account_and_storage(
58                    *address,
59                    storage.map(|i| U256::from_be_bytes(i.0)),
60                )?;
61            }
62        }
63    }
64
65    Ok(())
66}
67
68#[inline]
69pub fn deduct_caller<CTX: ContextTr>(
70    context: &mut CTX,
71) -> Result<(), <CTX::Db as Database>::Error> {
72    let basefee = context.block().basefee();
73    let blob_price = context.block().blob_gasprice().unwrap_or_default();
74    let effective_gas_price = context.tx().effective_gas_price(basefee as u128);
75    let is_balance_check_disabled = context.cfg().is_balance_check_disabled();
76    let value = context.tx().value();
77
78    // Subtract gas costs from the caller's account.
79    // We need to saturate the gas cost to prevent underflow in case that `disable_balance_check` is enabled.
80    let mut gas_cost = (context.tx().gas_limit() as u128).saturating_mul(effective_gas_price);
81
82    // EIP-4844
83    if context.tx().tx_type() == TransactionType::Eip4844 {
84        let blob_gas = context.tx().total_blob_gas() as u128;
85        gas_cost = gas_cost.saturating_add(blob_price.saturating_mul(blob_gas));
86    }
87
88    let is_call = context.tx().kind().is_call();
89    let caller = context.tx().caller();
90
91    // Load caller's account.
92    let caller_account = context.journal().load_account(caller)?.data;
93    // Set new caller account balance.
94    caller_account.info.balance = caller_account
95        .info
96        .balance
97        .saturating_sub(U256::from(gas_cost));
98
99    if is_balance_check_disabled {
100        // Make sure the caller's balance is at least the value of the transaction.
101        caller_account.info.balance = value.max(caller_account.info.balance);
102    }
103
104    // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`.
105    if is_call {
106        // Nonce is already checked
107        caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
108    }
109
110    // Touch account so we know it is changed.
111    caller_account.mark_touch();
112    Ok(())
113}
114
115/// Apply EIP-7702 auth list and return number gas refund on already created accounts.
116#[inline]
117pub fn apply_eip7702_auth_list<
118    CTX: ContextTr,
119    ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
120>(
121    context: &mut CTX,
122) -> Result<u64, ERROR> {
123    let spec = context.cfg().spec().into();
124    let tx = context.tx();
125    if !spec.is_enabled_in(SpecId::PRAGUE) {
126        return Ok(0);
127    }
128    // Return if there is no auth list.
129    if tx.tx_type() != TransactionType::Eip7702 {
130        return Ok(0);
131    }
132
133    let chain_id = context.cfg().chain_id();
134    let (tx, journal) = context.tx_journal();
135
136    let mut refunded_accounts = 0;
137    for authorization in tx.authorization_list() {
138        // 1. Verify the chain id is either 0 or the chain's current ID.
139        let auth_chain_id = authorization.chain_id();
140        if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
141            continue;
142        }
143
144        // 2. Verify the `nonce` is less than `2**64 - 1`.
145        if authorization.nonce() == u64::MAX {
146            continue;
147        }
148
149        // recover authority and authorized addresses.
150        // 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
151        let Some(authority) = authorization.authority() else {
152            continue;
153        };
154
155        // warm authority account and check nonce.
156        // 4. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).)
157        let mut authority_acc = journal.load_account_code(authority)?;
158
159        // 5. Verify the code of `authority` is either empty or already delegated.
160        if let Some(bytecode) = &authority_acc.info.code {
161            // if it is not empty and it is not eip7702
162            if !bytecode.is_empty() && !bytecode.is_eip7702() {
163                continue;
164            }
165        }
166
167        // 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`.
168        if authorization.nonce() != authority_acc.info.nonce {
169            continue;
170        }
171
172        // 7. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if `authority` exists in the trie.
173        if !authority_acc.is_empty() {
174            refunded_accounts += 1;
175        }
176
177        // 8. Set the code of `authority` to be `0xef0100 || address`. This is a delegation designation.
178        //  * As a special case, if `address` is `0x0000000000000000000000000000000000000000` do not write the designation.
179        //    Clear the accounts code and reset the account's code hash to the empty hash `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`.
180        let address = authorization.address();
181        let (bytecode, hash) = if address.is_zero() {
182            (Bytecode::default(), KECCAK_EMPTY)
183        } else {
184            let bytecode = Bytecode::new_eip7702(address);
185            let hash = bytecode.hash_slow();
186            (bytecode, hash)
187        };
188        authority_acc.info.code_hash = hash;
189        authority_acc.info.code = Some(bytecode);
190
191        // 9. Increase the nonce of `authority` by one.
192        authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1);
193        authority_acc.mark_touch();
194    }
195
196    let refunded_gas =
197        refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
198
199    Ok(refunded_gas)
200}