revm_interpreter/gas/
calc.rs

1use super::constants::*;
2use crate::{num_words, tri, SStoreResult, SelfDestructResult, StateLoad};
3use context_interface::{
4    journaled_state::AccountLoad, transaction::AccessListItemTr as _, Transaction, TransactionType,
5};
6use primitives::{eip7702, hardfork::SpecId, U256};
7
8/// `SSTORE` opcode refund calculation.
9#[allow(clippy::collapsible_else_if)]
10#[inline]
11pub fn sstore_refund(spec_id: SpecId, vals: &SStoreResult) -> i64 {
12    if spec_id.is_enabled_in(SpecId::ISTANBUL) {
13        // EIP-3529: Reduction in refunds
14        let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
15            (SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
16        } else {
17            REFUND_SSTORE_CLEARS
18        };
19        if vals.is_new_eq_present() {
20            0
21        } else {
22            if vals.is_original_eq_present() && vals.is_new_zero() {
23                sstore_clears_schedule
24            } else {
25                let mut refund = 0;
26
27                if !vals.is_original_zero() {
28                    if vals.is_present_zero() {
29                        refund -= sstore_clears_schedule;
30                    } else if vals.is_new_zero() {
31                        refund += sstore_clears_schedule;
32                    }
33                }
34
35                if vals.is_original_eq_new() {
36                    let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
37                        (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
38                    } else {
39                        (SSTORE_RESET, sload_cost(spec_id, false))
40                    };
41                    if vals.is_original_zero() {
42                        refund += (SSTORE_SET - gas_sload) as i64;
43                    } else {
44                        refund += (gas_sstore_reset - gas_sload) as i64;
45                    }
46                }
47
48                refund
49            }
50        }
51    } else {
52        if !vals.is_present_zero() && vals.is_new_zero() {
53            REFUND_SSTORE_CLEARS
54        } else {
55            0
56        }
57    }
58}
59
60/// `CREATE2` opcode cost calculation.
61#[inline]
62pub const fn create2_cost(len: usize) -> Option<u64> {
63    CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
64}
65
66#[inline]
67const fn log2floor(value: U256) -> u64 {
68    let mut l: u64 = 256;
69    let mut i = 3;
70    loop {
71        if value.as_limbs()[i] == 0u64 {
72            l -= 64;
73        } else {
74            l -= value.as_limbs()[i].leading_zeros() as u64;
75            if l == 0 {
76                return l;
77            } else {
78                return l - 1;
79            }
80        }
81        if i == 0 {
82            break;
83        }
84        i -= 1;
85    }
86    l
87}
88
89/// `EXP` opcode cost calculation.
90#[inline]
91pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
92    if power.is_zero() {
93        Some(EXP)
94    } else {
95        // EIP-160: EXP cost increase
96        let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
97            50
98        } else {
99            10
100        });
101        let gas = U256::from(EXP)
102            .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?;
103
104        u64::try_from(gas).ok()
105    }
106}
107
108/// `*COPY` opcodes cost calculation.
109#[inline]
110pub const fn copy_cost_verylow(len: usize) -> Option<u64> {
111    copy_cost(VERYLOW, len)
112}
113
114/// `EXTCODECOPY` opcode cost calculation.
115#[inline]
116pub const fn extcodecopy_cost(spec_id: SpecId, len: usize, is_cold: bool) -> Option<u64> {
117    let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
118        warm_cold_cost(is_cold)
119    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
120        700
121    } else {
122        20
123    };
124    copy_cost(base_gas, len)
125}
126
127#[inline]
128/// Calculates the gas cost for copy operations based on data length.
129pub const fn copy_cost(base_cost: u64, len: usize) -> Option<u64> {
130    base_cost.checked_add(tri!(cost_per_word(len, COPY)))
131}
132
133/// `LOG` opcode cost calculation.
134#[inline]
135pub const fn log_cost(n: u8, len: u64) -> Option<u64> {
136    tri!(LOG.checked_add(tri!(LOGDATA.checked_mul(len)))).checked_add(LOGTOPIC * n as u64)
137}
138
139/// `KECCAK256` opcode cost calculation.
140#[inline]
141pub const fn keccak256_cost(len: usize) -> Option<u64> {
142    KECCAK256.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
143}
144
145/// Calculate the cost of buffer per word.
146#[inline]
147pub const fn cost_per_word(len: usize, multiple: u64) -> Option<u64> {
148    multiple.checked_mul(num_words(len) as u64)
149}
150
151/// EIP-3860: Limit and meter initcode
152///
153/// Apply extra gas cost of 2 for every 32-byte chunk of initcode.
154///
155/// This cannot overflow as the initcode length is assumed to be checked.
156#[inline]
157pub const fn initcode_cost(len: usize) -> u64 {
158    let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
159        panic!("initcode cost overflow")
160    };
161    cost
162}
163
164/// `SLOAD` opcode cost calculation.
165#[inline]
166pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
167    if spec_id.is_enabled_in(SpecId::BERLIN) {
168        if is_cold {
169            COLD_SLOAD_COST
170        } else {
171            WARM_STORAGE_READ_COST
172        }
173    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
174        // EIP-1884: Repricing for trie-size-dependent opcodes
175        ISTANBUL_SLOAD_GAS
176    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
177        // EIP-150: Gas cost changes for IO-heavy operations
178        200
179    } else {
180        50
181    }
182}
183
184/// Static gas cost for sstore.
185#[inline]
186pub const fn sstore_cost_static(spec_id: SpecId) -> u64 {
187    if spec_id.is_enabled_in(SpecId::BERLIN) {
188        WARM_STORAGE_READ_COST
189    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
190        ISTANBUL_SLOAD_GAS
191    } else {
192        SSTORE_RESET
193    }
194}
195
196/// Dynamic gas cost for sstore.
197#[inline]
198pub const fn sstore_cost_dynamic(spec_id: SpecId, vals: &SStoreResult, is_cold: bool) -> u64 {
199    sstore_cost(spec_id, vals, is_cold) - sstore_cost_static(spec_id)
200}
201
202/// Static gas cost for sstore.
203#[inline]
204pub const fn static_sstore_cost(spec_id: SpecId) -> u64 {
205    if spec_id.is_enabled_in(SpecId::BERLIN) {
206        WARM_STORAGE_READ_COST
207    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
208        ISTANBUL_SLOAD_GAS
209    } else {
210        SSTORE_RESET
211    }
212}
213
214/// Dynamic gas cost for sstore.
215#[inline]
216pub const fn dyn_sstore_cost(spec_id: SpecId, vals: &SStoreResult, is_cold: bool) -> u64 {
217    sstore_cost(spec_id, vals, is_cold) - static_sstore_cost(spec_id)
218}
219
220/// `SSTORE` opcode cost calculation.
221#[inline]
222pub const fn sstore_cost(spec_id: SpecId, vals: &SStoreResult, is_cold: bool) -> u64 {
223    if spec_id.is_enabled_in(SpecId::BERLIN) {
224        // Berlin specification logic
225        let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(vals);
226
227        if is_cold {
228            gas_cost += COLD_SLOAD_COST;
229        }
230        gas_cost
231    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
232        // Istanbul logic
233        istanbul_sstore_cost::<ISTANBUL_SLOAD_GAS, SSTORE_RESET>(vals)
234    } else {
235        // Frontier logic
236        frontier_sstore_cost(vals)
237    }
238}
239
240/// EIP-2200: Structured Definitions for Net Gas Metering
241#[inline]
242const fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
243    vals: &SStoreResult,
244) -> u64 {
245    if vals.is_new_eq_present() {
246        SLOAD_GAS
247    } else if vals.is_original_eq_present() && vals.is_original_zero() {
248        SSTORE_SET
249    } else if vals.is_original_eq_present() {
250        SSTORE_RESET_GAS
251    } else {
252        SLOAD_GAS
253    }
254}
255
256/// Frontier sstore cost just had two cases set and reset values.
257#[inline]
258const fn frontier_sstore_cost(vals: &SStoreResult) -> u64 {
259    if vals.is_present_zero() && !vals.is_new_zero() {
260        SSTORE_SET
261    } else {
262        SSTORE_RESET
263    }
264}
265
266/// Static gas cost for selfdestruct.
267#[inline]
268pub const fn static_selfdestruct_cost(spec_id: SpecId) -> u64 {
269    // EIP-150: Gas cost changes for IO-heavy operations
270    if spec_id.is_enabled_in(SpecId::TANGERINE) {
271        5000
272    } else {
273        0
274    }
275}
276
277/// `SELFDESTRUCT` opcode cost calculation.
278#[inline]
279pub const fn dyn_selfdestruct_cost(spec_id: SpecId, res: &StateLoad<SelfDestructResult>) -> u64 {
280    let is_tangerine = spec_id.is_enabled_in(SpecId::TANGERINE);
281    let mut gas = 0;
282
283    // EIP-161: State trie clearing (invariant-preserving alternative)
284    let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
285        res.data.had_value && !res.data.target_exists
286    } else {
287        !res.data.target_exists
288    };
289
290    // EIP-150: Gas cost changes for IO-heavy operations
291    if is_tangerine && should_charge_topup {
292        gas += NEWACCOUNT
293    }
294
295    if spec_id.is_enabled_in(SpecId::BERLIN) && res.is_cold {
296        gas += COLD_ACCOUNT_ACCESS_COST
297    }
298    gas
299}
300
301/// `SELFDESTRUCT` opcode cost calculation.
302#[inline]
303pub const fn selfdestruct_cost(spec_id: SpecId, res: StateLoad<SelfDestructResult>) -> u64 {
304    static_selfdestruct_cost(spec_id) + dyn_selfdestruct_cost(spec_id, &res)
305}
306
307/// Calculate static gas for the call
308///
309/// Gas depends on:
310/// * Spec. For berlin hardfork only warm gas [`WARM_STORAGE_READ_COST`] is calculated.
311/// * If there is transfer value. additional gas of [`CALLVALUE`] is added.
312#[inline]
313pub fn calc_call_static_gas(spec_id: SpecId, has_transfer: bool) -> u64 {
314    // Account access.
315    let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
316        WARM_STORAGE_READ_COST
317    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
318        // EIP-150: Gas cost changes for IO-heavy operations
319        700
320    } else {
321        40
322    };
323
324    // Transfer value cost
325    if has_transfer {
326        gas += CALLVALUE;
327    }
328
329    gas
330}
331
332/// Berlin warm and cold storage access cost for account access.
333#[inline]
334pub const fn warm_cold_cost(is_cold: bool) -> u64 {
335    if is_cold {
336        COLD_ACCOUNT_ACCESS_COST
337    } else {
338        WARM_STORAGE_READ_COST
339    }
340}
341
342/// Berlin warm and cold storage access cost for account access.
343///
344/// If delegation is Some, add additional cost for delegation account load.
345#[inline]
346pub const fn warm_cold_cost_with_delegation(load: StateLoad<AccountLoad>) -> u64 {
347    let mut gas = warm_cold_cost(load.is_cold);
348    if let Some(is_cold) = load.data.is_delegate_account_cold {
349        gas += warm_cold_cost(is_cold);
350    }
351    gas
352}
353
354/// Memory expansion cost calculation for a given number of words.
355#[inline]
356pub const fn memory_gas(num_words: usize) -> u64 {
357    let num_words = num_words as u64;
358    MEMORY
359        .saturating_mul(num_words)
360        .saturating_add(num_words.saturating_mul(num_words) / 512)
361}
362
363/// Init and floor gas from transaction
364#[derive(Clone, Copy, Debug, Default)]
365pub struct InitialAndFloorGas {
366    /// Initial gas for transaction.
367    pub initial_gas: u64,
368    /// If transaction is a Call and Prague is enabled
369    /// floor_gas is at least amount of gas that is going to be spent.
370    pub floor_gas: u64,
371}
372
373impl InitialAndFloorGas {
374    /// Create a new InitialAndFloorGas instance.
375    #[inline]
376    pub const fn new(initial_gas: u64, floor_gas: u64) -> Self {
377        Self {
378            initial_gas,
379            floor_gas,
380        }
381    }
382}
383
384/// Initial gas that is deducted for transaction to be included.
385/// Initial gas contains initial stipend gas, gas for access list and input data.
386///
387/// # Returns
388///
389/// - Intrinsic gas
390/// - Number of tokens in calldata
391pub fn calculate_initial_tx_gas(
392    spec_id: SpecId,
393    input: &[u8],
394    is_create: bool,
395    access_list_accounts: u64,
396    access_list_storages: u64,
397    authorization_list_num: u64,
398) -> InitialAndFloorGas {
399    let mut gas = InitialAndFloorGas::default();
400
401    // Initdate stipend
402    let tokens_in_calldata = get_tokens_in_calldata(input, spec_id.is_enabled_in(SpecId::ISTANBUL));
403
404    // TODO(EOF) Tx type is removed
405    // initcode stipend
406    // for initcode in initcodes {
407    //     tokens_in_calldata += get_tokens_in_calldata(initcode.as_ref(), true);
408    // }
409
410    gas.initial_gas += tokens_in_calldata * STANDARD_TOKEN_COST;
411
412    // Get number of access list account and storages.
413    gas.initial_gas += access_list_accounts * ACCESS_LIST_ADDRESS;
414    gas.initial_gas += access_list_storages * ACCESS_LIST_STORAGE_KEY;
415
416    // Base stipend
417    gas.initial_gas += if is_create {
418        if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
419            // EIP-2: Homestead Hard-fork Changes
420            53000
421        } else {
422            21000
423        }
424    } else {
425        21000
426    };
427
428    // EIP-3860: Limit and meter initcode
429    // Init code stipend for bytecode analysis
430    if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
431        gas.initial_gas += initcode_cost(input.len())
432    }
433
434    // EIP-7702
435    if spec_id.is_enabled_in(SpecId::PRAGUE) {
436        gas.initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
437
438        // Calculate gas floor for EIP-7623
439        gas.floor_gas = calc_tx_floor_cost(tokens_in_calldata);
440    }
441
442    gas
443}
444
445/// Initial gas that is deducted for transaction to be included.
446/// Initial gas contains initial stipend gas, gas for access list and input data.
447///
448/// # Returns
449///
450/// - Intrinsic gas
451/// - Number of tokens in calldata
452pub fn calculate_initial_tx_gas_for_tx(tx: impl Transaction, spec: SpecId) -> InitialAndFloorGas {
453    let mut accounts = 0;
454    let mut storages = 0;
455    // legacy is only tx type that does not have access list.
456    if tx.tx_type() != TransactionType::Legacy {
457        (accounts, storages) = tx
458            .access_list()
459            .map(|al| {
460                al.fold((0, 0), |(mut num_accounts, mut num_storage_slots), item| {
461                    num_accounts += 1;
462                    num_storage_slots += item.storage_slots().count();
463
464                    (num_accounts, num_storage_slots)
465                })
466            })
467            .unwrap_or_default();
468    }
469
470    // Access initcodes only if tx is Eip7873.
471    // TODO(EOF) Tx type is removed
472    // let initcodes = if tx.tx_type() == TransactionType::Eip7873 {
473    //     tx.initcodes()
474    // } else {
475    //     &[]
476    // };
477
478    calculate_initial_tx_gas(
479        spec,
480        tx.input(),
481        tx.kind().is_create(),
482        accounts as u64,
483        storages as u64,
484        tx.authorization_list_len() as u64,
485        //initcodes,
486    )
487}
488
489/// Retrieve the total number of tokens in calldata.
490#[inline]
491pub fn get_tokens_in_calldata(input: &[u8], is_istanbul: bool) -> u64 {
492    let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
493    let non_zero_data_len = input.len() as u64 - zero_data_len;
494    let non_zero_data_multiplier = if is_istanbul {
495        // EIP-2028: Transaction data gas cost reduction
496        NON_ZERO_BYTE_MULTIPLIER_ISTANBUL
497    } else {
498        NON_ZERO_BYTE_MULTIPLIER
499    };
500    zero_data_len + non_zero_data_len * non_zero_data_multiplier
501}
502
503/// Calculate the transaction cost floor as specified in EIP-7623.
504#[inline]
505pub fn calc_tx_floor_cost(tokens_in_calldata: u64) -> u64 {
506    tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN + 21_000
507}