Skip to content

Commit

Permalink
feat(Prague): EIP-7623 Increase Calldata Cost (#1744)
Browse files Browse the repository at this point in the history
* feat: eip-7623

* remove double iteration on data
  • Loading branch information
rkrasiuk authored Dec 6, 2024
1 parent a002459 commit 6882e22
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 38 deletions.
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)
}

0 comments on commit 6882e22

Please sign in to comment.