diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 39073c7a7165..c16e747d2283 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -28,7 +28,7 @@ use reth_primitives_traits::transaction::execute::FillTxEnv; use reth_revm::{inspector_handle_register, EvmBuilder}; use revm_primitives::{ AnalysisKind, BlobExcessGasAndPrice, BlockEnv, Bytes, CfgEnv, CfgEnvWithHandlerCfg, EVMError, - HandlerCfg, ResultAndState, SpecId, TxEnv, TxKind, + HaltReason, HandlerCfg, ResultAndState, SpecId, TxEnv, TxKind, }; mod config; @@ -53,6 +53,7 @@ impl Evm for EthEvm<'_, EXT, DB> { type DB = DB; type Tx = TxEnv; type Error = EVMError; + type HaltReason = HaltReason; fn block(&self) -> &BlockEnv { self.0.block() @@ -238,6 +239,7 @@ impl ConfigureEvmEnv for EthEvmConfig { impl ConfigureEvm for EthEvmConfig { type Evm<'a, DB: Database + 'a, I: 'a> = EthEvm<'a, I, DB>; type EvmError = EVMError; + type HaltReason = HaltReason; fn evm_with_env(&self, db: DB, evm_env: EvmEnv) -> Self::Evm<'_, DB, ()> { let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg { diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index f116e11b74f5..2514f33dd3bf 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -54,8 +54,13 @@ pub trait Evm { type DB; /// Transaction environment type Tx; - /// Error type. + /// Error type returned by EVM. Contains either errors related to invalid transactions or + /// internal irrecoverable execution errors. type Error; + /// Halt reason. Enum over all possible reasons for halting the execution. When execution halts, + /// it means that transaction is valid, however, it's execution was interrupted (e.g because of + /// running out of gas or overflowing stack). + type HaltReason; /// Reference to [`BlockEnv`]. fn block(&self) -> &BlockEnv; @@ -96,11 +101,15 @@ pub trait ConfigureEvm: ConfigureEvmEnv { Tx = Self::TxEnv, DB = DB, Error = Self::EvmError, + HaltReason = Self::HaltReason, >; - /// The error type returned by the EVM. + /// The error type returned by the EVM. See [`Evm::Error`]. type EvmError: EvmError; + /// Halt reason type returned by the EVM. See [`Evm::HaltReason`]. + type HaltReason; + /// Returns a new EVM with the given database configured with the given environment settings, /// including the spec id and transaction environment. /// @@ -147,6 +156,7 @@ where { type Evm<'a, DB: Database + 'a, I: 'a> = T::Evm<'a, DB, I>; type EvmError = T::EvmError; + type HaltReason = T::HaltReason; fn evm_for_block(&self, db: DB, header: &Self::Header) -> Self::Evm<'_, DB, ()> { (*self).evm_for_block(db, header) diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 0997dd706fbc..1270c2578c04 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -40,8 +40,8 @@ pub use receipts::*; mod error; pub use error::OpBlockExecutionError; use revm_primitives::{ - BlobExcessGasAndPrice, BlockEnv, Bytes, CfgEnv, EVMError, HandlerCfg, OptimismFields, - ResultAndState, SpecId, TxKind, + BlobExcessGasAndPrice, BlockEnv, Bytes, CfgEnv, EVMError, HaltReason, HandlerCfg, + OptimismFields, ResultAndState, SpecId, TxKind, }; /// OP EVM implementation. @@ -53,6 +53,7 @@ impl Evm for OpEvm<'_, EXT, DB> { type DB = DB; type Tx = TxEnv; type Error = EVMError; + type HaltReason = HaltReason; fn block(&self) -> &BlockEnv { self.0.block() @@ -225,6 +226,7 @@ impl ConfigureEvmEnv for OpEvmConfig { impl ConfigureEvm for OpEvmConfig { type Evm<'a, DB: Database + 'a, I: 'a> = OpEvm<'a, I, DB>; type EvmError = EVMError; + type HaltReason = HaltReason; fn evm_with_env(&self, db: DB, evm_env: EvmEnv) -> Self::Evm<'_, DB, ()> { let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg { diff --git a/crates/optimism/rpc/src/error.rs b/crates/optimism/rpc/src/error.rs index 5147647b2152..516c7ad9417b 100644 --- a/crates/optimism/rpc/src/error.rs +++ b/crates/optimism/rpc/src/error.rs @@ -4,9 +4,9 @@ use alloy_rpc_types_eth::{error::EthRpcErrorCode, BlockError}; use jsonrpsee_types::error::INTERNAL_ERROR_CODE; use reth_optimism_evm::OpBlockExecutionError; use reth_rpc_eth_api::AsEthApiError; -use reth_rpc_eth_types::EthApiError; +use reth_rpc_eth_types::{error::api::FromEvmHalt, EthApiError}; use reth_rpc_server_types::result::{internal_rpc_err, rpc_err}; -use revm::primitives::{EVMError, InvalidTransaction, OptimismInvalidTransaction}; +use revm::primitives::{EVMError, HaltReason, InvalidTransaction, OptimismInvalidTransaction}; /// Optimism specific errors, that extend [`EthApiError`]. #[derive(Debug, thiserror::Error)] @@ -128,3 +128,9 @@ where Self::Eth(error.into()) } } + +impl FromEvmHalt for OpEthApiError { + fn from_evm_halt(halt: HaltReason, gas_limit: u64) -> Self { + EthApiError::from_evm_halt(halt, gas_limit).into() + } +} diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 33339e91cf72..c0bc0a8e9923 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -30,7 +30,7 @@ use reth_revm::{ }; use reth_rpc_eth_types::{ cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, - error::ensure_success, + error::{api::FromEvmHalt, ensure_success}, revm_utils::{ apply_block_overrides, apply_state_overrides, caller_gas_allowance, get_precompiles, }, @@ -441,7 +441,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA match result.result { ExecutionResult::Halt { reason, gas_used } => { let error = - Some(RpcInvalidTransactionError::halt(reason, tx_env.gas_limit()).to_string()); + Some(Self::Error::from_evm_halt(reason, tx_env.gas_limit()).to_string()); return Ok(AccessListResult { access_list, gas_used: U256::from(gas_used), error }) } ExecutionResult::Revert { output, gas_used } => { @@ -456,7 +456,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA let res = match result.result { ExecutionResult::Halt { reason, gas_used } => { let error = - Some(RpcInvalidTransactionError::halt(reason, tx_env.gas_limit()).to_string()); + Some(Self::Error::from_evm_halt(reason, tx_env.gas_limit()).to_string()); AccessListResult { access_list, gas_used: U256::from(gas_used), error } } ExecutionResult::Revert { output, gas_used } => { diff --git a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs index ec8b17c765f4..b26f9e25c913 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs @@ -9,12 +9,9 @@ use reth_chainspec::MIN_TRANSACTION_GAS; use reth_errors::ProviderError; use reth_evm::{env::EvmEnv, ConfigureEvmEnv, Database, TransactionEnv}; use reth_provider::StateProvider; -use reth_revm::{ - database::StateProviderDatabase, - db::CacheDB, - primitives::{ExecutionResult, HaltReason}, -}; +use reth_revm::{database::StateProviderDatabase, db::CacheDB, primitives::ExecutionResult}; use reth_rpc_eth_types::{ + error::api::FromEvmHalt, revm_utils::{apply_state_overrides, caller_gas_allowance}, EthApiError, RevertError, RpcInvalidTransactionError, }; @@ -148,10 +145,10 @@ pub trait EstimateCall: Call { let gas_refund = match res.result { ExecutionResult::Success { gas_refunded, .. } => gas_refunded, - ExecutionResult::Halt { reason, gas_used } => { + ExecutionResult::Halt { reason, .. } => { // here we don't check for invalid opcode because already executed with highest gas // limit - return Err(RpcInvalidTransactionError::halt(reason, gas_used).into_eth_err()) + return Err(Self::Error::from_evm_halt(reason, tx_env.gas_limit())) } ExecutionResult::Revert { output, .. } => { // if price or limit was included in the request then we can execute the request @@ -330,32 +327,17 @@ pub fn update_estimated_gas_range( // Cap the highest gas limit with the succeeding gas limit. *highest_gas_limit = tx_gas_limit; } - ExecutionResult::Revert { .. } => { - // Increase the lowest gas limit. + ExecutionResult::Revert { .. } | ExecutionResult::Halt { .. } => { + // We know that transaction succeeded with a higher gas limit before, so any failure + // means that we need to increase it. + // + // We are ignoring all halts here, and not just OOG errors because there are cases when + // non-OOG halt might flag insufficient gas limit as well. + // + // Common usage of invalid opcode in OpenZeppelin: + // *lowest_gas_limit = tx_gas_limit; } - ExecutionResult::Halt { reason, .. } => { - match reason { - HaltReason::OutOfGas(_) | HaltReason::InvalidFEOpcode => { - // Both `OutOfGas` and `InvalidEFOpcode` can occur dynamically if the gas - // left is too low. Treat this as an out of gas - // condition, knowing that the call succeeds with a - // higher gas limit. - // - // Common usage of invalid opcode in OpenZeppelin: - // - - // Increase the lowest gas limit. - *lowest_gas_limit = tx_gas_limit; - } - err => { - // These cases should be unreachable because we know the transaction - // succeeds, but if they occur, treat them as an - // error. - return Err(RpcInvalidTransactionError::EvmHalt(err).into_eth_err()) - } - } - } }; Ok(()) diff --git a/crates/rpc/rpc-eth-types/src/error/api.rs b/crates/rpc/rpc-eth-types/src/error/api.rs index bb2b5617eddb..81f38ed29865 100644 --- a/crates/rpc/rpc-eth-types/src/error/api.rs +++ b/crates/rpc/rpc-eth-types/src/error/api.rs @@ -1,10 +1,10 @@ //! Helper traits to wrap generic l1 errors, in network specific error type configured in //! `reth_rpc_eth_api::EthApiTypes`. +use crate::{EthApiError, RpcInvalidTransactionError}; use reth_errors::ProviderError; use reth_evm::ConfigureEvm; - -use crate::EthApiError; +use revm_primitives::HaltReason; /// Helper trait to wrap core [`EthApiError`]. pub trait FromEthApiError: From { @@ -53,7 +53,7 @@ pub trait AsEthApiError { fn as_err(&self) -> Option<&EthApiError>; /// Returns `true` if error is - /// [`RpcInvalidTransactionError::GasTooHigh`](crate::RpcInvalidTransactionError::GasTooHigh). + /// [`RpcInvalidTransactionError::GasTooHigh`]. fn is_gas_too_high(&self) -> bool { if let Some(err) = self.as_err() { return err.is_gas_too_high() @@ -63,7 +63,7 @@ pub trait AsEthApiError { } /// Returns `true` if error is - /// [`RpcInvalidTransactionError::GasTooLow`](crate::RpcInvalidTransactionError::GasTooLow). + /// [`RpcInvalidTransactionError::GasTooLow`]. fn is_gas_too_low(&self) -> bool { if let Some(err) = self.as_err() { return err.is_gas_too_low() @@ -80,7 +80,9 @@ impl AsEthApiError for EthApiError { } /// Helper trait to convert from revm errors. -pub trait FromEvmError: From> { +pub trait FromEvmError: + From> + FromEvmHalt +{ /// Converts from EVM error to this type. fn from_evm_err(err: Evm::EvmError) -> Self { err.into() @@ -89,7 +91,19 @@ pub trait FromEvmError: From> { impl FromEvmError for T where - T: From>, + T: From> + FromEvmHalt, Evm: ConfigureEvm, { } + +/// Helper trait to convert from revm errors. +pub trait FromEvmHalt { + /// Converts from EVM halt to this type. + fn from_evm_halt(halt: Halt, gas_limit: u64) -> Self; +} + +impl FromEvmHalt for EthApiError { + fn from_evm_halt(halt: HaltReason, gas_limit: u64) -> Self { + RpcInvalidTransactionError::halt(halt, gas_limit).into() + } +} diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 0782d62ac200..dec476e26ee2 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -15,8 +15,11 @@ use revm::Database; use revm_primitives::{Address, Bytes, ExecutionResult, TxKind}; use crate::{ - error::{api::FromEthApiError, ToRpcError}, - EthApiError, RevertError, RpcInvalidTransactionError, + error::{ + api::{FromEthApiError, FromEvmHalt}, + ToRpcError, + }, + EthApiError, RevertError, }; /// Errors which may occur during `eth_simulateV1` execution. @@ -118,7 +121,7 @@ pub fn build_simulated_block( block: B, ) -> Result>>, T::Error> where - T: TransactionCompat, Error: FromEthApiError>, + T: TransactionCompat, Error: FromEthApiError + FromEvmHalt>, B: reth_primitives_traits::Block, { let mut calls: Vec = Vec::with_capacity(results.len()); @@ -127,12 +130,12 @@ where for (index, (result, tx)) in results.iter().zip(block.body().transactions()).enumerate() { let call = match result { ExecutionResult::Halt { reason, gas_used } => { - let error = RpcInvalidTransactionError::halt(*reason, tx.gas_limit()); + let error = T::Error::from_evm_halt(*reason, tx.gas_limit()); SimCallResult { return_data: Bytes::new(), error: Some(SimulateError { - code: error.error_code(), message: error.to_string(), + code: error.into().code(), }), gas_used: *gas_used, logs: Vec::new(), diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 08fcc578bc8c..f84708210ca2 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -16,7 +16,8 @@ use reth::{ inspector_handle_register, precompile::{Precompile, PrecompileOutput, PrecompileSpecId}, primitives::{ - CfgEnvWithHandlerCfg, EVMError, Env, HandlerCfg, PrecompileResult, SpecId, TxEnv, + CfgEnvWithHandlerCfg, EVMError, Env, HaltReason, HandlerCfg, PrecompileResult, SpecId, + TxEnv, }, ContextPrecompiles, EvmBuilder, GetInspector, }, @@ -112,6 +113,7 @@ impl ConfigureEvmEnv for MyEvmConfig { impl ConfigureEvm for MyEvmConfig { type Evm<'a, DB: Database + 'a, I: 'a> = EthEvm<'a, I, DB>; type EvmError = EVMError; + type HaltReason = HaltReason; fn evm_with_env(&self, db: DB, evm_env: EvmEnv) -> Self::Evm<'_, DB, ()> { let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg { diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index 99568e9c3adc..50f369af4e9b 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -14,7 +14,7 @@ use reth::{ inspector_handle_register, precompile::{Precompile, PrecompileSpecId}, primitives::{ - CfgEnvWithHandlerCfg, EVMError, Env, HandlerCfg, PrecompileResult, SpecId, + CfgEnvWithHandlerCfg, EVMError, Env, HaltReason, HandlerCfg, PrecompileResult, SpecId, StatefulPrecompileMut, TxEnv, }, ContextPrecompile, ContextPrecompiles, EvmBuilder, GetInspector, @@ -174,6 +174,7 @@ impl ConfigureEvmEnv for MyEvmConfig { impl ConfigureEvm for MyEvmConfig { type Evm<'a, DB: Database + 'a, I: 'a> = EthEvm<'a, I, DB>; type EvmError = EVMError; + type HaltReason = HaltReason; fn evm_with_env(&self, db: DB, evm_env: EvmEnv) -> Self::Evm<'_, DB, ()> { let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg {