diff --git a/cmd/ef_tests/levm/deserialize.rs b/cmd/ef_tests/levm/deserialize.rs index 89b4f51e97..a733797ff8 100644 --- a/cmd/ef_tests/levm/deserialize.rs +++ b/cmd/ef_tests/levm/deserialize.rs @@ -1,11 +1,77 @@ -use crate::types::EFTest; +use crate::types::{EFTest, TransactionExpectedException}; use bytes::Bytes; use ethrex_core::{H256, U256}; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use std::{collections::HashMap, str::FromStr}; use crate::types::{EFTestRawTransaction, EFTestTransaction}; +pub fn deserialize_transaction_expected_exception<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let option: Option = Option::deserialize(deserializer)?; + + if let Some(value) = option { + let exceptions = value + .split('|') + .map(|s| match s.trim() { + "TransactionException.INITCODE_SIZE_EXCEEDED" => { + TransactionExpectedException::InitcodeSizeExceeded + } + "TransactionException.NONCE_IS_MAX" => TransactionExpectedException::NonceIsMax, + "TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED" => { + TransactionExpectedException::Type3TxBlobCountExceeded + } + "TransactionException.TYPE_3_TX_ZERO_BLOBS" => { + TransactionExpectedException::Type3TxZeroBlobs + } + "TransactionException.TYPE_3_TX_CONTRACT_CREATION" => { + TransactionExpectedException::Type3TxContractCreation + } + "TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH" => { + TransactionExpectedException::Type3TxInvalidBlobVersionedHash + } + "TransactionException.INTRINSIC_GAS_TOO_LOW" => { + TransactionExpectedException::IntrinsicGasTooLow + } + "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS" => { + TransactionExpectedException::InsufficientAccountFunds + } + "TransactionException.SENDER_NOT_EOA" => TransactionExpectedException::SenderNotEoa, + "TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS" => { + TransactionExpectedException::PriorityGreaterThanMaxFeePerGas + } + "TransactionException.GAS_ALLOWANCE_EXCEEDED" => { + TransactionExpectedException::GasAllowanceExceeded + } + "TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS" => { + TransactionExpectedException::InsufficientMaxFeePerGas + } + "TransactionException.RLP_INVALID_VALUE" => { + TransactionExpectedException::RlpInvalidValue + } + "TransactionException.GASLIMIT_PRICE_PRODUCT_OVERFLOW" => { + TransactionExpectedException::GasLimitPriceProductOverflow + } + "TransactionException.TYPE_3_TX_PRE_FORK" => { + TransactionExpectedException::Type3TxPreFork + } + "TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS" => { + TransactionExpectedException::InsufficientMaxFeePerBlobGas + } + other => panic!("Unexpected error type: {}", other), // Should not fail, TODO is to return an error + }) + .collect(); + + Ok(Some(exceptions)) + } else { + Ok(None) + } +} + pub fn deserialize_ef_post_value_indexes<'de, D>( deserializer: D, ) -> Result, D::Error> diff --git a/cmd/ef_tests/levm/runner.rs b/cmd/ef_tests/levm/runner.rs index 49afa45b08..c777637cbb 100644 --- a/cmd/ef_tests/levm/runner.rs +++ b/cmd/ef_tests/levm/runner.rs @@ -1,6 +1,6 @@ use crate::{ report::EFTestsReport, - types::{EFTest, EFTestPostValue}, + types::{EFTest, EFTestPostValue, TransactionExpectedException}, utils, }; use ethrex_core::{ @@ -76,12 +76,12 @@ pub fn run_ef_test_tx( let mut evm = prepare_vm_for_tx(tx_id, test)?; ensure_pre_state(&evm, test)?; let execution_result = evm.transact(); - // ensure_post_state(execution_result, test, report, tx_id)?; + ensure_post_state(execution_result, test, report, tx_id)?; Ok(()) } pub fn run_ef_test(test: EFTest, report: &mut EFTestsReport) -> Result<(), Box> { - println!("Running test: {}", &test.name); + //println!("Running test: {}", &test.name); let mut failed = false; for (tx_id, (tx_indexes, _tx)) in test.transactions.iter().enumerate() { // Code for debugging a specific case. @@ -242,6 +242,61 @@ fn get_post_value(test: &EFTest, tx_id: usize) -> Option { } } +// Exceptions not covered: RlpInvalidValue and Type3TxPreFork +fn exception_is_expected( + expected_exceptions: Vec, + returned_error: VMError, +) -> bool { + expected_exceptions.iter().any(|exception| { + matches!( + (exception, &returned_error), + ( + TransactionExpectedException::IntrinsicGasTooLow, + VMError::IntrinsicGasTooLow + ) | ( + TransactionExpectedException::InsufficientAccountFunds, + VMError::InsufficientAccountFunds + ) | ( + TransactionExpectedException::PriorityGreaterThanMaxFeePerGas, + VMError::PriorityGreaterThanMaxFeePerGas + ) | ( + TransactionExpectedException::GasLimitPriceProductOverflow, + VMError::GasLimitPriceProductOverflow + ) | ( + TransactionExpectedException::SenderNotEoa, + VMError::SenderNotEOA + ) | ( + TransactionExpectedException::InsufficientMaxFeePerGas, + VMError::InsufficientMaxFeePerGas + ) | ( + TransactionExpectedException::NonceIsMax, + VMError::NonceIsMax + ) | ( + TransactionExpectedException::GasAllowanceExceeded, + VMError::GasAllowanceExceeded + ) | ( + TransactionExpectedException::Type3TxBlobCountExceeded, + VMError::Type3TxBlobCountExceeded + ) | ( + TransactionExpectedException::Type3TxZeroBlobs, + VMError::Type3TxZeroBlobs + ) | ( + TransactionExpectedException::Type3TxContractCreation, + VMError::Type3TxContractCreation + ) | ( + TransactionExpectedException::Type3TxInvalidBlobVersionedHash, + VMError::Type3TxInvalidBlobVersionedHash + ) | ( + TransactionExpectedException::InsufficientMaxFeePerBlobGas, + VMError::InsufficientMaxFeePerBlobGas + ) | ( + TransactionExpectedException::InitcodeSizeExceeded, + VMError::InitcodeSizeExceeded + ) + ) + }) +} + pub fn ensure_post_state( execution_result: Result, test: &EFTest, @@ -253,12 +308,26 @@ pub fn ensure_post_state( Ok(execution_report) => { match post_value.clone().map(|v| v.clone().expect_exception) { // Execution result was successful but an exception was expected. - Some(Some(expected_exception)) => { - let error_reason = format!("Expected exception: {expected_exception}"); + Some(Some(expected_exceptions)) => { + let error_reason = match expected_exceptions.get(1) { + Some(second_exception) => { + format!( + "Expected exception: {:?} or {:?}", + expected_exceptions.first().unwrap(), + second_exception + ) + } + None => { + format!( + "Expected exception: {:?}", + expected_exceptions.first().unwrap() + ) + } + }; + return Err(format!("Post-state condition failed: {error_reason}").into()); } // Execution result was successful and no exception was expected. - // TODO: Check that the post-state matches the expected post-state. None | Some(None) => { let pos_state_root = post_state_root(execution_report, test); if let Some(expected_post_state_root_hash) = post_value { @@ -281,8 +350,29 @@ pub fn ensure_post_state( Err(err) => { match post_value.map(|v| v.clone().expect_exception) { // Execution result was unsuccessful and an exception was expected. - // TODO: Check that the exception matches the expected exception. - Some(Some(_expected_exception)) => {} + Some(Some(expected_exceptions)) => { + // Instead of cloning could use references + if !exception_is_expected(expected_exceptions.clone(), err.clone()) { + let error_reason = match expected_exceptions.get(1) { + Some(second_exception) => { + format!( + "Returned exception is not the expected: Returned {:?} but expected {:?} or {:?}", + err, + expected_exceptions.first().unwrap(), + second_exception + ) + } + None => { + format!( + "Returned exception is not the expected: Returned {:?} but expected {:?}", + err, + expected_exceptions.first().unwrap() + ) + } + }; + return Err(format!("Post-state condition failed: {error_reason}").into()); + } + } // Execution result was unsuccessful but no exception was expected. None | Some(None) => { let error_reason = format!("Unexpected exception: {err:?}"); diff --git a/cmd/ef_tests/levm/types.rs b/cmd/ef_tests/levm/types.rs index 6ee27eac44..378a870d48 100644 --- a/cmd/ef_tests/levm/types.rs +++ b/cmd/ef_tests/levm/types.rs @@ -1,7 +1,8 @@ use crate::deserialize::{ deserialize_ef_post_value_indexes, deserialize_h256_vec_optional_safe, deserialize_hex_bytes, - deserialize_hex_bytes_vec, deserialize_u256_optional_safe, deserialize_u256_safe, - deserialize_u256_valued_hashmap_safe, deserialize_u256_vec_safe, + deserialize_hex_bytes_vec, deserialize_transaction_expected_exception, + deserialize_u256_optional_safe, deserialize_u256_safe, deserialize_u256_valued_hashmap_safe, + deserialize_u256_vec_safe, }; use bytes::Bytes; use ethrex_core::{ @@ -99,10 +100,34 @@ impl EFTestPost { } } +#[derive(Debug, Deserialize, Clone)] +pub enum TransactionExpectedException { + InitcodeSizeExceeded, + NonceIsMax, + Type3TxBlobCountExceeded, + Type3TxZeroBlobs, + Type3TxContractCreation, + Type3TxInvalidBlobVersionedHash, + IntrinsicGasTooLow, + InsufficientAccountFunds, + SenderNotEoa, + PriorityGreaterThanMaxFeePerGas, + GasAllowanceExceeded, + InsufficientMaxFeePerGas, + RlpInvalidValue, + GasLimitPriceProductOverflow, + Type3TxPreFork, + InsufficientMaxFeePerBlobGas, +} + #[derive(Debug, Deserialize, Clone)] pub struct EFTestPostValue { - #[serde(rename = "expectException")] - pub expect_exception: Option, + #[serde( + rename = "expectException", + default, + deserialize_with = "deserialize_transaction_expected_exception" + )] + pub expect_exception: Option>, pub hash: H256, #[serde(deserialize_with = "deserialize_ef_post_value_indexes")] pub indexes: HashMap, diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index fc5a6c6bdb..6105953865 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -695,7 +695,8 @@ impl VM { // Access List Cost // TODO: Implement access list cost. - self.increase_consumed_gas(initial_call_frame, intrinsic_gas)?; + self.increase_consumed_gas(initial_call_frame, intrinsic_gas) + .map_err(|_| VMError::IntrinsicGasTooLow)?; Ok(()) }