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::transaction::TransactionError;
11use core::fmt::{self, Debug};
12use database_interface::DBErrorMarker;
13use primitives::{Address, Bytes, Log, U256};
14use std::{boxed::Box, string::String, vec::Vec};
15
16/// Trait for the halt reason.
17pub trait HaltReasonTr: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
18
19impl<T> HaltReasonTr for T where T: Clone + Debug + PartialEq + Eq + From<HaltReason> {}
20
21/// Tuple containing evm execution result and state.s
22#[derive(Clone, Debug, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct ResultAndState<R, S> {
25    /// Execution result
26    pub result: R,
27    /// Output State.
28    pub state: S,
29}
30
31/// Tuple containing multiple execution results and state.
32pub type ResultVecAndState<R, S> = ResultAndState<Vec<R>, S>;
33
34impl<R, S> ResultAndState<R, S> {
35    /// Creates new ResultAndState.
36    pub fn new(result: R, state: S) -> Self {
37        Self { result, state }
38    }
39}
40
41/// Result of a transaction execution
42#[derive(Clone, Debug, PartialEq, Eq, Hash)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44pub enum ExecutionResult<HaltReasonTy = HaltReason> {
45    /// Returned successfully
46    Success {
47        /// Reason for the success.
48        reason: SuccessReason,
49        /// Gas used by the transaction.s
50        gas_used: u64,
51        /// Gas refunded by the transaction.
52        gas_refunded: u64,
53        /// Logs emitted by the transaction.
54        logs: Vec<Log>,
55        /// Output of the transaction.
56        output: Output,
57    },
58    /// Reverted by `REVERT` opcode that doesn't spend all gas
59    Revert {
60        /// Gas used by the transaction.
61        gas_used: u64,
62        /// Output of the transaction.
63        output: Bytes,
64    },
65    /// Reverted for various reasons and spend all gas
66    Halt {
67        /// Reason for the halt.
68        reason: HaltReasonTy,
69        /// Gas used by the transaction.
70        ///
71        /// Halting will spend all the gas, and will be equal to gas_limit.
72        gas_used: u64,
73    },
74}
75
76impl<HaltReasonTy> ExecutionResult<HaltReasonTy> {
77    /// Returns if transaction execution is successful.
78    ///
79    /// 1 indicates success, 0 indicates revert.
80    ///
81    /// <https://eips.ethereum.org/EIPS/eip-658>
82    pub fn is_success(&self) -> bool {
83        matches!(self, Self::Success { .. })
84    }
85
86    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
87    pub fn map_haltreason<F, OHR>(self, op: F) -> ExecutionResult<OHR>
88    where
89        F: FnOnce(HaltReasonTy) -> OHR,
90    {
91        match self {
92            Self::Success {
93                reason,
94                gas_used,
95                gas_refunded,
96                logs,
97                output,
98            } => ExecutionResult::Success {
99                reason,
100                gas_used,
101                gas_refunded,
102                logs,
103                output,
104            },
105            Self::Revert { gas_used, output } => ExecutionResult::Revert { gas_used, output },
106            Self::Halt { reason, gas_used } => ExecutionResult::Halt {
107                reason: op(reason),
108                gas_used,
109            },
110        }
111    }
112
113    /// Returns created address if execution is Create transaction
114    /// and Contract was created.
115    pub fn created_address(&self) -> Option<Address> {
116        match self {
117            Self::Success { output, .. } => output.address().cloned(),
118            _ => None,
119        }
120    }
121
122    /// Returns true if execution result is a Halt.
123    pub fn is_halt(&self) -> bool {
124        matches!(self, Self::Halt { .. })
125    }
126
127    /// Returns the output data of the execution.
128    ///
129    /// Returns [`None`] if the execution was halted.
130    pub fn output(&self) -> Option<&Bytes> {
131        match self {
132            Self::Success { output, .. } => Some(output.data()),
133            Self::Revert { output, .. } => Some(output),
134            _ => None,
135        }
136    }
137
138    /// Consumes the type and returns the output data of the execution.
139    ///
140    /// Returns [`None`] if the execution was halted.
141    pub fn into_output(self) -> Option<Bytes> {
142        match self {
143            Self::Success { output, .. } => Some(output.into_data()),
144            Self::Revert { output, .. } => Some(output),
145            _ => None,
146        }
147    }
148
149    /// Returns the logs if execution is successful, or an empty list otherwise.
150    pub fn logs(&self) -> &[Log] {
151        match self {
152            Self::Success { logs, .. } => logs.as_slice(),
153            _ => &[],
154        }
155    }
156
157    /// Consumes [`self`] and returns the logs if execution is successful, or an empty list otherwise.
158    pub fn into_logs(self) -> Vec<Log> {
159        match self {
160            Self::Success { logs, .. } => logs,
161            _ => Vec::new(),
162        }
163    }
164
165    /// Returns the gas used.
166    pub fn gas_used(&self) -> u64 {
167        match *self {
168            Self::Success { gas_used, .. }
169            | Self::Revert { gas_used, .. }
170            | Self::Halt { gas_used, .. } => gas_used,
171        }
172    }
173}
174
175/// Output of a transaction execution
176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub enum Output {
179    /// Output of a call.
180    Call(Bytes),
181    /// Output of a create.
182    Create(Bytes, Option<Address>),
183}
184
185impl Output {
186    /// Returns the output data of the execution output.
187    pub fn into_data(self) -> Bytes {
188        match self {
189            Output::Call(data) => data,
190            Output::Create(data, _) => data,
191        }
192    }
193
194    /// Returns the output data of the execution output.
195    pub fn data(&self) -> &Bytes {
196        match self {
197            Output::Call(data) => data,
198            Output::Create(data, _) => data,
199        }
200    }
201
202    /// Returns the created address, if any.
203    pub fn address(&self) -> Option<&Address> {
204        match self {
205            Output::Call(_) => None,
206            Output::Create(_, address) => address.as_ref(),
207        }
208    }
209}
210
211/// Main EVM error
212#[derive(Debug, Clone, PartialEq, Eq)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214pub enum EVMError<DBError, TransactionError = InvalidTransaction> {
215    /// Transaction validation error
216    Transaction(TransactionError),
217    /// Header validation error
218    Header(InvalidHeader),
219    /// Database error
220    Database(DBError),
221    /// Custom error
222    ///
223    /// Useful for handler registers where custom logic would want to return their own custom error.
224    Custom(String),
225}
226
227impl<DBError: DBErrorMarker, TX> From<DBError> for EVMError<DBError, TX> {
228    fn from(value: DBError) -> Self {
229        Self::Database(value)
230    }
231}
232
233/// Trait for converting a string to an [`EVMError::Custom`] error.
234pub trait FromStringError {
235    /// Converts a string to an [`EVMError::Custom`] error.
236    fn from_string(value: String) -> Self;
237}
238
239impl<DB, TX> FromStringError for EVMError<DB, TX> {
240    fn from_string(value: String) -> Self {
241        Self::Custom(value)
242    }
243}
244
245impl<DB, TXE: From<InvalidTransaction>> From<InvalidTransaction> for EVMError<DB, TXE> {
246    fn from(value: InvalidTransaction) -> Self {
247        Self::Transaction(TXE::from(value))
248    }
249}
250
251impl<DBError, TransactionValidationErrorT> EVMError<DBError, TransactionValidationErrorT> {
252    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
253    pub fn map_db_err<F, E>(self, op: F) -> EVMError<E, TransactionValidationErrorT>
254    where
255        F: FnOnce(DBError) -> E,
256    {
257        match self {
258            Self::Transaction(e) => EVMError::Transaction(e),
259            Self::Header(e) => EVMError::Header(e),
260            Self::Database(e) => EVMError::Database(op(e)),
261            Self::Custom(e) => EVMError::Custom(e),
262        }
263    }
264}
265
266impl<DBError, TransactionValidationErrorT> core::error::Error
267    for EVMError<DBError, TransactionValidationErrorT>
268where
269    DBError: core::error::Error + 'static,
270    TransactionValidationErrorT: core::error::Error + 'static,
271{
272    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
273        match self {
274            Self::Transaction(e) => Some(e),
275            Self::Header(e) => Some(e),
276            Self::Database(e) => Some(e),
277            Self::Custom(_) => None,
278        }
279    }
280}
281
282impl<DBError, TransactionValidationErrorT> fmt::Display
283    for EVMError<DBError, TransactionValidationErrorT>
284where
285    DBError: fmt::Display,
286    TransactionValidationErrorT: fmt::Display,
287{
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        match self {
290            Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
291            Self::Header(e) => write!(f, "header validation error: {e}"),
292            Self::Database(e) => write!(f, "database error: {e}"),
293            Self::Custom(e) => f.write_str(e),
294        }
295    }
296}
297
298impl<DBError, TransactionValidationErrorT> From<InvalidHeader>
299    for EVMError<DBError, TransactionValidationErrorT>
300{
301    fn from(value: InvalidHeader) -> Self {
302        Self::Header(value)
303    }
304}
305
306/// Transaction validation error.
307#[derive(Debug, Clone, PartialEq, Eq, Hash)]
308#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
309pub enum InvalidTransaction {
310    /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields:
311    /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee.
312    /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip".
313    ///
314    /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`.
315    PriorityFeeGreaterThanMaxFee,
316    /// EIP-1559: `gas_price` is less than `basefee`.
317    GasPriceLessThanBasefee,
318    /// `gas_limit` in the tx is bigger than `block_gas_limit`.
319    CallerGasLimitMoreThanBlock,
320    /// Initial gas for a Call is bigger than `gas_limit`.
321    ///
322    /// Initial gas for a Call contains:
323    /// - initial stipend gas
324    /// - gas for access list and input data
325    CallGasCostMoreThanGasLimit {
326        /// Initial gas for a Call.
327        initial_gas: u64,
328        /// Gas limit for the transaction.
329        gas_limit: u64,
330    },
331    /// Gas floor calculated from EIP-7623 Increase calldata cost
332    /// is more than the gas limit.
333    ///
334    /// Tx data is too large to be executed.
335    GasFloorMoreThanGasLimit {
336        /// Gas floor calculated from EIP-7623 Increase calldata cost.
337        gas_floor: u64,
338        /// Gas limit for the transaction.
339        gas_limit: u64,
340    },
341    /// EIP-3607 Reject transactions from senders with deployed code
342    RejectCallerWithCode,
343    /// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
344    LackOfFundForMaxFee {
345        /// Fee for the transaction.
346        fee: Box<U256>,
347        /// Balance of the sender.
348        balance: Box<U256>,
349    },
350    /// Overflow payment in transaction.
351    OverflowPaymentInTransaction,
352    /// Nonce overflows in transaction.
353    NonceOverflowInTransaction,
354    /// Nonce is too high.
355    NonceTooHigh {
356        /// Nonce of the transaction.
357        tx: u64,
358        /// Nonce of the state.
359        state: u64,
360    },
361    /// Nonce is too low.
362    NonceTooLow {
363        /// Nonce of the transaction.
364        tx: u64,
365        /// Nonce of the state.
366        state: u64,
367    },
368    /// EIP-3860: Limit and meter initcode
369    CreateInitCodeSizeLimit,
370    /// Transaction chain id does not match the config chain id.
371    InvalidChainId,
372    /// Missing chain id.
373    MissingChainId,
374    /// Access list is not supported for blocks before the Berlin hardfork.
375    AccessListNotSupported,
376    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
377    MaxFeePerBlobGasNotSupported,
378    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun hardfork.
379    BlobVersionedHashesNotSupported,
380    /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
381    BlobGasPriceGreaterThanMax,
382    /// There should be at least one blob in Blob transaction.
383    EmptyBlobs,
384    /// Blob transaction can't be a create transaction.
385    ///
386    /// `to` must be present
387    BlobCreateTransaction,
388    /// Transaction has more then `max` blobs
389    TooManyBlobs {
390        /// Maximum number of blobs allowed.
391        max: usize,
392        /// Number of blobs in the transaction.
393        have: usize,
394    },
395    /// Blob transaction contains a versioned hash with an incorrect version
396    BlobVersionNotSupported,
397    /// EOF create should have `to` address
398    EofCreateShouldHaveToAddress,
399    /// EIP-7702 is not enabled.
400    AuthorizationListNotSupported,
401    /// EIP-7702 transaction has invalid fields set.
402    AuthorizationListInvalidFields,
403    /// Empty Authorization List is not allowed.
404    EmptyAuthorizationList,
405    /// EIP-2930 is not supported.
406    Eip2930NotSupported,
407    /// EIP-1559 is not supported.
408    Eip1559NotSupported,
409    /// EIP-4844 is not supported.
410    Eip4844NotSupported,
411    /// EIP-7702 is not supported.
412    Eip7702NotSupported,
413    /// EIP-7873 is not supported.
414    Eip7873NotSupported,
415    // TODO (EOF)
416    // /// EIP-7873 needs to have at least one initcode.
417    // Eip7873EmptyInitcodeList,
418    // /// EIP-7873 initcode can't be zero length.
419    // Eip7873EmptyInitcode {
420    //     i: usize,
421    // },
422    // /// EIP-7873 initcodes can't be more than [`MAX_INITCODE_COUNT`].
423    // Eip7873TooManyInitcodes {
424    //     size: usize,
425    // },
426    // /// EIP-7873 initcodes can't be more than [`MAX_INITCODE_SIZE`].
427    // Eip7873InitcodeTooLarge {
428    //     i: usize,
429    //     size: usize,
430    // },
431    /// EIP-7873 initcode transaction should have `to` address.
432    Eip7873MissingTarget,
433}
434
435impl TransactionError for InvalidTransaction {}
436
437impl core::error::Error for InvalidTransaction {}
438
439impl fmt::Display for InvalidTransaction {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        match self {
442            Self::PriorityFeeGreaterThanMaxFee => {
443                write!(f, "priority fee is greater than max fee")
444            }
445            Self::GasPriceLessThanBasefee => {
446                write!(f, "gas price is less than basefee")
447            }
448            Self::CallerGasLimitMoreThanBlock => {
449                write!(f, "caller gas limit exceeds the block gas limit")
450            }
451            Self::CallGasCostMoreThanGasLimit {
452                initial_gas,
453                gas_limit,
454            } => {
455                write!(
456                    f,
457                    "call gas cost ({initial_gas}) exceeds the gas limit ({gas_limit})"
458                )
459            }
460            Self::GasFloorMoreThanGasLimit {
461                gas_floor,
462                gas_limit,
463            } => {
464                write!(
465                    f,
466                    "gas floor ({gas_floor}) exceeds the gas limit ({gas_limit})"
467                )
468            }
469            Self::RejectCallerWithCode => {
470                write!(f, "reject transactions from senders with deployed code")
471            }
472            Self::LackOfFundForMaxFee { fee, balance } => {
473                write!(f, "lack of funds ({balance}) for max fee ({fee})")
474            }
475            Self::OverflowPaymentInTransaction => {
476                write!(f, "overflow payment in transaction")
477            }
478            Self::NonceOverflowInTransaction => {
479                write!(f, "nonce overflow in transaction")
480            }
481            Self::NonceTooHigh { tx, state } => {
482                write!(f, "nonce {tx} too high, expected {state}")
483            }
484            Self::NonceTooLow { tx, state } => {
485                write!(f, "nonce {tx} too low, expected {state}")
486            }
487            Self::CreateInitCodeSizeLimit => {
488                write!(f, "create initcode size limit")
489            }
490            Self::InvalidChainId => write!(f, "invalid chain ID"),
491            Self::MissingChainId => write!(f, "missing chain ID"),
492            Self::AccessListNotSupported => write!(f, "access list not supported"),
493            Self::MaxFeePerBlobGasNotSupported => {
494                write!(f, "max fee per blob gas not supported")
495            }
496            Self::BlobVersionedHashesNotSupported => {
497                write!(f, "blob versioned hashes not supported")
498            }
499            Self::BlobGasPriceGreaterThanMax => {
500                write!(f, "blob gas price is greater than max fee per blob gas")
501            }
502            Self::EmptyBlobs => write!(f, "empty blobs"),
503            Self::BlobCreateTransaction => write!(f, "blob create transaction"),
504            Self::TooManyBlobs { max, have } => {
505                write!(f, "too many blobs, have {have}, max {max}")
506            }
507            Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
508            Self::EofCreateShouldHaveToAddress => write!(f, "EOF crate should have `to` address"),
509            Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
510            Self::AuthorizationListInvalidFields => {
511                write!(f, "authorization list tx has invalid fields")
512            }
513            Self::EmptyAuthorizationList => write!(f, "empty authorization list"),
514            Self::Eip2930NotSupported => write!(f, "Eip2930 is not supported"),
515            Self::Eip1559NotSupported => write!(f, "Eip1559 is not supported"),
516            Self::Eip4844NotSupported => write!(f, "Eip4844 is not supported"),
517            Self::Eip7702NotSupported => write!(f, "Eip7702 is not supported"),
518            Self::Eip7873NotSupported => write!(f, "Eip7873 is not supported"),
519            // TODO(EOF)
520            // Self::Eip7873EmptyInitcodeList => {
521            //     write!(f, "Eip7873 initcode list should have at least one initcode")
522            // }
523            // Self::Eip7873EmptyInitcode { i } => {
524            //     write!(f, "Eip7873 initcode {i} can't be zero length")
525            // }
526            // Self::Eip7873TooManyInitcodes { size } => {
527            //     write!(
528            //         f,
529            //         "Eip7873 initcodes can't be more than {MAX_INITCODE_COUNT}, have {size}"
530            //     )
531            // }
532            // Self::Eip7873InitcodeTooLarge { i, size } => {
533            //     write!(
534            //         f,
535            //         "Eip7873 initcode {i} can't be more than {MAX_INITCODE_SIZE}, have {size}"
536            //     )
537            // }
538            Self::Eip7873MissingTarget => {
539                write!(f, "Eip7873 initcode transaction should have `to` address")
540            }
541        }
542    }
543}
544
545/// Errors related to misconfiguration of a [`crate::Block`].
546#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
547#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
548pub enum InvalidHeader {
549    /// `prevrandao` is not set for Merge and above.
550    PrevrandaoNotSet,
551    /// `excess_blob_gas` is not set for Cancun and above.
552    ExcessBlobGasNotSet,
553}
554
555impl core::error::Error for InvalidHeader {}
556
557impl fmt::Display for InvalidHeader {
558    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559        match self {
560            Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
561            Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
562        }
563    }
564}
565
566/// Reason a transaction successfully completed.
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
568#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
569pub enum SuccessReason {
570    /// Stop [`state::bytecode::opcode::STOP`] opcode.
571    Stop,
572    /// Return [`state::bytecode::opcode::RETURN`] opcode.
573    Return,
574    /// Self destruct opcode.
575    SelfDestruct,
576    /// EOF [`state::bytecode::opcode::RETURNCONTRACT`] opcode.
577    EofReturnContract,
578}
579
580/// Indicates that the EVM has experienced an exceptional halt.
581///
582/// This causes execution to immediately end with all gas being consumed.
583#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
584#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
585pub enum HaltReason {
586    /// Out of gas error.
587    OutOfGas(OutOfGasError),
588    /// Opcode not found error.
589    OpcodeNotFound,
590    /// Invalid FE opcode error.
591    InvalidFEOpcode,
592    /// Invalid jump destination.
593    InvalidJump,
594    /// The feature or opcode is not activated in hardfork.
595    NotActivated,
596    /// Attempting to pop a value from an empty stack.
597    StackUnderflow,
598    /// Attempting to push a value onto a full stack.
599    StackOverflow,
600    /// Invalid memory or storage offset for [`state::bytecode::opcode::RETURNDATACOPY`].
601    OutOfOffset,
602    /// Address collision during contract creation.
603    CreateCollision,
604    /// Precompile error.
605    PrecompileError,
606    /// Nonce overflow.
607    NonceOverflow,
608    /// Create init code size exceeds limit (runtime).
609    CreateContractSizeLimit,
610    /// Error on created contract that begins with EF
611    CreateContractStartingWithEF,
612    /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
613    CreateInitCodeSizeLimit,
614
615    /* Internal Halts that can be only found inside Inspector */
616    /// Overflow payment. Not possible to happen on mainnet.
617    OverflowPayment,
618    /// State change during static call.
619    StateChangeDuringStaticCall,
620    /// Call not allowed inside static call.
621    CallNotAllowedInsideStatic,
622    /// Out of funds to pay for the call.
623    OutOfFunds,
624    /// Call is too deep.
625    CallTooDeep,
626
627    /// Aux data overflow, new aux data is larger than [u16] max size.
628    EofAuxDataOverflow,
629    /// Aux data is smaller than already present data size.
630    EofAuxDataTooSmall,
631    /// EOF Subroutine stack overflow
632    SubRoutineStackOverflow,
633    /// Check for target address validity is only done inside subcall.
634    InvalidEXTCALLTarget,
635}
636
637/// Out of gas errors.
638#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
639#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
640pub enum OutOfGasError {
641    /// Basic OOG error. Not enough gas to execute the opcode.
642    Basic,
643    /// Tried to expand past memory limit.
644    MemoryLimit,
645    /// Basic OOG error from memory expansion
646    Memory,
647    /// Precompile threw OOG error
648    Precompile,
649    /// When performing something that takes a U256 and casts down to a u64, if its too large this would fire
650    /// i.e. in `as_usize_or_fail`
651    InvalidOperand,
652    /// When performing SSTORE the gasleft is less than or equal to 2300
653    ReentrancySentry,
654}