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
179/// Output of a transaction execution
180#[derive(Debug, Clone, PartialEq, Eq, Hash)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
182pub enum Output {
183    /// Output of a call.
184    Call(Bytes),
185    /// Output of a create.
186    Create(Bytes, Option<Address>),
187}
188
189impl Output {
190    /// Returns the output data of the execution output.
191    pub fn into_data(self) -> Bytes {
192        match self {
193            Output::Call(data) => data,
194            Output::Create(data, _) => data,
195        }
196    }
197
198    /// Returns the output data of the execution output.
199    pub fn data(&self) -> &Bytes {
200        match self {
201            Output::Call(data) => data,
202            Output::Create(data, _) => data,
203        }
204    }
205
206    /// Returns the created address, if any.
207    pub fn address(&self) -> Option<&Address> {
208        match self {
209            Output::Call(_) => None,
210            Output::Create(_, address) => address.as_ref(),
211        }
212    }
213}
214
215/// Main EVM error
216#[derive(Debug, Clone, PartialEq, Eq)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218pub enum EVMError<DBError, TransactionError = InvalidTransaction> {
219    /// Transaction validation error
220    Transaction(TransactionError),
221    /// Header validation error
222    Header(InvalidHeader),
223    /// Database error
224    Database(DBError),
225    /// Custom error
226    ///
227    /// Useful for handler registers where custom logic would want to return their own custom error.
228    Custom(String),
229}
230
231impl<DBError, TransactionValidationErrorT> From<ContextError<DBError>>
232    for EVMError<DBError, TransactionValidationErrorT>
233{
234    fn from(value: ContextError<DBError>) -> Self {
235        match value {
236            ContextError::Db(e) => Self::Database(e),
237            ContextError::Custom(e) => Self::Custom(e),
238        }
239    }
240}
241
242impl<DBError: DBErrorMarker, TX> From<DBError> for EVMError<DBError, TX> {
243    fn from(value: DBError) -> Self {
244        Self::Database(value)
245    }
246}
247
248/// Trait for converting a string to an [`EVMError::Custom`] error.
249pub trait FromStringError {
250    /// Converts a string to an [`EVMError::Custom`] error.
251    fn from_string(value: String) -> Self;
252}
253
254impl<DB, TX> FromStringError for EVMError<DB, TX> {
255    fn from_string(value: String) -> Self {
256        Self::Custom(value)
257    }
258}
259
260impl<DB, TXE: From<InvalidTransaction>> From<InvalidTransaction> for EVMError<DB, TXE> {
261    fn from(value: InvalidTransaction) -> Self {
262        Self::Transaction(TXE::from(value))
263    }
264}
265
266impl<DBError, TransactionValidationErrorT> EVMError<DBError, TransactionValidationErrorT> {
267    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
268    pub fn map_db_err<F, E>(self, op: F) -> EVMError<E, TransactionValidationErrorT>
269    where
270        F: FnOnce(DBError) -> E,
271    {
272        match self {
273            Self::Transaction(e) => EVMError::Transaction(e),
274            Self::Header(e) => EVMError::Header(e),
275            Self::Database(e) => EVMError::Database(op(e)),
276            Self::Custom(e) => EVMError::Custom(e),
277        }
278    }
279}
280
281impl<DBError, TransactionValidationErrorT> core::error::Error
282    for EVMError<DBError, TransactionValidationErrorT>
283where
284    DBError: core::error::Error + 'static,
285    TransactionValidationErrorT: core::error::Error + 'static,
286{
287    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
288        match self {
289            Self::Transaction(e) => Some(e),
290            Self::Header(e) => Some(e),
291            Self::Database(e) => Some(e),
292            Self::Custom(_) => None,
293        }
294    }
295}
296
297impl<DBError, TransactionValidationErrorT> fmt::Display
298    for EVMError<DBError, TransactionValidationErrorT>
299where
300    DBError: fmt::Display,
301    TransactionValidationErrorT: fmt::Display,
302{
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        match self {
305            Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
306            Self::Header(e) => write!(f, "header validation error: {e}"),
307            Self::Database(e) => write!(f, "database error: {e}"),
308            Self::Custom(e) => f.write_str(e),
309        }
310    }
311}
312
313impl<DBError, TransactionValidationErrorT> From<InvalidHeader>
314    for EVMError<DBError, TransactionValidationErrorT>
315{
316    fn from(value: InvalidHeader) -> Self {
317        Self::Header(value)
318    }
319}
320
321/// Transaction validation error.
322#[derive(Debug, Clone, PartialEq, Eq, Hash)]
323#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
324pub enum InvalidTransaction {
325    /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields:
326    /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee.
327    /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip".
328    ///
329    /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`.
330    PriorityFeeGreaterThanMaxFee,
331    /// EIP-1559: `gas_price` is less than `basefee`.
332    GasPriceLessThanBasefee,
333    /// `gas_limit` in the tx is bigger than `block_gas_limit`.
334    CallerGasLimitMoreThanBlock,
335    /// Initial gas for a Call is bigger than `gas_limit`.
336    ///
337    /// Initial gas for a Call contains:
338    /// - initial stipend gas
339    /// - gas for access list and input data
340    CallGasCostMoreThanGasLimit {
341        /// Initial gas for a Call.
342        initial_gas: u64,
343        /// Gas limit for the transaction.
344        gas_limit: u64,
345    },
346    /// Gas floor calculated from EIP-7623 Increase calldata cost
347    /// is more than the gas limit.
348    ///
349    /// Tx data is too large to be executed.
350    GasFloorMoreThanGasLimit {
351        /// Gas floor calculated from EIP-7623 Increase calldata cost.
352        gas_floor: u64,
353        /// Gas limit for the transaction.
354        gas_limit: u64,
355    },
356    /// EIP-3607 Reject transactions from senders with deployed code
357    RejectCallerWithCode,
358    /// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
359    LackOfFundForMaxFee {
360        /// Fee for the transaction.
361        fee: Box<U256>,
362        /// Balance of the sender.
363        balance: Box<U256>,
364    },
365    /// Overflow payment in transaction.
366    OverflowPaymentInTransaction,
367    /// Nonce overflows in transaction.
368    NonceOverflowInTransaction,
369    /// Nonce is too high.
370    NonceTooHigh {
371        /// Nonce of the transaction.
372        tx: u64,
373        /// Nonce of the state.
374        state: u64,
375    },
376    /// Nonce is too low.
377    NonceTooLow {
378        /// Nonce of the transaction.
379        tx: u64,
380        /// Nonce of the state.
381        state: u64,
382    },
383    /// EIP-3860: Limit and meter initcode
384    CreateInitCodeSizeLimit,
385    /// Transaction chain id does not match the config chain id.
386    InvalidChainId,
387    /// Missing chain id.
388    MissingChainId,
389    /// Transaction gas limit is greater than the cap.
390    TxGasLimitGreaterThanCap {
391        /// Transaction gas limit.
392        gas_limit: u64,
393        /// Gas limit cap.
394        cap: u64,
395    },
396    /// Access list is not supported for blocks before the Berlin hardfork.
397    AccessListNotSupported,
398    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
399    MaxFeePerBlobGasNotSupported,
400    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun hardfork.
401    BlobVersionedHashesNotSupported,
402    /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
403    BlobGasPriceGreaterThanMax {
404        /// Block `blob_gas_price`.
405        block_blob_gas_price: u128,
406        /// Tx-specified `max_fee_per_blob_gas`.
407        tx_max_fee_per_blob_gas: u128,
408    },
409    /// There should be at least one blob in Blob transaction.
410    EmptyBlobs,
411    /// Blob transaction can't be a create transaction.
412    ///
413    /// `to` must be present
414    BlobCreateTransaction,
415    /// Transaction has more then `max` blobs
416    TooManyBlobs {
417        /// Maximum number of blobs allowed.
418        max: usize,
419        /// Number of blobs in the transaction.
420        have: usize,
421    },
422    /// Blob transaction contains a versioned hash with an incorrect version
423    BlobVersionNotSupported,
424    /// EIP-7702 is not enabled.
425    AuthorizationListNotSupported,
426    /// EIP-7702 transaction has invalid fields set.
427    AuthorizationListInvalidFields,
428    /// Empty Authorization List is not allowed.
429    EmptyAuthorizationList,
430    /// EIP-2930 is not supported.
431    Eip2930NotSupported,
432    /// EIP-1559 is not supported.
433    Eip1559NotSupported,
434    /// EIP-4844 is not supported.
435    Eip4844NotSupported,
436    /// EIP-7702 is not supported.
437    Eip7702NotSupported,
438    /// EIP-7873 is not supported.
439    Eip7873NotSupported,
440    /// EIP-7873 initcode transaction should have `to` address.
441    Eip7873MissingTarget,
442    /// Custom string error for flexible error handling.
443    Str(Cow<'static, str>),
444}
445
446impl TransactionError for InvalidTransaction {}
447
448impl core::error::Error for InvalidTransaction {}
449
450impl fmt::Display for InvalidTransaction {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        match self {
453            Self::PriorityFeeGreaterThanMaxFee => {
454                write!(f, "priority fee is greater than max fee")
455            }
456            Self::GasPriceLessThanBasefee => {
457                write!(f, "gas price is less than basefee")
458            }
459            Self::CallerGasLimitMoreThanBlock => {
460                write!(f, "caller gas limit exceeds the block gas limit")
461            }
462            Self::TxGasLimitGreaterThanCap { gas_limit, cap } => {
463                write!(
464                    f,
465                    "transaction gas limit ({gas_limit}) is greater than the cap ({cap})"
466                )
467            }
468            Self::CallGasCostMoreThanGasLimit {
469                initial_gas,
470                gas_limit,
471            } => {
472                write!(
473                    f,
474                    "call gas cost ({initial_gas}) exceeds the gas limit ({gas_limit})"
475                )
476            }
477            Self::GasFloorMoreThanGasLimit {
478                gas_floor,
479                gas_limit,
480            } => {
481                write!(
482                    f,
483                    "gas floor ({gas_floor}) exceeds the gas limit ({gas_limit})"
484                )
485            }
486            Self::RejectCallerWithCode => {
487                write!(f, "reject transactions from senders with deployed code")
488            }
489            Self::LackOfFundForMaxFee { fee, balance } => {
490                write!(f, "lack of funds ({balance}) for max fee ({fee})")
491            }
492            Self::OverflowPaymentInTransaction => {
493                write!(f, "overflow payment in transaction")
494            }
495            Self::NonceOverflowInTransaction => {
496                write!(f, "nonce overflow in transaction")
497            }
498            Self::NonceTooHigh { tx, state } => {
499                write!(f, "nonce {tx} too high, expected {state}")
500            }
501            Self::NonceTooLow { tx, state } => {
502                write!(f, "nonce {tx} too low, expected {state}")
503            }
504            Self::CreateInitCodeSizeLimit => {
505                write!(f, "create initcode size limit")
506            }
507            Self::InvalidChainId => write!(f, "invalid chain ID"),
508            Self::MissingChainId => write!(f, "missing chain ID"),
509            Self::AccessListNotSupported => write!(f, "access list not supported"),
510            Self::MaxFeePerBlobGasNotSupported => {
511                write!(f, "max fee per blob gas not supported")
512            }
513            Self::BlobVersionedHashesNotSupported => {
514                write!(f, "blob versioned hashes not supported")
515            }
516            Self::BlobGasPriceGreaterThanMax {
517                block_blob_gas_price,
518                tx_max_fee_per_blob_gas,
519            } => {
520                write!(
521                    f,
522                    "blob gas price ({block_blob_gas_price}) is greater than max fee per blob gas ({tx_max_fee_per_blob_gas})"
523                )
524            }
525            Self::EmptyBlobs => write!(f, "empty blobs"),
526            Self::BlobCreateTransaction => write!(f, "blob create transaction"),
527            Self::TooManyBlobs { max, have } => {
528                write!(f, "too many blobs, have {have}, max {max}")
529            }
530            Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
531            Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
532            Self::AuthorizationListInvalidFields => {
533                write!(f, "authorization list tx has invalid fields")
534            }
535            Self::EmptyAuthorizationList => write!(f, "empty authorization list"),
536            Self::Eip2930NotSupported => write!(f, "Eip2930 is not supported"),
537            Self::Eip1559NotSupported => write!(f, "Eip1559 is not supported"),
538            Self::Eip4844NotSupported => write!(f, "Eip4844 is not supported"),
539            Self::Eip7702NotSupported => write!(f, "Eip7702 is not supported"),
540            Self::Eip7873NotSupported => write!(f, "Eip7873 is not supported"),
541            Self::Eip7873MissingTarget => {
542                write!(f, "Eip7873 initcode transaction should have `to` address")
543            }
544            Self::Str(msg) => f.write_str(msg),
545        }
546    }
547}
548
549/// Errors related to misconfiguration of a [`crate::Block`].
550#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
551#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
552pub enum InvalidHeader {
553    /// `prevrandao` is not set for Merge and above.
554    PrevrandaoNotSet,
555    /// `excess_blob_gas` is not set for Cancun and above.
556    ExcessBlobGasNotSet,
557}
558
559impl core::error::Error for InvalidHeader {}
560
561impl fmt::Display for InvalidHeader {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        match self {
564            Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
565            Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
566        }
567    }
568}
569
570/// Reason a transaction successfully completed.
571#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
572#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
573pub enum SuccessReason {
574    /// Stop [`state::bytecode::opcode::STOP`] opcode.
575    Stop,
576    /// Return [`state::bytecode::opcode::RETURN`] opcode.
577    Return,
578    /// Self destruct opcode.
579    SelfDestruct,
580}
581
582/// Indicates that the EVM has experienced an exceptional halt.
583///
584/// This causes execution to immediately end with all gas being consumed.
585#[derive(Debug, Clone, PartialEq, Eq, Hash)]
586#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
587pub enum HaltReason {
588    /// Out of gas error.
589    OutOfGas(OutOfGasError),
590    /// Opcode not found error.
591    OpcodeNotFound,
592    /// Invalid FE opcode error.
593    InvalidFEOpcode,
594    /// Invalid jump destination.
595    InvalidJump,
596    /// The feature or opcode is not activated in hardfork.
597    NotActivated,
598    /// Attempting to pop a value from an empty stack.
599    StackUnderflow,
600    /// Attempting to push a value onto a full stack.
601    StackOverflow,
602    /// Invalid memory or storage offset for [`state::bytecode::opcode::RETURNDATACOPY`].
603    OutOfOffset,
604    /// Address collision during contract creation.
605    CreateCollision,
606    /// Precompile error.
607    PrecompileError,
608    /// Precompile error with message from context.
609    PrecompileErrorWithContext(String),
610    /// Nonce overflow.
611    NonceOverflow,
612    /// Create init code size exceeds limit (runtime).
613    CreateContractSizeLimit,
614    /// Error on created contract that begins with EF
615    CreateContractStartingWithEF,
616    /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
617    CreateInitCodeSizeLimit,
618
619    /* Internal Halts that can be only found inside Inspector */
620    /// Overflow payment. Not possible to happen on mainnet.
621    OverflowPayment,
622    /// State change during static call.
623    StateChangeDuringStaticCall,
624    /// Call not allowed inside static call.
625    CallNotAllowedInsideStatic,
626    /// Out of funds to pay for the call.
627    OutOfFunds,
628    /// Call is too deep.
629    CallTooDeep,
630}
631
632/// Out of gas errors.
633#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
634#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
635pub enum OutOfGasError {
636    /// Basic OOG error. Not enough gas to execute the opcode.
637    Basic,
638    /// Tried to expand past memory limit.
639    MemoryLimit,
640    /// Basic OOG error from memory expansion
641    Memory,
642    /// Precompile threw OOG error
643    Precompile,
644    /// When performing something that takes a U256 and casts down to a u64, if its too large this would fire
645    /// i.e. in `as_usize_or_fail`
646    InvalidOperand,
647    /// When performing SSTORE the gasleft is less than or equal to 2300
648    ReentrancySentry,
649}
650
651/// Error that includes transaction index for batch transaction processing.
652#[derive(Debug, Clone, PartialEq, Eq)]
653#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
654pub struct TransactionIndexedError<Error> {
655    /// The original error that occurred.
656    pub error: Error,
657    /// The index of the transaction that failed.
658    pub transaction_index: usize,
659}
660
661impl<Error> TransactionIndexedError<Error> {
662    /// Create a new `TransactionIndexedError` with the given error and transaction index.
663    #[must_use]
664    pub fn new(error: Error, transaction_index: usize) -> Self {
665        Self {
666            error,
667            transaction_index,
668        }
669    }
670
671    /// Get a reference to the underlying error.
672    pub fn error(&self) -> &Error {
673        &self.error
674    }
675
676    /// Convert into the underlying error.
677    #[must_use]
678    pub fn into_error(self) -> Error {
679        self.error
680    }
681}
682
683impl<Error: fmt::Display> fmt::Display for TransactionIndexedError<Error> {
684    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685        write!(
686            f,
687            "transaction {} failed: {}",
688            self.transaction_index, self.error
689        )
690    }
691}
692
693impl<Error: core::error::Error + 'static> core::error::Error for TransactionIndexedError<Error> {
694    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
695        Some(&self.error)
696    }
697}
698
699impl From<&'static str> for InvalidTransaction {
700    fn from(s: &'static str) -> Self {
701        Self::Str(Cow::Borrowed(s))
702    }
703}
704
705impl From<String> for InvalidTransaction {
706    fn from(s: String) -> Self {
707        Self::Str(Cow::Owned(s))
708    }
709}