revm_interpreter/gas/
calc.rs

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