Skip to main content

revm_context_interface/cfg/
gas.rs

1//! Gas constants and functions for gas calculation.
2
3use crate::{cfg::gas_params, cfg::GasParams, Transaction};
4use primitives::hardfork::SpecId;
5
6/// Tracker for gas during execution.
7///
8/// This is used to track the gas during execution.
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct GasTracker {
12    /// Gas Limit,
13    gas_limit: u64,
14    /// Regular gas remaining (`gas_left`). Reservoir is tracked separately.
15    remaining: u64,
16    /// State gas reservoir (gas exceeding TX_MAX_GAS_LIMIT). Starts as `execution_gas - min(execution_gas, regular_gas_budget)`.
17    /// When 0, all remaining gas is regular gas with hard cap at `TX_MAX_GAS_LIMIT`.
18    reservoir: u64,
19    /// Net state gas spent so far.
20    ///
21    /// Can be negative within a call frame when 0→x→0 storage restoration refills
22    /// more state gas than the frame itself has charged (the parent previously
23    /// charged the 0→x portion). The net is reconciled on frame return.
24    state_gas_spent: i64,
25    /// State gas drawn from regular gas (`remaining`) because the reservoir was
26    /// empty (EIP-8037's `state_gas_from_gas_left`).
27    ///
28    /// Incremented by [`Self::record_state_cost`] whenever a state-gas charge
29    /// spills out of the reservoir into regular gas. On frame rollback (revert or
30    /// halt) the spilled portion is credited back to `remaining` in last-in-
31    /// first-out order by [`Self::rollback_state_gas`]; on success it is
32    /// propagated to the parent frame so a later parent rollback can return it.
33    state_gas_spilled: u64,
34    /// Refunded gas. Used to refund the gas to the caller at the end of execution.
35    refunded: i64,
36}
37
38impl GasTracker {
39    /// Creates a new `GasTracker` with the given remaining gas and reservoir.
40    #[inline]
41    pub const fn new(gas_limit: u64, remaining: u64, reservoir: u64) -> Self {
42        Self {
43            gas_limit,
44            remaining,
45            reservoir,
46            state_gas_spent: 0,
47            state_gas_spilled: 0,
48            refunded: 0,
49        }
50    }
51
52    /// Creates a new `GasTracker` with the given used gas and reservoir.
53    /// Remaining gas saturates at zero when used gas exceeds the gas limit.
54    #[inline]
55    pub const fn new_used_gas(gas_limit: u64, used_gas: u64, reservoir: u64) -> Self {
56        Self::new(gas_limit, gas_limit.saturating_sub(used_gas), reservoir)
57    }
58
59    /// Returns the gas limit.
60    #[inline]
61    pub const fn limit(&self) -> u64 {
62        self.gas_limit
63    }
64
65    /// Sets the gas limit.
66    #[inline]
67    pub const fn set_limit(&mut self, val: u64) {
68        self.gas_limit = val;
69    }
70
71    /// Returns the remaining gas.
72    #[inline]
73    pub const fn remaining(&self) -> u64 {
74        self.remaining
75    }
76
77    /// Sets the remaining gas.
78    #[inline]
79    pub const fn set_remaining(&mut self, val: u64) {
80        self.remaining = val;
81    }
82
83    /// Returns the reservoir gas.
84    #[inline]
85    pub const fn reservoir(&self) -> u64 {
86        self.reservoir
87    }
88
89    /// Sets the reservoir gas.
90    #[inline]
91    pub const fn set_reservoir(&mut self, val: u64) {
92        self.reservoir = val;
93    }
94
95    /// Returns the state gas spent.
96    #[inline]
97    pub const fn state_gas_spent(&self) -> i64 {
98        self.state_gas_spent
99    }
100
101    /// Sets the state gas spent.
102    #[inline]
103    pub const fn set_state_gas_spent(&mut self, val: i64) {
104        self.state_gas_spent = val;
105    }
106
107    /// Returns the state gas drawn from regular gas (`remaining`) because the
108    /// reservoir was empty (EIP-8037's `state_gas_from_gas_left`).
109    #[inline]
110    pub const fn state_gas_spilled(&self) -> u64 {
111        self.state_gas_spilled
112    }
113
114    /// Sets the spilled state gas.
115    #[inline]
116    pub const fn set_state_gas_spilled(&mut self, val: u64) {
117        self.state_gas_spilled = val;
118    }
119
120    /// Adds `delta` to the spilled state gas, saturating.
121    ///
122    /// Used to merge a successful child frame's spilled state gas into this
123    /// (parent) frame so a later parent rollback can return it.
124    #[inline]
125    pub const fn add_state_gas_spilled(&mut self, delta: u64) {
126        self.state_gas_spilled = self.state_gas_spilled.saturating_add(delta);
127    }
128
129    /// Returns the refunded gas.
130    #[inline]
131    pub const fn refunded(&self) -> i64 {
132        self.refunded
133    }
134
135    /// Sets the refunded gas.
136    #[inline]
137    pub const fn set_refunded(&mut self, val: i64) {
138        self.refunded = val;
139    }
140
141    /// Records a regular gas cost.
142    ///
143    /// Deducts from `remaining`. Returns `false` if insufficient gas.
144    #[inline]
145    #[must_use = "In case of not enough gas, the interpreter should halt with an out-of-gas error"]
146    pub const fn record_regular_cost(&mut self, cost: u64) -> bool {
147        if let Some(new_remaining) = self.remaining.checked_sub(cost) {
148            self.remaining = new_remaining;
149            return true;
150        }
151        false
152    }
153
154    /// Records a state gas cost (EIP-8037 reservoir model).
155    ///
156    /// State gas charges deduct from the reservoir first. If the reservoir is exhausted,
157    /// remaining charges spill into `remaining` (requiring `remaining >= cost`).
158    /// Tracks state gas spent.
159    ///
160    /// Returns `false` if total remaining gas is insufficient.
161    #[inline]
162    #[must_use = "In case of not enough gas, the interpreter should halt with an out-of-gas error"]
163    pub const fn record_state_cost(&mut self, cost: u64) -> bool {
164        if self.reservoir >= cost {
165            self.state_gas_spent = self.state_gas_spent.saturating_add(cost as i64);
166            self.reservoir -= cost;
167            return true;
168        }
169
170        let spill = cost - self.reservoir;
171
172        let success = self.record_regular_cost(spill);
173        if success {
174            self.state_gas_spent = self.state_gas_spent.saturating_add(cost as i64);
175            self.state_gas_spilled = self.state_gas_spilled.saturating_add(spill);
176            self.reservoir = 0;
177        }
178        success
179    }
180
181    /// Rolls back this frame's state-gas charges on revert or exceptional halt
182    /// (EIP-8037).
183    ///
184    /// The state gas charged within the frame is refilled in last-in-first-out
185    /// order: the spilled portion is credited back to `remaining` (the pool
186    /// charged last) and the rest restores the reservoir to its frame-start
187    /// value. Concretely, `remaining` gains `state_gas_spilled` and the reservoir
188    /// becomes `reservoir + state_gas_spent - state_gas_spilled`, which is exactly
189    /// the reservoir the frame inherited. Both state-gas counters are then reset.
190    ///
191    /// On revert the resulting `remaining` (including the refilled spill) is
192    /// returned to the parent; on halt the caller additionally zeroes `remaining`
193    /// so the spilled gas is consumed while the reservoir is left untouched.
194    #[inline]
195    pub const fn rollback_state_gas(&mut self) {
196        self.reservoir = self
197            .reservoir
198            .saturating_add_signed(self.state_gas_spent)
199            .saturating_sub(self.state_gas_spilled);
200        self.remaining = self.remaining.saturating_add(self.state_gas_spilled);
201        self.state_gas_spent = 0;
202        self.state_gas_spilled = 0;
203    }
204
205    /// Refills the reservoir with state gas that is returned by 0→x→0 storage
206    /// restoration (EIP-8037 issue #2).
207    ///
208    /// Per the spec, when a storage slot is restored to its original zero value
209    /// within the same transaction, the state gas charged for the initial 0→x
210    /// transition is directly restored to the reservoir rather than routed
211    /// through the capped refund counter.
212    ///
213    /// `state_gas_spent` is decremented by the full `amount` and may become
214    /// negative if the matching 0→x charge was made by a parent frame (so this
215    /// frame's `state_gas_spilled` is zero and the whole refill lands in the
216    /// reservoir); the parent's total is reconciled on frame return.
217    ///
218    /// Because charges deduct from the reservoir first and from regular gas
219    /// (`remaining`) last, the refill credits the pool charged last first:
220    /// `remaining` is credited up to `state_gas_spilled` and any remainder tops
221    /// up the reservoir.
222    #[inline]
223    pub const fn refill_reservoir(&mut self, amount: u64) {
224        let to_remaining = if amount < self.state_gas_spilled {
225            amount
226        } else {
227            self.state_gas_spilled
228        };
229        self.remaining = self.remaining.saturating_add(to_remaining);
230        self.state_gas_spilled -= to_remaining;
231        self.reservoir = self.reservoir.saturating_add(amount - to_remaining);
232        self.state_gas_spent = self.state_gas_spent.saturating_sub(amount as i64);
233    }
234
235    /// Records a refund value.
236    #[inline]
237    pub const fn record_refund(&mut self, refund: i64) {
238        self.refunded += refund;
239    }
240
241    /// Erases a gas cost from remaining (returns gas from child frame).
242    #[inline]
243    pub const fn erase_cost(&mut self, returned: u64) {
244        self.remaining += returned;
245    }
246
247    /// Spends all remaining gas excluding the reservoir.
248    #[inline]
249    pub const fn spend_all(&mut self) {
250        self.remaining = 0;
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::GasTracker;
257
258    #[test]
259    fn new_used_gas_saturates_remaining_at_zero() {
260        assert_eq!(
261            GasTracker::new_used_gas(10, 9, 3),
262            GasTracker::new(10, 1, 3)
263        );
264        assert_eq!(
265            GasTracker::new_used_gas(10, 10, 3),
266            GasTracker::new(10, 0, 3)
267        );
268        assert_eq!(
269            GasTracker::new_used_gas(10, 11, 3),
270            GasTracker::new(10, 0, 3)
271        );
272    }
273}
274
275/// Gas cost for operations that consume zero gas.
276pub const ZERO: u64 = 0;
277/// Base gas cost for basic operations.
278pub const BASE: u64 = 2;
279
280/// Gas cost for very low-cost operations.
281pub const VERYLOW: u64 = 3;
282/// Gas cost for DATALOADN instruction.
283pub const DATA_LOADN_GAS: u64 = 3;
284
285/// Gas cost for conditional jump instructions.
286pub const CONDITION_JUMP_GAS: u64 = 4;
287/// Gas cost for RETF instruction.
288pub const RETF_GAS: u64 = 3;
289/// Gas cost for DATALOAD instruction.
290pub const DATA_LOAD_GAS: u64 = 4;
291
292/// Gas cost for low-cost operations.
293pub const LOW: u64 = 5;
294/// Gas cost for medium-cost operations.
295pub const MID: u64 = 8;
296/// Gas cost for high-cost operations.
297pub const HIGH: u64 = 10;
298/// Gas cost for JUMPDEST instruction.
299pub const JUMPDEST: u64 = 1;
300/// Gas cost for REFUND SELFDESTRUCT instruction.
301pub const SELFDESTRUCT_REFUND: i64 = 24000;
302/// Gas cost for CREATE instruction.
303pub const CREATE: u64 = 32000;
304/// Additional gas cost when a call transfers value.
305pub const CALLVALUE: u64 = 9000;
306/// Gas cost for creating a new account.
307pub const NEWACCOUNT: u64 = 25000;
308/// Base gas cost for EXP instruction.
309pub const EXP: u64 = 10;
310/// Gas cost per word for memory operations.
311pub const MEMORY: u64 = 3;
312/// Base gas cost for LOG instructions.
313pub const LOG: u64 = 375;
314/// Gas cost per byte of data in LOG instructions.
315pub const LOGDATA: u64 = 8;
316/// Gas cost per topic in LOG instructions.
317pub const LOGTOPIC: u64 = 375;
318/// Base gas cost for KECCAK256 instruction.
319pub const KECCAK256: u64 = 30;
320/// Gas cost per word for KECCAK256 instruction.
321pub const KECCAK256WORD: u64 = 6;
322/// Gas cost per word for copy operations.
323pub const COPY: u64 = 3;
324/// Gas cost for BLOCKHASH instruction.
325pub const BLOCKHASH: u64 = 20;
326/// Gas cost per byte for code deposit during contract creation.
327pub const CODEDEPOSIT: u64 = 200;
328
329/// EIP-1884: Repricing for trie-size-dependent opcodes
330pub const ISTANBUL_SLOAD_GAS: u64 = 800;
331/// Gas cost for SSTORE when setting a storage slot from zero to non-zero.
332pub const SSTORE_SET: u64 = 20000;
333/// Gas cost for SSTORE when modifying an existing non-zero storage slot.
334pub const SSTORE_RESET: u64 = 5000;
335/// Gas refund for SSTORE when clearing a storage slot (setting to zero).
336pub const REFUND_SSTORE_CLEARS: i64 = 15000;
337
338/// The standard cost of calldata token.
339pub const STANDARD_TOKEN_COST: u64 = 4;
340/// The cost of a non-zero byte in calldata.
341pub const NON_ZERO_BYTE_DATA_COST: u64 = 68;
342/// The multiplier for a non zero byte in calldata.
343pub const NON_ZERO_BYTE_MULTIPLIER: u64 = NON_ZERO_BYTE_DATA_COST / STANDARD_TOKEN_COST;
344/// The cost of a non-zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
345pub const NON_ZERO_BYTE_DATA_COST_ISTANBUL: u64 = 16;
346/// The multiplier for a non zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
347pub const NON_ZERO_BYTE_MULTIPLIER_ISTANBUL: u64 =
348    NON_ZERO_BYTE_DATA_COST_ISTANBUL / STANDARD_TOKEN_COST;
349/// The cost floor per token as defined by [EIP-7623](https://eips.ethereum.org/EIPS/eip-7623).
350pub const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10;
351
352/// Gas cost for EOF CREATE instruction.
353pub const EOF_CREATE_GAS: u64 = 32000;
354
355// Berlin EIP-2929/EIP-2930 constants
356/// Gas cost for accessing an address in the access list (EIP-2930).
357pub const ACCESS_LIST_ADDRESS: u64 = 2400;
358/// Gas cost for accessing a storage key in the access list (EIP-2930).
359pub const ACCESS_LIST_STORAGE_KEY: u64 = 1900;
360
361/// Gas cost for SLOAD when accessing a cold storage slot (EIP-2929).
362pub const COLD_SLOAD_COST: u64 = 2100;
363/// Gas cost for accessing a cold account (EIP-2929).
364pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600;
365/// Additional gas cost for accessing a cold account.
366pub const COLD_ACCOUNT_ACCESS_COST_ADDITIONAL: u64 =
367    COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST;
368/// Gas cost for reading from a warm storage slot (EIP-2929).
369pub const WARM_STORAGE_READ_COST: u64 = 100;
370/// Gas cost for SSTORE reset operation on a warm storage slot.
371pub const WARM_SSTORE_RESET: u64 = SSTORE_RESET - COLD_SLOAD_COST;
372
373/// EIP-3860 : Limit and meter initcode
374pub const INITCODE_WORD_COST: u64 = 2;
375
376/// Gas stipend provided to the recipient of a CALL with value transfer.
377pub const CALL_STIPEND: u64 = 2300;
378
379/// Init and floor gas from transaction
380#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
381#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
382pub struct InitialAndFloorGas {
383    /// Regular (non-state) portion of the initial intrinsic gas.
384    ///
385    /// Under EIP-8037, this is the part constrained by `TX_MAX_GAS_LIMIT`;
386    /// state gas uses its own reservoir and is not subject to that cap.
387    pub initial_regular_gas: u64,
388    /// State gas charged at the intrinsic phase, before the first frame is
389    /// entered.
390    ///
391    /// The state-dependent charges of the EIP-2780 runtime gas phase are not
392    /// included here: they are recorded directly on the transaction-level gas.
393    pub initial_state_gas: u64,
394    /// If transaction is a Call and Prague is enabled
395    /// floor_gas is at least amount of gas that is going to be spent.
396    pub floor_gas: u64,
397}
398
399impl InitialAndFloorGas {
400    /***** Constructors *****/
401
402    /// Create a new InitialAndFloorGas instance.
403    #[inline]
404    pub const fn new(initial_regular_gas: u64, floor_gas: u64) -> Self {
405        Self {
406            initial_regular_gas,
407            initial_state_gas: 0,
408            floor_gas,
409        }
410    }
411
412    /// Create a new InitialAndFloorGas instance with state gas tracking.
413    #[inline]
414    pub const fn new_with_state_gas(
415        initial_regular_gas: u64,
416        initial_state_gas: u64,
417        floor_gas: u64,
418    ) -> Self {
419        Self {
420            initial_regular_gas,
421            initial_state_gas,
422            floor_gas,
423        }
424    }
425
426    /***** Simple getters *****/
427
428    /// Regular (non-state) portion of the initial intrinsic gas.
429    ///
430    /// Under EIP-8037, this is the part constrained by `TX_MAX_GAS_LIMIT`;
431    /// state gas uses its own reservoir and is not subject to that cap.
432    #[inline]
433    pub const fn initial_regular_gas(&self) -> u64 {
434        self.initial_regular_gas
435    }
436
437    /// State gas charged before the first frame is entered.
438    #[inline]
439    pub const fn initial_state_gas_final(&self) -> u64 {
440        self.initial_state_gas
441    }
442
443    /// EIP-7623 floor gas.
444    #[inline]
445    pub const fn floor_gas(&self) -> u64 {
446        self.floor_gas
447    }
448
449    /// Total initial intrinsic gas: `initial_regular_gas + initial_state_gas`.
450    #[inline]
451    pub const fn initial_total_gas(&self) -> u64 {
452        self.initial_regular_gas + self.initial_state_gas_final()
453    }
454
455    /***** Simple setters *****/
456
457    /// Sets the `initial_regular_gas` field by mutable reference.
458    #[inline]
459    pub const fn set_initial_regular_gas(&mut self, initial_regular_gas: u64) {
460        self.initial_regular_gas = initial_regular_gas;
461    }
462
463    /// Sets the `initial_state_gas` field by mutable reference.
464    #[inline]
465    pub const fn set_initial_state_gas(&mut self, initial_state_gas: u64) {
466        self.initial_state_gas = initial_state_gas;
467    }
468
469    /// Sets the `floor_gas` field by mutable reference.
470    #[inline]
471    pub const fn set_floor_gas(&mut self, floor_gas: u64) {
472        self.floor_gas = floor_gas;
473    }
474
475    /***** Builder with_* methods *****/
476
477    /// Sets the `initial_regular_gas` field.
478    #[inline]
479    pub const fn with_initial_regular_gas(mut self, initial_regular_gas: u64) -> Self {
480        self.initial_regular_gas = initial_regular_gas;
481        self
482    }
483
484    /// Sets the `initial_state_gas` field.
485    #[inline]
486    pub const fn with_initial_state_gas(mut self, initial_state_gas: u64) -> Self {
487        self.initial_state_gas = initial_state_gas;
488        self
489    }
490
491    /// Sets the `floor_gas` field.
492    #[inline]
493    pub const fn with_floor_gas(mut self, floor_gas: u64) -> Self {
494        self.floor_gas = floor_gas;
495        self
496    }
497
498    /// Computes the regular gas budget and reservoir for the initial call frame.
499    ///
500    /// EIP-8037 reservoir model:
501    ///   execution_gas = tx.gas_limit - intrinsic_gas  (= gas_limit parameter)
502    ///   regular_gas_budget = min(execution_gas, TX_MAX_GAS_LIMIT - intrinsic_gas)
503    ///   reservoir = execution_gas - regular_gas_budget
504    ///
505    /// Initial state gas is then deducted from the reservoir (spilling into the
506    /// regular budget when the reservoir is insufficient).
507    ///
508    /// On mainnet (state gas disabled), reservoir = 0 and gas_limit is unchanged.
509    ///
510    /// All subtractions saturate at zero: callers normally guarantee
511    /// `tx_gas_limit >= initial_total_gas` via validation, but if that invariant
512    /// is violated the result clamps to `(0, 0)` instead of underflowing.
513    ///
514    /// Returns `(gas_limit, reservoir)`.
515    pub fn initial_gas_and_reservoir(
516        &self,
517        tx_gas_limit: u64,
518        tx_gas_limit_cap: u64,
519    ) -> (u64, u64) {
520        let execution_gas = tx_gas_limit.saturating_sub(self.initial_regular_gas());
521
522        // System calls pass InitialAndFloorGas with all zeros and should not be
523        // subject to the TX_MAX_GAS_LIMIT cap.
524        let tx_gas_limit_cap = if self.initial_total_gas() == 0 {
525            u64::MAX
526        } else {
527            tx_gas_limit_cap
528        };
529
530        let mut regular_gas_limit = core::cmp::min(tx_gas_limit, tx_gas_limit_cap)
531            .saturating_sub(self.initial_regular_gas());
532        let mut reservoir = execution_gas.saturating_sub(regular_gas_limit);
533
534        // Deduct initial state gas from the reservoir. When the reservoir is
535        // insufficient, the deficit is charged from the regular gas budget.
536        if reservoir >= self.initial_state_gas {
537            reservoir -= self.initial_state_gas;
538        } else {
539            regular_gas_limit =
540                regular_gas_limit.saturating_sub(self.initial_state_gas - reservoir);
541            reservoir = 0;
542        }
543
544        (regular_gas_limit, reservoir)
545    }
546}
547
548/// Initial gas that is deducted for transaction to be included.
549/// Initial gas contains initial stipend gas, gas for access list and input data.
550///
551/// # Returns
552///
553/// - Intrinsic gas
554/// - Number of tokens in calldata
555#[allow(clippy::too_many_arguments)]
556pub fn calculate_initial_tx_gas(
557    spec_id: SpecId,
558    input: &[u8],
559    is_create: bool,
560    access_list_accounts: u64,
561    access_list_storages: u64,
562    authorization_list_num: u64,
563    eip2780: Option<gas_params::Eip2780TxInfo>,
564) -> InitialAndFloorGas {
565    GasParams::new_spec(spec_id).initial_tx_gas(
566        input,
567        is_create,
568        access_list_accounts,
569        access_list_storages,
570        authorization_list_num,
571        eip2780,
572    )
573}
574
575/// Initial gas that is deducted for transaction to be included.
576/// Initial gas contains initial stipend gas, gas for access list and input data.
577///
578/// # Returns
579///
580/// - Intrinsic gas
581/// - Number of tokens in calldata
582pub fn calculate_initial_tx_gas_for_tx(
583    tx: impl Transaction,
584    spec: SpecId,
585    eip2780: Option<gas_params::Eip2780TxInfo>,
586) -> InitialAndFloorGas {
587    GasParams::new_spec(spec).initial_tx_gas_for_tx(tx, eip2780)
588}
589
590/// Retrieve the total number of tokens in calldata.
591#[inline]
592pub fn get_tokens_in_calldata_istanbul(input: &[u8]) -> u64 {
593    get_tokens_in_calldata(input, NON_ZERO_BYTE_MULTIPLIER_ISTANBUL)
594}
595
596/// Retrieve the total number of tokens in calldata.
597#[inline]
598pub fn get_tokens_in_calldata(input: &[u8], non_zero_data_multiplier: u64) -> u64 {
599    let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
600    let non_zero_data_len = input.len() as u64 - zero_data_len;
601    zero_data_len + non_zero_data_len * non_zero_data_multiplier
602}