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