From 65f8fb3e99a4b5a8249000a50e1cc682045a0cc2 Mon Sep 17 00:00:00 2001 From: figtracer <1gusredo@gmail.com> Date: Wed, 3 Dec 2025 15:18:09 +0000 Subject: [PATCH 1/6] extend EthSimulateError --- crates/rpc/rpc-eth-types/src/simulate.rs | 59 +++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 4fd93e12679..155dd90e761 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -23,7 +23,7 @@ use reth_storage_api::noop::NoopProvider; use revm::{ context::Block, context_interface::result::ExecutionResult, - primitives::{Address, Bytes, TxKind}, + primitives::{Address, Bytes, TxKind, U256}, Database, }; @@ -36,12 +36,69 @@ pub enum EthSimulateError { /// Max gas limit for entire operation exceeded. #[error("Client adjustable limit reached")] GasLimitReached, + /// Block number in sequence did not increase. + #[error("Block number in sequence did not increase")] + BlockNumberInvalid, + /// Block timestamp in sequence did not increase or stay the same. + #[error("Block timestamp in sequence did not increase")] + BlockTimestampInvalid, + /// Transaction nonce is too low. + #[error("nonce too low: next nonce {state}, tx nonce {tx}")] + NonceTooLow { + /// Transaction nonce. + tx: u64, + /// Current state nonce. + state: u64, + }, + /// Transaction nonce is too high. + #[error("nonce too high")] + NonceTooHigh, + /// Transaction's baseFeePerGas is too low. + #[error("max fee per gas less than block base fee")] + BaseFeePerGasTooLow, + /// Not enough gas provided to pay for intrinsic gas. + #[error("intrinsic gas too low")] + IntrinsicGasTooLow, + /// Insufficient funds to pay for gas fees and value. + #[error("insufficient funds for gas * price + value: have {balance} want {cost}")] + InsufficientFunds { + /// Transaction cost. + cost: U256, + /// Sender balance. + balance: U256, + }, + /// Sender is not an EOA. + #[error("sender is not an EOA")] + SenderNotEOA, + /// Max init code size exceeded. + #[error("max initcode size exceeded")] + MaxInitCodeSizeExceeded, + /// `MovePrecompileToAddress` referenced itself in replacement. + #[error("MovePrecompileToAddress referenced itself")] + PrecompileSelfReference, + /// Multiple `MovePrecompileToAddress` referencing the same address. + #[error("Multiple MovePrecompileToAddress referencing the same address")] + PrecompileDuplicateAddress, } + impl EthSimulateError { + /// Returns the JSON-RPC error code for this error. const fn error_code(&self) -> i32 { match self { + // Transaction errors + Self::NonceTooLow { .. } => -38010, + Self::NonceTooHigh => -38011, + Self::BaseFeePerGasTooLow => -38012, + Self::IntrinsicGasTooLow => -38013, + Self::InsufficientFunds { .. } => -38014, Self::BlockGasLimitExceeded => -38015, + Self::BlockNumberInvalid => -38020, + Self::BlockTimestampInvalid => -38021, + Self::PrecompileSelfReference => -38022, + Self::PrecompileDuplicateAddress => -38023, + Self::SenderNotEOA => -38024, + Self::MaxInitCodeSizeExceeded => -38025, Self::GasLimitReached => -38026, } } From 2cffc84e224f157eb5a6c11e6e287a80f7f65844 Mon Sep 17 00:00:00 2001 From: figtracer <1gusredo@gmail.com> Date: Wed, 3 Dec 2025 15:53:50 +0000 Subject: [PATCH 2/6] extend trait AsEthApiError with as_simulate_error() + fmt --- crates/rpc/rpc-eth-types/src/error/api.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-eth-types/src/error/api.rs b/crates/rpc/rpc-eth-types/src/error/api.rs index a39e8ea25e4..aa4c4760fab 100644 --- a/crates/rpc/rpc-eth-types/src/error/api.rs +++ b/crates/rpc/rpc-eth-types/src/error/api.rs @@ -1,7 +1,7 @@ //! Helper traits to wrap generic l1 errors, in network specific error type configured in //! `reth_rpc_eth_api::EthApiTypes`. -use crate::{EthApiError, RevertError}; +use crate::{simulate::EthSimulateError, EthApiError, RevertError}; use alloy_primitives::Bytes; use reth_errors::ProviderError; use reth_evm::{ConfigureEvm, EvmErrorFor, HaltReasonFor}; @@ -74,6 +74,27 @@ pub trait AsEthApiError { false } + + /// Returns [`EthSimulateError`] if this error maps to a simulate-specific error code. + fn as_simulate_error(&self) -> Option { + let err = self.as_err()?; + match err { + EthApiError::InvalidTransaction(tx_err) => match tx_err { + RpcInvalidTransactionError::NonceTooLow { tx, state } => + Some(EthSimulateError::NonceTooLow { tx: *tx, state: *state }), + RpcInvalidTransactionError::NonceTooHigh => Some(EthSimulateError::NonceTooHigh), + RpcInvalidTransactionError::FeeCapTooLow => Some(EthSimulateError::BaseFeePerGasTooLow), + RpcInvalidTransactionError::GasTooLow => Some(EthSimulateError::IntrinsicGasTooLow), + RpcInvalidTransactionError::InsufficientFunds { cost, balance } => + Some(EthSimulateError::InsufficientFunds { cost: *cost, balance: *balance }), + RpcInvalidTransactionError::SenderNoEOA => Some(EthSimulateError::SenderNotEOA), + RpcInvalidTransactionError::MaxInitCodeSizeExceeded => + Some(EthSimulateError::MaxInitCodeSizeExceeded), + _ => None, + }, + _ => None, + } + } } impl AsEthApiError for EthApiError { From 65c4ad26c4180c44357e77cfd1fdb1cfa2684e12 Mon Sep 17 00:00:00 2001 From: figtracer <1gusredo@gmail.com> Date: Wed, 3 Dec 2025 15:53:59 +0000 Subject: [PATCH 3/6] fmt --- crates/rpc/rpc-eth-types/src/error/api.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/error/api.rs b/crates/rpc/rpc-eth-types/src/error/api.rs index aa4c4760fab..744314ecb01 100644 --- a/crates/rpc/rpc-eth-types/src/error/api.rs +++ b/crates/rpc/rpc-eth-types/src/error/api.rs @@ -80,16 +80,21 @@ pub trait AsEthApiError { let err = self.as_err()?; match err { EthApiError::InvalidTransaction(tx_err) => match tx_err { - RpcInvalidTransactionError::NonceTooLow { tx, state } => - Some(EthSimulateError::NonceTooLow { tx: *tx, state: *state }), + RpcInvalidTransactionError::NonceTooLow { tx, state } => { + Some(EthSimulateError::NonceTooLow { tx: *tx, state: *state }) + } RpcInvalidTransactionError::NonceTooHigh => Some(EthSimulateError::NonceTooHigh), - RpcInvalidTransactionError::FeeCapTooLow => Some(EthSimulateError::BaseFeePerGasTooLow), + RpcInvalidTransactionError::FeeCapTooLow => { + Some(EthSimulateError::BaseFeePerGasTooLow) + } RpcInvalidTransactionError::GasTooLow => Some(EthSimulateError::IntrinsicGasTooLow), - RpcInvalidTransactionError::InsufficientFunds { cost, balance } => - Some(EthSimulateError::InsufficientFunds { cost: *cost, balance: *balance }), + RpcInvalidTransactionError::InsufficientFunds { cost, balance } => { + Some(EthSimulateError::InsufficientFunds { cost: *cost, balance: *balance }) + } RpcInvalidTransactionError::SenderNoEOA => Some(EthSimulateError::SenderNotEOA), - RpcInvalidTransactionError::MaxInitCodeSizeExceeded => - Some(EthSimulateError::MaxInitCodeSizeExceeded), + RpcInvalidTransactionError::MaxInitCodeSizeExceeded => { + Some(EthSimulateError::MaxInitCodeSizeExceeded) + } _ => None, }, _ => None, From 8c7312f2ee4695f971f4b6c4291439fced2fb628 Mon Sep 17 00:00:00 2001 From: figtracer <1gusredo@gmail.com> Date: Wed, 3 Dec 2025 17:51:58 +0000 Subject: [PATCH 4/6] refactor using new method rebase --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 15 ++++++++--- crates/rpc/rpc-eth-types/src/simulate.rs | 31 ++++++++++++++-------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index bc2a39e59c1..28202f81609 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -29,7 +29,7 @@ use reth_revm::{database::StateProviderDatabase, db::State}; use reth_rpc_convert::{RpcConvert, RpcTxReq}; use reth_rpc_eth_types::{ cache::db::StateProviderTraitObjWrapper, - error::FromEthApiError, + error::{AsEthApiError, FromEthApiError}, simulate::{self, EthSimulateError}, EthApiError, StateCacheDb, }; @@ -159,6 +159,13 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA .context_for_next_block(&parent, this.next_env_attributes(&parent)?) .map_err(RethError::other) .map_err(Self::Error::from_eth_err)?; + let map_err = |e: EthApiError| -> Self::Error { + match e.as_simulate_error() { + Some(sim_err) => Self::Error::from_eth_err(EthApiError::other(sim_err)), + None => Self::Error::from_eth_err(e), + } + }; + let (result, results) = if trace_transfers { // prepare inspector to capture transfer inside the evm so they are recorded // and included in logs @@ -173,7 +180,8 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA default_gas_limit, chain_id, this.converter(), - )? + ) + .map_err(map_err)? } else { let evm = this.evm_config().evm_with_env(&mut db, evm_env); let builder = this.evm_config().create_block_builder(evm, &parent, ctx); @@ -183,7 +191,8 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA default_gas_limit, chain_id, this.converter(), - )? + ) + .map_err(map_err)? }; parent = result.block.clone_sealed_header(); diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 155dd90e761..df48fdc0e1c 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -1,7 +1,10 @@ //! Utilities for serving `eth_simulateV1` use crate::{ - error::{api::FromEthApiError, FromEvmError, ToRpcError}, + error::{ + api::{AsEthApiError, FromEthApiError}, + FromEvmError, ToRpcError, + }, EthApiError, }; use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _}; @@ -81,10 +84,9 @@ pub enum EthSimulateError { PrecompileDuplicateAddress, } - impl EthSimulateError { /// Returns the JSON-RPC error code for this error. - const fn error_code(&self) -> i32 { + pub const fn error_code(&self) -> i32 { match self { // Transaction errors Self::NonceTooLow { .. } => -38010, @@ -249,6 +251,7 @@ where Err: std::error::Error + FromEthApiError + FromEvmError + + AsEthApiError + From + Into>, T: RpcConvert, @@ -260,12 +263,15 @@ where let call = match result { ExecutionResult::Halt { reason, gas_used } => { let error = Err::from_evm_halt(reason, tx.gas_limit()); + let message = error.to_string(); + // Use simulate-specific error code if available, otherwise fall back to standard + let code = error + .as_simulate_error() + .map(|sim_err| sim_err.error_code()) + .unwrap_or_else(|| error.into().code()); SimCallResult { return_data: Bytes::new(), - error: Some(SimulateError { - message: error.to_string(), - code: error.into().code(), - }), + error: Some(SimulateError { message, code }), gas_used, logs: Vec::new(), status: false, @@ -273,12 +279,15 @@ where } ExecutionResult::Revert { output, gas_used } => { let error = Err::from_revert(output.clone()); + let message = error.to_string(); + // Use simulate-specific error code if available, otherwise fall back to standard + let code = error + .as_simulate_error() + .map(|sim_err| sim_err.error_code()) + .unwrap_or_else(|| error.into().code()); SimCallResult { return_data: output, - error: Some(SimulateError { - message: error.to_string(), - code: error.into().code(), - }), + error: Some(SimulateError { message, code }), gas_used, status: false, logs: Vec::new(), From 8eca3634da8cf232aa8a98c7772f6ff31f6ae699 Mon Sep 17 00:00:00 2001 From: figtracer <1gusredo@gmail.com> Date: Wed, 3 Dec 2025 18:23:27 +0000 Subject: [PATCH 5/6] removed unecessary refactor --- crates/rpc/rpc-eth-types/src/simulate.rs | 28 ++++++++---------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index df48fdc0e1c..22c0a76b1ba 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -1,10 +1,7 @@ //! Utilities for serving `eth_simulateV1` use crate::{ - error::{ - api::{AsEthApiError, FromEthApiError}, - FromEvmError, ToRpcError, - }, + error::{api::FromEthApiError, FromEvmError, ToRpcError}, EthApiError, }; use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _}; @@ -251,7 +248,6 @@ where Err: std::error::Error + FromEthApiError + FromEvmError - + AsEthApiError + From + Into>, T: RpcConvert, @@ -263,15 +259,12 @@ where let call = match result { ExecutionResult::Halt { reason, gas_used } => { let error = Err::from_evm_halt(reason, tx.gas_limit()); - let message = error.to_string(); - // Use simulate-specific error code if available, otherwise fall back to standard - let code = error - .as_simulate_error() - .map(|sim_err| sim_err.error_code()) - .unwrap_or_else(|| error.into().code()); SimCallResult { return_data: Bytes::new(), - error: Some(SimulateError { message, code }), + error: Some(SimulateError { + message: error.to_string(), + code: error.into().code(), + }), gas_used, logs: Vec::new(), status: false, @@ -279,15 +272,12 @@ where } ExecutionResult::Revert { output, gas_used } => { let error = Err::from_revert(output.clone()); - let message = error.to_string(); - // Use simulate-specific error code if available, otherwise fall back to standard - let code = error - .as_simulate_error() - .map(|sim_err| sim_err.error_code()) - .unwrap_or_else(|| error.into().code()); SimCallResult { return_data: output, - error: Some(SimulateError { message, code }), + error: Some(SimulateError { + message: error.to_string(), + code: error.into().code(), + }), gas_used, status: false, logs: Vec::new(), From cb6cf78ab19a5547a45584c235a95631bb688802 Mon Sep 17 00:00:00 2001 From: figtracer <1gusredo@gmail.com> Date: Wed, 3 Dec 2025 18:40:55 +0000 Subject: [PATCH 6/6] clarified comment --- crates/rpc/rpc-eth-types/src/simulate.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 22c0a76b1ba..7f122723fa3 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -82,10 +82,9 @@ pub enum EthSimulateError { } impl EthSimulateError { - /// Returns the JSON-RPC error code for this error. + /// Returns the JSON-RPC error code for a `eth_simulateV1` error. pub const fn error_code(&self) -> i32 { match self { - // Transaction errors Self::NonceTooLow { .. } => -38010, Self::NonceTooHigh => -38011, Self::BaseFeePerGasTooLow => -38012,