revm_interpreter/
gas.rs

1//! EVM gas calculation utilities.
2
3mod calc;
4mod constants;
5pub mod params;
6
7pub use calc::*;
8pub use constants::*;
9
10/// Represents the state of gas during execution.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct Gas {
14    /// The initial gas limit. This is constant throughout execution.
15    limit: u64,
16    /// The remaining gas.
17    remaining: u64,
18    /// Refunded gas. This is used only at the end of execution.
19    refunded: i64,
20    /// Memoisation of values for memory expansion cost.
21    memory: MemoryGas,
22}
23
24impl Gas {
25    /// Creates a new `Gas` struct with the given gas limit.
26    #[inline]
27    pub const fn new(limit: u64) -> Self {
28        Self {
29            limit,
30            remaining: limit,
31            refunded: 0,
32            memory: MemoryGas::new(),
33        }
34    }
35
36    /// Creates a new `Gas` struct with the given gas limit, but without any gas remaining.
37    #[inline]
38    pub const fn new_spent(limit: u64) -> Self {
39        Self {
40            limit,
41            remaining: 0,
42            refunded: 0,
43            memory: MemoryGas::new(),
44        }
45    }
46
47    /// Returns the gas limit.
48    #[inline]
49    pub const fn limit(&self) -> u64 {
50        self.limit
51    }
52
53    /// Returns the memory gas.
54    #[inline]
55    pub fn memory(&self) -> &MemoryGas {
56        &self.memory
57    }
58
59    /// Returns the memory gas.
60    #[inline]
61    pub fn memory_mut(&mut self) -> &mut MemoryGas {
62        &mut self.memory
63    }
64
65    /// Returns the total amount of gas that was refunded.
66    #[inline]
67    pub const fn refunded(&self) -> i64 {
68        self.refunded
69    }
70
71    /// Returns the total amount of gas spent.
72    #[inline]
73    pub const fn spent(&self) -> u64 {
74        self.limit - self.remaining
75    }
76
77    /// Returns the final amount of gas used by subtracting the refund from spent gas.
78    #[inline]
79    pub const fn used(&self) -> u64 {
80        self.spent().saturating_sub(self.refunded() as u64)
81    }
82
83    /// Returns the total amount of gas spent, minus the refunded gas.
84    #[inline]
85    pub const fn spent_sub_refunded(&self) -> u64 {
86        self.spent().saturating_sub(self.refunded as u64)
87    }
88
89    /// Returns the amount of gas remaining.
90    #[inline]
91    pub const fn remaining(&self) -> u64 {
92        self.remaining
93    }
94
95    /// Erases a gas cost from the totals.
96    #[inline]
97    pub fn erase_cost(&mut self, returned: u64) {
98        self.remaining += returned;
99    }
100
101    /// Spends all remaining gas.
102    #[inline]
103    pub fn spend_all(&mut self) {
104        self.remaining = 0;
105    }
106
107    /// Records a refund value.
108    ///
109    /// `refund` can be negative but `self.refunded` should always be positive
110    /// at the end of transact.
111    #[inline]
112    pub fn record_refund(&mut self, refund: i64) {
113        self.refunded += refund;
114    }
115
116    /// Set a refund value for final refund.
117    ///
118    /// Max refund value is limited to Nth part (depending of fork) of gas spend.
119    ///
120    /// Related to EIP-3529: Reduction in refunds
121    #[inline]
122    pub fn set_final_refund(&mut self, is_london: bool) {
123        let max_refund_quotient = if is_london { 5 } else { 2 };
124        self.refunded = (self.refunded() as u64).min(self.spent() / max_refund_quotient) as i64;
125    }
126
127    /// Set a refund value. This overrides the current refund value.
128    #[inline]
129    pub fn set_refund(&mut self, refund: i64) {
130        self.refunded = refund;
131    }
132
133    /// Set a spent value. This overrides the current spent value.
134    #[inline]
135    pub fn set_spent(&mut self, spent: u64) {
136        self.remaining = self.limit.saturating_sub(spent);
137    }
138
139    /// Records an explicit cost.
140    ///
141    /// Returns `false` if the gas limit is exceeded.
142    #[inline]
143    #[must_use = "prefer using `gas!` instead to return an out-of-gas error on failure"]
144    pub fn record_cost(&mut self, cost: u64) -> bool {
145        if let Some(new_remaining) = self.remaining.checked_sub(cost) {
146            self.remaining = new_remaining;
147            return true;
148        }
149        false
150    }
151
152    /// Records an explicit cost. In case of underflow the gas will wrap around cost.
153    ///
154    /// Returns `true` if the gas limit is exceeded.
155    #[inline(always)]
156    #[must_use = "In case of not enough gas, the interpreter should halt with an out-of-gas error"]
157    pub fn record_cost_unsafe(&mut self, cost: u64) -> bool {
158        let oog = self.remaining < cost;
159        self.remaining = self.remaining.wrapping_sub(cost);
160        oog
161    }
162}
163
164/// Result of attempting to extend memory during execution.
165#[derive(Debug)]
166pub enum MemoryExtensionResult {
167    /// Memory was extended.
168    Extended,
169    /// Memory size stayed the same.
170    Same,
171    /// Not enough gas to extend memory.
172    OutOfGas,
173}
174
175/// Utility struct that speeds up calculation of memory expansion
176/// It contains the current memory length and its memory expansion cost.
177///
178/// It allows us to split gas accounting from memory structure.
179#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub struct MemoryGas {
182    /// Current memory length
183    pub words_num: usize,
184    /// Current memory expansion cost
185    pub expansion_cost: u64,
186}
187
188impl MemoryGas {
189    /// Creates a new `MemoryGas` instance with zero memory allocation.
190    #[inline]
191    pub const fn new() -> Self {
192        Self {
193            words_num: 0,
194            expansion_cost: 0,
195        }
196    }
197
198    /// Sets the number of words and the expansion cost.
199    ///
200    /// Returns the difference between the new and old expansion cost.
201    #[inline]
202    pub fn set_words_num(&mut self, words_num: usize, mut expansion_cost: u64) -> Option<u64> {
203        self.words_num = words_num;
204        core::mem::swap(&mut self.expansion_cost, &mut expansion_cost);
205        self.expansion_cost.checked_sub(expansion_cost)
206    }
207
208    /// Records a new memory length and calculates additional cost if memory is expanded.
209    /// Returns the additional gas cost required, or None if no expansion is needed.
210    #[inline]
211    pub fn record_new_len(
212        &mut self,
213        new_num: usize,
214        linear_cost: u64,
215        quadratic_cost: u64,
216    ) -> Option<u64> {
217        if new_num <= self.words_num {
218            return None;
219        }
220        self.words_num = new_num;
221        let mut cost = crate::gas::calc::memory_gas(new_num, linear_cost, quadratic_cost);
222        core::mem::swap(&mut self.expansion_cost, &mut cost);
223        // Safe to subtract because we know that new_len > length
224        // Notice the swap above.
225        Some(self.expansion_cost - cost)
226    }
227}