anvil/eth/
error.rs

1//! Aggregated error type for this module
2
3use crate::eth::pool::transactions::PoolTransaction;
4use alloy_primitives::{Bytes, SignatureError};
5use alloy_rpc_types::BlockNumberOrTag;
6use alloy_signer::Error as SignerError;
7use alloy_transport::TransportError;
8use anvil_core::eth::wallet::WalletError;
9use anvil_rpc::{
10    error::{ErrorCode, RpcError},
11    response::ResponseResult,
12};
13use foundry_evm::{
14    backend::DatabaseError,
15    decode::RevertDecoder,
16    revm::{
17        interpreter::InstructionResult,
18        primitives::{EVMError, InvalidHeader},
19    },
20};
21use serde::Serialize;
22
23pub(crate) type Result<T> = std::result::Result<T, BlockchainError>;
24
25#[derive(Debug, thiserror::Error)]
26pub enum BlockchainError {
27    #[error(transparent)]
28    Pool(#[from] PoolError),
29    #[error("No signer available")]
30    NoSignerAvailable,
31    #[error("Chain Id not available")]
32    ChainIdNotAvailable,
33    #[error("Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`")]
34    InvalidFeeInput,
35    #[error("Transaction data is empty")]
36    EmptyRawTransactionData,
37    #[error("Failed to decode signed transaction")]
38    FailedToDecodeSignedTransaction,
39    #[error("Failed to decode transaction")]
40    FailedToDecodeTransaction,
41    #[error("Failed to decode receipt")]
42    FailedToDecodeReceipt,
43    #[error("Failed to decode state")]
44    FailedToDecodeStateDump,
45    #[error("Prevrandao not in th EVM's environment after merge")]
46    PrevrandaoNotSet,
47    #[error(transparent)]
48    SignatureError(#[from] SignatureError),
49    #[error(transparent)]
50    SignerError(#[from] SignerError),
51    #[error("Rpc Endpoint not implemented")]
52    RpcUnimplemented,
53    #[error("Rpc error {0:?}")]
54    RpcError(RpcError),
55    #[error(transparent)]
56    InvalidTransaction(#[from] InvalidTransactionError),
57    #[error(transparent)]
58    FeeHistory(#[from] FeeHistoryError),
59    #[error(transparent)]
60    AlloyForkProvider(#[from] TransportError),
61    #[error("EVM error {0:?}")]
62    EvmError(InstructionResult),
63    #[error("Invalid url {0:?}")]
64    InvalidUrl(String),
65    #[error("Internal error: {0:?}")]
66    Internal(String),
67    #[error("BlockOutOfRangeError: block height is {0} but requested was {1}")]
68    BlockOutOfRange(u64, u64),
69    #[error("Resource not found")]
70    BlockNotFound,
71    #[error("Required data unavailable")]
72    DataUnavailable,
73    #[error("Trie error: {0}")]
74    TrieError(String),
75    #[error("{0}")]
76    UintConversion(&'static str),
77    #[error("State override error: {0}")]
78    StateOverrideError(String),
79    #[error("Timestamp error: {0}")]
80    TimestampError(String),
81    #[error(transparent)]
82    DatabaseError(#[from] DatabaseError),
83    #[error("EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork.\n\nYou can use them by running anvil with '--hardfork london' or later.")]
84    EIP1559TransactionUnsupportedAtHardfork,
85    #[error("Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later.")]
86    EIP2930TransactionUnsupportedAtHardfork,
87    #[error("EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later.")]
88    EIP4844TransactionUnsupportedAtHardfork,
89    #[error("EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later.")]
90    EIP7702TransactionUnsupportedAtHardfork,
91    #[error("op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'.")]
92    DepositTransactionUnsupported,
93    #[error("UnknownTransactionType not supported ")]
94    UnknownTransactionType,
95    #[error("Excess blob gas not set.")]
96    ExcessBlobGasNotSet,
97    #[error("{0}")]
98    Message(String),
99}
100
101impl From<eyre::Report> for BlockchainError {
102    fn from(err: eyre::Report) -> Self {
103        Self::Message(err.to_string())
104    }
105}
106
107impl From<RpcError> for BlockchainError {
108    fn from(err: RpcError) -> Self {
109        Self::RpcError(err)
110    }
111}
112
113impl<T> From<EVMError<T>> for BlockchainError
114where
115    T: Into<Self>,
116{
117    fn from(err: EVMError<T>) -> Self {
118        match err {
119            EVMError::Transaction(err) => InvalidTransactionError::from(err).into(),
120            EVMError::Header(err) => match err {
121                InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet,
122                InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet,
123            },
124            EVMError::Database(err) => err.into(),
125            EVMError::Precompile(err) => Self::Message(err),
126            EVMError::Custom(err) => Self::Message(err),
127        }
128    }
129}
130
131impl From<WalletError> for BlockchainError {
132    fn from(value: WalletError) -> Self {
133        match value {
134            WalletError::ValueNotZero => Self::Message("tx value not zero".to_string()),
135            WalletError::FromSet => Self::Message("tx from field is set".to_string()),
136            WalletError::NonceSet => Self::Message("tx nonce is set".to_string()),
137            WalletError::InvalidAuthorization => {
138                Self::Message("invalid authorization address".to_string())
139            }
140            WalletError::IllegalDestination => Self::Message(
141                "the destination of the transaction is not a delegated account".to_string(),
142            ),
143            WalletError::InternalError => Self::Message("internal error".to_string()),
144            WalletError::InvalidTransactionRequest => {
145                Self::Message("invalid tx request".to_string())
146            }
147        }
148    }
149}
150
151/// Errors that can occur in the transaction pool
152#[derive(Debug, thiserror::Error)]
153pub enum PoolError {
154    #[error("Transaction with cyclic dependent transactions")]
155    CyclicTransaction,
156    /// Thrown if a replacement transaction's gas price is below the already imported transaction
157    #[error("Tx: [{0:?}] insufficient gas price to replace existing transaction")]
158    ReplacementUnderpriced(Box<PoolTransaction>),
159    #[error("Tx: [{0:?}] already Imported")]
160    AlreadyImported(Box<PoolTransaction>),
161}
162
163/// Errors that can occur with `eth_feeHistory`
164#[derive(Debug, thiserror::Error)]
165pub enum FeeHistoryError {
166    #[error("requested block range is out of bounds")]
167    InvalidBlockRange,
168    #[error("could not find newest block number requested: {0}")]
169    BlockNotFound(BlockNumberOrTag),
170}
171
172#[derive(Debug)]
173pub struct ErrDetail {
174    pub detail: String,
175}
176
177/// An error due to invalid transaction
178#[derive(Debug, thiserror::Error)]
179pub enum InvalidTransactionError {
180    /// returned if the nonce of a transaction is lower than the one present in the local chain.
181    #[error("nonce too low")]
182    NonceTooLow,
183    /// returned if the nonce of a transaction is higher than the next one expected based on the
184    /// local chain.
185    #[error("Nonce too high")]
186    NonceTooHigh,
187    /// Returned if the nonce of a transaction is too high
188    /// Incrementing the nonce would lead to invalid state (overflow)
189    #[error("nonce has max value")]
190    NonceMaxValue,
191    /// thrown if the transaction sender doesn't have enough funds for a transfer
192    #[error("insufficient funds for transfer")]
193    InsufficientFundsForTransfer,
194    /// thrown if creation transaction provides the init code bigger than init code size limit.
195    #[error("max initcode size exceeded")]
196    MaxInitCodeSizeExceeded,
197    /// Represents the inability to cover max cost + value (account balance too low).
198    #[error("Insufficient funds for gas * price + value")]
199    InsufficientFunds,
200    /// Thrown when calculating gas usage
201    #[error("gas uint64 overflow")]
202    GasUintOverflow,
203    /// returned if the transaction is specified to use less gas than required to start the
204    /// invocation.
205    #[error("intrinsic gas too low")]
206    GasTooLow,
207    /// returned if the transaction gas exceeds the limit
208    #[error("intrinsic gas too high -- {}",.0.detail)]
209    GasTooHigh(ErrDetail),
210    /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total
211    /// fee cap.
212    #[error("max priority fee per gas higher than max fee per gas")]
213    TipAboveFeeCap,
214    /// Thrown post London if the transaction's fee is less than the base fee of the block
215    #[error("max fee per gas less than block base fee")]
216    FeeCapTooLow,
217    /// Thrown during estimate if caller has insufficient funds to cover the tx.
218    #[error("Out of gas: gas required exceeds allowance: {0:?}")]
219    BasicOutOfGas(u128),
220    /// Thrown if executing a transaction failed during estimate/call
221    #[error("execution reverted: {0:?}")]
222    Revert(Option<Bytes>),
223    /// Thrown if the sender of a transaction is a contract.
224    #[error("sender not an eoa")]
225    SenderNoEOA,
226    /// Thrown when a tx was signed with a different chain_id
227    #[error("invalid chain id for signer")]
228    InvalidChainId,
229    /// Thrown when a legacy tx was signed for a different chain
230    #[error("Incompatible EIP-155 transaction, signed for another chain")]
231    IncompatibleEIP155,
232    /// Thrown when an access list is used before the berlin hard fork.
233    #[error("Access lists are not supported before the Berlin hardfork")]
234    AccessListNotSupported,
235    /// Thrown when the block's `blob_gas_price` is greater than tx-specified
236    /// `max_fee_per_blob_gas` after Cancun.
237    #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")]
238    BlobFeeCapTooLow,
239    /// Thrown when we receive a tx with `blob_versioned_hashes` and we're not on the Cancun hard
240    /// fork.
241    #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")]
242    BlobVersionedHashesNotSupported,
243    /// Thrown when `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
244    #[error("`max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.")]
245    MaxFeePerBlobGasNotSupported,
246    /// Thrown when there are no `blob_hashes` in the transaction, and it is an EIP-4844 tx.
247    #[error("`blob_hashes` are required for EIP-4844 transactions")]
248    NoBlobHashes,
249    #[error("too many blobs in one transaction, have: {0}")]
250    TooManyBlobs(usize),
251    /// Thrown when there's a blob validation error
252    #[error(transparent)]
253    BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError),
254    /// Thrown when Blob transaction is a create transaction. `to` must be present.
255    #[error("Blob transaction can't be a create transaction. `to` must be present.")]
256    BlobCreateTransaction,
257    /// Thrown when Blob transaction contains a versioned hash with an incorrect version.
258    #[error("Blob transaction contains a versioned hash with an incorrect version")]
259    BlobVersionNotSupported,
260    /// Thrown when there are no `blob_hashes` in the transaction.
261    #[error("There should be at least one blob in a Blob transaction.")]
262    EmptyBlobs,
263    /// Thrown when an access list is used before the berlin hard fork.
264    #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")]
265    AuthorizationListNotSupported,
266    /// Forwards error from the revm
267    #[error(transparent)]
268    Revm(revm::primitives::InvalidTransaction),
269}
270
271impl From<revm::primitives::InvalidTransaction> for InvalidTransactionError {
272    fn from(err: revm::primitives::InvalidTransaction) -> Self {
273        use revm::primitives::InvalidTransaction;
274        match err {
275            InvalidTransaction::InvalidChainId => Self::InvalidChainId,
276            InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap,
277            InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow,
278            InvalidTransaction::CallerGasLimitMoreThanBlock => {
279                Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") })
280            }
281            InvalidTransaction::CallGasCostMoreThanGasLimit => {
282                Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") })
283            }
284            InvalidTransaction::GasFloorMoreThanGasLimit => {
285                Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") })
286            }
287            InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA,
288            InvalidTransaction::LackOfFundForMaxFee { .. } => Self::InsufficientFunds,
289            InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow,
290            InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue,
291            InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded,
292            InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh,
293            InvalidTransaction::NonceTooLow { .. } => Self::NonceTooLow,
294            InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported,
295            InvalidTransaction::BlobGasPriceGreaterThanMax => Self::BlobFeeCapTooLow,
296            InvalidTransaction::BlobVersionedHashesNotSupported => {
297                Self::BlobVersionedHashesNotSupported
298            }
299            InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported,
300            InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction,
301            InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported,
302            InvalidTransaction::EmptyBlobs => Self::EmptyBlobs,
303            InvalidTransaction::TooManyBlobs { have } => Self::TooManyBlobs(have),
304            InvalidTransaction::AuthorizationListNotSupported => {
305                Self::AuthorizationListNotSupported
306            }
307            InvalidTransaction::AuthorizationListInvalidFields |
308            InvalidTransaction::OptimismError(_) |
309            InvalidTransaction::EofCrateShouldHaveToAddress |
310            InvalidTransaction::EmptyAuthorizationList => Self::Revm(err),
311        }
312    }
313}
314
315/// Helper trait to easily convert results to rpc results
316pub(crate) trait ToRpcResponseResult {
317    fn to_rpc_result(self) -> ResponseResult;
318}
319
320/// Converts a serializable value into a `ResponseResult`
321pub fn to_rpc_result<T: Serialize>(val: T) -> ResponseResult {
322    match serde_json::to_value(val) {
323        Ok(success) => ResponseResult::Success(success),
324        Err(err) => {
325            error!(%err, "Failed serialize rpc response");
326            ResponseResult::error(RpcError::internal_error())
327        }
328    }
329}
330
331impl<T: Serialize> ToRpcResponseResult for Result<T> {
332    fn to_rpc_result(self) -> ResponseResult {
333        match self {
334            Ok(val) => to_rpc_result(val),
335            Err(err) => match err {
336                BlockchainError::Pool(err) => {
337                    error!(%err, "txpool error");
338                    match err {
339                        PoolError::CyclicTransaction => {
340                            RpcError::transaction_rejected("Cyclic transaction detected")
341                        }
342                        PoolError::ReplacementUnderpriced(_) => {
343                            RpcError::transaction_rejected("replacement transaction underpriced")
344                        }
345                        PoolError::AlreadyImported(_) => {
346                            RpcError::transaction_rejected("transaction already imported")
347                        }
348                    }
349                }
350                BlockchainError::NoSignerAvailable => {
351                    RpcError::invalid_params("No Signer available")
352                }
353                BlockchainError::ChainIdNotAvailable => {
354                    RpcError::invalid_params("Chain Id not available")
355                }
356                BlockchainError::InvalidTransaction(err) => match err {
357                    InvalidTransactionError::Revert(data) => {
358                        // this mimics geth revert error
359                        let mut msg = "execution reverted".to_string();
360                        if let Some(reason) = data
361                            .as_ref()
362                            .and_then(|data| RevertDecoder::new().maybe_decode(data, None))
363                        {
364                            msg = format!("{msg}: {reason}");
365                        }
366                        RpcError {
367                            // geth returns this error code on reverts, See <https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal>
368                            code: ErrorCode::ExecutionError,
369                            message: msg.into(),
370                            data: serde_json::to_value(data).ok(),
371                        }
372                    }
373                    InvalidTransactionError::GasTooLow => {
374                        // <https://eips.ethereum.org/EIPS/eip-1898>
375                        RpcError {
376                            code: ErrorCode::ServerError(-32000),
377                            message: err.to_string().into(),
378                            data: None,
379                        }
380                    }
381                    InvalidTransactionError::GasTooHigh(_) => {
382                        // <https://eips.ethereum.org/EIPS/eip-1898>
383                        RpcError {
384                            code: ErrorCode::ServerError(-32000),
385                            message: err.to_string().into(),
386                            data: None,
387                        }
388                    }
389                    _ => RpcError::transaction_rejected(err.to_string()),
390                },
391                BlockchainError::FeeHistory(err) => RpcError::invalid_params(err.to_string()),
392                BlockchainError::EmptyRawTransactionData => {
393                    RpcError::invalid_params("Empty transaction data")
394                }
395                BlockchainError::FailedToDecodeSignedTransaction => {
396                    RpcError::invalid_params("Failed to decode transaction")
397                }
398                BlockchainError::FailedToDecodeTransaction => {
399                    RpcError::invalid_params("Failed to decode transaction")
400                }
401                BlockchainError::FailedToDecodeReceipt => {
402                    RpcError::invalid_params("Failed to decode receipt")
403                }
404                BlockchainError::FailedToDecodeStateDump => {
405                    RpcError::invalid_params("Failed to decode state dump")
406                }
407                BlockchainError::SignerError(err) => RpcError::invalid_params(err.to_string()),
408                BlockchainError::SignatureError(err) => RpcError::invalid_params(err.to_string()),
409                BlockchainError::RpcUnimplemented => {
410                    RpcError::internal_error_with("Not implemented")
411                }
412                BlockchainError::PrevrandaoNotSet => RpcError::internal_error_with(err.to_string()),
413                BlockchainError::RpcError(err) => err,
414                BlockchainError::InvalidFeeInput => RpcError::invalid_params(
415                    "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`",
416                ),
417                BlockchainError::AlloyForkProvider(err) => {
418                    error!(target: "backend", %err, "fork provider error");
419                    match err {
420                        TransportError::ErrorResp(err) => RpcError {
421                            code: ErrorCode::from(err.code),
422                            message: err.message,
423                            data: err.data.and_then(|data| serde_json::to_value(data).ok()),
424                        },
425                        err => RpcError::internal_error_with(format!("Fork Error: {err:?}")),
426                    }
427                }
428                err @ BlockchainError::EvmError(_) => {
429                    RpcError::internal_error_with(err.to_string())
430                }
431                err @ BlockchainError::InvalidUrl(_) => RpcError::invalid_params(err.to_string()),
432                BlockchainError::Internal(err) => RpcError::internal_error_with(err),
433                err @ BlockchainError::BlockOutOfRange(_, _) => {
434                    RpcError::invalid_params(err.to_string())
435                }
436                err @ BlockchainError::BlockNotFound => RpcError {
437                    // <https://eips.ethereum.org/EIPS/eip-1898>
438                    code: ErrorCode::ServerError(-32001),
439                    message: err.to_string().into(),
440                    data: None,
441                },
442                err @ BlockchainError::DataUnavailable => {
443                    RpcError::internal_error_with(err.to_string())
444                }
445                err @ BlockchainError::TrieError(_) => {
446                    RpcError::internal_error_with(err.to_string())
447                }
448                BlockchainError::UintConversion(err) => RpcError::invalid_params(err),
449                err @ BlockchainError::StateOverrideError(_) => {
450                    RpcError::invalid_params(err.to_string())
451                }
452                err @ BlockchainError::TimestampError(_) => {
453                    RpcError::invalid_params(err.to_string())
454                }
455                BlockchainError::DatabaseError(err) => {
456                    RpcError::internal_error_with(err.to_string())
457                }
458                err @ BlockchainError::EIP1559TransactionUnsupportedAtHardfork => {
459                    RpcError::invalid_params(err.to_string())
460                }
461                err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => {
462                    RpcError::invalid_params(err.to_string())
463                }
464                err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => {
465                    RpcError::invalid_params(err.to_string())
466                }
467                err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => {
468                    RpcError::invalid_params(err.to_string())
469                }
470                err @ BlockchainError::DepositTransactionUnsupported => {
471                    RpcError::invalid_params(err.to_string())
472                }
473                err @ BlockchainError::ExcessBlobGasNotSet => {
474                    RpcError::invalid_params(err.to_string())
475                }
476                err @ BlockchainError::Message(_) => RpcError::internal_error_with(err.to_string()),
477                err @ BlockchainError::UnknownTransactionType => {
478                    RpcError::invalid_params(err.to_string())
479                }
480            }
481            .into(),
482        }
483    }
484}