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): Increase calldata cost. backport from rel/v51 #1965

Merged
merged 2 commits into from
Jan 6, 2025
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
3 changes: 1 addition & 2 deletions .github/workflows/ethereum-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ jobs:
ethtests/EIPTests/StateTests/stEIP2537/ \
ethtests/EIPTests/StateTests/stEOF \
tests/eof_suite/eest/state_tests \
tests/eof_suite/evmone/state_tests \
# tests/prague_suite/state_tests
tests/pectra_devnet5/state_tests/prague/eip7623_increase_calldata_cost
- name: Run EOF validation tests
run: |
cross run --target ${{matrix.target}} --profile ${{ matrix.profile }} -p revme -- eof-validation \
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions crates/context/interface/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,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 @@ -346,6 +351,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
3 changes: 1 addition & 2 deletions crates/handler/interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ all = "warn"
[dependencies]
# revm
primitives.workspace = true
interpreter.workspace = true

[dev-dependencies]
database.workspace = true

[features]
default = ["std"]
std = []
serde = ["std", "primitives/serde", "interpreter/serde"]
serde = ["std", "primitives/serde"]
2 changes: 1 addition & 1 deletion crates/handler/interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ pub use post_execution::PostExecutionHandler;
pub use pre_execution::PreExecutionHandler;
pub use precompile_provider::PrecompileProvider;
pub use util::FrameOrResultGen;
pub use validation::ValidationHandler;
pub use validation::{InitialAndFloorGas, ValidationHandler};
10 changes: 10 additions & 0 deletions crates/handler/interface/src/post_execution.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
use crate::InitialAndFloorGas;

