reth_rpc_eth_types/error/
mod.rs

1//! Implementation specific Errors for the `eth_` namespace.
2
3pub mod api;
4use alloy_eips::BlockId;
5use alloy_evm::{call::CallError, overrides::StateOverrideError};
6use alloy_primitives::{Address, Bytes, B256, U256};
7use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError};
8use alloy_sol_types::{ContractError, RevertReason};
9use alloy_transport::{RpcError, TransportErrorKind};
10pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError};
11use core::time::Duration;
12use reth_errors::{BlockExecutionError, BlockValidationError, RethError};
13use reth_primitives_traits::transaction::{error::InvalidTransactionError, signed::RecoveryError};
14use reth_rpc_convert::{CallFeesError, EthTxEnvError, TransactionConversionError};
15use reth_rpc_server_types::result::{
16    block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code,
17};
18use reth_transaction_pool::error::{
19    Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
20    PoolError, PoolErrorKind, PoolTransactionError,
21};
22use revm::context_interface::result::{
23    EVMError, HaltReason, InvalidHeader, InvalidTransaction, OutOfGasError,
24};
25use revm_inspectors::tracing::{DebugInspectorError, MuxError};
26use std::convert::Infallible;
27use tokio::sync::oneshot::error::RecvError;
28
29/// A trait to convert an error to an RPC error.
30pub trait ToRpcError: core::error::Error + Send + Sync + 'static {
31    /// Converts the error to a JSON-RPC error object.
32    fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static>;
33}
34
35impl ToRpcError for jsonrpsee_types::ErrorObject<'static> {
36    fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> {
37        self.clone()
38    }
39}
40
41impl ToRpcError for RpcError<TransportErrorKind> {
42    fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> {
43        match self {
44            Self::ErrorResp(payload) => jsonrpsee_types::error::ErrorObject::owned(
45                payload.code as i32,
46                payload.message.clone(),
47                payload.data.clone(),
48            ),
49            err => internal_rpc_err(err.to_string()),
50        }
51    }
52}
53
54/// Result alias
55pub type EthResult<T> = Result<T, EthApiError>;
56
57/// Errors that can occur when interacting with the `eth_` namespace
58#[derive(Debug, thiserror::Error)]
59pub enum EthApiError {
60    /// When a raw transaction is empty
61    #[error("empty transaction data")]
62    EmptyRawTransactionData,
63    /// When decoding a signed transaction fails
64    #[error("failed to decode signed transaction")]
65    FailedToDecodeSignedTransaction,
66    /// When the transaction signature is invalid
67    #[error("invalid transaction signature")]
68    InvalidTransactionSignature,
69    /// Errors related to the transaction pool
70    #[error(transparent)]
71    PoolError(#[from] RpcPoolError),
72    /// Header not found for block hash/number/tag
73    #[error("header not found")]
74    HeaderNotFound(BlockId),
75    /// Header range not found for start block hash/number/tag to end block hash/number/tag
76    #[error("header range not found, start block {0:?}, end block {1:?}")]
77    HeaderRangeNotFound(BlockId, BlockId),
78    /// Thrown when historical data is not available because it has been pruned
79    ///
80    /// This error is intended for use as a standard response when historical data is
81    /// requested that has been pruned according to the node's data retention policy.
82    ///
83    /// See also <https://eips.ethereum.org/EIPS/eip-4444>
84    #[error("pruned history unavailable")]
85    PrunedHistoryUnavailable,
86    /// Receipts not found for block hash/number/tag
87    #[error("receipts not found")]
88    ReceiptsNotFound(BlockId),
89    /// Thrown when an unknown block or transaction index is encountered
90    #[error("unknown block or tx index")]
91    UnknownBlockOrTxIndex,
92    /// When an invalid block range is provided
93    #[error("invalid block range")]
94    InvalidBlockRange,
95    /// Requested block number is beyond the head block
96    #[error("request beyond head block: requested {requested}, head {head}")]
97    RequestBeyondHead {
98        /// The requested block number
99        requested: u64,
100        /// The current head block number
101        head: u64,
102    },
103    /// Thrown when the target block for proof computation exceeds the maximum configured window.
104    #[error("distance to target block exceeds maximum proof window")]
105    ExceedsMaxProofWindow,
106    /// An internal error where prevrandao is not set in the evm's environment
107    #[error("prevrandao not in the EVM's environment after merge")]
108    PrevrandaoNotSet,
109    /// `excess_blob_gas` is not set for Cancun and above
110    #[error("excess blob gas missing in the EVM's environment after Cancun")]
111    ExcessBlobGasNotSet,
112    /// Thrown when a call or transaction request (`eth_call`, `eth_estimateGas`,
113    /// `eth_sendTransaction`) contains conflicting fields (legacy, EIP-1559)
114    #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")]
115    ConflictingFeeFieldsInRequest,
116    /// Errors related to invalid transactions
117    #[error(transparent)]
118    InvalidTransaction(#[from] RpcInvalidTransactionError),
119    /// Thrown when constructing an RPC block from primitive block data fails
120    #[error(transparent)]
121    InvalidBlockData(#[from] BlockError),
122    /// Thrown when an `AccountOverride` contains conflicting `state` and `stateDiff` fields
123    #[error("account {0:?} has both 'state' and 'stateDiff'")]
124    BothStateAndStateDiffInOverride(Address),
125    /// Other internal error
126    #[error(transparent)]
127    Internal(RethError),
128    /// Error related to signing
129    #[error(transparent)]
130    Signing(#[from] SignError),
131    /// Thrown when a requested transaction is not found
132    #[error("transaction not found")]
133    TransactionNotFound,
134    /// Some feature is unsupported
135    #[error("unsupported")]
136    Unsupported(&'static str),
137    /// General purpose error for invalid params
138    #[error("{0}")]
139    InvalidParams(String),
140    /// When the tracer config does not match the tracer
141    #[error("invalid tracer config")]
142    InvalidTracerConfig,
143    /// When the percentile array is invalid
144    #[error("invalid reward percentiles")]
145    InvalidRewardPercentiles,
146    /// Error thrown when a spawned blocking task failed to deliver an anticipated response.
147    ///
148    /// This only happens if the blocking task panics and is aborted before it can return a
149    /// response back to the request handler.
150    #[error("internal blocking task error")]
151    InternalBlockingTaskError,
152    /// Error thrown when a spawned blocking task failed to deliver an anticipated response
153    #[error("internal eth error")]
154    InternalEthError,
155    /// Error thrown when a (tracing) call exceeds the configured timeout
156    #[error("execution aborted (timeout = {0:?})")]
157    ExecutionTimedOut(Duration),
158    /// Internal Error thrown by the javascript tracer
159    #[error("{0}")]
160    InternalJsTracerError(String),
161    #[error(transparent)]
162    /// Call Input error when both `data` and `input` fields are set and not equal.
163    TransactionInputError(#[from] TransactionInputError),
164    /// Evm generic purpose error.
165    #[error("Revm error: {0}")]
166    EvmCustom(String),
167    /// Bytecode override is invalid.
168    ///
169    /// This can happen if bytecode provided in an
170    /// [`AccountOverride`](alloy_rpc_types_eth::state::AccountOverride) is malformed, e.g. invalid
171    /// 7702 bytecode.
172    #[error("Invalid bytecode: {0}")]
173    InvalidBytecode(String),
174    /// Error encountered when converting a transaction type
175    #[error(transparent)]
176    TransactionConversionError(#[from] TransactionConversionError),
177    /// Error thrown when tracing with a muxTracer fails
178    #[error(transparent)]
179    MuxTracerError(#[from] MuxError),
180    /// Error thrown when waiting for transaction confirmation times out
181    #[error(
182        "Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}."
183    )]
184    TransactionConfirmationTimeout {
185        /// Hash of the transaction that timed out
186        hash: B256,
187        /// Duration that was waited before timing out
188        duration: Duration,
189    },
190    /// Error thrown when batch tx response channel fails
191    #[error(transparent)]
192    BatchTxRecvError(#[from] RecvError),
193    /// Error thrown when batch tx send channel fails
194    #[error("Batch transaction sender channel closed")]
195    BatchTxSendError,
196    /// Error that occurred during `call_many` execution with bundle and transaction context
197    #[error("call_many error in bundle {bundle_index} and transaction {tx_index}: {}", .error.message())]
198    CallManyError {
199        /// Bundle index where the error occurred
200        bundle_index: usize,
201        /// Transaction index within the bundle where the error occurred  
202        tx_index: usize,
203        /// The underlying error object
204        error: jsonrpsee_types::ErrorObject<'static>,
205    },
206    /// Any other error
207    #[error("{0}")]
208    Other(Box<dyn ToRpcError>),
209}
210
211impl EthApiError {
212    /// Creates a new [`EthApiError::Other`] variant.
213    pub fn other<E: ToRpcError>(err: E) -> Self {
214        Self::Other(Box::new(err))
215    }
216
217    /// Creates a new [`EthApiError::CallManyError`] variant.
218    pub const fn call_many_error(
219        bundle_index: usize,
220        tx_index: usize,
221        error: jsonrpsee_types::ErrorObject<'static>,
222    ) -> Self {
223        Self::CallManyError { bundle_index, tx_index, error }
224    }
225
226    /// Returns `true` if error is [`RpcInvalidTransactionError::GasTooHigh`]
227    pub const fn is_gas_too_high(&self) -> bool {
228        matches!(
229            self,
230            Self::InvalidTransaction(
231                RpcInvalidTransactionError::GasTooHigh |
232                    RpcInvalidTransactionError::GasLimitTooHigh
233            )
234        )
235    }
236
237    /// Returns `true` if error is [`RpcInvalidTransactionError::GasTooLow`]
238    pub const fn is_gas_too_low(&self) -> bool {
239        matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooLow))
240    }
241
242    /// Returns the [`RpcInvalidTransactionError`] if this is a [`EthApiError::InvalidTransaction`]
243    pub const fn as_invalid_transaction(&self) -> Option<&RpcInvalidTransactionError> {
244        match self {
245            Self::InvalidTransaction(e) => Some(e),
246            _ => None,
247        }
248    }
249
250    /// Converts the given [`StateOverrideError`] into a new [`EthApiError`] instance.
251    pub fn from_state_overrides_err<E>(err: StateOverrideError<E>) -> Self
252    where
253        E: Into<Self>,
254    {
255        err.into()
256    }
257
258    /// Converts the given [`CallError`] into a new [`EthApiError`] instance.
259    pub fn from_call_err<E>(err: CallError<E>) -> Self
260    where
261        E: Into<Self>,
262    {
263        err.into()
264    }
265
266    /// Converts this error into the rpc error object.
267    pub fn into_rpc_err(self) -> jsonrpsee_types::error::ErrorObject<'static> {
268        self.into()
269    }
270}
271
272impl From<EthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
273    fn from(error: EthApiError) -> Self {
274        match error {
275            EthApiError::FailedToDecodeSignedTransaction |
276            EthApiError::InvalidTransactionSignature |
277            EthApiError::EmptyRawTransactionData |
278            EthApiError::InvalidBlockRange |
279            EthApiError::RequestBeyondHead { .. } |
280            EthApiError::ExceedsMaxProofWindow |
281            EthApiError::ConflictingFeeFieldsInRequest |
282            EthApiError::Signing(_) |
283            EthApiError::BothStateAndStateDiffInOverride(_) |
284            EthApiError::InvalidTracerConfig |
285            EthApiError::TransactionConversionError(_) |
286            EthApiError::InvalidRewardPercentiles |
287            EthApiError::InvalidBytecode(_) => invalid_params_rpc_err(error.to_string()),
288            EthApiError::InvalidTransaction(err) => err.into(),
289            EthApiError::PoolError(err) => err.into(),
290            EthApiError::PrevrandaoNotSet |
291            EthApiError::ExcessBlobGasNotSet |
292            EthApiError::InvalidBlockData(_) |
293            EthApiError::Internal(_) |
294            EthApiError::EvmCustom(_) => internal_rpc_err(error.to_string()),
295            EthApiError::UnknownBlockOrTxIndex | EthApiError::TransactionNotFound => {
296                rpc_error_with_code(EthRpcErrorCode::ResourceNotFound.code(), error.to_string())
297            }
298            EthApiError::HeaderNotFound(id) | EthApiError::ReceiptsNotFound(id) => {
299                rpc_error_with_code(
300                    EthRpcErrorCode::ResourceNotFound.code(),
301                    format!("block not found: {}", block_id_to_str(id)),
302                )
303            }
304            EthApiError::HeaderRangeNotFound(start_id, end_id) => rpc_error_with_code(
305                EthRpcErrorCode::ResourceNotFound.code(),
306                format!(
307                    "{error}: start block: {}, end block: {}",
308                    block_id_to_str(start_id),
309                    block_id_to_str(end_id),
310                ),
311            ),
312            err @ EthApiError::TransactionConfirmationTimeout { .. } => rpc_error_with_code(
313                EthRpcErrorCode::TransactionConfirmationTimeout.code(),
314                err.to_string(),
315            ),
316            EthApiError::Unsupported(msg) => internal_rpc_err(msg),
317            EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg),
318            EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg),
319            err @ EthApiError::ExecutionTimedOut(_) => rpc_error_with_code(
320                jsonrpsee_types::error::CALL_EXECUTION_FAILED_CODE,
321                err.to_string(),
322            ),
323            err @ (EthApiError::InternalBlockingTaskError | EthApiError::InternalEthError) => {
324                internal_rpc_err(err.to_string())
325            }
326            err @ EthApiError::TransactionInputError(_) => invalid_params_rpc_err(err.to_string()),
327            EthApiError::PrunedHistoryUnavailable => rpc_error_with_code(4444, error.to_string()),
328            EthApiError::Other(err) => err.to_rpc_error(),
329            EthApiError::MuxTracerError(msg) => internal_rpc_err(msg.to_string()),
330            EthApiError::BatchTxRecvError(err) => internal_rpc_err(err.to_string()),
331            EthApiError::BatchTxSendError => {
332                internal_rpc_err("Batch transaction sender channel closed".to_string())
333            }
334            EthApiError::CallManyError { bundle_index, tx_index, error } => {
335                jsonrpsee_types::error::ErrorObject::owned(
336                    error.code(),
337                    format!(
338                        "call_many error in bundle {bundle_index} and transaction {tx_index}: {}",
339                        error.message()
340                    ),
341                    error.data(),
342                )
343            }
344        }
345    }
346}
347
348impl<E> From<CallError<E>> for EthApiError
349where
350    E: Into<Self>,
351{
352    fn from(value: CallError<E>) -> Self {
353        match value {
354            CallError::Database(err) => err.into(),
355            CallError::InsufficientFunds(insufficient_funds_error) => {
356                Self::InvalidTransaction(RpcInvalidTransactionError::InsufficientFunds {
357                    cost: insufficient_funds_error.cost,
358                    balance: insufficient_funds_error.balance,
359                })
360            }
361        }
362    }
363}
364
365impl<E> From<StateOverrideError<E>> for EthApiError
366where
367    E: Into<Self>,
368{
369    fn from(value: StateOverrideError<E>) -> Self {
370        match value {
371            StateOverrideError::InvalidBytecode(bytecode_decode_error) => {
372                Self::InvalidBytecode(bytecode_decode_error.to_string())
373            }
374            StateOverrideError::BothStateAndStateDiff(address) => {
375                Self::BothStateAndStateDiffInOverride(address)
376            }
377            StateOverrideError::Database(err) => err.into(),
378        }
379    }
380}
381
382impl From<EthTxEnvError> for EthApiError {
383    fn from(value: EthTxEnvError) -> Self {
384        match value {
385            EthTxEnvError::CallFees(CallFeesError::BlobTransactionMissingBlobHashes) => {
386                Self::InvalidTransaction(
387                    RpcInvalidTransactionError::BlobTransactionMissingBlobHashes,
388                )
389            }
390            EthTxEnvError::CallFees(CallFeesError::FeeCapTooLow) => {
391                Self::InvalidTransaction(RpcInvalidTransactionError::FeeCapTooLow)
392            }
393            EthTxEnvError::CallFees(CallFeesError::ConflictingFeeFieldsInRequest) => {
394                Self::ConflictingFeeFieldsInRequest
395            }
396            EthTxEnvError::CallFees(CallFeesError::TipAboveFeeCap) => {
397                Self::InvalidTransaction(RpcInvalidTransactionError::TipAboveFeeCap)
398            }
399            EthTxEnvError::CallFees(CallFeesError::TipVeryHigh) => {
400                Self::InvalidTransaction(RpcInvalidTransactionError::TipVeryHigh)
401            }
402            EthTxEnvError::Input(err) => Self::TransactionInputError(err),
403        }
404    }
405}
406
407#[cfg(feature = "js-tracer")]
408impl From<revm_inspectors::tracing::js::JsInspectorError> for EthApiError {
409    fn from(error: revm_inspectors::tracing::js::JsInspectorError) -> Self {
410        match error {
411            err @ revm_inspectors::tracing::js::JsInspectorError::JsError(_) => {
412                Self::InternalJsTracerError(err.to_string())
413            }
414            err => Self::InvalidParams(err.to_string()),
415        }
416    }
417}
418
419impl<Err> From<DebugInspectorError<Err>> for EthApiError
420where
421    Err: core::error::Error + Send + Sync + 'static,
422{
423    fn from(error: DebugInspectorError<Err>) -> Self {
424        match error {
425            DebugInspectorError::InvalidTracerConfig => Self::InvalidTracerConfig,
426            DebugInspectorError::UnsupportedTracer => Self::Unsupported("unsupported tracer"),
427            DebugInspectorError::JsTracerNotEnabled => {
428                Self::Unsupported("JS Tracer is not enabled")
429            }
430            DebugInspectorError::MuxInspector(err) => err.into(),
431            DebugInspectorError::Database(err) => Self::Internal(RethError::other(err)),
432            #[cfg(feature = "js-tracer")]
433            DebugInspectorError::JsInspector(err) => err.into(),
434        }
435    }
436}
437
438impl From<RethError> for EthApiError {
439    fn from(error: RethError) -> Self {
440        match error {
441            RethError::Provider(err) => err.into(),
442            err => Self::Internal(err),
443        }
444    }
445}
446
447impl From<BlockExecutionError> for EthApiError {
448    fn from(error: BlockExecutionError) -> Self {
449        match error {
450            BlockExecutionError::Validation(validation_error) => match validation_error {
451                BlockValidationError::InvalidTx { error, .. } => {
452                    if let Some(invalid_tx) = error.as_invalid_tx_err() {
453                        Self::InvalidTransaction(RpcInvalidTransactionError::from(
454                            invalid_tx.clone(),
455                        ))
456                    } else {
457                        Self::InvalidTransaction(RpcInvalidTransactionError::other(
458                            rpc_error_with_code(
459                                EthRpcErrorCode::TransactionRejected.code(),
460                                error.to_string(),
461                            ),
462                        ))
463                    }
464                }
465                _ => Self::Internal(RethError::Execution(BlockExecutionError::Validation(
466                    validation_error,
467                ))),
468            },
469            BlockExecutionError::Internal(internal_error) => {
470                Self::Internal(RethError::Execution(BlockExecutionError::Internal(internal_error)))
471            }
472        }
473    }
474}
475
476impl From<reth_errors::ProviderError> for EthApiError {
477    fn from(error: reth_errors::ProviderError) -> Self {
478        use reth_errors::ProviderError;
479        match error {
480            ProviderError::HeaderNotFound(hash) => Self::HeaderNotFound(hash.into()),
481            ProviderError::BlockHashNotFound(hash) | ProviderError::UnknownBlockHash(hash) => {
482                Self::HeaderNotFound(hash.into())
483            }
484            ProviderError::BestBlockNotFound => Self::HeaderNotFound(BlockId::latest()),
485            ProviderError::BlockNumberForTransactionIndexNotFound => Self::UnknownBlockOrTxIndex,
486            ProviderError::FinalizedBlockNotFound => Self::HeaderNotFound(BlockId::finalized()),
487            ProviderError::SafeBlockNotFound => Self::HeaderNotFound(BlockId::safe()),
488            err => Self::Internal(err.into()),
489        }
490    }
491}
492
493impl From<InvalidHeader> for EthApiError {
494    fn from(value: InvalidHeader) -> Self {
495        match value {
496            InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
497            InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
498        }
499    }
500}
501
502impl<T, TxError> From<EVMError<T, TxError>> for EthApiError
503where
504    T: Into<Self>,
505    TxError: reth_evm::InvalidTxError,
506{
507    fn from(err: EVMError<T, TxError>) -> Self {
508        match err {
509            EVMError::Transaction(invalid_tx) => {
510                // Try to get the underlying InvalidTransaction if available
511                if let Some(eth_tx_err) = invalid_tx.as_invalid_tx_err() {
512                    // Handle the special NonceTooLow case
513                    match eth_tx_err {
514                        InvalidTransaction::NonceTooLow { tx, state } => {
515                            Self::InvalidTransaction(RpcInvalidTransactionError::NonceTooLow {
516                                tx: *tx,
517                                state: *state,
518                            })
519                        }
520                        _ => RpcInvalidTransactionError::from(eth_tx_err.clone()).into(),
521                    }
522                } else {
523                    // For custom transaction errors that don't wrap InvalidTransaction,
524                    // convert to a custom error message
525                    Self::EvmCustom(invalid_tx.to_string())
526                }
527            }
528            EVMError::Header(err) => err.into(),
529            EVMError::Database(err) => err.into(),
530            EVMError::Custom(err) => Self::EvmCustom(err),
531        }
532    }
533}
534
535impl From<RecoveryError> for EthApiError {
536    fn from(_: RecoveryError) -> Self {
537        Self::InvalidTransactionSignature
538    }
539}
540
541impl From<Infallible> for EthApiError {
542    fn from(_: Infallible) -> Self {
543        unreachable!()
544    }
545}
546
547/// An error due to invalid transaction.
548///
549/// The only reason this exists is to maintain compatibility with other clients de-facto standard
550/// error messages.
551///
552/// These error variants can be thrown when the transaction is checked prior to execution.
553///
554/// These variants also cover all errors that can be thrown by revm.
555///
556/// ## Nomenclature
557///
558/// This type is explicitly modeled after geth's error variants and uses
559///   `fee cap` for `max_fee_per_gas`
560///   `tip` for `max_priority_fee_per_gas`
561#[derive(thiserror::Error, Debug)]
562pub enum RpcInvalidTransactionError {
563    /// returned if the nonce of a transaction is lower than the one present in the local chain.
564    #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
565    NonceTooLow {
566        /// The nonce of the transaction.
567        tx: u64,
568        /// The current state of the nonce in the local chain.
569        state: u64,
570    },
571    /// returned if the nonce of a transaction is higher than the next one expected based on the
572    /// local chain.
573    #[error("nonce too high")]
574    NonceTooHigh,
575    /// Returned if the nonce of a transaction is too high
576    /// Incrementing the nonce would lead to invalid state (overflow)
577    #[error("nonce has max value")]
578    NonceMaxValue,
579    /// thrown if the transaction sender doesn't have enough funds for a transfer
580    #[error("insufficient funds for transfer")]
581    InsufficientFundsForTransfer,
582    /// thrown if creation transaction provides the init code bigger than init code size limit.
583    #[error("max initcode size exceeded")]
584    MaxInitCodeSizeExceeded,
585    /// Represents the inability to cover max fee + value (account balance too low).
586    #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
587    InsufficientFunds {
588        /// Transaction cost.
589        cost: U256,
590        /// Current balance of transaction sender.
591        balance: U256,
592    },
593    /// This is similar to [`Self::InsufficientFunds`] but with a different error message and
594    /// exists for compatibility reasons.
595    ///
596    /// This error is used in `eth_estimateCall` when the highest available gas limit, capped with
597    /// the allowance of the caller is too low: [`Self::GasTooLow`].
598    #[error("gas required exceeds allowance ({gas_limit})")]
599    GasRequiredExceedsAllowance {
600        /// The gas limit the transaction was executed with.
601        gas_limit: u64,
602    },
603    /// Thrown when calculating gas usage
604    #[error("gas uint64 overflow")]
605    GasUintOverflow,
606    /// Thrown if the transaction is specified to use less gas than required to start the
607    /// invocation.
608    #[error("intrinsic gas too low")]
609    GasTooLow,
610    /// Thrown if the transaction gas exceeds the limit
611    #[error("intrinsic gas too high")]
612    GasTooHigh,
613    /// Thrown if the transaction gas limit exceeds the maximum
614    #[error("gas limit too high")]
615    GasLimitTooHigh,
616    /// Thrown if a transaction is not supported in the current network configuration.
617    #[error("transaction type not supported")]
618    TxTypeNotSupported,
619    /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total
620    /// fee cap.
621    #[error("max priority fee per gas higher than max fee per gas")]
622    TipAboveFeeCap,
623    /// A sanity error to avoid huge numbers specified in the tip field.
624    #[error("max priority fee per gas higher than 2^256-1")]
625    TipVeryHigh,
626    /// A sanity error to avoid huge numbers specified in the fee cap field.
627    #[error("max fee per gas higher than 2^256-1")]
628    FeeCapVeryHigh,
629    /// Thrown post London if the transaction's fee is less than the base fee of the block
630    #[error("max fee per gas less than block base fee")]
631    FeeCapTooLow,
632    /// Thrown if the sender of a transaction is a contract.
633    #[error("sender is not an EOA")]
634    SenderNoEOA,
635    /// Gas limit was exceeded during execution.
636    /// Contains the gas limit.
637    #[error("out of gas: gas required exceeds: {0}")]
638    BasicOutOfGas(u64),
639    /// Gas limit was exceeded during memory expansion.
640    /// Contains the gas limit.
641    #[error("out of gas: gas exhausted during memory expansion: {0}")]
642    MemoryOutOfGas(u64),
643    /// Memory limit was exceeded during memory expansion.
644    #[error("out of memory: memory limit exceeded during memory expansion")]
645    MemoryLimitOutOfGas,
646    /// Gas limit was exceeded during precompile execution.
647    /// Contains the gas limit.
648    #[error("out of gas: gas exhausted during precompiled contract execution: {0}")]
649    PrecompileOutOfGas(u64),
650    /// An operand to an opcode was invalid or out of range.
651    /// Contains the gas limit.
652    #[error("out of gas: invalid operand to an opcode: {0}")]
653    InvalidOperandOutOfGas(u64),
654    /// Thrown if executing a transaction failed during estimate/call
655    #[error(transparent)]
656    Revert(RevertError),
657    /// Unspecific EVM halt error.
658    #[error("EVM error: {0:?}")]
659    EvmHalt(HaltReason),
660    /// Invalid chain id set for the transaction.
661    #[error("invalid chain ID")]
662    InvalidChainId,
663    /// The transaction is before Spurious Dragon and has a chain ID
664    #[error("transactions before Spurious Dragon should not have a chain ID")]
665    OldLegacyChainId,
666    /// The transaction is before Berlin and has access list
667    #[error("transactions before Berlin should not have access list")]
668    AccessListNotSupported,
669    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
670    #[error("max_fee_per_blob_gas is not supported for blocks before the Cancun hardfork")]
671    MaxFeePerBlobGasNotSupported,
672    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun
673    /// hardfork.
674    #[error("blob_versioned_hashes is not supported for blocks before the Cancun hardfork")]
675    BlobVersionedHashesNotSupported,
676    /// Block `blob_base_fee` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
677    #[error("max fee per blob gas less than block blob gas fee")]
678    BlobFeeCapTooLow,
679    /// Blob transaction has a versioned hash with an invalid blob
680    #[error("blob hash version mismatch")]
681    BlobHashVersionMismatch,
682    /// Blob transaction has no versioned hashes
683    #[error("blob transaction missing blob hashes")]
684    BlobTransactionMissingBlobHashes,
685    /// Blob transaction has too many blobs
686    #[error("blob transaction exceeds max blobs per block; got {have}")]
687    TooManyBlobs {
688        /// The number of blobs in the transaction.
689        have: usize,
690    },
691    /// Blob transaction is a create transaction
692    #[error("blob transaction is a create transaction")]
693    BlobTransactionIsCreate,
694    /// EIP-7702 is not enabled.
695    #[error("EIP-7702 authorization list not supported")]
696    AuthorizationListNotSupported,
697    /// EIP-7702 transaction has invalid fields set.
698    #[error("EIP-7702 authorization list has invalid fields")]
699    AuthorizationListInvalidFields,
700    /// Transaction priority fee is below the minimum required priority fee.
701    #[error("transaction priority fee below minimum required priority fee {minimum_priority_fee}")]
702    PriorityFeeBelowMinimum {
703        /// Minimum required priority fee.
704        minimum_priority_fee: u128,
705    },
706    /// Any other error
707    #[error("{0}")]
708    Other(Box<dyn ToRpcError>),
709}
710
711impl RpcInvalidTransactionError {
712    /// Creates a new [`RpcInvalidTransactionError::Other`] variant.
713    pub fn other<E: ToRpcError>(err: E) -> Self {
714        Self::Other(Box::new(err))
715    }
716
717    /// Returns the rpc error code for this error.
718    pub const fn error_code(&self) -> i32 {
719        match self {
720            Self::InvalidChainId |
721            Self::GasTooLow |
722            Self::GasTooHigh |
723            Self::GasRequiredExceedsAllowance { .. } |
724            Self::NonceTooLow { .. } |
725            Self::NonceTooHigh { .. } |
726            Self::FeeCapTooLow |
727            Self::FeeCapVeryHigh => EthRpcErrorCode::InvalidInput.code(),
728            Self::Revert(_) => EthRpcErrorCode::ExecutionError.code(),
729            _ => EthRpcErrorCode::TransactionRejected.code(),
730        }
731    }
732
733    /// Converts the halt error
734    ///
735    /// Takes the configured gas limit of the transaction which is attached to the error
736    pub fn halt(reason: HaltReason, gas_limit: u64) -> Self {
737        match reason {
738            HaltReason::OutOfGas(err) => Self::out_of_gas(err, gas_limit),
739            HaltReason::NonceOverflow => Self::NonceMaxValue,
740            err => Self::EvmHalt(err),
741        }
742    }
743
744    /// Converts the out of gas error
745    pub const fn out_of_gas(reason: OutOfGasError, gas_limit: u64) -> Self {
746        match reason {
747            OutOfGasError::Basic | OutOfGasError::ReentrancySentry => {
748                Self::BasicOutOfGas(gas_limit)
749            }
750            OutOfGasError::Memory => Self::MemoryOutOfGas(gas_limit),
751            OutOfGasError::MemoryLimit => Self::MemoryLimitOutOfGas,
752            OutOfGasError::Precompile => Self::PrecompileOutOfGas(gas_limit),
753            OutOfGasError::InvalidOperand => Self::InvalidOperandOutOfGas(gas_limit),
754        }
755    }
756
757    /// Converts this error into the rpc error object.
758    pub fn into_rpc_err(self) -> jsonrpsee_types::error::ErrorObject<'static> {
759        self.into()
760    }
761}
762
763impl From<RpcInvalidTransactionError> for jsonrpsee_types::error::ErrorObject<'static> {
764    fn from(err: RpcInvalidTransactionError) -> Self {
765        match err {
766            RpcInvalidTransactionError::Revert(revert) => {
767                // include out data if some
768                rpc_err(
769                    revert.error_code(),
770                    revert.to_string(),
771                    revert.output.as_ref().map(|out| out.as_ref()),
772                )
773            }
774            RpcInvalidTransactionError::Other(err) => err.to_rpc_error(),
775            err => rpc_err(err.error_code(), err.to_string(), None),
776        }
777    }
778}
779
780impl From<InvalidTransaction> for RpcInvalidTransactionError {
781    fn from(err: InvalidTransaction) -> Self {
782        match err {
783            InvalidTransaction::InvalidChainId | InvalidTransaction::MissingChainId => {
784                Self::InvalidChainId
785            }
786            InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
787            InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
788            InvalidTransaction::CallerGasLimitMoreThanBlock |
789            InvalidTransaction::TxGasLimitGreaterThanCap { .. } => {
790                // tx.gas > block.gas_limit
791                Self::GasTooHigh
792            }
793            InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => {
794                // tx.gas < cost
795                Self::GasTooLow
796            }
797            InvalidTransaction::GasFloorMoreThanGasLimit { .. } => {
798                // Post prague EIP-7623 tx floor calldata gas cost > tx.gas_limit
799                // where floor gas is the minimum amount of gas that will be spent
800                // In other words, the tx's gas limit is lower that the minimum gas requirements of
801                // the tx's calldata
802                Self::GasTooLow
803            }
804            InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
805            InvalidTransaction::LackOfFundForMaxFee { fee, balance } => {
806                Self::InsufficientFunds { cost: *fee, balance: *balance }
807            }
808            InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
809            InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
810            InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
811            InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
812            InvalidTransaction::NonceTooLow { tx, state } => Self::NonceTooLow { tx, state },
813            InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
814            InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
815            InvalidTransaction::BlobVersionedHashesNotSupported => {
816                Self::BlobVersionedHashesNotSupported
817            }
818            InvalidTransaction::BlobGasPriceGreaterThanMax { .. } => Self::BlobFeeCapTooLow,
819            InvalidTransaction::EmptyBlobs => Self::BlobTransactionMissingBlobHashes,
820            InvalidTransaction::BlobVersionNotSupported => Self::BlobHashVersionMismatch,
821            InvalidTransaction::TooManyBlobs { have, .. } => Self::TooManyBlobs { have },
822            InvalidTransaction::BlobCreateTransaction => Self::BlobTransactionIsCreate,
823            InvalidTransaction::AuthorizationListNotSupported => {
824                Self::AuthorizationListNotSupported
825            }
826            InvalidTransaction::AuthorizationListInvalidFields |
827            InvalidTransaction::EmptyAuthorizationList => Self::AuthorizationListInvalidFields,
828            InvalidTransaction::Eip2930NotSupported |
829            InvalidTransaction::Eip1559NotSupported |
830            InvalidTransaction::Eip4844NotSupported |
831            InvalidTransaction::Eip7702NotSupported |
832            InvalidTransaction::Eip7873NotSupported => Self::TxTypeNotSupported,
833            InvalidTransaction::Eip7873MissingTarget => {
834                Self::other(internal_rpc_err(err.to_string()))
835            }
836            InvalidTransaction::Str(_) => Self::other(internal_rpc_err(err.to_string())),
837        }
838    }
839}
840
841impl From<InvalidTransactionError> for RpcInvalidTransactionError {
842    fn from(err: InvalidTransactionError) -> Self {
843        use InvalidTransactionError;
844        // This conversion is used to convert any transaction errors that could occur inside the
845        // txpool (e.g. `eth_sendRawTransaction`) to their corresponding RPC
846        match err {
847            InvalidTransactionError::InsufficientFunds(res) => {
848                Self::InsufficientFunds { cost: res.expected, balance: res.got }
849            }
850            InvalidTransactionError::NonceNotConsistent { tx, state } => {
851                Self::NonceTooLow { tx, state }
852            }
853            InvalidTransactionError::OldLegacyChainId => {
854                // Note: this should be unreachable since Spurious Dragon now enabled
855                Self::OldLegacyChainId
856            }
857            InvalidTransactionError::ChainIdMismatch => Self::InvalidChainId,
858            InvalidTransactionError::Eip2930Disabled |
859            InvalidTransactionError::Eip1559Disabled |
860            InvalidTransactionError::Eip4844Disabled |
861            InvalidTransactionError::Eip7702Disabled |
862            InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported,
863            InvalidTransactionError::GasUintOverflow => Self::GasUintOverflow,
864            InvalidTransactionError::GasTooLow => Self::GasTooLow,
865            InvalidTransactionError::GasTooHigh => Self::GasTooHigh,
866            InvalidTransactionError::TipAboveFeeCap => Self::TipAboveFeeCap,
867            InvalidTransactionError::FeeCapTooLow => Self::FeeCapTooLow,
868            InvalidTransactionError::SignerAccountHasBytecode => Self::SenderNoEOA,
869            InvalidTransactionError::GasLimitTooHigh => Self::GasLimitTooHigh,
870        }
871    }
872}
873
874/// Represents a reverted transaction and its output data.
875///
876/// Displays "execution reverted(: reason)?" if the reason is a string.
877#[derive(Debug, Clone, thiserror::Error)]
878pub struct RevertError {
879    /// The transaction output data
880    ///
881    /// Note: this is `None` if output was empty
882    output: Option<Bytes>,
883}
884
885// === impl RevertError ==
886
887impl RevertError {
888    /// Wraps the output bytes
889    ///
890    /// Note: this is intended to wrap a revm output
891    pub fn new(output: Bytes) -> Self {
892        if output.is_empty() {
893            Self { output: None }
894        } else {
895            Self { output: Some(output) }
896        }
897    }
898
899    /// Returns error code to return for this error.
900    pub const fn error_code(&self) -> i32 {
901        EthRpcErrorCode::ExecutionError.code()
902    }
903}
904
905impl std::fmt::Display for RevertError {
906    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907        f.write_str("execution reverted")?;
908        if let Some(reason) = self.output.as_ref().and_then(|out| RevertReason::decode(out)) {
909            let error = reason.to_string();
910            let mut error = error.as_str();
911            if matches!(reason, RevertReason::ContractError(ContractError::Revert(_))) {
912                // we strip redundant `revert: ` prefix from the revert reason
913                error = error.trim_start_matches("revert: ");
914            }
915            write!(f, ": {error}")?;
916        }
917        Ok(())
918    }
919}
920
921/// A helper error type that's mainly used to mirror `geth` Txpool's error messages
922#[derive(Debug, thiserror::Error)]
923pub enum RpcPoolError {
924    /// When the transaction is already known
925    #[error("already known")]
926    AlreadyKnown,
927    /// When the sender is invalid
928    #[error("invalid sender")]
929    InvalidSender,
930    /// When the transaction is underpriced
931    #[error("transaction underpriced")]
932    Underpriced,
933    /// When the transaction pool is full
934    #[error("txpool is full")]
935    TxPoolOverflow,
936    /// When the replacement transaction is underpriced
937    #[error("replacement transaction underpriced")]
938    ReplaceUnderpriced,
939    /// When the transaction exceeds the block gas limit
940    #[error("exceeds block gas limit")]
941    ExceedsGasLimit,
942    /// When the transaction gas limit exceeds the maximum transaction gas limit
943    #[error("exceeds max transaction gas limit")]
944    MaxTxGasLimitExceeded,
945    /// Thrown when a new transaction is added to the pool, but then immediately discarded to
946    /// respect the tx fee exceeds the configured cap
947    #[error("tx fee ({max_tx_fee_wei} wei) exceeds the configured cap ({tx_fee_cap_wei} wei)")]
948    ExceedsFeeCap {
949        /// max fee in wei of new tx submitted to the pool (e.g. 0.11534 ETH)
950        max_tx_fee_wei: u128,
951        /// configured tx fee cap in wei (e.g. 1.0 ETH)
952        tx_fee_cap_wei: u128,
953    },
954    /// When a negative value is encountered
955    #[error("negative value")]
956    NegativeValue,
957    /// When oversized data is encountered
958    #[error("oversized data: transaction size {size}, limit {limit}")]
959    OversizedData {
960        /// Size of the transaction/input data that exceeded the limit.
961        size: usize,
962        /// Configured limit that was exceeded.
963        limit: usize,
964    },
965    /// When the max initcode size is exceeded
966    #[error("max initcode size exceeded")]
967    ExceedsMaxInitCodeSize,
968    /// Errors related to invalid transactions
969    #[error(transparent)]
970    Invalid(#[from] RpcInvalidTransactionError),
971    /// Custom pool error
972    #[error(transparent)]
973    PoolTransactionError(Box<dyn PoolTransactionError>),
974    /// EIP-4844 related error
975    #[error(transparent)]
976    Eip4844(#[from] Eip4844PoolTransactionError),
977    /// EIP-7702 related error
978    #[error(transparent)]
979    Eip7702(#[from] Eip7702PoolTransactionError),
980    /// Thrown if a conflicting transaction type is already in the pool
981    ///
982    /// In other words, thrown if a transaction with the same sender that violates the exclusivity
983    /// constraint (blob vs normal tx)
984    #[error("address already reserved")]
985    AddressAlreadyReserved,
986    /// Other unspecified error
987    #[error(transparent)]
988    Other(Box<dyn core::error::Error + Send + Sync>),
989}
990
991impl From<RpcPoolError> for jsonrpsee_types::error::ErrorObject<'static> {
992    fn from(error: RpcPoolError) -> Self {
993        match error {
994            RpcPoolError::Invalid(err) => err.into(),
995            RpcPoolError::TxPoolOverflow => {
996                rpc_error_with_code(EthRpcErrorCode::TransactionRejected.code(), error.to_string())
997            }
998            RpcPoolError::AlreadyKnown |
999            RpcPoolError::InvalidSender |
1000            RpcPoolError::Underpriced |
1001            RpcPoolError::ReplaceUnderpriced |
1002            RpcPoolError::ExceedsGasLimit |
1003            RpcPoolError::MaxTxGasLimitExceeded |
1004            RpcPoolError::ExceedsFeeCap { .. } |
1005            RpcPoolError::NegativeValue |
1006            RpcPoolError::OversizedData { .. } |
1007            RpcPoolError::ExceedsMaxInitCodeSize |
1008            RpcPoolError::PoolTransactionError(_) |
1009            RpcPoolError::Eip4844(_) |
1010            RpcPoolError::Eip7702(_) |
1011            RpcPoolError::AddressAlreadyReserved => {
1012                rpc_error_with_code(EthRpcErrorCode::InvalidInput.code(), error.to_string())
1013            }
1014            RpcPoolError::Other(other) => internal_rpc_err(other.to_string()),
1015        }
1016    }
1017}
1018
1019impl From<PoolError> for RpcPoolError {
1020    fn from(err: PoolError) -> Self {
1021        match err.kind {
1022            PoolErrorKind::ReplacementUnderpriced => Self::ReplaceUnderpriced,
1023            PoolErrorKind::FeeCapBelowMinimumProtocolFeeCap(_) => Self::Underpriced,
1024            PoolErrorKind::SpammerExceededCapacity(_) | PoolErrorKind::DiscardedOnInsert => {
1025                Self::TxPoolOverflow
1026            }
1027            PoolErrorKind::InvalidTransaction(err) => err.into(),
1028            PoolErrorKind::Other(err) => Self::Other(err),
1029            PoolErrorKind::AlreadyImported => Self::AlreadyKnown,
1030            PoolErrorKind::ExistingConflictingTransactionType(_, _) => Self::AddressAlreadyReserved,
1031        }
1032    }
1033}
1034
1035impl From<InvalidPoolTransactionError> for RpcPoolError {
1036    fn from(err: InvalidPoolTransactionError) -> Self {
1037        match err {
1038            InvalidPoolTransactionError::Consensus(err) => Self::Invalid(err.into()),
1039            InvalidPoolTransactionError::ExceedsGasLimit(_, _) => Self::ExceedsGasLimit,
1040            InvalidPoolTransactionError::MaxTxGasLimitExceeded(_, _) => Self::MaxTxGasLimitExceeded,
1041            InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei } => {
1042                Self::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1043            }
1044            InvalidPoolTransactionError::ExceedsMaxInitCodeSize(_, _) => {
1045                Self::ExceedsMaxInitCodeSize
1046            }
1047            InvalidPoolTransactionError::IntrinsicGasTooLow => {
1048                Self::Invalid(RpcInvalidTransactionError::GasTooLow)
1049            }
1050            InvalidPoolTransactionError::OversizedData { size, limit } => {
1051                Self::OversizedData { size, limit }
1052            }
1053            InvalidPoolTransactionError::Underpriced => Self::Underpriced,
1054            InvalidPoolTransactionError::Eip2681 => {
1055                Self::Invalid(RpcInvalidTransactionError::NonceMaxValue)
1056            }
1057            InvalidPoolTransactionError::Other(err) => Self::PoolTransactionError(err),
1058            InvalidPoolTransactionError::Eip4844(err) => Self::Eip4844(err),
1059            InvalidPoolTransactionError::Eip7702(err) => Self::Eip7702(err),
1060            InvalidPoolTransactionError::Overdraft { cost, balance } => {
1061                Self::Invalid(RpcInvalidTransactionError::InsufficientFunds { cost, balance })
1062            }
1063            InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee } => {
1064                Self::Invalid(RpcInvalidTransactionError::PriorityFeeBelowMinimum {
1065                    minimum_priority_fee,
1066                })
1067            }
1068        }
1069    }
1070}
1071
1072impl From<PoolError> for EthApiError {
1073    fn from(err: PoolError) -> Self {
1074        Self::PoolError(RpcPoolError::from(err))
1075    }
1076}
1077
1078/// Errors returned from a sign request.
1079#[derive(Debug, thiserror::Error)]
1080pub enum SignError {
1081    /// Error occurred while trying to sign data.
1082    #[error("could not sign")]
1083    CouldNotSign,
1084    /// Signer for requested account not found.
1085    #[error("unknown account")]
1086    NoAccount,
1087    /// `TypedData` has invalid format.
1088    #[error("given typed data is not valid")]
1089    InvalidTypedData,
1090    /// Invalid transaction request in `sign_transaction`.
1091    #[error("invalid transaction request")]
1092    InvalidTransactionRequest,
1093    /// No chain ID was given.
1094    #[error("no chainid")]
1095    NoChainId,
1096}
1097
1098#[cfg(test)]
1099mod tests {
1100    use super::*;
1101    use alloy_sol_types::{Revert, SolError};
1102    use revm::primitives::b256;
1103
1104    #[test]
1105    fn timed_out_error() {
1106        let err = EthApiError::ExecutionTimedOut(Duration::from_secs(10));
1107        assert_eq!(err.to_string(), "execution aborted (timeout = 10s)");
1108    }
1109
1110    #[test]
1111    fn header_not_found_message() {
1112        let err: jsonrpsee_types::error::ErrorObject<'static> =
1113            EthApiError::HeaderNotFound(BlockId::hash(b256!(
1114                "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1115            )))
1116            .into();
1117        assert_eq!(
1118            err.message(),
1119            "block not found: hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1120        );
1121        let err: jsonrpsee_types::error::ErrorObject<'static> =
1122            EthApiError::HeaderNotFound(BlockId::hash_canonical(b256!(
1123                "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1124            )))
1125            .into();
1126        assert_eq!(
1127            err.message(),
1128            "block not found: canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1129        );
1130        let err: jsonrpsee_types::error::ErrorObject<'static> =
1131            EthApiError::HeaderNotFound(BlockId::number(100000)).into();
1132        assert_eq!(err.message(), "block not found: 0x186a0");
1133        let err: jsonrpsee_types::error::ErrorObject<'static> =
1134            EthApiError::HeaderNotFound(BlockId::latest()).into();
1135        assert_eq!(err.message(), "block not found: latest");
1136        let err: jsonrpsee_types::error::ErrorObject<'static> =
1137            EthApiError::HeaderNotFound(BlockId::safe()).into();
1138        assert_eq!(err.message(), "block not found: safe");
1139        let err: jsonrpsee_types::error::ErrorObject<'static> =
1140            EthApiError::HeaderNotFound(BlockId::finalized()).into();
1141        assert_eq!(err.message(), "block not found: finalized");
1142    }
1143
1144    #[test]
1145    fn receipts_not_found_message() {
1146        let err: jsonrpsee_types::error::ErrorObject<'static> =
1147            EthApiError::ReceiptsNotFound(BlockId::hash(b256!(
1148                "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1149            )))
1150            .into();
1151        assert_eq!(
1152            err.message(),
1153            "block not found: hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1154        );
1155        let err: jsonrpsee_types::error::ErrorObject<'static> =
1156            EthApiError::ReceiptsNotFound(BlockId::hash_canonical(b256!(
1157                "0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1158            )))
1159            .into();
1160        assert_eq!(
1161            err.message(),
1162            "block not found: canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"
1163        );
1164        let err: jsonrpsee_types::error::ErrorObject<'static> =
1165            EthApiError::ReceiptsNotFound(BlockId::number(100000)).into();
1166        assert_eq!(err.code(), EthRpcErrorCode::ResourceNotFound.code());
1167        assert_eq!(err.message(), "block not found: 0x186a0");
1168        let err: jsonrpsee_types::error::ErrorObject<'static> =
1169            EthApiError::ReceiptsNotFound(BlockId::latest()).into();
1170        assert_eq!(err.message(), "block not found: latest");
1171        let err: jsonrpsee_types::error::ErrorObject<'static> =
1172            EthApiError::ReceiptsNotFound(BlockId::safe()).into();
1173        assert_eq!(err.message(), "block not found: safe");
1174        let err: jsonrpsee_types::error::ErrorObject<'static> =
1175            EthApiError::ReceiptsNotFound(BlockId::finalized()).into();
1176        assert_eq!(err.message(), "block not found: finalized");
1177        let err: jsonrpsee_types::error::ErrorObject<'static> =
1178            EthApiError::ReceiptsNotFound(BlockId::pending()).into();
1179        assert_eq!(err.message(), "block not found: pending");
1180        let err: jsonrpsee_types::error::ErrorObject<'static> =
1181            EthApiError::ReceiptsNotFound(BlockId::earliest()).into();
1182        assert_eq!(err.message(), "block not found: earliest");
1183    }
1184
1185    #[test]
1186    fn revert_err_display() {
1187        let revert = Revert::from("test_revert_reason");
1188        let err = RevertError::new(revert.abi_encode().into());
1189        let msg = err.to_string();
1190        assert_eq!(msg, "execution reverted: test_revert_reason");
1191    }
1192}