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/error/api.rs b/crates/rpc/rpc-eth-types/src/error/api.rs index a39e8ea25e4..744314ecb01 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,32 @@ 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 { diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 4fd93e12679..7f122723fa3 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,67 @@ 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 { - const fn error_code(&self) -> i32 { + /// Returns the JSON-RPC error code for a `eth_simulateV1` error. + pub const fn error_code(&self) -> i32 { match self { + 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, } }