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