pub trait PostExecutionHandler {
type Context;
type Error;
type ExecResult;
type Output;

/// Calculate final refund.
fn eip7623_check_gas_floor(
&self,
context: &mut Self::Context,
exec_result: &mut Self::ExecResult,
init_and_floor_gas: InitialAndFloorGas,
);

/// Calculate final refund.
fn refund(
&self,
Expand Down
4 changes: 2 additions & 2 deletions crates/handler/interface/src/precompile_provider.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use interpreter::InterpreterResult;
use primitives::{Address, Bytes};

pub trait PrecompileProvider: Clone {
type Context;
type Output;
type Error;

/// Create a new precompile.
Expand All @@ -15,7 +15,7 @@ pub trait PrecompileProvider: Clone {
address: &Address,
bytes: &Bytes,
gas_limit: u64,
) -> Result<Option<InterpreterResult>, Self::Error>;
) -> Result<Option<Self::Output>, Self::Error>;

/// Get the warm addresses.
fn warm_addresses(&self) -> impl Iterator<Item = Address>;
Expand Down
15 changes: 14 additions & 1 deletion crates/handler/interface/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,18 @@ pub trait ValidationHandler {
fn validate_tx_against_state(&self, context: &mut Self::Context) -> Result<(), Self::Error>;

/// Validate initial gas.
fn validate_initial_tx_gas(&self, context: &Self::Context) -> Result<u64, Self::Error>;
fn validate_initial_tx_gas(
&self,
context: &Self::Context,
) -> Result<InitialAndFloorGas, Self::Error>;
}

/// 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,
}
4 changes: 2 additions & 2 deletions crates/handler/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl<CTX, ERROR, PRECOMPILE, INSTRUCTION>
where
CTX: EthFrameContext,
ERROR: EthFrameError<CTX>,
PRECOMPILE: PrecompileProvider<Context = CTX, Error = ERROR>,
PRECOMPILE: PrecompileProvider<Context = CTX, Error = ERROR, Output = InterpreterResult>,
{
/// Make call frame
#[inline]
Expand Down Expand Up @@ -456,7 +456,7 @@ impl<CTX, ERROR, PRECOMPILE, INSTRUCTION> Frame
where
CTX: EthFrameContext,
ERROR: EthFrameError<CTX>,
PRECOMPILE: PrecompileProvider<Context = CTX, Error = ERROR>,
PRECOMPILE: PrecompileProvider<Context = CTX, Error = ERROR, Output = InterpreterResult>,
INSTRUCTION: InstructionProvider<WIRE = EthInterpreter<()>, Host = CTX>,
{
type Context = CTX;
Expand Down
12 changes: 12 additions & 0 deletions crates/handler/src/post_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ where
type ExecResult = FrameResult;
type Output = ResultAndState<HALTREASON>;

fn eip7623_check_gas_floor(
&self,
_context: &mut Self::Context,
exec_result: &mut Self::ExecResult,
init_and_floor_gas: handler_interface::InitialAndFloorGas,
) {
let gas_result = exec_result.gas_mut();
if gas_result.spent() < init_and_floor_gas.floor_gas {
let _ = gas_result.record_cost(init_and_floor_gas.floor_gas - gas_result.spent());
}
}

fn refund(
&self,
context: &mut Self::Context,
Expand Down
8 changes: 0 additions & 8 deletions crates/handler/src/pre_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,6 @@ where

// Load access list
context.load_access_list()?;
// if let Some(access_list) = context.tx().access_list().cloned() {
// for access_list in access_list.iter() {
// context.journal().warm_account_and_storage(
// access_list.0,
// access_list.1.map(|i| U256::from_be_bytes(i.0)),
// )?;
// }
// };

Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions crates/handler/src/precompile_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ where
{
type Context = CTX;
type Error = ERROR;
type Output = InterpreterResult;

fn new(context: &mut Self::Context) -> Self {
let spec = context.cfg().spec().into();
Expand Down
30 changes: 22 additions & 8 deletions crates/handler/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use context_interface::{
Block, BlockGetter, Cfg, CfgGetter, JournalDBError, JournalGetter, TransactionGetter,
};
use core::cmp::{self, Ordering};
use handler_interface::ValidationHandler;
use interpreter::gas;
use handler_interface::{InitialAndFloorGas, ValidationHandler};
use interpreter::gas::{self};
use primitives::{B256, U256};
use specification::{eip4844, hardfork::SpecId};
use state::Account;
Expand Down Expand Up @@ -69,7 +69,10 @@ where
validate_tx_against_account::<CTX, ERROR>(&account, context)
}

fn validate_initial_tx_gas(&self, context: &Self::Context) -> Result<u64, Self::Error> {
fn validate_initial_tx_gas(
&self,
context: &Self::Context,
) -> Result<InitialAndFloorGas, Self::Error> {
let spec = context.cfg().spec().into();
validate_initial_tx_gas::<&Self::Context, InvalidTransaction>(context, spec)
.map_err(Into::into)
Expand Down Expand Up @@ -328,15 +331,19 @@ where
}

/// Validate initial transaction gas.
pub fn validate_initial_tx_gas<CTX, Error>(context: CTX, spec_id: SpecId) -> Result<u64, Error>
pub fn validate_initial_tx_gas<CTX, Error>(
context: CTX,
spec_id: SpecId,
) -> Result<InitialAndFloorGas, Error>
where
CTX: TransactionGetter,
CTX: TransactionGetter + CfgGetter,
Error: From<InvalidTransaction>,
{
let spec = context.cfg().spec().into();
let tx = context.tx();
let (accounts, storages) = tx.access_list_nums().unwrap_or_default();

let initial_gas_spend = gas::validate_initial_tx_gas(
let gas = gas::calculate_initial_tx_gas(
spec_id,
tx.input(),
tx.kind().is_create(),
Expand All @@ -346,10 +353,17 @@ where
);

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

// EIP-7623: Increase calldata cost
// floor gas should be less than gas limit.
if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
return Err(InvalidTransaction::GasFloorMoreThanGasLimit.into());
};

Ok(gas)
}

/// Helper trait that summarizes ValidationHandler requirements from Context.
Expand Down
4 changes: 2 additions & 2 deletions crates/inspector/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use revm::{
interpreter_types::{Jumps, LoopControl},
table::CustomInstruction,
CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, FrameInput, Host,
Instruction, InstructionResult, Interpreter, InterpreterTypes,
Instruction, InstructionResult, Interpreter, InterpreterResult, InterpreterTypes,
},
precompile::PrecompileErrors,
primitives::{Address, Log, U256},
Expand Down Expand Up @@ -254,7 +254,7 @@ where
+ Host
+ InspectorCtx<IT = EthInterpreter>,
ERROR: From<JournalDBError<CTX>> + From<PrecompileErrors>,
PRECOMPILE: PrecompileProvider<Context = CTX, Error = ERROR>,
PRECOMPILE: PrecompileProvider<Context = CTX, Error = ERROR, Output = InterpreterResult>,
{
type Context = CTX;
type Error = ERROR;
Expand Down
1 change: 1 addition & 0 deletions crates/interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ bytecode.workspace = true
primitives.workspace = true
specification.workspace = true
context-interface.workspace = true
handler-interface.workspace = true

# optional
serde = { version = "1.0", default-features = false, features = [
Expand Down
59 changes: 40 additions & 19 deletions crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::constants::*;
use crate::{num_words, tri, SStoreResult, SelfDestructResult, StateLoad};
use context_interface::journaled_state::{AccountLoad, Eip7702CodeLoad};
use handler_interface::InitialAndFloorGas;
use primitives::U256;
use specification::{eip7702, hardfork::SpecId};

Expand Down Expand Up @@ -347,34 +348,31 @@ pub const fn memory_gas(num_words: usize) -> 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_accounts: u64,
access_list_storages: u64,
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.
initial_gas += access_list_accounts * ACCESS_LIST_ADDRESS;
initial_gas += access_list_storages * ACCESS_LIST_STORAGE_KEY;
gas.initial_gas += access_list_accounts * ACCESS_LIST_ADDRESS;
gas.initial_gas += access_list_storages * 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 @@ -388,13 +386,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())
gas.initial_gas += initcode_cost(input.len())
}

// 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
Loading
Loading