Skip to main content

revm_context_interface/cfg/
gas_params.rs

1//! Gas table for dynamic gas constants.
2
3use crate::{
4    cfg::gas::{self, get_tokens_in_calldata, InitialAndFloorGas},
5    context::SStoreResult,
6};
7use core::hash::{Hash, Hasher};
8use primitives::{
9    eip7702,
10    hardfork::SpecId::{self},
11    OnceLock, U256,
12};
13use std::sync::Arc;
14
15/// Gas table for dynamic gas constants.
16#[derive(Clone)]
17pub struct GasParams {
18    /// Table of gas costs for operations
19    table: Arc<[u64; 256]>,
20    /// Pointer to the table.
21    ptr: *const u64,
22}
23
24impl PartialEq<GasParams> for GasParams {
25    fn eq(&self, other: &GasParams) -> bool {
26        self.table == other.table
27    }
28}
29
30impl Hash for GasParams {
31    fn hash<H: Hasher>(&self, hasher: &mut H) {
32        self.table.hash(hasher);
33    }
34}
35
36/// Pointer points to Arc so it is safe to send across threads
37unsafe impl Send for GasParams {}
38/// Pointer points to Arc so it is safe to access
39unsafe impl Sync for GasParams {}
40
41impl core::fmt::Debug for GasParams {
42    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43        write!(f, "GasParams {{ table: {:?} }}", self.table)
44    }
45}
46
47/// Returns number of words what would fit to provided number of bytes,
48/// i.e. it rounds up the number bytes to number of words.
49#[inline]
50pub const fn num_words(len: usize) -> usize {
51    len.div_ceil(32)
52}
53
54impl Eq for GasParams {}
55#[cfg(feature = "serde")]
56mod serde {
57    use super::{Arc, GasParams};
58    use std::vec::Vec;
59
60    #[derive(serde::Serialize, serde::Deserialize)]
61    struct GasParamsSerde {
62        table: Vec<u64>,
63    }
64
65    #[cfg(feature = "serde")]
66    impl serde::Serialize for GasParams {
67        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68        where
69            S: serde::Serializer,
70        {
71            GasParamsSerde {
72                table: self.table.to_vec(),
73            }
74            .serialize(serializer)
75        }
76    }
77
78    impl<'de> serde::Deserialize<'de> for GasParams {
79        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80        where
81            D: serde::Deserializer<'de>,
82        {
83            let table = GasParamsSerde::deserialize(deserializer)?;
84            if table.table.len() != 256 {
85                return Err(serde::de::Error::custom("Invalid gas params length"));
86            }
87            Ok(Self::new(Arc::new(table.table.try_into().unwrap())))
88        }
89    }
90}
91
92impl Default for GasParams {
93    fn default() -> Self {
94        Self::new_spec(SpecId::default())
95    }
96}
97
98impl GasParams {
99    /// Creates a new `GasParams` with the given table.
100    #[inline]
101    pub fn new(table: Arc<[u64; 256]>) -> Self {
102        Self {
103            ptr: table.as_ptr(),
104            table,
105        }
106    }
107
108    /// Overrides the gas cost for the given gas id.
109    ///
110    /// It will clone underlying table and override the values.
111    ///
112    /// Use to override default gas cost
113    ///
114    /// ```rust
115    /// use revm_context_interface::cfg::gas_params::{GasParams, GasId};
116    /// use primitives::hardfork::SpecId;
117    ///
118    /// let mut gas_table = GasParams::new_spec(SpecId::default());
119    /// gas_table.override_gas([(GasId::memory_linear_cost(), 2), (GasId::memory_quadratic_reduction(), 512)].into_iter());
120    /// assert_eq!(gas_table.get(GasId::memory_linear_cost()), 2);
121    /// assert_eq!(gas_table.get(GasId::memory_quadratic_reduction()), 512);
122    /// ```
123    pub fn override_gas(&mut self, values: impl IntoIterator<Item = (GasId, u64)>) {
124        let mut table = *self.table.clone();
125        for (id, value) in values.into_iter() {
126            table[id.as_usize()] = value;
127        }
128        *self = Self::new(Arc::new(table));
129    }
130
131    /// Returns the table.
132    #[inline]
133    pub fn table(&self) -> &[u64; 256] {
134        &self.table
135    }
136
137    /// Creates a new `GasParams` for the given spec.
138    #[inline(never)]
139    pub fn new_spec(spec: SpecId) -> Self {
140        use SpecId::*;
141        let gas_params = match spec {
142            FRONTIER | FRONTIER_THAWING => {
143                static TABLE: OnceLock<GasParams> = OnceLock::new();
144                TABLE.get_or_init(|| Self::new_spec_inner(spec))
145            }
146            // Transaction creation cost was added in homestead fork.
147            HOMESTEAD | DAO_FORK => {
148                static TABLE: OnceLock<GasParams> = OnceLock::new();
149                TABLE.get_or_init(|| Self::new_spec_inner(spec))
150            }
151            // New account cost for selfdestruct was added in tangerine fork.
152            TANGERINE => {
153                static TABLE: OnceLock<GasParams> = OnceLock::new();
154                TABLE.get_or_init(|| Self::new_spec_inner(spec))
155            }
156            // EXP cost was increased in spurious dragon fork.
157            SPURIOUS_DRAGON | BYZANTIUM | CONSTANTINOPLE | PETERSBURG => {
158                static TABLE: OnceLock<GasParams> = OnceLock::new();
159                TABLE.get_or_init(|| Self::new_spec_inner(spec))
160            }
161            // SSTORE gas calculation changed in istanbul fork.
162            ISTANBUL | MUIR_GLACIER => {
163                static TABLE: OnceLock<GasParams> = OnceLock::new();
164                TABLE.get_or_init(|| Self::new_spec_inner(spec))
165            }
166            // Warm/cold state access
167            BERLIN => {
168                static TABLE: OnceLock<GasParams> = OnceLock::new();
169                TABLE.get_or_init(|| Self::new_spec_inner(spec))
170            }
171            // Refund reduction in london fork.
172            LONDON | ARROW_GLACIER | GRAY_GLACIER | MERGE => {
173                static TABLE: OnceLock<GasParams> = OnceLock::new();
174                TABLE.get_or_init(|| Self::new_spec_inner(spec))
175            }
176            // Transaction initcode cost was introduced in shanghai fork.
177            SHANGHAI | CANCUN => {
178                static TABLE: OnceLock<GasParams> = OnceLock::new();
179                TABLE.get_or_init(|| Self::new_spec_inner(spec))
180            }
181            // EIP-7702 was introduced in prague fork.
182            PRAGUE | OSAKA => {
183                static TABLE: OnceLock<GasParams> = OnceLock::new();
184                TABLE.get_or_init(|| Self::new_spec_inner(spec))
185            }
186            // New fork.
187            SpecId::AMSTERDAM => {
188                static TABLE: OnceLock<GasParams> = OnceLock::new();
189                TABLE.get_or_init(|| Self::new_spec_inner(spec))
190            }
191        };
192        gas_params.clone()
193    }
194
195    /// Creates a new `GasParams` for the given spec.
196    #[inline]
197    fn new_spec_inner(spec: SpecId) -> Self {
198        let mut table = [0; 256];
199
200        table[GasId::exp_byte_gas().as_usize()] = 10;
201        table[GasId::logdata().as_usize()] = gas::LOGDATA;
202        table[GasId::logtopic().as_usize()] = gas::LOGTOPIC;
203        table[GasId::copy_per_word().as_usize()] = gas::COPY;
204        table[GasId::extcodecopy_per_word().as_usize()] = gas::COPY;
205        table[GasId::mcopy_per_word().as_usize()] = gas::COPY;
206        table[GasId::keccak256_per_word().as_usize()] = gas::KECCAK256WORD;
207        table[GasId::memory_linear_cost().as_usize()] = gas::MEMORY;
208        table[GasId::memory_quadratic_reduction().as_usize()] = 512;
209        table[GasId::initcode_per_word().as_usize()] = gas::INITCODE_WORD_COST;
210        table[GasId::create().as_usize()] = gas::CREATE;
211        table[GasId::call_stipend_reduction().as_usize()] = 64;
212        table[GasId::transfer_value_cost().as_usize()] = gas::CALLVALUE;
213        table[GasId::cold_account_additional_cost().as_usize()] = 0;
214        table[GasId::new_account_cost().as_usize()] = gas::NEWACCOUNT;
215        table[GasId::warm_storage_read_cost().as_usize()] = 0;
216        // Frontiers had fixed 5k cost.
217        table[GasId::sstore_static().as_usize()] = gas::SSTORE_RESET;
218        // SSTORE SET
219        table[GasId::sstore_set_without_load_cost().as_usize()] =
220            gas::SSTORE_SET - gas::SSTORE_RESET;
221        // SSTORE RESET Is covered in SSTORE_STATIC.
222        table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = 0;
223        // SSTORE SET REFUND (same as sstore_set_without_load_cost but used only in sstore_refund)
224        table[GasId::sstore_set_refund().as_usize()] =
225            table[GasId::sstore_set_without_load_cost().as_usize()];
226        // SSTORE RESET REFUND (same as sstore_reset_without_cold_load_cost but used only in sstore_refund)
227        table[GasId::sstore_reset_refund().as_usize()] =
228            table[GasId::sstore_reset_without_cold_load_cost().as_usize()];
229        // SSTORE CLEARING SLOT REFUND
230        table[GasId::sstore_clearing_slot_refund().as_usize()] = 15000;
231        table[GasId::selfdestruct_refund().as_usize()] = 24000;
232        table[GasId::call_stipend().as_usize()] = gas::CALL_STIPEND;
233        table[GasId::cold_storage_additional_cost().as_usize()] = 0;
234        table[GasId::cold_storage_cost().as_usize()] = 0;
235        table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0;
236        table[GasId::code_deposit_cost().as_usize()] = gas::CODEDEPOSIT;
237        table[GasId::tx_token_non_zero_byte_multiplier().as_usize()] =
238            gas::NON_ZERO_BYTE_MULTIPLIER;
239        table[GasId::tx_token_cost().as_usize()] = gas::STANDARD_TOKEN_COST;
240        table[GasId::tx_base_stipend().as_usize()] = 21000;
241
242        if spec.is_enabled_in(SpecId::HOMESTEAD) {
243            table[GasId::tx_create_cost().as_usize()] = gas::CREATE;
244        }
245
246        if spec.is_enabled_in(SpecId::TANGERINE) {
247            table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT;
248        }
249
250        if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
251            table[GasId::exp_byte_gas().as_usize()] = 50;
252        }
253
254        if spec.is_enabled_in(SpecId::ISTANBUL) {
255            table[GasId::sstore_static().as_usize()] = gas::ISTANBUL_SLOAD_GAS;
256            table[GasId::sstore_set_without_load_cost().as_usize()] =
257                gas::SSTORE_SET - gas::ISTANBUL_SLOAD_GAS;
258            table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
259                gas::SSTORE_RESET - gas::ISTANBUL_SLOAD_GAS;
260            table[GasId::sstore_set_refund().as_usize()] =
261                table[GasId::sstore_set_without_load_cost().as_usize()];
262            table[GasId::sstore_reset_refund().as_usize()] =
263                table[GasId::sstore_reset_without_cold_load_cost().as_usize()];
264            table[GasId::tx_token_non_zero_byte_multiplier().as_usize()] =
265                gas::NON_ZERO_BYTE_MULTIPLIER_ISTANBUL;
266        }
267
268        if spec.is_enabled_in(SpecId::BERLIN) {
269            table[GasId::sstore_static().as_usize()] = gas::WARM_STORAGE_READ_COST;
270            table[GasId::cold_account_additional_cost().as_usize()] =
271                gas::COLD_ACCOUNT_ACCESS_COST_ADDITIONAL;
272            table[GasId::cold_storage_additional_cost().as_usize()] =
273                gas::COLD_SLOAD_COST - gas::WARM_STORAGE_READ_COST;
274            table[GasId::cold_storage_cost().as_usize()] = gas::COLD_SLOAD_COST;
275            table[GasId::warm_storage_read_cost().as_usize()] = gas::WARM_STORAGE_READ_COST;
276
277            table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
278                gas::WARM_SSTORE_RESET - gas::WARM_STORAGE_READ_COST;
279            table[GasId::sstore_set_without_load_cost().as_usize()] =
280                gas::SSTORE_SET - gas::WARM_STORAGE_READ_COST;
281            table[GasId::sstore_set_refund().as_usize()] =
282                table[GasId::sstore_set_without_load_cost().as_usize()];
283            table[GasId::sstore_reset_refund().as_usize()] =
284                table[GasId::sstore_reset_without_cold_load_cost().as_usize()];
285
286            table[GasId::tx_access_list_address_cost().as_usize()] = gas::ACCESS_LIST_ADDRESS;
287            table[GasId::tx_access_list_storage_key_cost().as_usize()] =
288                gas::ACCESS_LIST_STORAGE_KEY;
289        }
290
291        if spec.is_enabled_in(SpecId::LONDON) {
292            // EIP-3529: Reduction in refunds
293
294            // Replace SSTORE_CLEARS_SCHEDULE (as defined in EIP-2200) with
295            // SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST (4,800 gas as of EIP-2929 + EIP-2930)
296            table[GasId::sstore_clearing_slot_refund().as_usize()] =
297                gas::WARM_SSTORE_RESET + gas::ACCESS_LIST_STORAGE_KEY;
298
299            table[GasId::selfdestruct_refund().as_usize()] = 0;
300        }
301
302        if spec.is_enabled_in(SpecId::SHANGHAI) {
303            table[GasId::tx_initcode_cost().as_usize()] = gas::INITCODE_WORD_COST;
304        }
305
306        if spec.is_enabled_in(SpecId::PRAGUE) {
307            table[GasId::tx_eip7702_per_empty_account_cost().as_usize()] =
308                eip7702::PER_EMPTY_ACCOUNT_COST;
309
310            // EIP-7702 authorization refund for existing accounts
311            table[GasId::tx_eip7702_auth_refund().as_usize()] =
312                eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST;
313
314            table[GasId::tx_floor_cost_per_token().as_usize()] = gas::TOTAL_COST_FLOOR_PER_TOKEN;
315            table[GasId::tx_floor_cost_base_gas().as_usize()] = 21000;
316        }
317
318        // EIP-8037: State creation gas cost increase
319        if spec.is_enabled_in(SpecId::AMSTERDAM) {
320            // Hardcoded cost_per_state_byte for 100M block gas limit
321            const CPSB: u64 = 1174;
322
323            // Regular gas changes
324            table[GasId::create().as_usize()] = 9000;
325            table[GasId::tx_create_cost().as_usize()] = 9000;
326            table[GasId::code_deposit_cost().as_usize()] = 0;
327            table[GasId::new_account_cost().as_usize()] = 0;
328            table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0;
329            // GAS_STORAGE_SET regular = GAS_STORAGE_UPDATE - GAS_COLD_SLOAD = 5000 - 2100 = 2900
330            // sstore_set_without_load_cost = 2900 - WARM_STORAGE_READ_COST(100) = 2800
331            table[GasId::sstore_set_without_load_cost().as_usize()] = 2800;
332
333            // State gas values
334            table[GasId::sstore_set_state_gas().as_usize()] = 32 * CPSB;
335            table[GasId::new_account_state_gas().as_usize()] = 112 * CPSB;
336            table[GasId::code_deposit_state_gas().as_usize()] = CPSB;
337            table[GasId::create_state_gas().as_usize()] = 112 * CPSB;
338
339            // SSTORE refund for 0→X→0 restoration: state gas + regular gas
340            table[GasId::sstore_set_refund().as_usize()] = 32 * CPSB + 2800;
341
342            // EIP-7702 parameter updates under EIP-8037
343            // Total per auth charged pessimistically:
344            //   regular: PER_AUTH_BASE_COST_REGULAR (7500)
345            //   state: (PER_EMPTY_ACCOUNT 112 + PER_AUTH_BASE 23) × CPSB
346            table[GasId::tx_eip7702_per_empty_account_cost().as_usize()] = 7500 + (112 + 23) * CPSB;
347            // Refund for existing accounts: PER_EMPTY_ACCOUNT state gas (112 × CPSB)
348            table[GasId::tx_eip7702_auth_refund().as_usize()] = 112 * CPSB;
349
350            // State gas per auth for initial_state_gas tracking
351            table[GasId::tx_eip7702_per_auth_state_gas().as_usize()] = (112 + 23) * CPSB;
352        }
353
354        Self::new(Arc::new(table))
355    }
356
357    /// Gets the gas cost for the given gas id.
358    #[inline]
359    pub const fn get(&self, id: GasId) -> u64 {
360        unsafe { *self.ptr.add(id.as_usize()) }
361    }
362
363    /// `EXP` opcode cost calculation.
364    #[inline]
365    pub fn exp_cost(&self, power: U256) -> u64 {
366        if power.is_zero() {
367            return 0;
368        }
369        // EIP-160: EXP cost increase
370        self.get(GasId::exp_byte_gas())
371            .saturating_mul(log2floor(power) / 8 + 1)
372    }
373
374    /// Selfdestruct refund.
375    #[inline]
376    pub fn selfdestruct_refund(&self) -> i64 {
377        self.get(GasId::selfdestruct_refund()) as i64
378    }
379
380    /// Selfdestruct cold cost is calculated differently from other cold costs.
381    /// and it contains both cold and warm costs.
382    #[inline]
383    pub fn selfdestruct_cold_cost(&self) -> u64 {
384        self.cold_account_additional_cost() + self.warm_storage_read_cost()
385    }
386
387    /// Selfdestruct cost.
388    #[inline]
389    pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 {
390        let mut gas = 0;
391
392        // EIP-150: Gas cost changes for IO-heavy operations
393        if should_charge_topup {
394            gas += self.new_account_cost_for_selfdestruct();
395        }
396
397        if is_cold {
398            // Note: SELFDESTRUCT does not charge a WARM_STORAGE_READ_COST in case the recipient is already warm,
399            // which differs from how the other call-variants work. The reasoning behind this is to keep
400            // the changes small, a SELFDESTRUCT already costs 5K and is a no-op if invoked more than once.
401            //
402            // For GasParams both values are zero before BERLIN fork.
403            gas += self.selfdestruct_cold_cost();
404        }
405        gas
406    }
407
408    /// EXTCODECOPY gas cost
409    #[inline]
410    pub fn extcodecopy(&self, len: usize) -> u64 {
411        self.get(GasId::extcodecopy_per_word())
412            .saturating_mul(num_words(len) as u64)
413    }
414
415    /// MCOPY gas cost
416    #[inline]
417    pub fn mcopy_cost(&self, len: usize) -> u64 {
418        self.get(GasId::mcopy_per_word())
419            .saturating_mul(num_words(len) as u64)
420    }
421
422    /// Static gas cost for SSTORE opcode
423    #[inline]
424    pub fn sstore_static_gas(&self) -> u64 {
425        self.get(GasId::sstore_static())
426    }
427
428    /// SSTORE set cost
429    #[inline]
430    pub fn sstore_set_without_load_cost(&self) -> u64 {
431        self.get(GasId::sstore_set_without_load_cost())
432    }
433
434    /// SSTORE reset cost
435    #[inline]
436    pub fn sstore_reset_without_cold_load_cost(&self) -> u64 {
437        self.get(GasId::sstore_reset_without_cold_load_cost())
438    }
439
440    /// SSTORE clearing slot refund
441    #[inline]
442    pub fn sstore_clearing_slot_refund(&self) -> u64 {
443        self.get(GasId::sstore_clearing_slot_refund())
444    }
445
446    /// SSTORE set refund. Used in sstore_refund for SSTORE_SET_GAS - SLOAD_GAS.
447    #[inline]
448    pub fn sstore_set_refund(&self) -> u64 {
449        self.get(GasId::sstore_set_refund())
450    }
451
452    /// SSTORE reset refund. Used in sstore_refund for SSTORE_RESET_GAS - SLOAD_GAS.
453    #[inline]
454    pub fn sstore_reset_refund(&self) -> u64 {
455        self.get(GasId::sstore_reset_refund())
456    }
457
458    /// Dynamic gas cost for SSTORE opcode.
459    ///
460    /// Dynamic gas cost is gas that needs input from SSTORE operation to be calculated.
461    #[inline]
462    pub fn sstore_dynamic_gas(&self, is_istanbul: bool, vals: &SStoreResult, is_cold: bool) -> u64 {
463        // frontier logic gets charged for every SSTORE operation if original value is zero.
464        // this behaviour is fixed in istanbul fork.
465        if !is_istanbul {
466            if vals.is_present_zero() && !vals.is_new_zero() {
467                return self.sstore_set_without_load_cost();
468            } else {
469                return self.sstore_reset_without_cold_load_cost();
470            }
471        }
472
473        let mut gas = 0;
474
475        // this will be zero before berlin fork.
476        if is_cold {
477            gas += self.cold_storage_cost();
478        }
479
480        // if new values changed present value and present value is unchanged from original.
481        if vals.new_values_changes_present() && vals.is_original_eq_present() {
482            gas += if vals.is_original_zero() {
483                // set cost for creating storage slot (Zero slot means it is not existing).
484                // and previous condition says present is same as original.
485                self.sstore_set_without_load_cost()
486            } else {
487                // if new value is not zero, this means we are setting some value to it.
488                self.sstore_reset_without_cold_load_cost()
489            };
490        }
491        gas
492    }
493
494    /// SSTORE refund calculation.
495    #[inline]
496    pub fn sstore_refund(&self, is_istanbul: bool, vals: &SStoreResult) -> i64 {
497        // EIP-3529: Reduction in refunds
498        let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64;
499
500        if !is_istanbul {
501            // // before istanbul fork, refund was always awarded without checking original state.
502            if !vals.is_present_zero() && vals.is_new_zero() {
503                return sstore_clearing_slot_refund;
504            }
505            return 0;
506        }
507
508        // If current value equals new value (this is a no-op)
509        if vals.is_new_eq_present() {
510            return 0;
511        }
512
513        // refund for the clearing of storage slot.
514        // As new is not equal to present, new values zero means that original and present values are not zero
515        if vals.is_original_eq_present() && vals.is_new_zero() {
516            return sstore_clearing_slot_refund;
517        }
518
519        let mut refund = 0;
520        // If original value is not 0
521        if !vals.is_original_zero() {
522            // If current value is 0 (also means that new value is not 0),
523            if vals.is_present_zero() {
524                // remove SSTORE_CLEARS_SCHEDULE gas from refund counter.
525                refund -= sstore_clearing_slot_refund;
526            // If new value is 0 (also means that current value is not 0),
527            } else if vals.is_new_zero() {
528                // add SSTORE_CLEARS_SCHEDULE gas to refund counter.
529                refund += sstore_clearing_slot_refund;
530            }
531        }
532
533        // If original value equals new value (this storage slot is reset)
534        if vals.is_original_eq_new() {
535            // If original value is 0
536            if vals.is_original_zero() {
537                // add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
538                refund += self.sstore_set_refund() as i64;
539            // Otherwise
540            } else {
541                // add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
542                refund += self.sstore_reset_refund() as i64;
543            }
544        }
545        refund
546    }
547
548    /// `LOG` opcode cost calculation.
549    #[inline]
550    pub const fn log_cost(&self, n: u8, len: u64) -> u64 {
551        self.get(GasId::logdata())
552            .saturating_mul(len)
553            .saturating_add(self.get(GasId::logtopic()) * n as u64)
554    }
555
556    /// KECCAK256 gas cost per word
557    #[inline]
558    pub fn keccak256_cost(&self, len: usize) -> u64 {
559        self.get(GasId::keccak256_per_word())
560            .saturating_mul(num_words(len) as u64)
561    }
562
563    /// Memory gas cost
564    #[inline]
565    pub fn memory_cost(&self, len: usize) -> u64 {
566        let len = len as u64;
567        self.get(GasId::memory_linear_cost())
568            .saturating_mul(len)
569            .saturating_add(
570                (len.saturating_mul(len))
571                    .saturating_div(self.get(GasId::memory_quadratic_reduction())),
572            )
573    }
574
575    /// Initcode word cost
576    #[inline]
577    pub fn initcode_cost(&self, len: usize) -> u64 {
578        self.get(GasId::initcode_per_word())
579            .saturating_mul(num_words(len) as u64)
580    }
581
582    /// Create gas cost
583    #[inline]
584    pub fn create_cost(&self) -> u64 {
585        self.get(GasId::create())
586    }
587
588    /// Create2 gas cost.
589    #[inline]
590    pub fn create2_cost(&self, len: usize) -> u64 {
591        self.get(GasId::create()).saturating_add(
592            self.get(GasId::keccak256_per_word())
593                .saturating_mul(num_words(len) as u64),
594        )
595    }
596
597    /// Call stipend.
598    #[inline]
599    pub fn call_stipend(&self) -> u64 {
600        self.get(GasId::call_stipend())
601    }
602
603    /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit.
604    #[inline]
605    pub fn call_stipend_reduction(&self, gas_limit: u64) -> u64 {
606        gas_limit - gas_limit / self.get(GasId::call_stipend_reduction())
607    }
608
609    /// Transfer value cost
610    #[inline]
611    pub fn transfer_value_cost(&self) -> u64 {
612        self.get(GasId::transfer_value_cost())
613    }
614
615    /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded.
616    #[inline]
617    pub fn cold_account_additional_cost(&self) -> u64 {
618        self.get(GasId::cold_account_additional_cost())
619    }
620
621    /// Cold storage additional cost.
622    #[inline]
623    pub fn cold_storage_additional_cost(&self) -> u64 {
624        self.get(GasId::cold_storage_additional_cost())
625    }
626
627    /// Cold storage cost.
628    #[inline]
629    pub fn cold_storage_cost(&self) -> u64 {
630        self.get(GasId::cold_storage_cost())
631    }
632
633    /// New account cost. New account cost is added to the gas cost if the account is empty.
634    #[inline]
635    pub fn new_account_cost(&self, is_spurious_dragon: bool, transfers_value: bool) -> u64 {
636        // EIP-161: State trie clearing (invariant-preserving alternative)
637        // Pre-Spurious Dragon: always charge for new account
638        // Post-Spurious Dragon: only charge if value is transferred
639        if !is_spurious_dragon || transfers_value {
640            return self.get(GasId::new_account_cost());
641        }
642        0
643    }
644
645    /// New account cost for selfdestruct.
646    #[inline]
647    pub fn new_account_cost_for_selfdestruct(&self) -> u64 {
648        self.get(GasId::new_account_cost_for_selfdestruct())
649    }
650
651    /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded.
652    #[inline]
653    pub fn warm_storage_read_cost(&self) -> u64 {
654        self.get(GasId::warm_storage_read_cost())
655    }
656
657    /// Copy cost
658    #[inline]
659    pub fn copy_cost(&self, len: usize) -> u64 {
660        self.copy_per_word_cost(num_words(len))
661    }
662
663    /// Copy per word cost
664    #[inline]
665    pub fn copy_per_word_cost(&self, word_num: usize) -> u64 {
666        self.get(GasId::copy_per_word())
667            .saturating_mul(word_num as u64)
668    }
669
670    /// Code deposit cost, calculated per byte as len * code_deposit_cost.
671    #[inline]
672    pub fn code_deposit_cost(&self, len: usize) -> u64 {
673        self.get(GasId::code_deposit_cost())
674            .saturating_mul(len as u64)
675    }
676
677    /// State gas for SSTORE: charges for new slot creation (zero → non-zero).
678    #[inline]
679    pub fn sstore_state_gas(&self, vals: &SStoreResult) -> u64 {
680        if vals.new_values_changes_present()
681            && vals.is_original_eq_present()
682            && vals.is_original_zero()
683        {
684            self.get(GasId::sstore_set_state_gas())
685        } else {
686            0
687        }
688    }
689
690    /// State gas for new account creation.
691    #[inline]
692    pub fn new_account_state_gas(&self) -> u64 {
693        self.get(GasId::new_account_state_gas())
694    }
695
696    /// State gas per byte for code deposit.
697    #[inline]
698    pub fn code_deposit_state_gas(&self, len: usize) -> u64 {
699        self.get(GasId::code_deposit_state_gas())
700            .saturating_mul(len as u64)
701    }
702
703    /// State gas for contract metadata creation.
704    #[inline]
705    pub fn create_state_gas(&self) -> u64 {
706        self.get(GasId::create_state_gas())
707    }
708
709    /// Used in [GasParams::initial_tx_gas] to calculate the eip7702 per empty account cost.
710    #[inline]
711    pub fn tx_eip7702_per_empty_account_cost(&self) -> u64 {
712        self.get(GasId::tx_eip7702_per_empty_account_cost())
713    }
714
715    /// EIP-7702 authorization refund per existing account.
716    ///
717    /// This is the gas refund given when an EIP-7702 authorization is applied
718    /// to an account that already exists in the trie. By default this is
719    /// `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` (25000 - 12500 = 12500).
720    #[inline]
721    pub fn tx_eip7702_auth_refund(&self) -> u64 {
722        self.get(GasId::tx_eip7702_auth_refund())
723    }
724
725    /// EIP-8037: State gas per EIP-7702 authorization (pessimistic).
726    ///
727    /// Used for `initial_state_gas` tracking. Zero before AMSTERDAM.
728    #[inline]
729    pub fn tx_eip7702_per_auth_state_gas(&self) -> u64 {
730        self.get(GasId::tx_eip7702_per_auth_state_gas())
731    }
732
733    /// EIP-8037: Splits a total EIP-7702 refund into state gas and regular gas portions.
734    ///
735    /// At validation time, `initial_tx_gas` splits each auth cost into state + regular.
736    /// This method is the inverse: it recovers how much of the total refund was state gas.
737    ///
738    /// The state gas portion reduces `initial_state_gas` directly (not subject to refund caps).
739    /// The regular gas portion goes through the standard 1/5 refund cap.
740    ///
741    /// # Returns
742    ///
743    /// `(state_refund, regular_refund)` for the given total refund.
744    #[inline]
745    pub fn split_eip7702_refund(&self, total_refund: u64) -> (u64, u64) {
746        let per_auth_refund = self.tx_eip7702_auth_refund();
747        let per_auth_state_gas = self.tx_eip7702_per_auth_state_gas();
748        if per_auth_state_gas > 0 && per_auth_refund > 0 && total_refund > 0 {
749            let state_refund_per_auth = core::cmp::min(per_auth_refund, per_auth_state_gas);
750            let num_refunded = total_refund / per_auth_refund;
751            let state_refund = num_refunded * state_refund_per_auth;
752            (state_refund, total_refund - state_refund)
753        } else {
754            (0, total_refund)
755        }
756    }
757
758    /// Used in [GasParams::initial_tx_gas] to calculate the token non zero byte multiplier.
759    #[inline]
760    pub fn tx_token_non_zero_byte_multiplier(&self) -> u64 {
761        self.get(GasId::tx_token_non_zero_byte_multiplier())
762    }
763
764    /// Used in [GasParams::initial_tx_gas] to calculate the token cost for input data.
765    #[inline]
766    pub fn tx_token_cost(&self) -> u64 {
767        self.get(GasId::tx_token_cost())
768    }
769
770    /// Used in [GasParams::initial_tx_gas] to calculate the floor gas per token.
771    pub fn tx_floor_cost_per_token(&self) -> u64 {
772        self.get(GasId::tx_floor_cost_per_token())
773    }
774
775    /// Used [GasParams::initial_tx_gas] to calculate the floor gas.
776    ///
777    /// Floor gas is introduced in EIP-7623.
778    #[inline]
779    pub fn tx_floor_cost(&self, tokens_in_calldata: u64) -> u64 {
780        self.tx_floor_cost_per_token() * tokens_in_calldata + self.tx_floor_cost_base_gas()
781    }
782
783    /// Used in [GasParams::initial_tx_gas] to calculate the floor gas base gas.
784    pub fn tx_floor_cost_base_gas(&self) -> u64 {
785        self.get(GasId::tx_floor_cost_base_gas())
786    }
787
788    /// Used in [GasParams::initial_tx_gas] to calculate the access list address cost.
789    pub fn tx_access_list_address_cost(&self) -> u64 {
790        self.get(GasId::tx_access_list_address_cost())
791    }
792
793    /// Used in [GasParams::initial_tx_gas] to calculate the access list storage key cost.
794    pub fn tx_access_list_storage_key_cost(&self) -> u64 {
795        self.get(GasId::tx_access_list_storage_key_cost())
796    }
797
798    /// Calculate the total gas cost for an access list.
799    ///
800    /// This is a helper method that calculates the combined cost of:
801    /// - `accounts` addresses in the access list
802    /// - `storages` storage keys in the access list
803    ///
804    /// # Examples
805    ///
806    /// ```
807    /// use revm_context_interface::cfg::gas_params::GasParams;
808    /// use primitives::hardfork::SpecId;
809    ///
810    /// let gas_params = GasParams::new_spec(SpecId::BERLIN);
811    /// // Calculate cost for 2 addresses and 5 storage keys
812    /// let cost = gas_params.tx_access_list_cost(2, 5);
813    /// assert_eq!(cost, 2 * 2400 + 5 * 1900); // 2 * ACCESS_LIST_ADDRESS + 5 * ACCESS_LIST_STORAGE_KEY
814    /// ```
815    #[inline]
816    pub fn tx_access_list_cost(&self, accounts: u64, storages: u64) -> u64 {
817        accounts
818            .saturating_mul(self.tx_access_list_address_cost())
819            .saturating_add(storages.saturating_mul(self.tx_access_list_storage_key_cost()))
820    }
821
822    /// Used in [GasParams::initial_tx_gas] to calculate the base transaction stipend.
823    pub fn tx_base_stipend(&self) -> u64 {
824        self.get(GasId::tx_base_stipend())
825    }
826
827    /// Used in [GasParams::initial_tx_gas] to calculate the create cost.
828    ///
829    /// Similar to the [`Self::create_cost`] method but it got activated in different fork,
830    #[inline]
831    pub fn tx_create_cost(&self) -> u64 {
832        self.get(GasId::tx_create_cost())
833    }
834
835    /// Used in [GasParams::initial_tx_gas] to calculate the initcode cost per word of len.
836    #[inline]
837    pub fn tx_initcode_cost(&self, len: usize) -> u64 {
838        self.get(GasId::tx_initcode_cost())
839            .saturating_mul(num_words(len) as u64)
840    }
841
842    /// Initial gas that is deducted for transaction to be included.
843    /// Initial gas contains initial stipend gas, gas for access list and input data.
844    ///
845    /// Under EIP-8037, state gas is tracked separately in `initial_state_gas` and
846    /// added to `initial_total_gas` at the end. The state gas components are:
847    /// - EIP-7702 auth list state gas (per-auth account creation + metadata costs)
848    /// - For CREATE transactions: `create_state_gas` (account creation + contract metadata)
849    ///
850    /// Note: `code_deposit_state_gas` is not included since deployed code size is unknown at validation time.
851    ///
852    /// # Returns
853    ///
854    /// - Intrinsic gas (including state gas for CREATE)
855    /// - Number of tokens in calldata
856    pub fn initial_tx_gas(
857        &self,
858        input: &[u8],
859        is_create: bool,
860        access_list_accounts: u64,
861        access_list_storages: u64,
862        authorization_list_num: u64,
863    ) -> InitialAndFloorGas {
864        let mut gas = InitialAndFloorGas::default();
865
866        // Initdate stipend
867        let tokens_in_calldata =
868            get_tokens_in_calldata(input, self.tx_token_non_zero_byte_multiplier());
869
870        // EIP-7702: Compute auth list costs.
871        // Under EIP-8037, tx_eip7702_per_empty_account_cost bundles regular + state gas.
872        // We split them: regular goes in initial_total_gas, state goes in initial_state_gas.
873        let auth_total_cost = authorization_list_num * self.tx_eip7702_per_empty_account_cost();
874        let auth_state_gas = authorization_list_num * self.tx_eip7702_per_auth_state_gas();
875        let auth_regular_cost = auth_total_cost - auth_state_gas;
876
877        gas.initial_total_gas += tokens_in_calldata * self.tx_token_cost()
878            // before berlin tx_access_list_address_cost will be zero
879            + access_list_accounts * self.tx_access_list_address_cost()
880            // before berlin tx_access_list_storage_key_cost will be zero
881            + access_list_storages * self.tx_access_list_storage_key_cost()
882            + self.tx_base_stipend()
883            // EIP-7702: Only the regular portion of auth list cost
884            + auth_regular_cost;
885
886        // EIP-8037: Track auth list state gas separately for reservoir handling.
887        // State gas is added to initial_total_gas at the end of this function.
888        gas.initial_state_gas += auth_state_gas;
889
890        if is_create {
891            // EIP-2: Homestead Hard-fork Changes
892            gas.initial_total_gas += self.tx_create_cost();
893
894            // EIP-3860: Limit and meter initcode
895            gas.initial_total_gas += self.tx_initcode_cost(input.len());
896
897            // EIP-8037: State gas for CREATE transactions.
898            // create_state_gas covers both account creation and contract metadata.
899            gas.initial_state_gas += self.create_state_gas();
900        }
901
902        // Calculate gas floor for EIP-7623
903        gas.floor_gas = self.tx_floor_cost(tokens_in_calldata);
904
905        // EIP-8037: Include state gas in total initial gas.
906        // State gas is a subset of initial_total_gas, deducted before execution starts.
907        gas.initial_total_gas += gas.initial_state_gas;
908
909        gas
910    }
911}
912
913#[inline]
914pub(crate) fn log2floor(value: U256) -> u64 {
915    for i in (0..4).rev() {
916        let limb = value.as_limbs()[i];
917        if limb != 0 {
918            return i as u64 * 64 + 63 - limb.leading_zeros() as u64;
919        }
920    }
921    0
922}
923
924/// Gas identifier that maps onto index in gas table.
925#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
926pub struct GasId(u8);
927
928impl GasId {
929    /// Creates a new `GasId` with the given id.
930    pub const fn new(id: u8) -> Self {
931        Self(id)
932    }
933
934    /// Returns the id of the gas.
935    pub const fn as_u8(&self) -> u8 {
936        self.0
937    }
938
939    /// Returns the id of the gas as a usize.
940    pub const fn as_usize(&self) -> usize {
941        self.0 as usize
942    }
943
944    /// Returns the name of the gas identifier as a string.
945    ///
946    /// # Examples
947    ///
948    /// ```
949    /// use revm_context_interface::cfg::gas_params::GasId;
950    ///
951    /// assert_eq!(GasId::exp_byte_gas().name(), "exp_byte_gas");
952    /// assert_eq!(GasId::memory_linear_cost().name(), "memory_linear_cost");
953    /// assert_eq!(GasId::sstore_static().name(), "sstore_static");
954    /// ```
955    pub const fn name(&self) -> &'static str {
956        match self.0 {
957            x if x == Self::exp_byte_gas().as_u8() => "exp_byte_gas",
958            x if x == Self::extcodecopy_per_word().as_u8() => "extcodecopy_per_word",
959            x if x == Self::copy_per_word().as_u8() => "copy_per_word",
960            x if x == Self::logdata().as_u8() => "logdata",
961            x if x == Self::logtopic().as_u8() => "logtopic",
962            x if x == Self::mcopy_per_word().as_u8() => "mcopy_per_word",
963            x if x == Self::keccak256_per_word().as_u8() => "keccak256_per_word",
964            x if x == Self::memory_linear_cost().as_u8() => "memory_linear_cost",
965            x if x == Self::memory_quadratic_reduction().as_u8() => "memory_quadratic_reduction",
966            x if x == Self::initcode_per_word().as_u8() => "initcode_per_word",
967            x if x == Self::create().as_u8() => "create",
968            x if x == Self::call_stipend_reduction().as_u8() => "call_stipend_reduction",
969            x if x == Self::transfer_value_cost().as_u8() => "transfer_value_cost",
970            x if x == Self::cold_account_additional_cost().as_u8() => {
971                "cold_account_additional_cost"
972            }
973            x if x == Self::new_account_cost().as_u8() => "new_account_cost",
974            x if x == Self::warm_storage_read_cost().as_u8() => "warm_storage_read_cost",
975            x if x == Self::sstore_static().as_u8() => "sstore_static",
976            x if x == Self::sstore_set_without_load_cost().as_u8() => {
977                "sstore_set_without_load_cost"
978            }
979            x if x == Self::sstore_reset_without_cold_load_cost().as_u8() => {
980                "sstore_reset_without_cold_load_cost"
981            }
982            x if x == Self::sstore_clearing_slot_refund().as_u8() => "sstore_clearing_slot_refund",
983            x if x == Self::selfdestruct_refund().as_u8() => "selfdestruct_refund",
984            x if x == Self::call_stipend().as_u8() => "call_stipend",
985            x if x == Self::cold_storage_additional_cost().as_u8() => {
986                "cold_storage_additional_cost"
987            }
988            x if x == Self::cold_storage_cost().as_u8() => "cold_storage_cost",
989            x if x == Self::new_account_cost_for_selfdestruct().as_u8() => {
990                "new_account_cost_for_selfdestruct"
991            }
992            x if x == Self::code_deposit_cost().as_u8() => "code_deposit_cost",
993            x if x == Self::tx_eip7702_per_empty_account_cost().as_u8() => {
994                "tx_eip7702_per_empty_account_cost"
995            }
996            x if x == Self::tx_token_non_zero_byte_multiplier().as_u8() => {
997                "tx_token_non_zero_byte_multiplier"
998            }
999            x if x == Self::tx_token_cost().as_u8() => "tx_token_cost",
1000            x if x == Self::tx_floor_cost_per_token().as_u8() => "tx_floor_cost_per_token",
1001            x if x == Self::tx_floor_cost_base_gas().as_u8() => "tx_floor_cost_base_gas",
1002            x if x == Self::tx_access_list_address_cost().as_u8() => "tx_access_list_address_cost",
1003            x if x == Self::tx_access_list_storage_key_cost().as_u8() => {
1004                "tx_access_list_storage_key_cost"
1005            }
1006            x if x == Self::tx_base_stipend().as_u8() => "tx_base_stipend",
1007            x if x == Self::tx_create_cost().as_u8() => "tx_create_cost",
1008            x if x == Self::tx_initcode_cost().as_u8() => "tx_initcode_cost",
1009            x if x == Self::sstore_set_refund().as_u8() => "sstore_set_refund",
1010            x if x == Self::sstore_reset_refund().as_u8() => "sstore_reset_refund",
1011            x if x == Self::tx_eip7702_auth_refund().as_u8() => "tx_eip7702_auth_refund",
1012            x if x == Self::sstore_set_state_gas().as_u8() => "sstore_set_state_gas",
1013            x if x == Self::new_account_state_gas().as_u8() => "new_account_state_gas",
1014            x if x == Self::code_deposit_state_gas().as_u8() => "code_deposit_state_gas",
1015            x if x == Self::create_state_gas().as_u8() => "create_state_gas",
1016            x if x == Self::tx_eip7702_per_auth_state_gas().as_u8() => {
1017                "tx_eip7702_per_auth_state_gas"
1018            }
1019            _ => "unknown",
1020        }
1021    }
1022
1023    /// Converts a string to a `GasId`.
1024    ///
1025    /// Returns `None` if the string does not match any known gas identifier.
1026    ///
1027    /// # Examples
1028    ///
1029    /// ```
1030    /// use revm_context_interface::cfg::gas_params::GasId;
1031    ///
1032    /// assert_eq!(GasId::from_name("exp_byte_gas"), Some(GasId::exp_byte_gas()));
1033    /// assert_eq!(GasId::from_name("memory_linear_cost"), Some(GasId::memory_linear_cost()));
1034    /// assert_eq!(GasId::from_name("invalid_name"), None);
1035    /// ```
1036    pub fn from_name(s: &str) -> Option<GasId> {
1037        match s {
1038            "exp_byte_gas" => Some(Self::exp_byte_gas()),
1039            "extcodecopy_per_word" => Some(Self::extcodecopy_per_word()),
1040            "copy_per_word" => Some(Self::copy_per_word()),
1041            "logdata" => Some(Self::logdata()),
1042            "logtopic" => Some(Self::logtopic()),
1043            "mcopy_per_word" => Some(Self::mcopy_per_word()),
1044            "keccak256_per_word" => Some(Self::keccak256_per_word()),
1045            "memory_linear_cost" => Some(Self::memory_linear_cost()),
1046            "memory_quadratic_reduction" => Some(Self::memory_quadratic_reduction()),
1047            "initcode_per_word" => Some(Self::initcode_per_word()),
1048            "create" => Some(Self::create()),
1049            "call_stipend_reduction" => Some(Self::call_stipend_reduction()),
1050            "transfer_value_cost" => Some(Self::transfer_value_cost()),
1051            "cold_account_additional_cost" => Some(Self::cold_account_additional_cost()),
1052            "new_account_cost" => Some(Self::new_account_cost()),
1053            "warm_storage_read_cost" => Some(Self::warm_storage_read_cost()),
1054            "sstore_static" => Some(Self::sstore_static()),
1055            "sstore_set_without_load_cost" => Some(Self::sstore_set_without_load_cost()),
1056            "sstore_reset_without_cold_load_cost" => {
1057                Some(Self::sstore_reset_without_cold_load_cost())
1058            }
1059            "sstore_clearing_slot_refund" => Some(Self::sstore_clearing_slot_refund()),
1060            "selfdestruct_refund" => Some(Self::selfdestruct_refund()),
1061            "call_stipend" => Some(Self::call_stipend()),
1062            "cold_storage_additional_cost" => Some(Self::cold_storage_additional_cost()),
1063            "cold_storage_cost" => Some(Self::cold_storage_cost()),
1064            "new_account_cost_for_selfdestruct" => Some(Self::new_account_cost_for_selfdestruct()),
1065            "code_deposit_cost" => Some(Self::code_deposit_cost()),
1066            "tx_eip7702_per_empty_account_cost" => Some(Self::tx_eip7702_per_empty_account_cost()),
1067            "tx_token_non_zero_byte_multiplier" => Some(Self::tx_token_non_zero_byte_multiplier()),
1068            "tx_token_cost" => Some(Self::tx_token_cost()),
1069            "tx_floor_cost_per_token" => Some(Self::tx_floor_cost_per_token()),
1070            "tx_floor_cost_base_gas" => Some(Self::tx_floor_cost_base_gas()),
1071            "tx_access_list_address_cost" => Some(Self::tx_access_list_address_cost()),
1072            "tx_access_list_storage_key_cost" => Some(Self::tx_access_list_storage_key_cost()),
1073            "tx_base_stipend" => Some(Self::tx_base_stipend()),
1074            "tx_create_cost" => Some(Self::tx_create_cost()),
1075            "tx_initcode_cost" => Some(Self::tx_initcode_cost()),
1076            "sstore_set_refund" => Some(Self::sstore_set_refund()),
1077            "sstore_reset_refund" => Some(Self::sstore_reset_refund()),
1078            "tx_eip7702_auth_refund" => Some(Self::tx_eip7702_auth_refund()),
1079            "sstore_set_state_gas" => Some(Self::sstore_set_state_gas()),
1080            "new_account_state_gas" => Some(Self::new_account_state_gas()),
1081            "code_deposit_state_gas" => Some(Self::code_deposit_state_gas()),
1082            "create_state_gas" => Some(Self::create_state_gas()),
1083            "tx_eip7702_per_auth_state_gas" => Some(Self::tx_eip7702_per_auth_state_gas()),
1084            _ => None,
1085        }
1086    }
1087
1088    /// EXP gas cost per byte
1089    pub const fn exp_byte_gas() -> GasId {
1090        Self::new(1)
1091    }
1092
1093    /// EXTCODECOPY gas cost per word
1094    pub const fn extcodecopy_per_word() -> GasId {
1095        Self::new(2)
1096    }
1097
1098    /// Copy copy per word
1099    pub const fn copy_per_word() -> GasId {
1100        Self::new(3)
1101    }
1102
1103    /// Log data gas cost per byte
1104    pub const fn logdata() -> GasId {
1105        Self::new(4)
1106    }
1107
1108    /// Log topic gas cost per topic
1109    pub const fn logtopic() -> GasId {
1110        Self::new(5)
1111    }
1112
1113    /// MCOPY gas cost per word
1114    pub const fn mcopy_per_word() -> GasId {
1115        Self::new(6)
1116    }
1117
1118    /// KECCAK256 gas cost per word
1119    pub const fn keccak256_per_word() -> GasId {
1120        Self::new(7)
1121    }
1122
1123    /// Memory linear cost. Memory is additionally added as n*linear_cost.
1124    pub const fn memory_linear_cost() -> GasId {
1125        Self::new(8)
1126    }
1127
1128    /// Memory quadratic reduction. Memory is additionally added as n*n/quadratic_reduction.
1129    pub const fn memory_quadratic_reduction() -> GasId {
1130        Self::new(9)
1131    }
1132
1133    /// Initcode word cost
1134    pub const fn initcode_per_word() -> GasId {
1135        Self::new(10)
1136    }
1137
1138    /// Create gas cost
1139    pub const fn create() -> GasId {
1140        Self::new(11)
1141    }
1142
1143    /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit.
1144    pub const fn call_stipend_reduction() -> GasId {
1145        Self::new(12)
1146    }
1147
1148    /// Transfer value cost
1149    pub const fn transfer_value_cost() -> GasId {
1150        Self::new(13)
1151    }
1152
1153    /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded.
1154    pub const fn cold_account_additional_cost() -> GasId {
1155        Self::new(14)
1156    }
1157
1158    /// New account cost. New account cost is added to the gas cost if the account is empty.
1159    pub const fn new_account_cost() -> GasId {
1160        Self::new(15)
1161    }
1162
1163    /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded.
1164    ///
1165    /// Used in delegated account access to specify delegated account warm gas cost.
1166    pub const fn warm_storage_read_cost() -> GasId {
1167        Self::new(16)
1168    }
1169
1170    /// Static gas cost for SSTORE opcode. This gas in comparison with other gas const needs
1171    /// to be deducted after check for minimal stipend gas cost. This is a reason why it is here.
1172    pub const fn sstore_static() -> GasId {
1173        Self::new(17)
1174    }
1175
1176    /// SSTORE set cost additional amount after SSTORE_RESET is added.
1177    pub const fn sstore_set_without_load_cost() -> GasId {
1178        Self::new(18)
1179    }
1180
1181    /// SSTORE reset cost
1182    pub const fn sstore_reset_without_cold_load_cost() -> GasId {
1183        Self::new(19)
1184    }
1185
1186    /// SSTORE clearing slot refund
1187    pub const fn sstore_clearing_slot_refund() -> GasId {
1188        Self::new(20)
1189    }
1190
1191    /// Selfdestruct refund.
1192    pub const fn selfdestruct_refund() -> GasId {
1193        Self::new(21)
1194    }
1195
1196    /// Call stipend checked in sstore.
1197    pub const fn call_stipend() -> GasId {
1198        Self::new(22)
1199    }
1200
1201    /// Cold storage additional cost.
1202    pub const fn cold_storage_additional_cost() -> GasId {
1203        Self::new(23)
1204    }
1205
1206    /// Cold storage cost
1207    pub const fn cold_storage_cost() -> GasId {
1208        Self::new(24)
1209    }
1210
1211    /// New account cost for selfdestruct.
1212    pub const fn new_account_cost_for_selfdestruct() -> GasId {
1213        Self::new(25)
1214    }
1215
1216    /// Code deposit cost. Calculated as len * code_deposit_cost.
1217    pub const fn code_deposit_cost() -> GasId {
1218        Self::new(26)
1219    }
1220
1221    /// EIP-7702 PER_EMPTY_ACCOUNT_COST gas
1222    pub const fn tx_eip7702_per_empty_account_cost() -> GasId {
1223        Self::new(27)
1224    }
1225
1226    /// Initial tx gas token non zero byte multiplier.
1227    pub const fn tx_token_non_zero_byte_multiplier() -> GasId {
1228        Self::new(28)
1229    }
1230
1231    /// Initial tx gas token cost.
1232    pub const fn tx_token_cost() -> GasId {
1233        Self::new(29)
1234    }
1235
1236    /// Initial tx gas floor cost per token.
1237    pub const fn tx_floor_cost_per_token() -> GasId {
1238        Self::new(30)
1239    }
1240
1241    /// Initial tx gas floor cost base gas.
1242    pub const fn tx_floor_cost_base_gas() -> GasId {
1243        Self::new(31)
1244    }
1245
1246    /// Initial tx gas access list address cost.
1247    pub const fn tx_access_list_address_cost() -> GasId {
1248        Self::new(32)
1249    }
1250
1251    /// Initial tx gas access list storage key cost.
1252    pub const fn tx_access_list_storage_key_cost() -> GasId {
1253        Self::new(33)
1254    }
1255
1256    /// Initial tx gas base stipend.
1257    pub const fn tx_base_stipend() -> GasId {
1258        Self::new(34)
1259    }
1260
1261    /// Initial tx gas create cost.
1262    pub const fn tx_create_cost() -> GasId {
1263        Self::new(35)
1264    }
1265
1266    /// Initial tx gas initcode cost per word.
1267    pub const fn tx_initcode_cost() -> GasId {
1268        Self::new(36)
1269    }
1270
1271    /// SSTORE set refund. Used in sstore_refund for SSTORE_SET_GAS - SLOAD_GAS refund calculation.
1272    pub const fn sstore_set_refund() -> GasId {
1273        Self::new(37)
1274    }
1275
1276    /// SSTORE reset refund. Used in sstore_refund for SSTORE_RESET_GAS - SLOAD_GAS refund calculation.
1277    pub const fn sstore_reset_refund() -> GasId {
1278        Self::new(38)
1279    }
1280
1281    /// EIP-7702 authorization refund per existing account.
1282    /// This is the refund given when an authorization is applied to an already existing account.
1283    /// Calculated as PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST (25000 - 12500 = 12500).
1284    pub const fn tx_eip7702_auth_refund() -> GasId {
1285        Self::new(39)
1286    }
1287
1288    /// State gas for new storage slot creation (SSTORE zero → non-zero).
1289    pub const fn sstore_set_state_gas() -> GasId {
1290        Self::new(40)
1291    }
1292
1293    /// State gas for new account creation.
1294    pub const fn new_account_state_gas() -> GasId {
1295        Self::new(41)
1296    }
1297
1298    /// State gas per byte for code deposit.
1299    pub const fn code_deposit_state_gas() -> GasId {
1300        Self::new(42)
1301    }
1302
1303    /// State gas for contract metadata creation.
1304    pub const fn create_state_gas() -> GasId {
1305        Self::new(43)
1306    }
1307
1308    /// EIP-8037: State gas per EIP-7702 authorization (pessimistic).
1309    /// Includes both PER_EMPTY_ACCOUNT (112 × cpsb) and PER_AUTH_BASE (23 × cpsb).
1310    /// Zero before AMSTERDAM.
1311    pub const fn tx_eip7702_per_auth_state_gas() -> GasId {
1312        Self::new(44)
1313    }
1314}
1315
1316#[cfg(test)]
1317mod tests {
1318    use super::*;
1319    use std::collections::HashSet;
1320
1321    #[cfg(test)]
1322    mod log2floor_tests {
1323        use super::*;
1324
1325        #[test]
1326        fn test_log2floor_edge_cases() {
1327            // Test zero
1328            assert_eq!(log2floor(U256::ZERO), 0);
1329
1330            // Test powers of 2
1331            assert_eq!(log2floor(U256::from(1u64)), 0); // log2(1) = 0
1332            assert_eq!(log2floor(U256::from(2u64)), 1); // log2(2) = 1
1333            assert_eq!(log2floor(U256::from(4u64)), 2); // log2(4) = 2
1334            assert_eq!(log2floor(U256::from(8u64)), 3); // log2(8) = 3
1335            assert_eq!(log2floor(U256::from(256u64)), 8); // log2(256) = 8
1336
1337            // Test non-powers of 2
1338            assert_eq!(log2floor(U256::from(3u64)), 1); // log2(3) = 1.58... -> floor = 1
1339            assert_eq!(log2floor(U256::from(5u64)), 2); // log2(5) = 2.32... -> floor = 2
1340            assert_eq!(log2floor(U256::from(255u64)), 7); // log2(255) = 7.99... -> floor = 7
1341
1342            // Test large values
1343            assert_eq!(log2floor(U256::from(u64::MAX)), 63);
1344            assert_eq!(log2floor(U256::from(u64::MAX) + U256::from(1u64)), 64);
1345            assert_eq!(log2floor(U256::MAX), 255);
1346        }
1347    }
1348
1349    #[test]
1350    fn test_gas_id_name_and_from_str_coverage() {
1351        let mut unique_names = HashSet::new();
1352        let mut known_gas_ids = 0;
1353
1354        // Iterate over all possible GasId values (0..256)
1355        for i in 0..=255 {
1356            let gas_id = GasId::new(i);
1357            let name = gas_id.name();
1358
1359            // Count unique names (excluding "unknown")
1360            if name != "unknown" {
1361                unique_names.insert(name);
1362            }
1363        }
1364
1365        // Now test from_str for each unique name
1366        for name in &unique_names {
1367            if let Some(gas_id) = GasId::from_name(name) {
1368                known_gas_ids += 1;
1369                // Verify round-trip: name -> GasId -> name should be consistent
1370                assert_eq!(gas_id.name(), *name, "Round-trip failed for {}", name);
1371            }
1372        }
1373
1374        println!("Total unique named GasIds: {}", unique_names.len());
1375        println!("GasIds resolvable via from_str: {}", known_gas_ids);
1376
1377        // All unique names should be resolvable via from_str
1378        assert_eq!(
1379            unique_names.len(),
1380            known_gas_ids,
1381            "Not all unique names are resolvable via from_str"
1382        );
1383
1384        // We should have exactly 44 known GasIds (based on the indices 1-44 used)
1385        assert_eq!(
1386            unique_names.len(),
1387            44,
1388            "Expected 44 unique GasIds, found {}",
1389            unique_names.len()
1390        );
1391    }
1392
1393    #[test]
1394    fn test_tx_access_list_cost() {
1395        use crate::cfg::gas;
1396
1397        // Test with Berlin spec (when access list was introduced)
1398        let gas_params = GasParams::new_spec(SpecId::BERLIN);
1399
1400        // Test with 0 accounts and 0 storages
1401        assert_eq!(gas_params.tx_access_list_cost(0, 0), 0);
1402
1403        // Test with 1 account and 0 storages
1404        assert_eq!(
1405            gas_params.tx_access_list_cost(1, 0),
1406            gas::ACCESS_LIST_ADDRESS
1407        );
1408
1409        // Test with 0 accounts and 1 storage
1410        assert_eq!(
1411            gas_params.tx_access_list_cost(0, 1),
1412            gas::ACCESS_LIST_STORAGE_KEY
1413        );
1414
1415        // Test with 2 accounts and 5 storages
1416        assert_eq!(
1417            gas_params.tx_access_list_cost(2, 5),
1418            2 * gas::ACCESS_LIST_ADDRESS + 5 * gas::ACCESS_LIST_STORAGE_KEY
1419        );
1420
1421        // Test with large numbers to ensure no overflow
1422        assert_eq!(
1423            gas_params.tx_access_list_cost(100, 200),
1424            100 * gas::ACCESS_LIST_ADDRESS + 200 * gas::ACCESS_LIST_STORAGE_KEY
1425        );
1426
1427        // Test with pre-Berlin spec (should return 0)
1428        let gas_params_pre_berlin = GasParams::new_spec(SpecId::ISTANBUL);
1429        assert_eq!(gas_params_pre_berlin.tx_access_list_cost(10, 20), 0);
1430    }
1431
1432    #[test]
1433    fn test_initial_state_gas_for_create() {
1434        // Use AMSTERDAM spec since EIP-8037 state gas is only enabled starting from Amsterdam
1435        let gas_params = GasParams::new_spec(SpecId::AMSTERDAM);
1436
1437        // Test CREATE transaction (is_create = true)
1438        let create_gas = gas_params.initial_tx_gas(b"", true, 0, 0, 0);
1439        let expected_state_gas = gas_params.create_state_gas();
1440
1441        assert_eq!(create_gas.initial_state_gas, expected_state_gas);
1442        assert_eq!(create_gas.initial_state_gas, 131488);
1443
1444        // initial_total_gas includes both regular and state gas
1445        let create_cost = gas_params.tx_create_cost();
1446        let initcode_cost = gas_params.tx_initcode_cost(0);
1447        assert_eq!(
1448            create_gas.initial_total_gas,
1449            gas_params.tx_base_stipend() + create_cost + initcode_cost + expected_state_gas
1450        );
1451
1452        // Test CALL transaction (is_create = false)
1453        let call_gas = gas_params.initial_tx_gas(b"", false, 0, 0, 0);
1454        assert_eq!(call_gas.initial_state_gas, 0);
1455        // initial_gas should be unchanged for calls
1456        assert_eq!(call_gas.initial_total_gas, gas_params.tx_base_stipend());
1457    }
1458}