diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs index 1222dc5b..88905556 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -190,6 +190,7 @@ pub fn run_test( Fork::London => Config::london(), Fork::Shanghai => Config::shanghai(), Fork::Cancun => Config::cancun(), + Fork::Prague => Config::prague(), _ => return Err(Error::UnsupportedFork), }; config_change(&mut config); diff --git a/jsontests/src/types.rs b/jsontests/src/types.rs index 40dfbc76..160991e2 100644 --- a/jsontests/src/types.rs +++ b/jsontests/src/types.rs @@ -166,6 +166,7 @@ pub enum Fork { Paris, Berlin, Cancun, + Prague, London, Merge, Shanghai, diff --git a/src/standard/config.rs b/src/standard/config.rs index a7a65d39..4a3fd78c 100644 --- a/src/standard/config.rs +++ b/src/standard/config.rs @@ -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 { @@ -132,6 +134,7 @@ impl Config { eip2930_access_list: false, eip4844_shard_blob: false, eip7516_blob_base_fee: false, + eip7623_calldata_floor: false, } } @@ -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 } @@ -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 } diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index 0cb7ce3f..4d3e561a 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -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, @@ -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, @@ -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); @@ -99,6 +107,7 @@ impl GasometerState { gas_limit, memory_gas: 0, used_gas: 0, + floor_gas: 0, refunded_gas: 0, is_static, } @@ -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) } @@ -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) } @@ -148,22 +167,22 @@ impl GasometerState { /// /// 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. @@ -940,6 +959,11 @@ enum TransactionCost { }, } +pub struct TransactionGas { + used: u64, + floor: u64, +} + impl TransactionCost { pub fn call(data: &[u8], access_list: &[(H160, Vec)]) -> TransactionCost { let zero_data_len = data.iter().filter(|v| **v == 0).count(); @@ -969,7 +993,7 @@ impl TransactionCost { } } - pub fn cost(&self, config: &Config) -> u64 { + pub fn cost(&self, config: &Config) -> TransactionGas { match self { TransactionCost::Call { zero_data_len, @@ -977,14 +1001,24 @@ impl TransactionCost { 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, @@ -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 } } } }