revm_interpreter/gas/
params.rs

1//! Gas table for dynamic gas constants.
2
3use crate::{
4    gas::{self, log2floor, ISTANBUL_SLOAD_GAS, SSTORE_RESET, SSTORE_SET, WARM_SSTORE_RESET},
5    num_words,
6};
7use context_interface::context::SStoreResult;
8use primitives::{
9    hardfork::SpecId::{self},
10    U256,
11};
12use std::sync::Arc;
13
14/// Gas table for dynamic gas constants.
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
16pub struct GasParams {
17    /// Table of gas costs for operations
18    table: Arc<[u64; 256]>,
19    /// Pointer to the table.
20    ptr: *const u64,
21}
22
23#[cfg(feature = "serde")]
24mod serde {
25    use super::{Arc, GasParams};
26    use std::vec::Vec;
27
28    #[derive(serde::Serialize, serde::Deserialize)]
29    struct GasParamsSerde {
30        table: Vec<u64>,
31    }
32
33    #[cfg(feature = "serde")]
34    impl serde::Serialize for GasParams {
35        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
36        where
37            S: serde::Serializer,
38        {
39            GasParamsSerde {
40                table: self.table.to_vec(),
41            }
42            .serialize(serializer)
43        }
44    }
45
46    impl<'de> serde::Deserialize<'de> for GasParams {
47        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
48        where
49            D: serde::Deserializer<'de>,
50        {
51            let table = GasParamsSerde::deserialize(deserializer)?;
52            if table.table.len() != 256 {
53                return Err(serde::de::Error::custom("Invalid gas params length"));
54            }
55            Ok(Self::new(Arc::new(table.table.try_into().unwrap())))
56        }
57    }
58}
59
60impl Default for GasParams {
61    fn default() -> Self {
62        Self::new_spec(SpecId::default())
63    }
64}
65
66impl GasParams {
67    /// Creates a new `GasParams` with the given table.
68    #[inline]
69    pub fn new(table: Arc<[u64; 256]>) -> Self {
70        Self {
71            ptr: table.as_ptr(),
72            table,
73        }
74    }
75
76    /// Overrides the gas cost for the given gas id.
77    ///
78    /// It will clone underlying table and override the values.
79    ///
80    /// Use to override default gas cost
81    ///
82    /// ```rust
83    /// use revm_interpreter::gas::params::{GasParams, GasId};
84    /// use primitives::hardfork::SpecId;
85    ///
86    /// let mut gas_table = GasParams::new_spec(SpecId::default());
87    /// gas_table.override_gas([(GasId::memory_linear_cost(), 2), (GasId::memory_quadratic_reduction(), 512)].into_iter());
88    /// assert_eq!(gas_table.get(GasId::memory_linear_cost()), 2);
89    /// assert_eq!(gas_table.get(GasId::memory_quadratic_reduction()), 512);
90    /// ```
91    pub fn override_gas(&mut self, values: impl IntoIterator<Item = (GasId, u64)>) {
92        let mut table = *self.table.clone();
93        for (id, value) in values.into_iter() {
94            table[id.as_usize()] = value;
95        }
96        *self = Self::new(Arc::new(table));
97    }
98
99    /// Returns the table.
100    #[inline]
101    pub fn table(&self) -> &[u64; 256] {
102        &self.table
103    }
104
105    /// Creates a new `GasParams` for the given spec.
106    #[inline]
107    pub fn new_spec(spec: SpecId) -> Self {
108        let mut table = [0; 256];
109
110        table[GasId::exp_byte_gas().as_usize()] = 10;
111        table[GasId::logdata().as_usize()] = gas::LOGDATA;
112        table[GasId::logtopic().as_usize()] = gas::LOGTOPIC;
113        table[GasId::copy_per_word().as_usize()] = gas::COPY;
114        table[GasId::extcodecopy_per_word().as_usize()] = gas::COPY;
115        table[GasId::mcopy_per_word().as_usize()] = gas::COPY;
116        table[GasId::keccak256_per_word().as_usize()] = gas::KECCAK256WORD;
117        table[GasId::memory_linear_cost().as_usize()] = gas::MEMORY;
118        table[GasId::memory_quadratic_reduction().as_usize()] = 512;
119        table[GasId::initcode_per_word().as_usize()] = gas::INITCODE_WORD_COST;
120        table[GasId::create().as_usize()] = gas::CREATE;
121        table[GasId::call_stipend_reduction().as_usize()] = 64;
122        table[GasId::transfer_value_cost().as_usize()] = gas::CALLVALUE;
123        table[GasId::cold_account_additional_cost().as_usize()] = 0;
124        table[GasId::new_account_cost().as_usize()] = gas::NEWACCOUNT;
125        table[GasId::warm_storage_read_cost().as_usize()] = 0;
126        // Frontiers had fixed 5k cost.
127        table[GasId::sstore_static().as_usize()] = SSTORE_RESET;
128        // SSTORE SET
129        table[GasId::sstore_set_without_load_cost().as_usize()] = SSTORE_SET - SSTORE_RESET;
130        // SSTORE RESET Is covered in SSTORE_STATIC.
131        table[GasId::sstore_reset_without_cold_load_cost().as_usize()] = 0;
132        // SSTORE CLEARING SLOT REFUND
133        table[GasId::sstore_clearing_slot_refund().as_usize()] = 15000;
134        table[GasId::selfdestruct_refund().as_usize()] = 24000;
135        table[GasId::call_stipend().as_usize()] = gas::CALL_STIPEND;
136        table[GasId::cold_storage_additional_cost().as_usize()] = 0;
137        table[GasId::cold_storage_cost().as_usize()] = 0;
138        table[GasId::new_account_cost_for_selfdestruct().as_usize()] = 0;
139
140        if spec.is_enabled_in(SpecId::TANGERINE) {
141            table[GasId::new_account_cost_for_selfdestruct().as_usize()] = gas::NEWACCOUNT;
142        }
143
144        if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
145            table[GasId::exp_byte_gas().as_usize()] = 50;
146        }
147
148        if spec.is_enabled_in(SpecId::ISTANBUL) {
149            table[GasId::sstore_static().as_usize()] = gas::ISTANBUL_SLOAD_GAS;
150            table[GasId::sstore_set_without_load_cost().as_usize()] =
151                SSTORE_SET - ISTANBUL_SLOAD_GAS;
152            table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
153                SSTORE_RESET - ISTANBUL_SLOAD_GAS;
154        }
155
156        if spec.is_enabled_in(SpecId::BERLIN) {
157            table[GasId::sstore_static().as_usize()] = gas::WARM_STORAGE_READ_COST;
158            table[GasId::cold_account_additional_cost().as_usize()] =
159                gas::COLD_ACCOUNT_ACCESS_COST_ADDITIONAL;
160            table[GasId::cold_storage_additional_cost().as_usize()] =
161                gas::COLD_SLOAD_COST - gas::WARM_STORAGE_READ_COST;
162            table[GasId::cold_storage_cost().as_usize()] = gas::COLD_SLOAD_COST;
163            table[GasId::warm_storage_read_cost().as_usize()] = gas::WARM_STORAGE_READ_COST;
164
165            table[GasId::sstore_reset_without_cold_load_cost().as_usize()] =
166                WARM_SSTORE_RESET - gas::WARM_STORAGE_READ_COST;
167            table[GasId::sstore_set_without_load_cost().as_usize()] =
168                SSTORE_SET - gas::WARM_STORAGE_READ_COST;
169        }
170
171        if spec.is_enabled_in(SpecId::LONDON) {
172            // EIP-3529: Reduction in refunds
173
174            // Replace SSTORE_CLEARS_SCHEDULE (as defined in EIP-2200) with
175            // SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST (4,800 gas as of EIP-2929 + EIP-2930)
176            table[GasId::sstore_clearing_slot_refund().as_usize()] =
177                WARM_SSTORE_RESET + gas::ACCESS_LIST_STORAGE_KEY;
178
179            table[GasId::selfdestruct_refund().as_usize()] = 0;
180        }
181
182        Self::new(Arc::new(table))
183    }
184
185    /// Gets the gas cost for the given gas id.
186    #[inline]
187    pub const fn get(&self, id: GasId) -> u64 {
188        unsafe { *self.ptr.add(id.as_usize()) }
189    }
190
191    /// `EXP` opcode cost calculation.
192    #[inline]
193    pub fn exp_cost(&self, power: U256) -> u64 {
194        if power.is_zero() {
195            return 0;
196        }
197        // EIP-160: EXP cost increase
198        self.get(GasId::exp_byte_gas())
199            .saturating_mul(log2floor(power) / 8 + 1)
200    }
201
202    /// Selfdestruct refund.
203    #[inline]
204    pub fn selfdestruct_refund(&self) -> i64 {
205        self.get(GasId::selfdestruct_refund()) as i64
206    }
207
208    /// Selfdestruct cost.
209    #[inline]
210    pub fn selfdestruct_cost(&self, should_charge_topup: bool, is_cold: bool) -> u64 {
211        let mut gas = 0;
212
213        // EIP-150: Gas cost changes for IO-heavy operations
214        if should_charge_topup {
215            gas += self.new_account_cost_for_selfdestruct();
216        }
217
218        if is_cold {
219            // Note: SELFDESTRUCT does not charge a WARM_STORAGE_READ_COST in case the recipient is already warm,
220            // which differs from how the other call-variants work. The reasoning behind this is to keep
221            // the changes small, a SELFDESTRUCT already costs 5K and is a no-op if invoked more than once.
222            //
223            // For GasParams both values are zero before BERLIN fork.
224            gas += self.cold_account_additional_cost() + self.warm_storage_read_cost();
225        }
226        gas
227    }
228
229    /// EXTCODECOPY gas cost
230    #[inline]
231    pub fn extcodecopy(&self, len: usize) -> u64 {
232        self.get(GasId::extcodecopy_per_word())
233            .saturating_mul(num_words(len) as u64)
234    }
235
236    /// MCOPY gas cost
237    #[inline]
238    pub fn mcopy_cost(&self, len: usize) -> u64 {
239        self.get(GasId::mcopy_per_word())
240            .saturating_mul(num_words(len) as u64)
241    }
242
243    /// Static gas cost for SSTORE opcode
244    #[inline]
245    pub fn sstore_static_gas(&self) -> u64 {
246        self.get(GasId::sstore_static())
247    }
248
249    /// SSTORE set cost
250    #[inline]
251    pub fn sstore_set_without_load_cost(&self) -> u64 {
252        self.get(GasId::sstore_set_without_load_cost())
253    }
254
255    /// SSTORE reset cost
256    #[inline]
257    pub fn sstore_reset_without_cold_load_cost(&self) -> u64 {
258        self.get(GasId::sstore_reset_without_cold_load_cost())
259    }
260
261    /// SSTORE clearing slot refund
262    #[inline]
263    pub fn sstore_clearing_slot_refund(&self) -> u64 {
264        self.get(GasId::sstore_clearing_slot_refund())
265    }
266
267    /// Dynamic gas cost for SSTORE opcode.
268    ///
269    /// Dynamic gas cost is gas that needs input from SSTORE operation to be calculated.
270    #[inline]
271    pub fn sstore_dynamic_gas(&self, is_istanbul: bool, vals: &SStoreResult, is_cold: bool) -> u64 {
272        // frontier logic gets charged for every SSTORE operation if original value is zero.
273        // this behaviour is fixed in istanbul fork.
274        if !is_istanbul {
275            if vals.is_present_zero() && !vals.is_new_zero() {
276                return self.sstore_set_without_load_cost();
277            } else {
278                return self.sstore_reset_without_cold_load_cost();
279            }
280        }
281
282        let mut gas = 0;
283
284        // this will be zero before berlin fork.
285        if is_cold {
286            gas += self.cold_storage_cost();
287        }
288
289        // if new values changed present value and present value is unchanged from original.
290        if vals.new_values_changes_present() && vals.is_original_eq_present() {
291            gas += if vals.is_original_zero() {
292                // set cost for creating storage slot (Zero slot means it is not existing).
293                // and previous condition says present is same as original.
294                self.sstore_set_without_load_cost()
295            } else {
296                // if new value is not zero, this means we are setting some value to it.
297                self.sstore_reset_without_cold_load_cost()
298            };
299        }
300        gas
301    }
302
303    /// SSTORE refund calculation.
304    #[inline]
305    pub fn sstore_refund(&self, is_istanbul: bool, vals: &SStoreResult) -> i64 {
306        // EIP-3529: Reduction in refunds
307        let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64;
308
309        if !is_istanbul {
310            // // before istanbul fork, refund was always awarded without checking original state.
311            if !vals.is_present_zero() && vals.is_new_zero() {
312                return sstore_clearing_slot_refund;
313            }
314            return 0;
315        }
316
317        // If current value equals new value (this is a no-op)
318        if vals.is_new_eq_present() {
319            return 0;
320        }
321
322        // refund for the clearing of storage slot.
323        // As new is not equal to present, new values zero means that original and present values are not zero
324        if vals.is_original_eq_present() && vals.is_new_zero() {
325            return sstore_clearing_slot_refund;
326        }
327
328        let mut refund = 0;
329        // If original value is not 0
330        if !vals.is_original_zero() {
331            // If current value is 0 (also means that new value is not 0),
332            if vals.is_present_zero() {
333                // remove SSTORE_CLEARS_SCHEDULE gas from refund counter.
334                refund -= sstore_clearing_slot_refund;
335            // If new value is 0 (also means that current value is not 0),
336            } else if vals.is_new_zero() {
337                // add SSTORE_CLEARS_SCHEDULE gas to refund counter.
338                refund += sstore_clearing_slot_refund;
339            }
340        }
341
342        // If original value equals new value (this storage slot is reset)
343        if vals.is_original_eq_new() {
344            // If original value is 0
345            if vals.is_original_zero() {
346                // add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
347                refund += self.sstore_set_without_load_cost() as i64;
348            // Otherwise
349            } else {
350                // add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
351                refund += self.sstore_reset_without_cold_load_cost() as i64;
352            }
353        }
354        refund
355    }
356
357    /// `LOG` opcode cost calculation.
358    #[inline]
359    pub const fn log_cost(&self, n: u8, len: u64) -> u64 {
360        self.get(GasId::logdata())
361            .saturating_mul(len)
362            .saturating_add(self.get(GasId::logtopic()) * n as u64)
363    }
364
365    /// KECCAK256 gas cost per word
366    #[inline]
367    pub fn keccak256_cost(&self, len: usize) -> u64 {
368        self.get(GasId::keccak256_per_word())
369            .saturating_mul(num_words(len) as u64)
370    }
371
372    /// Memory gas cost
373    #[inline]
374    pub fn memory_cost(&self, len: usize) -> u64 {
375        let len = len as u64;
376        self.get(GasId::memory_linear_cost())
377            .saturating_mul(len)
378            .saturating_add(
379                (len.saturating_mul(len))
380                    .saturating_div(self.get(GasId::memory_quadratic_reduction())),
381            )
382    }
383
384    /// Initcode word cost
385    #[inline]
386    pub fn initcode_cost(&self, len: usize) -> u64 {
387        self.get(GasId::initcode_per_word())
388            .saturating_mul(num_words(len) as u64)
389    }
390
391    /// Create gas cost
392    #[inline]
393    pub fn create_cost(&self) -> u64 {
394        self.get(GasId::create())
395    }
396
397    /// Create2 gas cost.
398    #[inline]
399    pub fn create2_cost(&self, len: usize) -> u64 {
400        self.get(GasId::create()).saturating_add(
401            self.get(GasId::keccak256_per_word())
402                .saturating_mul(num_words(len) as u64),
403        )
404    }
405
406    /// Call stipend.
407    #[inline]
408    pub fn call_stipend(&self) -> u64 {
409        self.get(GasId::call_stipend())
410    }
411
412    /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit.
413    #[inline]
414    pub fn call_stipend_reduction(&self, gas_limit: u64) -> u64 {
415        gas_limit - gas_limit / self.get(GasId::call_stipend_reduction())
416    }
417
418    /// Transfer value cost
419    #[inline]
420    pub fn transfer_value_cost(&self) -> u64 {
421        self.get(GasId::transfer_value_cost())
422    }
423
424    /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded.
425    #[inline]
426    pub fn cold_account_additional_cost(&self) -> u64 {
427        self.get(GasId::cold_account_additional_cost())
428    }
429
430    /// Cold storage additional cost.
431    #[inline]
432    pub fn cold_storage_additional_cost(&self) -> u64 {
433        self.get(GasId::cold_storage_additional_cost())
434    }
435
436    /// Cold storage cost.
437    #[inline]
438    pub fn cold_storage_cost(&self) -> u64 {
439        self.get(GasId::cold_storage_cost())
440    }
441
442    /// New account cost. New account cost is added to the gas cost if the account is empty.
443    #[inline]
444    pub fn new_account_cost(&self, is_spurious_dragon: bool, transfers_value: bool) -> u64 {
445        // EIP-161: State trie clearing (invariant-preserving alternative)
446        // Pre-Spurious Dragon: always charge for new account
447        // Post-Spurious Dragon: only charge if value is transferred
448        if !is_spurious_dragon || transfers_value {
449            return self.get(GasId::new_account_cost());
450        }
451        0
452    }
453
454    /// New account cost for selfdestruct.
455    #[inline]
456    pub fn new_account_cost_for_selfdestruct(&self) -> u64 {
457        self.get(GasId::new_account_cost_for_selfdestruct())
458    }
459
460    /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded.
461    #[inline]
462    pub fn warm_storage_read_cost(&self) -> u64 {
463        self.get(GasId::warm_storage_read_cost())
464    }
465
466    /// Copy cost
467    #[inline]
468    pub fn copy_cost(&self, len: usize) -> u64 {
469        self.copy_per_word_cost(num_words(len))
470    }
471
472    /// Copy per word cost
473    #[inline]
474    pub fn copy_per_word_cost(&self, word_num: usize) -> u64 {
475        self.get(GasId::copy_per_word())
476            .saturating_mul(word_num as u64)
477    }
478}
479
480/// Gas identifier that maps onto index in gas table.
481#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
482pub struct GasId(u8);
483
484impl GasId {
485    /// Creates a new `GasId` with the given id.
486    pub const fn new(id: u8) -> Self {
487        Self(id)
488    }
489
490    /// Returns the id of the gas.
491    pub const fn as_u8(&self) -> u8 {
492        self.0
493    }
494
495    /// Returns the id of the gas as a usize.
496    pub const fn as_usize(&self) -> usize {
497        self.0 as usize
498    }
499
500    /// Returns the name of the gas identifier as a string.
501    ///
502    /// # Examples
503    ///
504    /// ```
505    /// use revm_interpreter::gas::params::GasId;
506    ///
507    /// assert_eq!(GasId::exp_byte_gas().name(), "exp_byte_gas");
508    /// assert_eq!(GasId::memory_linear_cost().name(), "memory_linear_cost");
509    /// assert_eq!(GasId::sstore_static().name(), "sstore_static");
510    /// ```
511    pub const fn name(&self) -> &'static str {
512        match self.0 {
513            x if x == Self::exp_byte_gas().as_u8() => "exp_byte_gas",
514            x if x == Self::extcodecopy_per_word().as_u8() => "extcodecopy_per_word",
515            x if x == Self::copy_per_word().as_u8() => "copy_per_word",
516            x if x == Self::logdata().as_u8() => "logdata",
517            x if x == Self::logtopic().as_u8() => "logtopic",
518            x if x == Self::mcopy_per_word().as_u8() => "mcopy_per_word",
519            x if x == Self::keccak256_per_word().as_u8() => "keccak256_per_word",
520            x if x == Self::memory_linear_cost().as_u8() => "memory_linear_cost",
521            x if x == Self::memory_quadratic_reduction().as_u8() => "memory_quadratic_reduction",
522            x if x == Self::initcode_per_word().as_u8() => "initcode_per_word",
523            x if x == Self::create().as_u8() => "create",
524            x if x == Self::call_stipend_reduction().as_u8() => "call_stipend_reduction",
525            x if x == Self::transfer_value_cost().as_u8() => "transfer_value_cost",
526            x if x == Self::cold_account_additional_cost().as_u8() => {
527                "cold_account_additional_cost"
528            }
529            x if x == Self::new_account_cost().as_u8() => "new_account_cost",
530            x if x == Self::warm_storage_read_cost().as_u8() => "warm_storage_read_cost",
531            x if x == Self::sstore_static().as_u8() => "sstore_static",
532            x if x == Self::sstore_set_without_load_cost().as_u8() => {
533                "sstore_set_without_load_cost"
534            }
535            x if x == Self::sstore_reset_without_cold_load_cost().as_u8() => {
536                "sstore_reset_without_cold_load_cost"
537            }
538            x if x == Self::sstore_clearing_slot_refund().as_u8() => "sstore_clearing_slot_refund",
539            x if x == Self::selfdestruct_refund().as_u8() => "selfdestruct_refund",
540            x if x == Self::call_stipend().as_u8() => "call_stipend",
541            x if x == Self::cold_storage_additional_cost().as_u8() => {
542                "cold_storage_additional_cost"
543            }
544            x if x == Self::cold_storage_cost().as_u8() => "cold_storage_cost",
545            x if x == Self::new_account_cost_for_selfdestruct().as_u8() => {
546                "new_account_cost_for_selfdestruct"
547            }
548            _ => "unknown",
549        }
550    }
551
552    /// Converts a string to a `GasId`.
553    ///
554    /// Returns `None` if the string does not match any known gas identifier.
555    ///
556    /// # Examples
557    ///
558    /// ```
559    /// use revm_interpreter::gas::params::GasId;
560    ///
561    /// assert_eq!(GasId::from_name("exp_byte_gas"), Some(GasId::exp_byte_gas()));
562    /// assert_eq!(GasId::from_name("memory_linear_cost"), Some(GasId::memory_linear_cost()));
563    /// assert_eq!(GasId::from_name("invalid_name"), None);
564    /// ```
565    pub fn from_name(s: &str) -> Option<GasId> {
566        match s {
567            "exp_byte_gas" => Some(Self::exp_byte_gas()),
568            "extcodecopy_per_word" => Some(Self::extcodecopy_per_word()),
569            "copy_per_word" => Some(Self::copy_per_word()),
570            "logdata" => Some(Self::logdata()),
571            "logtopic" => Some(Self::logtopic()),
572            "mcopy_per_word" => Some(Self::mcopy_per_word()),
573            "keccak256_per_word" => Some(Self::keccak256_per_word()),
574            "memory_linear_cost" => Some(Self::memory_linear_cost()),
575            "memory_quadratic_reduction" => Some(Self::memory_quadratic_reduction()),
576            "initcode_per_word" => Some(Self::initcode_per_word()),
577            "create" => Some(Self::create()),
578            "call_stipend_reduction" => Some(Self::call_stipend_reduction()),
579            "transfer_value_cost" => Some(Self::transfer_value_cost()),
580            "cold_account_additional_cost" => Some(Self::cold_account_additional_cost()),
581            "new_account_cost" => Some(Self::new_account_cost()),
582            "warm_storage_read_cost" => Some(Self::warm_storage_read_cost()),
583            "sstore_static" => Some(Self::sstore_static()),
584            "sstore_set_without_load_cost" => Some(Self::sstore_set_without_load_cost()),
585            "sstore_reset_without_cold_load_cost" => {
586                Some(Self::sstore_reset_without_cold_load_cost())
587            }
588            "sstore_clearing_slot_refund" => Some(Self::sstore_clearing_slot_refund()),
589            "selfdestruct_refund" => Some(Self::selfdestruct_refund()),
590            "call_stipend" => Some(Self::call_stipend()),
591            "cold_storage_additional_cost" => Some(Self::cold_storage_additional_cost()),
592            "cold_storage_cost" => Some(Self::cold_storage_cost()),
593            "new_account_cost_for_selfdestruct" => Some(Self::new_account_cost_for_selfdestruct()),
594            _ => None,
595        }
596    }
597
598    /// EXP gas cost per byte
599    pub const fn exp_byte_gas() -> GasId {
600        Self::new(1)
601    }
602
603    /// EXTCODECOPY gas cost per word
604    pub const fn extcodecopy_per_word() -> GasId {
605        Self::new(2)
606    }
607
608    /// Copy copy per word
609    pub const fn copy_per_word() -> GasId {
610        Self::new(3)
611    }
612
613    /// Log data gas cost per byte
614    pub const fn logdata() -> GasId {
615        Self::new(4)
616    }
617
618    /// Log topic gas cost per topic
619    pub const fn logtopic() -> GasId {
620        Self::new(5)
621    }
622
623    /// MCOPY gas cost per word
624    pub const fn mcopy_per_word() -> GasId {
625        Self::new(6)
626    }
627
628    /// KECCAK256 gas cost per word
629    pub const fn keccak256_per_word() -> GasId {
630        Self::new(7)
631    }
632
633    /// Memory linear cost. Memory is additionally added as n*linear_cost.
634    pub const fn memory_linear_cost() -> GasId {
635        Self::new(8)
636    }
637
638    /// Memory quadratic reduction. Memory is additionally added as n*n/quadratic_reduction.
639    pub const fn memory_quadratic_reduction() -> GasId {
640        Self::new(9)
641    }
642
643    /// Initcode word cost
644    pub const fn initcode_per_word() -> GasId {
645        Self::new(10)
646    }
647
648    /// Create gas cost
649    pub const fn create() -> GasId {
650        Self::new(11)
651    }
652
653    /// Call stipend reduction. Call stipend is reduced by 1/64 of the gas limit.
654    pub const fn call_stipend_reduction() -> GasId {
655        Self::new(12)
656    }
657
658    /// Transfer value cost
659    pub const fn transfer_value_cost() -> GasId {
660        Self::new(13)
661    }
662
663    /// Additional cold cost. Additional cold cost is added to the gas cost if the account is cold loaded.
664    pub const fn cold_account_additional_cost() -> GasId {
665        Self::new(14)
666    }
667
668    /// New account cost. New account cost is added to the gas cost if the account is empty.
669    pub const fn new_account_cost() -> GasId {
670        Self::new(15)
671    }
672
673    /// Warm storage read cost. Warm storage read cost is added to the gas cost if the account is warm loaded.
674    ///
675    /// Used in delegated account access to specify delegated account warm gas cost.
676    pub const fn warm_storage_read_cost() -> GasId {
677        Self::new(16)
678    }
679
680    /// Static gas cost for SSTORE opcode. This gas in comparison with other gas const needs
681    /// to be deducted after check for minimal stipend gas cost. This is a reason why it is here.
682    pub const fn sstore_static() -> GasId {
683        Self::new(17)
684    }
685
686    /// SSTORE set cost additional amount after SSTORE_RESET is added.
687    pub const fn sstore_set_without_load_cost() -> GasId {
688        Self::new(18)
689    }
690
691    /// SSTORE reset cost
692    pub const fn sstore_reset_without_cold_load_cost() -> GasId {
693        Self::new(19)
694    }
695
696    /// SSTORE clearing slot refund
697    pub const fn sstore_clearing_slot_refund() -> GasId {
698        Self::new(20)
699    }
700
701    /// Selfdestruct refund.
702    pub const fn selfdestruct_refund() -> GasId {
703        Self::new(21)
704    }
705
706    /// Call stipend checked in sstore.
707    pub const fn call_stipend() -> GasId {
708        Self::new(22)
709    }
710
711    /// Cold storage additional cost.
712    pub const fn cold_storage_additional_cost() -> GasId {
713        Self::new(23)
714    }
715
716    /// Cold storage cost
717    pub const fn cold_storage_cost() -> GasId {
718        Self::new(24)
719    }
720
721    /// New account cost for selfdestruct.
722    pub const fn new_account_cost_for_selfdestruct() -> GasId {
723        Self::new(25)
724    }
725}
726
727#[cfg(test)]
728mod tests {
729    use super::*;
730    use std::collections::HashSet;
731
732    #[test]
733    fn test_gas_id_name_and_from_str_coverage() {
734        let mut unique_names = HashSet::new();
735        let mut known_gas_ids = 0;
736
737        // Iterate over all possible GasId values (0..256)
738        for i in 0..=255 {
739            let gas_id = GasId::new(i);
740            let name = gas_id.name();
741
742            // Count unique names (excluding "unknown")
743            if name != "unknown" {
744                unique_names.insert(name);
745            }
746        }
747
748        // Now test from_str for each unique name
749        for name in &unique_names {
750            if let Some(gas_id) = GasId::from_name(name) {
751                known_gas_ids += 1;
752                // Verify round-trip: name -> GasId -> name should be consistent
753                assert_eq!(gas_id.name(), *name, "Round-trip failed for {}", name);
754            }
755        }
756
757        println!("Total unique named GasIds: {}", unique_names.len());
758        println!("GasIds resolvable via from_str: {}", known_gas_ids);
759
760        // All unique names should be resolvable via from_str
761        assert_eq!(
762            unique_names.len(),
763            known_gas_ids,
764            "Not all unique names are resolvable via from_str"
765        );
766
767        // We should have exactly 25 known GasIds (based on the indices 1-25 used)
768        assert_eq!(
769            unique_names.len(),
770            25,
771            "Expected 25 unique GasIds, found {}",
772            unique_names.len()
773        );
774    }
775}