Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
Copy Markdown
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?

@manuelmauro manuelmauro Sep 8, 2025

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sorpaas sorpaas Sep 8, 2025

Copy link
Copy Markdown
Member

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
Copy Markdown
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
86 changes: 59 additions & 27 deletions src/standard/gasometer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod costs;
mod utils;

use alloc::vec::Vec;
use core::cmp::{max, min};
use core::cmp::max;

use evm_interpreter::{
Control, ExitError, ExitException, Machine, Opcode, Stack,
Expand All @@ -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;
Comment thread
manuelmauro marked this conversation as resolved.
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);

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

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

Expand All @@ -137,33 +151,38 @@ 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)
}

/// The effective used gas at the end of the transaction.
///
/// In case of revert, refunded gas are not taken into account.
pub fn effective_gas(&self, with_refund: bool, config: &Config) -> U256 {
let refunded_gas = if self.refunded_gas >= 0 {
self.refunded_gas as u64
pub fn effective_gas(&self, with_refund: bool, with_floor: bool, config: &Config) -> U256 {
let refunded_gas = self.refunded_gas.max(0) as u64;

let used_gas = if with_refund {
let max_refund = self.total_used_gas() / config.max_refund_quotient();
self.total_used_gas() - refunded_gas.min(max_refund)
} else {
0
self.total_used_gas()
};

U256::from(if with_refund {
self.gas_limit
- (self.total_used_gas()
- min(
self.total_used_gas() / config.max_refund_quotient(),
refunded_gas,
))
let used_gas = if with_floor {
used_gas.max(self.floor_gas)
} else {
self.gas_limit - self.total_used_gas()
})
used_gas
};

U256::from(self.gas_limit - used_gas)
}

/// Create a submeter.
Expand Down Expand Up @@ -940,6 +959,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 +993,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 }
Comment thread
manuelmauro marked this conversation as resolved.
Outdated
}
TransactionCost::Create {
zero_data_len,
Expand All @@ -993,16 +1020,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
4 changes: 2 additions & 2 deletions src/standard/invoker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,12 @@ where
Ok(_) => exit
.substate
.as_ref()
.map(|s| s.effective_gas(true))
.map(|s| s.effective_gas(true, invoke.config.eip7623_calldata_floor))
.unwrap_or_default(),
Err(ExitError::Reverted) => exit
.substate
.as_ref()
.map(|s| s.effective_gas(false))
.map(|s| s.effective_gas(false, invoke.config.eip7623_calldata_floor))
.unwrap_or_default(),
Err(_) => U256::zero(),
};
Expand Down
2 changes: 1 addition & 1 deletion src/standard/invoker/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ pub trait InvokerState: GasState + Sized {
/// Whether the current state is in the static frame.
fn is_static(&self) -> bool;
/// Effective gas. The final used gas as reported by the transaction.
fn effective_gas(&self, with_refund: bool) -> U256;
fn effective_gas(&self, with_refund: bool, with_floor: bool) -> U256;
}
5 changes: 3 additions & 2 deletions src/standard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ impl<'config> InvokerState for State<'config> {
self.gasometer.is_static
}

fn effective_gas(&self, with_refund: bool) -> U256 {
self.gasometer.effective_gas(with_refund, self.config)
fn effective_gas(&self, with_refund: bool, with_floor: bool) -> U256 {
self.gasometer
.effective_gas(with_refund, with_floor, self.config)
}
}
Loading