diff --git a/Cargo.toml b/Cargo.toml index 1c613c07..33da9951 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ force-debug = [ "evm-gasometer/force-debug", ] create-fixed = [] +print-debug = ["evm-gasometer/print-debug"] [workspace] members = [ diff --git a/benches/loop.rs b/benches/loop.rs index 687a9a13..13ebc42d 100644 --- a/benches/loop.rs +++ b/benches/loop.rs @@ -10,6 +10,7 @@ fn run_loop_contract() { let vicinity = MemoryVicinity { gas_price: U256::zero(), + effective_gas_price: U256::zero(), origin: H160::default(), block_hashes: Vec::new(), block_number: Default::default(), @@ -20,7 +21,8 @@ fn run_loop_contract() { chain_id: U256::one(), block_base_fee_per_gas: U256::zero(), block_randomness: None, - blob_base_fee: None, + blob_gas_price: None, + blob_hashes: Vec::new(), }; let mut state = BTreeMap::new(); diff --git a/core/src/opcode.rs b/core/src/opcode.rs index 6a960a95..07d8627a 100644 --- a/core/src/opcode.rs +++ b/core/src/opcode.rs @@ -1,3 +1,5 @@ +use core::fmt::{Display, Formatter}; + /// Opcode enum. One-to-one corresponding to an `u8` value. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr( @@ -286,3 +288,162 @@ impl Opcode { self.0 as usize } } + +impl Display for Opcode { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let name = match *self { + Self::STOP => "STOP", + Self::ADD => "ADD", + Self::MUL => "MUL", + Self::SUB => "SUB", + Self::DIV => "DIV", + Self::SDIV => "SDIV", + Self::MOD => "MOD", + Self::SMOD => "SMOD", + Self::ADDMOD => "ADDMOD", + Self::MULMOD => "MULMOD", + Self::EXP => "EXP", + Self::SIGNEXTEND => "SIGNEXTEND", + Self::LT => "LT", + Self::GT => "GT", + Self::SLT => "SLT", + Self::SGT => "SGT", + Self::EQ => "EQ", + Self::ISZERO => "ISZERO", + Self::AND => "AND", + Self::OR => "OR", + Self::XOR => "XOR", + Self::NOT => "NOT", + Self::BYTE => "BYTE", + Self::CALLDATALOAD => "CALLDATALOAD", + Self::CALLDATASIZE => "CALLDATASIZE", + Self::CALLDATACOPY => "CALLDATACOPY", + Self::CODESIZE => "CODESIZE", + Self::CODECOPY => "CODECOPY", + Self::SHL => "SHL", + Self::SHR => "SHR", + Self::SAR => "SAR", + Self::POP => "POP", + Self::MLOAD => "MLOAD", + Self::MSTORE => "MSTORE", + Self::MSTORE8 => "MSTORE8", + Self::JUMP => "JUMP", + Self::JUMPI => "JUMPI", + Self::PC => "PC", + Self::MSIZE => "MSIZE", + Self::JUMPDEST => "JUMPDEST", + Self::TLOAD => "TLOAD", + Self::TSTORE => "TSTORE", + Self::MCOPY => "MCOPY", + Self::PUSH0 => "PUSH0", + Self::PUSH1 => "PUSH1", + Self::PUSH2 => "PUSH2", + Self::PUSH3 => "PUSH3", + Self::PUSH4 => "PUSH4", + Self::PUSH5 => "PUSH5", + Self::PUSH6 => "PUSH6", + Self::PUSH7 => "PUSH7", + Self::PUSH8 => "PUSH8", + Self::PUSH9 => "PUSH9", + Self::PUSH10 => "PUSH10", + Self::PUSH11 => "PUSH11", + Self::PUSH12 => "PUSH12", + Self::PUSH13 => "PUSH13", + Self::PUSH14 => "PUSH14", + Self::PUSH15 => "PUSH15", + Self::PUSH16 => "PUSH16", + Self::PUSH17 => "PUSH17", + Self::PUSH18 => "PUSH18", + Self::PUSH19 => "PUSH19", + Self::PUSH20 => "PUSH20", + Self::PUSH21 => "PUSH21", + Self::PUSH22 => "PUSH22", + Self::PUSH23 => "PUSH23", + Self::PUSH24 => "PUSH24", + Self::PUSH25 => "PUSH25", + Self::PUSH26 => "PUSH26", + Self::PUSH27 => "PUSH27", + Self::PUSH28 => "PUSH28", + Self::PUSH29 => "PUSH29", + Self::PUSH30 => "PUSH30", + Self::PUSH31 => "PUSH31", + Self::PUSH32 => "PUSH32", + Self::DUP1 => "DUP1", + Self::DUP2 => "DUP2", + Self::DUP3 => "DUP3", + Self::DUP4 => "DUP4", + Self::DUP5 => "DUP5", + Self::DUP6 => "DUP6", + Self::DUP7 => "DUP7", + Self::DUP8 => "DUP8", + Self::DUP9 => "DUP9", + Self::DUP10 => "DUP10", + Self::DUP11 => "DUP11", + Self::DUP12 => "DUP12", + Self::DUP13 => "DUP13", + Self::DUP14 => "DUP14", + Self::DUP15 => "DUP15", + Self::DUP16 => "DUP16", + Self::SWAP1 => "SWAP1", + Self::SWAP2 => "SWAP2", + Self::SWAP3 => "SWAP3", + Self::SWAP4 => "SWAP4", + Self::SWAP5 => "SWAP5", + Self::SWAP6 => "SWAP6", + Self::SWAP7 => "SWAP7", + Self::SWAP8 => "SWAP8", + Self::SWAP9 => "SWAP9", + Self::SWAP10 => "SWAP10", + Self::SWAP11 => "SWAP11", + Self::SWAP12 => "SWAP12", + Self::SWAP13 => "SWAP13", + Self::SWAP14 => "SWAP14", + Self::SWAP15 => "SWAP15", + Self::SWAP16 => "SWAP16", + Self::RETURN => "RETURN", + Self::REVERT => "REVERT", + Self::INVALID => "INVALID", + Self::EOFMAGIC => "EOFMAGIC", + Self::SHA3 => "SHA3", + Self::ADDRESS => "ADDRESS", + Self::BALANCE => "BALANCE", + Self::SELFBALANCE => "SELFBALANCE", + Self::BASEFEE => "BASEFEE", + Self::BLOBHASH => "BLOBHASH", + Self::BLOBBASEFEE => "BLOBBASEFEE", + Self::ORIGIN => "ORIGIN", + Self::CALLER => "CALLER", + Self::CALLVALUE => "CALLVALUE", + Self::GASPRICE => "GASPRICE", + Self::EXTCODESIZE => "EXTCODESIZE", + Self::EXTCODECOPY => "EXTCODECOPY", + Self::EXTCODEHASH => "EXTCODEHASH", + Self::RETURNDATASIZE => "RETURNDATASIZE", + Self::RETURNDATACOPY => "RETURNDATACOPY", + Self::BLOCKHASH => "BLOCKHASH", + Self::COINBASE => "COINBASE", + Self::TIMESTAMP => "TIMESTAMP", + Self::NUMBER => "NUMBER", + Self::DIFFICULTY => "DIFFICULTY", + Self::GASLIMIT => "GASLIMIT", + Self::SLOAD => "SLOAD", + Self::SSTORE => "SSTORE", + Self::GAS => "GAS", + Self::LOG0 => "LOG0", + Self::LOG1 => "LOG1", + Self::LOG2 => "LOG2", + Self::LOG3 => "LOG3", + Self::LOG4 => "LOG4", + Self::CREATE => "CREATE", + Self::CREATE2 => "CREATE2", + Self::CALL => "CALL", + Self::CALLCODE => "CALLCODE", + Self::DELEGATECALL => "DELEGATECALL", + Self::STATICCALL => "STATICCALL", + Self::SUICIDE => "SUICIDE", + Self::CHAINID => "CHAINID", + _ => "UNKNOWN", + }; + write!(f, "{name} [{}]", self.0) + } +} diff --git a/core/src/utils.rs b/core/src/utils.rs index 540ccc3c..ea4bebac 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -67,7 +67,7 @@ pub fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u128 { /// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers) /// (`get_blob_gasprice`). #[inline] -pub fn calc_blob_gasprice(excess_blob_gas: u64) -> u128 { +pub fn calc_blob_gas_price(excess_blob_gas: u64) -> u128 { fake_exponential( MIN_BLOB_GASPRICE, excess_blob_gas, @@ -75,6 +75,30 @@ pub fn calc_blob_gasprice(excess_blob_gas: u64) -> u128 { ) } +/// Calculates the [EIP-4844] `data_fee` of the transaction. +/// +/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 +#[inline] +pub fn calc_max_data_fee(max_fee_per_blob_gas: U256, blob_hashes_len: usize) -> U256 { + max_fee_per_blob_gas.saturating_mul(U256::from(get_total_blob_gas(blob_hashes_len))) +} + +/// Calculates the [EIP-4844] `data_fee` of the transaction. +/// +/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 +#[inline] +pub fn calc_data_fee(blob_gas_price: u128, blob_hashes_len: usize) -> U256 { + U256::from(blob_gas_price).saturating_mul(U256::from(get_total_blob_gas(blob_hashes_len))) +} + +/// See [EIP-4844], [`calc_max_data_fee`] +/// +/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 +#[inline] +pub const fn get_total_blob_gas(blob_hashes_len: usize) -> u64 { + GAS_PER_BLOB * blob_hashes_len as u64 +} + #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Sign { Plus, diff --git a/evm-tests/ethcore-builtin/src/lib.rs b/evm-tests/ethcore-builtin/src/lib.rs index 4b06fcde..fbef6a07 100644 --- a/evm-tests/ethcore-builtin/src/lib.rs +++ b/evm-tests/ethcore-builtin/src/lib.rs @@ -1342,14 +1342,11 @@ impl Implementation for Kzg { fn execute(&self, input: &[u8], output: &mut BytesRef) -> Result<(), &'static str> { // Get and verify KZG input. let kzg_input: kzg::KzgInput = input.try_into()?; - if output.is_empty() { - return Err("BlobInvalidOutputLength"); - } let kzg_settings = kzg::EnvKzgSettings::Default; if !kzg_input.verify_kzg_proof(&kzg_settings.get()) { return Err("BlobVerifyKzgProofFailed"); } - output.copy_from_slice(kzg::RETURN_VALUE.as_slice()); + output.write(0, kzg::RETURN_VALUE.as_slice()); Ok(()) } } diff --git a/evm-tests/ethjson/src/spec/spec.rs b/evm-tests/ethjson/src/spec/spec.rs index b534f20b..130dd1f0 100644 --- a/evm-tests/ethjson/src/spec/spec.rs +++ b/evm-tests/ethjson/src/spec/spec.rs @@ -25,6 +25,18 @@ use std::io::Read; /// Fork spec definition #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)] pub enum ForkSpec { + /// Byzantium transition test-net + EIP158ToByzantiumAt5, + /// Homestead transition test-net + FrontierToHomesteadAt5, + /// Homestead transition test-net + HomesteadToDaoAt5, + /// EIP158/EIP161 transition test-net + HomesteadToEIP150At5, + /// ConstantinopleFix transition test-net + ByzantiumToConstantinopleFixAt5, + /// Istanbul transition test-net + ConstantinopleFixToIstanbulAt5, /// EIP 150 Tangerine Whistle: Gas cost changes for IO-heavy operations (#2,463,000, 2016-10-18) EIP150, /// EIP 158/EIP 161 Spurious Dragon: State trie clearing (#2,675,000, 2016-11-22) @@ -43,6 +55,7 @@ pub enum ForkSpec { Istanbul, /// Berlin (#12,244,000, 2021-04-15) Berlin, + /// London (#12,965,000, 2021-08-05) London, /// Paris - The Merge (#15,537,394, 2022-09-15) @@ -53,28 +66,25 @@ pub enum ForkSpec { Shanghai, /// Cancun (2024-03-13) Cancun, - - /// Byzantium transition test-net - EIP158ToByzantiumAt5, - /// Homestead transition test-net - FrontierToHomesteadAt5, - /// Homestead transition test-net - HomesteadToDaoAt5, - /// EIP158/EIP161 transition test-net - HomesteadToEIP150At5, - /// ConstantinopleFix transition test-net - ByzantiumToConstantinopleFixAt5, - /// Istanbul transition test-net - ConstantinopleFixToIstanbulAt5, } impl ForkSpec { /// Returns true if the fork is at or after the merge. pub const fn is_eth2(&self) -> bool { - // NOTE: Include new forks in this match arm. - matches!( + !matches!( *self, - Self::Cancun | Self::London | Self::Merge | Self::Paris | Self::Shanghai + Self::EIP158ToByzantiumAt5 + | Self::FrontierToHomesteadAt5 + | Self::HomesteadToDaoAt5 + | Self::HomesteadToEIP150At5 + | Self::ByzantiumToConstantinopleFixAt5 + | Self::ConstantinopleFixToIstanbulAt5 + | Self::EIP150 | Self::EIP158 + | Self::Frontier | Self::Homestead + | Self::Byzantium + | Self::Constantinople + | Self::ConstantinopleFix + | Self::Istanbul | Self::Berlin ) } } diff --git a/evm-tests/ethjson/src/test_helpers/state.rs b/evm-tests/ethjson/src/test_helpers/state.rs index 12785df9..ec7034d5 100644 --- a/evm-tests/ethjson/src/test_helpers/state.rs +++ b/evm-tests/ethjson/src/test_helpers/state.rs @@ -59,14 +59,11 @@ pub struct MultiTransaction { /// Gas limit set. pub gas_limit: Vec, /// Gas price. - #[serde(default)] - pub gas_price: Uint, + pub gas_price: Option, /// for details on `maxFeePerGas` see EIP-1559 - #[serde(default)] - pub max_fee_per_gas: Uint, + pub max_fee_per_gas: Option, /// for details on `maxPriorityFeePerGas` see EIP-1559 - #[serde(default)] - pub max_priority_fee_per_gas: Uint, + pub max_priority_fee_per_gas: Option, /// Nonce. pub nonce: Uint, /// Secret key. @@ -79,30 +76,12 @@ pub struct MultiTransaction { /// EIP-4844 #[serde(default)] - pub blob_versioned_hashes: Vec, + pub blob_versioned_hashes: Vec, /// EIP-4844 pub max_fee_per_blob_gas: Option, } impl MultiTransaction { - /// max_priority_fee_per_gas (see EIP-1559) - pub const fn max_priority_fee_per_gas(&self) -> U256 { - if self.max_priority_fee_per_gas.0.is_zero() { - self.gas_price.0 - } else { - self.max_priority_fee_per_gas.0 - } - } - - /// max_fee_per_gas (see EIP-1559) - pub const fn max_fee_per_gas(&self) -> U256 { - if self.max_fee_per_gas.0.is_zero() { - self.gas_price.0 - } else { - self.max_fee_per_gas.0 - } - } - /// Build transaction with given indexes. pub fn select(&self, indexes: &PostStateIndexes) -> Transaction { let data_index = indexes.data as usize; @@ -120,17 +99,9 @@ impl MultiTransaction { Vec::new() }; - let gas_price = if self.gas_price.0.is_zero() { - self.max_fee_per_gas.0 + self.max_priority_fee_per_gas.0 - } else { - self.gas_price.0 - }; - Transaction { data: self.data[data_index].clone(), gas_limit: self.gas_limit[indexes.gas as usize], - gas_price: Uint(gas_price), - nonce: self.nonce, to: self.to.clone(), value: self.value[indexes.value as usize], r: Default::default(), diff --git a/evm-tests/ethjson/src/transaction.rs b/evm-tests/ethjson/src/transaction.rs index 479fce06..47e5508b 100644 --- a/evm-tests/ethjson/src/transaction.rs +++ b/evm-tests/ethjson/src/transaction.rs @@ -35,10 +35,6 @@ pub struct Transaction { pub access_list: Vec<(Address, Vec)>, /// Gas limit. pub gas_limit: Uint, - /// Gas price. - pub gas_price: Uint, - /// Nonce. - pub nonce: Uint, /// To. pub to: MaybeEmpty
, /// Value. @@ -79,10 +75,7 @@ mod tests { let tx: Transaction = serde_json::from_str(s).expect("JSON string is valid"); assert_eq!(tx.data, Bytes::new(Vec::new())); assert_eq!(tx.gas_limit, Uint(U256::from(0xf388))); - assert_eq!(tx.gas_price, Uint(U256::from(0x09184e72a000_u64))); - assert_eq!(tx.nonce, Uint(U256::zero())); assert_eq!(tx.to, MaybeEmpty::None); - assert_eq!(tx.value, Uint(U256::zero())); assert_eq!(tx.r, Uint(U256::zero()).into()); assert_eq!(tx.s, Uint(U256::one()).into()); assert_eq!(tx.v, Uint(U256::from(2)).into()); diff --git a/evm-tests/ethjson/src/vm.rs b/evm-tests/ethjson/src/vm.rs index 71406a07..611bbb11 100644 --- a/evm-tests/ethjson/src/vm.rs +++ b/evm-tests/ethjson/src/vm.rs @@ -101,6 +101,7 @@ pub struct Transaction { /// Environment. #[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Env { /// Address. #[serde(rename = "currentCoinbase")] @@ -125,9 +126,13 @@ pub struct Env { #[serde(rename = "currentRandom")] #[serde(default)] pub random: Option, - /// EIP-7516: Blob base fee - #[serde(default)] - pub blob_base_fee: Option, + + /// EIP-4844 + pub parent_blob_gas_used: Option, + /// EIP-4844 + pub parent_excess_blob_gas: Option, + /// EIP-4844 + pub current_excess_blob_gas: Option, } #[cfg(test)] @@ -202,7 +207,9 @@ mod tests { timestamp: Uint(1.into()), block_base_fee_per_gas: Uint(0.into()), random: Some(Uint(1.into())), - blob_base_fee: None, + parent_excess_blob_gas: None, + parent_blob_gas_used: None, + current_excess_blob_gas: None } ); assert_eq!( diff --git a/evm-tests/jsontests/src/main.rs b/evm-tests/jsontests/src/main.rs index 6c8ca53c..c53f02a8 100644 --- a/evm-tests/jsontests/src/main.rs +++ b/evm-tests/jsontests/src/main.rs @@ -188,29 +188,59 @@ const SKIPPED_CASES: &[&str] = &[ // custom json parser. https://github.com/ethereum/tests/issues/971 "stTransactionTest/ValueOverflow", "stTransactionTest/ValueOverflowParis", - // These tests are passing, but they take a lot of time to execute so we are going to skip them. - "stTimeConsuming/static_Call50000_sha256", - "vmPerformance/loopMul", - "stTimeConsuming/CALLBlake2f_MaxRounds", - // Skip python-specific tests - "eip4844_blobs", - // Cancun blob txs - "stEIP4844-blobtransactions", + // These tests are passing, but they take a lot of time to execute so can going to skip them. + // NOTE: do not remove it to know slowest tests. It's useful for development. + // "stTimeConsuming/static_Call50000_sha256", + // "vmPerformance/loopMul", + // "stTimeConsuming/CALLBlake2f_MaxRounds", ]; +/// Check is path should be skip. +/// It checks: +/// - path/and_file_stam - check path and file name (without extention) +/// - path/with/sub/path - recursively check path fn should_skip(path: &Path) -> bool { let matches = |case: &str| { - let file_stem = path.file_stem().unwrap(); - let dir_path = path.parent().unwrap(); - let dir_name = dir_path.file_name().unwrap(); - Path::new(dir_name).join(file_stem) == Path::new(case) - || Path::new(dir_name) == Path::new(case) + let case_path = Path::new(case); + let case_path_components: Vec<_> = case_path.components().collect(); + let path_components: Vec<_> = path.components().collect(); + let case_path_len = case_path_components.len(); + let path_len = path_components.len(); + + // Check path length without file name + if case_path_len > path_len { + return false; + } + // Check stem file name (without extension) + if let (Some(file_path_stem), Some(case_file_path_stem)) = + (path.file_stem(), case_path.file_stem()) + { + if file_path_stem == case_file_path_stem { + // If case path contains only file name + if case_path_len == 1 { + return true; + } + // Check sub path without file names + if case_path_len > 1 + && path_len > 1 && case_path_components[..case_path_len - 1] + == path_components[path_len - case_path_len..path_len - 1] + { + return true; + } + } + } + // Check recursively path from the end without file name + if case_path_len < path_len && path_len > 1 { + for i in 1..=path_len - case_path_len { + if case_path_components + == path_components[path_len - case_path_len - i..path_len - i] + { + return true; + } + } + } + false }; - for case in SKIPPED_CASES { - if matches(case) { - return true; - } - } - false + SKIPPED_CASES.iter().any(|case| matches(case)) } diff --git a/evm-tests/jsontests/src/state.rs b/evm-tests/jsontests/src/state.rs index 78d660b8..98a68720 100644 --- a/evm-tests/jsontests/src/state.rs +++ b/evm-tests/jsontests/src/state.rs @@ -1,4 +1,4 @@ -use crate::utils::*; +use crate::utils::transaction::InvalidTxReason; use ethjson::hash::Address; use ethjson::spec::builtin::{AltBn128ConstOperations, AltBn128Pairing, PricingAt}; use ethjson::spec::{ForkSpec, Pricing}; @@ -8,6 +8,7 @@ use evm::executor::stack::{ MemoryStackState, PrecompileFailure, PrecompileFn, PrecompileOutput, StackExecutor, StackSubstateMetadata, }; +use evm::utils::{calc_blob_gas_price, calc_data_fee, calc_excess_blob_gas, calc_max_data_fee}; use evm::{Config, Context, ExitError, ExitReason, ExitSucceed}; use lazy_static::lazy_static; use libsecp256k1::SecretKey; @@ -64,7 +65,7 @@ pub struct Test(ethjson::test_helpers::state::State); impl Test { pub fn unwrap_to_pre_state(&self) -> BTreeMap { - unwrap_to_state(&self.0.pre_state) + crate::utils::unwrap_to_state(&self.0.pre_state) } pub fn unwrap_caller(&self) -> H160 { @@ -79,40 +80,31 @@ impl Test { H160::from(H256::from_slice(Keccak256::digest(res).as_slice())) } - pub fn unwrap_to_vicinity(&self, spec: &ForkSpec) -> Option { + pub fn unwrap_to_vicinity( + &self, + spec: &ForkSpec, + blob_gas_price: Option, + ) -> Result { let block_base_fee_per_gas = self.0.env.block_base_fee_per_gas.0; - let gas_price = if self.0.transaction.gas_price.0.is_zero() { - let max_fee_per_gas = self.0.transaction.max_fee_per_gas.0; + let tx = &self.0.transaction; + let gas_price = tx.gas_price.or(tx.max_fee_per_gas).unwrap_or_default().0; - // max_fee_per_gas is only defined for London and later - if !max_fee_per_gas.is_zero() && spec < &ForkSpec::London { - return None; + // EIP-1559: priority fee must be lower than gas_price + if let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas { + if max_priority_fee_per_gas.0 > gas_price { + return Err(InvalidTxReason::PriorityFeeTooLarge); } - - // Cannot specify a lower fee than the base fee - if max_fee_per_gas < block_base_fee_per_gas { - return None; - } - - let max_priority_fee_per_gas = self.0.transaction.max_priority_fee_per_gas.0; - - // priority fee must be lower than regaular fee - if max_fee_per_gas < max_priority_fee_per_gas { - return None; - } - - let priority_fee_per_gas = std::cmp::min( - max_priority_fee_per_gas, - max_fee_per_gas - block_base_fee_per_gas, - ); - priority_fee_per_gas + block_base_fee_per_gas - } else { - self.0.transaction.gas_price.0 - }; + } + let effective_gas_price = self.0.transaction.max_priority_fee_per_gas.map_or( + gas_price, + |max_priority_fee_per_gas| { + gas_price.min(max_priority_fee_per_gas.0 + block_base_fee_per_gas) + }, + ); // gas price cannot be lower than base fee if gas_price < block_base_fee_per_gas { - return None; + return Err(InvalidTxReason::GasPriceLessThenBlockBaseFee); } let block_randomness = if spec.is_eth2() { @@ -124,14 +116,16 @@ impl Test { // (0x44), and so for older forks of Ethereum, the threshold value of 2^64 is used to // distinguish between the two: if it's below, the value corresponds to the DIFFICULTY // opcode, otherwise to the PREVRANDAO opcode. - u256_to_h256(r.0) + crate::utils::u256_to_h256(r.0) }) } else { None }; + let blob_hashes = tx.blob_versioned_hashes.clone(); - Some(MemoryVicinity { + Ok(MemoryVicinity { gas_price, + effective_gas_price, origin: self.unwrap_caller(), block_hashes: Vec::new(), block_number: self.0.env.number.into(), @@ -142,7 +136,8 @@ impl Test { chain_id: U256::one(), block_base_fee_per_gas, block_randomness, - blob_base_fee: self.0.env.blob_base_fee, + blob_gas_price, + blob_hashes, }) } } @@ -389,7 +384,10 @@ fn cancun_builtins() -> BTreeMap { Address(H160::from_low_u64_be(0xA)), BuiltinCompat { name: "kzg".to_string(), - pricing: PricingCompat::Empty, + pricing: PricingCompat::Single(Pricing::Linear(Linear { + base: 50_000, + word: 0, + })), activate_at: None, }, ), @@ -426,22 +424,185 @@ pub fn test( child.join().unwrap() } +/// Validate EIP-3607 - empty create caller +fn assert_empty_create_caller(expect_exception: &Option, name: &str) { + let exception = expect_exception + .as_deref() + .expect("expected evm-json-test exception"); + let check_exception = exception == "SenderNotEOA"; + assert!( + check_exception, + "expected EmptyCaller exception for test: {name}" + ); +} + +/// Check call expected exception +fn assert_call_exit_exception(spec: &ForkSpec, expect_exception: &Option) { + if *spec == ForkSpec::Berlin { + if let Some(exception) = expect_exception.as_deref() { + let check_result = exception == "TR_TypeNotSupported"; + assert!(check_result, "expected call exception"); + } + } else { + assert!(expect_exception.is_none(), "unexpected call exception"); + } +} + /// Check Exit Reason of EVM execution -fn check_create_exit_reason(reason: &ExitReason, expect_exception: &Option) -> bool { - if let Some(exception) = expect_exception.as_deref() { - if matches!(reason, ExitReason::Error(ExitError::CreateContractLimit)) { - let check_result = exception == "TR_InitCodeLimitExceeded" - || exception == "TransactionException.INITCODE_SIZE_EXCEEDED"; +fn check_create_exit_reason( + reason: &ExitReason, + expect_exception: &Option, + name: &str, +) -> bool { + match reason { + ExitReason::Error(err) => { + if let Some(exception) = expect_exception.as_deref() { + match err { + ExitError::CreateContractLimit => { + let check_result = exception == "TR_InitCodeLimitExceeded" + || exception == "TransactionException.INITCODE_SIZE_EXCEEDED"; + assert!( + check_result, + "unexpected exception {exception:?} for CreateContractLimit error for test: {name}" + ); + return true; + } + ExitError::MaxNonce => { + let check_result = exception == "TR_NonceHasMaxValue"; + assert!(check_result, + "unexpected exception {exception:?} for MaxNonce error for test: {name}" + ); + return true; + } + _ => { + panic!("unexpected error: {err:?} for exception: {exception}") + } + } + } else { + return false; + } + } + ExitReason::Fatal(err) => { + panic!("Unexpected error: {err:?}") + } + _ => { assert!( - check_result, - "message: {exception}\nbut expected init code limit exceeded" + expect_exception.is_none(), + "Unexpected json-test error: {expect_exception:?}" ); - return true; } } false } +/// Check Exit Reason of EVM execution +fn check_validate_exit_reason( + reason: &InvalidTxReason, + expect_exception: &Option, + name: &str, +) -> bool { + expect_exception.as_deref().map_or_else( + || { + panic!("unexpected validation error reason: {reason:?}"); + }, + |exception| { + match reason { + InvalidTxReason::OutOfFund => { + let check_result = exception + == "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS" + || exception == "TR_TypeNotSupported" + || exception == "TR_NoFunds" + || exception == "TR_NoFundsX" + || exception == "TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS"; + assert!( + check_result, + "unexpected exception {exception:?} for OutOfFund for test: {name}" + ); + } + InvalidTxReason::GasLimitReached => { + let check_result = exception == "TR_GasLimitReached"; + assert!( + check_result, + "unexpected exception {exception:?} for GasLimitReached for test: {name}" + ); + } + InvalidTxReason::IntrinsicGas => { + let check_result = exception == "TR_NoFundsOrGas" + || exception == "TR_IntrinsicGas" + || exception == "TransactionException.INTRINSIC_GAS_TOO_LOW" + || exception == "IntrinsicGas" + || exception == "TR_TypeNotSupported"; + assert!( + check_result, + "unexpected exception {exception:?} for IntrinsicGas for test: {name}" + ); + } + InvalidTxReason::BlobVersionNotSupported => { + let check_result = exception + == "TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH" + || exception == "TR_BLOBVERSION_INVALID"; + assert!( + check_result, + "unexpected exception {exception:?} for BlobVersionNotSupported for test: {name}" + ); + } + InvalidTxReason::BlobCreateTransaction => { + let check_result = exception == "TR_BLOBCREATE"; + assert!( + check_result, + "unexpected exception {exception:?} for BlobCreateTransaction for test: {name}" + ); + } + InvalidTxReason::BlobGasPriceGreaterThanMax => { + let check_result = + exception == "TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS"; + assert!( + check_result, + "unexpected exception {exception:?} for BlobGasPriceGreaterThanMax for test: {name}" + ); + } + InvalidTxReason::TooManyBlobs => { + let check_result = exception == "TR_BLOBLIST_OVERSIZE" + || exception == "TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED"; + assert!( + check_result, + "unexpected exception {exception:?} for TooManyBlobs for test: {name}" + ); + } + InvalidTxReason::EmptyBlobs => { + let check_result = exception == "TransactionException.TYPE_3_TX_ZERO_BLOBS" + || exception == "TR_EMPTYBLOB"; + assert!( + check_result, + "unexpected exception {exception:?} for EmptyBlobs for test: {name}" + ); + } + InvalidTxReason::MaxFeePerBlobGasNotSupported => { + let check_result = + exception == "TransactionException.TYPE_3_TX_PRE_FORK|TransactionException.TYPE_3_TX_ZERO_BLOBS"; + assert!( + check_result, + "unexpected exception {exception:?} for MaxFeePerBlobGasNotSupported for test: {name}" + ); + } + InvalidTxReason::BlobVersionedHashesNotSupported => { + let check_result = exception == "TransactionException.TYPE_3_TX_PRE_FORK"; + assert!( + check_result, + "unexpected exception {exception:?} for BlobVersionedHashesNotSupported for test: {name}" + ); + } + _ => { + panic!( + "unexpected exception {exception:?} for reason {reason:?} for test {name}" + ); + } + } + true + }, + ) +} + #[allow(clippy::cognitive_complexity)] fn test_run( verbose_output: &VerboseOutput, @@ -450,6 +611,7 @@ fn test_run( specific_spec: Option, ) -> TestExecutionResult { let mut tests_result = TestExecutionResult::new(); + let test_tx = &test.0.transaction; for (spec, states) in &test.0.post_states { // Run tests for specific SPEC (Hard fork) if let Some(s) = specific_spec.as_ref() { @@ -465,19 +627,53 @@ fn test_run( ForkSpec::Paris => (Config::merge(), true), ForkSpec::Shanghai => (Config::shanghai(), true), ForkSpec::Cancun => (Config::cancun(), true), - spec => { - println!("Skip spec {spec:?}"); + _ => { continue; } }; + // EIP-4844 + let blob_gas_price = + if let Some(current_excess_blob_gas) = test.0.env.current_excess_blob_gas { + Some(calc_blob_gas_price(current_excess_blob_gas.0.as_u64())) + } else if let (Some(parent_blob_gas_used), Some(parent_excess_blob_gas)) = ( + test.0.env.parent_blob_gas_used, + test.0.env.parent_excess_blob_gas, + ) { + let excess_blob_gas = calc_excess_blob_gas( + parent_blob_gas_used.0.as_u64(), + parent_excess_blob_gas.0.as_u64(), + ); + Some(calc_blob_gas_price(excess_blob_gas)) + } else { + None + }; + // EIP-4844 + let data_max_fee = if gasometer_config.has_shard_blob_transactions { + let max_fee_per_blob_gas = test_tx.max_fee_per_blob_gas.unwrap_or_default().0; + Some(calc_max_data_fee( + max_fee_per_blob_gas, + test_tx.blob_versioned_hashes.len(), + )) + } else { + None + }; + let data_fee = if gasometer_config.has_shard_blob_transactions { + Some(calc_data_fee( + blob_gas_price.expect("expect blob_gas_price"), + test_tx.blob_versioned_hashes.len(), + )) + } else { + None + }; + let original_state = test.unwrap_to_pre_state(); - let vicinity = test.unwrap_to_vicinity(spec); - if vicinity.is_none() { + let vicinity = test.unwrap_to_vicinity(spec, blob_gas_price); + if let Err(tx_err) = vicinity { let h = states.first().unwrap().hash.0; // if vicinity could not be computed then the transaction was invalid so we simply // check the original state and move on - let (is_valid_hash, actual_hash) = assert_valid_hash(&h, &original_state); + let (is_valid_hash, actual_hash) = crate::utils::assert_valid_hash(&h, &original_state); if !is_valid_hash { tests_result.failed_tests.push(FailedTestDetails { expected_hash: h, @@ -487,8 +683,17 @@ fn test_run( spec: spec.clone(), state: original_state, }); + + if verbose_output.verbose_failed { + println!(" [{spec:?}] {name}: {tx_err:?} ... validation failed\t<----"); + } tests_result.failed += 1; } + // Set test to passed as it pass hash-validation + tests_result.total += states.len() as u64; + if verbose_output.verbose_failed { + println!("---> SKIPPED [{tx_err:?}]: [{spec:?}] {name}"); + } continue; } let vicinity = vicinity.unwrap(); @@ -502,7 +707,7 @@ fn test_run( .map_or_else(Vec::new, |acc| acc.code.clone()); for (i, state) in states.iter().enumerate() { - let transaction = test.0.transaction.select(&state.indexes); + let transaction = test_tx.select(&state.indexes); let mut backend = MemoryBackend::new(&vicinity, original_state.clone()); // Test case may be expected to fail with an unsupported tx type if the current fork is @@ -525,15 +730,35 @@ fn test_run( tests_result.total += 1; - // Only execute valid transactions - if let Ok(transaction) = crate::utils::transaction::validate( - transaction, + let gas_limit: u64 = transaction.gas_limit.into(); + let data: Vec = transaction.data.clone().into(); + + let valid_tx = crate::utils::transaction::validate( + &transaction, test.0.env.gas_limit.0, caller_balance, &gasometer_config, - ) { - let gas_limit: u64 = transaction.gas_limit.into(); - let data: Vec = transaction.data.into(); + test_tx, + &vicinity, + blob_gas_price, + data_max_fee, + spec, + ); + if let Err(err) = &valid_tx { + if check_validate_exit_reason(err, &state.expect_exception, name) { + continue; + } + } + + // We do not check overflow after TX validation + let total_fee = if let Some(data_fee) = data_fee { + vicinity.effective_gas_price * gas_limit + data_fee + } else { + vicinity.effective_gas_price * gas_limit + }; + + // Only execute valid transactions + if valid_tx.is_ok() { let metadata = StackSubstateMetadata::new(transaction.gas_limit.into(), &gasometer_config); let executor_state = MemoryStackState::new(metadata, &backend); @@ -543,8 +768,6 @@ fn test_run( &gasometer_config, &precompile, ); - let total_fee = vicinity.gas_price * gas_limit; - executor.state_mut().withdraw(caller, total_fee).unwrap(); let access_list = transaction @@ -559,6 +782,7 @@ fn test_run( ethjson::maybe::MaybeEmpty::Some(to) => { let value = transaction.value.into(); + // Exit reason for Call do not analyzed as it mostly do not expect exceptions let _reason = executor.transact_call( caller, to.into(), @@ -567,6 +791,7 @@ fn test_run( gas_limit, access_list, ); + assert_call_exit_exception(spec, &state.expect_exception); } ethjson::maybe::MaybeEmpty::None => { let code = data; @@ -579,11 +804,17 @@ fn test_run( gas_limit, access_list, ); - if check_create_exit_reason(&reason.0, &state.expect_exception) { + if check_create_exit_reason( + &reason.0, + &state.expect_exception, + &format!("{spec:?}-{name}-{i}"), + ) { continue; } } } + } else { + assert_empty_create_caller(&state.expect_exception, name); } if verbose_output.print_state { @@ -593,16 +824,14 @@ fn test_run( ); } - let actual_fee = executor.fee(vicinity.gas_price); + let actual_fee = executor.fee(vicinity.effective_gas_price); // Forks after London burn miner rewards and thus have different gas fee // calculation (see EIP-1559) let miner_reward = if spec.is_eth2() { - let max_priority_fee_per_gas = test.0.transaction.max_priority_fee_per_gas(); - let max_fee_per_gas = test.0.transaction.max_fee_per_gas(); - let base_fee_per_gas = vicinity.block_base_fee_per_gas; - let priority_fee_per_gas = - std::cmp::min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas); - executor.fee(priority_fee_per_gas) + let coinbase_gas_price = vicinity + .effective_gas_price + .saturating_sub(vicinity.block_base_fee_per_gas); + executor.fee(coinbase_gas_price) } else { actual_fee }; @@ -610,13 +839,26 @@ fn test_run( executor .state_mut() .deposit(vicinity.block_coinbase, miner_reward); - executor.state_mut().deposit(caller, total_fee - actual_fee); + + let amount_to_return_for_caller = data_fee.map_or_else( + || total_fee - actual_fee, + |data_fee| total_fee - actual_fee - data_fee, + ); + executor + .state_mut() + .deposit(caller, amount_to_return_for_caller); let (values, logs) = executor.into_state().deconstruct(); backend.apply(values, logs, delete_empty); + } else { + if let Some(e) = state.expect_exception.as_ref() { + panic!("unexpected exception: {e} for test {name}-{i}"); + } + panic!("unexpected validation for test {name}-{i}") } - let (is_valid_hash, actual_hash) = assert_valid_hash(&state.hash.0, backend.state()); + let (is_valid_hash, actual_hash) = + crate::utils::assert_valid_hash(&state.hash.0, backend.state()); if !is_valid_hash { let failed_res = FailedTestDetails { expected_hash: state.hash.0, @@ -629,7 +871,9 @@ fn test_run( tests_result.failed_tests.push(failed_res); tests_result.failed += 1; - println!(" [{:?}] {}:{} ... failed\t<----", spec, name, i); + if verbose_output.verbose_failed { + println!(" [{spec:?}] {name}:{i} ... failed\t<----"); + } if verbose_output.print_state { // Print detailed state data println!( @@ -640,12 +884,6 @@ fn test_run( // Decode balance let mut write_buf = [0u8; 32]; acc.balance.to_big_endian(&mut write_buf[..]); - // Convert to balance to Hex format - // let balance = if acc.balance > U256::from(u128::MAX) { - // hex::encode(write_buf) - // } else { - // format!("{:x?}", acc.balance.as_u128()) - // }; let balance = acc.balance.to_string(); println!( diff --git a/evm-tests/jsontests/src/utils.rs b/evm-tests/jsontests/src/utils.rs index b6657cbd..4c090bcb 100644 --- a/evm-tests/jsontests/src/utils.rs +++ b/evm-tests/jsontests/src/utils.rs @@ -157,19 +157,31 @@ pub fn flush() { } pub mod transaction { + use ethjson::hash::Address; use ethjson::maybe::MaybeEmpty; + use ethjson::spec::ForkSpec; + use ethjson::test_helpers::state::MultiTransaction; use ethjson::transaction::Transaction; use ethjson::uint::Uint; + use evm::backend::MemoryVicinity; use evm::gasometer::{self, Gasometer}; + use evm::utils::{MAX_BLOB_NUMBER_PER_BLOCK, VERSIONED_HASH_VERSION_KZG}; use primitive_types::{H160, H256, U256}; + // TODO: it will be refactored as old solution inefficient, also will be removed clippy-allow flag + #[allow(clippy::too_many_arguments)] pub fn validate( - tx: Transaction, + tx: &Transaction, block_gas_limit: U256, caller_balance: U256, config: &evm::Config, - ) -> Result { - match intrinsic_gas(&tx, config) { + test_tx: &MultiTransaction, + vicinity: &MemoryVicinity, + blob_gas_price: Option, + data_fee: Option, + spec: &ForkSpec, + ) -> Result<(), InvalidTxReason> { + match intrinsic_gas(tx, config) { None => return Err(InvalidTxReason::IntrinsicGas), Some(required_gas) => { if tx.gas_limit < Uint(U256::from(required_gas)) { @@ -182,21 +194,73 @@ pub mod transaction { return Err(InvalidTxReason::GasLimitReached); } - let required_funds = if let Some(x) = tx.gas_limit.0.checked_mul(tx.gas_price.0) { - if let Some(y) = x.checked_add(tx.value.0) { - y - } else { - return Err(InvalidTxReason::OutOfFund); - } + let required_funds = tx + .gas_limit + .0 + .checked_mul(vicinity.gas_price) + .ok_or(InvalidTxReason::OutOfFund)? + .checked_add(tx.value.0) + .ok_or(InvalidTxReason::OutOfFund)?; + + let required_funds = if let Some(data_fee) = data_fee { + required_funds + .checked_add(data_fee) + .ok_or(InvalidTxReason::OutOfFund)? } else { - return Err(InvalidTxReason::OutOfFund); + required_funds }; - if caller_balance < required_funds { return Err(InvalidTxReason::OutOfFund); } - Ok(tx) + // CANCUN tx validation + // Presence of max_fee_per_blob_gas means that this is blob transaction. + if *spec >= ForkSpec::Cancun { + if let Some(max) = test_tx.max_fee_per_blob_gas { + // ensure that the user was willing to at least pay the current blob gasprice + if U256::from(blob_gas_price.expect("expect blob_gas_price")) > max.0 { + return Err(InvalidTxReason::BlobGasPriceGreaterThanMax); + } + + // there must be at least one blob + if test_tx.blob_versioned_hashes.is_empty() { + return Err(InvalidTxReason::EmptyBlobs); + } + + // The field `to` deviates slightly from the semantics with the exception + // that it MUST NOT be nil and therefore must always represent + // a 20-byte address. This means that blob transactions cannot + // have the form of a create transaction. + let to_address: Option
= test_tx.to.clone().into(); + if to_address.is_none() { + return Err(InvalidTxReason::BlobCreateTransaction); + } + + // all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG + for blob in test_tx.blob_versioned_hashes.iter() { + let mut blob_hash = H256([0; 32]); + blob.to_big_endian(&mut blob_hash[..]); + if blob_hash[0] != VERSIONED_HASH_VERSION_KZG { + return Err(InvalidTxReason::BlobVersionNotSupported); + } + } + + // ensure the total blob gas spent is at most equal to the limit + // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK + if test_tx.blob_versioned_hashes.len() > MAX_BLOB_NUMBER_PER_BLOCK as usize { + return Err(InvalidTxReason::TooManyBlobs); + } + } + } else { + if !test_tx.blob_versioned_hashes.is_empty() { + return Err(InvalidTxReason::BlobVersionedHashesNotSupported); + } + if test_tx.max_fee_per_blob_gas.is_some() { + return Err(InvalidTxReason::MaxFeePerBlobGasNotSupported); + } + } + + Ok(()) } fn intrinsic_gas(tx: &Transaction, config: &evm::Config) -> Option { @@ -223,9 +287,19 @@ pub mod transaction { Some(g.total_used_gas()) } + #[derive(Debug)] pub enum InvalidTxReason { IntrinsicGas, OutOfFund, GasLimitReached, + PriorityFeeTooLarge, + GasPriceLessThenBlockBaseFee, + BlobCreateTransaction, + BlobVersionNotSupported, + TooManyBlobs, + EmptyBlobs, + BlobGasPriceGreaterThanMax, + BlobVersionedHashesNotSupported, + MaxFeePerBlobGasNotSupported, } } diff --git a/evm-tests/jsontests/src/vm.rs b/evm-tests/jsontests/src/vm.rs index 5d388399..f8ab506d 100644 --- a/evm-tests/jsontests/src/vm.rs +++ b/evm-tests/jsontests/src/vm.rs @@ -31,6 +31,7 @@ impl Test { MemoryVicinity { gas_price: self.0.transaction.gas_price.into(), + effective_gas_price: self.0.transaction.gas_price.into(), origin: self.0.transaction.origin.into(), block_hashes: Vec::new(), block_number: self.0.env.number.into(), @@ -41,7 +42,8 @@ impl Test { chain_id: U256::zero(), block_base_fee_per_gas: self.0.transaction.gas_price.into(), block_randomness, - blob_base_fee: None, + blob_gas_price: None, + blob_hashes: Vec::new(), } } diff --git a/evm-tests/jsontests/tests/state.rs b/evm-tests/jsontests/tests/state.rs index 1a109f9d..e0b909a5 100644 --- a/evm-tests/jsontests/tests/state.rs +++ b/evm-tests/jsontests/tests/state.rs @@ -17,8 +17,6 @@ fn short_test_file_name(name: &str) -> String { pub fn run(dir: &str) { const SPEC: Option = Some(ForkSpec::Cancun); - //const SPEC: Option = Some(ForkSpec::Shanghai); - let _ = env_logger::try_init(); let mut dest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); diff --git a/gasometer/Cargo.toml b/gasometer/Cargo.toml index 25f4a2b9..15fe09e4 100644 --- a/gasometer/Cargo.toml +++ b/gasometer/Cargo.toml @@ -19,15 +19,16 @@ evm-runtime = { version = "0.41", path = "../runtime", default-features = false [features] default = ["std"] std = [ - "environmental/std", - "primitive-types/std", - "evm-core/std", - "evm-runtime/std", + "environmental/std", + "primitive-types/std", + "evm-core/std", + "evm-runtime/std", ] tracing = [ - "environmental", + "environmental", ] force-debug = [ - "log", + "log", ] +print-debug = ["force-debug"] diff --git a/gasometer/src/lib.rs b/gasometer/src/lib.rs index 58ced78b..bd3901aa 100644 --- a/gasometer/src/lib.rs +++ b/gasometer/src/lib.rs @@ -32,6 +32,8 @@ macro_rules! log_gas { ($self:expr, $($arg:tt)*) => ( log::trace!(target: "evm", "Gasometer {} [Gas used: {}, Gas left: {}]", format_args!($($arg)*), $self.total_used_gas(), $self.gas()); + #[cfg(feature = "print-debug")] + println!("\t# {} [{} | {}]", format_args!($($arg)*), $self.total_used_gas(), $self.gas()); ); } @@ -182,7 +184,7 @@ impl<'config> Gasometer<'config> { } self.inner_mut()?.used_gas += cost; - log_gas!(self, "Record cost {}", cost); + log_gas!(self, "record_cost: {}", cost); Ok(()) } @@ -193,7 +195,7 @@ impl<'config> Gasometer<'config> { refund, snapshot: self.snapshot(), }); - log_gas!(self, "Record refund -{}", refund); + log_gas!(self, "record_refund: -{}", refund); self.inner_mut()?.refunded_gas += refund; Ok(()) @@ -252,13 +254,8 @@ impl<'config> Gasometer<'config> { inner_mut.memory_gas = memory_gas; inner_mut.refunded_gas += gas_refund; - log_gas!( - self, - "Record dynamic cost {} - memory_gas {} - gas_refund {}", - gas_cost, - memory_gas, - gas_refund - ); + // NOTE Extended meesage: "Record dynamic cost {gas_cost} - memory_gas {} - gas_refund {}", + log_gas!(self, "record_dynamic_cost: {}", gas_cost,); Ok(()) } @@ -272,7 +269,7 @@ impl<'config> Gasometer<'config> { }); self.inner_mut()?.used_gas -= stipend; - log_gas!(self, "Record stipent {}", stipend); + log_gas!(self, "record_stipent: {}", stipend); Ok(()) } diff --git a/runtime/src/eval/system.rs b/runtime/src/eval/system.rs index eb6aba4e..e6b0f438 100644 --- a/runtime/src/eval/system.rs +++ b/runtime/src/eval/system.rs @@ -119,10 +119,7 @@ pub fn blob_hash(runtime: &mut Runtime, handler: &H) -> Control { // Get blob_hash from `tx.blob_versioned_hashes[index]` // as described: // - https://eips.ethereum.org/EIPS/eip-4844#opcode-to-get-versioned-hashes - let blob_hash = match handler.get_blob_hash(index) { - Ok(value) => value, - Err(e) => return Control::Exit(e.into()), - }; + let blob_hash = handler.get_blob_hash(index).unwrap_or(U256::zero()); // Set top stack index with `blob_hash` value if let Err(e) = runtime.machine.stack_mut().set(0, blob_hash) { return Control::Exit(e.into()); diff --git a/runtime/src/handler.rs b/runtime/src/handler.rs index a92f9aed..c832ccc4 100644 --- a/runtime/src/handler.rs +++ b/runtime/src/handler.rs @@ -123,7 +123,7 @@ pub trait Handler { fn blob_base_fee(&self) -> Option; /// Get `blob_hash` from `blob_versioned_hashes` by index /// [EIP-4844]: BLOBHASH - https://eips.ethereum.org/EIPS/eip-4844#opcode-to-get-versioned-hashes - fn get_blob_hash(&self, index: usize) -> Result; + fn get_blob_hash(&self, index: usize) -> Option; /// Set tstorage value of address at index. /// [EIP-1153]: Transient storage fn tstore(&mut self, address: H160, index: H256, value: U256) -> Result<(), ExitError>; diff --git a/src/backend/memory.rs b/src/backend/memory.rs index 5c5f348e..541bada2 100644 --- a/src/backend/memory.rs +++ b/src/backend/memory.rs @@ -1,6 +1,5 @@ use super::{Apply, ApplyBackend, Backend, Basic, Log}; use crate::prelude::*; -use evm_core::ExitError; use primitive_types::{H160, H256, U256}; /// Vicinity value of a memory backend. @@ -13,6 +12,8 @@ use primitive_types::{H160, H256, U256}; pub struct MemoryVicinity { /// Gas price. pub gas_price: U256, + /// Effective gas price. + pub effective_gas_price: U256, /// Origin. pub origin: H160, /// Chain ID. @@ -37,11 +38,10 @@ pub struct MemoryVicinity { /// In Ethereum, this is the randomness beacon provided by the beacon /// chain and is only enabled post Merge. pub block_randomness: Option, - /// Environmental blob base fee per gas. - /// CANCUN hard fork - /// [EIP-4844]: Shard Blob Transactions - /// [EIP-7516]: BLOBBASEFEE instruction - pub blob_base_fee: Option, + /// EIP-4844 + pub blob_gas_price: Option, + /// EIP-4844 + pub blob_hashes: Vec, } /// Account information of a memory backend. @@ -92,8 +92,9 @@ impl<'vicinity> MemoryBackend<'vicinity> { } impl<'vicinity> Backend for MemoryBackend<'vicinity> { + #[allow(clippy::misnamed_getters)] fn gas_price(&self) -> U256 { - self.vicinity.gas_price + self.vicinity.effective_gas_price } fn origin(&self) -> H160 { self.vicinity.origin @@ -166,11 +167,11 @@ impl<'vicinity> Backend for MemoryBackend<'vicinity> { fn original_storage(&self, address: H160, index: H256) -> Option { Some(self.storage(address, index)) } - fn blob_gasprice(&self) -> Option { - self.vicinity.blob_base_fee + fn blob_gas_price(&self) -> Option { + self.vicinity.blob_gas_price } - fn get_blob_hash(&self, _index: usize) -> Result { - Ok(U256::zero()) + fn get_blob_hash(&self, index: usize) -> Option { + self.vicinity.blob_hashes.get(index).cloned() } } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 85205a74..56fbdef1 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -23,7 +23,6 @@ pub struct Basic { } pub use ethereum::Log; -use evm_core::ExitError; /// Apply state operation. #[derive(Clone, Debug)] @@ -88,12 +87,10 @@ pub trait Backend { /// CANCUN hard fork /// [EIP-4844]: Shard Blob Transactions /// [EIP-7516]: BLOBBASEFEE instruction - /// NOTE: `blob_gasprice` should precalculated via [evm_core::utils::calc_blob_gasprice] - /// with `excess_blob_gas` parameter in [Backend] as described in EIP-4844. - fn blob_gasprice(&self) -> Option; + fn blob_gas_price(&self) -> Option; /// Get `blob_hash` from `blob_versioned_hashes` by index /// [EIP-4844]: BLOBHASH - https://eips.ethereum.org/EIPS/eip-4844#opcode-to-get-versioned-hashes - fn get_blob_hash(&self, index: usize) -> Result; + fn get_blob_hash(&self, index: usize) -> Option; } /// EVM backend that can apply changes. diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index be603e34..0066c40f 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1168,6 +1168,8 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Interprete }); } + #[cfg(feature = "print-debug")] + println!("### {opcode}"); if let Some(cost) = gasometer::static_opcode_cost(opcode) { self.state .metadata_mut() @@ -1486,17 +1488,17 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler /// [EIP-7516]: BLOBBASEFEE instruction fn blob_base_fee(&self) -> Option { if self.config.has_blob_base_fee { - self.state.blob_gasprice() + self.state.blob_gas_price() } else { None } } - fn get_blob_hash(&self, index: usize) -> Result { + fn get_blob_hash(&self, index: usize) -> Option { if self.config.has_shard_blob_transactions { self.state.get_blob_hash(index) } else { - Err(ExitError::InvalidCode(Opcode::BLOBHASH)) + None } } diff --git a/src/executor/stack/memory.rs b/src/executor/stack/memory.rs index 659b03ac..885afed1 100644 --- a/src/executor/stack/memory.rs +++ b/src/executor/stack/memory.rs @@ -518,10 +518,10 @@ impl<'backend, 'config, B: Backend> Backend for MemoryStackState<'backend, 'conf self.backend.original_storage(address, key) } - fn blob_gasprice(&self) -> Option { - self.backend.blob_gasprice() + fn blob_gas_price(&self) -> Option { + self.backend.blob_gas_price() } - fn get_blob_hash(&self, index: usize) -> Result { + fn get_blob_hash(&self, index: usize) -> Option { self.backend.get_blob_hash(index) } }