revm_interpreter/
gas.rs

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