Skip to content
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
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
98 changes: 72 additions & 26 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;
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,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
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 config.eip7623_calldata_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,32 @@ 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()
.saturating_add(
(*zero_data_len as u64)
.saturating_mul(config.gas_floor_transaction_zero_data()),
)
.saturating_add(
(*non_zero_data_len as u64)
.saturating_mul(config.gas_floor_transaction_non_zero_data()),
);

TransactionGas { used, floor }
}
TransactionCost::Create {
zero_data_len,
Expand All @@ -993,16 +1027,28 @@ 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()
.saturating_add(
(*zero_data_len as u64)
.saturating_mul(config.gas_floor_transaction_zero_data()),
)
.saturating_add(
(*non_zero_data_len as u64)
.saturating_mul(config.gas_floor_transaction_non_zero_data()),
);

TransactionGas { used, floor }
}
}
}
Expand Down