Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions jsontests/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ pub fn run_test(
Fork::London => Config::london(),
Fork::Shanghai => Config::shanghai(),
Fork::Cancun => Config::cancun(),
Fork::Prague => Config::prague(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm why is this tests passing?

Copy link
Contributor Author

@manuelmauro manuelmauro Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be because the EIP-7702 testing is mostly implemented in this framework https://github.com/ethereum/execution-spec-tests (rather then https://github.com/ethereum/tests)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the https://github.com/ethereum/tests/tree/428f218d7d6f4a52544e12684afbfe6e2882ffbf submodule is pointing to a commit from 2 years ago.

Copy link
Member

@sorpaas sorpaas Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the https://github.com/ethereum/tests/tree/428f218d7d6f4a52544e12684afbfe6e2882ffbf submodule is pointing to a commit from 2 years ago.

Yeah this is expected. At some point last year, legacy tests are removed from ethereum/tests and replaced by legacytests. Then EIP-7610 is implemented (for all hard forks). The tests of oldethtests and legacytests are actualy redundant to each other. legacytests is simply a newer version of oldethtests, with the only difference of whether it implements EIP-7610.

Mainnet clients don't care, but we still want to support non-EIP-7610 situations, so we still keep oldethtests with a commit from 2 years ago.

_ => return Err(Error::UnsupportedFork),
};
config_change(&mut config);
Expand Down
1 change: 1 addition & 0 deletions jsontests/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub enum Fork {
Paris,
Berlin,
Cancun,
Prague,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I think I know -- The commit of legacytests doesn't yet contain any Prague tests. It's up to the last hard fork.

To test Prague we need to update legacytests or add ethereum-spec-tests.

As said in previous comment, on the other hand, oldethtests is used only for EIP-7610 and it should never be updated.

London,
Merge,
Shanghai,
Expand Down
20 changes: 20 additions & 0 deletions src/standard/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub struct Config {
pub eip4844_shard_blob: bool,
/// EIP-7516: Blob base fee per gas.
pub eip7516_blob_base_fee: bool,
/// EIP-7623: Increase calldata cost with floor.
pub eip7623_calldata_floor: bool,
}

impl Config {
Expand Down Expand Up @@ -132,6 +134,7 @@ impl Config {
eip2930_access_list: false,
eip4844_shard_blob: false,
eip7516_blob_base_fee: false,
eip7623_calldata_floor: false,
}
}

Expand Down Expand Up @@ -237,6 +240,13 @@ impl Config {
config
}

/// Prague
pub const fn prague() -> Config {
let mut config = Self::cancun();
config.eip7623_calldata_floor = true;
config
}

/// Gas paid for extcode.
pub fn gas_ext_code(&self) -> u64 {
if self.eip150_gas_increase { 700 } else { 20 }
Expand Down Expand Up @@ -364,6 +374,16 @@ impl Config {
}
}

/// Floor gas paid for zero data in a transaction.
pub fn gas_floor_transaction_zero_data(&self) -> u64 {
if self.eip7623_calldata_floor { 10 } else { 0 }
}

/// Floor gas paid for non-zero data in a transaction.
pub fn gas_floor_transaction_non_zero_data(&self) -> u64 {
if self.eip7623_calldata_floor { 40 } else { 0 }
}

/// Gas paid per address in transaction access list (see EIP-2930).
pub fn gas_access_list_address(&self) -> u64 {
if self.eip2930_access_list { 2400 } else { 0 }
Expand Down
63 changes: 50 additions & 13 deletions src/standard/gasometer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct GasometerState {
gas_limit: u64,
memory_gas: u64,
used_gas: u64,
floor_gas: u64,
refunded_gas: i64,
/// Whether the gasometer is in static context.
pub is_static: bool,
Expand Down Expand Up @@ -78,6 +79,13 @@ impl GasometerState {
Ok(())
}

/// Record used and floor costs of a transaction.
pub fn records_transaction_cost(&mut self, cost: TransactionGas) -> Result<(), ExitError> {
self.record_gas64(cost.used)?;
self.floor_gas = cost.floor;
Ok(())
}

/// Set memory gas usage.
pub fn set_memory_gas(&mut self, memory_cost: u64) -> Result<(), ExitError> {
let all_gas_cost = self.used_gas.checked_add(memory_cost);
Expand All @@ -99,6 +107,7 @@ impl GasometerState {
gas_limit,
memory_gas: 0,
used_gas: 0,
floor_gas: 0,
refunded_gas: 0,
is_static,
}
Expand All @@ -117,10 +126,15 @@ impl GasometerState {
gas_limit.as_u64()
};

let mut s = Self::new(gas_limit, false);
let transaction_cost = TransactionCost::call(data, access_list).cost(config);
let cost = TransactionCost::call(data, access_list).cost(config);

// EIP-7623: Check if gas limit meets the floor requirement
if config.eip7623_calldata_floor && gas_limit < cost.floor {
return Err(ExitException::OutOfGas.into());
}

s.record_gas64(transaction_cost)?;
let mut s = Self::new(gas_limit, false);
s.records_transaction_cost(cost)?;
Ok(s)
}

Expand All @@ -137,10 +151,15 @@ impl GasometerState {
gas_limit.as_u64()
};

let mut s = Self::new(gas_limit, false);
let transaction_cost = TransactionCost::create(code, access_list).cost(config);
let cost = TransactionCost::create(code, access_list).cost(config);

// EIP-7623: Check if gas limit meets the floor requirement
if config.eip7623_calldata_floor && gas_limit < cost.floor {
return Err(ExitException::OutOfGas.into());
}

s.record_gas64(transaction_cost)?;
let mut s = Self::new(gas_limit, false);
s.records_transaction_cost(cost)?;
Ok(s)
}

Expand Down Expand Up @@ -202,6 +221,11 @@ impl GasometerState {
MergeStrategy::Discard => {}
}
}

/// Apply the floor gas cost for calldata as defined in EIP-7623
pub fn apply_transaction_floor_cost(&mut self) {
self.used_gas = max(self.used_gas, self.floor_gas);
}
}

/// The eval function of the entire gasometer.
Expand Down Expand Up @@ -940,6 +964,11 @@ enum TransactionCost {
},
}

