Skip to main content

revm_context_interface/cfg/
gas_params.rs

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