Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: eip-7623 #1744

Merged
merged 3 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 50 additions & 21 deletions crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,38 +361,44 @@ pub const fn memory_gas(num_words: u64) -> u64 {
.saturating_add(num_words.saturating_mul(num_words) / 512)
}

/// Init and floor gas from transaction
#[derive(Clone, Copy, Debug, Default)]
pub struct InitialAndFloorGas {
/// Initial gas for transaction.
pub initial_gas: u64,
/// If transaction is a Call and Prague is enabled
/// floor_gas is at least amount of gas that is going to be spent.
pub floor_gas: u64,
}

/// Initial gas that is deducted for transaction to be included.
/// Initial gas contains initial stipend gas, gas for access list and input data.
pub fn validate_initial_tx_gas(
///
/// # Returns
///
/// - Intrinsic gas
/// - Number of tokens in calldata
pub fn calculate_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;
) -> InitialAndFloorGas {
let mut gas = InitialAndFloorGas::default();

// initdate stipend
initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
// EIP-2028: Transaction data gas cost reduction
initial_gas += non_zero_data_len
* if spec_id.is_enabled_in(SpecId::ISTANBUL) {
16
} else {
68
};
let tokens_in_calldata = get_tokens_in_calldata(input, spec_id.is_enabled_in(SpecId::ISTANBUL));
gas.initial_gas += tokens_in_calldata * STANDARD_TOKEN_COST;

// get number of access list account and storages.
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;
gas.initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
gas.initial_gas += accessed_slots as u64 * ACCESS_LIST_STORAGE_KEY;
}

