Skip to main content

revm_context_interface/cfg/
gas_params.rs

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