Skip to main content

ethrex_levm/
utils.rs

1use crate::{
2    EVMConfig, Environment,
3    account::{AccountStatus, LevmAccount},
4    call_frame::CallFrameBackup,
5    constants::*,
6    db::gen_db::GeneralizedDatabase,
7    errors::{ExceptionalHalt, InternalError, TxValidationError, VMError},
8    gas_cost::{
9        self, ACCESS_LIST_ADDRESS_COST, ACCESS_LIST_STORAGE_KEY_COST, BLOB_GAS_PER_BLOB,
10        COLD_ADDRESS_ACCESS_COST, CREATE_BASE_COST, REGULAR_GAS_CREATE, STANDARD_TOKEN_COST,
11        STATE_BYTES_PER_AUTH_TOTAL, STATE_BYTES_PER_NEW_ACCOUNT, WARM_ADDRESS_ACCESS_COST,
12        cost_per_state_byte, floor_tokens_in_access_list, total_cost_floor_per_token,
13    },
14    vm::{Substate, VM},
15};
16use ExceptionalHalt::OutOfGas;
17use bytes::Bytes;
18use ethrex_common::constants::SYSTEM_ADDRESS;
19use ethrex_common::types::Log;
20use ethrex_common::{
21    Address, H256, U256,
22    evm::calculate_create_address,
23    types::{Account, Code, Fork, Transaction, fake_exponential, tx_fields::*},
24    utils::{keccak, u256_to_big_endian},
25};
26use ethrex_common::{types::TxKind, utils::u256_from_big_endian_const};
27use ethrex_rlp;
28use rustc_hash::FxHashMap;
29pub type Storage = FxHashMap<U256, H256>;
30
31// ================== Address related functions ======================
32/// Converts address (H160) to word (U256)
33pub fn address_to_word(address: Address) -> U256 {
34    let mut word = [0u8; 32];
35
36    for (word_byte, address_byte) in word.iter_mut().skip(12).zip(address.as_bytes().iter()) {
37        *word_byte = *address_byte;
38    }
39
40    u256_from_big_endian_const(word)
41}
42
43/// Calculates the address of a new contract using the CREATE2 opcode as follows
44///
45/// initialization_code = memory[offset:offset+size]
46///
47/// address = keccak256(0xff || sender_address || salt || keccak256(initialization_code))[12:]
48pub fn calculate_create2_address(
49    sender_address: Address,
50    initialization_code: &Bytes,
51    salt: U256,
52) -> Result<Address, InternalError> {
53    let init_code_hash = keccak(initialization_code);
54
55    let generated_address = Address::from_slice(
56        keccak(
57            [
58                &[0xff],
59                sender_address.as_bytes(),
60                &salt.to_big_endian(),
61                init_code_hash.as_bytes(),
62            ]
63            .concat(),
64        )
65        .as_bytes()
66        .get(12..)
67        .ok_or(InternalError::Slicing)?,
68    );
69    Ok(generated_address)
70}
71
72// ================== Backup related functions =======================
73
74/// Restore the state of the cache to the state it in the callframe backup.
75/// Also restores BAL recorder state changes (but not touched_addresses) per EIP-7928.
76pub fn restore_cache_state(
77    db: &mut GeneralizedDatabase,
78    callframe_backup: CallFrameBackup,
79) -> Result<(), VMError> {
80    for (address, account) in callframe_backup.original_accounts_info {
81        if let Some(current_account) = db.current_accounts_state.get_mut(&address) {
82            current_account.info = account.info;
83            current_account.status = account.status;
84            current_account.has_storage = account.has_storage;
85            current_account.exists = account.exists;
86        }
87    }
88
89    for (address, storage) in callframe_backup.original_account_storage_slots {
90        // This call to `get_account_mut` should never return None, because we are looking up accounts
91        // that had their storage modified, which means they should be in the cache. That's why
92        // we return an internal error in case we haven't found it.
93        let account = db
94            .current_accounts_state
95            .get_mut(&address)
96            .ok_or(InternalError::AccountNotFound)?;
97
98        for (key, value) in storage {
99            account.storage.insert(key, value);
100        }
101    }
102
103    // Evict codes the reverted frame(s) deployed: a stale by-hash cache entry
104    // would serve a later read of the same hash (from a pre-existing account)
105    // without hitting the store, hiding the read from execution-witness
106    // recording (EIP-8025). Only hashes that were NOT cached before the frame
107    // are tracked, so committed or store-loaded codes are never evicted.
108    for code_hash in callframe_backup.inserted_code_hashes {
109        db.codes.remove(&code_hash);
110    }
111
112    // Restore BAL recorder to checkpoint (but keep touched_addresses per EIP-7928)
113    if let Some(checkpoint) = callframe_backup.bal_checkpoint
114        && let Some(recorder) = db.bal_recorder.as_mut()
115    {
116        recorder.restore(checkpoint);
117    }
118
119    Ok(())
120}
121
122// ================= Blob hash related functions =====================
123pub fn get_base_fee_per_blob_gas(
124    block_excess_blob_gas: Option<u64>,
125    evm_config: &EVMConfig,
126) -> Result<U256, VMError> {
127    let base_fee_update_fraction = evm_config.blob_schedule.base_fee_update_fraction;
128    let excess_blob_gas = block_excess_blob_gas.unwrap_or_default();
129
130    fake_exponential(
131        MIN_BASE_FEE_PER_BLOB_GAS.into(),
132        excess_blob_gas.into(),
133        base_fee_update_fraction,
134    )
135    .map_err(|err| VMError::Internal(InternalError::FakeExponentialError(err)))
136}
137
138/// Gets the max blob gas cost for a transaction that a user is
139/// willing to pay.
140pub fn get_max_blob_gas_price(
141    tx_blob_hashes: &[H256],
142    tx_max_fee_per_blob_gas: Option<U256>,
143) -> Result<U256, VMError> {
144    let blobhash_amount: u64 = tx_blob_hashes
145        .len()
146        .try_into()
147        .map_err(|_| InternalError::TypeConversion)?;
148
149    let blob_gas_used: u64 = blobhash_amount
150        .checked_mul(BLOB_GAS_PER_BLOB)
151        .unwrap_or_default();
152
153    let max_blob_gas_cost = tx_max_fee_per_blob_gas
154        .unwrap_or_default()
155        .checked_mul(blob_gas_used.into())
156        .ok_or(InternalError::Overflow)?;
157
158    Ok(max_blob_gas_cost)
159}
160/// Calculate the actual blob gas cost.
161pub fn calculate_blob_gas_cost(
162    tx_blob_hashes: &[H256],
163    base_blob_fee_per_gas: U256,
164) -> Result<U256, VMError> {
165    let blobhash_amount: u64 = tx_blob_hashes
166        .len()
167        .try_into()
168        .map_err(|_| InternalError::TypeConversion)?;
169
170    let blob_gas_used: u64 = blobhash_amount
171        .checked_mul(BLOB_GAS_PER_BLOB)
172        .unwrap_or_default();
173
174    let blob_gas_used: U256 = blob_gas_used.into();
175    let blob_fee: U256 = blob_gas_used
176        .checked_mul(base_blob_fee_per_gas)
177        .ok_or(InternalError::Overflow)?;
178
179    Ok(blob_fee)
180}
181
182// ==================== Word related functions =======================
183pub fn word_to_address(word: U256) -> Address {
184    Address::from_slice(&u256_to_big_endian(word)[12..])
185}
186
187// ================== EIP-7702 related functions =====================
188
189pub fn code_has_delegation(code: &[u8]) -> Result<bool, VMError> {
190    if code.len() == EIP7702_DELEGATED_CODE_LEN {
191        let first_3_bytes = &code.get(..3).ok_or(InternalError::Slicing)?;
192        return Ok(*first_3_bytes == SET_CODE_DELEGATION_BYTES);
193    }
194    Ok(false)
195}
196
197/// Gets the address inside the bytecode if it has been
198/// delegated as the EIP7702 determines.
199pub fn get_authorized_address_from_code(code: &[u8]) -> Result<Address, VMError> {
200    if code_has_delegation(code)? {
201        let address_bytes = &code
202            .get(SET_CODE_DELEGATION_BYTES.len()..)
203            .ok_or(InternalError::Slicing)?;
204        // It shouldn't panic when doing Address::from_slice()
205        // because the length is checked inside the code_has_delegation() function
206        let address = Address::from_slice(address_bytes);
207        Ok(address)
208    } else {
209        // if we end up here, it means that the address wasn't previously delegated.
210        Err(InternalError::AccountNotDelegated.into())
211    }
212}
213
214pub fn eip7702_recover_address(
215    auth_tuple: &AuthorizationTuple,
216    crypto: &dyn ethrex_crypto::Crypto,
217) -> Result<Option<Address>, VMError> {
218    use ethrex_rlp::encode::RLPEncode;
219
220    if auth_tuple.s_signature > *SECP256K1_ORDER_OVER2 || U256::zero() >= auth_tuple.s_signature {
221        return Ok(None);
222    }
223    if auth_tuple.r_signature > *SECP256K1_ORDER || U256::zero() >= auth_tuple.r_signature {
224        return Ok(None);
225    }
226    if auth_tuple.y_parity != U256::one() && auth_tuple.y_parity != U256::zero() {
227        return Ok(None);
228    }
229
230    let mut rlp_buf = Vec::with_capacity(128);
231    rlp_buf.push(MAGIC);
232    (auth_tuple.chain_id, auth_tuple.address, auth_tuple.nonce).encode(&mut rlp_buf);
233    let msg = crypto.keccak256(&rlp_buf);
234
235    let y_parity: u8 =
236        TryInto::<u8>::try_into(auth_tuple.y_parity).map_err(|_| InternalError::TypeConversion)?;
237
238    let mut sig = [0u8; 65];
239    sig[..32].copy_from_slice(&auth_tuple.r_signature.to_big_endian());
240    sig[32..64].copy_from_slice(&auth_tuple.s_signature.to_big_endian());
241    sig[64] = y_parity;
242
243    match crypto.recover_signer(&sig, &msg) {
244        Ok(address) => Ok(Some(address)),
245        Err(_) => Ok(None),
246    }
247}
248
249/// Gets code of an account, returning early if it's not a delegated account, otherwise
250/// Returns tuple (is_delegated, eip7702_cost, code_address, code).
251/// Notice that it also inserts the delegated account to the "accessed accounts" set.
252///
253/// Where:
254/// - `is_delegated`: True if account is a delegated account.
255/// - `eip7702_cost`: Cost of accessing the delegated account (if any)
256/// - `code_address`: Code address (if delegated, returns the delegated address)
257/// - `code`: Bytecode of the code_address, what the EVM will execute.
258pub fn eip7702_get_code(
259    db: &mut GeneralizedDatabase,
260    accrued_substate: &mut Substate,
261    address: Address,
262) -> Result<(bool, u64, Address, Code), VMError> {
263    let (bytecode, delegation) = eip7702_peek_delegation(db, accrued_substate, address)?;
264    let Some((auth_address, access_cost)) = delegation else {
265        return Ok((false, 0, address, bytecode));
266    };
267
268    accrued_substate.add_accessed_address(auth_address);
269    let authorized_bytecode = db.get_account_code(auth_address)?.clone();
270
271    Ok((true, access_cost, auth_address, authorized_bytecode))
272}
273
274/// First half of [`eip7702_get_code`]: read `address`'s code and detect a
275/// delegation designation WITHOUT touching the delegate account.
276///
277/// Returns `address`'s code and, when delegated, the delegate address with
278/// its warm/cold access cost (computed from the current substate, not
279/// recorded). CALL-family opcodes use this to gas-check the delegation
280/// access cost before reading the delegate (EELS order); reading it earlier
281/// would leak the delegate account into execution witnesses on OOG.
282pub fn eip7702_peek_delegation(
283    db: &mut GeneralizedDatabase,
284    substate: &Substate,
285    address: Address,
286) -> Result<(Code, Option<(Address, u64)>), VMError> {
287    let bytecode = db.get_account_code(address)?.clone();
288    if !code_has_delegation(bytecode.code())? {
289        return Ok((bytecode, None));
290    }
291    let auth_address = get_authorized_address_from_code(bytecode.code())?;
292    let access_cost = if substate.is_address_accessed(&auth_address) {
293        WARM_ADDRESS_ACCESS_COST
294    } else {
295        COLD_ADDRESS_ACCESS_COST
296    };
297    Ok((bytecode, Some((auth_address, access_cost))))
298}
299
300/// Precomputed intrinsic-gas components for a transaction.
301///
302/// Computed once per tx in the prepare-execution hook and reused by
303/// [`VM::validate_min_gas_limit`](crate::hooks::default_hook::validate_min_gas_limit)
304/// and [`VM::add_intrinsic_gas`]. Previously the full calldata / access-list /
305/// auth-list walk ran 2-3x per tx (once in each function, plus the pre-Amsterdam
306/// floor's own `tx_calldata`).
307#[derive(Clone, Copy, Debug)]
308pub struct IntrinsicGas {
309    /// Regular (EIP-8037) intrinsic-gas arm.
310    pub regular: u64,
311    /// State (EIP-8037, Amsterdam+) intrinsic-gas arm; always 0 pre-Amsterdam.
312    pub state: u64,
313    /// `gas_cost::tx_calldata` over `current_call_frame.calldata`. Reused by the
314    /// pre-Amsterdam floor check (same byte string, same point in execution).
315    pub calldata_cost: u64,
316}
317
318impl<'a> VM<'a> {
319    /// Sets the account code as the EIP7702 determines.
320    pub fn eip7702_set_access_code(&mut self) -> Result<(), VMError> {
321        let mut refunded_gas: u64 = 0;
322        // IMPORTANT:
323        // If any of the below steps fail, immediately stop processing that tuple and continue to the next tuple in the list. It will in the case of multiple tuples for the same authority, set the code using the address in the last valid occurrence.
324        // If transaction execution results in failure (any exceptional condition or code reverting), setting delegation designations is not rolled back.
325        for auth_tuple in self.tx.authorization_list().cloned().unwrap_or_default() {
326            let chain_id_not_equals_this_chain_id = auth_tuple.chain_id != self.env.chain_id;
327            let chain_id_not_zero = !auth_tuple.chain_id.is_zero();
328
329            // 1. Verify the chain id is either 0 or the chain’s current ID.
330            if chain_id_not_zero && chain_id_not_equals_this_chain_id {
331                continue;
332            }
333
334            // 2. Verify the nonce is less than 2**64 - 1.
335            // NOTE: nonce is a u64, it's always less than or equal to u64::MAX
336            if auth_tuple.nonce == u64::MAX {
337                continue;
338            }
339
340            // 3. authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s)
341            //      s value must be less than or equal to secp256k1n/2, as specified in EIP-2.
342            let Some(authority_address) = eip7702_recover_address(&auth_tuple, self.crypto)? else {
343                continue;
344            };
345
346            // 4. Add authority to accessed_addresses (as defined in EIP-2929).
347            let authority_account = self.db.get_account(authority_address)?;
348            let authority_exists = authority_account.exists;
349            let authority_info = authority_account.info.clone();
350            let authority_code = self.db.get_code(authority_info.code_hash)?;
351            self.substate.add_accessed_address(authority_address);
352
353            // 5. Verify the code of authority is either empty or already delegated.
354            // Check this BEFORE recording to BAL so we can release the borrow on authority_code.
355            let authority_code_is_empty = authority_code.is_empty();
356            let empty_or_delegated =
357                authority_code_is_empty || code_has_delegation(authority_code.code())?;
358
359            // Record authority as touched for BAL per EIP-7928, even if validation fails later.
360            // This ensures authority appears in BAL with empty change set when:
361            // - Authority was loaded (above)
362            // - But validation fails (checks below)
363            if let Some(recorder) = self.db.bal_recorder.as_mut() {
364                recorder.record_touched_address(authority_address);
365            }
366
367            if !empty_or_delegated {
368                continue;
369            }
370
371            // 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.
372            // If it doesn't exist, it means the nonce is zero. The get_account() function will return Account::default()
373            // If it has nonce, the account.info.nonce should equal auth_tuple.nonce
374            if authority_info.nonce != auth_tuple.nonce {
375                continue;
376            }
377
378            // 7. Refund if authority exists in the trie.
379            // EIP-8037 (Amsterdam+): return STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte
380            // to the state gas reservoir (the new-account portion of the auth state charge).
381            // Pre-Amsterdam: add REFUND_AUTH_PER_EXISTING_ACCOUNT (12500) to global refund counter.
382            // NOTE: Uses `exists` (account_exists in EELS / Exist in geth), NOT `!is_empty()`.
383            // An account can exist in the trie but be empty (e.g., has non-empty storage root).
384            if authority_exists {
385                if self.env.config.fork >= Fork::Amsterdam {
386                    // EIP-7702: refund
387                    // `STATE_BYTES_PER_NEW_ACCOUNT * cpsb` for each existing authority via
388                    // two independent channels:
389                    //   1. `state_gas_reservoir += refund` — sender gets the gas back via
390                    //      receipt refund at tx finalize.
391                    //   2. `state_refund += refund` — block-level state-gas accounting
392                    //      subtracts this at refund_sender (mirrors EELS
393                    //      `MessageCallOutput.state_refund`).
394                    // `state_gas_used` is NOT decremented here: the refund goes through
395                    // `state_refund` (tx-level channel) so block-level accounting subtracts it.
396                    let refund = self.state_gas_new_account;
397                    self.state_gas_reservoir = self
398                        .state_gas_reservoir
399                        .checked_add(refund)
400                        .ok_or(InternalError::Overflow)?;
401                    self.state_refund = self
402                        .state_refund
403                        .checked_add(refund)
404                        .ok_or(InternalError::Overflow)?;
405                } else {
406                    refunded_gas = refunded_gas
407                        .checked_add(REFUND_AUTH_PER_EXISTING_ACCOUNT)
408                        .ok_or(InternalError::Overflow)?;
409                }
410            }
411
412            // EIP-7702: refill the
413            // `STATE_BYTES_PER_AUTH_BASE * cpsb` portion of intrinsic state gas
414            // when no new delegation indicator bytes are written. That covers
415            // two cases:
416            //   1. Authority's code slot already holds a delegation indicator
417            //      (overwrite or clear in place — PR #2836).
418            //   2. The auth is a clear (`auth.address == 0x00`) against an
419            //      authority with no prior code — also writes zero bytes
420            //      (PR #2848).
421            // Step 5 already restricts non-empty pre-state code to a valid
422            // delegation indicator, so checking `!authority_code_is_empty` is
423            // equivalent to EELS's `code_hash != EMPTY_CODE_HASH`.
424            let writes_no_new_indicator =
425                !authority_code_is_empty || auth_tuple.address == Address::zero();
426            if self.env.config.fork >= Fork::Amsterdam && writes_no_new_indicator {
427                let refund = self.state_gas_auth_base;
428                self.state_gas_reservoir = self
429                    .state_gas_reservoir
430                    .checked_add(refund)
431                    .ok_or(InternalError::Overflow)?;
432                self.state_refund = self
433                    .state_refund
434                    .checked_add(refund)
435                    .ok_or(InternalError::Overflow)?;
436            }
437
438            // 8. Set the code of authority to be 0xef0100 || address. This is a delegation designation.
439            let delegation_bytes = [
440                &SET_CODE_DELEGATION_BYTES[..],
441                auth_tuple.address.as_bytes(),
442            ]
443            .concat();
444
445            // As a special case, if address is 0x0000000000000000000000000000000000000000 do not write the designation.
446            // Clear the account’s code and reset the account’s code hash to the empty hash.
447            let code = if auth_tuple.address != Address::zero() {
448                delegation_bytes.into()
449            } else {
450                Bytes::new()
451            };
452            self.update_account_bytecode(
453                authority_address,
454                Code::from_bytecode(code, self.crypto),
455            )?;
456
457            // 9. Increase the nonce of authority by one.
458            self.increment_account_nonce(authority_address)
459                .map_err(|_| TxValidationError::NonceIsMax)?;
460        }
461
462        self.substate.refunded_gas = self
463            .substate
464            .refunded_gas
465            .checked_add(refunded_gas)
466            .ok_or(InternalError::Overflow)?;
467
468        Ok(())
469    }
470
471    pub fn add_intrinsic_gas(&mut self, intrinsic: &IntrinsicGas) -> Result<(), VMError> {
472        // Intrinsic gas is the gas consumed by the transaction before the execution of the opcodes. Section 6.2 in the Yellow Paper.
473
474        let regular_gas = intrinsic.regular;
475        let state_gas = intrinsic.state;
476
477        let total_gas = regular_gas.checked_add(state_gas).ok_or(OutOfGas)?;
478
479        self.current_call_frame
480            .increase_consumed_gas(total_gas)
481            .map_err(|_| TxValidationError::IntrinsicGasTooLow)?;
482
483        // state_gas_used is i64; intrinsic state gas is bounded by tx gas limit (< i64::MAX).
484        self.state_gas_used = self
485            .state_gas_used
486            .checked_add(i64::try_from(state_gas).map_err(|_| InternalError::Overflow)?)
487            .ok_or(InternalError::Overflow)?;
488        // Remember the intrinsic split so we can leave it in state_gas_used on top-level
489        // error (matches EELS `tx_env.intrinsic_state_gas`, which is kept separate from
490        // `tx_output.state_gas_used` and never refunded).
491        debug_assert_eq!(self.intrinsic_state_gas, 0, "intrinsic_state_gas set twice");
492        self.intrinsic_state_gas = state_gas;
493
494        // EIP-8037 (Amsterdam+): compute state gas reservoir from excess gas_limit.
495        // execution_gas = what remains after all intrinsic gas; regular_gas_budget = how much
496        // regular execution gas is allowed (capped at TX_MAX_GAS_LIMIT_AMSTERDAM); the difference becomes
497        // the reservoir for drawing state gas without consuming regular gas_remaining.
498        if self.env.config.fork >= Fork::Amsterdam {
499            if self.env.is_system_call {
500                // EIP-8037: system
501                // transactions get a dedicated state-gas reservoir of
502                // `state_gas_storage_set * SYSTEM_MAX_SSTORES_PER_CALL` ON TOP of
503                // the full SYS_CALL_GAS_LIMIT regular budget — so SSTORE-heavy
504                // system contracts (EIP-2935, EIP-4788) cannot OOG on state-gas
505                // growth alone. Skip the regular reservoir computation so we don't
506                // pre-consume `gas_remaining`; EELS sets `intrinsic_regular_gas=0`
507                // and `gas=SYSTEM_TRANSACTION_GAS` for the message
508                // (amsterdam/fork.py::process_unchecked_system_transaction).
509                let sys_reservoir = self
510                    .state_gas_storage_set
511                    .saturating_mul(SYSTEM_MAX_SSTORES_PER_CALL);
512                self.state_gas_reservoir = sys_reservoir;
513                self.state_gas_reservoir_initial = sys_reservoir;
514            } else {
515                let gas_limit = self.tx.gas_limit();
516                let execution_gas = gas_limit.saturating_sub(total_gas);
517                let regular_gas_budget = TX_MAX_GAS_LIMIT_AMSTERDAM.saturating_sub(regular_gas);
518                let gas_left = regular_gas_budget.min(execution_gas);
519                let reservoir = execution_gas.saturating_sub(gas_left);
520                if reservoir > 0 {
521                    // Pre-consume reservoir from gas_remaining so GAS opcode returns <= TX_MAX_GAS_LIMIT_AMSTERDAM
522                    let reservoir_i64 =
523                        i64::try_from(reservoir).map_err(|_| InternalError::Overflow)?;
524                    self.current_call_frame.gas_remaining = self
525                        .current_call_frame
526                        .gas_remaining
527                        .checked_sub(reservoir_i64)
528                        .ok_or(InternalError::Overflow)?;
529                    self.state_gas_reservoir = reservoir;
530                }
531                // Capture initial reservoir for block-dimensional regular gas computation.
532                self.state_gas_reservoir_initial = reservoir;
533            }
534        }
535
536        Ok(())
537    }
538
539    // ==================== Gas related functions =======================
540    /// Returns `(regular_gas, state_gas)` intrinsic gas for the transaction.
541    /// For Amsterdam+, state_gas is the EIP-8037 state portion.
542    /// For pre-Amsterdam, state_gas is always 0.
543    pub fn get_intrinsic_gas(&self) -> Result<IntrinsicGas, VMError> {
544        // Intrinsic Gas = Calldata cost + Create cost + Base cost + Access list cost
545        let mut regular_gas: u64 = 0;
546        let mut state_gas: u64 = 0;
547        let fork = self.env.config.fork;
548
549        // Calldata Cost
550        // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction.
551        let calldata_cost = gas_cost::tx_calldata(&self.current_call_frame.calldata)?;
552
553        regular_gas = regular_gas.checked_add(calldata_cost).ok_or(OutOfGas)?;
554
555        // Base Cost
556        regular_gas = regular_gas.checked_add(TX_BASE_COST).ok_or(OutOfGas)?;
557
558        // Create Cost
559        if self.is_create()? {
560            if fork >= Fork::Amsterdam {
561                // EIP-8037: reduced regular cost + state gas for new account
562                regular_gas = regular_gas
563                    .checked_add(REGULAR_GAS_CREATE)
564                    .ok_or(OutOfGas)?;
565                state_gas = state_gas
566                    .checked_add(self.state_gas_new_account)
567                    .ok_or(OutOfGas)?;
568            } else {
569                // https://eips.ethereum.org/EIPS/eip-2#specification
570                regular_gas = regular_gas.checked_add(CREATE_BASE_COST).ok_or(OutOfGas)?;
571            }
572
573            // https://eips.ethereum.org/EIPS/eip-3860
574            if fork >= Fork::Shanghai {
575                let number_of_words = &self.current_call_frame.calldata.len().div_ceil(WORD_SIZE);
576                let double_number_of_words: u64 = number_of_words
577                    .checked_mul(2)
578                    .ok_or(OutOfGas)?
579                    .try_into()
580                    .map_err(|_| InternalError::TypeConversion)?;
581
582                regular_gas = regular_gas
583                    .checked_add(double_number_of_words)
584                    .ok_or(OutOfGas)?;
585            }
586        }
587
588        // Access List Cost
589        let mut access_lists_cost: u64 = 0;
590        for (_, keys) in self.tx.access_list() {
591            access_lists_cost = access_lists_cost
592                .checked_add(ACCESS_LIST_ADDRESS_COST)
593                .ok_or(OutOfGas)?;
594            for _ in keys {
595                access_lists_cost = access_lists_cost
596                    .checked_add(ACCESS_LIST_STORAGE_KEY_COST)
597                    .ok_or(OutOfGas)?;
598            }
599        }
600
601        // EIP-7981 (Amsterdam+): access-list data bytes also contribute to the regular arm.
602        // access_list_cost += floor_tokens_in_access_list * total_cost_floor_per_token
603        // = access_list_bytes * STANDARD_TOKEN_COST * total_cost_floor_per_token
604        // Effective: +1280 per address, +2048 per storage key.
605        if fork >= Fork::Amsterdam {
606            let al_floor_tokens = floor_tokens_in_access_list(self.tx.access_list());
607            let al_data_cost = al_floor_tokens
608                .checked_mul(total_cost_floor_per_token(fork))
609                .ok_or(InternalError::Overflow)?;
610            access_lists_cost = access_lists_cost
611                .checked_add(al_data_cost)
612                .ok_or(InternalError::Overflow)?;
613        }
614
615        regular_gas = regular_gas.checked_add(access_lists_cost).ok_or(OutOfGas)?;
616
617        // Authorization List Cost
618        // `unwrap_or_default` will return an empty vec when the `authorization_list` field is None.
619        // If the vec is empty, the len will be 0, thus the authorization_list_cost is 0.
620        let amount_of_auth_tuples: u64 = match self.tx.authorization_list() {
621            None => 0,
622            Some(list) => list
623                .len()
624                .try_into()
625                .map_err(|_| InternalError::TypeConversion)?,
626        };
627
628        if fork >= Fork::Amsterdam {
629            // EIP-8037: per-auth regular cost is PER_AUTH_BASE_COST, state is STATE_BYTES_PER_AUTH_TOTAL * cost_per_state_byte
630            let regular_auth_cost = PER_AUTH_BASE_COST
631                .checked_mul(amount_of_auth_tuples)
632                .ok_or(InternalError::Overflow)?;
633            regular_gas = regular_gas.checked_add(regular_auth_cost).ok_or(OutOfGas)?;
634            let state_auth_cost = self
635                .state_gas_auth_total
636                .checked_mul(amount_of_auth_tuples)
637                .ok_or(InternalError::Overflow)?;
638            state_gas = state_gas.checked_add(state_auth_cost).ok_or(OutOfGas)?;
639        } else {
640            let authorization_list_cost = PER_EMPTY_ACCOUNT_COST
641                .checked_mul(amount_of_auth_tuples)
642                .ok_or(InternalError::Overflow)?;
643            regular_gas = regular_gas
644                .checked_add(authorization_list_cost)
645                .ok_or(OutOfGas)?;
646        }
647
648        Ok(IntrinsicGas {
649            regular: regular_gas,
650            state: state_gas,
651            calldata_cost,
652        })
653    }
654
655    /// Calculates the minimum gas to be consumed in the transaction.
656    pub fn get_min_gas_used(&self) -> Result<u64, VMError> {
657        let fork = self.env.config.fork;
658
659        // If the transaction is a CREATE transaction, the calldata is emptied and the bytecode is assigned.
660        let calldata = if self.is_create()? {
661            self.current_call_frame.bytecode.code()
662        } else {
663            self.current_call_frame.calldata.as_ref()
664        };
665
666        // EIP-7976 floor tokens: for the floor arm, all calldata bytes count unweighted.
667        // floor_tokens_in_calldata = (zero_bytes + nonzero_bytes) * STANDARD_TOKEN_COST
668        // Pre-Amsterdam uses the weighted EIP-7623 formula: (nonzero * 16 + zero * 4) / 4
669        let mut tokens_in_calldata: u64 = if fork >= Fork::Amsterdam {
670            // EIP-7976: floor tokens = total_bytes * STANDARD_TOKEN_COST (unweighted).
671            let total_bytes: u64 = calldata
672                .len()
673                .try_into()
674                .map_err(|_| InternalError::TypeConversion)?;
675            total_bytes
676                .checked_mul(STANDARD_TOKEN_COST)
677                .ok_or(InternalError::Overflow)?
678        } else {
679            // Pre-Amsterdam: weighted EIP-7623 token count.
680            gas_cost::tx_calldata(calldata)? / STANDARD_TOKEN_COST
681        };
682
683        // EIP-7981 (Amsterdam+): access-list data bytes fold into the floor-token count.
684        // floor_tokens_in_access_list = access_list_bytes * STANDARD_TOKEN_COST
685        // where access_list_bytes = 20 * address_count + 32 * storage_key_count.
686        if fork >= Fork::Amsterdam {
687            let al_floor_tokens = floor_tokens_in_access_list(self.tx.access_list());
688            tokens_in_calldata = tokens_in_calldata
689                .checked_add(al_floor_tokens)
690                .ok_or(InternalError::Overflow)?;
691        }
692
693        // min_gas_used = TX_BASE_COST + total_cost_floor_per_token(fork) * tokens
694        // EIP-7976 (Amsterdam+) raises TOTAL_COST_FLOOR_PER_TOKEN from 10 to 16.
695        let mut min_gas_used: u64 = tokens_in_calldata
696            .checked_mul(total_cost_floor_per_token(fork))
697            .ok_or(InternalError::Overflow)?;
698
699        min_gas_used = min_gas_used
700            .checked_add(TX_BASE_COST)
701            .ok_or(InternalError::Overflow)?;
702
703        Ok(min_gas_used)
704    }
705
706    /// Gets transaction callee, calculating create address if it's a "Create" transaction.
707    /// Bool indicates whether it is a `create` transaction or not.
708    pub fn get_tx_callee(
709        tx: &Transaction,
710        db: &mut GeneralizedDatabase,
711        env: &Environment,
712        substate: &mut Substate,
713    ) -> Result<(Address, bool), VMError> {
714        match tx.to() {
715            TxKind::Call(address_to) => {
716                substate.add_accessed_address(address_to);
717
718                Ok((address_to, false))
719            }
720
721            TxKind::Create => {
722                let sender_nonce = db.get_account(env.origin)?.info.nonce;
723
724                let created_address = calculate_create_address(env.origin, sender_nonce);
725
726                substate.add_accessed_address(created_address);
727                substate.add_created_account(created_address);
728
729                Ok((created_address, true))
730            }
731        }
732    }
733}
734
735/// Compute `(regular, state)` intrinsic gas for a transaction without needing
736/// a full VM instance. Mirrors `VM::get_intrinsic_gas` but operates on the raw
737/// transaction, fork, and block gas limit (for cpsb derivation). Pre-Amsterdam
738/// returns `(regular, 0)`.
739///
740/// Used by the block executor to perform the EIP-8037 (PR #2703) per-tx 2D
741/// inclusion check before the tx runs.
742pub fn intrinsic_gas_dimensions(
743    tx: &Transaction,
744    fork: Fork,
745    block_gas_limit: u64,
746) -> Result<(u64, u64), VMError> {
747    let mut regular_gas: u64 = 0;
748    let mut state_gas: u64 = 0;
749
750    let (state_gas_new_account, state_gas_auth_total) = if fork >= Fork::Amsterdam {
751        let cpsb = cost_per_state_byte(block_gas_limit);
752        (
753            STATE_BYTES_PER_NEW_ACCOUNT
754                .checked_mul(cpsb)
755                .ok_or(InternalError::Overflow)?,
756            STATE_BYTES_PER_AUTH_TOTAL
757                .checked_mul(cpsb)
758                .ok_or(InternalError::Overflow)?,
759        )
760    } else {
761        (0, 0)
762    };
763
764    // Calldata cost (EIP-2028 weighted)
765    let calldata_cost = gas_cost::tx_calldata(tx.data())?;
766    regular_gas = regular_gas.checked_add(calldata_cost).ok_or(OutOfGas)?;
767
768    // Base cost
769    regular_gas = regular_gas.checked_add(TX_BASE_COST).ok_or(OutOfGas)?;
770
771    let is_create = matches!(tx.to(), TxKind::Create);
772    if is_create {
773        if fork >= Fork::Amsterdam {
774            regular_gas = regular_gas
775                .checked_add(REGULAR_GAS_CREATE)
776                .ok_or(OutOfGas)?;
777            state_gas = state_gas
778                .checked_add(state_gas_new_account)
779                .ok_or(OutOfGas)?;
780        } else {
781            regular_gas = regular_gas.checked_add(CREATE_BASE_COST).ok_or(OutOfGas)?;
782        }
783
784        // EIP-3860 init code words (Shanghai+)
785        if fork >= Fork::Shanghai {
786            let words = tx.data().len().div_ceil(WORD_SIZE);
787            let double_words: u64 = words
788                .checked_mul(2)
789                .ok_or(OutOfGas)?
790                .try_into()
791                .map_err(|_| InternalError::TypeConversion)?;
792            regular_gas = regular_gas.checked_add(double_words).ok_or(OutOfGas)?;
793        }
794    }
795
796    // Access list cost
797    let mut access_lists_cost: u64 = 0;
798    for (_, keys) in tx.access_list() {
799        access_lists_cost = access_lists_cost
800            .checked_add(ACCESS_LIST_ADDRESS_COST)
801            .ok_or(OutOfGas)?;
802        for _ in keys {
803            access_lists_cost = access_lists_cost
804                .checked_add(ACCESS_LIST_STORAGE_KEY_COST)
805                .ok_or(OutOfGas)?;
806        }
807    }
808
809    // EIP-7981 (Amsterdam+): access-list data bytes fold into regular gas
810    if fork >= Fork::Amsterdam {
811        let al_floor_tokens = floor_tokens_in_access_list(tx.access_list());
812        let al_data_cost = al_floor_tokens
813            .checked_mul(total_cost_floor_per_token(fork))
814            .ok_or(InternalError::Overflow)?;
815        access_lists_cost = access_lists_cost
816            .checked_add(al_data_cost)
817            .ok_or(InternalError::Overflow)?;
818    }
819    regular_gas = regular_gas.checked_add(access_lists_cost).ok_or(OutOfGas)?;
820
821    // Authorization list cost
822    let amount_of_auth_tuples: u64 = match tx.authorization_list() {
823        None => 0,
824        Some(list) => list
825            .len()
826            .try_into()
827            .map_err(|_| InternalError::TypeConversion)?,
828    };
829
830    if fork >= Fork::Amsterdam {
831        let regular_auth_cost = PER_AUTH_BASE_COST
832            .checked_mul(amount_of_auth_tuples)
833            .ok_or(InternalError::Overflow)?;
834        regular_gas = regular_gas.checked_add(regular_auth_cost).ok_or(OutOfGas)?;
835        let state_auth_cost = state_gas_auth_total
836            .checked_mul(amount_of_auth_tuples)
837            .ok_or(InternalError::Overflow)?;
838        state_gas = state_gas.checked_add(state_auth_cost).ok_or(OutOfGas)?;
839    } else {
840        let auth_cost = PER_EMPTY_ACCOUNT_COST
841            .checked_mul(amount_of_auth_tuples)
842            .ok_or(InternalError::Overflow)?;
843        regular_gas = regular_gas.checked_add(auth_cost).ok_or(OutOfGas)?;
844    }
845
846    Ok((regular_gas, state_gas))
847}
848
849/// Standalone EIP-7623/7976/7981 floor gas for a transaction. Mirrors
850/// [`VM::get_min_gas_used`] but operates on the raw transaction + fork, so it
851/// can be called by mempool admission / the payload builder without needing a
852/// VM instance. Returns `TX_BASE_COST + floor_rate * total_floor_tokens`.
853///
854/// Amsterdam+ uses the unweighted EIP-7976 floor (16 gas/token = 64 gas/byte)
855/// and folds EIP-7981 access-list data bytes into the token count. Pre-
856/// Amsterdam uses the weighted EIP-7623 formula.
857///
858/// A mismatch between this and `VM::get_min_gas_used` would cause mempool
859/// admission to drift from VM rejection; keep the two in sync. The
860/// `test_intrinsic_parity_*` suite also guards this.
861pub fn intrinsic_gas_floor(tx: &Transaction, fork: Fork) -> Result<u64, VMError> {
862    // EIP-7976: floor tokens count ALL calldata bytes unweighted. For CREATE
863    // txs the calldata is the init code. Mirrors `get_min_gas_used`.
864    let calldata = tx.data();
865
866    let mut tokens_in_calldata: u64 = if fork >= Fork::Amsterdam {
867        let total_bytes: u64 = calldata
868            .len()
869            .try_into()
870            .map_err(|_| InternalError::TypeConversion)?;
871        total_bytes
872            .checked_mul(STANDARD_TOKEN_COST)
873            .ok_or(InternalError::Overflow)?
874    } else {
875        gas_cost::tx_calldata(calldata)? / STANDARD_TOKEN_COST
876    };
877
878    if fork >= Fork::Amsterdam {
879        let al_floor_tokens = floor_tokens_in_access_list(tx.access_list());
880        tokens_in_calldata = tokens_in_calldata
881            .checked_add(al_floor_tokens)
882            .ok_or(InternalError::Overflow)?;
883    }
884
885    tokens_in_calldata
886        .checked_mul(total_cost_floor_per_token(fork))
887        .ok_or(InternalError::Overflow)?
888        .checked_add(TX_BASE_COST)
889        .ok_or(InternalError::Overflow.into())
890}
891
892/// Converts Account to LevmAccount
893/// The problem with this is that we don't have the storage root.
894pub fn account_to_levm_account(account: Account) -> (LevmAccount, Code) {
895    (
896        LevmAccount {
897            info: account.info,
898            has_storage: !account.storage.is_empty(), // This is used in scenarios in which the storage is already all in the account. For the Levm Runner
899            storage: account.storage,
900            status: AccountStatus::Unmodified,
901            exists: true,
902        },
903        account.code,
904    )
905}
906
907/// Converts a U256 value into usize, returning an error if the value is over 32 bits
908/// This is generally used for memory offsets and sizes, 32 bits is more than enough for this purpose.
909#[expect(clippy::as_conversions)]
910pub fn u256_to_usize(val: U256) -> Result<usize, VMError> {
911    if val.0[0] > u32::MAX as u64 || val.0[1] != 0 || val.0[2] != 0 || val.0[3] != 0 {
912        return Err(VMError::ExceptionalHalt(ExceptionalHalt::VeryLargeNumber));
913    }
914    Ok(val.0[0] as usize)
915}
916
917/// Converts U256 size and offset to usize.
918/// If the size is zero, the offset will be zero regardless of its original value as it is not relevant
919pub fn size_offset_to_usize(size: U256, offset: U256) -> Result<(usize, usize), VMError> {
920    if size.is_zero() {
921        // Offset is irrelevant
922        Ok((0, 0))
923    } else {
924        Ok((u256_to_usize(size)?, u256_to_usize(offset)?))
925    }
926}
927
928// ==================== EIP-7708 Helper Functions ====================
929
930/// Creates EIP-7708 Transfer log (LOG3) for ETH transfers.
931/// Emitted from SYSTEM_ADDRESS when ETH is transferred.
932#[inline]
933pub fn create_eth_transfer_log(from: Address, to: Address, value: U256) -> Log {
934    let mut from_topic = [0u8; 32];
935    from_topic[12..].copy_from_slice(from.as_bytes());
936
937    let mut to_topic = [0u8; 32];
938    to_topic[12..].copy_from_slice(to.as_bytes());
939
940    let data = value.to_big_endian();
941
942    Log {
943        address: SYSTEM_ADDRESS,
944        topics: vec![
945            TRANSFER_EVENT_TOPIC,
946            H256::from(from_topic),
947            H256::from(to_topic),
948        ],
949        data: Bytes::from(data.to_vec()),
950    }
951}
952
953/// Creates EIP-7708 Burn log (LOG2) for ETH burns.
954/// Emitted from SYSTEM_ADDRESS when ETH is burned (e.g. via SELFDESTRUCT).
955#[inline]
956pub fn create_burn_log(address: Address, amount: U256) -> Log {
957    let mut address_topic = [0u8; 32];
958    address_topic[12..].copy_from_slice(address.as_bytes());
959
960    let data = amount.to_big_endian();
961
962    Log {
963        address: SYSTEM_ADDRESS,
964        topics: vec![BURN_EVENT_TOPIC, H256::from(address_topic)],
965        data: Bytes::from(data.to_vec()),
966    }
967}