revm_interpreter/gas/
calc.rs

1use revm_primitives::eip7702;
2
3use super::constants::*;
4use crate::{
5    num_words,
6    primitives::{AccessListItem, SpecId, U256},
7    AccountLoad, Eip7702CodeLoad, SStoreResult, SelfDestructResult, StateLoad,
8};
9
10/// `const` Option `?`.
11macro_rules! tri {
12    ($e:expr) => {
13        match $e {
14            Some(v) => v,
15            None => return None,
16        }
17    };
18}
19
20/// `SSTORE` opcode refund calculation.
21#[allow(clippy::collapsible_else_if)]
22#[inline]
23pub fn sstore_refund(spec_id: SpecId, vals: &SStoreResult) -> i64 {
24    if spec_id.is_enabled_in(SpecId::ISTANBUL) {
25        // EIP-3529: Reduction in refunds
26        let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
27            (SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
28        } else {
29            REFUND_SSTORE_CLEARS
30        };
31        if vals.is_new_eq_present() {
32            0
33        } else {
34            if vals.is_original_eq_present() && vals.is_new_zero() {
35                sstore_clears_schedule
36            } else {
37                let mut refund = 0;
38
39                if !vals.is_original_zero() {
40                    if vals.is_present_zero() {
41                        refund -= sstore_clears_schedule;
42                    } else if vals.is_new_zero() {
43                        refund += sstore_clears_schedule;
44                    }
45                }
46
47                if vals.is_original_eq_new() {
48                    let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
49                        (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
50                    } else {
51                        (SSTORE_RESET, sload_cost(spec_id, false))
52                    };
53                    if vals.is_original_zero() {
54                        refund += (SSTORE_SET - gas_sload) as i64;
55                    } else {
56                        refund += (gas_sstore_reset - gas_sload) as i64;
57                    }
58                }
59
60                refund
61            }
62        }
63    } else {
64        if !vals.is_present_zero() && vals.is_new_zero() {
65            REFUND_SSTORE_CLEARS
66        } else {
67            0
68        }
69    }
70}
71
72/// `CREATE2` opcode cost calculation.
73#[inline]
74pub const fn create2_cost(len: u64) -> Option<u64> {
75    CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
76}
77
78#[inline]
79const fn log2floor(value: U256) -> u64 {
80    let mut l: u64 = 256;
81    let mut i = 3;
82    loop {
83        if value.as_limbs()[i] == 0u64 {
84            l -= 64;
85        } else {
86            l -= value.as_limbs()[i].leading_zeros() as u64;
87            if l == 0 {
88                return l;
89            } else {
90                return l - 1;
91            }
92        }
93        if i == 0 {
94            break;
95        }
96        i -= 1;
97    }
98    l
99}
100
101/// `EXP` opcode cost calculation.
102#[inline]
103pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
104    if power.is_zero() {
105        Some(EXP)
106    } else {
107        // EIP-160: EXP cost increase
108        let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
109            50
110        } else {
111            10
112        });
113        let gas = U256::from(EXP)
114            .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?;
115
116        u64::try_from(gas).ok()
117    }
118}
119
120/// `*COPY` opcodes cost calculation.
121#[inline]
122pub const fn verylowcopy_cost(len: u64) -> Option<u64> {
123    VERYLOW.checked_add(tri!(cost_per_word(len, COPY)))
124}
125
126/// `EXTCODECOPY` opcode cost calculation.
127#[inline]
128pub const fn extcodecopy_cost(spec_id: SpecId, len: u64, load: Eip7702CodeLoad<()>) -> Option<u64> {
129    let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
130        warm_cold_cost_with_delegation(load)
131    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
132        700
133    } else {
134        20
135    };
136    base_gas.checked_add(tri!(cost_per_word(len, COPY)))
137}
138
139/// `LOG` opcode cost calculation.
140#[inline]
141pub const fn log_cost(n: u8, len: u64) -> Option<u64> {
142    tri!(LOG.checked_add(tri!(LOGDATA.checked_mul(len)))).checked_add(LOGTOPIC * n as u64)
143}
144
145/// `KECCAK256` opcode cost calculation.
146#[inline]
147pub const fn keccak256_cost(len: u64) -> Option<u64> {
148    KECCAK256.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
149}
150
151/// Calculate the cost of buffer per word.
152#[inline]
153pub const fn cost_per_word(len: u64, multiple: u64) -> Option<u64> {
154    multiple.checked_mul(num_words(len))
155}
156
157/// EIP-3860: Limit and meter initcode
158///
159/// Apply extra gas cost of 2 for every 32-byte chunk of initcode.
160///
161/// This cannot overflow as the initcode length is assumed to be checked.
162#[inline]
163pub const fn initcode_cost(len: u64) -> u64 {
164    let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
165        panic!("initcode cost overflow")
166    };
167    cost
168}
169
170/// `SLOAD` opcode cost calculation.
171#[inline]
172pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
173    if spec_id.is_enabled_in(SpecId::BERLIN) {
174        if is_cold {
175            COLD_SLOAD_COST
176        } else {
177            WARM_STORAGE_READ_COST
178        }
179    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
180        // EIP-1884: Repricing for trie-size-dependent opcodes
181        INSTANBUL_SLOAD_GAS
182    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
183        // EIP-150: Gas cost changes for IO-heavy operations
184        200
185    } else {
186        50
187    }
188}
189
190/// `SSTORE` opcode cost calculation.
191#[inline]
192pub fn sstore_cost(spec_id: SpecId, vals: &SStoreResult, gas: u64, is_cold: bool) -> Option<u64> {
193    // EIP-1706 Disable SSTORE with gasleft lower than call stipend
194    if spec_id.is_enabled_in(SpecId::ISTANBUL) && gas <= CALL_STIPEND {
195        return None;
196    }
197
198    if spec_id.is_enabled_in(SpecId::BERLIN) {
199        // Berlin specification logic
200        let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(vals);
201
202        if is_cold {
203            gas_cost += COLD_SLOAD_COST;
204        }
205        Some(gas_cost)
206    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
207        // Istanbul logic
208        Some(istanbul_sstore_cost::<INSTANBUL_SLOAD_GAS, SSTORE_RESET>(
209            vals,
210        ))
211    } else {
212        // Frontier logic
213        Some(frontier_sstore_cost(vals))
214    }
215}
216
217/// EIP-2200: Structured Definitions for Net Gas Metering
218#[inline]
219fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
220    vals: &SStoreResult,
221) -> u64 {
222    if vals.is_new_eq_present() {
223        SLOAD_GAS
224    } else if vals.is_original_eq_present() && vals.is_original_zero() {
225        SSTORE_SET
226    } else if vals.is_original_eq_present() {
227        SSTORE_RESET_GAS
228    } else {
229        SLOAD_GAS
230    }
231}
232
233/// Frontier sstore cost just had two cases set and reset values.
234#[inline]
235fn frontier_sstore_cost(vals: &SStoreResult) -> u64 {
236    if vals.is_present_zero() && !vals.is_new_zero() {
237        SSTORE_SET
238    } else {
239        SSTORE_RESET
240    }
241}
242
243/// `SELFDESTRUCT` opcode cost calculation.
244#[inline]
245pub const fn selfdestruct_cost(spec_id: SpecId, res: StateLoad<SelfDestructResult>) -> u64 {
246    // EIP-161: State trie clearing (invariant-preserving alternative)
247    let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
248        res.data.had_value && !res.data.target_exists
249    } else {
250        !res.data.target_exists
251    };
252
253    // EIP-150: Gas cost changes for IO-heavy operations
254    let selfdestruct_gas_topup = if spec_id.is_enabled_in(SpecId::TANGERINE) && should_charge_topup
255    {
256        25000
257    } else {
258        0
259    };
260
261    // EIP-150: Gas cost changes for IO-heavy operations
262    let selfdestruct_gas = if spec_id.is_enabled_in(SpecId::TANGERINE) {
263        5000
264    } else {
265        0
266    };
267
268    let mut gas = selfdestruct_gas + selfdestruct_gas_topup;
269    if spec_id.is_enabled_in(SpecId::BERLIN) && res.is_cold {
270        gas += COLD_ACCOUNT_ACCESS_COST
271    }
272    gas
273}
274
275/// Calculate call gas cost for the call instruction.
276///
277/// There is three types of gas.
278/// * Account access gas. after berlin it can be cold or warm.
279/// * Transfer value gas. If value is transferred and balance of target account is updated.
280/// * If account is not existing and needs to be created. After Spurious dragon
281///   this is only accounted if value is transferred.
282///
283/// account_load.is_empty will be accounted only if hardfork is SPURIOUS_DRAGON and
284/// there is transfer value.
285///
286/// This means that [`crate::OpCode::EXTSTATICCALL`],
287/// [`crate::OpCode::EXTDELEGATECALL`] that dont transfer value will not be
288/// effected by this field.
289///
290/// [`crate::OpCode::CALL`], [`crate::OpCode::EXTCALL`] use this field.
291///
292/// While [`crate::OpCode::STATICCALL`], [`crate::OpCode::DELEGATECALL`],
293/// [`crate::OpCode::CALLCODE`] need to have this field hardcoded to false
294/// as they were present before SPURIOUS_DRAGON hardfork.
295#[inline]
296pub const fn call_cost(spec_id: SpecId, transfers_value: bool, account_load: AccountLoad) -> u64 {
297    // Account access.
298    let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
299        warm_cold_cost_with_delegation(account_load.load)
300    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
301        // EIP-150: Gas cost changes for IO-heavy operations
302        700
303    } else {
304        40
305    };
306
307    // transfer value cost
308    if transfers_value {
309        gas += CALLVALUE;
310    }
311
312    // new account cost
313    if account_load.is_empty {
314        // EIP-161: State trie clearing (invariant-preserving alternative)
315        if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
316            // account only if there is value transferred.
317            if transfers_value {
318                gas += NEWACCOUNT;
319            }
320        } else {
321            gas += NEWACCOUNT;
322        }
323    }
324
325    gas
326}
327
328/// Berlin warm and cold storage access cost for account access.
329#[inline]
330pub const fn warm_cold_cost(is_cold: bool) -> u64 {
331    if is_cold {
332        COLD_ACCOUNT_ACCESS_COST
333    } else {
334        WARM_STORAGE_READ_COST
335    }
336}
337
338/// Berlin warm and cold storage access cost for account access.
339///
340/// If delegation is Some, add additional cost for delegation account load.
341#[inline]
342pub const fn warm_cold_cost_with_delegation(load: Eip7702CodeLoad<()>) -> u64 {
343    let mut gas = warm_cold_cost(load.state_load.is_cold);
344    if let Some(is_cold) = load.is_delegate_account_cold {
345        gas += warm_cold_cost(is_cold);
346    }
347    gas
348}
349
350/// Memory expansion cost calculation for a given memory length.
351#[inline]
352pub const fn memory_gas_for_len(len: usize) -> u64 {
353    memory_gas(crate::interpreter::num_words(len as u64))
354}
355
356/// Memory expansion cost calculation for a given number of words.
357#[inline]
358pub const fn memory_gas(num_words: u64) -> u64 {
359    MEMORY
360        .saturating_mul(num_words)
361        .saturating_add(num_words.saturating_mul(num_words) / 512)
362}
363
364/// Initial gas that is deducted for transaction to be included.
365/// Initial gas contains initial stipend gas, gas for access list and input data.
366pub fn validate_initial_tx_gas(
367    spec_id: SpecId,
368    input: &[u8],
369    is_create: bool,
370    access_list: &[AccessListItem],
371    authorization_list_num: u64,
372) -> u64 {
373    let mut initial_gas = 0;
374    let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
375    let non_zero_data_len = input.len() as u64 - zero_data_len;
376
377    // initdate stipend
378    initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
379    // EIP-2028: Transaction data gas cost reduction
380    initial_gas += non_zero_data_len
381        * if spec_id.is_enabled_in(SpecId::ISTANBUL) {
382            16
383        } else {
384            68
385        };
386
387    // get number of access list account and storages.
388    if spec_id.is_enabled_in(SpecId::BERLIN) {
389        let accessed_slots: usize = access_list.iter().map(|item| item.storage_keys.len()).sum();
390        initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
391        initial_gas += accessed_slots as u64 * ACCESS_LIST_STORAGE_KEY;
392    }
393
394    // base stipend
395    initial_gas += if is_create {
396        if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
397            // EIP-2: Homestead Hard-fork Changes
398            53000
399        } else {
400            21000
401        }
402    } else {
403        21000
404    };
405
406    // EIP-3860: Limit and meter initcode
407    // Init code stipend for bytecode analysis
408    if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
409        initial_gas += initcode_cost(input.len() as u64)
410    }
411
412    //   EIP-7702
413    if spec_id.is_enabled_in(SpecId::PRAGUE) {
414        initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
415    }
416
417    initial_gas
418}