Skip to main content

revm_context_interface/cfg/
gas_params.rs

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