revmc_builtins/
gas.rs

1//! Gas calculation utilities.
2
3use revm_interpreter::{SStoreResult, StateLoad};
4use revm_primitives::{hardfork::SpecId, U256};
5
6pub use revm_context_interface::journaled_state::AccountLoad;
7pub use revm_interpreter::gas::*;
8
9/// `const` Option `?`.
10#[allow(unused_macros)]
11macro_rules! tri {
12    ($e:expr) => {
13        match $e {
14            Some(v) => v,
15            None => return None,
16        }
17    };
18}
19
20/// Gas cost for SELFDESTRUCT operation.
21pub const SELFDESTRUCT: i64 = SELFDESTRUCT_REFUND;
22
23/// Minimum gas required for callee.
24pub const MIN_CALLEE_GAS: u64 = 2300;
25
26/// Calculate gas cost per word.
27#[inline]
28pub const fn cost_per_word(len: u64, per_word_cost: u64) -> Option<u64> {
29    let words = len.div_ceil(32);
30    words.checked_mul(per_word_cost)
31}
32
33/// Returns warm/cold cost based on is_cold flag.
34#[inline]
35pub const fn warm_cold_cost(is_cold: bool) -> u64 {
36    if is_cold {
37        COLD_ACCOUNT_ACCESS_COST
38    } else {
39        WARM_STORAGE_READ_COST
40    }
41}
42
43/// Returns warm/cold cost with delegation support.
44#[inline]
45pub fn warm_cold_cost_with_delegation(state: StateLoad<bool>) -> u64 {
46    if state.is_cold {
47        COLD_ACCOUNT_ACCESS_COST
48    } else {
49        WARM_STORAGE_READ_COST
50    }
51}
52
53/// Calculate EXTCODECOPY gas cost.
54#[inline]
55pub fn extcodecopy_cost(spec_id: SpecId, len: u64, is_cold: bool) -> Option<u64> {
56    let base = if spec_id.is_enabled_in(SpecId::BERLIN) {
57        warm_cold_cost(is_cold)
58    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
59        700
60    } else {
61        20
62    };
63    cost_per_word(len, COPY).map(|word_cost| base.saturating_add(word_cost))
64}
65
66/// Calculate SLOAD gas cost.
67#[inline]
68pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
69    if spec_id.is_enabled_in(SpecId::BERLIN) {
70        if is_cold {
71            COLD_SLOAD_COST
72        } else {
73            WARM_STORAGE_READ_COST
74        }
75    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
76        ISTANBUL_SLOAD_GAS
77    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
78        200
79    } else {
80        50
81    }
82}
83
84/// Calculate SSTORE gas cost.
85#[inline]
86pub fn sstore_cost(
87    spec_id: SpecId,
88    result: &SStoreResult,
89    remaining_gas: u64,
90    is_cold: bool,
91) -> Option<u64> {
92    if spec_id.is_enabled_in(SpecId::BERLIN) {
93        let base = if is_cold { COLD_SLOAD_COST } else { 0 };
94        let cost = if result.original_value == result.new_value {
95            WARM_STORAGE_READ_COST
96        } else if result.original_value == result.present_value {
97            if result.original_value.is_zero() {
98                SSTORE_SET
99            } else {
100                WARM_SSTORE_RESET
101            }
102        } else {
103            WARM_STORAGE_READ_COST
104        };
105        Some(base.saturating_add(cost))
106    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
107        let stipend = 2300;
108        if remaining_gas <= stipend {
109            return None;
110        }
111        let cost = if result.original_value == result.new_value {
112            ISTANBUL_SLOAD_GAS
113        } else if result.original_value == result.present_value {
114            if result.original_value.is_zero() {
115                SSTORE_SET
116            } else {
117                SSTORE_RESET
118            }
119        } else {
120            ISTANBUL_SLOAD_GAS
121        };
122        Some(cost)
123    } else {
124        let cost = if result.present_value.is_zero() && !result.new_value.is_zero() {
125            SSTORE_SET
126        } else {
127            SSTORE_RESET
128        };
129        Some(cost)
130    }
131}
132
133/// Calculate SSTORE refund.
134#[inline]
135pub fn sstore_refund(spec_id: SpecId, result: &SStoreResult) -> i64 {
136    if spec_id.is_enabled_in(SpecId::BERLIN) {
137        let mut refund = 0i64;
138        if result.original_value != result.present_value
139            && result.original_value == result.new_value
140        {
141            if result.original_value.is_zero() {
142                refund += (SSTORE_SET - WARM_STORAGE_READ_COST) as i64;
143            } else {
144                refund += (WARM_SSTORE_RESET - WARM_STORAGE_READ_COST) as i64;
145            }
146        }
147        if !result.present_value.is_zero() && result.new_value.is_zero() {
148            refund += REFUND_SSTORE_CLEARS;
149        }
150        if !result.original_value.is_zero()
151            && result.present_value.is_zero()
152            && result.new_value == result.original_value
153        {
154            refund -= REFUND_SSTORE_CLEARS;
155        }
156        refund
157    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
158        let mut refund = 0i64;
159        if result.original_value != result.present_value
160            && result.original_value == result.new_value
161        {
162            if result.original_value.is_zero() {
163                refund += (SSTORE_SET - ISTANBUL_SLOAD_GAS) as i64;
164            } else {
165                refund += (SSTORE_RESET - ISTANBUL_SLOAD_GAS) as i64;
166            }
167        }
168        if !result.present_value.is_zero() && result.new_value.is_zero() {
169            refund += REFUND_SSTORE_CLEARS;
170        }
171        if !result.original_value.is_zero()
172            && result.present_value.is_zero()
173            && result.new_value == result.original_value
174        {
175            refund -= REFUND_SSTORE_CLEARS;
176        }
177        refund
178    } else if !result.present_value.is_zero() && result.new_value.is_zero() {
179        REFUND_SSTORE_CLEARS
180    } else {
181        0
182    }
183}
184
185/// Calculate CALL gas cost.
186#[inline]
187pub fn call_cost(
188    spec_id: SpecId,
189    transfers_value: bool,
190    account_load: StateLoad<AccountLoad>,
191) -> u64 {
192    let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
193        warm_cold_cost(account_load.is_cold)
194            + if let Some(is_cold) = account_load.data.is_delegate_account_cold {
195                warm_cold_cost(is_cold)
196            } else {
197                0
198            }
199    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
200        700
201    } else {
202        40
203    };
204
205    if transfers_value {
206        gas += CALLVALUE;
207    }
208
209    if account_load.data.is_empty
210        && (transfers_value || !spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON))
211    {
212        gas += NEWACCOUNT;
213    }
214
215    gas
216}
217
218/// Calculate CREATE2 gas cost.
219#[inline]
220pub fn create2_cost(len: u64) -> Option<u64> {
221    cost_per_word(len, KECCAK256WORD).map(|x| CREATE.saturating_add(x))
222}
223
224/// Calculate initcode gas cost.
225#[inline]
226pub const fn initcode_cost(len: u64) -> u64 {
227    let words = len.div_ceil(32);
228    words.saturating_mul(INITCODE_WORD_COST)
229}
230
231// These are overridden to only account for the dynamic cost.
232
233/// `EXP` opcode cost calculation.
234#[inline]
235pub fn dyn_exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
236    #[inline]
237    const fn log2floor(value: U256) -> u64 {
238        let mut l: u64 = 256;
239        let mut i = 3;
240        loop {
241            if value.as_limbs()[i] == 0u64 {
242                l -= 64;
243            } else {
244                l -= value.as_limbs()[i].leading_zeros() as u64;
245                if l == 0 {
246                    return l;
247                } else {
248                    return l - 1;
249                }
250            }
251            if i == 0 {
252                break;
253            }
254            i -= 1;
255        }
256        l
257    }
258
259    if power == U256::ZERO {
260        Some(0)
261    } else {
262        // EIP-160: EXP cost increase
263        let gas_byte =
264            U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) { 50 } else { 10 });
265        let gas = gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?;
266        u64::try_from(gas).ok()
267    }
268}
269
270/// `LOG` opcode cost calculation.
271#[inline]
272pub const fn dyn_log_cost(len: u64) -> Option<u64> {
273    LOGDATA.checked_mul(len)
274}
275
276/// `KECCAK256` opcode cost calculation.
277#[inline]
278pub const fn dyn_keccak256_cost(len: u64) -> Option<u64> {
279    cost_per_word(len, KECCAK256WORD)
280}
281
282/// `*COPY` opcodes cost calculation.
283#[inline]
284pub const fn dyn_verylowcopy_cost(len: u64) -> Option<u64> {
285    cost_per_word(len, COPY)
286}
287
288/// `EXP` opcode gas cost (base + dynamic).
289#[inline]
290pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
291    dyn_exp_cost(spec_id, power).map(|dyn_cost| EXP.saturating_add(dyn_cost))
292}
293
294/// `LOG` opcode gas cost (base + topics + dynamic).
295#[inline]
296pub const fn log_cost(n_topics: u8, len: u64) -> Option<u64> {
297    let base = LOG + (LOGTOPIC * n_topics as u64);
298    match dyn_log_cost(len) {
299        Some(dyn_cost) => Some(base.saturating_add(dyn_cost)),
300        None => None,
301    }
302}
303
304/// `KECCAK256` opcode gas cost (base + dynamic).
305#[inline]
306pub const fn keccak256_cost(len: u64) -> Option<u64> {
307    match dyn_keccak256_cost(len) {
308        Some(dyn_cost) => Some(KECCAK256.saturating_add(dyn_cost)),
309        None => None,
310    }
311}
312
313/// `CALLDATACOPY`, `CODECOPY`, `RETURNDATACOPY` opcode gas cost (base + dynamic).
314#[inline]
315pub const fn verylowcopy_cost(len: u64) -> Option<u64> {
316    match dyn_verylowcopy_cost(len) {
317        Some(dyn_cost) => Some(VERYLOW.saturating_add(dyn_cost)),
318        None => None,
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn exp_cost() {
328        for (spec_id, power) in [
329            (SpecId::CANCUN, U256::from(0)),
330            (SpecId::CANCUN, U256::from(1)),
331            (SpecId::CANCUN, U256::from(69)),
332        ] {
333            assert_eq!(
334                super::exp_cost(spec_id, power).unwrap(),
335                EXP + dyn_exp_cost(spec_id, power).unwrap(),
336            );
337        }
338    }
339
340    #[test]
341    fn log_cost() {
342        for n_topics in [0, 1, 2] {
343            for len in [0, 1, 69] {
344                assert_eq!(
345                    super::log_cost(n_topics, len).unwrap(),
346                    LOG + (LOGTOPIC * n_topics as u64) + dyn_log_cost(len).unwrap(),
347                );
348            }
349        }
350    }
351
352    #[test]
353    fn keccak256_cost() {
354        for len in [0, 1, 69] {
355            assert_eq!(
356                super::keccak256_cost(len).unwrap(),
357                KECCAK256 + dyn_keccak256_cost(len).unwrap(),
358            );
359        }
360    }
361
362    #[test]
363    fn verylowcopy_cost() {
364        for len in [0, 1, 69] {
365            assert_eq!(
366                super::verylowcopy_cost(len).unwrap(),
367                VERYLOW + dyn_verylowcopy_cost(len).unwrap(),
368            );
369        }
370    }
371}