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