Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(levm): ensure returned errors are correct #1225

Merged
70 changes: 68 additions & 2 deletions cmd/ef_tests/levm/deserialize.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Vec<TransactionExpectedException>>, D::Error>
where
D: Deserializer<'de>,
{
let option: Option<String> = 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<HashMap<String, U256>, D::Error>
Expand Down
89 changes: 84 additions & 5 deletions cmd/ef_tests/levm/runner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
report::EFTestsReport,
types::{EFTest, EFTestPostValue},
types::{EFTest, EFTestPostValue, TransactionExpectedException},
utils,
};
use ethrex_core::{
Expand Down Expand Up @@ -76,7 +76,7 @@ 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(())
}

Expand Down Expand Up @@ -242,6 +242,46 @@ fn get_post_value(test: &EFTest, tx_id: usize) -> Option<EFTestPostValue> {
}
}

fn exception_is_expected(
expected_exceptions: Vec<TransactionExpectedException>,
returned_error: VMError,
) -> bool {
for expected_exception in expected_exceptions {
if matches!(
(expected_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
)
) {
return true;
}
}

false
}

pub fn ensure_post_state(
execution_result: Result<TransactionReport, VMError>,
test: &EFTest,
Expand All @@ -253,8 +293,23 @@ 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.
Expand Down Expand Up @@ -282,7 +337,31 @@ pub fn ensure_post_state(
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)) => {
println!("Expected exception is {:?}", 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:?}");
Expand Down
33 changes: 29 additions & 4 deletions cmd/ef_tests/levm/types.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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<String>,
#[serde(
rename = "expectException",
default,
deserialize_with = "deserialize_transaction_expected_exception"
)]
pub expect_exception: Option<Vec<TransactionExpectedException>>,
pub hash: H256,
#[serde(deserialize_with = "deserialize_ef_post_value_indexes")]
pub indexes: HashMap<String, U256>,
Expand Down
3 changes: 2 additions & 1 deletion crates/vm/levm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
Loading