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