revm_context_interface/
result.rs

1//! Result of the EVM execution. Containing both execution result, state and errors.
2//!
3//! [`ExecutionResult`] is the result of the EVM execution.
4//!
5//! [`InvalidTransaction`] is the error that is returned when the transaction is invalid.
6//!
7//! [`InvalidHeader`] is the error that is returned when the header is invalid.
8//!
9//! [`SuccessReason`] is the reason that the transaction successfully completed.
10use crate::{context::ContextError, transaction::TransactionError};
11use core::fmt::{self, Debug};
12use database_interface::DBErrorMarker;
13use primitives::{Address, Bytes, Log, U256};
14use state::EvmState;
15use std::{borrow::Cow, boxed::Box, string::String, vec::Vec};
16
17/// Trait for the halt reason.
18pub trait HaltReasonTr: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
19
20impl<T> HaltReasonTr for T where T: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
21
22/// Tuple containing evm execution result and state.s
23#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct ExecResultAndState<R, S = EvmState> {
26    /// Execution result
27    pub result: R,
28    /// Output State.
29    pub state: S,
30}
31
32/// Type alias for backwards compatibility.
33pub type ResultAndState<H = HaltReason, S = EvmState> = ExecResultAndState<ExecutionResult<H>, S>;
34
35/// Tuple containing multiple execution results and state.
36pub type ResultVecAndState<R, S> = ExecResultAndState<Vec<R>, S>;
37
38impl<R, S> ExecResultAndState<R, S> {
39    /// Creates new ResultAndState.
40    pub fn new(result: R, state: S) -> Self {
41        Self { result, state }
42    }
43}
44
45/// Result of a transaction execution
46#[derive(Clone, Debug, PartialEq, Eq, Hash)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub enum ExecutionResult<HaltReasonTy = HaltReason> {
49    /// Returned successfully
50    Success {
51        /// Reason for the success.
52        reason: SuccessReason,
53        /// Gas used by the transaction.s
54        gas_used: u64,
55        /// Gas refunded by the transaction.
56        gas_refunded: u64,
57        /// Logs emitted by the transaction.
58        logs: Vec<Log>,
59        /// Output of the transaction.
60        output: Output,
61    },
62    /// Reverted by `REVERT` opcode that doesn't spend all gas
63    Revert {
64        /// Gas used by the transaction.
65        gas_used: u64,
66        /// Output of the transaction.
67        output: Bytes,
68    },
69    /// Reverted for various reasons and spend all gas
70    Halt {
71        /// Reason for the halt.
72        reason: HaltReasonTy,
73        /// Gas used by the transaction.
74        ///
75        /// Halting will spend all the gas, and will be equal to gas_limit.
76        gas_used: u64,
77    },
78}
79
80impl<HaltReasonTy> ExecutionResult<HaltReasonTy> {
81    /// Returns if transaction execution is successful.
82    ///
83    /// 1 indicates success, 0 indicates revert.
84    ///
85    /// <https://eips.ethereum.org/EIPS/eip-658>
86    pub fn is_success(&self) -> bool {
87        matches!(self, Self::Success { .. })
88    }
89
90    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
91    pub fn map_haltreason<F, OHR>(self, op: F) -> ExecutionResult<OHR>
92    where
93        F: FnOnce(HaltReasonTy) -> OHR,
94    {
95        match self {
96            Self::Success {
97                reason,
98                gas_used,
99                gas_refunded,
100                logs,
101                output,
102            } => ExecutionResult::Success {
103                reason,
104                gas_used,
105                gas_refunded,
106                logs,
107                output,
108            },
109            Self::Revert { gas_used, output } => ExecutionResult::Revert { gas_used, output },
110            Self::Halt { reason, gas_used } => ExecutionResult::Halt {
111                reason: op(reason),
112                gas_used,
113            },
114        }
115    }
116
117    /// Returns created address if execution is Create transaction
118    /// and Contract was created.
119    pub fn created_address(&self) -> Option<Address> {
120        match self {
121            Self::Success { output, .. } => output.address().cloned(),
122            _ => None,
123        }
124    }
125
126    /// Returns true if execution result is a Halt.
127    pub fn is_halt(&self) -> bool {
128        matches!(self, Self::Halt { .. })
129    }
130
131    /// Returns the output data of the execution.
132    ///
133    /// Returns [`None`] if the execution was halted.
134    pub fn output(&self) -> Option<&Bytes> {
135        match self {
136            Self::Success { output, .. } => Some(output.data()),
137            Self::Revert { output, .. } => Some(output),
138            _ => None,
139        }
140    }
141
142    /// Consumes the type and returns the output data of the execution.
143    ///
144    /// Returns [`None`] if the execution was halted.
145    pub fn into_output(self) -> Option<Bytes> {
146        match self {
147            Self::Success { output, .. } => Some(output.into_data()),
148            Self::Revert { output, .. } => Some(output),
149            _ => None,
150        }
151    }
152
153    /// Returns the logs if execution is successful, or an empty list otherwise.
154    pub fn logs(&self) -> &[Log] {
155        match self {
156            Self::Success { logs, .. } => logs.as_slice(),
157            _ => &[],
158        }
159    }
160
161    /// Consumes [`self`] and returns the logs if execution is successful, or an empty list otherwise.
162    pub fn into_logs(self) -> Vec<Log> {
163        match self {
164            Self::Success { logs, .. } => logs,
165            _ => Vec::new(),
166        }
167    }
168
169    /// Returns the gas used.
170    pub fn gas_used(&self) -> u64 {
171        match *self {
172            Self::Success { gas_used, .. }
173            | Self::Revert { gas_used, .. }
174            | Self::Halt { gas_used, .. } => gas_used,
175        }
176    }
177}
178
179impl<HaltReasonTy: fmt::Display> fmt::Display for ExecutionResult<HaltReasonTy> {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        match self {
182            Self::Success {
183                reason,
184                gas_used,
185                gas_refunded,
186                logs,
187                output,
188            } => {
189                write!(
190                    f,
191                    "Success ({}): {} gas used, {} refunded",
192                    reason, gas_used, gas_refunded
193                )?;
194                if !logs.is_empty() {
195                    write!(
196                        f,
197                        ", {} log{}",
198                        logs.len(),
199                        if logs.len() == 1 { "" } else { "s" }
200                    )?;
201                }
202                write!(f, ", {}", output)
203            }
204            Self::Revert { gas_used, output } => {
205                write!(f, "Revert: {} gas used", gas_used)?;
206                if !output.is_empty() {
207                    write!(f, ", {} bytes output", output.len())?;
208                }
209                Ok(())
210            }
211            Self::Halt { reason, gas_used } => {
212                write!(f, "Halted: {} ({} gas used)", reason, gas_used)
213            }
214        }
215    }
216}
217
218/// Output of a transaction execution
219#[derive(Debug, Clone, PartialEq, Eq, Hash)]
220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
221pub enum Output {
222    /// Output of a call.
223    Call(Bytes),
224    /// Output of a create.
225    Create(Bytes, Option<Address>),
226}
227
228impl Output {
229    /// Returns the output data of the execution output.
230    pub fn into_data(self) -> Bytes {
231        match self {
232            Output::Call(data) => data,
233            Output::Create(data, _) => data,
234        }
235    }
236
237    /// Returns the output data of the execution output.
238    pub fn data(&self) -> &Bytes {
239        match self {
240            Output::Call(data) => data,
241            Output::Create(data, _) => data,
242        }
243    }
244
245    /// Returns the created address, if any.
246    pub fn address(&self) -> Option<&Address> {
247        match self {
248            Output::Call(_) => None,
249            Output::Create(_, address) => address.as_ref(),
250        }
251    }
252}
253
254impl fmt::Display for Output {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        match self {
257            Output::Call(data) => {
258                if data.is_empty() {
259                    write!(f, "no output")
260                } else {
261                    write!(f, "{} bytes output", data.len())
262                }
263            }
264            Output::Create(data, Some(addr)) => {
265                if data.is_empty() {
266                    write!(f, "contract created at {}", addr)
267                } else {
268                    write!(f, "contract created at {} ({} bytes)", addr, data.len())
269                }
270            }
271            Output::Create(data, None) => {
272                if data.is_empty() {
273                    write!(f, "contract creation (no address)")
274                } else {
275                    write!(f, "contract creation (no address, {} bytes)", data.len())
276                }
277            }
278        }
279    }
280}
281
282/// Main EVM error
283#[derive(Debug, Clone, PartialEq, Eq)]
284#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
285pub enum EVMError<DBError, TransactionError = InvalidTransaction> {
286    /// Transaction validation error
287    Transaction(TransactionError),
288    /// Header validation error
289    Header(InvalidHeader),
290    /// Database error
291    Database(DBError),
292    /// Custom error
293    ///
294    /// Useful for handler registers where custom logic would want to return their own custom error.
295    Custom(String),
296}
297
298impl<DBError, TransactionValidationErrorT> From<ContextError<DBError>>
299    for EVMError<DBError, TransactionValidationErrorT>
300{
301    fn from(value: ContextError<DBError>) -> Self {
302        match value {
303            ContextError::Db(e) => Self::Database(e),
304            ContextError::Custom(e) => Self::Custom(e),
305        }
306    }
307}
308
309impl<DBError: DBErrorMarker, TX> From<DBError> for EVMError<DBError, TX> {
310    fn from(value: DBError) -> Self {
311        Self::Database(value)
312    }
313}
314
315/// Trait for converting a string to an [`EVMError::Custom`] error.
316pub trait FromStringError {
317    /// Converts a string to an [`EVMError::Custom`] error.
318    fn from_string(value: String) -> Self;
319}
320
321impl<DB, TX> FromStringError for EVMError<DB, TX> {
322    fn from_string(value: String) -> Self {
323        Self::Custom(value)
324    }
325}
326
327impl<DB, TXE: From<InvalidTransaction>> From<InvalidTransaction> for EVMError<DB, TXE> {
328    fn from(value: InvalidTransaction) -> Self {
329        Self::Transaction(TXE::from(value))
330    }
331}
332
333impl<DBError, TransactionValidationErrorT> EVMError<DBError, TransactionValidationErrorT> {
334    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
335    pub fn map_db_err<F, E>(self, op: F) -> EVMError<E, TransactionValidationErrorT>
336    where
337        F: FnOnce(DBError) -> E,
338    {
339        match self {
340            Self::Transaction(e) => EVMError::Transaction(e),
341            Self::Header(e) => EVMError::Header(e),
342            Self::Database(e) => EVMError::Database(op(e)),
343            Self::Custom(e) => EVMError::Custom(e),
344        }
345    }
346}
347
348impl<DBError, TransactionValidationErrorT> core::error::Error
349    for EVMError<DBError, TransactionValidationErrorT>
350where
351    DBError: core::error::Error + 'static,
352    TransactionValidationErrorT: core::error::Error + 'static,
353{
354    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
355        match self {
356            Self::Transaction(e) => Some(e),
357            Self::Header(e) => Some(e),
358            Self::Database(e) => Some(e),
359            Self::Custom(_) => None,
360        }
361    }
362}
363
364impl<DBError, TransactionValidationErrorT> fmt::Display
365    for EVMError<DBError, TransactionValidationErrorT>
366where
367    DBError: fmt::Display,
368    TransactionValidationErrorT: fmt::Display,
369{
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        match self {
372            Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
373            Self::Header(e) => write!(f, "header validation error: {e}"),
374            Self::Database(e) => write!(f, "database error: {e}"),
375            Self::Custom(e) => f.write_str(e),
376        }
377    }
378}
379
380impl<DBError, TransactionValidationErrorT> From<InvalidHeader>
381    for EVMError<DBError, TransactionValidationErrorT>
382{
383    fn from(value: InvalidHeader) -> Self {
384        Self::Header(value)
385    }
386}
387
388/// Transaction validation error.
389#[derive(Debug, Clone, PartialEq, Eq, Hash)]
390#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
391pub enum InvalidTransaction {
392    /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields:
393    /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee.
394    /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip".
395    ///
396    /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`.
397    PriorityFeeGreaterThanMaxFee,
398    /// EIP-1559: `gas_price` is less than `basefee`.
399    GasPriceLessThanBasefee,
400    /// `gas_limit` in the tx is bigger than `block_gas_limit`.
401    CallerGasLimitMoreThanBlock,
402    /// Initial gas for a Call is bigger than `gas_limit`.
403    ///
404    /// Initial gas for a Call contains:
405    /// - initial stipend gas
406    /// - gas for access list and input data
407    CallGasCostMoreThanGasLimit {
408        /// Initial gas for a Call.
409        initial_gas: u64,
410        /// Gas limit for the transaction.
411        gas_limit: u64,
412    },
413    /// Gas floor calculated from EIP-7623 Increase calldata cost
414    /// is more than the gas limit.
415    ///
416    /// Tx data is too large to be executed.
417    GasFloorMoreThanGasLimit {
418        /// Gas floor calculated from EIP-7623 Increase calldata cost.
419        gas_floor: u64,
420        /// Gas limit for the transaction.
421        gas_limit: u64,
422    },
423    /// EIP-3607 Reject transactions from senders with deployed code
424    RejectCallerWithCode,
425    /// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
426    LackOfFundForMaxFee {
427        /// Fee for the transaction.
428        fee: Box<U256>,
429        /// Balance of the sender.
430        balance: Box<U256>,
431    },
432    /// Overflow payment in transaction.
433    OverflowPaymentInTransaction,
434    /// Nonce overflows in transaction.
435    NonceOverflowInTransaction,
436    /// Nonce is too high.
437    NonceTooHigh {
438        /// Nonce of the transaction.
439        tx: u64,
440        /// Nonce of the state.
441        state: u64,
442    },
443    /// Nonce is too low.
444    NonceTooLow {
445        /// Nonce of the transaction.
446        tx: u64,
447        /// Nonce of the state.
448        state: u64,
449    },
450    /// EIP-3860: Limit and meter initcode
451    CreateInitCodeSizeLimit,
452    /// Transaction chain id does not match the config chain id.
453    InvalidChainId,
454    /// Missing chain id.
455    MissingChainId,
456    /// Transaction gas limit is greater than the cap.
457    TxGasLimitGreaterThanCap {
458        /// Transaction gas limit.
459        gas_limit: u64,
460        /// Gas limit cap.
461        cap: u64,
462    },
463    /// Access list is not supported for blocks before the Berlin hardfork.
464    AccessListNotSupported,
465    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
466    MaxFeePerBlobGasNotSupported,
467    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun hardfork.
468    BlobVersionedHashesNotSupported,
469    /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
470    BlobGasPriceGreaterThanMax {
471        /// Block `blob_gas_price`.
472        block_blob_gas_price: u128,
473        /// Tx-specified `max_fee_per_blob_gas`.
474        tx_max_fee_per_blob_gas: u128,
475    },
476    /// There should be at least one blob in Blob transaction.
477    EmptyBlobs,
478    /// Blob transaction can't be a create transaction.
479    ///
480    /// `to` must be present
481    BlobCreateTransaction,
482    /// Transaction has more then `max` blobs
483    TooManyBlobs {
484        /// Maximum number of blobs allowed.
485        max: usize,
486        /// Number of blobs in the transaction.
487        have: usize,
488    },
489    /// Blob transaction contains a versioned hash with an incorrect version
490    BlobVersionNotSupported,
491    /// EIP-7702 is not enabled.
492    AuthorizationListNotSupported,
493    /// EIP-7702 transaction has invalid fields set.
494    AuthorizationListInvalidFields,
495    /// Empty Authorization List is not allowed.
496    EmptyAuthorizationList,
497    /// EIP-2930 is not supported.
498    Eip2930NotSupported,
499    /// EIP-1559 is not supported.
500    Eip1559NotSupported,
501    /// EIP-4844 is not supported.
502    Eip4844NotSupported,
503    /// EIP-7702 is not supported.
504    Eip7702NotSupported,
505    /// EIP-7873 is not supported.
506    Eip7873NotSupported,
507    /// EIP-7873 initcode transaction should have `to` address.
508    Eip7873MissingTarget,
509    /// Custom string error for flexible error handling.
510    Str(Cow<'static, str>),
511}
512
513impl TransactionError for InvalidTransaction {}
514
515impl core::error::Error for InvalidTransaction {}
516
517impl fmt::Display for InvalidTransaction {
518    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519        match self {
520            Self::PriorityFeeGreaterThanMaxFee => {
521                write!(f, "priority fee is greater than max fee")
522            }
523            Self::GasPriceLessThanBasefee => {
524                write!(f, "gas price is less than basefee")
525            }
526            Self::CallerGasLimitMoreThanBlock => {
527                write!(f, "caller gas limit exceeds the block gas limit")
528            }
529            Self::TxGasLimitGreaterThanCap { gas_limit, cap } => {
530                write!(
531                    f,
532                    "transaction gas limit ({gas_limit}) is greater than the cap ({cap})"
533                )
534            }
535            Self::CallGasCostMoreThanGasLimit {
536                initial_gas,
537                gas_limit,
538            } => {
539                write!(
540                    f,
541                    "call gas cost ({initial_gas}) exceeds the gas limit ({gas_limit})"
542                )
543            }
544            Self::GasFloorMoreThanGasLimit {
545                gas_floor,
546                gas_limit,
547            } => {
548                write!(
549                    f,
550                    "gas floor ({gas_floor}) exceeds the gas limit ({gas_limit})"
551                )
552            }
553            Self::RejectCallerWithCode => {
554                write!(f, "reject transactions from senders with deployed code")
555            }
556            Self::LackOfFundForMaxFee { fee, balance } => {
557                write!(f, "lack of funds ({balance}) for max fee ({fee})")
558            }
559            Self::OverflowPaymentInTransaction => {
560                write!(f, "overflow payment in transaction")
561            }
562            Self::NonceOverflowInTransaction => {
563                write!(f, "nonce overflow in transaction")
564            }
565            Self::NonceTooHigh { tx, state } => {
566                write!(f, "nonce {tx} too high, expected {state}")
567            }
568            Self::NonceTooLow { tx, state } => {
569                write!(f, "nonce {tx} too low, expected {state}")
570            }
571            Self::CreateInitCodeSizeLimit => {
572                write!(f, "create initcode size limit")
573            }
574            Self::InvalidChainId => write!(f, "invalid chain ID"),
575            Self::MissingChainId => write!(f, "missing chain ID"),
576            Self::AccessListNotSupported => write!(f, "access list not supported"),
577            Self::MaxFeePerBlobGasNotSupported => {
578                write!(f, "max fee per blob gas not supported")
579            }
580            Self::BlobVersionedHashesNotSupported => {
581                write!(f, "blob versioned hashes not supported")
582            }
583            Self::BlobGasPriceGreaterThanMax {
584                block_blob_gas_price,
585                tx_max_fee_per_blob_gas,
586            } => {
587                write!(
588                    f,
589                    "blob gas price ({block_blob_gas_price}) is greater than max fee per blob gas ({tx_max_fee_per_blob_gas})"
590                )
591            }
592            Self::EmptyBlobs => write!(f, "empty blobs"),
593            Self::BlobCreateTransaction => write!(f, "blob create transaction"),
594            Self::TooManyBlobs { max, have } => {
595                write!(f, "too many blobs, have {have}, max {max}")
596            }
597            Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
598            Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
599            Self::AuthorizationListInvalidFields => {
600                write!(f, "authorization list tx has invalid fields")
601            }
602            Self::EmptyAuthorizationList => write!(f, "empty authorization list"),
603            Self::Eip2930NotSupported => write!(f, "Eip2930 is not supported"),
604            Self::Eip1559NotSupported => write!(f, "Eip1559 is not supported"),
605            Self::Eip4844NotSupported => write!(f, "Eip4844 is not supported"),
606            Self::Eip7702NotSupported => write!(f, "Eip7702 is not supported"),
607            Self::Eip7873NotSupported => write!(f, "Eip7873 is not supported"),
608            Self::Eip7873MissingTarget => {
609                write!(f, "Eip7873 initcode transaction should have `to` address")
610            }
611            Self::Str(msg) => f.write_str(msg),
612        }
613    }
614}
615
616/// Errors related to misconfiguration of a [`crate::Block`].
617#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
618#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
619pub enum InvalidHeader {
620    /// `prevrandao` is not set for Merge and above.
621    PrevrandaoNotSet,
622    /// `excess_blob_gas` is not set for Cancun and above.
623    ExcessBlobGasNotSet,
624}
625
626impl core::error::Error for InvalidHeader {}
627
628impl fmt::Display for InvalidHeader {
629    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630        match self {
631            Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
632            Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
633        }
634    }
635}
636
637/// Reason a transaction successfully completed.
638#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
639#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
640pub enum SuccessReason {
641    /// Stop [`state::bytecode::opcode::STOP`] opcode.
642    Stop,
643    /// Return [`state::bytecode::opcode::RETURN`] opcode.
644    Return,
645    /// Self destruct opcode.
646    SelfDestruct,
647}
648
649impl fmt::Display for SuccessReason {
650    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651        match self {
652            Self::Stop => write!(f, "Stop"),
653            Self::Return => write!(f, "Return"),
654            Self::SelfDestruct => write!(f, "SelfDestruct"),
655        }
656    }
657}
658
659/// Indicates that the EVM has experienced an exceptional halt.
660///
661/// This causes execution to immediately end with all gas being consumed.
662#[derive(Debug, Clone, PartialEq, Eq, Hash)]
663#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
664pub enum HaltReason {
665    /// Out of gas error.
666    OutOfGas(OutOfGasError),
667    /// Opcode not found error.
668    OpcodeNotFound,
669    /// Invalid FE opcode error.
670    InvalidFEOpcode,
671    /// Invalid jump destination.
672    InvalidJump,
673    /// The feature or opcode is not activated in hardfork.
674    NotActivated,
675    /// Attempting to pop a value from an empty stack.
676    StackUnderflow,
677    /// Attempting to push a value onto a full stack.
678    StackOverflow,
679    /// Invalid memory or storage offset for [`state::bytecode::opcode::RETURNDATACOPY`].
680    OutOfOffset,
681    /// Address collision during contract creation.
682    CreateCollision,
683    /// Precompile error.
684    PrecompileError,
685    /// Precompile error with message from context.
686    PrecompileErrorWithContext(String),
687    /// Nonce overflow.
688    NonceOverflow,
689    /// Create init code size exceeds limit (runtime).
690    CreateContractSizeLimit,
691    /// Error on created contract that begins with EF
692    CreateContractStartingWithEF,
693    /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
694    CreateInitCodeSizeLimit,
695
696    /* Internal Halts that can be only found inside Inspector */
697    /// Overflow payment. Not possible to happen on mainnet.
698    OverflowPayment,
699    /// State change during static call.
700    StateChangeDuringStaticCall,
701    /// Call not allowed inside static call.
702    CallNotAllowedInsideStatic,
703    /// Out of funds to pay for the call.
704    OutOfFunds,
705    /// Call is too deep.
706    CallTooDeep,
707}
708
709impl core::error::Error for HaltReason {}
710
711impl fmt::Display for HaltReason {
712    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
713        match self {
714            Self::OutOfGas(err) => write!(f, "{err}"),
715            Self::OpcodeNotFound => write!(f, "opcode not found"),
716            Self::InvalidFEOpcode => write!(f, "invalid 0xFE opcode"),
717            Self::InvalidJump => write!(f, "invalid jump destination"),
718            Self::NotActivated => write!(f, "feature or opcode not activated"),
719            Self::StackUnderflow => write!(f, "stack underflow"),
720            Self::StackOverflow => write!(f, "stack overflow"),
721            Self::OutOfOffset => write!(f, "out of offset"),
722            Self::CreateCollision => write!(f, "create collision"),
723            Self::PrecompileError => write!(f, "precompile error"),
724            Self::PrecompileErrorWithContext(msg) => write!(f, "precompile error: {msg}"),
725            Self::NonceOverflow => write!(f, "nonce overflow"),
726            Self::CreateContractSizeLimit => write!(f, "create contract size limit"),
727            Self::CreateContractStartingWithEF => {
728                write!(f, "create contract starting with 0xEF")
729            }
730            Self::CreateInitCodeSizeLimit => write!(f, "create initcode size limit"),
731            Self::OverflowPayment => write!(f, "overflow payment"),
732            Self::StateChangeDuringStaticCall => write!(f, "state change during static call"),
733            Self::CallNotAllowedInsideStatic => write!(f, "call not allowed inside static call"),
734            Self::OutOfFunds => write!(f, "out of funds"),
735            Self::CallTooDeep => write!(f, "call too deep"),
736        }
737    }
738}
739
740/// Out of gas errors.
741#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
742#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
743pub enum OutOfGasError {
744    /// Basic OOG error. Not enough gas to execute the opcode.
745    Basic,
746    /// Tried to expand past memory limit.
747    MemoryLimit,
748    /// Basic OOG error from memory expansion
749    Memory,
750    /// Precompile threw OOG error
751    Precompile,
752    /// When performing something that takes a U256 and casts down to a u64, if its too large this would fire
753    /// i.e. in `as_usize_or_fail`
754    InvalidOperand,
755    /// When performing SSTORE the gasleft is less than or equal to 2300
756    ReentrancySentry,
757}
758
759impl core::error::Error for OutOfGasError {}
760
761impl fmt::Display for OutOfGasError {
762    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
763        match self {
764            Self::Basic => write!(f, "out of gas"),
765            Self::MemoryLimit => write!(f, "out of gas: memory limit exceeded"),
766            Self::Memory => write!(f, "out of gas: memory expansion"),
767            Self::Precompile => write!(f, "out of gas: precompile"),
768            Self::InvalidOperand => write!(f, "out of gas: invalid operand"),
769            Self::ReentrancySentry => write!(f, "out of gas: reentrancy sentry"),
770        }
771    }
772}
773
774/// Error that includes transaction index for batch transaction processing.
775#[derive(Debug, Clone, PartialEq, Eq)]
776#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
777pub struct TransactionIndexedError<Error> {
778    /// The original error that occurred.
779    pub error: Error,
780    /// The index of the transaction that failed.
781    pub transaction_index: usize,
782}
783
784impl<Error> TransactionIndexedError<Error> {
785    /// Create a new `TransactionIndexedError` with the given error and transaction index.
786    #[must_use]
787    pub fn new(error: Error, transaction_index: usize) -> Self {
788        Self {
789            error,
790            transaction_index,
791        }
792    }
793
794    /// Get a reference to the underlying error.
795    pub fn error(&self) -> &Error {
796        &self.error
797    }
798
799    /// Convert into the underlying error.
800    #[must_use]
801    pub fn into_error(self) -> Error {
802        self.error
803    }
804}
805
806impl<Error: fmt::Display> fmt::Display for TransactionIndexedError<Error> {
807    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
808        write!(
809            f,
810            "transaction {} failed: {}",
811            self.transaction_index, self.error
812        )
813    }
814}
815
816impl<Error: core::error::Error + 'static> core::error::Error for TransactionIndexedError<Error> {
817    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
818        Some(&self.error)
819    }
820}
821
822impl From<&'static str> for InvalidTransaction {
823    fn from(s: &'static str) -> Self {
824        Self::Str(Cow::Borrowed(s))
825    }
826}
827
828impl From<String> for InvalidTransaction {
829    fn from(s: String) -> Self {
830        Self::Str(Cow::Owned(s))
831    }
832}
833
834#[cfg(test)]
835mod tests {
836    use super::*;
837
838    #[test]
839    fn test_execution_result_display() {
840        let result: ExecutionResult<HaltReason> = ExecutionResult::Success {
841            reason: SuccessReason::Return,
842            gas_used: 21000,
843            gas_refunded: 5000,
844            logs: vec![Log::default(), Log::default()],
845            output: Output::Call(Bytes::from(vec![1, 2, 3])),
846        };
847        assert_eq!(
848            result.to_string(),
849            "Success (Return): 21000 gas used, 5000 refunded, 2 logs, 3 bytes output"
850        );
851
852        let result: ExecutionResult<HaltReason> = ExecutionResult::Revert {
853            gas_used: 100000,
854            output: Bytes::from(vec![1, 2, 3, 4]),
855        };
856        assert_eq!(
857            result.to_string(),
858            "Revert: 100000 gas used, 4 bytes output"
859        );
860
861        let result: ExecutionResult<HaltReason> = ExecutionResult::Halt {
862            reason: HaltReason::OutOfGas(OutOfGasError::Basic),
863            gas_used: 1000000,
864        };
865        assert_eq!(result.to_string(), "Halted: out of gas (1000000 gas used)");
866    }
867}