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