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()).clone()
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 CLEARING SLOT REFUND
224        table[GasId::sstore_clearing_slot_refund().as_usize()] = 15000;
225        table[GasId::selfdestruct_refund().as_usize()] = 24000;
226        table[GasId::call_stipend().as_usize()] = gas::CALL_STIPEND;
227        table[GasId::cold_storage_additional_cost().as_usize()] = 0;
228        table[GasId::cold_storage_cost().as_usize()] = 0;
229        table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0;
230        table[GasId::code_deposit_cost().as_usize()] = gas::CODEDEPOSIT;
231        table[GasId::tx_token_non_zero_byte_multiplier().as_usize()] =
232            gas::NON_ZERO_BYTE_MULTIPLIER;
233        table[GasId::tx_token_cost().as_usize()] = gas::STANDARD_TOKEN_COST;
234        table[GasId::tx_base_stipend().as_usize()] = 21000;
235
236        if spec.is_enabled_in(SpecId::HOMESTEAD) {
237            table[GasId::tx_create_cost().as_usize()] = gas::CREATE;
238        }
239
240        if spec.is_enabled_in(SpecId::TANGERINE) {
241            table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT;
242        }
243
244        if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
245            table[GasId::exp_byte_gas().as_usize()] = 50;
246        }
247
248        if spec.is_enabled_in(SpecId::ISTANBUL) {
249            table[GasId::sstore_static().as_usize()] = gas::ISTANBUL_SLOAD_GAS;
250            table[GasId::sstore_set_without_load_cost().as_usize()] =
251                gas::SSTORE_SET - gas::ISTANBUL_SLOAD_GAS;
252            table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
253                gas::SSTORE_RESET - gas::ISTANBUL_SLOAD_GAS;
254            table[GasId::tx_token_non_zero_byte_multiplier().as_usize()] =
255                gas::NON_ZERO_BYTE_MULTIPLIER_ISTANBUL;
256        }
257
258        if spec.is_enabled_in(SpecId::BERLIN) {
259            table[GasId::sstore_static().as_usize()] = gas::WARM_STORAGE_READ_COST;
260            table[GasId::cold_account_additional_cost().as_usize()] =
261                gas::COLD_ACCOUNT_ACCESS_COST_ADDITIONAL;
262            table[GasId::cold_storage_additional_cost().as_usize()] =
263                gas::COLD_SLOAD_COST - gas::WARM_STORAGE_READ_COST;
264            table[GasId::cold_storage_cost().as_usize()] = gas::COLD_SLOAD_COST;
265            table[GasId::warm_storage_read_cost().as_usize()] = gas::WARM_STORAGE_READ_COST;
266
267            table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
268                gas::WARM_SSTORE_RESET - gas::WARM_STORAGE_READ_COST;
269            table[GasId::sstore_set_without_load_cost().as_usize()] =
270                gas::SSTORE_SET - gas::WARM_STORAGE_READ_COST;
271
272            table[GasId::tx_access_list_address_cost().as_usize()] = gas::ACCESS_LIST_ADDRESS;
273            table[GasId::tx_access_list_storage_key_cost().as_usize()] =
274                gas::ACCESS_LIST_STORAGE_KEY;
275        }
276
277        if spec.is_enabled_in(SpecId::LONDON) {
278            // EIP-3529: Reduction in refunds
279
280            // Replace SSTORE_CLEARS_SCHEDULE (as defined in EIP-2200) with
281            // SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST (4,800 gas as of EIP-2929 + EIP-2930)
282            table[GasId::sstore_clearing_slot_refund().as_usize()] =
283                gas::WARM_SSTORE_RESET + gas::ACCESS_LIST_STORAGE_KEY;
284
285            table[GasId::selfdestruct_refund().as_usize()] = 0;
286        }
287
288        if spec.is_enabled_in(SpecId::SHANGHAI) {
289            table[GasId::tx_initcode_cost().as_usize()] = gas::INITCODE_WORD_COST;
290        }
291
292        if spec.is_enabled_in(SpecId::PRAGUE) {
293            table[GasId::tx_eip7702_per_empty_account_cost().as_usize()] =
294                eip7702::PER_EMPTY_ACCOUNT_COST;
295
296            table[GasId::tx_floor_cost_per_token().as_usize()] = gas::TOTAL_COST_FLOOR_PER_TOKEN;
297            table[GasId::tx_floor_cost_base_gas().as_usize()] = 21000;
298        }
299
300        Self::new(Arc::new(table))
301    }
302
303    /// Gets the gas cost for the given gas id.
304    #[inline]
305    pub const fn get(&self, id: GasId) -> u64 {
306        unsafe { *self.ptr.add(id.as_usize()) }
307    }
308
309    /// `EXP` opcode cost calculation.
310    #[inline]
311    pub fn exp_cost(&self, power: U256) -> u64 {
312        if power.is_zero() {
313            return 0;
314        }
315        // EIP-160: EXP cost increase
316        self.get(GasId::exp_byte_gas())
317            .saturating_mul(log2floor(power) / 8 + 1)
318    }
319
320    /// Selfdestruct refund.
321    #[inline]
322    pub fn selfdestruct_refund(&self) -> i64 {
323        self.get(GasId::selfdestruct_refund()) as i64
324    }
325
326    /// Selfdestruct cold cost is calculated differently from other cold costs.
327    /// and it contains both cold and warm costs.
328    #[inline]
329    pub fn selfdestruct_cold_cost(&self) -> u64 {
330        self.cold_account_additional_cost() + self.warm_storage_read_cost()
331    }
332
333    /// Selfdestruct cost.
334    #[inline]
335    pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 {
336        let mut gas = 0;
337
338        // EIP-150: Gas cost changes for IO-heavy operations
339        if should_charge_topup {
340            gas += self.new_account_cost_for_selfdestruct();
341        }
342
343        if is_cold {
344            // Note: SELFDESTRUCT does not charge a WARM_STORAGE_READ_COST in case the recipient is already warm,
345            // which differs from how the other call-variants work. The reasoning behind this is to keep
346            // the changes small, a SELFDESTRUCT already costs 5K and is a no-op if invoked more than once.
347            //
348            // For GasParams both values are zero before BERLIN fork.
349            gas += self.selfdestruct_cold_cost();
350        }
351        gas
352    }
353
354    /// EXTCODECOPY gas cost
355    #[inline]
356    pub fn extcodecopy(&self, len: usize) -> u64 {
357        self.get(GasId::extcodecopy_per_word())
358            .saturating_mul(num_words(len) as u64)
359    }
360
361    /// MCOPY gas cost
362    #[inline]
363    pub fn mcopy_cost(&self, len: usize) -> u64 {
364        self.get(GasId::mcopy_per_word())
365            .saturating_mul(num_words(len) as u64)
366    }
367
368    /// Static gas cost for SSTORE opcode
369    #[inline]
370    pub fn sstore_static_gas(&self) -> u64 {
371        self.get(GasId::sstore_static())
372    }
373
374    /// SSTORE set cost
375    #[inline]
376    pub fn sstore_set_without_load_cost(&self) -> u64 {
377        self.get(GasId::sstore_set_without_load_cost())
378    }
379
380    /// SSTORE reset cost
381    #[inline]
382    pub fn sstore_reset_without_cold_load_cost(&self) -> u64 {
383        self.get(GasId::sstore_reset_without_cold_load_cost())
384    }
385
386    /// SSTORE clearing slot refund
387    #[inline]
388    pub fn sstore_clearing_slot_refund(&self) -> u64 {
389        self.get(GasId::sstore_clearing_slot_refund())
390    }
391
392    /// Dynamic gas cost for SSTORE opcode.
393    ///
394    /// Dynamic gas cost is gas that needs input from SSTORE operation to be calculated.
395    #[inline]
396    pub fn sstore_dynamic_gas(&self, is_istanbul: bool, vals: &SStoreResult, is_cold: bool) -> u64 {
397        // frontier logic gets charged for every SSTORE operation if original value is zero.
398        // this behaviour is fixed in istanbul fork.
399        if !is_istanbul {
400            if vals.is_present_zero() && !vals.is_new_zero() {
401                return self.sstore_set_without_load_cost();
402            } else {
403                return self.sstore_reset_without_cold_load_cost();
404            }
405        }
406
407        let mut gas = 0;
408
409        // this will be zero before berlin fork.
410        if is_cold {
411            gas += self.cold_storage_cost();
412        }
413
414        // if new values changed present value and present value is unchanged from original.
415        if vals.new_values_changes_present() && vals.is_original_eq_present() {
416            gas += if vals.is_original_zero() {
417                // set cost for creating storage slot (Zero slot means it is not existing).
418                // and previous condition says present is same as original.
419                self.sstore_set_without_load_cost()
420            } else {
421                // if new value is not zero, this means we are setting some value to it.
422                self.sstore_reset_without_cold_load_cost()
423            };
424        }
425        gas
426    }
427
428    /// SSTORE refund calculation.
429    #[inline]
430    pub fn sstore_refund(&self, is_istanbul: bool, vals: &SStoreResult) -> i64 {
431        // EIP-3529: Reduction in refunds
432        let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64;
433
434        if !is_istanbul {
435            // // before istanbul fork, refund was always awarded without checking original state.
436            if !vals.is_present_zero() && vals.is_new_zero() {
437                return sstore_clearing_slot_refund;
438            }
439            return 0;
440        }
441
442        // If current value equals new value (this is a no-op)
443        if vals.is_new_eq_present() {
444            return 0;
445        }
446
447        // refund for the clearing of storage slot.
448        // As new is not equal to present, new values zero means that original and present values are not zero
449        if vals.is_original_eq_present() && vals.is_new_zero() {
450            return sstore_clearing_slot_refund;
451        }
452
453        let mut refund = 0;
454        // If original value is not 0
455        if !vals.is_original_zero() {
456            // If current value is 0 (also means that new value is not 0),
457            if vals.is_present_zero() {
458                // remove SSTORE_CLEARS_SCHEDULE gas from refund counter.
459                refund -= sstore_clearing_slot_refund;
460            // If new value is 0 (also means that current value is not 0),
461            } else if vals.is_new_zero() {
462                // add SSTORE_CLEARS_SCHEDULE gas to refund counter.
463                refund += sstore_clearing_slot_refund;
464            }
465        }
466
467        // If original value equals new value (this storage slot is reset)
468        if vals.is_original_eq_new() {
469            // If original value is 0
470            if vals.is_original_zero() {
471                // add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
472                refund += self.sstore_set_without_load_cost() as i64;
473            // Otherwise
474            } else {
475                // add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
476                refund += self.sstore_reset_without_cold_load_cost() as i64;
477            }
478        }
479        refund
480    }
481
482    /// `LOG` opcode cost calculation.
483    #[inline]
484    pub const fn log_cost(&self, n: u8, len: u64) -> u64 {
485        self.get(GasId::logdata())
486            .saturating_mul(len)
487            .saturating_add(self.get(GasId::logtopic()) * n as u64)
488    }
489
490    /// KECCAK256 gas cost per word
491    #[inline]
492    pub fn keccak256_cost(&self, len: usize) -> u64 {
493        self.get(GasId::keccak256_per_word())
494            .saturating_mul(num_words(len) as u64)
495    }
496
497    /// Memory gas cost
498    #[inline]
499    pub fn memory_cost(&self, len: usize) -> u64 {
500        let len = len as u64;
501        self.get(GasId::memory_linear_cost())
502            .saturating_mul(len)
503            .saturating_add(
504                (len.saturating_mul(len))
505                    .saturating_div(self.get(GasId::memory_quadratic_reduction())),
506            )
507    }
508
509    /// Initcode word cost
510    #[inline]
511    pub fn initcode_cost(&self, len: usize) -> u64 {
512        self.get(GasId::initcode_per_word())
513            .saturating_mul(num_words(len) as u64)
514    }
515
516    /// Create gas cost
517    #[inline]
518    pub fn create_cost(&self) -> u64 {
519        self.get(GasId::create())
520    }
521
522    /// Create2 gas cost.
523    #[inline]
524    pub fn create2_cost(&self, len: usize) -> u64 {
525        self.get(GasId::create()).saturating_add(
526            self.get(GasId::keccak256_per_word())
527                .saturating_mul(num_words(len) as u64),
528        )
529    }
530
531    /// Call stipend.
532    #[inline]
533    pub fn call_stipend(&self) -> u64 {
534        self.get(GasId::call_stipend())
535    }
536
537    /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit.
538    #[inline]
539    pub fn call_stipend_reduction(&self, gas_limit: u64) -> u64 {
540        gas_limit - gas_limit / self.get(GasId::call_stipend_reduction())
541    }
542
543    /// Transfer value cost
544    #[inline]
545    pub fn transfer_value_cost(&self) -> u64 {
546        self.get(GasId::transfer_value_cost())
547    }
548
549    /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded.
550    #[inline]
551    pub fn cold_account_additional_cost(&self) -> u64 {
552        self.get(GasId::cold_account_additional_cost())
553    }
554
555    /// Cold storage additional cost.
556    #[inline]
557    pub fn cold_storage_additional_cost(&self) -> u64 {
558        self.get(GasId::cold_storage_additional_cost())
559    }
560
561    /// Cold storage cost.
562    #[inline]
563    pub fn cold_storage_cost(&self) -> u64 {
564        self.get(GasId::cold_storage_cost())
565    }
566
567    /// New account cost. New account cost is added to the gas cost if the account is empty.
568    #[inline]
569    pub fn new_account_cost(&self, is_spurious_dragon: bool, transfers_value: bool) -> u64 {
570        // EIP-161: State trie clearing (invariant-preserving alternative)
571        // Pre-Spurious Dragon: always charge for new account
572        // Post-Spurious Dragon: only charge if value is transferred
573        if !is_spurious_dragon || transfers_value {
574            return self.get(GasId::new_account_cost());
575        }
576        0
577    }
578
579    /// New account cost for selfdestruct.
580    #[inline]
581    pub fn new_account_cost_for_selfdestruct(&self) -> u64 {
582        self.get(GasId::new_account_cost_for_selfdestruct())
583    }
584
585    /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded.
586    #[inline]
587    pub fn warm_storage_read_cost(&self) -> u64 {
588        self.get(GasId::warm_storage_read_cost())
589    }
590
591    /// Copy cost
592    #[inline]
593    pub fn copy_cost(&self, len: usize) -> u64 {
594        self.copy_per_word_cost(num_words(len))
595    }
596
597    /// Copy per word cost
598    #[inline]
599    pub fn copy_per_word_cost(&self, word_num: usize) -> u64 {
600        self.get(GasId::copy_per_word())
601            .saturating_mul(word_num as u64)
602    }
603
604    /// Code deposit cost, calculated per byte as len * code_deposit_cost.
605    #[inline]
606    pub fn code_deposit_cost(&self, len: usize) -> u64 {
607        self.get(GasId::code_deposit_cost())
608            .saturating_mul(len as u64)
609    }
610
611    /// Used in [GasParams::initial_tx_gas] to calculate the eip7702 per empty account cost.
612    #[inline]
613    pub fn tx_eip7702_per_empty_account_cost(&self) -> u64 {
614        self.get(GasId::tx_eip7702_per_empty_account_cost())
615    }
616
617    /// Used in [GasParams::initial_tx_gas] to calculate the token non zero byte multiplier.
618    #[inline]
619    pub fn tx_token_non_zero_byte_multiplier(&self) -> u64 {
620        self.get(GasId::tx_token_non_zero_byte_multiplier())
621    }
622
623    /// Used in [GasParams::initial_tx_gas] to calculate the token cost for input data.
624    #[inline]
625    pub fn tx_token_cost(&self) -> u64 {
626        self.get(GasId::tx_token_cost())
627    }
628
629    /// Used in [GasParams::initial_tx_gas] to calculate the floor gas per token.
630    pub fn tx_floor_cost_per_token(&self) -> u64 {
631        self.get(GasId::tx_floor_cost_per_token())
632    }
633
634    /// Used [GasParams::initial_tx_gas] to calculate the floor gas.
635    ///
636    /// Floor gas is introduced in EIP-7623.
637    #[inline]
638    pub fn tx_floor_cost(&self, tokens_in_calldata: u64) -> u64 {
639        self.tx_floor_cost_per_token() * tokens_in_calldata + self.tx_floor_cost_base_gas()
640    }
641
642    /// Used in [GasParams::initial_tx_gas] to calculate the floor gas base gas.
643    pub fn tx_floor_cost_base_gas(&self) -> u64 {
644        self.get(GasId::tx_floor_cost_base_gas())
645    }
646
647    /// Used in [GasParams::initial_tx_gas] to calculate the access list address cost.
648    pub fn tx_access_list_address_cost(&self) -> u64 {
649        self.get(GasId::tx_access_list_address_cost())
650    }
651
652    /// Used in [GasParams::initial_tx_gas] to calculate the access list storage key cost.
653    pub fn tx_access_list_storage_key_cost(&self) -> u64 {
654        self.get(GasId::tx_access_list_storage_key_cost())
655    }
656
657    /// Used in [GasParams::initial_tx_gas] to calculate the base transaction stipend.
658    pub fn tx_base_stipend(&self) -> u64 {
659        self.get(GasId::tx_base_stipend())
660    }
661
662    /// Used in [GasParams::initial_tx_gas] to calculate the create cost.
663    ///
664    /// Similar to the [`Self::create_cost`] method but it got activated in different fork,
665    #[inline]
666    pub fn tx_create_cost(&self) -> u64 {
667        self.get(GasId::tx_create_cost())
668    }
669
670    /// Used in [GasParams::initial_tx_gas] to calculate the initcode cost per word of len.
671    #[inline]
672    pub fn tx_initcode_cost(&self, len: usize) -> u64 {
673        self.get(GasId::tx_initcode_cost())
674            .saturating_mul(num_words(len) as u64)
675    }
676
677    /// Initial gas that is deducted for transaction to be included.
678    /// Initial gas contains initial stipend gas, gas for access list and input data.
679    ///
680    /// # Returns
681    ///
682    /// - Intrinsic gas
683    /// - Number of tokens in calldata
684    pub fn initial_tx_gas(
685        &self,
686        input: &[u8],
687        is_create: bool,
688        access_list_accounts: u64,
689        access_list_storages: u64,
690        authorization_list_num: u64,
691    ) -> InitialAndFloorGas {
692        let mut gas = InitialAndFloorGas::default();
693
694        // Initdate stipend
695        let tokens_in_calldata =
696            get_tokens_in_calldata(input, self.tx_token_non_zero_byte_multiplier());
697
698        gas.initial_gas += tokens_in_calldata * self.tx_token_cost()
699            // before berlin tx_access_list_address_cost will be zero
700            + access_list_accounts * self.tx_access_list_address_cost()
701            // before berlin tx_access_list_storage_key_cost will be zero
702            + access_list_storages * self.tx_access_list_storage_key_cost()
703            + self.tx_base_stipend()
704            // EIP-7702: Authorization list
705            + authorization_list_num * self.tx_eip7702_per_empty_account_cost();
706
707        if is_create {
708            // EIP-2: Homestead Hard-fork Changes
709            gas.initial_gas += self.tx_create_cost();
710
711            // EIP-3860: Limit and meter initcode
712            gas.initial_gas += self.tx_initcode_cost(input.len());
713        }
714
715        // Calculate gas floor for EIP-7623
716        gas.floor_gas = self.tx_floor_cost(tokens_in_calldata);
717
718        gas
719    }
720}
721
722#[inline]
723pub(crate) const fn log2floor(value: U256) -> u64 {
724    let mut l: u64 = 256;
725    let mut i = 3;
726    loop {
727        if value.as_limbs()[i] == 0u64 {
728            l -= 64;
729        } else {
730            l -= value.as_limbs()[i].leading_zeros() as u64;
731            if l == 0 {
732                return l;
733            } else {
734                return l - 1;
735            }
736        }
737        if i == 0 {
738            break;
739        }
740        i -= 1;
741    }
742    l
743}
744
745/// Gas identifier that maps onto index in gas table.
746#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
747pub struct GasId(u8);
748
749impl GasId {
750    /// Creates a new `GasId` with the given id.
751    pub const fn new(id: u8) -> Self {
752        Self(id)
753    }
754
755    /// Returns the id of the gas.
756    pub const fn as_u8(&self) -> u8 {
757        self.0
758    }
759
760    /// Returns the id of the gas as a usize.
761    pub const fn as_usize(&self) -> usize {
762        self.0 as usize
763    }
764
765    /// Returns the name of the gas identifier as a string.
766    ///
767    /// # Examples
768    ///
769    /// ```
770    /// use revm_context_interface::cfg::gas_params::GasId;
771    ///
772    /// assert_eq!(GasId::exp_byte_gas().name(), "exp_byte_gas");
773    /// assert_eq!(GasId::memory_linear_cost().name(), "memory_linear_cost");
774    /// assert_eq!(GasId::sstore_static().name(), "sstore_static");
775    /// ```
776    pub const fn name(&self) -> &'static str {
777        match self.0 {
778            x if x == Self::exp_byte_gas().as_u8() => "exp_byte_gas",
779            x if x == Self::extcodecopy_per_word().as_u8() => "extcodecopy_per_word",
780            x if x == Self::copy_per_word().as_u8() => "copy_per_word",
781            x if x == Self::logdata().as_u8() => "logdata",
782            x if x == Self::logtopic().as_u8() => "logtopic",
783            x if x == Self::mcopy_per_word().as_u8() => "mcopy_per_word",
784            x if x == Self::keccak256_per_word().as_u8() => "keccak256_per_word",
785            x if x == Self::memory_linear_cost().as_u8() => "memory_linear_cost",
786            x if x == Self::memory_quadratic_reduction().as_u8() => "memory_quadratic_reduction",
787            x if x == Self::initcode_per_word().as_u8() => "initcode_per_word",
788            x if x == Self::create().as_u8() => "create",
789            x if x == Self::call_stipend_reduction().as_u8() => "call_stipend_reduction",
790            x if x == Self::transfer_value_cost().as_u8() => "transfer_value_cost",
791            x if x == Self::cold_account_additional_cost().as_u8() => {
792                "cold_account_additional_cost"
793            }
794            x if x == Self::new_account_cost().as_u8() => "new_account_cost",
795            x if x == Self::warm_storage_read_cost().as_u8() => "warm_storage_read_cost",
796            x if x == Self::sstore_static().as_u8() => "sstore_static",
797            x if x == Self::sstore_set_without_load_cost().as_u8() => {
798                "sstore_set_without_load_cost"
799            }
800            x if x == Self::sstore_reset_without_cold_load_cost().as_u8() => {
801                "sstore_reset_without_cold_load_cost"
802            }
803            x if x == Self::sstore_clearing_slot_refund().as_u8() => "sstore_clearing_slot_refund",
804            x if x == Self::selfdestruct_refund().as_u8() => "selfdestruct_refund",
805            x if x == Self::call_stipend().as_u8() => "call_stipend",
806            x if x == Self::cold_storage_additional_cost().as_u8() => {
807                "cold_storage_additional_cost"
808            }
809            x if x == Self::cold_storage_cost().as_u8() => "cold_storage_cost",
810            x if x == Self::new_account_cost_for_selfdestruct().as_u8() => {
811                "new_account_cost_for_selfdestruct"
812            }
813            x if x == Self::code_deposit_cost().as_u8() => "code_deposit_cost",
814            x if x == Self::tx_eip7702_per_empty_account_cost().as_u8() => {
815                "tx_eip7702_per_empty_account_cost"
816            }
817            x if x == Self::tx_token_non_zero_byte_multiplier().as_u8() => {
818                "tx_token_non_zero_byte_multiplier"
819            }
820            x if x == Self::tx_token_cost().as_u8() => "tx_token_cost",
821            x if x == Self::tx_floor_cost_per_token().as_u8() => "tx_floor_cost_per_token",
822            x if x == Self::tx_floor_cost_base_gas().as_u8() => "tx_floor_cost_base_gas",
823            x if x == Self::tx_access_list_address_cost().as_u8() => "tx_access_list_address_cost",
824            x if x == Self::tx_access_list_storage_key_cost().as_u8() => {
825                "tx_access_list_storage_key_cost"
826            }
827            x if x == Self::tx_base_stipend().as_u8() => "tx_base_stipend",
828            x if x == Self::tx_create_cost().as_u8() => "tx_create_cost",
829            x if x == Self::tx_initcode_cost().as_u8() => "tx_initcode_cost",
830            _ => "unknown",
831        }
832    }
833
834    /// Converts a string to a `GasId`.
835    ///
836    /// Returns `None` if the string does not match any known gas identifier.
837    ///
838    /// # Examples
839    ///
840    /// ```
841    /// use revm_context_interface::cfg::gas_params::GasId;
842    ///
843    /// assert_eq!(GasId::from_name("exp_byte_gas"), Some(GasId::exp_byte_gas()));
844    /// assert_eq!(GasId::from_name("memory_linear_cost"), Some(GasId::memory_linear_cost()));
845    /// assert_eq!(GasId::from_name("invalid_name"), None);
846    /// ```
847    pub fn from_name(s: &str) -> Option<GasId> {
848        match s {
849            "exp_byte_gas" => Some(Self::exp_byte_gas()),
850            "extcodecopy_per_word" => Some(Self::extcodecopy_per_word()),
851            "copy_per_word" => Some(Self::copy_per_word()),
852            "logdata" => Some(Self::logdata()),
853            "logtopic" => Some(Self::logtopic()),
854            "mcopy_per_word" => Some(Self::mcopy_per_word()),
855            "keccak256_per_word" => Some(Self::keccak256_per_word()),
856            "memory_linear_cost" => Some(Self::memory_linear_cost()),
857            "memory_quadratic_reduction" => Some(Self::memory_quadratic_reduction()),
858            "initcode_per_word" => Some(Self::initcode_per_word()),
859            "create" => Some(Self::create()),
860            "call_stipend_reduction" => Some(Self::call_stipend_reduction()),
861            "transfer_value_cost" => Some(Self::transfer_value_cost()),
862            "cold_account_additional_cost" => Some(Self::cold_account_additional_cost()),
863            "new_account_cost" => Some(Self::new_account_cost()),
864            "warm_storage_read_cost" => Some(Self::warm_storage_read_cost()),
865            "sstore_static" => Some(Self::sstore_static()),
866            "sstore_set_without_load_cost" => Some(Self::sstore_set_without_load_cost()),
867            "sstore_reset_without_cold_load_cost" => {
868                Some(Self::sstore_reset_without_cold_load_cost())
869            }
870            "sstore_clearing_slot_refund" => Some(Self::sstore_clearing_slot_refund()),
871            "selfdestruct_refund" => Some(Self::selfdestruct_refund()),
872            "call_stipend" => Some(Self::call_stipend()),
873            "cold_storage_additional_cost" => Some(Self::cold_storage_additional_cost()),
874            "cold_storage_cost" => Some(Self::cold_storage_cost()),
875            "new_account_cost_for_selfdestruct" => Some(Self::new_account_cost_for_selfdestruct()),
876            "code_deposit_cost" => Some(Self::code_deposit_cost()),
877            "tx_eip7702_per_empty_account_cost" => Some(Self::tx_eip7702_per_empty_account_cost()),
878            "tx_token_non_zero_byte_multiplier" => Some(Self::tx_token_non_zero_byte_multiplier()),
879            "tx_token_cost" => Some(Self::tx_token_cost()),
880            "tx_floor_cost_per_token" => Some(Self::tx_floor_cost_per_token()),
881            "tx_floor_cost_base_gas" => Some(Self::tx_floor_cost_base_gas()),
882            "tx_access_list_address_cost" => Some(Self::tx_access_list_address_cost()),
883            "tx_access_list_storage_key_cost" => Some(Self::tx_access_list_storage_key_cost()),
884            "tx_base_stipend" => Some(Self::tx_base_stipend()),
885            "tx_create_cost" => Some(Self::tx_create_cost()),
886            "tx_initcode_cost" => Some(Self::tx_initcode_cost()),
887            _ => None,
888        }
889    }
890
891    /// EXP gas cost per byte
892    pub const fn exp_byte_gas() -> GasId {
893        Self::new(1)
894    }
895
896    /// EXTCODECOPY gas cost per word
897    pub const fn extcodecopy_per_word() -> GasId {
898        Self::new(2)
899    }
900
901    /// Copy copy per word
902    pub const fn copy_per_word() -> GasId {
903        Self::new(3)
904    }
905
906    /// Log data gas cost per byte
907    pub const fn logdata() -> GasId {
908        Self::new(4)
909    }
910
911    /// Log topic gas cost per topic
912    pub const fn logtopic() -> GasId {
913        Self::new(5)
914    }
915
916    /// MCOPY gas cost per word
917    pub const fn mcopy_per_word() -> GasId {
918        Self::new(6)
919    }
920
921    /// KECCAK256 gas cost per word
922    pub const fn keccak256_per_word() -> GasId {
923        Self::new(7)
924    }
925
926    /// Memory linear cost. Memory is additionally added as n*linear_cost.
927    pub const fn memory_linear_cost() -> GasId {
928        Self::new(8)
929    }
930
931    /// Memory quadratic reduction. Memory is additionally added as n*n/quadratic_reduction.
932    pub const fn memory_quadratic_reduction() -> GasId {
933        Self::new(9)
934    }
935
936    /// Initcode word cost
937    pub const fn initcode_per_word() -> GasId {
938        Self::new(10)
939    }
940
941    /// Create gas cost
942    pub const fn create() -> GasId {
943        Self::new(11)
944    }
945
946    /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit.
947    pub const fn call_stipend_reduction() -> GasId {
948        Self::new(12)
949    }
950
951    /// Transfer value cost
952    pub const fn transfer_value_cost() -> GasId {
953        Self::new(13)
954    }
955
956    /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded.
957    pub const fn cold_account_additional_cost() -> GasId {
958        Self::new(14)
959    }
960
961    /// New account cost. New account cost is added to the gas cost if the account is empty.
962    pub const fn new_account_cost() -> GasId {
963        Self::new(15)
964    }
965
966    /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded.
967    ///
968    /// Used in delegated account access to specify delegated account warm gas cost.
969    pub const fn warm_storage_read_cost() -> GasId {
970        Self::new(16)
971    }
972
973    /// Static gas cost for SSTORE opcode. This gas in comparison with other gas const needs
974    /// to be deducted after check for minimal stipend gas cost. This is a reason why it is here.
975    pub const fn sstore_static() -> GasId {
976        Self::new(17)
977    }
978
979    /// SSTORE set cost additional amount after SSTORE_RESET is added.
980    pub const fn sstore_set_without_load_cost() -> GasId {
981        Self::new(18)
982    }
983
984    /// SSTORE reset cost
985    pub const fn sstore_reset_without_cold_load_cost() -> GasId {
986        Self::new(19)
987    }
988
989    /// SSTORE clearing slot refund
990    pub const fn sstore_clearing_slot_refund() -> GasId {
991        Self::new(20)
992    }
993
994    /// Selfdestruct refund.
995    pub const fn selfdestruct_refund() -> GasId {
996        Self::new(21)
997    }
998
999    /// Call stipend checked in sstore.
1000    pub const fn call_stipend() -> GasId {
1001        Self::new(22)
1002    }
1003
1004    /// Cold storage additional cost.
1005    pub const fn cold_storage_additional_cost() -> GasId {
1006        Self::new(23)
1007    }
1008
1009    /// Cold storage cost
1010    pub const fn cold_storage_cost() -> GasId {
1011        Self::new(24)
1012    }
1013
1014    /// New account cost for selfdestruct.
1015    pub const fn new_account_cost_for_selfdestruct() -> GasId {
1016        Self::new(25)
1017    }
1018
1019    /// Code deposit cost. Calculated as len * code_deposit_cost.
1020    pub const fn code_deposit_cost() -> GasId {
1021        Self::new(26)
1022    }
1023
1024    /// EIP-7702 PER_EMPTY_ACCOUNT_COST gas
1025    pub const fn tx_eip7702_per_empty_account_cost() -> GasId {
1026        Self::new(27)
1027    }
1028
1029    /// Initial tx gas token non zero byte multiplier.
1030    pub const fn tx_token_non_zero_byte_multiplier() -> GasId {
1031        Self::new(28)
1032    }
1033
1034    /// Initial tx gas token cost.
1035    pub const fn tx_token_cost() -> GasId {
1036        Self::new(29)
1037    }
1038
1039    /// Initial tx gas floor cost per token.
1040    pub const fn tx_floor_cost_per_token() -> GasId {
1041        Self::new(30)
1042    }
1043
1044    /// Initial tx gas floor cost base gas.
1045    pub const fn tx_floor_cost_base_gas() -> GasId {
1046        Self::new(31)
1047    }
1048
1049    /// Initial tx gas access list address cost.
1050    pub const fn tx_access_list_address_cost() -> GasId {
1051        Self::new(32)
1052    }
1053
1054    /// Initial tx gas access list storage key cost.
1055    pub const fn tx_access_list_storage_key_cost() -> GasId {
1056        Self::new(33)
1057    }
1058
1059    /// Initial tx gas base stipend.
1060    pub const fn tx_base_stipend() -> GasId {
1061        Self::new(34)
1062    }
1063
1064    /// Initial tx gas create cost.
1065    pub const fn tx_create_cost() -> GasId {
1066        Self::new(35)
1067    }
1068
1069    /// Initial tx gas initcode cost per word.
1070    pub const fn tx_initcode_cost() -> GasId {
1071        Self::new(36)
1072    }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077    use super::*;
1078    use std::collections::HashSet;
1079
1080    #[test]
1081    fn test_gas_id_name_and_from_str_coverage() {
1082        let mut unique_names = HashSet::new();
1083        let mut known_gas_ids = 0;
1084
1085        // Iterate over all possible GasId values (0..256)
1086        for i in 0..=255 {
1087            let gas_id = GasId::new(i);
1088            let name = gas_id.name();
1089
1090            // Count unique names (excluding "unknown")
1091            if name != "unknown" {
1092                unique_names.insert(name);
1093            }
1094        }
1095
1096        // Now test from_str for each unique name
1097        for name in &unique_names {
1098            if let Some(gas_id) = GasId::from_name(name) {
1099                known_gas_ids += 1;
1100                // Verify round-trip: name -> GasId -> name should be consistent
1101                assert_eq!(gas_id.name(), *name, "Round-trip failed for {}", name);
1102            }
1103        }
1104
1105        println!("Total unique named GasIds: {}", unique_names.len());
1106        println!("GasIds resolvable via from_str: {}", known_gas_ids);
1107
1108        // All unique names should be resolvable via from_str
1109        assert_eq!(
1110            unique_names.len(),
1111            known_gas_ids,
1112            "Not all unique names are resolvable via from_str"
1113        );
1114
1115        // We should have exactly 36 known GasIds (based on the indices 1-36 used)
1116        assert_eq!(
1117            unique_names.len(),
1118            36,
1119            "Expected 36 unique GasIds, found {}",
1120            unique_names.len()
1121        );
1122    }
1123}