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