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}