diff --git a/crates/context/interface/src/cfg/gas.rs b/crates/context/interface/src/cfg/gas.rs index 5b122b7657..fc3762f299 100644 --- a/crates/context/interface/src/cfg/gas.rs +++ b/crates/context/interface/src/cfg/gas.rs @@ -1,7 +1,7 @@ //! Gas constants and functions for gas calculation. -use crate::{transaction::AccessListItemTr as _, Transaction, TransactionType}; -use primitives::{eip7702, hardfork::SpecId, U256}; +use crate::{cfg::GasParams, transaction::AccessListItemTr as _, Transaction, TransactionType}; +use primitives::hardfork::SpecId; /// Gas cost for operations that consume zero gas. pub const ZERO: u64 = 0; @@ -107,57 +107,6 @@ 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))] @@ -195,44 +144,13 @@ pub fn calculate_initial_tx_gas( 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 + GasParams::new_spec(spec_id).initial_tx_gas( + input, + is_create, + access_list_accounts, + access_list_storages, + authorization_list_num, + ) } /// Initial gas that is deducted for transaction to be included. @@ -272,27 +190,14 @@ pub fn calculate_initial_tx_gas_for_tx(tx: impl Transaction, spec: SpecId) -> In /// 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 +pub fn get_tokens_in_calldata_istanbul(input: &[u8]) -> u64 { + get_tokens_in_calldata(input, NON_ZERO_BYTE_MULTIPLIER_ISTANBUL) } -/// Returns number of words what would fit to provided number of bytes, -/// i.e. it rounds up the number bytes to number of words. +/// Retrieve the total number of tokens in calldata. #[inline] -pub const fn num_words(len: usize) -> usize { - len.div_ceil(32) +pub fn get_tokens_in_calldata(input: &[u8], non_zero_data_multiplier: u64) -> 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; + zero_data_len + non_zero_data_len * non_zero_data_multiplier } diff --git a/crates/context/interface/src/cfg/gas_params.rs b/crates/context/interface/src/cfg/gas_params.rs index 68e195eb49..7e59f6a027 100644 --- a/crates/context/interface/src/cfg/gas_params.rs +++ b/crates/context/interface/src/cfg/gas_params.rs @@ -1,15 +1,14 @@ //! Gas table for dynamic gas constants. use crate::{ - cfg::gas::{ - self, log2floor, num_words, ISTANBUL_SLOAD_GAS, SSTORE_RESET, SSTORE_SET, WARM_SSTORE_RESET, - }, + cfg::gas::{self, get_tokens_in_calldata, InitialAndFloorGas}, context::SStoreResult, }; use core::hash::{Hash, Hasher}; use primitives::{ + eip7702, hardfork::SpecId::{self}, - U256, + OnceLock, U256, }; use std::sync::Arc; @@ -45,6 +44,13 @@ impl core::fmt::Debug for GasParams { } } +/// 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) +} + impl Eq for GasParams {} #[cfg(feature = "serde")] mod serde { @@ -85,7 +91,7 @@ mod serde { impl Default for GasParams { fn default() -> Self { - Self::new_spec(SpecId::default()) + Self::new_spec(SpecId::default()).clone() } } @@ -129,8 +135,66 @@ impl GasParams { } /// Creates a new `GasParams` for the given spec. - #[inline] + #[inline(never)] pub fn new_spec(spec: SpecId) -> Self { + use SpecId::*; + let gas_params = match spec { + FRONTIER | FRONTIER_THAWING => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // Transaction creation cost was added in homestead fork. + HOMESTEAD | DAO_FORK => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // New account cost for selfdestruct was added in tangerine fork. + TANGERINE => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // EXP cost was increased in spurious dragon fork. + SPURIOUS_DRAGON | BYZANTIUM | CONSTANTINOPLE | PETERSBURG => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // SSTORE gas calculation changed in istanbul fork. + ISTANBUL | MUIR_GLACIER => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // Warm/cold state access + BERLIN => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // Refund reduction in london fork. + LONDON | ARROW_GLACIER | GRAY_GLACIER | MERGE => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // Transaction initcode cost was introduced in shanghai fork. + SHANGHAI | CANCUN => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // EIP-7702 was introduced in prague fork. + PRAGUE | OSAKA => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + // New fork. + SpecId::AMSTERDAM => { + static TABLE: OnceLock = OnceLock::new(); + TABLE.get_or_init(|| Self::new_spec_inner(spec)) + } + }; + gas_params.clone() + } + + /// Creates a new `GasParams` for the given spec. + #[inline] + fn new_spec_inner(spec: SpecId) -> Self { let mut table = [0; 256]; table[GasId::exp_byte_gas().as_usize()] = 10; @@ -150,9 +214,10 @@ impl GasParams { 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; + table[GasId::sstore_static().as_usize()] = gas::SSTORE_RESET; // SSTORE SET - table[GasId::sstore_set_without_load_cost().as_usize()] = SSTORE_SET - SSTORE_RESET; + table[GasId::sstore_set_without_load_cost().as_usize()] = + gas::SSTORE_SET - gas::SSTORE_RESET; // SSTORE RESET Is covered in SSTORE_STATIC. table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = 0; // SSTORE CLEARING SLOT REFUND @@ -162,6 +227,15 @@ impl GasParams { 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; + table[GasId::code_deposit_cost().as_usize()] = gas::CODEDEPOSIT; + table[GasId::tx_token_non_zero_byte_multiplier().as_usize()] = + gas::NON_ZERO_BYTE_MULTIPLIER; + table[GasId::tx_token_cost().as_usize()] = gas::STANDARD_TOKEN_COST; + table[GasId::tx_base_stipend().as_usize()] = 21000; + + if spec.is_enabled_in(SpecId::HOMESTEAD) { + table[GasId::tx_create_cost().as_usize()] = gas::CREATE; + } if spec.is_enabled_in(SpecId::TANGERINE) { table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT; @@ -174,9 +248,11 @@ impl GasParams { 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; + gas::SSTORE_SET - gas::ISTANBUL_SLOAD_GAS; table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = - SSTORE_RESET - ISTANBUL_SLOAD_GAS; + gas::SSTORE_RESET - gas::ISTANBUL_SLOAD_GAS; + table[GasId::tx_token_non_zero_byte_multiplier().as_usize()] = + gas::NON_ZERO_BYTE_MULTIPLIER_ISTANBUL; } if spec.is_enabled_in(SpecId::BERLIN) { @@ -189,9 +265,13 @@ impl GasParams { 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; + gas::WARM_SSTORE_RESET - gas::WARM_STORAGE_READ_COST; table[GasId::sstore_set_without_load_cost().as_usize()] = - SSTORE_SET - gas::WARM_STORAGE_READ_COST; + gas::SSTORE_SET - gas::WARM_STORAGE_READ_COST; + + table[GasId::tx_access_list_address_cost().as_usize()] = gas::ACCESS_LIST_ADDRESS; + table[GasId::tx_access_list_storage_key_cost().as_usize()] = + gas::ACCESS_LIST_STORAGE_KEY; } if spec.is_enabled_in(SpecId::LONDON) { @@ -200,11 +280,23 @@ impl GasParams { // 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; + gas::WARM_SSTORE_RESET + gas::ACCESS_LIST_STORAGE_KEY; table[GasId::selfdestruct_refund().as_usize()] = 0; } + if spec.is_enabled_in(SpecId::SHANGHAI) { + table[GasId::tx_initcode_cost().as_usize()] = gas::INITCODE_WORD_COST; + } + + if spec.is_enabled_in(SpecId::PRAGUE) { + table[GasId::tx_eip7702_per_empty_account_cost().as_usize()] = + eip7702::PER_EMPTY_ACCOUNT_COST; + + table[GasId::tx_floor_cost_per_token().as_usize()] = gas::TOTAL_COST_FLOOR_PER_TOKEN; + table[GasId::tx_floor_cost_base_gas().as_usize()] = 21000; + } + Self::new(Arc::new(table)) } @@ -508,6 +600,146 @@ impl GasParams { self.get(GasId::copy_per_word()) .saturating_mul(word_num as u64) } + + /// Code deposit cost, calculated per byte as len * code_deposit_cost. + #[inline] + pub fn code_deposit_cost(&self, len: usize) -> u64 { + self.get(GasId::code_deposit_cost()) + .saturating_mul(len as u64) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the eip7702 per empty account cost. + #[inline] + pub fn tx_eip7702_per_empty_account_cost(&self) -> u64 { + self.get(GasId::tx_eip7702_per_empty_account_cost()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the token non zero byte multiplier. + #[inline] + pub fn tx_token_non_zero_byte_multiplier(&self) -> u64 { + self.get(GasId::tx_token_non_zero_byte_multiplier()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the token cost for input data. + #[inline] + pub fn tx_token_cost(&self) -> u64 { + self.get(GasId::tx_token_cost()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the floor gas per token. + pub fn tx_floor_cost_per_token(&self) -> u64 { + self.get(GasId::tx_floor_cost_per_token()) + } + + /// Used [GasParams::initial_tx_gas] to calculate the floor gas. + /// + /// Floor gas is introduced in EIP-7623. + #[inline] + pub fn tx_floor_cost(&self, tokens_in_calldata: u64) -> u64 { + self.tx_floor_cost_per_token() * tokens_in_calldata + self.tx_floor_cost_base_gas() + } + + /// Used in [GasParams::initial_tx_gas] to calculate the floor gas base gas. + pub fn tx_floor_cost_base_gas(&self) -> u64 { + self.get(GasId::tx_floor_cost_base_gas()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the access list address cost. + pub fn tx_access_list_address_cost(&self) -> u64 { + self.get(GasId::tx_access_list_address_cost()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the access list storage key cost. + pub fn tx_access_list_storage_key_cost(&self) -> u64 { + self.get(GasId::tx_access_list_storage_key_cost()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the base transaction stipend. + pub fn tx_base_stipend(&self) -> u64 { + self.get(GasId::tx_base_stipend()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the create cost. + /// + /// Similar to the [`Self::create_cost`] method but it got activated in different fork, + #[inline] + pub fn tx_create_cost(&self) -> u64 { + self.get(GasId::tx_create_cost()) + } + + /// Used in [GasParams::initial_tx_gas] to calculate the initcode cost per word of len. + #[inline] + pub fn tx_initcode_cost(&self, len: usize) -> u64 { + self.get(GasId::tx_initcode_cost()) + .saturating_mul(num_words(len) as u64) + } + + /// 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 initial_tx_gas( + &self, + 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, self.tx_token_non_zero_byte_multiplier()); + + gas.initial_gas += tokens_in_calldata * self.tx_token_cost() + // before berlin tx_access_list_address_cost will be zero + + access_list_accounts * self.tx_access_list_address_cost() + // before berlin tx_access_list_storage_key_cost will be zero + + access_list_storages * self.tx_access_list_storage_key_cost() + + self.tx_base_stipend() + // EIP-7702: Authorization list + + authorization_list_num * self.tx_eip7702_per_empty_account_cost(); + + if is_create { + // EIP-2: Homestead Hard-fork Changes + gas.initial_gas += self.tx_create_cost(); + + // EIP-3860: Limit and meter initcode + gas.initial_gas += self.tx_initcode_cost(input.len()); + } + + // Calculate gas floor for EIP-7623 + gas.floor_gas = self.tx_floor_cost(tokens_in_calldata); + + gas + } +} + +#[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 } /// Gas identifier that maps onto index in gas table. @@ -578,6 +810,23 @@ impl GasId { x if x == Self::new_account_cost_for_selfdestruct().as_u8() => { "new_account_cost_for_selfdestruct" } + x if x == Self::code_deposit_cost().as_u8() => "code_deposit_cost", + x if x == Self::tx_eip7702_per_empty_account_cost().as_u8() => { + "tx_eip7702_per_empty_account_cost" + } + x if x == Self::tx_token_non_zero_byte_multiplier().as_u8() => { + "tx_token_non_zero_byte_multiplier" + } + x if x == Self::tx_token_cost().as_u8() => "tx_token_cost", + x if x == Self::tx_floor_cost_per_token().as_u8() => "tx_floor_cost_per_token", + x if x == Self::tx_floor_cost_base_gas().as_u8() => "tx_floor_cost_base_gas", + x if x == Self::tx_access_list_address_cost().as_u8() => "tx_access_list_address_cost", + x if x == Self::tx_access_list_storage_key_cost().as_u8() => { + "tx_access_list_storage_key_cost" + } + x if x == Self::tx_base_stipend().as_u8() => "tx_base_stipend", + x if x == Self::tx_create_cost().as_u8() => "tx_create_cost", + x if x == Self::tx_initcode_cost().as_u8() => "tx_initcode_cost", _ => "unknown", } } @@ -624,6 +873,17 @@ impl GasId { "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()), + "code_deposit_cost" => Some(Self::code_deposit_cost()), + "eip7702_per_empty_account_cost" => Some(Self::tx_eip7702_per_empty_account_cost()), + "tx_token_non_zero_byte_multiplier" => Some(Self::tx_token_non_zero_byte_multiplier()), + "tx_token_cost" => Some(Self::tx_token_cost()), + "tx_floor_cost_per_token" => Some(Self::tx_floor_cost_per_token()), + "tx_floor_cost_base_gas" => Some(Self::tx_floor_cost_base_gas()), + "tx_access_list_address_cost" => Some(Self::tx_access_list_address_cost()), + "tx_access_list_storage_key_cost" => Some(Self::tx_access_list_storage_key_cost()), + "tx_base_stipend" => Some(Self::tx_base_stipend()), + "tx_create_cost" => Some(Self::tx_create_cost()), + "tx_initcode_cost" => Some(Self::tx_initcode_cost()), _ => None, } } @@ -755,6 +1015,61 @@ impl GasId { pub const fn new_account_cost_for_selfdestruct() -> GasId { Self::new(25) } + + /// Code deposit cost. Calculated as len * code_deposit_cost. + pub const fn code_deposit_cost() -> GasId { + Self::new(26) + } + + /// EIP-7702 PER_EMPTY_ACCOUNT_COST gas + pub const fn tx_eip7702_per_empty_account_cost() -> GasId { + Self::new(27) + } + + /// Initial tx gas token non zero byte multiplier. + pub const fn tx_token_non_zero_byte_multiplier() -> GasId { + Self::new(28) + } + + /// Initial tx gas token cost. + pub const fn tx_token_cost() -> GasId { + Self::new(29) + } + + /// Initial tx gas floor cost per token. + pub const fn tx_floor_cost_per_token() -> GasId { + Self::new(30) + } + + /// Initial tx gas floor cost base gas. + pub const fn tx_floor_cost_base_gas() -> GasId { + Self::new(31) + } + + /// Initial tx gas access list address cost. + pub const fn tx_access_list_address_cost() -> GasId { + Self::new(32) + } + + /// Initial tx gas access list storage key cost. + pub const fn tx_access_list_storage_key_cost() -> GasId { + Self::new(33) + } + + /// Initial tx gas base stipend. + pub const fn tx_base_stipend() -> GasId { + Self::new(34) + } + + /// Initial tx gas create cost. + pub const fn tx_create_cost() -> GasId { + Self::new(35) + } + + /// Initial tx gas initcode cost per word. + pub const fn tx_initcode_cost() -> GasId { + Self::new(36) + } } #[cfg(test)] diff --git a/crates/context/interface/src/context.rs b/crates/context/interface/src/context.rs index 014dcf85e1..d544e1cff4 100644 --- a/crates/context/interface/src/context.rs +++ b/crates/context/interface/src/context.rs @@ -152,6 +152,13 @@ pub trait ContextTr: Host { let (_, tx, _, _, _, local) = self.all_mut(); (tx, local) } + + /// Get the configuration and journal mutably + #[inline] + fn cfg_journal_mut(&mut self) -> (&Self::Cfg, &mut Self::Journal) { + let (_, _, cfg, journal, _, _) = self.all_mut(); + (cfg, journal) + } } /// Inner Context error used for Interpreter to set error without returning it from instruction diff --git a/crates/context/interface/src/host.rs b/crates/context/interface/src/host.rs index e4efb22081..d3fb01ca58 100644 --- a/crates/context/interface/src/host.rs +++ b/crates/context/interface/src/host.rs @@ -213,7 +213,7 @@ impl DummyHost { /// Create a new dummy host with the given spec. pub fn new(spec: SpecId) -> Self { Self { - gas_params: GasParams::new_spec(spec), + gas_params: GasParams::new_spec(spec).clone(), } } } diff --git a/crates/context/interface/src/journaled_state.rs b/crates/context/interface/src/journaled_state.rs index 7edc224ae8..33c085a705 100644 --- a/crates/context/interface/src/journaled_state.rs +++ b/crates/context/interface/src/journaled_state.rs @@ -189,9 +189,16 @@ pub trait JournalTr { &mut self, address: Address, ) -> Result>, ::Error> { - self.load_account_mut_optional_code(address, false) + self.load_account_mut_skip_cold_load(address, false) } + /// Loads the journaled account. + fn load_account_mut_skip_cold_load( + &mut self, + address: Address, + skip_cold_load: bool, + ) -> Result>, ::Error>; + /// Loads the journaled account. #[inline] fn load_account_with_code_mut( diff --git a/crates/context/src/journal.rs b/crates/context/src/journal.rs index 8360261e70..6814627d10 100644 --- a/crates/context/src/journal.rs +++ b/crates/context/src/journal.rs @@ -256,6 +256,17 @@ impl JournalTr for Journal { self.inner.load_account(&mut self.database, address) } + #[inline] + fn load_account_mut_skip_cold_load( + &mut self, + address: Address, + skip_cold_load: bool, + ) -> Result>, DB::Error> { + self.inner + .load_account_mut_optional(&mut self.database, address, skip_cold_load) + .map_err(JournalLoadError::unwrap_db_error) + } + #[inline] fn load_account_mut_optional_code( &mut self, diff --git a/crates/handler/src/frame.rs b/crates/handler/src/frame.rs index 2b3ced2c1b..f5aea40738 100644 --- a/crates/handler/src/frame.rs +++ b/crates/handler/src/frame.rs @@ -4,7 +4,6 @@ use crate::{ }; use context::result::FromStringError; use context_interface::{ - cfg::gas::CODEDEPOSIT, context::ContextError, journaled_state::{account::JournaledAccountTr, JournalCheckpoint, JournalTr}, local::{FrameToken, OutFrame}, @@ -388,8 +387,6 @@ impl EthFrame { context: &mut CTX, next_action: InterpreterAction, ) -> Result, ERROR> { - let spec = context.cfg().spec().into(); - // Run interpreter let mut interpreter_result = match next_action { @@ -420,16 +417,13 @@ impl EthFrame { ))) } FrameData::Create(frame) => { - let max_code_size = context.cfg().max_code_size(); - let is_eip3541_disabled = context.cfg().is_eip3541_disabled(); + let (cfg, journal) = context.cfg_journal_mut(); return_create( - context.journal_mut(), + journal, + cfg, self.checkpoint, &mut interpreter_result, frame.created_address, - max_code_size, - is_eip3541_disabled, - spec, ); ItemOrResult::Result(FrameResult::Create(CreateOutcome::new( @@ -535,15 +529,17 @@ impl EthFrame { } /// Handles the result of a CREATE operation, including validation and state updates. -pub fn return_create( +pub fn return_create( journal: &mut JOURNAL, + cfg: CFG, checkpoint: JournalCheckpoint, interpreter_result: &mut InterpreterResult, address: Address, - max_code_size: usize, - is_eip3541_disabled: bool, - spec_id: SpecId, ) { + let max_code_size = cfg.max_code_size(); + let is_eip3541_disabled = cfg.is_eip3541_disabled(); + let spec_id = cfg.spec().into(); + // If return is not ok revert and return. if !interpreter_result.result.is_ok() { journal.checkpoint_revert(checkpoint); @@ -569,7 +565,9 @@ pub fn return_create( interpreter_result.result = InstructionResult::CreateContractSizeLimit; return; } - let gas_for_code = interpreter_result.output.len() as u64 * CODEDEPOSIT; + let gas_for_code = cfg + .gas_params() + .code_deposit_cost(interpreter_result.output.len()); 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/validation.rs b/crates/handler/src/validation.rs index d038cd3f87..569ff23784 100644 --- a/crates/handler/src/validation.rs +++ b/crates/handler/src/validation.rs @@ -4,7 +4,7 @@ use context_interface::{ Block, Cfg, ContextTr, }; use core::cmp; -use interpreter::gas::{self, InitialAndFloorGas}; +use interpreter::{instructions::calculate_initial_tx_gas_for_tx, InitialAndFloorGas}; use primitives::{eip4844, hardfork::SpecId, B256}; /// Validates the execution environment including block and transaction parameters. @@ -230,7 +230,7 @@ pub fn validate_initial_tx_gas( spec: SpecId, is_eip7623_disabled: bool, ) -> Result { - let mut gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec); + let mut gas = calculate_initial_tx_gas_for_tx(&tx, spec); if is_eip7623_disabled { gas.floor_gas = 0 diff --git a/crates/interpreter/src/gas.rs b/crates/interpreter/src/gas.rs index 68dbc24103..d862ab9499 100644 --- a/crates/interpreter/src/gas.rs +++ b/crates/interpreter/src/gas.rs @@ -1,8 +1,6 @@ //! EVM gas calculation utilities. -mod calc; - -pub use calc::*; +pub use context_interface::cfg::gas::*; /// 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 deleted file mode 100644 index ef4e79469b..0000000000 --- a/crates/interpreter/src/gas/calc.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::num_words; -use context_interface::{ - cfg::gas::*, transaction::AccessListItemTr as _, Transaction, TransactionType, -}; -use primitives::{eip7702, hardfork::SpecId}; - -/// 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 -} - -/// 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 -} diff --git a/crates/interpreter/src/lib.rs b/crates/interpreter/src/lib.rs index 675554b623..dc4f546424 100644 --- a/crates/interpreter/src/lib.rs +++ b/crates/interpreter/src/lib.rs @@ -27,10 +27,11 @@ pub mod interpreter_types; // Reexport primary types. pub use context_interface::{ + cfg::gas::InitialAndFloorGas, context::{SStoreResult, SelfDestructResult, StateLoad}, host, CreateScheme, Host, }; -pub use gas::{Gas, InitialAndFloorGas}; +pub use gas::Gas; pub use instruction_context::InstructionContext; pub use instruction_result::*; pub use instructions::{instruction_table, Instruction, InstructionTable}; diff --git a/crates/op-revm/src/l1block.rs b/crates/op-revm/src/l1block.rs index 325796f4da..eff2ab0a53 100644 --- a/crates/op-revm/src/l1block.rs +++ b/crates/op-revm/src/l1block.rs @@ -13,7 +13,7 @@ use crate::{ use revm::{ context_interface::cfg::gas::{NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, database_interface::Database, - interpreter::{gas::get_tokens_in_calldata, Gas}, + interpreter::{gas::get_tokens_in_calldata_istanbul, Gas}, primitives::U256, }; @@ -234,7 +234,7 @@ impl L1BlockInfo { }; // tokens in calldata where non-zero bytes are priced 4 times higher than zero bytes (Same as in Istanbul). - let mut tokens_in_transaction_data = get_tokens_in_calldata(input, true); + let mut tokens_in_transaction_data = get_tokens_in_calldata_istanbul(input); // Prior to regolith, an extra 68 non zero bytes were included in the rollup data costs. if !spec_id.is_enabled_in(OpSpecId::REGOLITH) { diff --git a/examples/cheatcode_inspector/src/main.rs b/examples/cheatcode_inspector/src/main.rs index cbe0007aaa..71d5b67690 100644 --- a/examples/cheatcode_inspector/src/main.rs +++ b/examples/cheatcode_inspector/src/main.rs @@ -292,6 +292,15 @@ impl JournalTr for Backend { .sstore_skip_cold_load(address, key, value, skip_cold_load) } + fn load_account_mut_skip_cold_load( + &mut self, + address: Address, + skip_cold_load: bool, + ) -> Result>, Infallible> { + self.journaled_state + .load_account_mut_skip_cold_load(address, skip_cold_load) + } + fn load_account_info_skip_cold_load( &mut self, address: Address,