pub struct TransactionGas {
used: u64,
floor: u64,
}

impl TransactionCost {
pub fn call(data: &[u8], access_list: &[(H160, Vec<H256>)]) -> TransactionCost {
let zero_data_len = data.iter().filter(|v| **v == 0).count();
Expand Down Expand Up @@ -969,22 +998,25 @@ impl TransactionCost {
}
}

pub fn cost(&self, config: &Config) -> u64 {
pub fn cost(&self, config: &Config) -> TransactionGas {
match self {
TransactionCost::Call {
zero_data_len,
non_zero_data_len,
access_list_address_len,
access_list_storage_len,
} => {
#[deny(clippy::let_and_return)]
let cost = config.gas_transaction_call()
let used = config.gas_transaction_call()
+ *zero_data_len as u64 * config.gas_transaction_zero_data()
+ *non_zero_data_len as u64 * config.gas_transaction_non_zero_data()
+ *access_list_address_len as u64 * config.gas_access_list_address()
+ *access_list_storage_len as u64 * config.gas_access_list_storage_key();

cost
let floor = config.gas_transaction_call()
+ *zero_data_len as u64 * config.gas_floor_transaction_zero_data()
+ *non_zero_data_len as u64 * config.gas_floor_transaction_non_zero_data();

TransactionGas { used, floor }
}
TransactionCost::Create {
zero_data_len,
Expand All @@ -993,16 +1025,21 @@ impl TransactionCost {
access_list_storage_len,
initcode_cost,
} => {
let mut cost = config.gas_transaction_create()
let mut used = config.gas_transaction_create()
+ *zero_data_len as u64 * config.gas_transaction_zero_data()
+ *non_zero_data_len as u64 * config.gas_transaction_non_zero_data()
+ *access_list_address_len as u64 * config.gas_access_list_address()
+ *access_list_storage_len as u64 * config.gas_access_list_storage_key();

if config.max_initcode_size().is_some() {
cost += initcode_cost;
used += initcode_cost;
}

cost
let floor = config.gas_transaction_call()
+ *zero_data_len as u64 * config.gas_floor_transaction_zero_data()
+ *non_zero_data_len as u64 * config.gas_floor_transaction_non_zero_data();

TransactionGas { used, floor }
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/standard/invoker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,13 @@ where

let result = work();

// Apply EIP-7623 floor cost before calculating effective gas
if let Some(substate) = exit.substate.as_mut()
&& invoke.config.eip7623_calldata_floor
{
substate.apply_transaction_floor_cost();
}

let effective_gas = match result {
Ok(_) => exit
.substate
Expand Down
2 changes: 2 additions & 0 deletions src/standard/invoker/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,6 @@ pub trait InvokerState: GasState + Sized {
fn is_static(&self) -> bool;
/// Effective gas. The final used gas as reported by the transaction.
fn effective_gas(&self, with_refund: bool) -> U256;
/// Apply transaction floor cost for EIP-7623.
fn apply_transaction_floor_cost(&mut self);
}
4 changes: 4 additions & 0 deletions src/standard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,8 @@ impl<'config> InvokerState for State<'config> {
fn effective_gas(&self, with_refund: bool) -> U256 {
self.gasometer.effective_gas(with_refund, self.config)
}

fn apply_transaction_floor_cost(&mut self) {
self.gasometer.apply_transaction_floor_cost()
}
}
Loading