// base stipend
initial_gas += if is_create {
gas.initial_gas += if is_create {
if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
// EIP-2: Homestead Hard-fork Changes
53000
Expand All @@ -406,13 +412,36 @@ pub fn validate_initial_tx_gas(
// EIP-3860: Limit and meter initcode
// Init code stipend for bytecode analysis
if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
initial_gas += initcode_cost(input.len() as u64)
gas.initial_gas += initcode_cost(input.len() as u64)
}

// EIP-7702
// EIP-7702
if spec_id.is_enabled_in(SpecId::PRAGUE) {
initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
gas.initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;

// Calculate gas floor for EIP-7623
gas.floor_gas = calc_tx_floor_cost(tokens_in_calldata);
}

initial_gas
gas
}

/// Retrieve the total number of tokens in calldata.
#[inline]
pub fn get_tokens_in_calldata(input: &[u8], is_istanbul: bool) -> u64 {
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;
let non_zero_data_multiplier = if is_istanbul {
// EIP-2028: Transaction data gas cost reduction
NON_ZERO_BYTE_MULTIPLIER_ISTANBUL
} else {
NON_ZERO_BYTE_MULTIPLIER
};
zero_data_len + non_zero_data_len * non_zero_data_multiplier
}

/// Calculate the transaction cost floor as specified in EIP-7623.
#[inline]
pub fn calc_tx_floor_cost(tokens_in_calldata: u64) -> u64 {
tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN + 21_000
}
16 changes: 13 additions & 3 deletions crates/interpreter/src/gas/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,19 @@ pub const SSTORE_SET: u64 = 20000;
pub const SSTORE_RESET: u64 = 5000;
pub const REFUND_SSTORE_CLEARS: i64 = 15000;

pub const TRANSACTION_ZERO_DATA: u64 = 4;
pub const TRANSACTION_NON_ZERO_DATA_INIT: u64 = 16;
pub const TRANSACTION_NON_ZERO_DATA_FRONTIER: u64 = 68;
/// The standard cost of calldata token.
pub const STANDARD_TOKEN_COST: u64 = 4;
/// The cost of a non-zero byte in calldata.
pub const NON_ZERO_BYTE_DATA_COST: u64 = 68;
/// The multiplier for a non zero byte in calldata.
pub const NON_ZERO_BYTE_MULTIPLIER: u64 = NON_ZERO_BYTE_DATA_COST / STANDARD_TOKEN_COST;
/// The cost of a non-zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
pub const NON_ZERO_BYTE_DATA_COST_ISTANBUL: u64 = 16;
/// The multiplier for a non zero byte in calldata adjusted by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
pub const NON_ZERO_BYTE_MULTIPLIER_ISTANBUL: u64 =
NON_ZERO_BYTE_DATA_COST_ISTANBUL / STANDARD_TOKEN_COST;
// The cost floor per token as defined by [EIP-2028](https://eips.ethereum.org/EIPS/eip-2028).
pub const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10;

pub const EOF_CREATE_GAS: u64 = 32000;

Expand Down
8 changes: 8 additions & 0 deletions crates/primitives/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ pub enum InvalidTransaction {
/// - initial stipend gas
/// - gas for access list and input data
CallGasCostMoreThanGasLimit,
/// Gas floor calculated from EIP-7623 Increase calldata cost
/// is more than the gas limit.
///
/// Tx data is too large to be executed.
GasFloorMoreThanGasLimit,
/// EIP-3607 Reject transactions from senders with deployed code
RejectCallerWithCode,
/// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
Expand Down Expand Up @@ -352,6 +357,9 @@ impl fmt::Display for InvalidTransaction {
Self::CallGasCostMoreThanGasLimit => {
write!(f, "call gas cost exceeds the gas limit")
}
Self::GasFloorMoreThanGasLimit => {
write!(f, "gas floor exceeds the gas limit")
}
Self::RejectCallerWithCode => {
write!(f, "reject transactions from senders with deployed code")
}
Expand Down
23 changes: 16 additions & 7 deletions crates/revm/src/evm.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use revm_interpreter::gas::InitialAndFloorGas;

use crate::{
builder::{EvmBuilder, HandlerStage, SetGenericStage},
db::{Database, DatabaseCommit, EmptyDB},
Expand Down Expand Up @@ -195,20 +197,20 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
/// This function will not validate the transaction.
#[inline]
pub fn transact_preverified(&mut self) -> EVMResult<DB::Error> {
let initial_gas_spend = self
let init_and_floor_gas = self
.handler
.validation()
.initial_tx_gas(&self.context.evm.env)
.inspect_err(|_e| self.clear())?;
let output = self.transact_preverified_inner(initial_gas_spend);
let output = self.transact_preverified_inner(init_and_floor_gas);
let output = self.handler.post_execution().end(&mut self.context, output);
self.clear();
output
}

/// Pre verify transaction inner.
#[inline]
fn preverify_transaction_inner(&mut self) -> Result<u64, EVMError<DB::Error>> {
fn preverify_transaction_inner(&mut self) -> Result<InitialAndFloorGas, EVMError<DB::Error>> {
self.handler.validation().env(&self.context.evm.env)?;
let initial_gas_spend = self
.handler
Expand All @@ -225,11 +227,11 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
/// This function will validate the transaction.
#[inline]
pub fn transact(&mut self) -> EVMResult<DB::Error> {
let initial_gas_spend = self
let init_and_floor_gas = self
.preverify_transaction_inner()
.inspect_err(|_e| self.clear())?;

let output = self.transact_preverified_inner(initial_gas_spend);
let output = self.transact_preverified_inner(init_and_floor_gas);
let output = self.handler.post_execution().end(&mut self.context, output);
self.clear();
output
Expand Down Expand Up @@ -319,7 +321,7 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
}

/// Transact pre-verified transaction.
fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult<DB::Error> {
fn transact_preverified_inner(&mut self, gas: InitialAndFloorGas) -> EVMResult<DB::Error> {
let spec_id = self.spec_id();
let ctx = &mut self.context;
let pre_exec = self.handler.pre_execution();
Expand All @@ -334,7 +336,7 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
// deduce caller balance with its limit.
pre_exec.deduct_caller(ctx)?;

let gas_limit = ctx.evm.env.tx.gas_limit - initial_gas_spend;
let gas_limit = ctx.evm.env.tx.gas_limit - gas.initial_gas;

// apply EIP-7702 auth list.
let eip7702_gas_refund = pre_exec.apply_eip7702_auth_list(ctx)? as i64;
Expand Down Expand Up @@ -378,6 +380,13 @@ impl<EXT, DB: Database> Evm<'_, EXT, DB> {
.execution()
.last_frame_return(ctx, &mut result)?;

// EIP-7623: Increase calldata cost
// spend at least a gas_floor amount of gas.
let gas_result = result.gas_mut();
if gas_result.spent() < gas.floor_gas {
let _ = gas_result.record_cost(gas.floor_gas - gas_result.spent());
}

let post_exec = self.handler.post_execution();
// calculate final refund and add EIP-7702 refund to gas.
post_exec.refund(ctx, result.gas_mut(), eip7702_gas_refund);
Expand Down
6 changes: 4 additions & 2 deletions crates/revm/src/handler/handle_types/validation.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use revm_interpreter::gas::InitialAndFloorGas;

use crate::{
handler::mainnet,
primitives::{db::Database, EVMError, Env, Spec},
Expand All @@ -16,7 +18,7 @@ pub type ValidateTxEnvAgainstState<'a, EXT, DB> =

/// Initial gas calculation handle
pub type ValidateInitialTxGasHandle<'a, DB> =
Arc<dyn Fn(&Env) -> Result<u64, EVMError<<DB as Database>::Error>> + 'a>;
Arc<dyn Fn(&Env) -> Result<InitialAndFloorGas, EVMError<<DB as Database>::Error>> + 'a>;

/// Handles related to validation.
pub struct ValidationHandler<'a, EXT, DB: Database> {
Expand Down Expand Up @@ -46,7 +48,7 @@ impl<EXT, DB: Database> ValidationHandler<'_, EXT, DB> {
}

/// Initial gas
pub fn initial_tx_gas(&self, env: &Env) -> Result<u64, EVMError<DB::Error>> {
pub fn initial_tx_gas(&self, env: &Env) -> Result<InitialAndFloorGas, EVMError<DB::Error>> {
(self.initial_tx_gas)(env)
}

Expand Down
17 changes: 12 additions & 5 deletions crates/revm/src/handler/mainnet/validation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use revm_interpreter::gas;
use revm_interpreter::gas::{self, InitialAndFloorGas};

use crate::{
handler::SpecId,
primitives::{db::Database, EVMError, Env, InvalidTransaction, Spec},
Context,
};
Expand Down Expand Up @@ -38,7 +39,7 @@ pub fn validate_tx_against_state<SPEC: Spec, EXT, DB: Database>(
/// Validate initial transaction gas.
pub fn validate_initial_tx_gas<SPEC: Spec, DB: Database>(
env: &Env,
) -> Result<u64, EVMError<DB::Error>> {
) -> Result<InitialAndFloorGas, EVMError<DB::Error>> {
let input = &env.tx.data;
let is_create = env.tx.transact_to.is_create();
let access_list = &env.tx.access_list;
Expand All @@ -49,7 +50,7 @@ pub fn validate_initial_tx_gas<SPEC: Spec, DB: Database>(
.map(|l| l.len() as u64)
.unwrap_or_default();

let initial_gas_spend = gas::validate_initial_tx_gas(
let gas = gas::calculate_initial_tx_gas(
SPEC::SPEC_ID,
input,
is_create,
Expand All @@ -58,8 +59,14 @@ pub fn validate_initial_tx_gas<SPEC: Spec, DB: Database>(
);

// Additional check to see if limit is big enough to cover initial gas.
if initial_gas_spend > env.tx.gas_limit {
if gas.initial_gas > env.tx.gas_limit {
return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into());
}
Ok(initial_gas_spend)

// EIP-7623
if SPEC::SPEC_ID.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > env.tx.gas_limit {
return Err(InvalidTransaction::GasFloorMoreThanGasLimit.into());
};

Ok(gas)
}
Loading