From 75a97548358dcd4c9b95c366cd22029b34b64b59 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 16 Dec 2025 23:20:39 +0100 Subject: [PATCH 01/19] feat: move GasParams to Cfg --- crates/context/interface/src/cfg.rs | 9 + crates/context/interface/src/cfg/gas.rs | 298 +++++++ .../context/interface/src/cfg/gas_params.rs | 776 ++++++++++++++++++ crates/context/interface/src/host.rs | 25 +- crates/context/src/cfg.rs | 17 +- crates/context/src/context.rs | 6 + crates/handler/src/evm.rs | 8 +- crates/handler/src/frame.rs | 20 +- crates/handler/src/handler.rs | 13 - crates/handler/src/instructions.rs | 42 +- crates/inspector/src/handler.rs | 3 - crates/interpreter/src/gas.rs | 11 +- crates/interpreter/src/gas/calc.rs | 14 +- crates/interpreter/src/gas/constants.rs | 102 --- crates/interpreter/src/gas/params.rs | 774 ----------------- crates/interpreter/src/instructions.rs | 5 +- .../src/instructions/arithmetic.rs | 5 +- .../interpreter/src/instructions/bitwise.rs | 12 +- .../interpreter/src/instructions/contract.rs | 33 +- .../src/instructions/contract/call_helpers.rs | 32 +- .../interpreter/src/instructions/control.rs | 20 +- crates/interpreter/src/instructions/host.rs | 45 +- crates/interpreter/src/instructions/macros.rs | 13 +- crates/interpreter/src/instructions/memory.rs | 24 +- crates/interpreter/src/instructions/system.rs | 50 +- crates/interpreter/src/interpreter.rs | 38 +- .../src/interpreter/shared_memory.rs | 3 +- crates/op-revm/src/l1block.rs | 6 +- 28 files changed, 1302 insertions(+), 1102 deletions(-) create mode 100644 crates/context/interface/src/cfg/gas.rs create mode 100644 crates/context/interface/src/cfg/gas_params.rs diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index 11e5d99659..cb107693d6 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -1,4 +1,10 @@ //! Configuration for the EVM. Containing [`SpecId`]. + +pub mod gas; +pub mod gas_params; + +pub use gas_params::{GasId, GasParams}; + use auto_impl::auto_impl; use core::{fmt::Debug, hash::Hash}; use primitives::{hardfork::SpecId, Address, TxKind, U256}; @@ -66,6 +72,9 @@ pub trait Cfg { /// Returns the limit in bytes for the memory buffer. fn memory_limit(&self) -> u64; + + /// Returns the gas params for the EVM. + fn gas_params(&self) -> &GasParams; } /// What bytecode analysis to perform diff --git a/crates/context/interface/src/cfg/gas.rs b/crates/context/interface/src/cfg/gas.rs new file mode 100644 index 0000000000..5b122b7657 --- /dev/null +++ b/crates/context/interface/src/cfg/gas.rs @@ -0,0 +1,298 @@ +//! Gas constants and functions for gas calculation. + +use crate::{transaction::AccessListItemTr as _, Transaction, TransactionType}; +use primitives::{eip7702, hardfork::SpecId, U256}; + +/// Gas cost for operations that consume zero gas. +pub const ZERO: u64 = 0; +/// Base gas cost for basic operations. +pub const BASE: u64 = 2; + +/// Gas cost for very low-cost operations. +pub const VERYLOW: u64 = 3; +/// Gas cost for DATALOADN instruction. +pub const DATA_LOADN_GAS: u64 = 3; + +/// Gas cost for conditional jump instructions. +pub const CONDITION_JUMP_GAS: u64 = 4; +/// Gas cost for RETF instruction. +pub const RETF_GAS: u64 = 3; +/// Gas cost for DATALOAD instruction. +pub const DATA_LOAD_GAS: u64 = 4; + +/// Gas cost for low-cost operations. +pub const LOW: u64 = 5; +/// Gas cost for medium-cost operations. +pub const MID: u64 = 8; +/// Gas cost for high-cost operations. +pub const HIGH: u64 = 10; +/// Gas cost for JUMPDEST instruction. +pub const JUMPDEST: u64 = 1; +/// Gas cost for REFUND SELFDESTRUCT instruction. +pub const SELFDESTRUCT_REFUND: i64 = 24000; +/// Gas cost for CREATE instruction. +pub const CREATE: u64 = 32000; +/// Additional gas cost when a call transfers value. +pub const CALLVALUE: u64 = 9000; +/// Gas cost for creating a new account. +pub const NEWACCOUNT: u64 = 25000; +/// Base gas cost for EXP instruction. +pub const EXP: u64 = 10; +/// Gas cost per word for memory operations. +pub const MEMORY: u64 = 3; +/// Base gas cost for LOG instructions. +pub const LOG: u64 = 375; +/// Gas cost per byte of data in LOG instructions. +pub const LOGDATA: u64 = 8; +/// Gas cost per topic in LOG instructions. +pub const LOGTOPIC: u64 = 375; +/// Base gas cost for KECCAK256 instruction. +pub const KECCAK256: u64 = 30; +/// Gas cost per word for KECCAK256 instruction. +pub const KECCAK256WORD: u64 = 6; +/// Gas cost per word for copy operations. +pub const COPY: u64 = 3; +/// Gas cost for BLOCKHASH instruction. +pub const BLOCKHASH: u64 = 20; +/// Gas cost per byte for code deposit during contract creation. +pub const CODEDEPOSIT: u64 = 200; + +/// EIP-1884: Repricing for trie-size-dependent opcodes +pub const ISTANBUL_SLOAD_GAS: u64 = 800; +/// Gas cost for SSTORE when setting a storage slot from zero to non-zero. +pub const SSTORE_SET: u64 = 20000; +/// Gas cost for SSTORE when modifying an existing non-zero storage slot. +pub const SSTORE_RESET: u64 = 5000; +/// Gas refund for SSTORE when clearing a storage slot (setting to zero). +pub const REFUND_SSTORE_CLEARS: i64 = 15000; + +/// The standard cost of calldata token. +pub const STANDARD_TOKEN_COST: u64 = 4; +/// The cost of a non-zero byte in calldata. +pub const NON_ZERO_BYTE_DATA_COST: u64 = 68; +/// The multiplier for a non zero byte in calldata. +pub const NON_ZERO_BYTE_MULTIPLIER: u64 = NON_ZERO_BYTE_DATA_COST / STANDARD_TOKEN_COST; +/// The cost of a non-zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028). +pub const NON_ZERO_BYTE_DATA_COST_ISTANBUL: u64 = 16; +/// The multiplier for a non zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028). +pub const NON_ZERO_BYTE_MULTIPLIER_ISTANBUL: u64 = + NON_ZERO_BYTE_DATA_COST_ISTANBUL / STANDARD_TOKEN_COST; +/// The cost floor per token as defined by EIP-2028. +pub const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; + +/// Gas cost for EOF CREATE instruction. +pub const EOF_CREATE_GAS: u64 = 32000; + +// Berlin EIP-2929/EIP-2930 constants +/// Gas cost for accessing an address in the access list (EIP-2930). +pub const ACCESS_LIST_ADDRESS: u64 = 2400; +/// Gas cost for accessing a storage key in the access list (EIP-2930). +pub const ACCESS_LIST_STORAGE_KEY: u64 = 1900; + +/// Gas cost for SLOAD when accessing a cold storage slot (EIP-2929). +pub const COLD_SLOAD_COST: u64 = 2100; +/// Gas cost for accessing a cold account (EIP-2929). +pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600; +/// Additional gas cost for accessing a cold account. +pub const COLD_ACCOUNT_ACCESS_COST_ADDITIONAL: u64 = + COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST; +/// Gas cost for reading from a warm storage slot (EIP-2929). +pub const WARM_STORAGE_READ_COST: u64 = 100; +/// Gas cost for SSTORE reset operation on a warm storage slot. +pub const WARM_SSTORE_RESET: u64 = SSTORE_RESET - COLD_SLOAD_COST; + +/// EIP-3860 : Limit and meter initcode +pub const INITCODE_WORD_COST: u64 = 2; + +/// Gas stipend provided to the recipient of a CALL with value transfer. +pub const CALL_STIPEND: u64 = 2300; + +#[inline] +pub(crate) const fn log2floor(value: U256) -> u64 { + let mut l: u64 = 256; + let mut i = 3; + loop { + if value.as_limbs()[i] == 0u64 { + l -= 64; + } else { + l -= value.as_limbs()[i].leading_zeros() as u64; + if l == 0 { + return l; + } else { + return l - 1; + } + } + if i == 0 { + break; + } + i -= 1; + } + l +} + +/// Calculate the cost of buffer per word. +#[inline] +pub const fn cost_per_word(len: usize, multiple: u64) -> Option { + multiple.checked_mul(num_words(len) as u64) +} + +/// EIP-3860: Limit and meter initcode +/// +/// Apply extra gas cost of 2 for every 32-byte chunk of initcode. +/// +/// This cannot overflow as the initcode length is assumed to be checked. +#[inline] +pub const fn initcode_cost(len: usize) -> u64 { + let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else { + panic!("initcode cost overflow") + }; + cost +} + +/// Memory expansion cost calculation for a given number of words. +#[inline] +pub const fn memory_gas(num_words: usize, linear_cost: u64, quadratic_cost: u64) -> u64 { + let num_words = num_words as u64; + linear_cost + .saturating_mul(num_words) + .saturating_add(num_words.saturating_mul(num_words) / quadratic_cost) +} + +/// Init and floor gas from transaction +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct InitialAndFloorGas { + /// Initial gas for transaction. + pub initial_gas: u64, + /// If transaction is a Call and Prague is enabled + /// floor_gas is at least amount of gas that is going to be spent. + pub floor_gas: u64, +} + +impl InitialAndFloorGas { + /// Create a new InitialAndFloorGas instance. + #[inline] + pub const fn new(initial_gas: u64, floor_gas: u64) -> Self { + Self { + initial_gas, + floor_gas, + } + } +} + +/// Initial gas that is deducted for transaction to be included. +/// Initial gas contains initial stipend gas, gas for access list and input data. +/// +/// # Returns +/// +/// - Intrinsic gas +/// - Number of tokens in calldata +pub fn calculate_initial_tx_gas( + spec_id: SpecId, + input: &[u8], + is_create: bool, + access_list_accounts: u64, + access_list_storages: u64, + authorization_list_num: u64, +) -> InitialAndFloorGas { + let mut gas = InitialAndFloorGas::default(); + + // Initdate stipend + let tokens_in_calldata = get_tokens_in_calldata(input, spec_id.is_enabled_in(SpecId::ISTANBUL)); + + gas.initial_gas += tokens_in_calldata * STANDARD_TOKEN_COST; + + // Get number of access list account and storages. + gas.initial_gas += access_list_accounts * ACCESS_LIST_ADDRESS; + gas.initial_gas += access_list_storages * ACCESS_LIST_STORAGE_KEY; + + // Base stipend + gas.initial_gas += if is_create { + if spec_id.is_enabled_in(SpecId::HOMESTEAD) { + // EIP-2: Homestead Hard-fork Changes + 53000 + } else { + 21000 + } + } else { + 21000 + }; + + // EIP-3860: Limit and meter initcode + // Init code stipend for bytecode analysis + if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create { + gas.initial_gas += initcode_cost(input.len()) + } + + // EIP-7702 + if spec_id.is_enabled_in(SpecId::PRAGUE) { + gas.initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST; + + // Calculate gas floor for EIP-7623 + gas.floor_gas = calc_tx_floor_cost(tokens_in_calldata); + } + + gas +} + +/// Initial gas that is deducted for transaction to be included. +/// Initial gas contains initial stipend gas, gas for access list and input data. +/// +/// # Returns +/// +/// - Intrinsic gas +/// - Number of tokens in calldata +pub fn calculate_initial_tx_gas_for_tx(tx: impl Transaction, spec: SpecId) -> InitialAndFloorGas { + let mut accounts = 0; + let mut storages = 0; + // legacy is only tx type that does not have access list. + if tx.tx_type() != TransactionType::Legacy { + (accounts, storages) = tx + .access_list() + .map(|al| { + al.fold((0, 0), |(mut num_accounts, mut num_storage_slots), item| { + num_accounts += 1; + num_storage_slots += item.storage_slots().count(); + + (num_accounts, num_storage_slots) + }) + }) + .unwrap_or_default(); + } + + calculate_initial_tx_gas( + spec, + tx.input(), + tx.kind().is_create(), + accounts as u64, + storages as u64, + tx.authorization_list_len() as u64, + ) +} + +/// Retrieve the total number of tokens in calldata. +#[inline] +pub fn get_tokens_in_calldata(input: &[u8], is_istanbul: bool) -> u64 { + let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64; + let non_zero_data_len = input.len() as u64 - zero_data_len; + let non_zero_data_multiplier = if is_istanbul { + // EIP-2028: Transaction data gas cost reduction + NON_ZERO_BYTE_MULTIPLIER_ISTANBUL + } else { + NON_ZERO_BYTE_MULTIPLIER + }; + zero_data_len + non_zero_data_len * non_zero_data_multiplier +} + +/// Calculate the transaction cost floor as specified in EIP-7623. +#[inline] +pub fn calc_tx_floor_cost(tokens_in_calldata: u64) -> u64 { + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN + 21_000 +} + +/// Returns number of words what would fit to provided number of bytes, +/// i.e. it rounds up the number bytes to number of words. +#[inline] +pub const fn num_words(len: usize) -> usize { + len.div_ceil(32) +} diff --git a/crates/context/interface/src/cfg/gas_params.rs b/crates/context/interface/src/cfg/gas_params.rs new file mode 100644 index 0000000000..fd8e5eab41 --- /dev/null +++ b/crates/context/interface/src/cfg/gas_params.rs @@ -0,0 +1,776 @@ +//! Gas table for dynamic gas constants. + +use crate::{ + cfg::gas::{ + self, log2floor, num_words, ISTANBUL_SLOAD_GAS, SSTORE_RESET, SSTORE_SET, WARM_SSTORE_RESET, + }, + context::SStoreResult, +}; +use primitives::{ + hardfork::SpecId::{self}, + U256, +}; +use std::sync::Arc; + +/// Gas table for dynamic gas constants. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct GasParams { + /// Table of gas costs for operations + table: Arc<[u64; 256]>, + /// Pointer to the table. + ptr: *const u64, +} + +#[cfg(feature = "serde")] +mod serde { + use super::{Arc, GasParams}; + use std::vec::Vec; + + #[derive(serde::Serialize, serde::Deserialize)] + struct GasParamsSerde { + table: Vec, + } + + #[cfg(feature = "serde")] + impl serde::Serialize for GasParams { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + GasParamsSerde { + table: self.table.to_vec(), + } + .serialize(serializer) + } + } + + impl<'de> serde::Deserialize<'de> for GasParams { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let table = GasParamsSerde::deserialize(deserializer)?; + if table.table.len() != 256 { + return Err(serde::de::Error::custom("Invalid gas params length")); + } + Ok(Self::new(Arc::new(table.table.try_into().unwrap()))) + } + } +} + +impl Default for GasParams { + fn default() -> Self { + Self::new_spec(SpecId::default()) + } +} + +impl GasParams { + /// Creates a new `GasParams` with the given table. + #[inline] + pub fn new(table: Arc<[u64; 256]>) -> Self { + Self { + ptr: table.as_ptr(), + table, + } + } + + /// Overrides the gas cost for the given gas id. + /// + /// It will clone underlying table and override the values. + /// + /// Use to override default gas cost + /// + /// ```rust + /// use revm_interpreter::gas::params::{GasParams, GasId}; + /// use primitives::hardfork::SpecId; + /// + /// let mut gas_table = GasParams::new_spec(SpecId::default()); + /// gas_table.override_gas([(GasId::memory_linear_cost(), 2), (GasId::memory_quadratic_reduction(), 512)].into_iter()); + /// assert_eq!(gas_table.get(GasId::memory_linear_cost()), 2); + /// assert_eq!(gas_table.get(GasId::memory_quadratic_reduction()), 512); + /// ``` + pub fn override_gas(&mut self, values: impl IntoIterator) { + let mut table = *self.table.clone(); + for (id, value) in values.into_iter() { + table[id.as_usize()] = value; + } + *self = Self::new(Arc::new(table)); + } + + /// Returns the table. + #[inline] + pub fn table(&self) -> &[u64; 256] { + &self.table + } + + /// Creates a new `GasParams` for the given spec. + #[inline] + pub fn new_spec(spec: SpecId) -> Self { + let mut table = [0; 256]; + + table[GasId::exp_byte_gas().as_usize()] = 10; + table[GasId::logdata().as_usize()] = gas::LOGDATA; + table[GasId::logtopic().as_usize()] = gas::LOGTOPIC; + table[GasId::copy_per_word().as_usize()] = gas::COPY; + table[GasId::extcodecopy_per_word().as_usize()] = gas::COPY; + table[GasId::mcopy_per_word().as_usize()] = gas::COPY; + table[GasId::keccak256_per_word().as_usize()] = gas::KECCAK256WORD; + table[GasId::memory_linear_cost().as_usize()] = gas::MEMORY; + table[GasId::memory_quadratic_reduction().as_usize()] = 512; + table[GasId::initcode_per_word().as_usize()] = gas::INITCODE_WORD_COST; + table[GasId::create().as_usize()] = gas::CREATE; + table[GasId::call_stipend_reduction().as_usize()] = 64; + table[GasId::transfer_value_cost().as_usize()] = gas::CALLVALUE; + table[GasId::cold_account_additional_cost().as_usize()] = 0; + table[GasId::new_account_cost().as_usize()] = gas::NEWACCOUNT; + table[GasId::warm_storage_read_cost().as_usize()] = 0; + // Frontiers had fixed 5k cost. + table[GasId::sstore_static().as_usize()] = SSTORE_RESET; + // SSTORE SET + table[GasId::sstore_set_without_load_cost().as_usize()] = SSTORE_SET - SSTORE_RESET; + // SSTORE RESET Is covered in SSTORE_STATIC. + table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = 0; + // SSTORE CLEARING SLOT REFUND + table[GasId::sstore_clearing_slot_refund().as_usize()] = 15000; + table[GasId::selfdestruct_refund().as_usize()] = 24000; + table[GasId::call_stipend().as_usize()] = gas::CALL_STIPEND; + table[GasId::cold_storage_additional_cost().as_usize()] = 0; + table[GasId::cold_storage_cost().as_usize()] = 0; + table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0; + + if spec.is_enabled_in(SpecId::TANGERINE) { + table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT; + } + + if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) { + table[GasId::exp_byte_gas().as_usize()] = 50; + } + + if spec.is_enabled_in(SpecId::ISTANBUL) { + table[GasId::sstore_static().as_usize()] = gas::ISTANBUL_SLOAD_GAS; + table[GasId::sstore_set_without_load_cost().as_usize()] = + SSTORE_SET - ISTANBUL_SLOAD_GAS; + table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = + SSTORE_RESET - ISTANBUL_SLOAD_GAS; + } + + if spec.is_enabled_in(SpecId::BERLIN) { + table[GasId::sstore_static().as_usize()] = gas::WARM_STORAGE_READ_COST; + table[GasId::cold_account_additional_cost().as_usize()] = + gas::COLD_ACCOUNT_ACCESS_COST_ADDITIONAL; + table[GasId::cold_storage_additional_cost().as_usize()] = + gas::COLD_SLOAD_COST - gas::WARM_STORAGE_READ_COST; + table[GasId::cold_storage_cost().as_usize()] = gas::COLD_SLOAD_COST; + table[GasId::warm_storage_read_cost().as_usize()] = gas::WARM_STORAGE_READ_COST; + + table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = + WARM_SSTORE_RESET - gas::WARM_STORAGE_READ_COST; + table[GasId::sstore_set_without_load_cost().as_usize()] = + SSTORE_SET - gas::WARM_STORAGE_READ_COST; + } + + if spec.is_enabled_in(SpecId::LONDON) { + // EIP-3529: Reduction in refunds + + // Replace SSTORE_CLEARS_SCHEDULE (as defined in EIP-2200) with + // SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST (4,800 gas as of EIP-2929 + EIP-2930) + table[GasId::sstore_clearing_slot_refund().as_usize()] = + WARM_SSTORE_RESET + gas::ACCESS_LIST_STORAGE_KEY; + + table[GasId::selfdestruct_refund().as_usize()] = 0; + } + + Self::new(Arc::new(table)) + } + + /// Gets the gas cost for the given gas id. + #[inline] + pub const fn get(&self, id: GasId) -> u64 { + unsafe { *self.ptr.add(id.as_usize()) } + } + + /// `EXP` opcode cost calculation. + #[inline] + pub fn exp_cost(&self, power: U256) -> u64 { + if power.is_zero() { + return 0; + } + // EIP-160: EXP cost increase + self.get(GasId::exp_byte_gas()) + .saturating_mul(log2floor(power) / 8 + 1) + } + + /// Selfdestruct refund. + #[inline] + pub fn selfdestruct_refund(&self) -> i64 { + self.get(GasId::selfdestruct_refund()) as i64 + } + + /// Selfdestruct cost. + #[inline] + pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 { + let mut gas = 0; + + // EIP-150: Gas cost changes for IO-heavy operations + if should_charge_topup { + gas += self.new_account_cost_for_selfdestruct(); + } + + if is_cold { + // Note: SELFDESTRUCT does not charge a WARM_STORAGE_READ_COST in case the recipient is already warm, + // which differs from how the other call-variants work. The reasoning behind this is to keep + // the changes small, a SELFDESTRUCT already costs 5K and is a no-op if invoked more than once. + // + // For GasParams both values are zero before BERLIN fork. + gas += self.cold_account_additional_cost() + self.warm_storage_read_cost(); + } + gas + } + + /// EXTCODECOPY gas cost + #[inline] + pub fn extcodecopy(&self, len: usize) -> u64 { + self.get(GasId::extcodecopy_per_word()) + .saturating_mul(num_words(len) as u64) + } + + /// MCOPY gas cost + #[inline] + pub fn mcopy_cost(&self, len: usize) -> u64 { + self.get(GasId::mcopy_per_word()) + .saturating_mul(num_words(len) as u64) + } + + /// Static gas cost for SSTORE opcode + #[inline] + pub fn sstore_static_gas(&self) -> u64 { + self.get(GasId::sstore_static()) + } + + /// SSTORE set cost + #[inline] + pub fn sstore_set_without_load_cost(&self) -> u64 { + self.get(GasId::sstore_set_without_load_cost()) + } + + /// SSTORE reset cost + #[inline] + pub fn sstore_reset_without_cold_load_cost(&self) -> u64 { + self.get(GasId::sstore_reset_without_cold_load_cost()) + } + + /// SSTORE clearing slot refund + #[inline] + pub fn sstore_clearing_slot_refund(&self) -> u64 { + self.get(GasId::sstore_clearing_slot_refund()) + } + + /// Dynamic gas cost for SSTORE opcode. + /// + /// Dynamic gas cost is gas that needs input from SSTORE operation to be calculated. + #[inline] + pub fn sstore_dynamic_gas(&self, is_istanbul: bool, vals: &SStoreResult, is_cold: bool) -> u64 { + // frontier logic gets charged for every SSTORE operation if original value is zero. + // this behaviour is fixed in istanbul fork. + if !is_istanbul { + if vals.is_present_zero() && !vals.is_new_zero() { + return self.sstore_set_without_load_cost(); + } else { + return self.sstore_reset_without_cold_load_cost(); + } + } + + let mut gas = 0; + + // this will be zero before berlin fork. + if is_cold { + gas += self.cold_storage_cost(); + } + + // if new values changed present value and present value is unchanged from original. + if vals.new_values_changes_present() && vals.is_original_eq_present() { + gas += if vals.is_original_zero() { + // set cost for creating storage slot (Zero slot means it is not existing). + // and previous condition says present is same as original. + self.sstore_set_without_load_cost() + } else { + // if new value is not zero, this means we are setting some value to it. + self.sstore_reset_without_cold_load_cost() + }; + } + gas + } + + /// SSTORE refund calculation. + #[inline] + pub fn sstore_refund(&self, is_istanbul: bool, vals: &SStoreResult) -> i64 { + // EIP-3529: Reduction in refunds + let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64; + + if !is_istanbul { + // // before istanbul fork, refund was always awarded without checking original state. + if !vals.is_present_zero() && vals.is_new_zero() { + return sstore_clearing_slot_refund; + } + return 0; + } + + // If current value equals new value (this is a no-op) + if vals.is_new_eq_present() { + return 0; + } + + // refund for the clearing of storage slot. + // As new is not equal to present, new values zero means that original and present values are not zero + if vals.is_original_eq_present() && vals.is_new_zero() { + return sstore_clearing_slot_refund; + } + + let mut refund = 0; + // If original value is not 0 + if !vals.is_original_zero() { + // If current value is 0 (also means that new value is not 0), + if vals.is_present_zero() { + // remove SSTORE_CLEARS_SCHEDULE gas from refund counter. + refund -= sstore_clearing_slot_refund; + // If new value is 0 (also means that current value is not 0), + } else if vals.is_new_zero() { + // add SSTORE_CLEARS_SCHEDULE gas to refund counter. + refund += sstore_clearing_slot_refund; + } + } + + // If original value equals new value (this storage slot is reset) + if vals.is_original_eq_new() { + // If original value is 0 + if vals.is_original_zero() { + // add SSTORE_SET_GAS - SLOAD_GAS to refund counter. + refund += self.sstore_set_without_load_cost() as i64; + // Otherwise + } else { + // add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. + refund += self.sstore_reset_without_cold_load_cost() as i64; + } + } + refund + } + + /// `LOG` opcode cost calculation. + #[inline] + pub const fn log_cost(&self, n: u8, len: u64) -> u64 { + self.get(GasId::logdata()) + .saturating_mul(len) + .saturating_add(self.get(GasId::logtopic()) * n as u64) + } + + /// KECCAK256 gas cost per word + #[inline] + pub fn keccak256_cost(&self, len: usize) -> u64 { + self.get(GasId::keccak256_per_word()) + .saturating_mul(num_words(len) as u64) + } + + /// Memory gas cost + #[inline] + pub fn memory_cost(&self, len: usize) -> u64 { + let len = len as u64; + self.get(GasId::memory_linear_cost()) + .saturating_mul(len) + .saturating_add( + (len.saturating_mul(len)) + .saturating_div(self.get(GasId::memory_quadratic_reduction())), + ) + } + + /// Initcode word cost + #[inline] + pub fn initcode_cost(&self, len: usize) -> u64 { + self.get(GasId::initcode_per_word()) + .saturating_mul(num_words(len) as u64) + } + + /// Create gas cost + #[inline] + pub fn create_cost(&self) -> u64 { + self.get(GasId::create()) + } + + /// Create2 gas cost. + #[inline] + pub fn create2_cost(&self, len: usize) -> u64 { + self.get(GasId::create()).saturating_add( + self.get(GasId::keccak256_per_word()) + .saturating_mul(num_words(len) as u64), + ) + } + + /// Call stipend. + #[inline] + pub fn call_stipend(&self) -> u64 { + self.get(GasId::call_stipend()) + } + + /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit. + #[inline] + pub fn call_stipend_reduction(&self, gas_limit: u64) -> u64 { + gas_limit - gas_limit / self.get(GasId::call_stipend_reduction()) + } + + /// Transfer value cost + #[inline] + pub fn transfer_value_cost(&self) -> u64 { + self.get(GasId::transfer_value_cost()) + } + + /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded. + #[inline] + pub fn cold_account_additional_cost(&self) -> u64 { + self.get(GasId::cold_account_additional_cost()) + } + + /// Cold storage additional cost. + #[inline] + pub fn cold_storage_additional_cost(&self) -> u64 { + self.get(GasId::cold_storage_additional_cost()) + } + + /// Cold storage cost. + #[inline] + pub fn cold_storage_cost(&self) -> u64 { + self.get(GasId::cold_storage_cost()) + } + + /// New account cost. New account cost is added to the gas cost if the account is empty. + #[inline] + pub fn new_account_cost(&self, is_spurious_dragon: bool, transfers_value: bool) -> u64 { + // EIP-161: State trie clearing (invariant-preserving alternative) + // Pre-Spurious Dragon: always charge for new account + // Post-Spurious Dragon: only charge if value is transferred + if !is_spurious_dragon || transfers_value { + return self.get(GasId::new_account_cost()); + } + 0 + } + + /// New account cost for selfdestruct. + #[inline] + pub fn new_account_cost_for_selfdestruct(&self) -> u64 { + self.get(GasId::new_account_cost_for_selfdestruct()) + } + + /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded. + #[inline] + pub fn warm_storage_read_cost(&self) -> u64 { + self.get(GasId::warm_storage_read_cost()) + } + + /// Copy cost + #[inline] + pub fn copy_cost(&self, len: usize) -> u64 { + self.copy_per_word_cost(num_words(len)) + } + + /// Copy per word cost + #[inline] + pub fn copy_per_word_cost(&self, word_num: usize) -> u64 { + self.get(GasId::copy_per_word()) + .saturating_mul(word_num as u64) + } +} + +/// Gas identifier that maps onto index in gas table. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct GasId(u8); + +impl GasId { + /// Creates a new `GasId` with the given id. + pub const fn new(id: u8) -> Self { + Self(id) + } + + /// Returns the id of the gas. + pub const fn as_u8(&self) -> u8 { + self.0 + } + + /// Returns the id of the gas as a usize. + pub const fn as_usize(&self) -> usize { + self.0 as usize + } + + /// Returns the name of the gas identifier as a string. + /// + /// # Examples + /// + /// ``` + /// use revm_interpreter::gas::params::GasId; + /// + /// assert_eq!(GasId::exp_byte_gas().name(), "exp_byte_gas"); + /// assert_eq!(GasId::memory_linear_cost().name(), "memory_linear_cost"); + /// assert_eq!(GasId::sstore_static().name(), "sstore_static"); + /// ``` + pub const fn name(&self) -> &'static str { + match self.0 { + x if x == Self::exp_byte_gas().as_u8() => "exp_byte_gas", + x if x == Self::extcodecopy_per_word().as_u8() => "extcodecopy_per_word", + x if x == Self::copy_per_word().as_u8() => "copy_per_word", + x if x == Self::logdata().as_u8() => "logdata", + x if x == Self::logtopic().as_u8() => "logtopic", + x if x == Self::mcopy_per_word().as_u8() => "mcopy_per_word", + x if x == Self::keccak256_per_word().as_u8() => "keccak256_per_word", + x if x == Self::memory_linear_cost().as_u8() => "memory_linear_cost", + x if x == Self::memory_quadratic_reduction().as_u8() => "memory_quadratic_reduction", + x if x == Self::initcode_per_word().as_u8() => "initcode_per_word", + x if x == Self::create().as_u8() => "create", + x if x == Self::call_stipend_reduction().as_u8() => "call_stipend_reduction", + x if x == Self::transfer_value_cost().as_u8() => "transfer_value_cost", + x if x == Self::cold_account_additional_cost().as_u8() => { + "cold_account_additional_cost" + } + x if x == Self::new_account_cost().as_u8() => "new_account_cost", + x if x == Self::warm_storage_read_cost().as_u8() => "warm_storage_read_cost", + x if x == Self::sstore_static().as_u8() => "sstore_static", + x if x == Self::sstore_set_without_load_cost().as_u8() => { + "sstore_set_without_load_cost" + } + x if x == Self::sstore_reset_without_cold_load_cost().as_u8() => { + "sstore_reset_without_cold_load_cost" + } + x if x == Self::sstore_clearing_slot_refund().as_u8() => "sstore_clearing_slot_refund", + x if x == Self::selfdestruct_refund().as_u8() => "selfdestruct_refund", + x if x == Self::call_stipend().as_u8() => "call_stipend", + x if x == Self::cold_storage_additional_cost().as_u8() => { + "cold_storage_additional_cost" + } + x if x == Self::cold_storage_cost().as_u8() => "cold_storage_cost", + x if x == Self::new_account_cost_for_selfdestruct().as_u8() => { + "new_account_cost_for_selfdestruct" + } + _ => "unknown", + } + } + + /// Converts a string to a `GasId`. + /// + /// Returns `None` if the string does not match any known gas identifier. + /// + /// # Examples + /// + /// ``` + /// use revm_interpreter::gas::params::GasId; + /// + /// assert_eq!(GasId::from_name("exp_byte_gas"), Some(GasId::exp_byte_gas())); + /// assert_eq!(GasId::from_name("memory_linear_cost"), Some(GasId::memory_linear_cost())); + /// assert_eq!(GasId::from_name("invalid_name"), None); + /// ``` + pub fn from_name(s: &str) -> Option { + match s { + "exp_byte_gas" => Some(Self::exp_byte_gas()), + "extcodecopy_per_word" => Some(Self::extcodecopy_per_word()), + "copy_per_word" => Some(Self::copy_per_word()), + "logdata" => Some(Self::logdata()), + "logtopic" => Some(Self::logtopic()), + "mcopy_per_word" => Some(Self::mcopy_per_word()), + "keccak256_per_word" => Some(Self::keccak256_per_word()), + "memory_linear_cost" => Some(Self::memory_linear_cost()), + "memory_quadratic_reduction" => Some(Self::memory_quadratic_reduction()), + "initcode_per_word" => Some(Self::initcode_per_word()), + "create" => Some(Self::create()), + "call_stipend_reduction" => Some(Self::call_stipend_reduction()), + "transfer_value_cost" => Some(Self::transfer_value_cost()), + "cold_account_additional_cost" => Some(Self::cold_account_additional_cost()), + "new_account_cost" => Some(Self::new_account_cost()), + "warm_storage_read_cost" => Some(Self::warm_storage_read_cost()), + "sstore_static" => Some(Self::sstore_static()), + "sstore_set_without_load_cost" => Some(Self::sstore_set_without_load_cost()), + "sstore_reset_without_cold_load_cost" => { + Some(Self::sstore_reset_without_cold_load_cost()) + } + "sstore_clearing_slot_refund" => Some(Self::sstore_clearing_slot_refund()), + "selfdestruct_refund" => Some(Self::selfdestruct_refund()), + "call_stipend" => Some(Self::call_stipend()), + "cold_storage_additional_cost" => Some(Self::cold_storage_additional_cost()), + "cold_storage_cost" => Some(Self::cold_storage_cost()), + "new_account_cost_for_selfdestruct" => Some(Self::new_account_cost_for_selfdestruct()), + _ => None, + } + } + + /// EXP gas cost per byte + pub const fn exp_byte_gas() -> GasId { + Self::new(1) + } + + /// EXTCODECOPY gas cost per word + pub const fn extcodecopy_per_word() -> GasId { + Self::new(2) + } + + /// Copy copy per word + pub const fn copy_per_word() -> GasId { + Self::new(3) + } + + /// Log data gas cost per byte + pub const fn logdata() -> GasId { + Self::new(4) + } + + /// Log topic gas cost per topic + pub const fn logtopic() -> GasId { + Self::new(5) + } + + /// MCOPY gas cost per word + pub const fn mcopy_per_word() -> GasId { + Self::new(6) + } + + /// KECCAK256 gas cost per word + pub const fn keccak256_per_word() -> GasId { + Self::new(7) + } + + /// Memory linear cost. Memory is additionally added as n*linear_cost. + pub const fn memory_linear_cost() -> GasId { + Self::new(8) + } + + /// Memory quadratic reduction. Memory is additionally added as n*n/quadratic_reduction. + pub const fn memory_quadratic_reduction() -> GasId { + Self::new(9) + } + + /// Initcode word cost + pub const fn initcode_per_word() -> GasId { + Self::new(10) + } + + /// Create gas cost + pub const fn create() -> GasId { + Self::new(11) + } + + /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit. + pub const fn call_stipend_reduction() -> GasId { + Self::new(12) + } + + /// Transfer value cost + pub const fn transfer_value_cost() -> GasId { + Self::new(13) + } + + /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded. + pub const fn cold_account_additional_cost() -> GasId { + Self::new(14) + } + + /// New account cost. New account cost is added to the gas cost if the account is empty. + pub const fn new_account_cost() -> GasId { + Self::new(15) + } + + /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded. + /// + /// Used in delegated account access to specify delegated account warm gas cost. + pub const fn warm_storage_read_cost() -> GasId { + Self::new(16) + } + + /// Static gas cost for SSTORE opcode. This gas in comparison with other gas const needs + /// to be deducted after check for minimal stipend gas cost. This is a reason why it is here. + pub const fn sstore_static() -> GasId { + Self::new(17) + } + + /// SSTORE set cost additional amount after SSTORE_RESET is added. + pub const fn sstore_set_without_load_cost() -> GasId { + Self::new(18) + } + + /// SSTORE reset cost + pub const fn sstore_reset_without_cold_load_cost() -> GasId { + Self::new(19) + } + + /// SSTORE clearing slot refund + pub const fn sstore_clearing_slot_refund() -> GasId { + Self::new(20) + } + + /// Selfdestruct refund. + pub const fn selfdestruct_refund() -> GasId { + Self::new(21) + } + + /// Call stipend checked in sstore. + pub const fn call_stipend() -> GasId { + Self::new(22) + } + + /// Cold storage additional cost. + pub const fn cold_storage_additional_cost() -> GasId { + Self::new(23) + } + + /// Cold storage cost + pub const fn cold_storage_cost() -> GasId { + Self::new(24) + } + + /// New account cost for selfdestruct. + pub const fn new_account_cost_for_selfdestruct() -> GasId { + Self::new(25) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + #[test] + fn test_gas_id_name_and_from_str_coverage() { + let mut unique_names = HashSet::new(); + let mut known_gas_ids = 0; + + // Iterate over all possible GasId values (0..256) + for i in 0..=255 { + let gas_id = GasId::new(i); + let name = gas_id.name(); + + // Count unique names (excluding "unknown") + if name != "unknown" { + unique_names.insert(name); + } + } + + // Now test from_str for each unique name + for name in &unique_names { + if let Some(gas_id) = GasId::from_name(name) { + known_gas_ids += 1; + // Verify round-trip: name -> GasId -> name should be consistent + assert_eq!(gas_id.name(), *name, "Round-trip failed for {}", name); + } + } + + println!("Total unique named GasIds: {}", unique_names.len()); + println!("GasIds resolvable via from_str: {}", known_gas_ids); + + // All unique names should be resolvable via from_str + assert_eq!( + unique_names.len(), + known_gas_ids, + "Not all unique names are resolvable via from_str" + ); + + // We should have exactly 25 known GasIds (based on the indices 1-25 used) + assert_eq!( + unique_names.len(), + 25, + "Expected 25 unique GasIds, found {}", + unique_names.len() + ); + } +} diff --git a/crates/context/interface/src/host.rs b/crates/context/interface/src/host.rs index 833346696a..e4efb22081 100644 --- a/crates/context/interface/src/host.rs +++ b/crates/context/interface/src/host.rs @@ -1,11 +1,12 @@ //! Host interface for external blockchain state access. use crate::{ + cfg::GasParams, context::{SStoreResult, SelfDestructResult, StateLoad}, journaled_state::{AccountInfoLoad, AccountLoad}, }; use auto_impl::auto_impl; -use primitives::{Address, Bytes, Log, StorageKey, StorageValue, B256, U256}; +use primitives::{hardfork::SpecId, Address, Bytes, Log, StorageKey, StorageValue, B256, U256}; use state::Bytecode; /// Error that can happen when loading account info. @@ -60,6 +61,9 @@ pub trait Host { /// Max initcode size, calls `ContextTr::cfg().max_code_size().saturating_mul(2)` fn max_initcode_size(&self) -> usize; + /// Gas params contains the dynamic gas constants for the EVM. + fn gas_params(&self) -> &GasParams; + /* Database */ /// Block hash, calls `ContextTr::journal_mut().db().block_hash(number)` @@ -200,8 +204,19 @@ pub trait Host { } /// Dummy host that implements [`Host`] trait and returns all default values. -#[derive(Debug)] -pub struct DummyHost; +#[derive(Default, Debug)] +pub struct DummyHost { + gas_params: GasParams, +} + +impl DummyHost { + /// Create a new dummy host with the given spec. + pub fn new(spec: SpecId) -> Self { + Self { + gas_params: GasParams::new_spec(spec), + } + } +} impl Host for DummyHost { fn basefee(&self) -> U256 { @@ -216,6 +231,10 @@ impl Host for DummyHost { U256::ZERO } + fn gas_params(&self) -> &GasParams { + &self.gas_params + } + fn difficulty(&self) -> U256 { U256::ZERO } diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 2aea1efcf6..c8165bc752 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -1,6 +1,7 @@ //! This module contains [`CfgEnv`] and implements [`Cfg`] trait for it. pub use context_interface::Cfg; +use context_interface::cfg::GasParams; use primitives::{eip170, eip3860, eip7825, hardfork::SpecId}; /// EVM configuration #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -55,6 +56,8 @@ pub struct CfgEnv { /// Introduced in Osaka in [EIP-7825: Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825) /// with initials cap of 30M. pub tx_gas_limit_cap: Option, + /// Gas params for the EVM. + pub gas_params: GasParams, /// A hard memory limit in bytes beyond which /// [OutOfGasError::Memory][context_interface::result::OutOfGasError::Memory] cannot be resized. /// @@ -146,7 +149,7 @@ impl + Copy> CfgEnv { } } -impl CfgEnv { +impl + Copy> CfgEnv { /// Create new `CfgEnv` with default values and specified spec. pub fn new_with_spec(spec: SPEC) -> Self { Self { @@ -159,6 +162,7 @@ impl CfgEnv { max_blobs_per_tx: None, tx_gas_limit_cap: None, blob_base_fee_update_fraction: None, + gas_params: GasParams::new_spec(spec.into()), #[cfg(feature = "memory_limit")] memory_limit: (1 << 32) - 1, #[cfg(feature = "optional_balance_check")] @@ -199,7 +203,8 @@ impl CfgEnv { } /// Consumes `self` and returns a new `CfgEnv` with the specified spec. - pub fn with_spec>(self, spec: OSPEC) -> CfgEnv { + pub fn with_spec + Copy>(self, spec: OSPEC) -> CfgEnv { + let eth_spec = spec.into(); CfgEnv { chain_id: self.chain_id, tx_chain_id_check: self.tx_chain_id_check, @@ -210,6 +215,7 @@ impl CfgEnv { tx_gas_limit_cap: self.tx_gas_limit_cap, max_blobs_per_tx: self.max_blobs_per_tx, blob_base_fee_update_fraction: self.blob_base_fee_update_fraction, + gas_params: GasParams::new_spec(eth_spec), #[cfg(feature = "memory_limit")] memory_limit: self.memory_limit, #[cfg(feature = "optional_balance_check")] @@ -410,9 +416,14 @@ impl + Copy> Cfg for CfgEnv { } } } + + #[inline] + fn gas_params(&self) -> &GasParams { + &self.gas_params + } } -impl Default for CfgEnv { +impl + Copy> Default for CfgEnv { fn default() -> Self { Self::new_with_spec(SPEC::default()) } diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index 5bf1832fa3..a1c517dd04 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -1,6 +1,7 @@ //! This module contains [`Context`] struct and implements [`ContextTr`] trait for it. use crate::{block::BlockEnv, cfg::CfgEnv, journal::Journal, tx::TxEnv, LocalContext}; use context_interface::{ + cfg::GasParams, context::{ContextError, ContextSetters, SStoreResult, SelfDestructResult, StateLoad}, host::LoadError, journaled_state::AccountInfoLoad, @@ -452,6 +453,11 @@ impl< self.block().prevrandao().map(|r| r.into()) } + #[inline] + fn gas_params(&self) -> &GasParams { + self.cfg().gas_params() + } + fn block_number(&self) -> U256 { self.block().number() } diff --git a/crates/handler/src/evm.rs b/crates/handler/src/evm.rs index 8607f48dce..0ad3130f0c 100644 --- a/crates/handler/src/evm.rs +++ b/crates/handler/src/evm.rs @@ -183,13 +183,7 @@ where let ctx = &mut self.ctx; let precompiles = &mut self.precompiles; - let res = Self::Frame::init_with_context( - new_frame, - ctx, - precompiles, - frame_input, - self.instruction.gas_params(), - )?; + let res = Self::Frame::init_with_context(new_frame, ctx, precompiles, frame_input)?; Ok(res.map_frame(|token| { if is_first_init { diff --git a/crates/handler/src/frame.rs b/crates/handler/src/frame.rs index 479e4093c1..2b3ced2c1b 100644 --- a/crates/handler/src/frame.rs +++ b/crates/handler/src/frame.rs @@ -4,6 +4,7 @@ use crate::{ }; use context::result::FromStringError; use context_interface::{ + cfg::gas::CODEDEPOSIT, context::ContextError, journaled_state::{account::JournaledAccountTr, JournalCheckpoint, JournalTr}, local::{FrameToken, OutFrame}, @@ -12,7 +13,6 @@ use context_interface::{ use core::cmp::min; use derive_where::derive_where; use interpreter::{ - gas::{self, params::GasParams}, interpreter::{EthInterpreter, ExtBytecode}, interpreter_action::FrameInit, interpreter_types::ReturnData, @@ -114,7 +114,6 @@ impl EthFrame { spec_id: SpecId, gas_limit: u64, checkpoint: JournalCheckpoint, - gas_params: GasParams, ) { let Self { data: data_ref, @@ -128,9 +127,7 @@ impl EthFrame { *input_ref = input; *depth_ref = depth; *is_finished_ref = false; - interpreter.clear( - memory, bytecode, inputs, is_static, spec_id, gas_limit, gas_params, - ); + interpreter.clear(memory, bytecode, inputs, is_static, spec_id, gas_limit); *checkpoint_ref = checkpoint; } @@ -147,7 +144,6 @@ impl EthFrame { depth: usize, memory: SharedMemory, inputs: Box, - gas_params: GasParams, ) -> Result, ERROR> { let gas = Gas::new(inputs.gas_limit); let return_result = |instruction_result: InstructionResult| { @@ -247,7 +243,6 @@ impl EthFrame { ctx.cfg().spec().into(), gas_limit, checkpoint, - gas_params, ); Ok(ItemOrResult::Item(this.consume())) } @@ -263,7 +258,6 @@ impl EthFrame { depth: usize, memory: SharedMemory, inputs: Box, - gas_params: GasParams, ) -> Result, ERROR> { let spec = context.cfg().spec().into(); let return_error = |e| { @@ -350,7 +344,6 @@ impl EthFrame { spec, gas_limit, checkpoint, - gas_params, ); Ok(ItemOrResult::Item(this.consume())) } @@ -364,7 +357,6 @@ impl EthFrame { ctx: &mut CTX, precompiles: &mut PRECOMPILES, frame_init: FrameInit, - gas_params: GasParams, ) -> Result< ItemOrResult, ContextError<<::Db as Database>::Error>, @@ -378,11 +370,9 @@ impl EthFrame { match frame_input { FrameInput::Call(inputs) => { - Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs, gas_params) - } - FrameInput::Create(inputs) => { - Self::make_create_frame(this, ctx, depth, memory, inputs, gas_params) + Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs) } + FrameInput::Create(inputs) => Self::make_create_frame(this, ctx, depth, memory, inputs), FrameInput::Empty => unreachable!(), } } @@ -579,7 +569,7 @@ pub fn return_create( interpreter_result.result = InstructionResult::CreateContractSizeLimit; return; } - let gas_for_code = interpreter_result.output.len() as u64 * gas::CODEDEPOSIT; + let gas_for_code = interpreter_result.output.len() as u64 * CODEDEPOSIT; if !interpreter_result.gas.record_cost(gas_for_code) { // Record code deposit gas cost and check if we are out of gas. // EIP-2 point 3: If contract creation does not have enough gas to pay for the diff --git a/crates/handler/src/handler.rs b/crates/handler/src/handler.rs index ca905a1683..9c862d9b4b 100644 --- a/crates/handler/src/handler.rs +++ b/crates/handler/src/handler.rs @@ -1,4 +1,3 @@ -use crate::instructions::InstructionProvider; use crate::{ evm::FrameTr, execution, post_execution, @@ -99,7 +98,6 @@ pub trait Handler { &mut self, evm: &mut Self::Evm, ) -> Result, Self::Error> { - self.configure(evm); // Run inner handler and catch all errors to handle cleanup. match self.run_without_catch_error(evm) { Ok(output) => Ok(output), @@ -107,15 +105,6 @@ pub trait Handler { } } - /// Configure the handler: - /// * Set Instruction gas table to the spec id. - #[inline] - fn configure(&mut self, evm: &mut Self::Evm) { - let spec_id = evm.ctx().cfg().spec().into(); - // sets static gas depending on the spec id. - evm.ctx_instructions().1.set_spec(spec_id); - } - /// Runs the system call. /// /// System call is a special transaction where caller is a [`crate::SYSTEM_ADDRESS`] @@ -137,8 +126,6 @@ pub trait Handler { ) -> Result, Self::Error> { // dummy values that are not used. let init_and_floor_gas = InitialAndFloorGas::new(0, 0); - // configure the evm for system call. - self.configure(evm); // call execution and than output. match self .execution(evm, &init_and_floor_gas) diff --git a/crates/handler/src/instructions.rs b/crates/handler/src/instructions.rs index ebe5a2d319..89bcf663c0 100644 --- a/crates/handler/src/instructions.rs +++ b/crates/handler/src/instructions.rs @@ -1,6 +1,5 @@ use auto_impl::auto_impl; use interpreter::{ - gas::params::GasParams, instructions::{instruction_table_gas_changes_spec, InstructionTable}, Host, Instruction, InterpreterTypes, }; @@ -17,12 +16,6 @@ pub trait InstructionProvider { /// Returns the instruction table that is used by EvmTr to execute instructions. fn instruction_table(&self) -> &InstructionTable; - - /// Returns the gas params that is used by EvmTr to execute instructions. - fn gas_params(&self) -> GasParams; - - /// Sets the spec. Return true if the spec was changed. - fn set_spec(&mut self, spec: SpecId) -> bool; } /// Ethereum instruction contains list of mainnet instructions that is used for Interpreter execution. @@ -30,8 +23,6 @@ pub trait InstructionProvider { pub struct EthInstructions { /// Table containing instruction implementations indexed by opcode. pub instruction_table: Box>, - /// Gas params that sets gas costs for instructions. - pub gas_params: GasParams, /// Spec that is used to set gas costs for instructions. pub spec: SpecId, } @@ -43,7 +34,6 @@ where fn clone(&self) -> Self { Self { instruction_table: self.instruction_table.clone(), - gas_params: self.gas_params.clone(), spec: self.spec, } } @@ -57,23 +47,19 @@ where /// Returns `EthInstructions` with mainnet spec. pub fn new_mainnet() -> Self { let spec = SpecId::default(); - Self::new( - instruction_table_gas_changes_spec(spec), - GasParams::new_spec(spec), - spec, - ) + Self::new(instruction_table_gas_changes_spec(spec), spec) + } + + /// Returns `EthInstructions` with mainnet spec. + pub fn new_mainnet_with_spec(spec: SpecId) -> Self { + Self::new(instruction_table_gas_changes_spec(spec), spec) } /// Returns a new instance of `EthInstructions` with custom instruction table. #[inline] - pub fn new( - base_table: InstructionTable, - gas_params: GasParams, - spec: SpecId, - ) -> Self { + pub fn new(base_table: InstructionTable, spec: SpecId) -> Self { Self { instruction_table: Box::new(base_table), - gas_params, spec, } } @@ -96,20 +82,6 @@ where fn instruction_table(&self) -> &InstructionTable { &self.instruction_table } - - fn gas_params(&self) -> GasParams { - self.gas_params.clone() - } - - fn set_spec(&mut self, spec: SpecId) -> bool { - if spec == self.spec { - return false; - } - *self.instruction_table = instruction_table_gas_changes_spec(spec); - self.gas_params = GasParams::new_spec(spec); - - true - } } impl Default for EthInstructions diff --git a/crates/inspector/src/handler.rs b/crates/inspector/src/handler.rs index fe43a71b46..53bdf436db 100644 --- a/crates/inspector/src/handler.rs +++ b/crates/inspector/src/handler.rs @@ -42,7 +42,6 @@ where &mut self, evm: &mut Self::Evm, ) -> Result, Self::Error> { - self.configure(evm); match self.inspect_run_without_catch_error(evm) { Ok(output) => Ok(output), Err(e) => self.catch_error(evm, e), @@ -137,8 +136,6 @@ where ) -> Result, Self::Error> { // dummy values that are not used. let init_and_floor_gas = InitialAndFloorGas::new(0, 0); - // configure - self.configure(evm); // call execution with inspection and then output. match self .inspect_execution(evm, &init_and_floor_gas) diff --git a/crates/interpreter/src/gas.rs b/crates/interpreter/src/gas.rs index a8ac93a37c..e4867e8e30 100644 --- a/crates/interpreter/src/gas.rs +++ b/crates/interpreter/src/gas.rs @@ -218,10 +218,19 @@ impl MemoryGas { return None; } self.words_num = new_num; - let mut cost = crate::gas::calc::memory_gas(new_num, linear_cost, quadratic_cost); + let mut cost = memory_gas(new_num, linear_cost, quadratic_cost); core::mem::swap(&mut self.expansion_cost, &mut cost); // Safe to subtract because we know that new_len > length // Notice the swap above. Some(self.expansion_cost - cost) } } + +/// Memory expansion cost calculation for a given number of words. +#[inline] +pub const fn memory_gas(num_words: usize, linear_cost: u64, quadratic_cost: u64) -> u64 { + let num_words = num_words as u64; + linear_cost + .saturating_mul(num_words) + .saturating_add(num_words.saturating_mul(num_words) / quadratic_cost) +} diff --git a/crates/interpreter/src/gas/calc.rs b/crates/interpreter/src/gas/calc.rs index 06d9e6550d..7668477cce 100644 --- a/crates/interpreter/src/gas/calc.rs +++ b/crates/interpreter/src/gas/calc.rs @@ -1,6 +1,7 @@ -use super::constants::*; use crate::num_words; -use context_interface::{transaction::AccessListItemTr as _, Transaction, TransactionType}; +use context_interface::{ + cfg::gas::*, transaction::AccessListItemTr as _, Transaction, TransactionType, +}; use primitives::{eip7702, hardfork::SpecId, U256}; #[inline] @@ -45,15 +46,6 @@ pub const fn initcode_cost(len: usize) -> u64 { cost } -/// Memory expansion cost calculation for a given number of words. -#[inline] -pub const fn memory_gas(num_words: usize, linear_cost: u64, quadratic_cost: u64) -> u64 { - let num_words = num_words as u64; - linear_cost - .saturating_mul(num_words) - .saturating_add(num_words.saturating_mul(num_words) / quadratic_cost) -} - /// Init and floor gas from transaction #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/interpreter/src/gas/constants.rs b/crates/interpreter/src/gas/constants.rs index a4a101a985..8b13789179 100644 --- a/crates/interpreter/src/gas/constants.rs +++ b/crates/interpreter/src/gas/constants.rs @@ -1,103 +1 @@ -/// Gas cost for operations that consume zero gas. -pub const ZERO: u64 = 0; -/// Base gas cost for basic operations. -pub const BASE: u64 = 2; -/// Gas cost for very low-cost operations. -pub const VERYLOW: u64 = 3; -/// Gas cost for DATALOADN instruction. -pub const DATA_LOADN_GAS: u64 = 3; - -/// Gas cost for conditional jump instructions. -pub const CONDITION_JUMP_GAS: u64 = 4; -/// Gas cost for RETF instruction. -pub const RETF_GAS: u64 = 3; -/// Gas cost for DATALOAD instruction. -pub const DATA_LOAD_GAS: u64 = 4; - -/// Gas cost for low-cost operations. -pub const LOW: u64 = 5; -/// Gas cost for medium-cost operations. -pub const MID: u64 = 8; -/// Gas cost for high-cost operations. -pub const HIGH: u64 = 10; -/// Gas cost for JUMPDEST instruction. -pub const JUMPDEST: u64 = 1; -/// Gas cost for REFUND SELFDESTRUCT instruction. -pub const SELFDESTRUCT_REFUND: i64 = 24000; -/// Gas cost for CREATE instruction. -pub const CREATE: u64 = 32000; -/// Additional gas cost when a call transfers value. -pub const CALLVALUE: u64 = 9000; -/// Gas cost for creating a new account. -pub const NEWACCOUNT: u64 = 25000; -/// Base gas cost for EXP instruction. -pub const EXP: u64 = 10; -/// Gas cost per word for memory operations. -pub const MEMORY: u64 = 3; -/// Base gas cost for LOG instructions. -pub const LOG: u64 = 375; -/// Gas cost per byte of data in LOG instructions. -pub const LOGDATA: u64 = 8; -/// Gas cost per topic in LOG instructions. -pub const LOGTOPIC: u64 = 375; -/// Base gas cost for KECCAK256 instruction. -pub const KECCAK256: u64 = 30; -/// Gas cost per word for KECCAK256 instruction. -pub const KECCAK256WORD: u64 = 6; -/// Gas cost per word for copy operations. -pub const COPY: u64 = 3; -/// Gas cost for BLOCKHASH instruction. -pub const BLOCKHASH: u64 = 20; -/// Gas cost per byte for code deposit during contract creation. -pub const CODEDEPOSIT: u64 = 200; - -/// EIP-1884: Repricing for trie-size-dependent opcodes -pub const ISTANBUL_SLOAD_GAS: u64 = 800; -/// Gas cost for SSTORE when setting a storage slot from zero to non-zero. -pub const SSTORE_SET: u64 = 20000; -/// Gas cost for SSTORE when modifying an existing non-zero storage slot. -pub const SSTORE_RESET: u64 = 5000; -/// Gas refund for SSTORE when clearing a storage slot (setting to zero). -pub const REFUND_SSTORE_CLEARS: i64 = 15000; - -/// The standard cost of calldata token. -pub const STANDARD_TOKEN_COST: u64 = 4; -/// The cost of a non-zero byte in calldata. -pub const NON_ZERO_BYTE_DATA_COST: u64 = 68; -/// The multiplier for a non zero byte in calldata. -pub const NON_ZERO_BYTE_MULTIPLIER: u64 = NON_ZERO_BYTE_DATA_COST / STANDARD_TOKEN_COST; -/// The cost of a non-zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028). -pub const NON_ZERO_BYTE_DATA_COST_ISTANBUL: u64 = 16; -/// The multiplier for a non zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028). -pub const NON_ZERO_BYTE_MULTIPLIER_ISTANBUL: u64 = - NON_ZERO_BYTE_DATA_COST_ISTANBUL / STANDARD_TOKEN_COST; -/// The cost floor per token as defined by EIP-2028. -pub const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; - -/// Gas cost for EOF CREATE instruction. -pub const EOF_CREATE_GAS: u64 = 32000; - -// Berlin EIP-2929/EIP-2930 constants -/// Gas cost for accessing an address in the access list (EIP-2930). -pub const ACCESS_LIST_ADDRESS: u64 = 2400; -/// Gas cost for accessing a storage key in the access list (EIP-2930). -pub const ACCESS_LIST_STORAGE_KEY: u64 = 1900; - -/// Gas cost for SLOAD when accessing a cold storage slot (EIP-2929). -pub const COLD_SLOAD_COST: u64 = 2100; -/// Gas cost for accessing a cold account (EIP-2929). -pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600; -/// Additional gas cost for accessing a cold account. -pub const COLD_ACCOUNT_ACCESS_COST_ADDITIONAL: u64 = - COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST; -/// Gas cost for reading from a warm storage slot (EIP-2929). -pub const WARM_STORAGE_READ_COST: u64 = 100; -/// Gas cost for SSTORE reset operation on a warm storage slot. -pub const WARM_SSTORE_RESET: u64 = SSTORE_RESET - COLD_SLOAD_COST; - -/// EIP-3860 : Limit and meter initcode -pub const INITCODE_WORD_COST: u64 = 2; - -/// Gas stipend provided to the recipient of a CALL with value transfer. -pub const CALL_STIPEND: u64 = 2300; diff --git a/crates/interpreter/src/gas/params.rs b/crates/interpreter/src/gas/params.rs index 77db84955b..8b13789179 100644 --- a/crates/interpreter/src/gas/params.rs +++ b/crates/interpreter/src/gas/params.rs @@ -1,775 +1 @@ -//! Gas table for dynamic gas constants. -use crate::{ - gas::{self, log2floor, ISTANBUL_SLOAD_GAS, SSTORE_RESET, SSTORE_SET, WARM_SSTORE_RESET}, - num_words, -}; -use context_interface::context::SStoreResult; -use primitives::{ - hardfork::SpecId::{self}, - U256, -}; -use std::sync::Arc; - -/// Gas table for dynamic gas constants. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct GasParams { - /// Table of gas costs for operations - table: Arc<[u64; 256]>, - /// Pointer to the table. - ptr: *const u64, -} - -#[cfg(feature = "serde")] -mod serde { - use super::{Arc, GasParams}; - use std::vec::Vec; - - #[derive(serde::Serialize, serde::Deserialize)] - struct GasParamsSerde { - table: Vec, - } - - #[cfg(feature = "serde")] - impl serde::Serialize for GasParams { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - GasParamsSerde { - table: self.table.to_vec(), - } - .serialize(serializer) - } - } - - impl<'de> serde::Deserialize<'de> for GasParams { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let table = GasParamsSerde::deserialize(deserializer)?; - if table.table.len() != 256 { - return Err(serde::de::Error::custom("Invalid gas params length")); - } - Ok(Self::new(Arc::new(table.table.try_into().unwrap()))) - } - } -} - -impl Default for GasParams { - fn default() -> Self { - Self::new_spec(SpecId::default()) - } -} - -impl GasParams { - /// Creates a new `GasParams` with the given table. - #[inline] - pub fn new(table: Arc<[u64; 256]>) -> Self { - Self { - ptr: table.as_ptr(), - table, - } - } - - /// Overrides the gas cost for the given gas id. - /// - /// It will clone underlying table and override the values. - /// - /// Use to override default gas cost - /// - /// ```rust - /// use revm_interpreter::gas::params::{GasParams, GasId}; - /// use primitives::hardfork::SpecId; - /// - /// let mut gas_table = GasParams::new_spec(SpecId::default()); - /// gas_table.override_gas([(GasId::memory_linear_cost(), 2), (GasId::memory_quadratic_reduction(), 512)].into_iter()); - /// assert_eq!(gas_table.get(GasId::memory_linear_cost()), 2); - /// assert_eq!(gas_table.get(GasId::memory_quadratic_reduction()), 512); - /// ``` - pub fn override_gas(&mut self, values: impl IntoIterator) { - let mut table = *self.table.clone(); - for (id, value) in values.into_iter() { - table[id.as_usize()] = value; - } - *self = Self::new(Arc::new(table)); - } - - /// Returns the table. - #[inline] - pub fn table(&self) -> &[u64; 256] { - &self.table - } - - /// Creates a new `GasParams` for the given spec. - #[inline] - pub fn new_spec(spec: SpecId) -> Self { - let mut table = [0; 256]; - - table[GasId::exp_byte_gas().as_usize()] = 10; - table[GasId::logdata().as_usize()] = gas::LOGDATA; - table[GasId::logtopic().as_usize()] = gas::LOGTOPIC; - table[GasId::copy_per_word().as_usize()] = gas::COPY; - table[GasId::extcodecopy_per_word().as_usize()] = gas::COPY; - table[GasId::mcopy_per_word().as_usize()] = gas::COPY; - table[GasId::keccak256_per_word().as_usize()] = gas::KECCAK256WORD; - table[GasId::memory_linear_cost().as_usize()] = gas::MEMORY; - table[GasId::memory_quadratic_reduction().as_usize()] = 512; - table[GasId::initcode_per_word().as_usize()] = gas::INITCODE_WORD_COST; - table[GasId::create().as_usize()] = gas::CREATE; - table[GasId::call_stipend_reduction().as_usize()] = 64; - table[GasId::transfer_value_cost().as_usize()] = gas::CALLVALUE; - table[GasId::cold_account_additional_cost().as_usize()] = 0; - table[GasId::new_account_cost().as_usize()] = gas::NEWACCOUNT; - table[GasId::warm_storage_read_cost().as_usize()] = 0; - // Frontiers had fixed 5k cost. - table[GasId::sstore_static().as_usize()] = SSTORE_RESET; - // SSTORE SET - table[GasId::sstore_set_without_load_cost().as_usize()] = SSTORE_SET - SSTORE_RESET; - // SSTORE RESET Is covered in SSTORE_STATIC. - table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = 0; - // SSTORE CLEARING SLOT REFUND - table[GasId::sstore_clearing_slot_refund().as_usize()] = 15000; - table[GasId::selfdestruct_refund().as_usize()] = 24000; - table[GasId::call_stipend().as_usize()] = gas::CALL_STIPEND; - table[GasId::cold_storage_additional_cost().as_usize()] = 0; - table[GasId::cold_storage_cost().as_usize()] = 0; - table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0; - - if spec.is_enabled_in(SpecId::TANGERINE) { - table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT; - } - - if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) { - table[GasId::exp_byte_gas().as_usize()] = 50; - } - - if spec.is_enabled_in(SpecId::ISTANBUL) { - table[GasId::sstore_static().as_usize()] = gas::ISTANBUL_SLOAD_GAS; - table[GasId::sstore_set_without_load_cost().as_usize()] = - SSTORE_SET - ISTANBUL_SLOAD_GAS; - table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = - SSTORE_RESET - ISTANBUL_SLOAD_GAS; - } - - if spec.is_enabled_in(SpecId::BERLIN) { - table[GasId::sstore_static().as_usize()] = gas::WARM_STORAGE_READ_COST; - table[GasId::cold_account_additional_cost().as_usize()] = - gas::COLD_ACCOUNT_ACCESS_COST_ADDITIONAL; - table[GasId::cold_storage_additional_cost().as_usize()] = - gas::COLD_SLOAD_COST - gas::WARM_STORAGE_READ_COST; - table[GasId::cold_storage_cost().as_usize()] = gas::COLD_SLOAD_COST; - table[GasId::warm_storage_read_cost().as_usize()] = gas::WARM_STORAGE_READ_COST; - - table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = - WARM_SSTORE_RESET - gas::WARM_STORAGE_READ_COST; - table[GasId::sstore_set_without_load_cost().as_usize()] = - SSTORE_SET - gas::WARM_STORAGE_READ_COST; - } - - if spec.is_enabled_in(SpecId::LONDON) { - // EIP-3529: Reduction in refunds - - // Replace SSTORE_CLEARS_SCHEDULE (as defined in EIP-2200) with - // SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST (4,800 gas as of EIP-2929 + EIP-2930) - table[GasId::sstore_clearing_slot_refund().as_usize()] = - WARM_SSTORE_RESET + gas::ACCESS_LIST_STORAGE_KEY; - - table[GasId::selfdestruct_refund().as_usize()] = 0; - } - - Self::new(Arc::new(table)) - } - - /// Gets the gas cost for the given gas id. - #[inline] - pub const fn get(&self, id: GasId) -> u64 { - unsafe { *self.ptr.add(id.as_usize()) } - } - - /// `EXP` opcode cost calculation. - #[inline] - pub fn exp_cost(&self, power: U256) -> u64 { - if power.is_zero() { - return 0; - } - // EIP-160: EXP cost increase - self.get(GasId::exp_byte_gas()) - .saturating_mul(log2floor(power) / 8 + 1) - } - - /// Selfdestruct refund. - #[inline] - pub fn selfdestruct_refund(&self) -> i64 { - self.get(GasId::selfdestruct_refund()) as i64 - } - - /// Selfdestruct cost. - #[inline] - pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 { - let mut gas = 0; - - // EIP-150: Gas cost changes for IO-heavy operations - if should_charge_topup { - gas += self.new_account_cost_for_selfdestruct(); - } - - if is_cold { - // Note: SELFDESTRUCT does not charge a WARM_STORAGE_READ_COST in case the recipient is already warm, - // which differs from how the other call-variants work. The reasoning behind this is to keep - // the changes small, a SELFDESTRUCT already costs 5K and is a no-op if invoked more than once. - // - // For GasParams both values are zero before BERLIN fork. - gas += self.cold_account_additional_cost() + self.warm_storage_read_cost(); - } - gas - } - - /// EXTCODECOPY gas cost - #[inline] - pub fn extcodecopy(&self, len: usize) -> u64 { - self.get(GasId::extcodecopy_per_word()) - .saturating_mul(num_words(len) as u64) - } - - /// MCOPY gas cost - #[inline] - pub fn mcopy_cost(&self, len: usize) -> u64 { - self.get(GasId::mcopy_per_word()) - .saturating_mul(num_words(len) as u64) - } - - /// Static gas cost for SSTORE opcode - #[inline] - pub fn sstore_static_gas(&self) -> u64 { - self.get(GasId::sstore_static()) - } - - /// SSTORE set cost - #[inline] - pub fn sstore_set_without_load_cost(&self) -> u64 { - self.get(GasId::sstore_set_without_load_cost()) - } - - /// SSTORE reset cost - #[inline] - pub fn sstore_reset_without_cold_load_cost(&self) -> u64 { - self.get(GasId::sstore_reset_without_cold_load_cost()) - } - - /// SSTORE clearing slot refund - #[inline] - pub fn sstore_clearing_slot_refund(&self) -> u64 { - self.get(GasId::sstore_clearing_slot_refund()) - } - - /// Dynamic gas cost for SSTORE opcode. - /// - /// Dynamic gas cost is gas that needs input from SSTORE operation to be calculated. - #[inline] - pub fn sstore_dynamic_gas(&self, is_istanbul: bool, vals: &SStoreResult, is_cold: bool) -> u64 { - // frontier logic gets charged for every SSTORE operation if original value is zero. - // this behaviour is fixed in istanbul fork. - if !is_istanbul { - if vals.is_present_zero() && !vals.is_new_zero() { - return self.sstore_set_without_load_cost(); - } else { - return self.sstore_reset_without_cold_load_cost(); - } - } - - let mut gas = 0; - - // this will be zero before berlin fork. - if is_cold { - gas += self.cold_storage_cost(); - } - - // if new values changed present value and present value is unchanged from original. - if vals.new_values_changes_present() && vals.is_original_eq_present() { - gas += if vals.is_original_zero() { - // set cost for creating storage slot (Zero slot means it is not existing). - // and previous condition says present is same as original. - self.sstore_set_without_load_cost() - } else { - // if new value is not zero, this means we are setting some value to it. - self.sstore_reset_without_cold_load_cost() - }; - } - gas - } - - /// SSTORE refund calculation. - #[inline] - pub fn sstore_refund(&self, is_istanbul: bool, vals: &SStoreResult) -> i64 { - // EIP-3529: Reduction in refunds - let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64; - - if !is_istanbul { - // // before istanbul fork, refund was always awarded without checking original state. - if !vals.is_present_zero() && vals.is_new_zero() { - return sstore_clearing_slot_refund; - } - return 0; - } - - // If current value equals new value (this is a no-op) - if vals.is_new_eq_present() { - return 0; - } - - // refund for the clearing of storage slot. - // As new is not equal to present, new values zero means that original and present values are not zero - if vals.is_original_eq_present() && vals.is_new_zero() { - return sstore_clearing_slot_refund; - } - - let mut refund = 0; - // If original value is not 0 - if !vals.is_original_zero() { - // If current value is 0 (also means that new value is not 0), - if vals.is_present_zero() { - // remove SSTORE_CLEARS_SCHEDULE gas from refund counter. - refund -= sstore_clearing_slot_refund; - // If new value is 0 (also means that current value is not 0), - } else if vals.is_new_zero() { - // add SSTORE_CLEARS_SCHEDULE gas to refund counter. - refund += sstore_clearing_slot_refund; - } - } - - // If original value equals new value (this storage slot is reset) - if vals.is_original_eq_new() { - // If original value is 0 - if vals.is_original_zero() { - // add SSTORE_SET_GAS - SLOAD_GAS to refund counter. - refund += self.sstore_set_without_load_cost() as i64; - // Otherwise - } else { - // add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. - refund += self.sstore_reset_without_cold_load_cost() as i64; - } - } - refund - } - - /// `LOG` opcode cost calculation. - #[inline] - pub const fn log_cost(&self, n: u8, len: u64) -> u64 { - self.get(GasId::logdata()) - .saturating_mul(len) - .saturating_add(self.get(GasId::logtopic()) * n as u64) - } - - /// KECCAK256 gas cost per word - #[inline] - pub fn keccak256_cost(&self, len: usize) -> u64 { - self.get(GasId::keccak256_per_word()) - .saturating_mul(num_words(len) as u64) - } - - /// Memory gas cost - #[inline] - pub fn memory_cost(&self, len: usize) -> u64 { - let len = len as u64; - self.get(GasId::memory_linear_cost()) - .saturating_mul(len) - .saturating_add( - (len.saturating_mul(len)) - .saturating_div(self.get(GasId::memory_quadratic_reduction())), - ) - } - - /// Initcode word cost - #[inline] - pub fn initcode_cost(&self, len: usize) -> u64 { - self.get(GasId::initcode_per_word()) - .saturating_mul(num_words(len) as u64) - } - - /// Create gas cost - #[inline] - pub fn create_cost(&self) -> u64 { - self.get(GasId::create()) - } - - /// Create2 gas cost. - #[inline] - pub fn create2_cost(&self, len: usize) -> u64 { - self.get(GasId::create()).saturating_add( - self.get(GasId::keccak256_per_word()) - .saturating_mul(num_words(len) as u64), - ) - } - - /// Call stipend. - #[inline] - pub fn call_stipend(&self) -> u64 { - self.get(GasId::call_stipend()) - } - - /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit. - #[inline] - pub fn call_stipend_reduction(&self, gas_limit: u64) -> u64 { - gas_limit - gas_limit / self.get(GasId::call_stipend_reduction()) - } - - /// Transfer value cost - #[inline] - pub fn transfer_value_cost(&self) -> u64 { - self.get(GasId::transfer_value_cost()) - } - - /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded. - #[inline] - pub fn cold_account_additional_cost(&self) -> u64 { - self.get(GasId::cold_account_additional_cost()) - } - - /// Cold storage additional cost. - #[inline] - pub fn cold_storage_additional_cost(&self) -> u64 { - self.get(GasId::cold_storage_additional_cost()) - } - - /// Cold storage cost. - #[inline] - pub fn cold_storage_cost(&self) -> u64 { - self.get(GasId::cold_storage_cost()) - } - - /// New account cost. New account cost is added to the gas cost if the account is empty. - #[inline] - pub fn new_account_cost(&self, is_spurious_dragon: bool, transfers_value: bool) -> u64 { - // EIP-161: State trie clearing (invariant-preserving alternative) - // Pre-Spurious Dragon: always charge for new account - // Post-Spurious Dragon: only charge if value is transferred - if !is_spurious_dragon || transfers_value { - return self.get(GasId::new_account_cost()); - } - 0 - } - - /// New account cost for selfdestruct. - #[inline] - pub fn new_account_cost_for_selfdestruct(&self) -> u64 { - self.get(GasId::new_account_cost_for_selfdestruct()) - } - - /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded. - #[inline] - pub fn warm_storage_read_cost(&self) -> u64 { - self.get(GasId::warm_storage_read_cost()) - } - - /// Copy cost - #[inline] - pub fn copy_cost(&self, len: usize) -> u64 { - self.copy_per_word_cost(num_words(len)) - } - - /// Copy per word cost - #[inline] - pub fn copy_per_word_cost(&self, word_num: usize) -> u64 { - self.get(GasId::copy_per_word()) - .saturating_mul(word_num as u64) - } -} - -/// Gas identifier that maps onto index in gas table. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct GasId(u8); - -impl GasId { - /// Creates a new `GasId` with the given id. - pub const fn new(id: u8) -> Self { - Self(id) - } - - /// Returns the id of the gas. - pub const fn as_u8(&self) -> u8 { - self.0 - } - - /// Returns the id of the gas as a usize. - pub const fn as_usize(&self) -> usize { - self.0 as usize - } - - /// Returns the name of the gas identifier as a string. - /// - /// # Examples - /// - /// ``` - /// use revm_interpreter::gas::params::GasId; - /// - /// assert_eq!(GasId::exp_byte_gas().name(), "exp_byte_gas"); - /// assert_eq!(GasId::memory_linear_cost().name(), "memory_linear_cost"); - /// assert_eq!(GasId::sstore_static().name(), "sstore_static"); - /// ``` - pub const fn name(&self) -> &'static str { - match self.0 { - x if x == Self::exp_byte_gas().as_u8() => "exp_byte_gas", - x if x == Self::extcodecopy_per_word().as_u8() => "extcodecopy_per_word", - x if x == Self::copy_per_word().as_u8() => "copy_per_word", - x if x == Self::logdata().as_u8() => "logdata", - x if x == Self::logtopic().as_u8() => "logtopic", - x if x == Self::mcopy_per_word().as_u8() => "mcopy_per_word", - x if x == Self::keccak256_per_word().as_u8() => "keccak256_per_word", - x if x == Self::memory_linear_cost().as_u8() => "memory_linear_cost", - x if x == Self::memory_quadratic_reduction().as_u8() => "memory_quadratic_reduction", - x if x == Self::initcode_per_word().as_u8() => "initcode_per_word", - x if x == Self::create().as_u8() => "create", - x if x == Self::call_stipend_reduction().as_u8() => "call_stipend_reduction", - x if x == Self::transfer_value_cost().as_u8() => "transfer_value_cost", - x if x == Self::cold_account_additional_cost().as_u8() => { - "cold_account_additional_cost" - } - x if x == Self::new_account_cost().as_u8() => "new_account_cost", - x if x == Self::warm_storage_read_cost().as_u8() => "warm_storage_read_cost", - x if x == Self::sstore_static().as_u8() => "sstore_static", - x if x == Self::sstore_set_without_load_cost().as_u8() => { - "sstore_set_without_load_cost" - } - x if x == Self::sstore_reset_without_cold_load_cost().as_u8() => { - "sstore_reset_without_cold_load_cost" - } - x if x == Self::sstore_clearing_slot_refund().as_u8() => "sstore_clearing_slot_refund", - x if x == Self::selfdestruct_refund().as_u8() => "selfdestruct_refund", - x if x == Self::call_stipend().as_u8() => "call_stipend", - x if x == Self::cold_storage_additional_cost().as_u8() => { - "cold_storage_additional_cost" - } - x if x == Self::cold_storage_cost().as_u8() => "cold_storage_cost", - x if x == Self::new_account_cost_for_selfdestruct().as_u8() => { - "new_account_cost_for_selfdestruct" - } - _ => "unknown", - } - } - - /// Converts a string to a `GasId`. - /// - /// Returns `None` if the string does not match any known gas identifier. - /// - /// # Examples - /// - /// ``` - /// use revm_interpreter::gas::params::GasId; - /// - /// assert_eq!(GasId::from_name("exp_byte_gas"), Some(GasId::exp_byte_gas())); - /// assert_eq!(GasId::from_name("memory_linear_cost"), Some(GasId::memory_linear_cost())); - /// assert_eq!(GasId::from_name("invalid_name"), None); - /// ``` - pub fn from_name(s: &str) -> Option { - match s { - "exp_byte_gas" => Some(Self::exp_byte_gas()), - "extcodecopy_per_word" => Some(Self::extcodecopy_per_word()), - "copy_per_word" => Some(Self::copy_per_word()), - "logdata" => Some(Self::logdata()), - "logtopic" => Some(Self::logtopic()), - "mcopy_per_word" => Some(Self::mcopy_per_word()), - "keccak256_per_word" => Some(Self::keccak256_per_word()), - "memory_linear_cost" => Some(Self::memory_linear_cost()), - "memory_quadratic_reduction" => Some(Self::memory_quadratic_reduction()), - "initcode_per_word" => Some(Self::initcode_per_word()), - "create" => Some(Self::create()), - "call_stipend_reduction" => Some(Self::call_stipend_reduction()), - "transfer_value_cost" => Some(Self::transfer_value_cost()), - "cold_account_additional_cost" => Some(Self::cold_account_additional_cost()), - "new_account_cost" => Some(Self::new_account_cost()), - "warm_storage_read_cost" => Some(Self::warm_storage_read_cost()), - "sstore_static" => Some(Self::sstore_static()), - "sstore_set_without_load_cost" => Some(Self::sstore_set_without_load_cost()), - "sstore_reset_without_cold_load_cost" => { - Some(Self::sstore_reset_without_cold_load_cost()) - } - "sstore_clearing_slot_refund" => Some(Self::sstore_clearing_slot_refund()), - "selfdestruct_refund" => Some(Self::selfdestruct_refund()), - "call_stipend" => Some(Self::call_stipend()), - "cold_storage_additional_cost" => Some(Self::cold_storage_additional_cost()), - "cold_storage_cost" => Some(Self::cold_storage_cost()), - "new_account_cost_for_selfdestruct" => Some(Self::new_account_cost_for_selfdestruct()), - _ => None, - } - } - - /// EXP gas cost per byte - pub const fn exp_byte_gas() -> GasId { - Self::new(1) - } - - /// EXTCODECOPY gas cost per word - pub const fn extcodecopy_per_word() -> GasId { - Self::new(2) - } - - /// Copy copy per word - pub const fn copy_per_word() -> GasId { - Self::new(3) - } - - /// Log data gas cost per byte - pub const fn logdata() -> GasId { - Self::new(4) - } - - /// Log topic gas cost per topic - pub const fn logtopic() -> GasId { - Self::new(5) - } - - /// MCOPY gas cost per word - pub const fn mcopy_per_word() -> GasId { - Self::new(6) - } - - /// KECCAK256 gas cost per word - pub const fn keccak256_per_word() -> GasId { - Self::new(7) - } - - /// Memory linear cost. Memory is additionally added as n*linear_cost. - pub const fn memory_linear_cost() -> GasId { - Self::new(8) - } - - /// Memory quadratic reduction. Memory is additionally added as n*n/quadratic_reduction. - pub const fn memory_quadratic_reduction() -> GasId { - Self::new(9) - } - - /// Initcode word cost - pub const fn initcode_per_word() -> GasId { - Self::new(10) - } - - /// Create gas cost - pub const fn create() -> GasId { - Self::new(11) - } - - /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit. - pub const fn call_stipend_reduction() -> GasId { - Self::new(12) - } - - /// Transfer value cost - pub const fn transfer_value_cost() -> GasId { - Self::new(13) - } - - /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded. - pub const fn cold_account_additional_cost() -> GasId { - Self::new(14) - } - - /// New account cost. New account cost is added to the gas cost if the account is empty. - pub const fn new_account_cost() -> GasId { - Self::new(15) - } - - /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded. - /// - /// Used in delegated account access to specify delegated account warm gas cost. - pub const fn warm_storage_read_cost() -> GasId { - Self::new(16) - } - - /// Static gas cost for SSTORE opcode. This gas in comparison with other gas const needs - /// to be deducted after check for minimal stipend gas cost. This is a reason why it is here. - pub const fn sstore_static() -> GasId { - Self::new(17) - } - - /// SSTORE set cost additional amount after SSTORE_RESET is added. - pub const fn sstore_set_without_load_cost() -> GasId { - Self::new(18) - } - - /// SSTORE reset cost - pub const fn sstore_reset_without_cold_load_cost() -> GasId { - Self::new(19) - } - - /// SSTORE clearing slot refund - pub const fn sstore_clearing_slot_refund() -> GasId { - Self::new(20) - } - - /// Selfdestruct refund. - pub const fn selfdestruct_refund() -> GasId { - Self::new(21) - } - - /// Call stipend checked in sstore. - pub const fn call_stipend() -> GasId { - Self::new(22) - } - - /// Cold storage additional cost. - pub const fn cold_storage_additional_cost() -> GasId { - Self::new(23) - } - - /// Cold storage cost - pub const fn cold_storage_cost() -> GasId { - Self::new(24) - } - - /// New account cost for selfdestruct. - pub const fn new_account_cost_for_selfdestruct() -> GasId { - Self::new(25) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::HashSet; - - #[test] - fn test_gas_id_name_and_from_str_coverage() { - let mut unique_names = HashSet::new(); - let mut known_gas_ids = 0; - - // Iterate over all possible GasId values (0..256) - for i in 0..=255 { - let gas_id = GasId::new(i); - let name = gas_id.name(); - - // Count unique names (excluding "unknown") - if name != "unknown" { - unique_names.insert(name); - } - } - - // Now test from_str for each unique name - for name in &unique_names { - if let Some(gas_id) = GasId::from_name(name) { - known_gas_ids += 1; - // Verify round-trip: name -> GasId -> name should be consistent - assert_eq!(gas_id.name(), *name, "Round-trip failed for {}", name); - } - } - - println!("Total unique named GasIds: {}", unique_names.len()); - println!("GasIds resolvable via from_str: {}", known_gas_ids); - - // All unique names should be resolvable via from_str - assert_eq!( - unique_names.len(), - known_gas_ids, - "Not all unique names are resolvable via from_str" - ); - - // We should have exactly 25 known GasIds (based on the indices 1-25 used) - assert_eq!( - unique_names.len(), - 25, - "Expected 25 unique GasIds, found {}", - unique_names.len() - ); - } -} diff --git a/crates/interpreter/src/instructions.rs b/crates/interpreter/src/instructions.rs index cae338c29f..969805b583 100644 --- a/crates/interpreter/src/instructions.rs +++ b/crates/interpreter/src/instructions.rs @@ -27,9 +27,10 @@ pub mod tx_info; /// Utility functions and helpers for instruction implementation. pub mod utility; -use primitives::hardfork::SpecId; +pub use context_interface::cfg::gas::{self, *}; -use crate::{gas, interpreter_types::InterpreterTypes, Host, InstructionContext}; +use crate::{interpreter_types::InterpreterTypes, Host, InstructionContext}; +use primitives::hardfork::SpecId; /// EVM opcode function signature. #[derive(Debug)] diff --git a/crates/interpreter/src/instructions/arithmetic.rs b/crates/interpreter/src/instructions/arithmetic.rs index 024938ad8d..9692adce74 100644 --- a/crates/interpreter/src/instructions/arithmetic.rs +++ b/crates/interpreter/src/instructions/arithmetic.rs @@ -3,6 +3,7 @@ use crate::{ interpreter_types::{InterpreterTypes, StackTr}, InstructionContext, }; +use context_interface::Host; use primitives::U256; /// Implements the ADD instruction - adds two values from stack. @@ -74,11 +75,11 @@ pub fn mulmod(context: InstructionContext<'_, } /// Implements the EXP instruction - exponentiates two values from stack. -pub fn exp(context: InstructionContext<'_, H, WIRE>) { +pub fn exp(context: InstructionContext<'_, H, WIRE>) { popn_top!([op1], op2, context.interpreter); gas!( context.interpreter, - context.interpreter.gas_params.exp_cost(*op2) + context.host.gas_params().exp_cost(*op2) ); *op2 = op1.pow(*op2); } diff --git a/crates/interpreter/src/instructions/bitwise.rs b/crates/interpreter/src/instructions/bitwise.rs index 170fbe56bd..82551d24c3 100644 --- a/crates/interpreter/src/instructions/bitwise.rs +++ b/crates/interpreter/src/instructions/bitwise.rs @@ -225,7 +225,7 @@ mod tests { push!(interpreter, test.value); push!(interpreter, test.shift); let context = InstructionContext { - host: &mut DummyHost, + host: &mut DummyHost::default(), interpreter: &mut interpreter, }; shl(context); @@ -308,7 +308,7 @@ mod tests { push!(interpreter, test.value); push!(interpreter, test.shift); let context = InstructionContext { - host: &mut DummyHost, + host: &mut DummyHost::default(), interpreter: &mut interpreter, }; shr(context); @@ -416,7 +416,7 @@ mod tests { push!(interpreter, test.value); push!(interpreter, test.shift); let context = InstructionContext { - host: &mut DummyHost, + host: &mut DummyHost::default(), interpreter: &mut interpreter, }; sar(context); @@ -454,7 +454,7 @@ mod tests { push!(interpreter, test.input); push!(interpreter, U256::from(test.index)); let context = InstructionContext { - host: &mut DummyHost, + host: &mut DummyHost::default(), interpreter: &mut interpreter, }; byte(context); @@ -466,7 +466,7 @@ mod tests { #[test] fn test_clz() { let mut interpreter = Interpreter::default(); - interpreter.set_spec_id(SpecId::OSAKA); + let mut host = DummyHost::new(SpecId::OSAKA); struct TestCase { value: U256, @@ -507,7 +507,7 @@ mod tests { for test in test_cases { push!(interpreter, test.value); let context = InstructionContext { - host: &mut DummyHost, + host: &mut host, interpreter: &mut interpreter, }; clz(context); diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index dbcefcc426..b7a7cc9927 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -52,12 +52,17 @@ pub fn create( } gas!( context.interpreter, - context.interpreter.gas_params.initcode_cost(len) + context.host.gas_params().initcode_cost(len) ); } let code_offset = as_usize_or_fail!(context.interpreter, code_offset); - resize_memory!(context.interpreter, code_offset, len); + resize_memory!( + context.interpreter, + context.host.gas_params(), + code_offset, + len + ); code = Bytes::copy_from_slice( context @@ -74,14 +79,11 @@ pub fn create( // SAFETY: `len` is reasonable in size as gas for it is already deducted. gas!( context.interpreter, - context.interpreter.gas_params.create2_cost(len) + context.host.gas_params().create2_cost(len) ); CreateScheme::Create2 { salt } } else { - gas!( - context.interpreter, - context.interpreter.gas_params.create_cost() - ); + gas!(context.interpreter, context.host.gas_params().create_cost()); CreateScheme::Create }; @@ -95,10 +97,7 @@ pub fn create( .is_enabled_in(SpecId::TANGERINE) { // Take remaining gas and deduce l64 part of it. - gas_limit = context - .interpreter - .gas_params - .call_stipend_reduction(gas_limit); + gas_limit = context.host.gas_params().call_stipend_reduction(gas_limit); } gas!(context.interpreter, gas_limit); @@ -136,7 +135,8 @@ pub fn call( return; } - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) + let Some((input, return_memory_offset)) = + get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params()) else { return; }; @@ -179,7 +179,8 @@ pub fn call_code( let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); let has_transfer = !value.is_zero(); - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) + let Some((input, return_memory_offset)) = + get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params()) else { return; }; @@ -222,7 +223,8 @@ pub fn delegate_call( // Max gas limit is not possible in real ethereum situation. let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) + let Some((input, return_memory_offset)) = + get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params()) else { return; }; @@ -265,7 +267,8 @@ pub fn static_call( // Max gas limit is not possible in real ethereum situation. let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) + let Some((input, return_memory_offset)) = + get_memory_input_and_out_ranges(context.interpreter, context.host.gas_params()) else { return; }; diff --git a/crates/interpreter/src/instructions/contract/call_helpers.rs b/crates/interpreter/src/instructions/contract/call_helpers.rs index f58ad3ae7a..47d49305fc 100644 --- a/crates/interpreter/src/instructions/contract/call_helpers.rs +++ b/crates/interpreter/src/instructions/contract/call_helpers.rs @@ -1,10 +1,9 @@ use crate::{ - gas::params::GasParams, interpreter::Interpreter, interpreter_types::{InterpreterTypes, MemoryTr, RuntimeFlag, StackTr}, InstructionContext, }; -use context_interface::{host::LoadError, Host}; +use context_interface::{cfg::GasParams, host::LoadError, Host}; use core::{cmp::min, ops::Range}; use primitives::{ hardfork::SpecId::{self, *}, @@ -16,17 +15,18 @@ use state::Bytecode; #[inline] pub fn get_memory_input_and_out_ranges( interpreter: &mut Interpreter, + gas_params: &GasParams, ) -> Option<(Range, Range)> { popn!([in_offset, in_len, out_offset, out_len], interpreter, None); - let mut in_range = resize_memory(interpreter, in_offset, in_len)?; + let mut in_range = resize_memory(interpreter, gas_params, in_offset, in_len)?; if !in_range.is_empty() { let offset = interpreter.memory.local_memory_offset(); in_range = in_range.start.saturating_add(offset)..in_range.end.saturating_add(offset); } - let ret_range = resize_memory(interpreter, out_offset, out_len)?; + let ret_range = resize_memory(interpreter, gas_params, out_offset, out_len)?; Some((in_range, ret_range)) } @@ -35,13 +35,14 @@ pub fn get_memory_input_and_out_ranges( #[inline] pub fn resize_memory( interpreter: &mut Interpreter, + gas_params: &GasParams, offset: U256, len: U256, ) -> Option> { let len = as_usize_or_fail_ret!(interpreter, len, None); let offset = if len != 0 { let offset = as_usize_or_fail_ret!(interpreter, offset, None); - resize_memory!(interpreter, offset, len, None); + resize_memory!(interpreter, gas_params, offset, len, None); offset } else { usize::MAX //unrealistic value so we are sure it is not used @@ -62,7 +63,7 @@ pub fn load_acc_and_calc_gas( if transfers_value { gas!( context.interpreter, - context.interpreter.gas_params.transfer_value_cost(), + context.host.gas_params().transfer_value_cost(), None ); } @@ -76,12 +77,13 @@ pub fn load_acc_and_calc_gas( gas!(interpreter, gas, None); let interpreter = &mut context.interpreter; + let host = &mut context.host; // EIP-150: Gas cost changes for IO-heavy operations let mut gas_limit = if interpreter.runtime_flag.spec_id().is_enabled_in(TANGERINE) { // On mainnet this will take return 63/64 of gas_limit. - let reduced_gas_limit = interpreter - .gas_params + let reduced_gas_limit = host + .gas_params() .call_stipend_reduction(interpreter.gas.remaining()); min(reduced_gas_limit, stack_gas_limit) } else { @@ -91,7 +93,7 @@ pub fn load_acc_and_calc_gas( // Add call stipend if there is value to be transferred. if transfers_value { - gas_limit = gas_limit.saturating_add(interpreter.gas_params.call_stipend()); + gas_limit = gas_limit.saturating_add(host.gas_params().call_stipend()); } Some((gas_limit, bytecode, code_hash)) @@ -107,10 +109,8 @@ pub fn load_account_delegated_handle_error( ) -> Option<(u64, Bytecode, B256)> { // move this to static gas. let remaining_gas = context.interpreter.gas.remaining(); - let gas_table = &context.interpreter.gas_params; match load_account_delegated( context.host, - gas_table, context.interpreter.runtime_flag.spec_id(), remaining_gas, to, @@ -134,7 +134,6 @@ pub fn load_account_delegated_handle_error( #[inline] pub fn load_account_delegated( host: &mut H, - gas_table: &GasParams, spec: SpecId, remaining_gas: u64, address: Address, @@ -145,7 +144,8 @@ pub fn load_account_delegated( let is_berlin = spec.is_enabled_in(SpecId::BERLIN); let is_spurious_dragon = spec.is_enabled_in(SpecId::SPURIOUS_DRAGON); - let additional_cold_cost = gas_table.cold_account_additional_cost(); + let additional_cold_cost = host.gas_params().cold_account_additional_cost(); + let warm_storage_read_cost = host.gas_params().warm_storage_read_cost(); let skip_cold_load = is_berlin && remaining_gas < additional_cold_cost; let account = host.load_account_info_skip_cold_load(address, true, skip_cold_load)?; @@ -156,14 +156,16 @@ pub fn load_account_delegated( let mut code_hash = account.code_hash(); // New account cost, as account is empty there is no delegated account and we can return early. if create_empty_account && account.is_empty { - cost += gas_table.new_account_cost(is_spurious_dragon, transfers_value); + cost += host + .gas_params() + .new_account_cost(is_spurious_dragon, transfers_value); return Ok((cost, bytecode, code_hash)); } // load delegate code if account is EIP-7702 if let Some(Bytecode::Eip7702(code)) = &account.code { // EIP-7702 is enabled after berlin hardfork. - cost += gas_table.warm_storage_read_cost(); + cost += warm_storage_read_cost; if cost > remaining_gas { return Err(LoadError::ColdLoadSkipped); } diff --git a/crates/interpreter/src/instructions/control.rs b/crates/interpreter/src/instructions/control.rs index 32ff53ca81..bb85393b6b 100644 --- a/crates/interpreter/src/instructions/control.rs +++ b/crates/interpreter/src/instructions/control.rs @@ -3,6 +3,7 @@ use crate::{ interpreter_types::{InterpreterTypes, Jumps, LoopControl, MemoryTr, RuntimeFlag, StackTr}, InstructionResult, InterpreterAction, }; +use context_interface::{cfg::GasParams, Host}; use primitives::{Bytes, U256}; use crate::InstructionContext; @@ -61,6 +62,7 @@ pub fn pc(context: InstructionContext<'_, H, /// Handles memory data retrieval and sets the return action. fn return_inner( interpreter: &mut Interpreter, + gas_params: &GasParams, instruction_result: InstructionResult, ) { popn!([offset, len], interpreter); @@ -69,7 +71,7 @@ fn return_inner( let mut output = Bytes::default(); if len != 0 { let offset = as_usize_or_fail!(interpreter, offset); - if !interpreter.resize_memory(offset, len) { + if !interpreter.resize_memory(gas_params, offset, len) { return; } output = interpreter.memory.slice_len(offset, len).to_vec().into() @@ -87,14 +89,22 @@ fn return_inner( /// Implements the RETURN instruction. /// /// Halts execution and returns data from memory. -pub fn ret(context: InstructionContext<'_, H, WIRE>) { - return_inner(context.interpreter, InstructionResult::Return); +pub fn ret(context: InstructionContext<'_, H, WIRE>) { + return_inner( + context.interpreter, + context.host.gas_params(), + InstructionResult::Return, + ); } /// EIP-140: REVERT instruction -pub fn revert(context: InstructionContext<'_, H, WIRE>) { +pub fn revert(context: InstructionContext<'_, H, WIRE>) { check!(context.interpreter, BYZANTIUM); - return_inner(context.interpreter, InstructionResult::Revert); + return_inner( + context.interpreter, + context.host.gas_params(), + InstructionResult::Revert, + ); } /// Stop opcode. This opcode halts the execution. diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index 7324c39802..c82fcf5a27 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -114,13 +114,14 @@ pub fn extcodecopy( context.interpreter ); let address = address.into_address(); + let u256 = address.into_word(); let spec_id = context.interpreter.runtime_flag.spec_id(); let len = as_usize_or_fail!(context.interpreter, len_u256); gas!( context.interpreter, - context.interpreter.gas_params.extcodecopy(len) + context.host.gas_params().extcodecopy(len) ); let mut memory_offset_usize = 0; @@ -129,7 +130,12 @@ pub fn extcodecopy( // fail on casting of memory_offset only if len is not zero. memory_offset_usize = as_usize_or_fail!(context.interpreter, memory_offset); // Resize memory to fit the code - resize_memory!(context.interpreter, memory_offset_usize, len); + resize_memory!( + context.interpreter, + context.host.gas_params(), + memory_offset_usize, + len + ); } let code = if spec_id.is_enabled_in(BERLIN) { @@ -195,10 +201,7 @@ pub fn sload(context: InstructionConte let target = context.interpreter.input.target_address(); if spec_id.is_enabled_in(BERLIN) { - let additional_cold_cost = context - .interpreter - .gas_params - .cold_storage_additional_cost(); + let additional_cold_cost = context.host.gas_params().cold_storage_additional_cost(); let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost; let res = context.host.sload_skip_cold_load(target, *index, skip_cold); match res { @@ -233,7 +236,7 @@ pub fn sstore(context: InstructionCont // EIP-2200: Structured Definitions for Net Gas Metering // If gasleft is less than or equal to gas stipend, fail the current call frame with ‘out of gas’ exception. if spec_id.is_enabled_in(ISTANBUL) - && context.interpreter.gas.remaining() <= context.interpreter.gas_params.call_stipend() + && context.interpreter.gas.remaining() <= context.host.gas_params().call_stipend() { context .interpreter @@ -243,14 +246,11 @@ pub fn sstore(context: InstructionCont gas!( context.interpreter, - context.interpreter.gas_params.sstore_static_gas() + context.host.gas_params().sstore_static_gas() ); let state_load = if spec_id.is_enabled_in(BERLIN) { - let additional_cold_cost = context - .interpreter - .gas_params - .cold_storage_additional_cost(); + let additional_cold_cost = context.host.gas_params().cold_storage_additional_cost(); let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost; let res = context .host @@ -272,7 +272,7 @@ pub fn sstore(context: InstructionCont // dynamic gas gas!( context.interpreter, - context.interpreter.gas_params.sstore_dynamic_gas( + context.host.gas_params().sstore_dynamic_gas( is_istanbul, &state_load.data, state_load.is_cold @@ -282,8 +282,8 @@ pub fn sstore(context: InstructionCont // refund context.interpreter.gas.record_refund( context - .interpreter - .gas_params + .host + .gas_params() .sstore_refund(is_istanbul, &state_load.data), ); } @@ -323,14 +323,14 @@ pub fn log( let len = as_usize_or_fail!(context.interpreter, len); gas!( context.interpreter, - context.interpreter.gas_params.log_cost(N as u8, len as u64) + context.host.gas_params().log_cost(N as u8, len as u64) ); let data = if len == 0 { Bytes::new() } else { let offset = as_usize_or_fail!(context.interpreter, offset); // Resize memory to fit the data - resize_memory!(context.interpreter, offset, len); + resize_memory!(context.interpreter, context.host.gas_params(), offset, len); Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref()) }; let Some(topics) = context.interpreter.stack.popn::() else { @@ -358,10 +358,7 @@ pub fn selfdestruct( let target = target.into_address(); let spec = context.interpreter.runtime_flag.spec_id(); - let cold_load_gas = context - .interpreter - .gas_params - .cold_account_additional_cost(); + let cold_load_gas = context.host.gas_params().cold_account_additional_cost(); let skip_cold_load = context.interpreter.gas.remaining() < cold_load_gas; let res = match context.host.selfdestruct( @@ -384,8 +381,8 @@ pub fn selfdestruct( gas!( context.interpreter, context - .interpreter - .gas_params + .host + .gas_params() .selfdestruct_cost(should_charge_topup, res.is_cold) ); @@ -393,7 +390,7 @@ pub fn selfdestruct( context .interpreter .gas - .record_refund(context.interpreter.gas_params.selfdestruct_refund()); + .record_refund(context.host.gas_params().selfdestruct_refund()); } context.interpreter.halt(InstructionResult::SelfDestruct); diff --git a/crates/interpreter/src/instructions/macros.rs b/crates/interpreter/src/instructions/macros.rs index 30e30fd949..a45648a52f 100644 --- a/crates/interpreter/src/instructions/macros.rs +++ b/crates/interpreter/src/instructions/macros.rs @@ -51,10 +51,7 @@ macro_rules! berlin_load_account { $crate::berlin_load_account!($context, $address, $load_code, ()) }; ($context:expr, $address:expr, $load_code:expr, $ret:expr) => {{ - let cold_load_gas = $context - .interpreter - .gas_params - .cold_account_additional_cost(); + let cold_load_gas = $context.host.gas_params().cold_account_additional_cost(); let skip_cold_load = $context.interpreter.gas.remaining() < cold_load_gas; match $context .host @@ -83,14 +80,14 @@ macro_rules! berlin_load_account { #[macro_export] #[collapse_debuginfo(yes)] macro_rules! resize_memory { - ($interpreter:expr, $offset:expr, $len:expr) => { - $crate::resize_memory!($interpreter, $offset, $len, ()) + ($interpreter:expr, $gas_params:expr, $offset:expr, $len:expr) => { + $crate::resize_memory!($interpreter, $gas_params, $offset, $len, ()) }; - ($interpreter:expr, $offset:expr, $len:expr, $ret:expr) => { + ($interpreter:expr, $gas_params:expr, $offset:expr, $len:expr, $ret:expr) => { if let Err(result) = $crate::interpreter::resize_memory( &mut $interpreter.gas, &mut $interpreter.memory, - &$interpreter.gas_params, + $gas_params, $offset, $len, ) { diff --git a/crates/interpreter/src/instructions/memory.rs b/crates/interpreter/src/instructions/memory.rs index 81d7e0c46a..f6856fc634 100644 --- a/crates/interpreter/src/instructions/memory.rs +++ b/crates/interpreter/src/instructions/memory.rs @@ -1,4 +1,5 @@ use crate::interpreter_types::{InterpreterTypes, MemoryTr, RuntimeFlag, StackTr}; +use context_interface::Host; use core::cmp::max; use primitives::U256; @@ -7,10 +8,10 @@ use crate::InstructionContext; /// Implements the MLOAD instruction. /// /// Loads a 32-byte word from memory. -pub fn mload(context: InstructionContext<'_, H, WIRE>) { +pub fn mload(context: InstructionContext<'_, H, WIRE>) { popn_top!([], top, context.interpreter); let offset = as_usize_or_fail!(context.interpreter, top); - resize_memory!(context.interpreter, offset, 32); + resize_memory!(context.interpreter, context.host.gas_params(), offset, 32); *top = U256::try_from_be_slice(context.interpreter.memory.slice_len(offset, 32).as_ref()).unwrap() } @@ -18,10 +19,10 @@ pub fn mload(context: InstructionContext<'_, /// Implements the MSTORE instruction. /// /// Stores a 32-byte word to memory. -pub fn mstore(context: InstructionContext<'_, H, WIRE>) { +pub fn mstore(context: InstructionContext<'_, H, WIRE>) { popn!([offset, value], context.interpreter); let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, 32); + resize_memory!(context.interpreter, context.host.gas_params(), offset, 32); context .interpreter .memory @@ -31,10 +32,10 @@ pub fn mstore(context: InstructionContext<'_, /// Implements the MSTORE8 instruction. /// /// Stores a single byte to memory. -pub fn mstore8(context: InstructionContext<'_, H, WIRE>) { +pub fn mstore8(context: InstructionContext<'_, H, WIRE>) { popn!([offset, value], context.interpreter); let offset = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, offset, 1); + resize_memory!(context.interpreter, context.host.gas_params(), offset, 1); context.interpreter.memory.set(offset, &[value.byte(0)]); } @@ -51,7 +52,7 @@ pub fn msize(context: InstructionContext<'_, /// Implements the MCOPY instruction. /// /// EIP-5656: Memory copying instruction that copies memory from one location to another. -pub fn mcopy(context: InstructionContext<'_, H, WIRE>) { +pub fn mcopy(context: InstructionContext<'_, H, WIRE>) { check!(context.interpreter, CANCUN); popn!([dst, src, len], context.interpreter); @@ -60,7 +61,7 @@ pub fn mcopy(context: InstructionContext<'_, // Deduce gas gas!( context.interpreter, - context.interpreter.gas_params.mcopy_cost(len) + context.host.gas_params().mcopy_cost(len) ); if len == 0 { @@ -70,7 +71,12 @@ pub fn mcopy(context: InstructionContext<'_, let dst = as_usize_or_fail!(context.interpreter, dst); let src = as_usize_or_fail!(context.interpreter, src); // Resize memory - resize_memory!(context.interpreter, max(dst, src), len); + resize_memory!( + context.interpreter, + context.host.gas_params(), + max(dst, src), + len + ); // Copy memory in place context.interpreter.memory.copy(dst, src, len); } diff --git a/crates/interpreter/src/instructions/system.rs b/crates/interpreter/src/instructions/system.rs index 678dd7bf6a..1ad3bf86cc 100644 --- a/crates/interpreter/src/instructions/system.rs +++ b/crates/interpreter/src/instructions/system.rs @@ -5,6 +5,7 @@ use crate::{ }, CallInput, InstructionResult, }; +use context_interface::{cfg::GasParams, Host}; use core::ptr; use primitives::{B256, KECCAK_EMPTY, U256}; @@ -13,18 +14,20 @@ use crate::InstructionContext; /// Implements the KECCAK256 instruction. /// /// Computes Keccak-256 hash of memory data. -pub fn keccak256(context: InstructionContext<'_, H, WIRE>) { +pub fn keccak256( + context: InstructionContext<'_, H, WIRE>, +) { popn_top!([offset], top, context.interpreter); let len = as_usize_or_fail!(context.interpreter, top); gas!( context.interpreter, - context.interpreter.gas_params.keccak256_cost(len) + context.host.gas_params().keccak256_cost(len) ); let hash = if len == 0 { KECCAK_EMPTY } else { let from = as_usize_or_fail!(context.interpreter, offset); - resize_memory!(context.interpreter, from, len); + resize_memory!(context.interpreter, context.host.gas_params(), from, len); primitives::keccak256(context.interpreter.memory.slice_len(from, len).as_ref()) }; *top = hash.into(); @@ -73,11 +76,17 @@ pub fn codesize(context: InstructionContext<' /// Implements the CODECOPY instruction. /// /// Copies running contract's bytecode to memory. -pub fn codecopy(context: InstructionContext<'_, H, WIRE>) { +pub fn codecopy( + context: InstructionContext<'_, H, WIRE>, +) { popn!([memory_offset, code_offset, len], context.interpreter); let len = as_usize_or_fail!(context.interpreter, len); - let Some(memory_offset) = copy_cost_and_memory_resize(context.interpreter, memory_offset, len) - else { + let Some(memory_offset) = copy_cost_and_memory_resize( + context.interpreter, + context.host.gas_params(), + memory_offset, + len, + ) else { return; }; let code_offset = as_usize_saturated!(code_offset); @@ -148,11 +157,17 @@ pub fn callvalue(context: InstructionContext< /// Implements the CALLDATACOPY instruction. /// /// Copies input data to memory. -pub fn calldatacopy(context: InstructionContext<'_, H, WIRE>) { +pub fn calldatacopy( + context: InstructionContext<'_, H, WIRE>, +) { popn!([memory_offset, data_offset, len], context.interpreter); let len = as_usize_or_fail!(context.interpreter, len); - let Some(memory_offset) = copy_cost_and_memory_resize(context.interpreter, memory_offset, len) - else { + let Some(memory_offset) = copy_cost_and_memory_resize( + context.interpreter, + context.host.gas_params(), + memory_offset, + len, + ) else { return; }; @@ -185,7 +200,9 @@ pub fn returndatasize(context: InstructionCon } /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY -pub fn returndatacopy(context: InstructionContext<'_, H, WIRE>) { +pub fn returndatacopy( + context: InstructionContext<'_, H, WIRE>, +) { check!(context.interpreter, BYZANTIUM); popn!([memory_offset, offset, len], context.interpreter); @@ -199,8 +216,12 @@ pub fn returndatacopy(context: InstructionCon return; } - let Some(memory_offset) = copy_cost_and_memory_resize(context.interpreter, memory_offset, len) - else { + let Some(memory_offset) = copy_cost_and_memory_resize( + context.interpreter, + context.host.gas_params(), + memory_offset, + len, + ) else { return; }; @@ -228,16 +249,17 @@ pub fn gas(context: InstructionContext<'_, H, /// Handles memory expansion and gas calculation for data copy operations. pub fn copy_cost_and_memory_resize( interpreter: &mut Interpreter, + gas_params: &GasParams, memory_offset: U256, len: usize, ) -> Option { // Safe to cast usize to u64 - gas!(interpreter, interpreter.gas_params.copy_cost(len), None); + gas!(interpreter, gas_params.copy_cost(len), None); if len == 0 { return None; } let memory_offset = as_usize_or_fail_ret!(interpreter, memory_offset, None); - resize_memory!(interpreter, memory_offset, len, None); + resize_memory!(interpreter, gas_params, memory_offset, len, None); Some(memory_offset) } diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index 2bfdab36a5..30b3d84256 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -9,6 +9,7 @@ mod runtime_flags; mod shared_memory; mod stack; +use context_interface::cfg::GasParams; // re-exports pub use ext_bytecode::ExtBytecode; pub use input::InputsImpl; @@ -19,8 +20,8 @@ pub use stack::{Stack, STACK_LIMIT}; // imports use crate::{ - gas::params::GasParams, host::DummyHost, instruction_context::InstructionContext, - interpreter_types::*, Gas, Host, InstructionResult, InstructionTable, InterpreterAction, + host::DummyHost, instruction_context::InstructionContext, interpreter_types::*, Gas, Host, + InstructionResult, InstructionTable, InterpreterAction, }; use bytecode::Bytecode; use primitives::{hardfork::SpecId, Bytes}; @@ -29,8 +30,6 @@ use primitives::{hardfork::SpecId, Bytes}; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Interpreter { - /// Gas table for dynamic gas constants. - pub gas_params: GasParams, /// Bytecode being executed. pub bytecode: WIRE::Bytecode, /// Gas tracking for execution costs. @@ -58,7 +57,6 @@ impl Interpreter> { is_static: bool, spec_id: SpecId, gas_limit: u64, - gas_params: GasParams, ) -> Self { Self::new_inner( Stack::new(), @@ -68,7 +66,6 @@ impl Interpreter> { is_static, spec_id, gas_limit, - gas_params, ) } @@ -91,7 +88,6 @@ impl Interpreter> { false, SpecId::default(), u64::MAX, - GasParams::default(), ) } @@ -104,12 +100,10 @@ impl Interpreter> { is_static: bool, spec_id: SpecId, gas_limit: u64, - gas_params: GasParams, ) -> Self { Self { bytecode, gas: Gas::new(gas_limit), - gas_params, stack, return_data: Default::default(), memory, @@ -130,12 +124,10 @@ impl Interpreter> { is_static: bool, spec_id: SpecId, gas_limit: u64, - gas_params: GasParams, ) { let Self { bytecode: bytecode_ref, gas, - gas_params: gas_params_ref, stack, return_data, memory: memory_ref, @@ -154,7 +146,6 @@ impl Interpreter> { *memory_ref = memory; *input_ref = input; *runtime_flag = RuntimeFlags { spec_id, is_static }; - *gas_params_ref = gas_params; *extend = EXT::default(); } @@ -163,12 +154,6 @@ impl Interpreter> { self.bytecode = ExtBytecode::new(bytecode); self } - - /// Sets the specid for the interpreter. - pub fn set_spec_id(&mut self, spec_id: SpecId) { - self.gas_params = GasParams::new_spec(spec_id); - self.runtime_flag.spec_id = spec_id; - } } impl Default for Interpreter { @@ -198,14 +183,9 @@ impl Interpreter { /// Performs EVM memory resize. #[inline] #[must_use] - pub fn resize_memory(&mut self, offset: usize, len: usize) -> bool { - if let Err(result) = resize_memory( - &mut self.gas, - &mut self.memory, - &self.gas_params, - offset, - len, - ) { + pub fn resize_memory(&mut self, gas_params: &GasParams, offset: usize, len: usize) -> bool { + if let Err(result) = resize_memory(&mut self.gas, &mut self.memory, gas_params, offset, len) + { self.halt(result); return false; } @@ -333,7 +313,7 @@ impl Interpreter { /// This uses dummy Host. #[inline] pub fn step_dummy(&mut self, instruction_table: &InstructionTable) { - self.step(instruction_table, &mut DummyHost); + self.step(instruction_table, &mut DummyHost::default()); } /// Executes the interpreter until it returns or stops. @@ -448,7 +428,6 @@ mod tests { false, SpecId::default(), u64::MAX, - GasParams::default(), ); let serialized = serde_json::to_string_pretty(&interpreter).unwrap(); @@ -486,11 +465,10 @@ fn test_mstore_big_offset_memory_oog() { false, SpecId::default(), 1000, - GasParams::default(), ); let table = instruction_table::(); - let mut host = DummyHost; + let mut host = DummyHost::default(); let action = interpreter.run_plain(&table, &mut host); assert!(action.is_return()); diff --git a/crates/interpreter/src/interpreter/shared_memory.rs b/crates/interpreter/src/interpreter/shared_memory.rs index abaf4ca1df..f7a7598a8c 100644 --- a/crates/interpreter/src/interpreter/shared_memory.rs +++ b/crates/interpreter/src/interpreter/shared_memory.rs @@ -1,5 +1,6 @@ use super::MemoryTr; -use crate::{gas::params::GasParams, InstructionResult}; +use crate::InstructionResult; +use context_interface::cfg::GasParams; use core::{ cell::{Ref, RefCell, RefMut}, cmp::min, diff --git a/crates/op-revm/src/l1block.rs b/crates/op-revm/src/l1block.rs index 3c86904b47..325796f4da 100644 --- a/crates/op-revm/src/l1block.rs +++ b/crates/op-revm/src/l1block.rs @@ -11,11 +11,9 @@ use crate::{ OpSpecId, }; use revm::{ + context_interface::cfg::gas::{NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, database_interface::Database, - interpreter::{ - gas::{get_tokens_in_calldata, NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, - Gas, - }, + interpreter::{gas::get_tokens_in_calldata, Gas}, primitives::U256, }; From dad347ba2c122b78825dd2e56679215dc91fef2e Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 17 Dec 2025 02:34:48 +0100 Subject: [PATCH 02/19] propagate spec id --- bins/revme/src/cmd/blockchaintest.rs | 4 +- bins/revme/src/cmd/statetest/runner.rs | 17 +++--- crates/context/interface/src/cfg.rs | 10 ++++ crates/context/interface/src/lib.rs | 2 +- crates/context/src/cfg.rs | 58 ++++++++++++++++++- crates/context/src/context.rs | 45 ++++++++++++-- crates/ee-tests/src/op_revm_tests.rs | 46 +++++++-------- crates/ee-tests/src/revm_tests.rs | 6 +- crates/handler/src/evm.rs | 23 +++++++- crates/handler/src/mainnet_builder.rs | 2 +- crates/handler/src/precompile_provider.rs | 29 +++++++++- crates/handler/src/validation.rs | 2 +- crates/interpreter/src/gas.rs | 3 - crates/interpreter/src/gas/calc.rs | 25 +------- crates/interpreter/src/gas/constants.rs | 1 - crates/interpreter/src/gas/params.rs | 1 - .../interpreter/src/instructions/bitwise.rs | 1 + crates/interpreter/src/instructions/host.rs | 1 - crates/interpreter/src/interpreter.rs | 3 +- crates/op-revm/src/evm.rs | 16 ++++- crates/op-revm/src/handler.rs | 46 +++++++-------- crates/op-revm/src/precompiles.rs | 1 + crates/statetest-types/src/test_unit.rs | 8 +-- examples/erc20_gas/src/main.rs | 2 +- 24 files changed, 238 insertions(+), 114 deletions(-) delete mode 100644 crates/interpreter/src/gas/constants.rs delete mode 100644 crates/interpreter/src/gas/params.rs diff --git a/bins/revme/src/cmd/blockchaintest.rs b/bins/revme/src/cmd/blockchaintest.rs index f286a3ca85..c0c12a9764 100644 --- a/bins/revme/src/cmd/blockchaintest.rs +++ b/bins/revme/src/cmd/blockchaintest.rs @@ -448,7 +448,7 @@ fn print_error_with_state( // Print configuration environment eprintln!("\n📋 Configuration Environment:"); - eprintln!(" Spec ID: {:?}", debug_info.cfg_env.spec); + eprintln!(" Spec ID: {:?}", debug_info.cfg_env.spec()); eprintln!(" Chain ID: {}", debug_info.cfg_env.chain_id); eprintln!( " Limit contract code size: {:?}", @@ -685,7 +685,7 @@ fn execute_blockchain_test( // Setup configuration based on fork let spec_id = fork_to_spec_id(test_case.network); let mut cfg = CfgEnv::default(); - cfg.spec = spec_id; + cfg.set_spec(spec_id); // Genesis block is not used yet. let mut parent_block_hash = Some(test_case.genesis_block_header.hash); diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 849748d51b..96150cac8a 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -2,10 +2,7 @@ use crate::cmd::statetest::merkle_trie::{compute_test_roots, TestValidationResul use indicatif::{ProgressBar, ProgressDrawTarget}; use revm::{ context::{block::BlockEnv, cfg::CfgEnv, tx::TxEnv}, - context_interface::{ - result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction}, - Cfg, - }, + context_interface::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction}, database, database_interface::EmptyDB, inspector::{inspectors::TracerEip3155, InspectCommitEvm}, @@ -328,12 +325,12 @@ pub fn execute_test_suite( continue; } - cfg.spec = spec_name.to_spec_id(); + cfg.set_spec(spec_name.to_spec_id()); // Configure max blobs per spec - if cfg.spec.is_enabled_in(SpecId::OSAKA) { + if cfg.spec().is_enabled_in(SpecId::OSAKA) { cfg.set_max_blobs_per_tx(6); - } else if cfg.spec.is_enabled_in(SpecId::PRAGUE) { + } else if cfg.spec().is_enabled_in(SpecId::PRAGUE) { cfg.set_max_blobs_per_tx(9); } else { cfg.set_max_blobs_per_tx(6); @@ -409,7 +406,7 @@ pub fn execute_test_suite( fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> { // Prepare state let mut cache = ctx.cache_state.clone(); - cache.set_state_clear_flag(ctx.cfg.spec.is_enabled_in(SpecId::SPURIOUS_DRAGON)); + cache.set_state_clear_flag(ctx.cfg.spec().is_enabled_in(SpecId::SPURIOUS_DRAGON)); let mut state = database::State::builder() .with_cached_prestate(cache) .with_bundle_update() @@ -454,7 +451,7 @@ fn debug_failed_test(ctx: DebugContext) { // Re-run with tracing let mut cache = ctx.cache_state.clone(); - cache.set_state_clear_flag(ctx.cfg.spec.is_enabled_in(SpecId::SPURIOUS_DRAGON)); + cache.set_state_clear_flag(ctx.cfg.spec().is_enabled_in(SpecId::SPURIOUS_DRAGON)); let mut state = database::State::builder() .with_cached_prestate(cache) .with_bundle_update() @@ -476,7 +473,7 @@ fn debug_failed_test(ctx: DebugContext) { "\nState after:\n{}", evm.ctx.journaled_state.database.cache.pretty_print() ); - println!("\nSpecification: {:?}", ctx.cfg.spec); + println!("\nSpecification: {:?}", ctx.cfg.spec()); println!("\nTx: {:#?}", ctx.tx); println!("Block: {:#?}", ctx.block); println!("Cfg: {:#?}", ctx.cfg); diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index cb107693d6..62576ca37b 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -77,6 +77,16 @@ pub trait Cfg { fn gas_params(&self) -> &GasParams; } +/// Sets the spec for the `Cfg`. +pub trait SetSpecTr { + /// Specification id type, it requires to be convertible to `SpecId` so it can be used + /// by default in mainnet. + type Spec: Into + Clone; + + /// Sets the spec for the `Cfg`. + fn set_spec(&mut self, spec: Self::Spec); +} + /// What bytecode analysis to perform #[derive(Clone, Default, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/context/interface/src/lib.rs b/crates/context/interface/src/lib.rs index cdf8a79c38..41ee615f32 100644 --- a/crates/context/interface/src/lib.rs +++ b/crates/context/interface/src/lib.rs @@ -15,7 +15,7 @@ pub mod result; pub mod transaction; pub use block::Block; -pub use cfg::{Cfg, CreateScheme, TransactTo}; +pub use cfg::{Cfg, CreateScheme, SetSpecTr, TransactTo}; pub use context::{ContextError, ContextSetters, ContextTr}; pub use database_interface::{erased_error::ErasedError, DBErrorMarker, Database}; pub use either; diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index c8165bc752..97865f202c 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -1,13 +1,19 @@ //! This module contains [`CfgEnv`] and implements [`Cfg`] trait for it. pub use context_interface::Cfg; -use context_interface::cfg::GasParams; +use context_interface::cfg::{GasParams, SetSpecTr}; use primitives::{eip170, eip3860, eip7825, hardfork::SpecId}; + +/// Gas params override function. +pub type GasParamsOverrideFn = fn(SPEC) -> GasParams; + /// EVM configuration #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub struct CfgEnv { + /// Specification for EVM represent the hardfork + spec: SPEC, /// Chain ID of the EVM. Used in CHAINID opcode and transaction's chain ID check. /// /// Chain ID is introduced EIP-155. @@ -18,8 +24,6 @@ pub struct CfgEnv { /// If set to `false`, the transaction's chain ID check will be skipped. pub tx_chain_id_check: bool, - /// Specification for EVM represent the hardfork - pub spec: SPEC, /// Contract code size limit override. /// /// If None, the limit will be determined by the SpecId (EIP-170 or EIP-7907) at runtime. @@ -58,6 +62,11 @@ pub struct CfgEnv { pub tx_gas_limit_cap: Option, /// Gas params for the EVM. pub gas_params: GasParams, + /// Gas params override function. + /// + /// If this is set, it will override the gas params with this function. + #[cfg_attr(feature = "serde", serde(skip))] + pub gas_params_override: Option>, /// A hard memory limit in bytes beyond which /// [OutOfGasError::Memory][context_interface::result::OutOfGasError::Memory] cannot be resized. /// @@ -163,6 +172,7 @@ impl + Copy> CfgEnv { tx_gas_limit_cap: None, blob_base_fee_update_fraction: None, gas_params: GasParams::new_spec(spec.into()), + gas_params_override: None, #[cfg(feature = "memory_limit")] memory_limit: (1 << 32) - 1, #[cfg(feature = "optional_balance_check")] @@ -184,6 +194,36 @@ impl + Copy> CfgEnv { } } + /// Returns the spec for the `CfgEnv`. + #[inline] + pub fn spec(&self) -> SPEC { + self.spec + } + + /// Sets the spec for the `CfgEnv`. + /// + /// It will update the gas params to the new spec. + /// + /// Please use Evm::set_spec so other parts of the system are updated. + #[inline] + pub fn set_spec(&mut self, spec: SPEC) { + self.gas_params = self + .gas_params_override + .map(|override_fn| override_fn(spec)) + .unwrap_or_else(|| GasParams::new_spec(spec.into())); + + self.spec = spec; + } + + /// Sets the gas params override function. + /// + /// It will override the gas params with this function. + #[inline] + pub fn set_gas_params_override(&mut self, override_fn: GasParamsOverrideFn) { + self.gas_params_override = Some(override_fn); + self.gas_params = override_fn(self.spec); + } + /// Consumes `self` and returns a new `CfgEnv` with the specified chain ID. pub fn with_chain_id(mut self, chain_id: u64) -> Self { self.chain_id = chain_id; @@ -203,6 +243,8 @@ impl + Copy> CfgEnv { } /// Consumes `self` and returns a new `CfgEnv` with the specified spec. + /// + /// Resets the gas params override function as it is generic over SPEC. pub fn with_spec + Copy>(self, spec: OSPEC) -> CfgEnv { let eth_spec = spec.into(); CfgEnv { @@ -216,6 +258,7 @@ impl + Copy> CfgEnv { max_blobs_per_tx: self.max_blobs_per_tx, blob_base_fee_update_fraction: self.blob_base_fee_update_fraction, gas_params: GasParams::new_spec(eth_spec), + gas_params_override: None, #[cfg(feature = "memory_limit")] memory_limit: self.memory_limit, #[cfg(feature = "optional_balance_check")] @@ -423,6 +466,15 @@ impl + Copy> Cfg for CfgEnv { } } +impl + Copy> SetSpecTr for CfgEnv { + type Spec = SPEC; + + #[inline] + fn set_spec(&mut self, spec: SPEC) { + self.set_spec(spec); + } +} + impl + Copy> Default for CfgEnv { fn default() -> Self { Self::new_with_spec(SPEC::default()) diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index a1c517dd04..bf22080ff7 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -5,7 +5,8 @@ use context_interface::{ context::{ContextError, ContextSetters, SStoreResult, SelfDestructResult, StateLoad}, host::LoadError, journaled_state::AccountInfoLoad, - Block, Cfg, ContextTr, Host, JournalTr, LocalContextTr, Transaction, TransactionType, + Block, Cfg, ContextTr, Host, JournalTr, LocalContextTr, SetSpecTr, Transaction, + TransactionType, }; use database_interface::{Database, DatabaseRef, EmptyDB, WrapDatabaseRef}; use derive_where::derive_where; @@ -146,10 +147,7 @@ impl< Self { tx: TX::default(), block: BLOCK::default(), - cfg: CfgEnv { - spec, - ..Default::default() - }, + cfg: CfgEnv::new_with_spec(spec), local: LOCAL::default(), journaled_state, chain: Default::default(), @@ -158,6 +156,43 @@ impl< } } +impl SetSpecTr + for Context +where + BLOCK: Block, + TX: Transaction, + CFG: Cfg + SetSpecTr::Spec>, + DB: Database, + JOURNAL: JournalTr, + LOCAL: LocalContextTr, +{ + type Spec = ::Spec; + + /// Sets the spec for the context. + #[inline] + fn set_spec(&mut self, spec: Self::Spec) { + self.cfg.set_spec(spec.clone()); + self.journaled_state.set_spec_id(spec.into()); + } +} + +impl Context +where + BLOCK: Block, + TX: Transaction, + CFG: Cfg + SetSpecTr::Spec>, + DB: Database, + JOURNAL: JournalTr, + LOCAL: LocalContextTr, +{ + /// Creates a new context with a new spec. + #[inline] + pub fn with_spec(mut self, spec: ::Spec) -> Self { + self.set_spec(spec); + self + } +} + impl Context where BLOCK: Block, diff --git a/crates/ee-tests/src/op_revm_tests.rs b/crates/ee-tests/src/op_revm_tests.rs index 1e44ad1f67..c8183d07ca 100644 --- a/crates/ee-tests/src/op_revm_tests.rs +++ b/crates/ee-tests/src/op_revm_tests.rs @@ -51,7 +51,7 @@ fn test_deposit_tx() { .source_hash(revm::primitives::B256::from([1u8; 32])) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE); + .with_spec(OpSpecId::HOLOCENE); let mut evm = ctx.build_op(); @@ -83,7 +83,7 @@ fn test_halted_deposit_tx() { .source_hash(revm::primitives::B256::from([1u8; 32])) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE) + .with_spec(OpSpecId::HOLOCENE) .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( [opcode::POP].into(), ))); @@ -127,7 +127,7 @@ fn p256verify_test_tx( ) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + .with_spec(SPEC_ID) } #[test] @@ -160,7 +160,7 @@ fn test_halted_tx_call_p256verify() { ) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -196,7 +196,7 @@ fn bn254_pair_test_tx( ) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = spec) + .with_spec(spec) } #[test] @@ -253,7 +253,7 @@ fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); @@ -290,7 +290,7 @@ fn test_halted_tx_call_bls12_381_g1_add_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -339,7 +339,7 @@ fn g1_msm_test_tx( l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + .with_spec(SPEC_ID) } #[test] @@ -369,7 +369,7 @@ fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -416,7 +416,7 @@ fn test_halted_tx_call_bls12_381_g1_msm_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -474,7 +474,7 @@ fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); @@ -511,7 +511,7 @@ fn test_halted_tx_call_bls12_381_g2_add_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); @@ -561,7 +561,7 @@ fn g2_msm_test_tx( l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + .with_spec(SPEC_ID) } #[test] @@ -591,7 +591,7 @@ fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -638,7 +638,7 @@ fn test_halted_tx_call_bls12_381_g2_msm_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -707,7 +707,7 @@ fn bl12_381_pairing_test_tx( l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) + .with_spec(OpSpecId::ISTHMUS) } #[test] @@ -734,7 +734,7 @@ fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -778,7 +778,7 @@ fn test_halted_tx_call_bls12_381_pairing_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -842,7 +842,7 @@ fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -884,7 +884,7 @@ fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -926,7 +926,7 @@ fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -967,7 +967,7 @@ fn test_l1block_load_for_pre_regolith() { .modify_chain_chained(|l1_block| { l1_block.l2_block = None; }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx .with_db( @@ -1006,7 +1006,7 @@ fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); + .with_spec(SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); diff --git a/crates/ee-tests/src/revm_tests.rs b/crates/ee-tests/src/revm_tests.rs index b89459ebfc..10efb689ff 100644 --- a/crates/ee-tests/src/revm_tests.rs +++ b/crates/ee-tests/src/revm_tests.rs @@ -38,7 +38,7 @@ const SELFDESTRUCT_BYTECODE: &[u8] = &[ #[test] fn test_selfdestruct_multi_tx() { let mut evm = Context::mainnet() - .modify_cfg_chained(|cfg| cfg.spec = SpecId::BERLIN) + .modify_cfg_chained(|cfg| cfg.set_spec(SpecId::BERLIN)) .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( SELFDESTRUCT_BYTECODE.into(), ))) @@ -85,7 +85,7 @@ fn test_selfdestruct_multi_tx() { fn test_multi_tx_create() { let mut evm = Context::mainnet() .modify_cfg_chained(|cfg| { - cfg.spec = SpecId::BERLIN; + cfg.set_spec(SpecId::BERLIN); cfg.disable_nonce_check = true; }) .with_db(BenchmarkDB::new_bytecode(Bytecode::new())) @@ -217,7 +217,7 @@ fn deployment_contract(bytes: &[u8]) -> Bytes { #[test] fn test_frame_stack_index() { let mut evm = Context::mainnet() - .modify_cfg_chained(|cfg| cfg.spec = SpecId::BERLIN) + .modify_cfg_chained(|cfg| cfg.set_spec(SpecId::BERLIN)) .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( SELFDESTRUCT_BYTECODE.into(), ))) diff --git a/crates/handler/src/evm.rs b/crates/handler/src/evm.rs index 0ad3130f0c..3bde94f9f3 100644 --- a/crates/handler/src/evm.rs +++ b/crates/handler/src/evm.rs @@ -3,7 +3,7 @@ use crate::{ ItemOrResult, PrecompileProvider, }; use auto_impl::auto_impl; -use context::{ContextTr, Database, Evm, FrameStack}; +use context::{Cfg, ContextTr, Database, Evm, FrameStack, SetSpecTr}; use context_interface::context::ContextError; use interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, InterpreterResult}; @@ -125,6 +125,27 @@ pub trait EvmTr { ) -> Result::FrameResult>, ContextDbError>; } +/// A trait that integrates context, instruction set, and precompiles to create an EVM struct. +/// +/// In addition to execution capabilities, this trait provides getter methods for its component fields. +pub trait EvmTrSetSpec: EvmTr { + /// Sets the spec for the EVM. + fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec); +} + +impl EvmTrSetSpec for Evm> +where + CTX: ContextTr + SetSpecTr::Cfg as Cfg>::Spec>, + I: InstructionProvider, + P: PrecompileProvider, +{ + /// Sets the spec for the EVM. + #[inline] + fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { + SetSpecTr::set_spec(self.ctx(), spec); + } +} + impl EvmTr for Evm> where CTX: ContextTr, diff --git a/crates/handler/src/mainnet_builder.rs b/crates/handler/src/mainnet_builder.rs index a640da1aca..00b4d71f1e 100644 --- a/crates/handler/src/mainnet_builder.rs +++ b/crates/handler/src/mainnet_builder.rs @@ -99,7 +99,7 @@ mod test { let bytecode = Bytecode::new_legacy([PUSH1, 0x01, PUSH1, 0x01, SSTORE].into()); let ctx = Context::mainnet() - .modify_cfg_chained(|cfg| cfg.spec = SpecId::PRAGUE) + .modify_cfg_chained(|cfg| cfg.set_spec(SpecId::PRAGUE)) .with_db(BenchmarkDB::new_bytecode(bytecode)); let mut evm = ctx.build_mainnet(); diff --git a/crates/handler/src/precompile_provider.rs b/crates/handler/src/precompile_provider.rs index c3ac4a3e0c..5ec542c260 100644 --- a/crates/handler/src/precompile_provider.rs +++ b/crates/handler/src/precompile_provider.rs @@ -1,5 +1,5 @@ use auto_impl::auto_impl; -use context::{Cfg, LocalContextTr}; +use context::{Cfg, LocalContextTr, SetSpecTr}; use context_interface::{ContextTr, JournalTr}; use interpreter::{CallInput, CallInputs, Gas, InstructionResult, InterpreterResult}; use precompile::{PrecompileError, PrecompileSpecId, Precompiles}; @@ -18,6 +18,9 @@ pub trait PrecompileProvider { /// Sets the spec id and returns true if the spec id was changed. Initial call to set_spec will always return true. /// /// Returns `true` if precompile addresses should be injected into the journal. + #[deprecated( + note = "We are moving away from runtime setting off spec to setting spec in initialization. Check EvmTrSetSpec trait for more information." + )] fn set_spec(&mut self, spec: ::Spec) -> bool; /// Run the precompile. @@ -41,6 +44,8 @@ pub struct EthPrecompiles { pub precompiles: &'static Precompiles, /// Current spec. None means that spec was not set yet. pub spec: SpecId, + /// Spec override function. + pub spec_override_fn: Option &'static Precompiles>, } impl EthPrecompiles { @@ -55,11 +60,27 @@ impl EthPrecompiles { } } +impl SetSpecTr for EthPrecompiles { + type Spec = SpecId; + + fn set_spec(&mut self, spec: Self::Spec) { + if spec == self.spec { + return; + } + self.precompiles = self + .spec_override_fn + .map(|override_fn| override_fn(spec)) + .unwrap_or_else(|| Precompiles::new(PrecompileSpecId::from_spec_id(spec))); + self.spec = spec; + } +} + impl Clone for EthPrecompiles { fn clone(&self) -> Self { Self { precompiles: self.precompiles, spec: self.spec, + spec_override_fn: self.spec_override_fn, } } } @@ -70,6 +91,7 @@ impl Default for EthPrecompiles { Self { precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), spec, + spec_override_fn: None, } } } @@ -83,7 +105,10 @@ impl PrecompileProvider for EthPrecompiles { if spec == self.spec { return false; } - self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec)); + self.precompiles = self + .spec_override_fn + .map(|override_fn| override_fn(spec)) + .unwrap_or_else(|| Precompiles::new(PrecompileSpecId::from_spec_id(spec))); self.spec = spec; true } diff --git a/crates/handler/src/validation.rs b/crates/handler/src/validation.rs index d9ba1c4415..85d0f1fdee 100644 --- a/crates/handler/src/validation.rs +++ b/crates/handler/src/validation.rs @@ -275,7 +275,7 @@ mod tests { let ctx = Context::mainnet() .modify_cfg_chained(|c| { if let Some(spec_id) = spec_id { - c.spec = spec_id; + c.set_spec(spec_id); } }) .with_db(CacheDB::::default()); diff --git a/crates/interpreter/src/gas.rs b/crates/interpreter/src/gas.rs index e4867e8e30..68dbc24103 100644 --- a/crates/interpreter/src/gas.rs +++ b/crates/interpreter/src/gas.rs @@ -1,11 +1,8 @@ //! EVM gas calculation utilities. mod calc; -mod constants; -pub mod params; pub use calc::*; -pub use constants::*; /// Represents the state of gas during execution. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] diff --git a/crates/interpreter/src/gas/calc.rs b/crates/interpreter/src/gas/calc.rs index 7668477cce..ef4e79469b 100644 --- a/crates/interpreter/src/gas/calc.rs +++ b/crates/interpreter/src/gas/calc.rs @@ -2,30 +2,7 @@ use crate::num_words; use context_interface::{ cfg::gas::*, transaction::AccessListItemTr as _, Transaction, TransactionType, }; -use primitives::{eip7702, hardfork::SpecId, U256}; - -#[inline] -pub(crate) const fn log2floor(value: U256) -> u64 { - let mut l: u64 = 256; - let mut i = 3; - loop { - if value.as_limbs()[i] == 0u64 { - l -= 64; - } else { - l -= value.as_limbs()[i].leading_zeros() as u64; - if l == 0 { - return l; - } else { - return l - 1; - } - } - if i == 0 { - break; - } - i -= 1; - } - l -} +use primitives::{eip7702, hardfork::SpecId}; /// Calculate the cost of buffer per word. #[inline] diff --git a/crates/interpreter/src/gas/constants.rs b/crates/interpreter/src/gas/constants.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/interpreter/src/gas/constants.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/interpreter/src/gas/params.rs b/crates/interpreter/src/gas/params.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/interpreter/src/gas/params.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/interpreter/src/instructions/bitwise.rs b/crates/interpreter/src/instructions/bitwise.rs index 82551d24c3..57972a2de2 100644 --- a/crates/interpreter/src/instructions/bitwise.rs +++ b/crates/interpreter/src/instructions/bitwise.rs @@ -466,6 +466,7 @@ mod tests { #[test] fn test_clz() { let mut interpreter = Interpreter::default(); + interpreter.runtime_flag.spec_id = SpecId::OSAKA; let mut host = DummyHost::new(SpecId::OSAKA); struct TestCase { diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index c82fcf5a27..ba637bb594 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -114,7 +114,6 @@ pub fn extcodecopy( context.interpreter ); let address = address.into_address(); - let u256 = address.into_word(); let spec_id = context.interpreter.runtime_flag.spec_id(); diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index 30b3d84256..698773409f 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -503,11 +503,10 @@ fn test_mstore_big_offset_memory_limit_oog() { false, SpecId::default(), 100000, - GasParams::default(), ); let table = instruction_table::(); - let mut host = DummyHost; + let mut host = DummyHost::default(); let action = interpreter.run_plain(&table, &mut host); assert!(action.is_return()); diff --git a/crates/op-revm/src/evm.rs b/crates/op-revm/src/evm.rs index a93a172756..12d28e4f7d 100644 --- a/crates/op-revm/src/evm.rs +++ b/crates/op-revm/src/evm.rs @@ -1,10 +1,10 @@ //! Contains the `[OpEvm]` type and its implementation of the execution EVM traits. use crate::precompiles::OpPrecompiles; use revm::{ - context::{ContextError, ContextSetters, Evm, FrameStack}, + context::{Cfg, ContextError, ContextSetters, Evm, FrameStack, SetSpecTr}, context_interface::ContextTr, handler::{ - evm::FrameTr, + evm::{EvmTrSetSpec, FrameTr}, instructions::{EthInstructions, InstructionProvider}, EthFrame, EvmTr, FrameInitOrResult, ItemOrResult, PrecompileProvider, }, @@ -92,6 +92,18 @@ where } } +impl EvmTrSetSpec for OpEvm> +where + CTX: ContextTr + SetSpecTr::Cfg as Cfg>::Spec>, + I: InstructionProvider, + P: PrecompileProvider, +{ + #[inline] + fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { + self.0.set_spec(spec); + } +} + impl EvmTr for OpEvm> where CTX: ContextTr, diff --git a/crates/op-revm/src/handler.rs b/crates/op-revm/src/handler.rs index 13ee5c4f92..b94d431dd0 100644 --- a/crates/op-revm/src/handler.rs +++ b/crates/op-revm/src/handler.rs @@ -465,7 +465,7 @@ mod tests { context_interface::result::InvalidTransaction, database::InMemoryDB, database_interface::EmptyDB, - handler::EthFrame, + handler::{evm::EvmTrSetSpec, EthFrame}, interpreter::{CallOutcome, InstructionResult, InterpreterResult}, primitives::{bytes, Address, Bytes, B256}, state::AccountInfo, @@ -508,7 +508,7 @@ mod tests { .base(TxEnv::builder().gas_limit(100)) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + .with_spec(OpSpecId::BEDROCK); let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90)); assert_eq!(gas.remaining(), 90); @@ -524,7 +524,7 @@ mod tests { .base(TxEnv::builder().gas_limit(100)) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 90); @@ -541,7 +541,7 @@ mod tests { .source_hash(B256::from([1u8; 32])) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); let mut ret_gas = Gas::new(90); ret_gas.record_refund(20); @@ -566,7 +566,7 @@ mod tests { .source_hash(B256::from([1u8; 32])) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + .with_spec(OpSpecId::BEDROCK); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 0); assert_eq!(gas.spent(), 100); @@ -583,7 +583,7 @@ mod tests { .is_system_transaction() .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + .with_spec(OpSpecId::BEDROCK); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 100); assert_eq!(gas.spent(), 0); @@ -610,7 +610,7 @@ mod tests { l1_base_fee_scalar: U256::from(1_000), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); ctx.modify_tx(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.mint = Some(10); @@ -649,7 +649,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .with_spec(OpSpecId::REGOLITH) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(100)) @@ -722,7 +722,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); @@ -809,7 +809,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + .with_spec(OpSpecId::JOVIAN) // set the operator fee to a low value .with_tx( OpTransaction::builder() @@ -877,7 +877,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); let mut evm = ctx.build_op(); assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); @@ -937,7 +937,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ECOTONE); + .with_spec(OpSpecId::ECOTONE); let mut evm = ctx.build_op(); assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); @@ -1010,7 +1010,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); @@ -1060,7 +1060,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .with_spec(OpSpecId::REGOLITH) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(100)) @@ -1103,7 +1103,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) + .with_spec(OpSpecId::ISTHMUS) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(10)) @@ -1145,7 +1145,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + .with_spec(OpSpecId::JOVIAN) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(10)) @@ -1187,7 +1187,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .with_spec(OpSpecId::REGOLITH) .modify_tx_chained(|tx| { tx.enveloped_tx = Some(bytes!("FACADE")); }); @@ -1218,7 +1218,7 @@ mod tests { tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.is_system_transaction = true; }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); let mut evm = ctx.build_op(); let handler = @@ -1231,7 +1231,7 @@ mod tests { )) ); - evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK); + evm.set_spec(OpSpecId::BEDROCK); // Pre-regolith system transactions should be allowed. assert!(handler.validate_env(&mut evm).is_ok()); @@ -1244,7 +1244,7 @@ mod tests { .modify_tx_chained(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); let mut evm = ctx.build_op(); let handler = @@ -1260,7 +1260,7 @@ mod tests { .modify_tx_chained(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); let mut evm = ctx.build_op(); let handler = @@ -1277,7 +1277,7 @@ mod tests { // Set up as deposit transaction by having a deposit with source_hash tx.deposit.source_hash = B256::from([1u8; 32]); }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_spec(OpSpecId::REGOLITH); let mut evm = ctx.build_op(); let mut handler = @@ -1360,7 +1360,7 @@ mod tests { }) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_spec(OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); let handler = diff --git a/crates/op-revm/src/precompiles.rs b/crates/op-revm/src/precompiles.rs index d234980164..d8fe69a10d 100644 --- a/crates/op-revm/src/precompiles.rs +++ b/crates/op-revm/src/precompiles.rs @@ -41,6 +41,7 @@ impl OpPrecompiles { inner: EthPrecompiles { precompiles, spec: SpecId::default(), + spec_override_fn: None, }, spec, } diff --git a/crates/statetest-types/src/test_unit.rs b/crates/statetest-types/src/test_unit.rs index 15d55d14dc..3a730956c8 100644 --- a/crates/statetest-types/src/test_unit.rs +++ b/crates/statetest-types/src/test_unit.rs @@ -121,7 +121,7 @@ impl TestUnit { } // Set default prevrandao for merge - if cfg.spec.is_enabled_in(SpecId::MERGE) && block.prevrandao.is_none() { + if cfg.spec().is_enabled_in(SpecId::MERGE) && block.prevrandao.is_none() { block.prevrandao = Some(B256::default()); } @@ -188,7 +188,7 @@ mod tests { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 let mut cfg = CfgEnv::default(); - cfg.spec = SpecId::CANCUN; + cfg.set_spec(SpecId::CANCUN); let block = unit.block_env(&mut cfg); @@ -212,7 +212,7 @@ mod tests { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 let mut cfg = CfgEnv::default(); - cfg.spec = SpecId::PRAGUE; + cfg.set_spec(SpecId::PRAGUE); let block = unit.block_env(&mut cfg); @@ -235,7 +235,7 @@ mod tests { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 let mut cfg = CfgEnv::default(); - cfg.spec = SpecId::OSAKA; + cfg.set_spec(SpecId::OSAKA); let block = unit.block_env(&mut cfg); diff --git a/examples/erc20_gas/src/main.rs b/examples/erc20_gas/src/main.rs index 1f16c47f9a..acfcd48f0f 100644 --- a/examples/erc20_gas/src/main.rs +++ b/examples/erc20_gas/src/main.rs @@ -86,7 +86,7 @@ fn transfer(from: Address, to: Address, amount: U256, cache_db: &mut AlloyCacheD let mut ctx = Context::mainnet() .with_db(cache_db) .modify_cfg_chained(|cfg| { - cfg.spec = SpecId::CANCUN; + cfg.set_spec(SpecId::CANCUN); }) .with_tx( TxEnv::builder() From 551fd21c83065e5994dbf75daffd3635214915f6 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 17 Dec 2025 03:17:21 +0100 Subject: [PATCH 03/19] use cfg spec when evm is created --- bins/revme/src/cmd/statetest/runner.rs | 5 ++++- crates/handler/src/evm.rs | 14 +++++++++++--- crates/handler/src/instructions.rs | 18 ++++++++++++++++++ crates/handler/src/lib.rs | 2 +- crates/handler/src/mainnet_builder.rs | 10 ++++++---- crates/handler/src/precompile_provider.rs | 10 ++++++++++ crates/op-revm/src/evm.rs | 11 +++++++---- 7 files changed, 57 insertions(+), 13 deletions(-) diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 96150cac8a..8439214830 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -5,6 +5,7 @@ use revm::{ context_interface::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction}, database, database_interface::EmptyDB, + handler::EvmTrSetSpec, inspector::{inspectors::TracerEip3155, InspectCommitEvm}, primitives::{hardfork::SpecId, Bytes, B256, U256}, Context, ExecuteCommitEvm, MainBuilder, MainContext, @@ -406,7 +407,8 @@ pub fn execute_test_suite( fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> { // Prepare state let mut cache = ctx.cache_state.clone(); - cache.set_state_clear_flag(ctx.cfg.spec().is_enabled_in(SpecId::SPURIOUS_DRAGON)); + let spec = ctx.cfg.spec(); + cache.set_state_clear_flag(spec.is_enabled_in(SpecId::SPURIOUS_DRAGON)); let mut state = database::State::builder() .with_cached_prestate(cache) .with_bundle_update() @@ -423,6 +425,7 @@ fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> { let (db, exec_result) = if ctx.trace { let mut evm = evm_context .build_mainnet_with_inspector(TracerEip3155::buffered(stderr()).without_summary()); + //evm.set_spec(spec.clone().into()); let res = evm.inspect_tx_commit(ctx.tx); let db = evm.ctx.journaled_state.database; (db, res) diff --git a/crates/handler/src/evm.rs b/crates/handler/src/evm.rs index 3bde94f9f3..8f148f8a04 100644 --- a/crates/handler/src/evm.rs +++ b/crates/handler/src/evm.rs @@ -133,16 +133,24 @@ pub trait EvmTrSetSpec: EvmTr { fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec); } +// CTX: ContextTr + SetSpecTr::Cfg as Cfg>::Spec>, +// I: InstructionProvider, +// P: PrecompileProvider, + impl EvmTrSetSpec for Evm> where CTX: ContextTr + SetSpecTr::Cfg as Cfg>::Spec>, - I: InstructionProvider, - P: PrecompileProvider, + I: InstructionProvider + + SetSpecTr::Cfg as Cfg>::Spec>, + P: PrecompileProvider + + SetSpecTr::Cfg as Cfg>::Spec>, { /// Sets the spec for the EVM. #[inline] fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { - SetSpecTr::set_spec(self.ctx(), spec); + self.ctx().set_spec(spec.clone()); + SetSpecTr::set_spec(&mut self.precompiles, spec.clone()); + self.instruction.set_spec(spec); } } diff --git a/crates/handler/src/instructions.rs b/crates/handler/src/instructions.rs index 89bcf663c0..bdba952a9a 100644 --- a/crates/handler/src/instructions.rs +++ b/crates/handler/src/instructions.rs @@ -1,4 +1,5 @@ use auto_impl::auto_impl; +use context::SetSpecTr; use interpreter::{ instructions::{instruction_table_gas_changes_spec, InstructionTable}, Host, Instruction, InterpreterTypes, @@ -39,6 +40,23 @@ where } } +impl SetSpecTr for EthInstructions +where + WIRE: InterpreterTypes, + HOST: Host, +{ + type Spec = SpecId; + + #[inline] + fn set_spec(&mut self, spec: Self::Spec) { + if spec == self.spec { + return; + } + self.spec = spec; + self.instruction_table = Box::new(instruction_table_gas_changes_spec(spec)); + } +} + impl EthInstructions where WIRE: InterpreterTypes, diff --git a/crates/handler/src/lib.rs b/crates/handler/src/lib.rs index a220439545..26f34bc272 100644 --- a/crates/handler/src/lib.rs +++ b/crates/handler/src/lib.rs @@ -33,7 +33,7 @@ pub mod validation; // Public exports pub use api::{ExecuteCommitEvm, ExecuteEvm}; -pub use evm::{EvmTr, FrameTr}; +pub use evm::{EvmTr, EvmTrSetSpec, FrameTr}; pub use frame::{return_create, ContextTrDbError, EthFrame}; pub use frame_data::{CallFrame, CreateFrame, FrameData, FrameResult}; pub use handler::{EvmTrError, Handler}; diff --git a/crates/handler/src/mainnet_builder.rs b/crates/handler/src/mainnet_builder.rs index 00b4d71f1e..2ca08b2118 100644 --- a/crates/handler/src/mainnet_builder.rs +++ b/crates/handler/src/mainnet_builder.rs @@ -36,11 +36,12 @@ where type Context = Self; fn build_mainnet(self) -> MainnetEvm { + let spec = self.cfg.spec().into(); Evm { ctx: self, inspector: (), - instruction: EthInstructions::default(), - precompiles: EthPrecompiles::default(), + instruction: EthInstructions::new_mainnet_with_spec(spec), + precompiles: EthPrecompiles::new(spec), frame_stack: FrameStack::new_prealloc(8), } } @@ -49,11 +50,12 @@ where self, inspector: INSP, ) -> MainnetEvm { + let spec = self.cfg.spec().into(); Evm { ctx: self, inspector, - instruction: EthInstructions::default(), - precompiles: EthPrecompiles::default(), + instruction: EthInstructions::new_mainnet_with_spec(spec), + precompiles: EthPrecompiles::new(spec), frame_stack: FrameStack::new_prealloc(8), } } diff --git a/crates/handler/src/precompile_provider.rs b/crates/handler/src/precompile_provider.rs index 5ec542c260..b2992c0a8e 100644 --- a/crates/handler/src/precompile_provider.rs +++ b/crates/handler/src/precompile_provider.rs @@ -49,6 +49,16 @@ pub struct EthPrecompiles { } impl EthPrecompiles { + + /// Create a new precompile provider with the given spec. + pub fn new(spec: SpecId) -> Self { + Self { + precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), + spec, + spec_override_fn: None, + } + } + /// Returns addresses of the precompiles. pub fn warm_addresses(&self) -> Box> { Box::new(self.precompiles.addresses().cloned()) diff --git a/crates/op-revm/src/evm.rs b/crates/op-revm/src/evm.rs index 12d28e4f7d..9cf5ca4d88 100644 --- a/crates/op-revm/src/evm.rs +++ b/crates/op-revm/src/evm.rs @@ -1,5 +1,5 @@ //! Contains the `[OpEvm]` type and its implementation of the execution EVM traits. -use crate::precompiles::OpPrecompiles; +use crate::{precompiles::OpPrecompiles, OpSpecId}; use revm::{ context::{Cfg, ContextError, ContextSetters, Evm, FrameStack, SetSpecTr}, context_interface::ContextTr, @@ -26,14 +26,17 @@ pub struct OpEvm< pub Evm, ); -impl OpEvm, OpPrecompiles> { +impl + Clone>>, INSP> + OpEvm, OpPrecompiles> +{ /// Create a new Optimism EVM. pub fn new(ctx: CTX, inspector: INSP) -> Self { + let spec: OpSpecId = ctx.cfg().spec().into(); Self(Evm { ctx, inspector, - instruction: EthInstructions::new_mainnet(), - precompiles: OpPrecompiles::default(), + instruction: EthInstructions::new_mainnet_with_spec(spec.clone().into()), + precompiles: OpPrecompiles::new_with_spec(spec), frame_stack: FrameStack::new_prealloc(8), }) } From 2d40c8f1f1e4c35f10e34f58afb889336cd9e605 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 17 Dec 2025 06:33:00 +0100 Subject: [PATCH 04/19] nits --- bins/revme/src/cmd/statetest/runner.rs | 2 -- crates/context/interface/src/cfg.rs | 8 ++------ crates/context/src/cfg.rs | 18 ++++++++---------- crates/context/src/context.rs | 15 +++++++-------- crates/handler/src/evm.rs | 6 +++--- crates/handler/src/instructions.rs | 7 +++---- crates/handler/src/precompile_provider.rs | 8 +++----- crates/op-revm/src/evm.rs | 4 ++-- 8 files changed, 28 insertions(+), 40 deletions(-) diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 8439214830..58f0f1e49c 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -5,7 +5,6 @@ use revm::{ context_interface::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction}, database, database_interface::EmptyDB, - handler::EvmTrSetSpec, inspector::{inspectors::TracerEip3155, InspectCommitEvm}, primitives::{hardfork::SpecId, Bytes, B256, U256}, Context, ExecuteCommitEvm, MainBuilder, MainContext, @@ -425,7 +424,6 @@ fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> { let (db, exec_result) = if ctx.trace { let mut evm = evm_context .build_mainnet_with_inspector(TracerEip3155::buffered(stderr()).without_summary()); - //evm.set_spec(spec.clone().into()); let res = evm.inspect_tx_commit(ctx.tx); let db = evm.ctx.journaled_state.database; (db, res) diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index 62576ca37b..283dba1d6c 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -78,13 +78,9 @@ pub trait Cfg { } /// Sets the spec for the `Cfg`. -pub trait SetSpecTr { - /// Specification id type, it requires to be convertible to `SpecId` so it can be used - /// by default in mainnet. - type Spec: Into + Clone; - +pub trait SetSpecTr + Clone> { /// Sets the spec for the `Cfg`. - fn set_spec(&mut self, spec: Self::Spec); + fn set_spec(&mut self, spec: SPEC); } /// What bytecode analysis to perform diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 97865f202c..8d955120ef 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -158,7 +158,7 @@ impl + Copy> CfgEnv { } } -impl + Copy> CfgEnv { +impl + Clone> CfgEnv { /// Create new `CfgEnv` with default values and specified spec. pub fn new_with_spec(spec: SPEC) -> Self { Self { @@ -166,7 +166,7 @@ impl + Copy> CfgEnv { tx_chain_id_check: true, limit_contract_code_size: None, limit_contract_initcode_size: None, - spec, + spec: spec.clone(), disable_nonce_check: false, max_blobs_per_tx: None, tx_gas_limit_cap: None, @@ -197,7 +197,7 @@ impl + Copy> CfgEnv { /// Returns the spec for the `CfgEnv`. #[inline] pub fn spec(&self) -> SPEC { - self.spec + self.spec.clone() } /// Sets the spec for the `CfgEnv`. @@ -207,12 +207,12 @@ impl + Copy> CfgEnv { /// Please use Evm::set_spec so other parts of the system are updated. #[inline] pub fn set_spec(&mut self, spec: SPEC) { + self.spec = spec.clone(); + self.gas_params = self .gas_params_override - .map(|override_fn| override_fn(spec)) + .map(|override_fn| override_fn(spec.clone())) .unwrap_or_else(|| GasParams::new_spec(spec.into())); - - self.spec = spec; } /// Sets the gas params override function. @@ -221,7 +221,7 @@ impl + Copy> CfgEnv { #[inline] pub fn set_gas_params_override(&mut self, override_fn: GasParamsOverrideFn) { self.gas_params_override = Some(override_fn); - self.gas_params = override_fn(self.spec); + self.gas_params = override_fn(self.spec.clone()); } /// Consumes `self` and returns a new `CfgEnv` with the specified chain ID. @@ -466,9 +466,7 @@ impl + Copy> Cfg for CfgEnv { } } -impl + Copy> SetSpecTr for CfgEnv { - type Spec = SPEC; - +impl + Clone> SetSpecTr for CfgEnv { #[inline] fn set_spec(&mut self, spec: SPEC) { self.set_spec(spec); diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index bf22080ff7..7d20ac48eb 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -135,7 +135,7 @@ impl< JOURNAL: JournalTr, CHAIN: Default, LOCAL: LocalContextTr + Default, - SPEC: Default + Copy + Into, + SPEC: Default + Into + Clone, > Context, DB, JOURNAL, CHAIN, LOCAL> { /// Creates a new context with a new database type. @@ -143,7 +143,7 @@ impl< /// This will create a new [`Journal`] object. pub fn new(db: DB, spec: SPEC) -> Self { let mut journaled_state = JOURNAL::new(db); - journaled_state.set_spec_id(spec.into()); + journaled_state.set_spec_id(spec.clone().into()); Self { tx: TX::default(), block: BLOCK::default(), @@ -156,21 +156,20 @@ impl< } } -impl SetSpecTr +impl SetSpecTr for Context where BLOCK: Block, TX: Transaction, - CFG: Cfg + SetSpecTr::Spec>, + CFG: Cfg + SetSpecTr, DB: Database, JOURNAL: JournalTr, LOCAL: LocalContextTr, + SPEC: Into + Clone, { - type Spec = ::Spec; - /// Sets the spec for the context. #[inline] - fn set_spec(&mut self, spec: Self::Spec) { + fn set_spec(&mut self, spec: ::Spec) { self.cfg.set_spec(spec.clone()); self.journaled_state.set_spec_id(spec.into()); } @@ -180,7 +179,7 @@ impl Context::Spec>, + CFG: Cfg + SetSpecTr<::Spec>, DB: Database, JOURNAL: JournalTr, LOCAL: LocalContextTr, diff --git a/crates/handler/src/evm.rs b/crates/handler/src/evm.rs index 8f148f8a04..986ab2f041 100644 --- a/crates/handler/src/evm.rs +++ b/crates/handler/src/evm.rs @@ -139,11 +139,11 @@ pub trait EvmTrSetSpec: EvmTr { impl EvmTrSetSpec for Evm> where - CTX: ContextTr + SetSpecTr::Cfg as Cfg>::Spec>, + CTX: ContextTr + SetSpecTr<<::Cfg as Cfg>::Spec>, I: InstructionProvider - + SetSpecTr::Cfg as Cfg>::Spec>, + + SetSpecTr<<::Cfg as Cfg>::Spec>, P: PrecompileProvider - + SetSpecTr::Cfg as Cfg>::Spec>, + + SetSpecTr<<::Cfg as Cfg>::Spec>, { /// Sets the spec for the EVM. #[inline] diff --git a/crates/handler/src/instructions.rs b/crates/handler/src/instructions.rs index bdba952a9a..f38cf0897e 100644 --- a/crates/handler/src/instructions.rs +++ b/crates/handler/src/instructions.rs @@ -40,15 +40,14 @@ where } } -impl SetSpecTr for EthInstructions +impl + Clone> SetSpecTr for EthInstructions where WIRE: InterpreterTypes, HOST: Host, { - type Spec = SpecId; - #[inline] - fn set_spec(&mut self, spec: Self::Spec) { + fn set_spec(&mut self, spec: SPEC) { + let spec = spec.into(); if spec == self.spec { return; } diff --git a/crates/handler/src/precompile_provider.rs b/crates/handler/src/precompile_provider.rs index b2992c0a8e..f39aaffbdd 100644 --- a/crates/handler/src/precompile_provider.rs +++ b/crates/handler/src/precompile_provider.rs @@ -49,7 +49,6 @@ pub struct EthPrecompiles { } impl EthPrecompiles { - /// Create a new precompile provider with the given spec. pub fn new(spec: SpecId) -> Self { Self { @@ -70,10 +69,9 @@ impl EthPrecompiles { } } -impl SetSpecTr for EthPrecompiles { - type Spec = SpecId; - - fn set_spec(&mut self, spec: Self::Spec) { +impl + Clone> SetSpecTr for EthPrecompiles { + fn set_spec(&mut self, spec: SPEC) { + let spec = spec.into(); if spec == self.spec { return; } diff --git a/crates/op-revm/src/evm.rs b/crates/op-revm/src/evm.rs index 9cf5ca4d88..ba4eb302a8 100644 --- a/crates/op-revm/src/evm.rs +++ b/crates/op-revm/src/evm.rs @@ -35,7 +35,7 @@ impl + Clone>>, INSP> Self(Evm { ctx, inspector, - instruction: EthInstructions::new_mainnet_with_spec(spec.clone().into()), + instruction: EthInstructions::new_mainnet_with_spec(spec.into()), precompiles: OpPrecompiles::new_with_spec(spec), frame_stack: FrameStack::new_prealloc(8), }) @@ -97,7 +97,7 @@ where impl EvmTrSetSpec for OpEvm> where - CTX: ContextTr + SetSpecTr::Cfg as Cfg>::Spec>, + CTX: ContextTr + SetSpecTr<<::Cfg as Cfg>::Spec>, I: InstructionProvider, P: PrecompileProvider, { From 92c6a93e6b89809d2908239e15a449a76e4cfda9 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 17 Dec 2025 13:07:05 +0100 Subject: [PATCH 05/19] nit --- crates/context/interface/src/cfg/gas_params.rs | 2 +- crates/handler/src/precompile_provider.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/context/interface/src/cfg/gas_params.rs b/crates/context/interface/src/cfg/gas_params.rs index fd8e5eab41..f98373bdc5 100644 --- a/crates/context/interface/src/cfg/gas_params.rs +++ b/crates/context/interface/src/cfg/gas_params.rs @@ -81,7 +81,7 @@ impl GasParams { /// Use to override default gas cost /// /// ```rust - /// use revm_interpreter::gas::params::{GasParams, GasId}; + /// use revm_context_interface::cfg::gas_params::{GasParams, GasId}; /// use primitives::hardfork::SpecId; /// /// let mut gas_table = GasParams::new_spec(SpecId::default()); diff --git a/crates/handler/src/precompile_provider.rs b/crates/handler/src/precompile_provider.rs index f39aaffbdd..52567736ca 100644 --- a/crates/handler/src/precompile_provider.rs +++ b/crates/handler/src/precompile_provider.rs @@ -18,9 +18,6 @@ pub trait PrecompileProvider { /// Sets the spec id and returns true if the spec id was changed. Initial call to set_spec will always return true. /// /// Returns `true` if precompile addresses should be injected into the journal. - #[deprecated( - note = "We are moving away from runtime setting off spec to setting spec in initialization. Check EvmTrSetSpec trait for more information." - )] fn set_spec(&mut self, spec: ::Spec) -> bool; /// Run the precompile. From fc11160b1a747da42446c87d83245998635bd521 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 17 Dec 2025 13:18:32 +0100 Subject: [PATCH 06/19] doc tests --- crates/context/interface/src/cfg/gas_params.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/context/interface/src/cfg/gas_params.rs b/crates/context/interface/src/cfg/gas_params.rs index f98373bdc5..5ca0fd40eb 100644 --- a/crates/context/interface/src/cfg/gas_params.rs +++ b/crates/context/interface/src/cfg/gas_params.rs @@ -503,7 +503,7 @@ impl GasId { /// # Examples /// /// ``` - /// use revm_interpreter::gas::params::GasId; + /// use revm_context_interface::cfg::gas_params::GasId; /// /// assert_eq!(GasId::exp_byte_gas().name(), "exp_byte_gas"); /// assert_eq!(GasId::memory_linear_cost().name(), "memory_linear_cost"); @@ -557,7 +557,7 @@ impl GasId { /// # Examples /// /// ``` - /// use revm_interpreter::gas::params::GasId; + /// use revm_context_interface::cfg::gas_params::GasId; /// /// assert_eq!(GasId::from_name("exp_byte_gas"), Some(GasId::exp_byte_gas())); /// assert_eq!(GasId::from_name("memory_linear_cost"), Some(GasId::memory_linear_cost())); From a552ff9865ae3b3f183dc51cbdac791c25f7eab7 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 17 Dec 2025 13:58:53 +0100 Subject: [PATCH 07/19] use derive-where to skip fn --- crates/context/src/cfg.rs | 5 ++++- crates/handler/src/instructions.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 8d955120ef..de375821a1 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -2,6 +2,7 @@ pub use context_interface::Cfg; use context_interface::cfg::{GasParams, SetSpecTr}; +use derive_where::derive_where; use primitives::{eip170, eip3860, eip7825, hardfork::SpecId}; /// Gas params override function. @@ -9,7 +10,8 @@ pub type GasParamsOverrideFn = fn(SPEC) -> GasParams; /// EVM configuration #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug)] +#[derive_where(Eq, PartialEq; SPEC)] #[non_exhaustive] pub struct CfgEnv { /// Specification for EVM represent the hardfork @@ -66,6 +68,7 @@ pub struct CfgEnv { /// /// If this is set, it will override the gas params with this function. #[cfg_attr(feature = "serde", serde(skip))] + #[derive_where(skip)] pub gas_params_override: Option>, /// A hard memory limit in bytes beyond which /// [OutOfGasError::Memory][context_interface::result::OutOfGasError::Memory] cannot be resized. diff --git a/crates/handler/src/instructions.rs b/crates/handler/src/instructions.rs index f38cf0897e..884835a13f 100644 --- a/crates/handler/src/instructions.rs +++ b/crates/handler/src/instructions.rs @@ -52,7 +52,7 @@ where return; } self.spec = spec; - self.instruction_table = Box::new(instruction_table_gas_changes_spec(spec)); + *self.instruction_table = instruction_table_gas_changes_spec(spec); } } From c4fc69b1ec47495a5ed1186aa78a41173bd2f731 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 18 Dec 2025 07:15:11 +0100 Subject: [PATCH 08/19] Move SetSpecTr and use it as main tr --- Cargo.lock | 1 + bins/revme/src/cmd/blockchaintest.rs | 5 ++- bins/revme/src/cmd/statetest/runner.rs | 5 ++- crates/context/interface/src/cfg.rs | 6 --- crates/context/interface/src/lib.rs | 2 +- crates/context/src/cfg.rs | 52 +++++++++-------------- crates/context/src/context.rs | 7 +-- crates/context/src/evm.rs | 19 ++++++++- crates/ee-tests/src/revm_tests.rs | 6 ++- crates/handler/src/evm.rs | 31 +------------- crates/handler/src/instructions.rs | 3 +- crates/handler/src/lib.rs | 2 +- crates/handler/src/mainnet_builder.rs | 5 ++- crates/handler/src/precompile_provider.rs | 7 ++- crates/handler/src/validation.rs | 4 +- crates/op-revm/src/evm.rs | 16 ++++--- crates/op-revm/src/handler.rs | 4 +- crates/op-revm/src/precompiles.rs | 14 +++++- crates/primitives/Cargo.toml | 1 + crates/primitives/src/hardfork.rs | 8 ++++ crates/statetest-types/src/test_unit.rs | 1 + examples/erc20_gas/src/main.rs | 4 +- 22 files changed, 107 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa8cef2155..8f21a59689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3718,6 +3718,7 @@ name = "revm-primitives" version = "21.0.2" dependencies = [ "alloy-primitives", + "auto_impl", "num_enum", "once_cell", "serde", diff --git a/bins/revme/src/cmd/blockchaintest.rs b/bins/revme/src/cmd/blockchaintest.rs index c0c12a9764..851363554c 100644 --- a/bins/revme/src/cmd/blockchaintest.rs +++ b/bins/revme/src/cmd/blockchaintest.rs @@ -10,7 +10,10 @@ use revm::{ database::{states::bundle_state::BundleRetention, EmptyDB, State}, handler::EvmTr, inspector::inspectors::TracerEip3155, - primitives::{hardfork::SpecId, hex, Address, HashMap, U256}, + primitives::{ + hardfork::{SetSpecTr, SpecId}, + hex, Address, HashMap, U256, + }, state::AccountInfo, Context, Database, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainBuilder, MainContext, }; diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 58f0f1e49c..ab9a5d9c2c 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -6,7 +6,10 @@ use revm::{ database, database_interface::EmptyDB, inspector::{inspectors::TracerEip3155, InspectCommitEvm}, - primitives::{hardfork::SpecId, Bytes, B256, U256}, + primitives::{ + hardfork::{SetSpecTr, SpecId}, + Bytes, B256, U256, + }, Context, ExecuteCommitEvm, MainBuilder, MainContext, }; use serde_json::json; diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index 283dba1d6c..cb107693d6 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -77,12 +77,6 @@ pub trait Cfg { fn gas_params(&self) -> &GasParams; } -/// Sets the spec for the `Cfg`. -pub trait SetSpecTr + Clone> { - /// Sets the spec for the `Cfg`. - fn set_spec(&mut self, spec: SPEC); -} - /// What bytecode analysis to perform #[derive(Clone, Default, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/context/interface/src/lib.rs b/crates/context/interface/src/lib.rs index 41ee615f32..cdf8a79c38 100644 --- a/crates/context/interface/src/lib.rs +++ b/crates/context/interface/src/lib.rs @@ -15,7 +15,7 @@ pub mod result; pub mod transaction; pub use block::Block; -pub use cfg::{Cfg, CreateScheme, SetSpecTr, TransactTo}; +pub use cfg::{Cfg, CreateScheme, TransactTo}; pub use context::{ContextError, ContextSetters, ContextTr}; pub use database_interface::{erased_error::ErasedError, DBErrorMarker, Database}; pub use either; diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index de375821a1..3470564bc3 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -1,12 +1,12 @@ //! This module contains [`CfgEnv`] and implements [`Cfg`] trait for it. pub use context_interface::Cfg; -use context_interface::cfg::{GasParams, SetSpecTr}; +use context_interface::cfg::GasParams; use derive_where::derive_where; -use primitives::{eip170, eip3860, eip7825, hardfork::SpecId}; - -/// Gas params override function. -pub type GasParamsOverrideFn = fn(SPEC) -> GasParams; +use primitives::{ + eip170, eip3860, eip7825, + hardfork::{SetSpecTr, SpecId}, +}; /// EVM configuration #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -69,7 +69,7 @@ pub struct CfgEnv { /// If this is set, it will override the gas params with this function. #[cfg_attr(feature = "serde", serde(skip))] #[derive_where(skip)] - pub gas_params_override: Option>, + pub gas_params_override: Option GasParams>, /// A hard memory limit in bytes beyond which /// [OutOfGasError::Memory][context_interface::result::OutOfGasError::Memory] cannot be resized. /// @@ -142,7 +142,7 @@ impl CfgEnv { } } -impl + Copy> CfgEnv { +impl + Clone> CfgEnv { /// Returns the blob base fee update fraction from [CfgEnv::blob_base_fee_update_fraction]. /// /// If this field is not set, return the default value for the spec. @@ -151,7 +151,7 @@ impl + Copy> CfgEnv { /// and for Prague is [`primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE`]. pub fn blob_base_fee_update_fraction(&mut self) -> u64 { self.blob_base_fee_update_fraction.unwrap_or_else(|| { - let spec: SpecId = self.spec.into(); + let spec: SpecId = self.spec.clone().into(); if spec.is_enabled_in(SpecId::PRAGUE) { primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE } else { @@ -159,9 +159,7 @@ impl + Copy> CfgEnv { } }) } -} -impl + Clone> CfgEnv { /// Create new `CfgEnv` with default values and specified spec. pub fn new_with_spec(spec: SPEC) -> Self { Self { @@ -203,26 +201,11 @@ impl + Clone> CfgEnv { self.spec.clone() } - /// Sets the spec for the `CfgEnv`. - /// - /// It will update the gas params to the new spec. - /// - /// Please use Evm::set_spec so other parts of the system are updated. - #[inline] - pub fn set_spec(&mut self, spec: SPEC) { - self.spec = spec.clone(); - - self.gas_params = self - .gas_params_override - .map(|override_fn| override_fn(spec.clone())) - .unwrap_or_else(|| GasParams::new_spec(spec.into())); - } - /// Sets the gas params override function. /// - /// It will override the gas params with this function. + /// Every change in spec will call `override_fn` to update [`GasParams`]. #[inline] - pub fn set_gas_params_override(&mut self, override_fn: GasParamsOverrideFn) { + pub fn set_gas_params_override(&mut self, override_fn: fn(SPEC) -> GasParams) { self.gas_params_override = Some(override_fn); self.gas_params = override_fn(self.spec.clone()); } @@ -321,7 +304,7 @@ impl + Clone> CfgEnv { } } -impl + Copy> Cfg for CfgEnv { +impl + Clone> Cfg for CfgEnv { type Spec = SPEC; #[inline] @@ -331,7 +314,7 @@ impl + Copy> Cfg for CfgEnv { #[inline] fn spec(&self) -> Self::Spec { - self.spec + self.spec.clone() } #[inline] @@ -342,7 +325,7 @@ impl + Copy> Cfg for CfgEnv { #[inline] fn tx_gas_limit_cap(&self) -> u64 { self.tx_gas_limit_cap - .unwrap_or(if self.spec.into().is_enabled_in(SpecId::OSAKA) { + .unwrap_or(if self.spec.clone().into().is_enabled_in(SpecId::OSAKA) { eip7825::TX_GAS_LIMIT_CAP } else { u64::MAX @@ -472,11 +455,16 @@ impl + Copy> Cfg for CfgEnv { impl + Clone> SetSpecTr for CfgEnv { #[inline] fn set_spec(&mut self, spec: SPEC) { - self.set_spec(spec); + self.spec = spec.clone(); + + self.gas_params = self + .gas_params_override + .map(|override_fn| override_fn(spec.clone())) + .unwrap_or_else(|| GasParams::new_spec(spec.into())); } } -impl + Copy> Default for CfgEnv { +impl + Clone> Default for CfgEnv { fn default() -> Self { Self::new_with_spec(SPEC::default()) } diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index 7d20ac48eb..e0bd3d1a8e 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -5,13 +5,14 @@ use context_interface::{ context::{ContextError, ContextSetters, SStoreResult, SelfDestructResult, StateLoad}, host::LoadError, journaled_state::AccountInfoLoad, - Block, Cfg, ContextTr, Host, JournalTr, LocalContextTr, SetSpecTr, Transaction, - TransactionType, + Block, Cfg, ContextTr, Host, JournalTr, LocalContextTr, Transaction, TransactionType, }; use database_interface::{Database, DatabaseRef, EmptyDB, WrapDatabaseRef}; use derive_where::derive_where; use primitives::{ - hardfork::SpecId, hints_util::cold_path, Address, Log, StorageKey, StorageValue, B256, U256, + hardfork::{SetSpecTr, SpecId}, + hints_util::cold_path, + Address, Log, StorageKey, StorageValue, B256, U256, }; /// EVM context contains data that EVM needs for execution. diff --git a/crates/context/src/evm.rs b/crates/context/src/evm.rs index 283fa917b2..fc424066a5 100644 --- a/crates/context/src/evm.rs +++ b/crates/context/src/evm.rs @@ -4,7 +4,8 @@ use core::{ ops::{Deref, DerefMut}, }; -use context_interface::FrameStack; +use context_interface::{Cfg, ContextTr, FrameStack}; +use primitives::hardfork::SetSpecTr; /// Main EVM structure that contains all data needed for execution. #[derive(Debug, Clone)] @@ -95,3 +96,19 @@ impl DerefMut for Evm { &mut self.ctx } } + +impl SetSpecTr<<::Cfg as Cfg>::Spec> + for Evm +where + CTX: ContextTr + SetSpecTr<<::Cfg as Cfg>::Spec>, + I: SetSpecTr<<::Cfg as Cfg>::Spec>, + P: SetSpecTr<<::Cfg as Cfg>::Spec>, +{ + /// Sets the spec for the EVM. + #[inline] + fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { + self.ctx.set_spec(spec.clone()); + self.precompiles.set_spec(spec.clone()); + self.instruction.set_spec(spec); + } +} diff --git a/crates/ee-tests/src/revm_tests.rs b/crates/ee-tests/src/revm_tests.rs index 10efb689ff..9ae5cc3c87 100644 --- a/crates/ee-tests/src/revm_tests.rs +++ b/crates/ee-tests/src/revm_tests.rs @@ -5,7 +5,11 @@ use revm::{ bytecode::opcode, context::{ContextTr, TxEnv}, database::{BenchmarkDB, BENCH_CALLER, BENCH_TARGET}, - primitives::{address, b256, hardfork::SpecId, Bytes, TxKind, KECCAK_EMPTY, U256}, + primitives::{ + address, b256, + hardfork::{SetSpecTr, SpecId}, + Bytes, TxKind, KECCAK_EMPTY, U256, + }, state::{AccountStatus, Bytecode}, Context, ExecuteEvm, MainBuilder, MainContext, }; diff --git a/crates/handler/src/evm.rs b/crates/handler/src/evm.rs index 986ab2f041..0ad3130f0c 100644 --- a/crates/handler/src/evm.rs +++ b/crates/handler/src/evm.rs @@ -3,7 +3,7 @@ use crate::{ ItemOrResult, PrecompileProvider, }; use auto_impl::auto_impl; -use context::{Cfg, ContextTr, Database, Evm, FrameStack, SetSpecTr}; +use context::{ContextTr, Database, Evm, FrameStack}; use context_interface::context::ContextError; use interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, InterpreterResult}; @@ -125,35 +125,6 @@ pub trait EvmTr { ) -> Result::FrameResult>, ContextDbError>; } -/// A trait that integrates context, instruction set, and precompiles to create an EVM struct. -/// -/// In addition to execution capabilities, this trait provides getter methods for its component fields. -pub trait EvmTrSetSpec: EvmTr { - /// Sets the spec for the EVM. - fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec); -} - -// CTX: ContextTr + SetSpecTr::Cfg as Cfg>::Spec>, -// I: InstructionProvider, -// P: PrecompileProvider, - -impl EvmTrSetSpec for Evm> -where - CTX: ContextTr + SetSpecTr<<::Cfg as Cfg>::Spec>, - I: InstructionProvider - + SetSpecTr<<::Cfg as Cfg>::Spec>, - P: PrecompileProvider - + SetSpecTr<<::Cfg as Cfg>::Spec>, -{ - /// Sets the spec for the EVM. - #[inline] - fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { - self.ctx().set_spec(spec.clone()); - SetSpecTr::set_spec(&mut self.precompiles, spec.clone()); - self.instruction.set_spec(spec); - } -} - impl EvmTr for Evm> where CTX: ContextTr, diff --git a/crates/handler/src/instructions.rs b/crates/handler/src/instructions.rs index 884835a13f..24c91a9161 100644 --- a/crates/handler/src/instructions.rs +++ b/crates/handler/src/instructions.rs @@ -1,10 +1,9 @@ use auto_impl::auto_impl; -use context::SetSpecTr; use interpreter::{ instructions::{instruction_table_gas_changes_spec, InstructionTable}, Host, Instruction, InterpreterTypes, }; -use primitives::hardfork::SpecId; +use primitives::hardfork::{SetSpecTr, SpecId}; use std::boxed::Box; /// Stores instructions for EVM. diff --git a/crates/handler/src/lib.rs b/crates/handler/src/lib.rs index 26f34bc272..a220439545 100644 --- a/crates/handler/src/lib.rs +++ b/crates/handler/src/lib.rs @@ -33,7 +33,7 @@ pub mod validation; // Public exports pub use api::{ExecuteCommitEvm, ExecuteEvm}; -pub use evm::{EvmTr, EvmTrSetSpec, FrameTr}; +pub use evm::{EvmTr, FrameTr}; pub use frame::{return_create, ContextTrDbError, EthFrame}; pub use frame_data::{CallFrame, CreateFrame, FrameData, FrameResult}; pub use handler::{EvmTrError, Handler}; diff --git a/crates/handler/src/mainnet_builder.rs b/crates/handler/src/mainnet_builder.rs index 2ca08b2118..be2a0aab43 100644 --- a/crates/handler/src/mainnet_builder.rs +++ b/crates/handler/src/mainnet_builder.rs @@ -85,7 +85,10 @@ mod test { use context::{Context, TxEnv}; use context_interface::transaction::Authorization; use database::{BenchmarkDB, EEADDRESS, FFADDRESS}; - use primitives::{hardfork::SpecId, StorageKey, StorageValue, TxKind, U256}; + use primitives::{ + hardfork::{SetSpecTr, SpecId}, + StorageKey, StorageValue, TxKind, U256, + }; #[test] fn sanity_eip7702_tx() { diff --git a/crates/handler/src/precompile_provider.rs b/crates/handler/src/precompile_provider.rs index 52567736ca..5c80f97f88 100644 --- a/crates/handler/src/precompile_provider.rs +++ b/crates/handler/src/precompile_provider.rs @@ -1,9 +1,12 @@ use auto_impl::auto_impl; -use context::{Cfg, LocalContextTr, SetSpecTr}; +use context::{Cfg, LocalContextTr}; use context_interface::{ContextTr, JournalTr}; use interpreter::{CallInput, CallInputs, Gas, InstructionResult, InterpreterResult}; use precompile::{PrecompileError, PrecompileSpecId, Precompiles}; -use primitives::{hardfork::SpecId, Address, Bytes}; +use primitives::{ + hardfork::{SetSpecTr, SpecId}, + Address, Bytes, +}; use std::{ boxed::Box, string::{String, ToString}, diff --git a/crates/handler/src/validation.rs b/crates/handler/src/validation.rs index 85d0f1fdee..de689b63d0 100644 --- a/crates/handler/src/validation.rs +++ b/crates/handler/src/validation.rs @@ -265,7 +265,9 @@ mod tests { Context, ContextTr, TxEnv, }; use database::{CacheDB, EmptyDB}; - use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind, B256}; + use primitives::{ + address, eip3860, eip7907, hardfork::SetSpecTr, hardfork::SpecId, Bytes, TxKind, B256, + }; use state::{AccountInfo, Bytecode}; fn deploy_contract( diff --git a/crates/op-revm/src/evm.rs b/crates/op-revm/src/evm.rs index ba4eb302a8..56a5657289 100644 --- a/crates/op-revm/src/evm.rs +++ b/crates/op-revm/src/evm.rs @@ -1,15 +1,16 @@ //! Contains the `[OpEvm]` type and its implementation of the execution EVM traits. use crate::{precompiles::OpPrecompiles, OpSpecId}; use revm::{ - context::{Cfg, ContextError, ContextSetters, Evm, FrameStack, SetSpecTr}, + context::{Cfg, ContextError, ContextSetters, Evm, FrameStack}, context_interface::ContextTr, handler::{ - evm::{EvmTrSetSpec, FrameTr}, + evm::FrameTr, instructions::{EthInstructions, InstructionProvider}, EthFrame, EvmTr, FrameInitOrResult, ItemOrResult, PrecompileProvider, }, inspector::{InspectorEvmTr, JournalExt}, interpreter::{interpreter::EthInterpreter, InterpreterResult}, + primitives::hardfork::SetSpecTr, Database, Inspector, }; @@ -95,18 +96,19 @@ where } } -impl EvmTrSetSpec for OpEvm> +impl SetSpecTr<<::Cfg as Cfg>::Spec> + for OpEvm> where CTX: ContextTr + SetSpecTr<<::Cfg as Cfg>::Spec>, - I: InstructionProvider, - P: PrecompileProvider, + I: SetSpecTr<<::Cfg as Cfg>::Spec>, + P: SetSpecTr<<::Cfg as Cfg>::Spec>, { + /// Sets the spec for the EVM. #[inline] - fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { + fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { self.0.set_spec(spec); } } - impl EvmTr for OpEvm> where CTX: ContextTr, diff --git a/crates/op-revm/src/handler.rs b/crates/op-revm/src/handler.rs index b94d431dd0..e37f183190 100644 --- a/crates/op-revm/src/handler.rs +++ b/crates/op-revm/src/handler.rs @@ -465,9 +465,9 @@ mod tests { context_interface::result::InvalidTransaction, database::InMemoryDB, database_interface::EmptyDB, - handler::{evm::EvmTrSetSpec, EthFrame}, + handler::EthFrame, interpreter::{CallOutcome, InstructionResult, InterpreterResult}, - primitives::{bytes, Address, Bytes, B256}, + primitives::{bytes, hardfork::SetSpecTr, Address, Bytes, B256}, state::AccountInfo, }; use rstest::rstest; diff --git a/crates/op-revm/src/precompiles.rs b/crates/op-revm/src/precompiles.rs index d8fe69a10d..4c6fdd3ec4 100644 --- a/crates/op-revm/src/precompiles.rs +++ b/crates/op-revm/src/precompiles.rs @@ -9,7 +9,10 @@ use revm::{ self, bn254, secp256r1, Precompile, PrecompileError, PrecompileId, PrecompileResult, Precompiles, }, - primitives::{hardfork::SpecId, Address, OnceLock}, + primitives::{ + hardfork::{SetSpecTr, SpecId}, + Address, OnceLock, + }, }; use std::{boxed::Box, string::String}; @@ -121,6 +124,15 @@ pub fn jovian() -> &'static Precompiles { }) } +impl SetSpecTr for OpPrecompiles { + fn set_spec(&mut self, spec: OpSpecId) { + if spec == self.spec { + return; + } + *self = Self::new_with_spec(spec); + } +} + impl PrecompileProvider for OpPrecompiles where CTX: ContextTr>, diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 044b3eb21c..b3b1fe9049 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -27,6 +27,7 @@ once_cell = { version = "1.21", default-features = false, features = [ "alloc", "race", ] } +auto_impl.workspace = true # Optional serde = { workspace = true, features = ["derive", "rc"], optional = true } diff --git a/crates/primitives/src/hardfork.rs b/crates/primitives/src/hardfork.rs index b094b2567b..5259bfb84e 100644 --- a/crates/primitives/src/hardfork.rs +++ b/crates/primitives/src/hardfork.rs @@ -2,6 +2,7 @@ // enumn has missing docs. Should be replaced in the future https://github.com/bluealloy/revm/issues/2402 #![allow(missing_docs)] +use auto_impl::auto_impl; use core::str::FromStr; pub use num_enum::TryFromPrimitive; pub use std::string::{String, ToString}; @@ -210,3 +211,10 @@ impl core::fmt::Display for SpecId { write!(f, "{}", <&'static str>::from(*self)) } } + +/// Utility interface that sets the spec for structs that implements it. +#[auto_impl(&mut, Box)] +pub trait SetSpecTr + Clone> { + /// Sets the spec for the struct. + fn set_spec(&mut self, spec: SPEC); +} diff --git a/crates/statetest-types/src/test_unit.rs b/crates/statetest-types/src/test_unit.rs index 3a730956c8..5fe9b3764b 100644 --- a/crates/statetest-types/src/test_unit.rs +++ b/crates/statetest-types/src/test_unit.rs @@ -136,6 +136,7 @@ mod tests { context_interface::block::calc_blob_gasprice, primitives::{ eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE}, + hardfork::SetSpecTr, U256, }, }; diff --git a/examples/erc20_gas/src/main.rs b/examples/erc20_gas/src/main.rs index acfcd48f0f..41528a9182 100644 --- a/examples/erc20_gas/src/main.rs +++ b/examples/erc20_gas/src/main.rs @@ -85,9 +85,7 @@ fn balance_of(address: Address, alloy_db: &mut AlloyCacheDB) -> Result Result<()> { let mut ctx = Context::mainnet() .with_db(cache_db) - .modify_cfg_chained(|cfg| { - cfg.set_spec(SpecId::CANCUN); - }) + .with_spec(SpecId::CANCUN) .with_tx( TxEnv::builder() .caller(from) From 21f8548354decfbf5578bd02d08892bcee94b04a Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 22 Dec 2025 13:26:00 +0100 Subject: [PATCH 09/19] remove SetSpecTr, introduce is_custom_gas_param flag --- Cargo.lock | 1 - bins/revme/src/cmd/blockchaintest.rs | 9 +-- bins/revme/src/cmd/statetest/runner.rs | 11 ++-- crates/context/interface/src/cfg.rs | 7 ++ crates/context/src/cfg.rs | 78 +++++++++++------------ crates/context/src/context.rs | 40 +----------- crates/context/src/evm.rs | 19 +----- crates/ee-tests/src/op_revm_tests.rs | 46 ++++++------- crates/ee-tests/src/revm_tests.rs | 14 ++-- crates/handler/src/instructions.rs | 18 +----- crates/handler/src/mainnet_builder.rs | 11 ++-- crates/handler/src/precompile_provider.rs | 19 +----- crates/handler/src/validation.rs | 6 +- crates/op-revm/src/evm.rs | 19 ++---- crates/op-revm/src/handler.rs | 52 ++++++++------- crates/op-revm/src/precompiles.rs | 14 +--- crates/primitives/Cargo.toml | 1 - crates/primitives/src/hardfork.rs | 8 --- crates/statetest-types/src/test_unit.rs | 10 +-- examples/erc20_gas/src/main.rs | 4 +- 20 files changed, 129 insertions(+), 258 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f21a59689..fa8cef2155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3718,7 +3718,6 @@ name = "revm-primitives" version = "21.0.2" dependencies = [ "alloy-primitives", - "auto_impl", "num_enum", "once_cell", "serde", diff --git a/bins/revme/src/cmd/blockchaintest.rs b/bins/revme/src/cmd/blockchaintest.rs index 851363554c..e6eaacc9d3 100644 --- a/bins/revme/src/cmd/blockchaintest.rs +++ b/bins/revme/src/cmd/blockchaintest.rs @@ -10,10 +10,7 @@ use revm::{ database::{states::bundle_state::BundleRetention, EmptyDB, State}, handler::EvmTr, inspector::inspectors::TracerEip3155, - primitives::{ - hardfork::{SetSpecTr, SpecId}, - hex, Address, HashMap, U256, - }, + primitives::{hardfork::SpecId, hex, Address, HashMap, U256}, state::AccountInfo, Context, Database, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainBuilder, MainContext, }; @@ -688,7 +685,7 @@ fn execute_blockchain_test( // Setup configuration based on fork let spec_id = fork_to_spec_id(test_case.network); let mut cfg = CfgEnv::default(); - cfg.set_spec(spec_id); + cfg.spec = spec_id; // Genesis block is not used yet. let mut parent_block_hash = Some(test_case.genesis_block_header.hash); @@ -729,7 +726,7 @@ fn execute_blockchain_test( // Create EVM context for each transaction to ensure fresh state access let evm_context = Context::mainnet() .with_block(&block_env) - .with_cfg(&cfg) + .with_cfg(cfg.clone()) .with_db(&mut state); // Build and execute with EVM - always use inspector when JSON output is enabled diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index ab9a5d9c2c..cc14bff2ee 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -6,10 +6,7 @@ use revm::{ database, database_interface::EmptyDB, inspector::{inspectors::TracerEip3155, InspectCommitEvm}, - primitives::{ - hardfork::{SetSpecTr, SpecId}, - Bytes, B256, U256, - }, + primitives::{hardfork::SpecId, Bytes, B256, U256}, Context, ExecuteCommitEvm, MainBuilder, MainContext, }; use serde_json::json; @@ -328,7 +325,7 @@ pub fn execute_test_suite( continue; } - cfg.set_spec(spec_name.to_spec_id()); + cfg.spec = spec_name.to_spec_id(); // Configure max blobs per spec if cfg.spec().is_enabled_in(SpecId::OSAKA) { @@ -419,7 +416,7 @@ fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> { let evm_context = Context::mainnet() .with_block(ctx.block) .with_tx(ctx.tx) - .with_cfg(ctx.cfg) + .with_cfg(ctx.cfg.clone()) .with_db(&mut state); // Execute @@ -465,7 +462,7 @@ fn debug_failed_test(ctx: DebugContext) { .with_db(&mut state) .with_block(ctx.block) .with_tx(ctx.tx) - .with_cfg(ctx.cfg) + .with_cfg(ctx.cfg.clone()) .build_mainnet_with_inspector(TracerEip3155::buffered(stderr()).without_summary()); let exec_result = evm.inspect_tx_commit(ctx.tx); diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index cb107693d6..405480735b 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -77,6 +77,13 @@ pub trait Cfg { fn gas_params(&self) -> &GasParams; } +/// Trait for initializing the configuration. +#[auto_impl(&mut, Box)] +pub trait InitializeCfg { + /// Utility function called when EVM is build. Used to set the gas params. + fn initialize(&mut self); +} + /// What bytecode analysis to perform #[derive(Clone, Default, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 3470564bc3..3fd338bb8e 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -1,21 +1,23 @@ //! This module contains [`CfgEnv`] and implements [`Cfg`] trait for it. pub use context_interface::Cfg; -use context_interface::cfg::GasParams; -use derive_where::derive_where; -use primitives::{ - eip170, eip3860, eip7825, - hardfork::{SetSpecTr, SpecId}, -}; +use context_interface::cfg::{GasParams, InitializeCfg}; +use primitives::{eip170, eip3860, eip7825, hardfork::SpecId}; /// EVM configuration #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Debug)] -#[derive_where(Eq, PartialEq; SPEC)] +#[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub struct CfgEnv { /// Specification for EVM represent the hardfork - spec: SPEC, + pub spec: SPEC, + + /// Gas params for the EVM. Use [`CfgEnv::set_gas_params`] to set the gas params. + /// If gas_params was not set it will be set to the default gas params for the spec. + gas_params: GasParams, + + /// If gas params were set this will be set to true. + is_custom_gas_params: bool, /// Chain ID of the EVM. Used in CHAINID opcode and transaction's chain ID check. /// /// Chain ID is introduced EIP-155. @@ -62,14 +64,6 @@ pub struct CfgEnv { /// Introduced in Osaka in [EIP-7825: Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825) /// with initials cap of 30M. pub tx_gas_limit_cap: Option, - /// Gas params for the EVM. - pub gas_params: GasParams, - /// Gas params override function. - /// - /// If this is set, it will override the gas params with this function. - #[cfg_attr(feature = "serde", serde(skip))] - #[derive_where(skip)] - pub gas_params_override: Option GasParams>, /// A hard memory limit in bytes beyond which /// [OutOfGasError::Memory][context_interface::result::OutOfGasError::Memory] cannot be resized. /// @@ -135,6 +129,17 @@ pub struct CfgEnv { pub disable_fee_charge: bool, } +impl + Clone> InitializeCfg for CfgEnv { + fn initialize(&mut self) { + if !self.is_custom_gas_params { + return; + } + self.is_custom_gas_params = false; + + self.gas_params = GasParams::new_spec(self.spec.clone().into()); + } +} + impl CfgEnv { /// Creates new `CfgEnv` with default values. pub fn new() -> Self { @@ -173,7 +178,7 @@ impl + Clone> CfgEnv { tx_gas_limit_cap: None, blob_base_fee_update_fraction: None, gas_params: GasParams::new_spec(spec.into()), - gas_params_override: None, + is_custom_gas_params: false, #[cfg(feature = "memory_limit")] memory_limit: (1 << 32) - 1, #[cfg(feature = "optional_balance_check")] @@ -201,21 +206,26 @@ impl + Clone> CfgEnv { self.spec.clone() } - /// Sets the gas params override function. - /// - /// Every change in spec will call `override_fn` to update [`GasParams`]. - #[inline] - pub fn set_gas_params_override(&mut self, override_fn: fn(SPEC) -> GasParams) { - self.gas_params_override = Some(override_fn); - self.gas_params = override_fn(self.spec.clone()); - } - /// Consumes `self` and returns a new `CfgEnv` with the specified chain ID. pub fn with_chain_id(mut self, chain_id: u64) -> Self { self.chain_id = chain_id; self } + /// Sets the gas params for the `CfgEnv`. + #[inline] + pub fn with_gas_params(mut self, gas_params: GasParams) -> Self { + self.set_gas_params(gas_params); + self + } + + /// Sets the gas params for the `CfgEnv`. + #[inline] + pub fn set_gas_params(&mut self, gas_params: GasParams) { + self.gas_params = gas_params; + self.is_custom_gas_params = true; + } + /// Enables the transaction's chain ID check. pub fn enable_tx_chain_id_check(mut self) -> Self { self.tx_chain_id_check = true; @@ -244,7 +254,7 @@ impl + Clone> CfgEnv { max_blobs_per_tx: self.max_blobs_per_tx, blob_base_fee_update_fraction: self.blob_base_fee_update_fraction, gas_params: GasParams::new_spec(eth_spec), - gas_params_override: None, + is_custom_gas_params: false, #[cfg(feature = "memory_limit")] memory_limit: self.memory_limit, #[cfg(feature = "optional_balance_check")] @@ -452,18 +462,6 @@ impl + Clone> Cfg for CfgEnv { } } -impl + Clone> SetSpecTr for CfgEnv { - #[inline] - fn set_spec(&mut self, spec: SPEC) { - self.spec = spec.clone(); - - self.gas_params = self - .gas_params_override - .map(|override_fn| override_fn(spec.clone())) - .unwrap_or_else(|| GasParams::new_spec(spec.into())); - } -} - impl + Clone> Default for CfgEnv { fn default() -> Self { Self::new_with_spec(SPEC::default()) diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index e0bd3d1a8e..de226f3342 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -10,9 +10,7 @@ use context_interface::{ use database_interface::{Database, DatabaseRef, EmptyDB, WrapDatabaseRef}; use derive_where::derive_where; use primitives::{ - hardfork::{SetSpecTr, SpecId}, - hints_util::cold_path, - Address, Log, StorageKey, StorageValue, B256, U256, + hardfork::SpecId, hints_util::cold_path, Address, Log, StorageKey, StorageValue, B256, U256, }; /// EVM context contains data that EVM needs for execution. @@ -157,42 +155,6 @@ impl< } } -impl SetSpecTr - for Context -where - BLOCK: Block, - TX: Transaction, - CFG: Cfg + SetSpecTr, - DB: Database, - JOURNAL: JournalTr, - LOCAL: LocalContextTr, - SPEC: Into + Clone, -{ - /// Sets the spec for the context. - #[inline] - fn set_spec(&mut self, spec: ::Spec) { - self.cfg.set_spec(spec.clone()); - self.journaled_state.set_spec_id(spec.into()); - } -} - -impl Context -where - BLOCK: Block, - TX: Transaction, - CFG: Cfg + SetSpecTr<::Spec>, - DB: Database, - JOURNAL: JournalTr, - LOCAL: LocalContextTr, -{ - /// Creates a new context with a new spec. - #[inline] - pub fn with_spec(mut self, spec: ::Spec) -> Self { - self.set_spec(spec); - self - } -} - impl Context where BLOCK: Block, diff --git a/crates/context/src/evm.rs b/crates/context/src/evm.rs index fc424066a5..283fa917b2 100644 --- a/crates/context/src/evm.rs +++ b/crates/context/src/evm.rs @@ -4,8 +4,7 @@ use core::{ ops::{Deref, DerefMut}, }; -use context_interface::{Cfg, ContextTr, FrameStack}; -use primitives::hardfork::SetSpecTr; +use context_interface::FrameStack; /// Main EVM structure that contains all data needed for execution. #[derive(Debug, Clone)] @@ -96,19 +95,3 @@ impl DerefMut for Evm { &mut self.ctx } } - -impl SetSpecTr<<::Cfg as Cfg>::Spec> - for Evm -where - CTX: ContextTr + SetSpecTr<<::Cfg as Cfg>::Spec>, - I: SetSpecTr<<::Cfg as Cfg>::Spec>, - P: SetSpecTr<<::Cfg as Cfg>::Spec>, -{ - /// Sets the spec for the EVM. - #[inline] - fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { - self.ctx.set_spec(spec.clone()); - self.precompiles.set_spec(spec.clone()); - self.instruction.set_spec(spec); - } -} diff --git a/crates/ee-tests/src/op_revm_tests.rs b/crates/ee-tests/src/op_revm_tests.rs index c8183d07ca..525a199a8a 100644 --- a/crates/ee-tests/src/op_revm_tests.rs +++ b/crates/ee-tests/src/op_revm_tests.rs @@ -51,7 +51,7 @@ fn test_deposit_tx() { .source_hash(revm::primitives::B256::from([1u8; 32])) .build_fill(), ) - .with_spec(OpSpecId::HOLOCENE); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::HOLOCENE)); let mut evm = ctx.build_op(); @@ -83,7 +83,7 @@ fn test_halted_deposit_tx() { .source_hash(revm::primitives::B256::from([1u8; 32])) .build_fill(), ) - .with_spec(OpSpecId::HOLOCENE) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::HOLOCENE)) .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( [opcode::POP].into(), ))); @@ -127,7 +127,7 @@ fn p256verify_test_tx( ) .build_fill(), ) - .with_spec(SPEC_ID) + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)) } #[test] @@ -160,7 +160,7 @@ fn test_halted_tx_call_p256verify() { ) .build_fill(), ) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -196,7 +196,7 @@ fn bn254_pair_test_tx( ) .build_fill(), ) - .with_spec(spec) + .with_cfg(CfgEnv::new_with_spec(spec)) } #[test] @@ -253,7 +253,7 @@ fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); @@ -290,7 +290,7 @@ fn test_halted_tx_call_bls12_381_g1_add_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -339,7 +339,7 @@ fn g1_msm_test_tx( l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID) + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)) } #[test] @@ -369,7 +369,7 @@ fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -416,7 +416,7 @@ fn test_halted_tx_call_bls12_381_g1_msm_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -474,7 +474,7 @@ fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); @@ -511,7 +511,7 @@ fn test_halted_tx_call_bls12_381_g2_add_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); @@ -561,7 +561,7 @@ fn g2_msm_test_tx( l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID) + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)) } #[test] @@ -591,7 +591,7 @@ fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -638,7 +638,7 @@ fn test_halted_tx_call_bls12_381_g2_msm_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -707,7 +707,7 @@ fn bl12_381_pairing_test_tx( l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(OpSpecId::ISTHMUS) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)) } #[test] @@ -734,7 +734,7 @@ fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -778,7 +778,7 @@ fn test_halted_tx_call_bls12_381_pairing_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -842,7 +842,7 @@ fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -884,7 +884,7 @@ fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -926,7 +926,7 @@ fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -967,7 +967,7 @@ fn test_l1block_load_for_pre_regolith() { .modify_chain_chained(|l1_block| { l1_block.l2_block = None; }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx .with_db( @@ -1006,7 +1006,7 @@ fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .with_spec(SPEC_ID); + .with_cfg(CfgEnv::new_with_spec(SPEC_ID)); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); diff --git a/crates/ee-tests/src/revm_tests.rs b/crates/ee-tests/src/revm_tests.rs index 9ae5cc3c87..790a204d8c 100644 --- a/crates/ee-tests/src/revm_tests.rs +++ b/crates/ee-tests/src/revm_tests.rs @@ -3,13 +3,9 @@ use crate::TestdataConfig; use revm::{ bytecode::opcode, - context::{ContextTr, TxEnv}, + context::{CfgEnv, ContextTr, TxEnv}, database::{BenchmarkDB, BENCH_CALLER, BENCH_TARGET}, - primitives::{ - address, b256, - hardfork::{SetSpecTr, SpecId}, - Bytes, TxKind, KECCAK_EMPTY, U256, - }, + primitives::{address, b256, hardfork::SpecId, Bytes, TxKind, KECCAK_EMPTY, U256}, state::{AccountStatus, Bytecode}, Context, ExecuteEvm, MainBuilder, MainContext, }; @@ -42,7 +38,7 @@ const SELFDESTRUCT_BYTECODE: &[u8] = &[ #[test] fn test_selfdestruct_multi_tx() { let mut evm = Context::mainnet() - .modify_cfg_chained(|cfg| cfg.set_spec(SpecId::BERLIN)) + .with_cfg(CfgEnv::new_with_spec(SpecId::BERLIN)) .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( SELFDESTRUCT_BYTECODE.into(), ))) @@ -89,7 +85,7 @@ fn test_selfdestruct_multi_tx() { fn test_multi_tx_create() { let mut evm = Context::mainnet() .modify_cfg_chained(|cfg| { - cfg.set_spec(SpecId::BERLIN); + cfg.spec = SpecId::BERLIN; cfg.disable_nonce_check = true; }) .with_db(BenchmarkDB::new_bytecode(Bytecode::new())) @@ -221,7 +217,7 @@ fn deployment_contract(bytes: &[u8]) -> Bytes { #[test] fn test_frame_stack_index() { let mut evm = Context::mainnet() - .modify_cfg_chained(|cfg| cfg.set_spec(SpecId::BERLIN)) + .with_cfg(CfgEnv::new_with_spec(SpecId::BERLIN)) .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( SELFDESTRUCT_BYTECODE.into(), ))) diff --git a/crates/handler/src/instructions.rs b/crates/handler/src/instructions.rs index 24c91a9161..89bcf663c0 100644 --- a/crates/handler/src/instructions.rs +++ b/crates/handler/src/instructions.rs @@ -3,7 +3,7 @@ use interpreter::{ instructions::{instruction_table_gas_changes_spec, InstructionTable}, Host, Instruction, InterpreterTypes, }; -use primitives::hardfork::{SetSpecTr, SpecId}; +use primitives::hardfork::SpecId; use std::boxed::Box; /// Stores instructions for EVM. @@ -39,22 +39,6 @@ where } } -impl + Clone> SetSpecTr for EthInstructions -where - WIRE: InterpreterTypes, - HOST: Host, -{ - #[inline] - fn set_spec(&mut self, spec: SPEC) { - let spec = spec.into(); - if spec == self.spec { - return; - } - self.spec = spec; - *self.instruction_table = instruction_table_gas_changes_spec(spec); - } -} - impl EthInstructions where WIRE: InterpreterTypes, diff --git a/crates/handler/src/mainnet_builder.rs b/crates/handler/src/mainnet_builder.rs index be2a0aab43..61bc98b395 100644 --- a/crates/handler/src/mainnet_builder.rs +++ b/crates/handler/src/mainnet_builder.rs @@ -1,6 +1,6 @@ use crate::{frame::EthFrame, instructions::EthInstructions, EthPrecompiles}; use context::{BlockEnv, Cfg, CfgEnv, Context, Evm, FrameStack, Journal, TxEnv}; -use context_interface::{Block, Database, JournalTr, Transaction}; +use context_interface::{cfg::InitializeCfg, Block, Database, JournalTr, Transaction}; use database_interface::EmptyDB; use interpreter::interpreter::EthInterpreter; use primitives::hardfork::SpecId; @@ -29,7 +29,7 @@ impl MainBuilder for Context, { @@ -85,10 +85,7 @@ mod test { use context::{Context, TxEnv}; use context_interface::transaction::Authorization; use database::{BenchmarkDB, EEADDRESS, FFADDRESS}; - use primitives::{ - hardfork::{SetSpecTr, SpecId}, - StorageKey, StorageValue, TxKind, U256, - }; + use primitives::{hardfork::SpecId, StorageKey, StorageValue, TxKind, U256}; #[test] fn sanity_eip7702_tx() { @@ -104,7 +101,7 @@ mod test { let bytecode = Bytecode::new_legacy([PUSH1, 0x01, PUSH1, 0x01, SSTORE].into()); let ctx = Context::mainnet() - .modify_cfg_chained(|cfg| cfg.set_spec(SpecId::PRAGUE)) + .modify_cfg_chained(|cfg| cfg.spec = SpecId::PRAGUE) .with_db(BenchmarkDB::new_bytecode(bytecode)); let mut evm = ctx.build_mainnet(); diff --git a/crates/handler/src/precompile_provider.rs b/crates/handler/src/precompile_provider.rs index 5c80f97f88..5b835b99c9 100644 --- a/crates/handler/src/precompile_provider.rs +++ b/crates/handler/src/precompile_provider.rs @@ -3,10 +3,7 @@ use context::{Cfg, LocalContextTr}; use context_interface::{ContextTr, JournalTr}; use interpreter::{CallInput, CallInputs, Gas, InstructionResult, InterpreterResult}; use precompile::{PrecompileError, PrecompileSpecId, Precompiles}; -use primitives::{ - hardfork::{SetSpecTr, SpecId}, - Address, Bytes, -}; +use primitives::{hardfork::SpecId, Address, Bytes}; use std::{ boxed::Box, string::{String, ToString}, @@ -69,20 +66,6 @@ impl EthPrecompiles { } } -impl + Clone> SetSpecTr for EthPrecompiles { - fn set_spec(&mut self, spec: SPEC) { - let spec = spec.into(); - if spec == self.spec { - return; - } - self.precompiles = self - .spec_override_fn - .map(|override_fn| override_fn(spec)) - .unwrap_or_else(|| Precompiles::new(PrecompileSpecId::from_spec_id(spec))); - self.spec = spec; - } -} - impl Clone for EthPrecompiles { fn clone(&self) -> Self { Self { diff --git a/crates/handler/src/validation.rs b/crates/handler/src/validation.rs index de689b63d0..d9ba1c4415 100644 --- a/crates/handler/src/validation.rs +++ b/crates/handler/src/validation.rs @@ -265,9 +265,7 @@ mod tests { Context, ContextTr, TxEnv, }; use database::{CacheDB, EmptyDB}; - use primitives::{ - address, eip3860, eip7907, hardfork::SetSpecTr, hardfork::SpecId, Bytes, TxKind, B256, - }; + use primitives::{address, eip3860, eip7907, hardfork::SpecId, Bytes, TxKind, B256}; use state::{AccountInfo, Bytecode}; fn deploy_contract( @@ -277,7 +275,7 @@ mod tests { let ctx = Context::mainnet() .modify_cfg_chained(|c| { if let Some(spec_id) = spec_id { - c.set_spec(spec_id); + c.spec = spec_id; } }) .with_db(CacheDB::::default()); diff --git a/crates/op-revm/src/evm.rs b/crates/op-revm/src/evm.rs index 56a5657289..64f28a3e35 100644 --- a/crates/op-revm/src/evm.rs +++ b/crates/op-revm/src/evm.rs @@ -10,7 +10,6 @@ use revm::{ }, inspector::{InspectorEvmTr, JournalExt}, interpreter::{interpreter::EthInterpreter, InterpreterResult}, - primitives::hardfork::SetSpecTr, Database, Inspector, }; @@ -41,6 +40,11 @@ impl + Clone>>, INSP> frame_stack: FrameStack::new_prealloc(8), }) } + + /// Consumes self and returns the inner context. + pub fn into_context(self) -> CTX { + self.0.ctx + } } impl OpEvm { @@ -96,19 +100,6 @@ where } } -impl SetSpecTr<<::Cfg as Cfg>::Spec> - for OpEvm> -where - CTX: ContextTr + SetSpecTr<<::Cfg as Cfg>::Spec>, - I: SetSpecTr<<::Cfg as Cfg>::Spec>, - P: SetSpecTr<<::Cfg as Cfg>::Spec>, -{ - /// Sets the spec for the EVM. - #[inline] - fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { - self.0.set_spec(spec); - } -} impl EvmTr for OpEvm> where CTX: ContextTr, diff --git a/crates/op-revm/src/handler.rs b/crates/op-revm/src/handler.rs index e37f183190..ee2a90272b 100644 --- a/crates/op-revm/src/handler.rs +++ b/crates/op-revm/src/handler.rs @@ -461,13 +461,13 @@ mod tests { }; use alloy_primitives::uint; use revm::{ - context::{BlockEnv, Context, TxEnv}, + context::{BlockEnv, CfgEnv, Context, TxEnv}, context_interface::result::InvalidTransaction, database::InMemoryDB, database_interface::EmptyDB, handler::EthFrame, interpreter::{CallOutcome, InstructionResult, InterpreterResult}, - primitives::{bytes, hardfork::SetSpecTr, Address, Bytes, B256}, + primitives::{bytes, Address, Bytes, B256}, state::AccountInfo, }; use rstest::rstest; @@ -508,7 +508,7 @@ mod tests { .base(TxEnv::builder().gas_limit(100)) .build_fill(), ) - .with_spec(OpSpecId::BEDROCK); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)); let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90)); assert_eq!(gas.remaining(), 90); @@ -524,7 +524,7 @@ mod tests { .base(TxEnv::builder().gas_limit(100)) .build_fill(), ) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 90); @@ -541,7 +541,7 @@ mod tests { .source_hash(B256::from([1u8; 32])) .build_fill(), ) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut ret_gas = Gas::new(90); ret_gas.record_refund(20); @@ -566,7 +566,7 @@ mod tests { .source_hash(B256::from([1u8; 32])) .build_fill(), ) - .with_spec(OpSpecId::BEDROCK); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 0); assert_eq!(gas.spent(), 100); @@ -583,7 +583,7 @@ mod tests { .is_system_transaction() .build_fill(), ) - .with_spec(OpSpecId::BEDROCK); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 100); assert_eq!(gas.spent(), 0); @@ -610,7 +610,7 @@ mod tests { l1_base_fee_scalar: U256::from(1_000), ..Default::default() }) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); ctx.modify_tx(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.mint = Some(10); @@ -649,7 +649,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .with_spec(OpSpecId::REGOLITH) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(100)) @@ -722,7 +722,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); @@ -809,7 +809,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .with_spec(OpSpecId::JOVIAN) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::JOVIAN)) // set the operator fee to a low value .with_tx( OpTransaction::builder() @@ -877,7 +877,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); @@ -937,7 +937,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .with_spec(OpSpecId::ECOTONE); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ECOTONE)); let mut evm = ctx.build_op(); assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); @@ -1010,7 +1010,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); @@ -1060,7 +1060,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .with_spec(OpSpecId::REGOLITH) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(100)) @@ -1103,7 +1103,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .with_spec(OpSpecId::ISTHMUS) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(10)) @@ -1145,7 +1145,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .with_spec(OpSpecId::JOVIAN) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::JOVIAN)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(10)) @@ -1187,7 +1187,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .with_spec(OpSpecId::REGOLITH) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)) .modify_tx_chained(|tx| { tx.enveloped_tx = Some(bytes!("FACADE")); }); @@ -1218,7 +1218,7 @@ mod tests { tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.is_system_transaction = true; }) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let handler = @@ -1231,7 +1231,11 @@ mod tests { )) ); - evm.set_spec(OpSpecId::BEDROCK); + // With BEDROCK spec. + let ctx = evm.into_context(); + let mut evm = ctx + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .build_op(); // Pre-regolith system transactions should be allowed. assert!(handler.validate_env(&mut evm).is_ok()); @@ -1244,7 +1248,7 @@ mod tests { .modify_tx_chained(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); }) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let handler = @@ -1260,7 +1264,7 @@ mod tests { .modify_tx_chained(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); }) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let handler = @@ -1277,7 +1281,7 @@ mod tests { // Set up as deposit transaction by having a deposit with source_hash tx.deposit.source_hash = B256::from([1u8; 32]); }) - .with_spec(OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let mut handler = @@ -1360,7 +1364,7 @@ mod tests { }) .build_fill(), ) - .with_spec(OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); let handler = diff --git a/crates/op-revm/src/precompiles.rs b/crates/op-revm/src/precompiles.rs index 4c6fdd3ec4..d8fe69a10d 100644 --- a/crates/op-revm/src/precompiles.rs +++ b/crates/op-revm/src/precompiles.rs @@ -9,10 +9,7 @@ use revm::{ self, bn254, secp256r1, Precompile, PrecompileError, PrecompileId, PrecompileResult, Precompiles, }, - primitives::{ - hardfork::{SetSpecTr, SpecId}, - Address, OnceLock, - }, + primitives::{hardfork::SpecId, Address, OnceLock}, }; use std::{boxed::Box, string::String}; @@ -124,15 +121,6 @@ pub fn jovian() -> &'static Precompiles { }) } -impl SetSpecTr for OpPrecompiles { - fn set_spec(&mut self, spec: OpSpecId) { - if spec == self.spec { - return; - } - *self = Self::new_with_spec(spec); - } -} - impl PrecompileProvider for OpPrecompiles where CTX: ContextTr>, diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index b3b1fe9049..044b3eb21c 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -27,7 +27,6 @@ once_cell = { version = "1.21", default-features = false, features = [ "alloc", "race", ] } -auto_impl.workspace = true # Optional serde = { workspace = true, features = ["derive", "rc"], optional = true } diff --git a/crates/primitives/src/hardfork.rs b/crates/primitives/src/hardfork.rs index 5259bfb84e..b094b2567b 100644 --- a/crates/primitives/src/hardfork.rs +++ b/crates/primitives/src/hardfork.rs @@ -2,7 +2,6 @@ // enumn has missing docs. Should be replaced in the future https://github.com/bluealloy/revm/issues/2402 #![allow(missing_docs)] -use auto_impl::auto_impl; use core::str::FromStr; pub use num_enum::TryFromPrimitive; pub use std::string::{String, ToString}; @@ -211,10 +210,3 @@ impl core::fmt::Display for SpecId { write!(f, "{}", <&'static str>::from(*self)) } } - -/// Utility interface that sets the spec for structs that implements it. -#[auto_impl(&mut, Box)] -pub trait SetSpecTr + Clone> { - /// Sets the spec for the struct. - fn set_spec(&mut self, spec: SPEC); -} diff --git a/crates/statetest-types/src/test_unit.rs b/crates/statetest-types/src/test_unit.rs index 5fe9b3764b..42b8ef2be4 100644 --- a/crates/statetest-types/src/test_unit.rs +++ b/crates/statetest-types/src/test_unit.rs @@ -136,7 +136,6 @@ mod tests { context_interface::block::calc_blob_gasprice, primitives::{ eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE}, - hardfork::SetSpecTr, U256, }, }; @@ -188,8 +187,7 @@ mod tests { fn test_block_env_blob_fee_fraction_cancun() { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 - let mut cfg = CfgEnv::default(); - cfg.set_spec(SpecId::CANCUN); + let mut cfg = CfgEnv::new_with_spec(SpecId::CANCUN); let block = unit.block_env(&mut cfg); @@ -212,8 +210,7 @@ mod tests { fn test_block_env_blob_fee_fraction_prague() { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 - let mut cfg = CfgEnv::default(); - cfg.set_spec(SpecId::PRAGUE); + let mut cfg = CfgEnv::new_with_spec(SpecId::CANCUN); let block = unit.block_env(&mut cfg); @@ -235,8 +232,7 @@ mod tests { fn test_block_env_blob_fee_fraction_osaka() { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 - let mut cfg = CfgEnv::default(); - cfg.set_spec(SpecId::OSAKA); + let mut cfg = CfgEnv::new_with_spec(SpecId::CANCUN); let block = unit.block_env(&mut cfg); diff --git a/examples/erc20_gas/src/main.rs b/examples/erc20_gas/src/main.rs index 41528a9182..781ecf67ff 100644 --- a/examples/erc20_gas/src/main.rs +++ b/examples/erc20_gas/src/main.rs @@ -9,7 +9,7 @@ use alloy_sol_types::SolValue; use anyhow::Result; use exec::transact_erc20evm_commit; use revm::{ - context::TxEnv, + context::{CfgEnv, TxEnv}, database::{AlloyDB, BlockId, CacheDB}, database_interface::WrapDatabaseAsync, primitives::{ @@ -85,7 +85,7 @@ fn balance_of(address: Address, alloy_db: &mut AlloyCacheDB) -> Result Result<()> { let mut ctx = Context::mainnet() .with_db(cache_db) - .with_spec(SpecId::CANCUN) + .with_cfg(CfgEnv::new_with_spec(SpecId::CANCUN)) .with_tx( TxEnv::builder() .caller(from) From 9b627c938cb39cb202ca792ece04a6d58907587b Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 22 Dec 2025 16:36:14 +0100 Subject: [PATCH 10/19] simplification --- bins/revme/src/cmd/blockchaintest.rs | 2 +- bins/revme/src/cmd/statetest/runner.rs | 2 +- crates/context/interface/src/cfg.rs | 7 --- crates/context/src/cfg.rs | 70 +++++++++++++++++--------- crates/context/src/context.rs | 5 +- crates/ee-tests/src/revm_tests.rs | 2 +- crates/handler/src/mainnet_builder.rs | 6 +-- crates/handler/src/validation.rs | 2 +- examples/erc20_gas/src/main.rs | 6 ++- 9 files changed, 63 insertions(+), 39 deletions(-) diff --git a/bins/revme/src/cmd/blockchaintest.rs b/bins/revme/src/cmd/blockchaintest.rs index 8137cf0417..bc0ae33bfb 100644 --- a/bins/revme/src/cmd/blockchaintest.rs +++ b/bins/revme/src/cmd/blockchaintest.rs @@ -714,7 +714,7 @@ fn execute_blockchain_test( // Setup configuration based on fork let spec_id = fork_to_spec_id(test_case.network); let mut cfg = CfgEnv::default(); - cfg.spec = spec_id; + cfg.set_spec_and_mainnet_gas_params(spec_id); // Genesis block is not used yet. let mut parent_block_hash = Some(test_case.genesis_block_header.hash); diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 2ed7704c7a..1c00e2447b 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -337,7 +337,7 @@ pub fn execute_test_suite( continue; } - cfg.spec = spec_name.to_spec_id(); + cfg.set_spec_and_mainnet_gas_params(spec_name.to_spec_id()); // Configure max blobs per spec if cfg.spec().is_enabled_in(SpecId::OSAKA) { diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index 405480735b..cb107693d6 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -77,13 +77,6 @@ pub trait Cfg { fn gas_params(&self) -> &GasParams; } -/// Trait for initializing the configuration. -#[auto_impl(&mut, Box)] -pub trait InitializeCfg { - /// Utility function called when EVM is build. Used to set the gas params. - fn initialize(&mut self); -} - /// What bytecode analysis to perform #[derive(Clone, Default, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 3fd338bb8e..340a0b9906 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -1,7 +1,7 @@ //! This module contains [`CfgEnv`] and implements [`Cfg`] trait for it. pub use context_interface::Cfg; -use context_interface::cfg::{GasParams, InitializeCfg}; +use context_interface::cfg::GasParams; use primitives::{eip170, eip3860, eip7825, hardfork::SpecId}; /// EVM configuration @@ -10,14 +10,17 @@ use primitives::{eip170, eip3860, eip7825, hardfork::SpecId}; #[non_exhaustive] pub struct CfgEnv { /// Specification for EVM represent the hardfork + /// + /// [`CfgEnv::new_with_spec`] is going to set both gas params and spec. + /// + /// As GasParams is spec dependent, it is recommended to use one of followin function to set both of them. + /// [`CfgEnv::set_spec_and_mainnet_gas_params`], [`CfgEnv::with_mainnet_gas_params`], [`CfgEnv::with_mainnet_gas_params`] pub spec: SPEC, /// Gas params for the EVM. Use [`CfgEnv::set_gas_params`] to set the gas params. /// If gas_params was not set it will be set to the default gas params for the spec. - gas_params: GasParams, + pub gas_params: GasParams, - /// If gas params were set this will be set to true. - is_custom_gas_params: bool, /// Chain ID of the EVM. Used in CHAINID opcode and transaction's chain ID check. /// /// Chain ID is introduced EIP-155. @@ -129,17 +132,6 @@ pub struct CfgEnv { pub disable_fee_charge: bool, } -impl + Clone> InitializeCfg for CfgEnv { - fn initialize(&mut self) { - if !self.is_custom_gas_params { - return; - } - self.is_custom_gas_params = false; - - self.gas_params = GasParams::new_spec(self.spec.clone().into()); - } -} - impl CfgEnv { /// Creates new `CfgEnv` with default values. pub fn new() -> Self { @@ -166,7 +158,15 @@ impl + Clone> CfgEnv { } /// Create new `CfgEnv` with default values and specified spec. + /// It will create a new gas params based on mainnet spec. + /// + /// Internally it will call [`CfgEnv::new_with_spec_and_gas_params`] with the mainnet gas params. pub fn new_with_spec(spec: SPEC) -> Self { + Self::new_with_spec_and_gas_params(spec.clone(), GasParams::new_spec(spec.into())) + } + + /// Create new `CfgEnv` with default values and specified spec. + pub fn new_with_spec_and_gas_params(spec: SPEC, gas_params: GasParams) -> Self { Self { chain_id: 1, tx_chain_id_check: true, @@ -177,8 +177,7 @@ impl + Clone> CfgEnv { max_blobs_per_tx: None, tx_gas_limit_cap: None, blob_base_fee_update_fraction: None, - gas_params: GasParams::new_spec(spec.into()), - is_custom_gas_params: false, + gas_params, #[cfg(feature = "memory_limit")] memory_limit: (1 << 32) - 1, #[cfg(feature = "optional_balance_check")] @@ -219,11 +218,31 @@ impl + Clone> CfgEnv { self } + /// Sets the gas params for the `CfgEnv` to the mainnet gas params. + /// + /// If spec gets changed, calling this function would use this spec to set the mainnetF gas params. + pub fn with_mainnet_gas_params(mut self) -> Self { + self.set_gas_params(GasParams::new_spec(self.spec.clone().into())); + self + } + + /// Sets the spec for the `CfgEnv`. + #[inline] + pub fn set_spec(&mut self, spec: SPEC) { + self.spec = spec; + } + + /// Sets the spec for the `CfgEnv` and the gas params to the mainnet gas params. + #[inline] + pub fn set_spec_and_mainnet_gas_params(&mut self, spec: SPEC) { + self.set_spec(spec.clone()); + self.set_gas_params(GasParams::new_spec(spec.into())); + } + /// Sets the gas params for the `CfgEnv`. #[inline] pub fn set_gas_params(&mut self, gas_params: GasParams) { self.gas_params = gas_params; - self.is_custom_gas_params = true; } /// Enables the transaction's chain ID check. @@ -241,8 +260,11 @@ impl + Clone> CfgEnv { /// Consumes `self` and returns a new `CfgEnv` with the specified spec. /// /// Resets the gas params override function as it is generic over SPEC. - pub fn with_spec + Copy>(self, spec: OSPEC) -> CfgEnv { - let eth_spec = spec.into(); + pub fn with_spec_and_gas_params + Copy>( + self, + spec: OSPEC, + gas_params: GasParams, + ) -> CfgEnv { CfgEnv { chain_id: self.chain_id, tx_chain_id_check: self.tx_chain_id_check, @@ -253,8 +275,7 @@ impl + Clone> CfgEnv { tx_gas_limit_cap: self.tx_gas_limit_cap, max_blobs_per_tx: self.max_blobs_per_tx, blob_base_fee_update_fraction: self.blob_base_fee_update_fraction, - gas_params: GasParams::new_spec(eth_spec), - is_custom_gas_params: false, + gas_params, #[cfg(feature = "memory_limit")] memory_limit: self.memory_limit, #[cfg(feature = "optional_balance_check")] @@ -464,7 +485,10 @@ impl + Clone> Cfg for CfgEnv { impl + Clone> Default for CfgEnv { fn default() -> Self { - Self::new_with_spec(SPEC::default()) + Self::new_with_spec_and_gas_params( + SPEC::default(), + GasParams::new_spec(SPEC::default().into()), + ) } } diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index de226f3342..4ceca6e00b 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -146,7 +146,10 @@ impl< Self { tx: TX::default(), block: BLOCK::default(), - cfg: CfgEnv::new_with_spec(spec), + cfg: CfgEnv::new_with_spec_and_gas_params( + spec.clone(), + GasParams::new_spec(spec.into()), + ), local: LOCAL::default(), journaled_state, chain: Default::default(), diff --git a/crates/ee-tests/src/revm_tests.rs b/crates/ee-tests/src/revm_tests.rs index 790a204d8c..fc66a77dfe 100644 --- a/crates/ee-tests/src/revm_tests.rs +++ b/crates/ee-tests/src/revm_tests.rs @@ -85,7 +85,7 @@ fn test_selfdestruct_multi_tx() { fn test_multi_tx_create() { let mut evm = Context::mainnet() .modify_cfg_chained(|cfg| { - cfg.spec = SpecId::BERLIN; + cfg.set_spec_and_mainnet_gas_params(SpecId::BERLIN); cfg.disable_nonce_check = true; }) .with_db(BenchmarkDB::new_bytecode(Bytecode::new())) diff --git a/crates/handler/src/mainnet_builder.rs b/crates/handler/src/mainnet_builder.rs index 61bc98b395..46baa54fc8 100644 --- a/crates/handler/src/mainnet_builder.rs +++ b/crates/handler/src/mainnet_builder.rs @@ -1,6 +1,6 @@ use crate::{frame::EthFrame, instructions::EthInstructions, EthPrecompiles}; use context::{BlockEnv, Cfg, CfgEnv, Context, Evm, FrameStack, Journal, TxEnv}; -use context_interface::{cfg::InitializeCfg, Block, Database, JournalTr, Transaction}; +use context_interface::{Block, Database, JournalTr, Transaction}; use database_interface::EmptyDB; use interpreter::interpreter::EthInterpreter; use primitives::hardfork::SpecId; @@ -29,7 +29,7 @@ impl MainBuilder for Context, { @@ -101,7 +101,7 @@ mod test { let bytecode = Bytecode::new_legacy([PUSH1, 0x01, PUSH1, 0x01, SSTORE].into()); let ctx = Context::mainnet() - .modify_cfg_chained(|cfg| cfg.spec = SpecId::PRAGUE) + .modify_cfg_chained(|cfg| cfg.set_spec_and_mainnet_gas_params(SpecId::PRAGUE)) .with_db(BenchmarkDB::new_bytecode(bytecode)); let mut evm = ctx.build_mainnet(); diff --git a/crates/handler/src/validation.rs b/crates/handler/src/validation.rs index d9ba1c4415..52a9404ec9 100644 --- a/crates/handler/src/validation.rs +++ b/crates/handler/src/validation.rs @@ -275,7 +275,7 @@ mod tests { let ctx = Context::mainnet() .modify_cfg_chained(|c| { if let Some(spec_id) = spec_id { - c.spec = spec_id; + c.set_spec_and_mainnet_gas_params(spec_id); } }) .with_db(CacheDB::::default()); diff --git a/examples/erc20_gas/src/main.rs b/examples/erc20_gas/src/main.rs index 8724cdc038..f409f9409a 100644 --- a/examples/erc20_gas/src/main.rs +++ b/examples/erc20_gas/src/main.rs @@ -10,6 +10,7 @@ use anyhow::Result; use exec::transact_erc20evm_commit; use revm::{ context::{CfgEnv, TxEnv}, + context_interface::cfg::GasParams, database::{AlloyDB, BlockId, CacheDB}, database_interface::WrapDatabaseAsync, primitives::{address, hardfork::SpecId, keccak256, Address, StorageValue, TxKind, U256}, @@ -81,7 +82,10 @@ fn balance_of(address: Address, alloy_db: &mut AlloyCacheDB) -> Result Result<()> { let mut ctx = Context::mainnet() .with_db(cache_db) - .with_cfg(CfgEnv::new_with_spec(SpecId::CANCUN)) + .with_cfg(CfgEnv::new_with_spec_and_gas_params( + SpecId::CANCUN, + GasParams::new_spec(SpecId::CANCUN), + )) .with_tx( TxEnv::builder() .caller(from) From eb14b9c52bb2fd522dc6b466e61b339fe749f0d9 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 22 Dec 2025 17:46:18 +0100 Subject: [PATCH 11/19] nits and tests fix --- bins/revme/src/cmd/statetest/runner.rs | 2 +- crates/context/src/cfg.rs | 24 ++++++++++++++++++++--- crates/context/src/context.rs | 5 +---- crates/handler/src/precompile_provider.rs | 10 +--------- crates/op-revm/src/precompiles.rs | 1 - crates/statetest-types/src/test_unit.rs | 4 ++-- examples/erc20_gas/src/main.rs | 6 +----- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 1c00e2447b..ee77eb066a 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -455,7 +455,7 @@ fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> { ctx.name, &exec_result, db, - ctx.cfg.spec(), + *ctx.cfg.spec(), ctx.print_json_outcome, ) } diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 340a0b9906..e0962a8e0d 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -172,7 +172,7 @@ impl + Clone> CfgEnv { tx_chain_id_check: true, limit_contract_code_size: None, limit_contract_initcode_size: None, - spec: spec.clone(), + spec, disable_nonce_check: false, max_blobs_per_tx: None, tx_gas_limit_cap: None, @@ -201,8 +201,8 @@ impl + Clone> CfgEnv { /// Returns the spec for the `CfgEnv`. #[inline] - pub fn spec(&self) -> SPEC { - self.spec.clone() + pub fn spec(&self) -> &SPEC { + &self.spec } /// Consumes `self` and returns a new `CfgEnv` with the specified chain ID. @@ -257,6 +257,24 @@ impl + Clone> CfgEnv { self } + /// Sets the spec for the `CfgEnv`. + #[inline] + #[deprecated( + since = "0.1.0", + note = "Use [`CfgEnv::with_spec_and_mainnet_gas_params`] instead" + )] + pub fn with_spec(mut self, spec: SPEC) -> Self { + self.spec = spec; + self + } + + /// Sets the spec for the `CfgEnv` and the gas params to the mainnet gas params. + pub fn with_spec_and_mainnet_gas_params(mut self, spec: SPEC) -> Self { + self.set_spec(spec.clone()); + self.set_gas_params(GasParams::new_spec(spec.into())); + self + } + /// Consumes `self` and returns a new `CfgEnv` with the specified spec. /// /// Resets the gas params override function as it is generic over SPEC. diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index 4ceca6e00b..de226f3342 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -146,10 +146,7 @@ impl< Self { tx: TX::default(), block: BLOCK::default(), - cfg: CfgEnv::new_with_spec_and_gas_params( - spec.clone(), - GasParams::new_spec(spec.into()), - ), + cfg: CfgEnv::new_with_spec(spec), local: LOCAL::default(), journaled_state, chain: Default::default(), diff --git a/crates/handler/src/precompile_provider.rs b/crates/handler/src/precompile_provider.rs index 5b835b99c9..dab165140f 100644 --- a/crates/handler/src/precompile_provider.rs +++ b/crates/handler/src/precompile_provider.rs @@ -41,8 +41,6 @@ pub struct EthPrecompiles { pub precompiles: &'static Precompiles, /// Current spec. None means that spec was not set yet. pub spec: SpecId, - /// Spec override function. - pub spec_override_fn: Option &'static Precompiles>, } impl EthPrecompiles { @@ -51,7 +49,6 @@ impl EthPrecompiles { Self { precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), spec, - spec_override_fn: None, } } @@ -71,7 +68,6 @@ impl Clone for EthPrecompiles { Self { precompiles: self.precompiles, spec: self.spec, - spec_override_fn: self.spec_override_fn, } } } @@ -82,7 +78,6 @@ impl Default for EthPrecompiles { Self { precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), spec, - spec_override_fn: None, } } } @@ -96,10 +91,7 @@ impl PrecompileProvider for EthPrecompiles { if spec == self.spec { return false; } - self.precompiles = self - .spec_override_fn - .map(|override_fn| override_fn(spec)) - .unwrap_or_else(|| Precompiles::new(PrecompileSpecId::from_spec_id(spec))); + self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec)); self.spec = spec; true } diff --git a/crates/op-revm/src/precompiles.rs b/crates/op-revm/src/precompiles.rs index d8fe69a10d..d234980164 100644 --- a/crates/op-revm/src/precompiles.rs +++ b/crates/op-revm/src/precompiles.rs @@ -41,7 +41,6 @@ impl OpPrecompiles { inner: EthPrecompiles { precompiles, spec: SpecId::default(), - spec_override_fn: None, }, spec, } diff --git a/crates/statetest-types/src/test_unit.rs b/crates/statetest-types/src/test_unit.rs index 56af221b69..f137ed73d8 100644 --- a/crates/statetest-types/src/test_unit.rs +++ b/crates/statetest-types/src/test_unit.rs @@ -211,7 +211,7 @@ mod tests { fn test_block_env_blob_fee_fraction_prague() { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 - let mut cfg = CfgEnv::new_with_spec(SpecId::CANCUN); + let mut cfg = CfgEnv::new_with_spec(SpecId::PRAGUE); let block = unit.block_env(&mut cfg); @@ -233,7 +233,7 @@ mod tests { fn test_block_env_blob_fee_fraction_osaka() { let unit = create_test_unit_with_excess_blob_gas(0x240000); // 2,359,296 - let mut cfg = CfgEnv::new_with_spec(SpecId::CANCUN); + let mut cfg = CfgEnv::new_with_spec(SpecId::OSAKA); let block = unit.block_env(&mut cfg); diff --git a/examples/erc20_gas/src/main.rs b/examples/erc20_gas/src/main.rs index f409f9409a..8724cdc038 100644 --- a/examples/erc20_gas/src/main.rs +++ b/examples/erc20_gas/src/main.rs @@ -10,7 +10,6 @@ use anyhow::Result; use exec::transact_erc20evm_commit; use revm::{ context::{CfgEnv, TxEnv}, - context_interface::cfg::GasParams, database::{AlloyDB, BlockId, CacheDB}, database_interface::WrapDatabaseAsync, primitives::{address, hardfork::SpecId, keccak256, Address, StorageValue, TxKind, U256}, @@ -82,10 +81,7 @@ fn balance_of(address: Address, alloy_db: &mut AlloyCacheDB) -> Result Result<()> { let mut ctx = Context::mainnet() .with_db(cache_db) - .with_cfg(CfgEnv::new_with_spec_and_gas_params( - SpecId::CANCUN, - GasParams::new_spec(SpecId::CANCUN), - )) + .with_cfg(CfgEnv::new_with_spec(SpecId::CANCUN)) .with_tx( TxEnv::builder() .caller(from) From cf341fae2b7c3034a860126d061da462778bb3fc Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 22 Dec 2025 17:53:00 +0100 Subject: [PATCH 12/19] morf spec type --- crates/context/src/cfg.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index e0962a8e0d..5b2e736c28 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -269,16 +269,17 @@ impl + Clone> CfgEnv { } /// Sets the spec for the `CfgEnv` and the gas params to the mainnet gas params. - pub fn with_spec_and_mainnet_gas_params(mut self, spec: SPEC) -> Self { - self.set_spec(spec.clone()); - self.set_gas_params(GasParams::new_spec(spec.into())); - self + pub fn with_spec_and_mainnet_gas_params + Clone>( + self, + spec: OSPEC, + ) -> CfgEnv { + self.with_spec_and_gas_params(spec.clone(), GasParams::new_spec(spec.into())) } /// Consumes `self` and returns a new `CfgEnv` with the specified spec. /// /// Resets the gas params override function as it is generic over SPEC. - pub fn with_spec_and_gas_params + Copy>( + pub fn with_spec_and_gas_params + Clone>( self, spec: OSPEC, gas_params: GasParams, From 7a7eed76c4fe3a5c370bbb8e46362b676d5d9e38 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 22 Dec 2025 18:08:02 +0100 Subject: [PATCH 13/19] dont compare ptr --- .../context/interface/src/cfg/gas_params.rs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/context/interface/src/cfg/gas_params.rs b/crates/context/interface/src/cfg/gas_params.rs index cf2f1edb56..3a801be950 100644 --- a/crates/context/interface/src/cfg/gas_params.rs +++ b/crates/context/interface/src/cfg/gas_params.rs @@ -10,10 +10,13 @@ use primitives::{ hardfork::SpecId::{self}, U256, }; -use std::sync::Arc; +use std::{ + hash::{Hash, Hasher}, + sync::Arc, +}; /// Gas table for dynamic gas constants. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone)] pub struct GasParams { /// Table of gas costs for operations table: Arc<[u64; 256]>, @@ -21,6 +24,25 @@ pub struct GasParams { ptr: *const u64, } +impl PartialEq for GasParams { + fn eq(&self, other: &GasParams) -> bool { + self.table == other.table + } +} + +impl Hash for GasParams { + fn hash(&self, hasher: &mut H) { + self.table.hash(hasher); + } +} + +impl core::fmt::Debug for GasParams { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "GasParams {{ table: {:?} }}", self.table) + } +} + +impl Eq for GasParams {} #[cfg(feature = "serde")] mod serde { use super::{Arc, GasParams}; From 99355ba9a73acaebe5a3fa72d7e9de4ed6dbd3a2 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 22 Dec 2025 18:16:58 +0100 Subject: [PATCH 14/19] import fix --- crates/context/interface/src/cfg/gas_params.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/context/interface/src/cfg/gas_params.rs b/crates/context/interface/src/cfg/gas_params.rs index 3a801be950..4e31f18533 100644 --- a/crates/context/interface/src/cfg/gas_params.rs +++ b/crates/context/interface/src/cfg/gas_params.rs @@ -6,14 +6,12 @@ use crate::{ }, context::SStoreResult, }; +use core::hash::{Hash, Hasher}; use primitives::{ hardfork::SpecId::{self}, U256, }; -use std::{ - hash::{Hash, Hasher}, - sync::Arc, -}; +use std::sync::Arc; /// Gas table for dynamic gas constants. #[derive(Clone)] From ee3ef23bfdceca983768bca7eca76ba2da5bae0a Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 22 Dec 2025 18:48:32 +0100 Subject: [PATCH 15/19] typo --- crates/context/src/cfg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 5b2e736c28..77776653f0 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -13,7 +13,7 @@ pub struct CfgEnv { /// /// [`CfgEnv::new_with_spec`] is going to set both gas params and spec. /// - /// As GasParams is spec dependent, it is recommended to use one of followin function to set both of them. + /// As GasParams is spec dependent, it is recommended to use one of following function to set both of them. /// [`CfgEnv::set_spec_and_mainnet_gas_params`], [`CfgEnv::with_mainnet_gas_params`], [`CfgEnv::with_mainnet_gas_params`] pub spec: SPEC, From dd9084b9f9f390d023bfc40033909ee9cd2e91f6 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 23 Dec 2025 13:48:14 +0100 Subject: [PATCH 16/19] ref alloy-eips --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a7f74b491..ce8a6a3c87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,7 +138,7 @@ dependencies = [ [[package]] name = "alloy-eip7928" version = "0.2.0" -source = "git+https://github.com/rakita/alloy-eips.git?rev=734beaf#734beafea409bdd87b3a4e33ac72ac68654bb8dd" +source = "git+https://github.com/alloy-rs/eips.git?rev=734beaf#734beafea409bdd87b3a4e33ac72ac68654bb8dd" dependencies = [ "alloy-primitives", "alloy-rlp", diff --git a/Cargo.toml b/Cargo.toml index 1bfb122ee9..6394f1ac52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ ee-tests = { path = "crates/ee-tests", package = "revm-ee-tests", version = "0.1 # alloy alloy-eip2930 = { version = "0.2.1", default-features = false } alloy-eip7702 = { version = "0.6.1", default-features = false } -alloy-eip7928 = { version = "0.2.0", default-features = false, git = "https://github.com/rakita/alloy-eips.git", rev = "734beaf" } +alloy-eip7928 = { version = "0.2.0", default-features = false, git = "https://github.com/alloy-rs/eips.git", rev = "734beaf" } alloy-primitives = { version = "1.5.0", default-features = false } # alloy in examples, revme or feature flagged. From de242a7c866fc6cfac7d3e46f3c5c8866ed7a6cd Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 23 Dec 2025 14:28:07 +0100 Subject: [PATCH 17/19] add core::error::Error impl to BalError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- crates/state/src/bal.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/state/src/bal.rs b/crates/state/src/bal.rs index 3f713d87c4..d7379bdc8a 100644 --- a/crates/state/src/bal.rs +++ b/crates/state/src/bal.rs @@ -230,6 +230,8 @@ pub enum BalError { AccountNotFound, /// Slot not found in BAL. SlotNotFound, + /// String error. + StrError(&'static str), } impl core::fmt::Display for BalError { @@ -237,6 +239,9 @@ impl core::fmt::Display for BalError { match self { Self::AccountNotFound => write!(f, "Account not found in BAL"), Self::SlotNotFound => write!(f, "Slot not found in BAL"), + Self::StrError(s) => write!(f, "{s}"), } } } + +impl core::error::Error for BalError {} From 473d1ae3baae76d300733e9ad348ee031d0b1eb5 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 23 Dec 2025 14:31:07 +0100 Subject: [PATCH 18/19] send/sync on GasParams --- crates/context/interface/src/cfg/gas_params.rs | 5 +++++ crates/state/src/bal.rs | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/context/interface/src/cfg/gas_params.rs b/crates/context/interface/src/cfg/gas_params.rs index 4e31f18533..68e195eb49 100644 --- a/crates/context/interface/src/cfg/gas_params.rs +++ b/crates/context/interface/src/cfg/gas_params.rs @@ -34,6 +34,11 @@ impl Hash for GasParams { } } +/// Pointer points to Arc so it is safe to send across threads +unsafe impl Send for GasParams {} +/// Pointer points to Arc so it is safe to access +unsafe impl Sync for GasParams {} + impl core::fmt::Debug for GasParams { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "GasParams {{ table: {:?} }}", self.table) diff --git a/crates/state/src/bal.rs b/crates/state/src/bal.rs index d7379bdc8a..bdaaa6953b 100644 --- a/crates/state/src/bal.rs +++ b/crates/state/src/bal.rs @@ -230,8 +230,6 @@ pub enum BalError { AccountNotFound, /// Slot not found in BAL. SlotNotFound, - /// String error. - StrError(&'static str), } impl core::fmt::Display for BalError { @@ -239,7 +237,6 @@ impl core::fmt::Display for BalError { match self { Self::AccountNotFound => write!(f, "Account not found in BAL"), Self::SlotNotFound => write!(f, "Slot not found in BAL"), - Self::StrError(s) => write!(f, "{s}"), } } } From 36170147177bd4700a4af33d136a826d3077e408 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 26 Dec 2025 14:56:57 +0100 Subject: [PATCH 19/19] relex generic for some Cfg functions --- crates/context/src/cfg.rs | 86 ++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 77776653f0..5a320ea660 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -139,32 +139,7 @@ impl CfgEnv { } } -impl + Clone> CfgEnv { - /// Returns the blob base fee update fraction from [CfgEnv::blob_base_fee_update_fraction]. - /// - /// If this field is not set, return the default value for the spec. - /// - /// Default values for Cancun is [`primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN`] - /// and for Prague is [`primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE`]. - pub fn blob_base_fee_update_fraction(&mut self) -> u64 { - self.blob_base_fee_update_fraction.unwrap_or_else(|| { - let spec: SpecId = self.spec.clone().into(); - if spec.is_enabled_in(SpecId::PRAGUE) { - primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE - } else { - primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN - } - }) - } - - /// Create new `CfgEnv` with default values and specified spec. - /// It will create a new gas params based on mainnet spec. - /// - /// Internally it will call [`CfgEnv::new_with_spec_and_gas_params`] with the mainnet gas params. - pub fn new_with_spec(spec: SPEC) -> Self { - Self::new_with_spec_and_gas_params(spec.clone(), GasParams::new_spec(spec.into())) - } - +impl CfgEnv { /// Create new `CfgEnv` with default values and specified spec. pub fn new_with_spec_and_gas_params(spec: SPEC, gas_params: GasParams) -> Self { Self { @@ -218,27 +193,12 @@ impl + Clone> CfgEnv { self } - /// Sets the gas params for the `CfgEnv` to the mainnet gas params. - /// - /// If spec gets changed, calling this function would use this spec to set the mainnetF gas params. - pub fn with_mainnet_gas_params(mut self) -> Self { - self.set_gas_params(GasParams::new_spec(self.spec.clone().into())); - self - } - /// Sets the spec for the `CfgEnv`. #[inline] pub fn set_spec(&mut self, spec: SPEC) { self.spec = spec; } - /// Sets the spec for the `CfgEnv` and the gas params to the mainnet gas params. - #[inline] - pub fn set_spec_and_mainnet_gas_params(&mut self, spec: SPEC) { - self.set_spec(spec.clone()); - self.set_gas_params(GasParams::new_spec(spec.into())); - } - /// Sets the gas params for the `CfgEnv`. #[inline] pub fn set_gas_params(&mut self, gas_params: GasParams) { @@ -354,6 +314,48 @@ impl + Clone> CfgEnv { } } +impl + Clone> CfgEnv { + /// Returns the blob base fee update fraction from [CfgEnv::blob_base_fee_update_fraction]. + /// + /// If this field is not set, return the default value for the spec. + /// + /// Default values for Cancun is [`primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN`] + /// and for Prague is [`primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE`]. + pub fn blob_base_fee_update_fraction(&mut self) -> u64 { + self.blob_base_fee_update_fraction.unwrap_or_else(|| { + let spec: SpecId = self.spec.clone().into(); + if spec.is_enabled_in(SpecId::PRAGUE) { + primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE + } else { + primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN + } + }) + } + + /// Create new `CfgEnv` with default values and specified spec. + /// It will create a new gas params based on mainnet spec. + /// + /// Internally it will call [`CfgEnv::new_with_spec_and_gas_params`] with the mainnet gas params. + pub fn new_with_spec(spec: SPEC) -> Self { + Self::new_with_spec_and_gas_params(spec.clone(), GasParams::new_spec(spec.into())) + } + + /// Sets the gas params for the `CfgEnv` to the mainnet gas params. + /// + /// If spec gets changed, calling this function would use this spec to set the mainnetF gas params. + pub fn with_mainnet_gas_params(mut self) -> Self { + self.set_gas_params(GasParams::new_spec(self.spec.clone().into())); + self + } + + /// Sets the spec for the `CfgEnv` and the gas params to the mainnet gas params. + #[inline] + pub fn set_spec_and_mainnet_gas_params(&mut self, spec: SPEC) { + self.set_spec(spec.clone()); + self.set_gas_params(GasParams::new_spec(spec.into())); + } +} + impl + Clone> Cfg for CfgEnv { type Spec = SPEC; @@ -502,7 +504,7 @@ impl + Clone> Cfg for CfgEnv { } } -impl + Clone> Default for CfgEnv { +impl> Default for CfgEnv { fn default() -> Self { Self::new_with_spec_and_gas_params( SPEC::default(),