revm_interpreter/gas/
calc.rsuse revm_primitives::eip7702;
use super::constants::*;
use crate::{
num_words,
primitives::{AccessListItem, SpecId, U256},
AccountLoad, Eip7702CodeLoad, SStoreResult, SelfDestructResult, StateLoad,
};
macro_rules! tri {
($e:expr) => {
match $e {
Some(v) => v,
None => return None,
}
};
}
#[allow(clippy::collapsible_else_if)]
#[inline]
pub fn sstore_refund(spec_id: SpecId, vals: &SStoreResult) -> i64 {
if spec_id.is_enabled_in(SpecId::ISTANBUL) {
let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
(SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
} else {
REFUND_SSTORE_CLEARS
};
if vals.is_new_eq_present() {
0
} else {
if vals.is_original_eq_present() && vals.is_new_zero() {
sstore_clears_schedule
} else {
let mut refund = 0;
if !vals.is_original_zero() {
if vals.is_present_zero() {
refund -= sstore_clears_schedule;
} else if vals.is_new_zero() {
refund += sstore_clears_schedule;
}
}
if vals.is_original_eq_new() {
let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
(SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
} else {
(SSTORE_RESET, sload_cost(spec_id, false))
};
if vals.is_original_zero() {
refund += (SSTORE_SET - gas_sload) as i64;
} else {
refund += (gas_sstore_reset - gas_sload) as i64;
}
}
refund
}
}
} else {
if !vals.is_present_zero() && vals.is_new_zero() {
REFUND_SSTORE_CLEARS
} else {
0
}
}
}
#[inline]
pub const fn create2_cost(len: u64) -> Option<u64> {
CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
}
#[inline]
const fn log2floor(value: U256) -> u64 {
let mut l: u64 = 256;
let mut i = 3;
loop {
if value.as_limbs()[i] == 0u64 {
l -= 64;
} else {
l -= value.as_limbs()[i].leading_zeros() as u64;
if l == 0 {
return l;
} else {
return l - 1;
}
}
if i == 0 {
break;
}
i -= 1;
}
l
}
#[inline]
pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
if power.is_zero() {
Some(EXP)
} else {
let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
50
} else {
10
});
let gas = U256::from(EXP)
.checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?;
u64::try_from(gas).ok()
}
}
#[inline]
pub const fn verylowcopy_cost(len: u64) -> Option<u64> {
VERYLOW.checked_add(tri!(cost_per_word(len, COPY)))
}
#[inline]
pub const fn extcodecopy_cost(spec_id: SpecId, len: u64, load: Eip7702CodeLoad<()>) -> Option<u64> {
let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
warm_cold_cost_with_delegation(load)
} else if spec_id.is_enabled_in(SpecId::TANGERINE) {
700
} else {
20
};
base_gas.checked_add(tri!(cost_per_word(len, COPY)))
}
#[inline]
pub const fn log_cost(n: u8, len: u64) -> Option<u64> {
tri!(LOG.checked_add(tri!(LOGDATA.checked_mul(len)))).checked_add(LOGTOPIC * n as u64)
}
#[inline]
pub const fn keccak256_cost(len: u64) -> Option<u64> {
KECCAK256.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
}
#[inline]
pub const fn cost_per_word(len: u64, multiple: u64) -> Option<u64> {
multiple.checked_mul(num_words(len))
}
#[inline]
pub const fn initcode_cost(len: u64) -> u64 {
let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
panic!("initcode cost overflow")
};
cost
}
#[inline]
pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
if spec_id.is_enabled_in(SpecId::BERLIN) {
if is_cold {
COLD_SLOAD_COST
} else {
WARM_STORAGE_READ_COST
}
} else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
INSTANBUL_SLOAD_GAS
} else if spec_id.is_enabled_in(SpecId::TANGERINE) {
200
} else {
50
}
}
#[inline]
pub fn sstore_cost(spec_id: SpecId, vals: &SStoreResult, gas: u64, is_cold: bool) -> Option<u64> {
if spec_id.is_enabled_in(SpecId::ISTANBUL) && gas <= CALL_STIPEND {
return None;
}
if spec_id.is_enabled_in(SpecId::BERLIN) {
let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(vals);
if is_cold {
gas_cost += COLD_SLOAD_COST;
}
Some(gas_cost)
} else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
Some(istanbul_sstore_cost::<INSTANBUL_SLOAD_GAS, SSTORE_RESET>(
vals,
))
} else {
Some(frontier_sstore_cost(vals))
}
}
#[inline]
fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
vals: &SStoreResult,
) -> u64 {
if vals.is_new_eq_present() {
SLOAD_GAS
} else if vals.is_original_eq_present() && vals.is_original_zero() {
SSTORE_SET
} else if vals.is_original_eq_present() {
SSTORE_RESET_GAS
} else {
SLOAD_GAS
}
}
#[inline]
fn frontier_sstore_cost(vals: &SStoreResult) -> u64 {
if vals.is_present_zero() && !vals.is_new_zero() {
SSTORE_SET
} else {
SSTORE_RESET
}
}
#[inline]
pub const fn selfdestruct_cost(spec_id: SpecId, res: StateLoad<SelfDestructResult>) -> u64 {
let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
res.data.had_value && !res.data.target_exists
} else {
!res.data.target_exists
};
let selfdestruct_gas_topup = if spec_id.is_enabled_in(SpecId::TANGERINE) && should_charge_topup
{
25000
} else {
0
};
let selfdestruct_gas = if spec_id.is_enabled_in(SpecId::TANGERINE) {
5000
} else {
0
};
let mut gas = selfdestruct_gas + selfdestruct_gas_topup;
if spec_id.is_enabled_in(SpecId::BERLIN) && res.is_cold {
gas += COLD_ACCOUNT_ACCESS_COST
}
gas
}
#[inline]
pub const fn call_cost(spec_id: SpecId, transfers_value: bool, account_load: AccountLoad) -> u64 {
let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
warm_cold_cost_with_delegation(account_load.load)
} else if spec_id.is_enabled_in(SpecId::TANGERINE) {
700
} else {
40
};
if transfers_value {
gas += CALLVALUE;
}
if account_load.is_empty {
if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
if transfers_value {
gas += NEWACCOUNT;
}
} else {
gas += NEWACCOUNT;
}
}
gas
}
#[inline]
pub const fn warm_cold_cost(is_cold: bool) -> u64 {
if is_cold {
COLD_ACCOUNT_ACCESS_COST
} else {
WARM_STORAGE_READ_COST
}
}
#[inline]
pub const fn warm_cold_cost_with_delegation(load: Eip7702CodeLoad<()>) -> u64 {
let mut gas = warm_cold_cost(load.state_load.is_cold);
if let Some(is_cold) = load.is_delegate_account_cold {
gas += warm_cold_cost(is_cold);
}
gas
}
#[inline]
pub const fn memory_gas_for_len(len: usize) -> u64 {
memory_gas(crate::interpreter::num_words(len as u64))
}
#[inline]
pub const fn memory_gas(num_words: u64) -> u64 {
MEMORY
.saturating_mul(num_words)
.saturating_add(num_words.saturating_mul(num_words) / 512)
}
pub fn validate_initial_tx_gas(
spec_id: SpecId,
input: &[u8],
is_create: bool,
access_list: &[AccessListItem],
authorization_list_num: u64,
) -> u64 {
let mut initial_gas = 0;
let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
let non_zero_data_len = input.len() as u64 - zero_data_len;
initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
initial_gas += non_zero_data_len
* if spec_id.is_enabled_in(SpecId::ISTANBUL) {
16
} else {
68
};
if spec_id.is_enabled_in(SpecId::BERLIN) {
let accessed_slots: usize = access_list.iter().map(|item| item.storage_keys.len()).sum();
initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
initial_gas += accessed_slots as u64 * ACCESS_LIST_STORAGE_KEY;
}
initial_gas += if is_create {
if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
53000
} else {
21000
}
} else {
21000
};
if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
initial_gas += initcode_cost(input.len() as u64)
}
if spec_id.is_enabled_in(SpecId::PRAGUE) {
initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
}
initial_gas
}