revm_interpreter/gas/
calc.rs

1use super::constants::*;
2use crate::num_words;
3use context_interface::{transaction::AccessListItemTr as _, Transaction, TransactionType};
4use primitives::{eip7702, hardfork::SpecId, U256};
5
6#[inline]
7pub(crate) const fn log2floor(value: U256) -> u64 {
8    let mut l: u64 = 256;
9    let mut i = 3;
10    loop {
11        if value.as_limbs()[i] == 0u64 {
12            l -= 64;
13        } else {
14            l -= value.as_limbs()[i].leading_zeros() as u64;
15            if l == 0 {
16                return l;
17            } else {
18                return l - 1;
19            }
20        }
21        if i == 0 {
22            break;
23        }
24        i -= 1;
25    }
26    l
27}
28
29/// Calculate the cost of buffer per word.
30#[inline]
31pub const fn cost_per_word(len: usize, multiple: u64) -> Option<u64> {
32    multiple.checked_mul(num_words(len) as u64)
33}
34
35/// EIP-3860: Limit and meter initcode
36///
37/// Apply extra gas cost of 2 for every 32-byte chunk of initcode.
38///
39/// This cannot overflow as the initcode length is assumed to be checked.
40#[inline]
41pub const fn initcode_cost(len: usize) -> u64 {
42    let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
43        panic!("initcode cost overflow")
44    };
45    cost
46}
47
48/// Memory expansion cost calculation for a given number of words.
49#[inline]
50pub const fn memory_gas(num_words: usize, linear_cost: u64, quadratic_cost: u64) -> u64 {
51    let num_words = num_words as u64;
52    linear_cost
53        .saturating_mul(num_words)
54        .saturating_add(num_words.saturating_mul(num_words) / quadratic_cost)
55}
56
57/// Init and floor gas from transaction
58#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60pub struct InitialAndFloorGas {
61    /// Initial gas for transaction.
62    pub initial_gas: u64,
63    /// If transaction is a Call and Prague is enabled
64    /// floor_gas is at least amount of gas that is going to be spent.
65    pub floor_gas: u64,
66}
67
68impl InitialAndFloorGas {
69    /// Create a new InitialAndFloorGas instance.
70    #[inline]
71    pub const fn new(initial_gas: u64, floor_gas: u64) -> Self {
72        Self {
73            initial_gas,
74            floor_gas,
75        }
76    }
77}
78
79/// Initial gas that is deducted for transaction to be included.
80/// Initial gas contains initial stipend gas, gas for access list and input data.
81///
82/// # Returns
83///
84/// - Intrinsic gas
85/// - Number of tokens in calldata
86pub fn calculate_initial_tx_gas(
87    spec_id: SpecId,
88    input: &[u8],
89    is_create: bool,
90    access_list_accounts: u64,
91    access_list_storages: u64,
92    authorization_list_num: u64,
93) -> InitialAndFloorGas {
94    let mut gas = InitialAndFloorGas::default();
95
96    // Initdate stipend
97    let tokens_in_calldata = get_tokens_in_calldata(input, spec_id.is_enabled_in(SpecId::ISTANBUL));
98
99    gas.initial_gas += tokens_in_calldata * STANDARD_TOKEN_COST;
100
101    // Get number of access list account and storages.
102    gas.initial_gas += access_list_accounts * ACCESS_LIST_ADDRESS;
103    gas.initial_gas += access_list_storages * ACCESS_LIST_STORAGE_KEY;
104
105    // Base stipend
106    gas.initial_gas += if is_create {
107        if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
108            // EIP-2: Homestead Hard-fork Changes
109            53000
110        } else {
111            21000
112        }
113    } else {
114        21000
115    };
116
117    // EIP-3860: Limit and meter initcode
118    // Init code stipend for bytecode analysis
119    if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
120        gas.initial_gas += initcode_cost(input.len())
121    }
122
123    // EIP-7702
124    if spec_id.is_enabled_in(SpecId::PRAGUE) {
125        gas.initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
126
127        // Calculate gas floor for EIP-7623
128        gas.floor_gas = calc_tx_floor_cost(tokens_in_calldata);
129    }
130
131    gas
132}
133
134/// Initial gas that is deducted for transaction to be included.
135/// Initial gas contains initial stipend gas, gas for access list and input data.
136///
137/// # Returns
138///
139/// - Intrinsic gas
140/// - Number of tokens in calldata
141pub fn calculate_initial_tx_gas_for_tx(tx: impl Transaction, spec: SpecId) -> InitialAndFloorGas {
142    let mut accounts = 0;
143    let mut storages = 0;
144    // legacy is only tx type that does not have access list.
145    if tx.tx_type() != TransactionType::Legacy {
146        (accounts, storages) = tx
147            .access_list()
148            .map(|al| {
149                al.fold((0, 0), |(mut num_accounts, mut num_storage_slots), item| {
150                    num_accounts += 1;
151                    num_storage_slots += item.storage_slots().count();
152
153                    (num_accounts, num_storage_slots)
154                })
155            })
156            .unwrap_or_default();
157    }
158
159    calculate_initial_tx_gas(
160        spec,
161        tx.input(),
162        tx.kind().is_create(),
163        accounts as u64,
164        storages as u64,
165        tx.authorization_list_len() as u64,
166    )
167}
168
169/// Retrieve the total number of tokens in calldata.
170#[inline]
171pub fn get_tokens_in_calldata(input: &[u8], is_istanbul: bool) -> u64 {
172    let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
173    let non_zero_data_len = input.len() as u64 - zero_data_len;
174    let non_zero_data_multiplier = if is_istanbul {
175        // EIP-2028: Transaction data gas cost reduction
176        NON_ZERO_BYTE_MULTIPLIER_ISTANBUL
177    } else {
178        NON_ZERO_BYTE_MULTIPLIER
179    };
180    zero_data_len + non_zero_data_len * non_zero_data_multiplier
181}
182
183/// Calculate the transaction cost floor as specified in EIP-7623.
184#[inline]
185pub fn calc_tx_floor_cost(tokens_in_calldata: u64) -> u64 {
186    tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN + 21_000
187}