revm_primitives/
result.rs

1use crate::{Address, Bytes, EvmState, Log, U256};
2use core::fmt;
3use std::{boxed::Box, string::String, vec::Vec};
4
5/// Result of EVM execution.
6pub type EVMResult<DBError> = EVMResultGeneric<ResultAndState, DBError>;
7
8/// Generic result of EVM execution. Used to represent error and generic output.
9pub type EVMResultGeneric<T, DBError> = core::result::Result<T, EVMError<DBError>>;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct ResultAndState {
14    /// Status of execution
15    pub result: ExecutionResult,
16    /// State that got updated
17    pub state: EvmState,
18}
19
20/// Result of a transaction execution.
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub enum ExecutionResult {
24    /// Returned successfully
25    Success {
26        reason: SuccessReason,
27        gas_used: u64,
28        gas_refunded: u64,
29        logs: Vec<Log>,
30        output: Output,
31    },
32    /// Reverted by `REVERT` opcode that doesn't spend all gas.
33    Revert { gas_used: u64, output: Bytes },
34    /// Reverted for various reasons and spend all gas.
35    Halt {
36        reason: HaltReason,
37        /// Halting will spend all the gas, and will be equal to gas_limit.
38        gas_used: u64,
39    },
40}
41
42impl ExecutionResult {
43    /// Returns if transaction execution is successful.
44    /// 1 indicates success, 0 indicates revert.
45    /// <https://eips.ethereum.org/EIPS/eip-658>
46    pub fn is_success(&self) -> bool {
47        matches!(self, Self::Success { .. })
48    }
49
50    /// Returns true if execution result is a Halt.
51    pub fn is_halt(&self) -> bool {
52        matches!(self, Self::Halt { .. })
53    }
54
55    /// Returns the output data of the execution.
56    ///
57    /// Returns `None` if the execution was halted.
58    pub fn output(&self) -> Option<&Bytes> {
59        match self {
60            Self::Success { output, .. } => Some(output.data()),
61            Self::Revert { output, .. } => Some(output),
62            _ => None,
63        }
64    }
65
66    /// Consumes the type and returns the output data of the execution.
67    ///
68    /// Returns `None` if the execution was halted.
69    pub fn into_output(self) -> Option<Bytes> {
70        match self {
71            Self::Success { output, .. } => Some(output.into_data()),
72            Self::Revert { output, .. } => Some(output),
73            _ => None,
74        }
75    }
76
77    /// Returns the logs if execution is successful, or an empty list otherwise.
78    pub fn logs(&self) -> &[Log] {
79        match self {
80            Self::Success { logs, .. } => logs,
81            _ => &[],
82        }
83    }
84
85    /// Consumes `self` and returns the logs if execution is successful, or an empty list otherwise.
86    pub fn into_logs(self) -> Vec<Log> {
87        match self {
88            Self::Success { logs, .. } => logs,
89            _ => Vec::new(),
90        }
91    }
92
93    /// Returns the gas used.
94    pub fn gas_used(&self) -> u64 {
95        match *self {
96            Self::Success { gas_used, .. }
97            | Self::Revert { gas_used, .. }
98            | Self::Halt { gas_used, .. } => gas_used,
99        }
100    }
101}
102
103/// Output of a transaction execution.
104#[derive(Debug, Clone, PartialEq, Eq, Hash)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum Output {
107    Call(Bytes),
108    Create(Bytes, Option<Address>),
109}
110
111impl Output {
112    /// Returns the output data of the execution output.
113    pub fn into_data(self) -> Bytes {
114        match self {
115            Output::Call(data) => data,
116            Output::Create(data, _) => data,
117        }
118    }
119
120    /// Returns the output data of the execution output.
121    pub fn data(&self) -> &Bytes {
122        match self {
123            Output::Call(data) => data,
124            Output::Create(data, _) => data,
125        }
126    }
127
128    /// Returns the created address, if any.
129    pub fn address(&self) -> Option<&Address> {
130        match self {
131            Output::Call(_) => None,
132            Output::Create(_, address) => address.as_ref(),
133        }
134    }
135}
136
137/// Main EVM error.
138#[derive(Debug, Clone, PartialEq, Eq, Hash)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
140pub enum EVMError<DBError> {
141    /// Transaction validation error.
142    Transaction(InvalidTransaction),
143    /// Header validation error.
144    Header(InvalidHeader),
145    /// Database error.
146    Database(DBError),
147    /// Custom error.
148    ///
149    /// Useful for handler registers where custom logic would want to return their own custom error.
150    Custom(String),
151    /// Precompile error.
152    Precompile(String),
153}
154
155impl<DBError> EVMError<DBError> {
156    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
157    pub fn map_db_err<F, E>(self, op: F) -> EVMError<E>
158    where
159        F: FnOnce(DBError) -> E,
160    {
161        match self {
162            Self::Transaction(e) => EVMError::Transaction(e),
163            Self::Header(e) => EVMError::Header(e),
164            Self::Database(e) => EVMError::Database(op(e)),
165            Self::Precompile(e) => EVMError::Precompile(e),
166            Self::Custom(e) => EVMError::Custom(e),
167        }
168    }
169}
170
171#[cfg(feature = "std")]
172impl<DBError: std::error::Error + 'static> std::error::Error for EVMError<DBError> {
173    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
174        match self {
175            Self::Transaction(e) => Some(e),
176            Self::Header(e) => Some(e),
177            Self::Database(e) => Some(e),
178            Self::Precompile(_) | Self::Custom(_) => None,
179        }
180    }
181}
182
183impl<DBError: fmt::Display> fmt::Display for EVMError<DBError> {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        match self {
186            Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
187            Self::Header(e) => write!(f, "header validation error: {e}"),
188            Self::Database(e) => write!(f, "database error: {e}"),
189            Self::Precompile(e) | Self::Custom(e) => f.write_str(e),
190        }
191    }
192}
193
194impl<DBError> From<InvalidTransaction> for EVMError<DBError> {
195    fn from(value: InvalidTransaction) -> Self {
196        Self::Transaction(value)
197    }
198}
199
200impl<DBError> From<InvalidHeader> for EVMError<DBError> {
201    fn from(value: InvalidHeader) -> Self {
202        Self::Header(value)
203    }
204}
205
206/// Transaction validation error for Optimism.
207#[cfg(feature = "optimism")]
208#[derive(Debug, Clone, PartialEq, Eq, Hash)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
210pub enum OptimismInvalidTransaction {
211    /// System transactions are not supported post-regolith hardfork.
212    ///
213    /// Before the Regolith hardfork, there was a special field in the `Deposit` transaction
214    /// type that differentiated between `system` and `user` deposit transactions. This field
215    /// was deprecated in the Regolith hardfork, and this error is thrown if a `Deposit` transaction
216    /// is found with this field set to `true` after the hardfork activation.
217    ///
218    /// In addition, this error is internal, and bubbles up into a [HaltReason::FailedDeposit] error
219    /// in the `revm` handler for the consumer to easily handle. This is due to a state transition
220    /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction
221    /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and
222    /// special gas accounting rules are applied. Normally on L1, [EVMError::Transaction] errors
223    /// are cause for non-inclusion, so a special [HaltReason] variant was introduced to handle this
224    /// case for failed deposit transactions.
225    #[cfg(feature = "optimism")]
226    DepositSystemTxPostRegolith,
227    /// Deposit transaction haults bubble up to the global main return handler, wiping state and
228    /// only increasing the nonce + persisting the mint value.
229    ///
230    /// This is a catch-all error for any deposit transaction that is results in a [HaltReason] error
231    /// post-regolith hardfork. This allows for a consumer to easily handle special cases where
232    /// a deposit transaction fails during validation, but must still be included in the block.
233    ///
234    /// In addition, this error is internal, and bubbles up into a [HaltReason::FailedDeposit] error
235    /// in the `revm` handler for the consumer to easily handle. This is due to a state transition
236    /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction
237    /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and
238    /// special gas accounting rules are applied. Normally on L1, [EVMError::Transaction] errors
239    /// are cause for non-inclusion, so a special [HaltReason] variant was introduced to handle this
240    /// case for failed deposit transactions.
241    #[cfg(feature = "optimism")]
242    HaltedDepositPostRegolith,
243}
244
245/// Transaction validation error.
246#[derive(Debug, Clone, PartialEq, Eq, Hash)]
247#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
248pub enum InvalidTransaction {
249    /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields:
250    /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee.
251    /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip".
252    ///
253    /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`.
254    PriorityFeeGreaterThanMaxFee,
255    /// EIP-1559: `gas_price` is less than `basefee`.
256    GasPriceLessThanBasefee,
257    /// `gas_limit` in the tx is bigger than `block_gas_limit`.
258    CallerGasLimitMoreThanBlock,
259    /// Initial gas for a Call is bigger than `gas_limit`.
260    ///
261    /// Initial gas for a Call contains:
262    /// - initial stipend gas
263    /// - gas for access list and input data
264    CallGasCostMoreThanGasLimit,
265    /// EIP-3607 Reject transactions from senders with deployed code
266    RejectCallerWithCode,
267    /// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
268    LackOfFundForMaxFee {
269        fee: Box<U256>,
270        balance: Box<U256>,
271    },
272    /// Overflow payment in transaction.
273    OverflowPaymentInTransaction,
274    /// Nonce overflows in transaction.
275    NonceOverflowInTransaction,
276    NonceTooHigh {
277        tx: u64,
278        state: u64,
279    },
280    NonceTooLow {
281        tx: u64,
282        state: u64,
283    },
284    /// EIP-3860: Limit and meter initcode
285    CreateInitCodeSizeLimit,
286    /// Transaction chain id does not match the config chain id.
287    InvalidChainId,
288    /// Access list is not supported for blocks before the Berlin hardfork.
289    AccessListNotSupported,
290    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
291    MaxFeePerBlobGasNotSupported,
292    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun hardfork.
293    BlobVersionedHashesNotSupported,
294    /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
295    BlobGasPriceGreaterThanMax,
296    /// There should be at least one blob in Blob transaction.
297    EmptyBlobs,
298    /// Blob transaction can't be a create transaction.
299    /// `to` must be present
300    BlobCreateTransaction,
301    /// Transaction has more then [`crate::MAX_BLOB_NUMBER_PER_BLOCK`] blobs
302    TooManyBlobs {
303        max: usize,
304        have: usize,
305    },
306    /// Blob transaction contains a versioned hash with an incorrect version
307    BlobVersionNotSupported,
308    /// EOF crate should have `to` address
309    EofCrateShouldHaveToAddress,
310    /// EIP-7702 is not enabled.
311    AuthorizationListNotSupported,
312    /// EIP-7702 transaction has invalid fields set.
313    AuthorizationListInvalidFields,
314    /// Empty Authorization List is not allowed.
315    EmptyAuthorizationList,
316    /// Optimism-specific transaction validation error.
317    #[cfg(feature = "optimism")]
318    OptimismError(OptimismInvalidTransaction),
319}
320
321#[cfg(feature = "std")]
322impl std::error::Error for InvalidTransaction {}
323
324#[cfg(feature = "optimism")]
325impl fmt::Display for OptimismInvalidTransaction {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        match self {
328            Self::DepositSystemTxPostRegolith => write!(
329                f,
330                "deposit system transactions post regolith hardfork are not supported"
331            ),
332            Self::HaltedDepositPostRegolith => write!(
333                f,
334                "deposit transaction halted post-regolith; error will be bubbled up to main return handler"
335            ),
336        }
337    }
338}
339
340impl fmt::Display for InvalidTransaction {
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        match self {
343            Self::PriorityFeeGreaterThanMaxFee => {
344                write!(f, "priority fee is greater than max fee")
345            }
346            Self::GasPriceLessThanBasefee => {
347                write!(f, "gas price is less than basefee")
348            }
349            Self::CallerGasLimitMoreThanBlock => {
350                write!(f, "caller gas limit exceeds the block gas limit")
351            }
352            Self::CallGasCostMoreThanGasLimit => {
353                write!(f, "call gas cost exceeds the gas limit")
354            }
355            Self::RejectCallerWithCode => {
356                write!(f, "reject transactions from senders with deployed code")
357            }
358            Self::LackOfFundForMaxFee { fee, balance } => {
359                write!(f, "lack of funds ({balance}) for max fee ({fee})")
360            }
361            Self::OverflowPaymentInTransaction => {
362                write!(f, "overflow payment in transaction")
363            }
364            Self::NonceOverflowInTransaction => {
365                write!(f, "nonce overflow in transaction")
366            }
367            Self::NonceTooHigh { tx, state } => {
368                write!(f, "nonce {tx} too high, expected {state}")
369            }
370            Self::NonceTooLow { tx, state } => {
371                write!(f, "nonce {tx} too low, expected {state}")
372            }
373            Self::CreateInitCodeSizeLimit => {
374                write!(f, "create initcode size limit")
375            }
376            Self::InvalidChainId => write!(f, "invalid chain ID"),
377            Self::AccessListNotSupported => write!(f, "access list not supported"),
378            Self::MaxFeePerBlobGasNotSupported => {
379                write!(f, "max fee per blob gas not supported")
380            }
381            Self::BlobVersionedHashesNotSupported => {
382                write!(f, "blob versioned hashes not supported")
383            }
384            Self::BlobGasPriceGreaterThanMax => {
385                write!(f, "blob gas price is greater than max fee per blob gas")
386            }
387            Self::EmptyBlobs => write!(f, "empty blobs"),
388            Self::BlobCreateTransaction => write!(f, "blob create transaction"),
389            Self::TooManyBlobs { max, have } => {
390                write!(f, "too many blobs, have {have}, max {max}")
391            }
392            Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
393            Self::EofCrateShouldHaveToAddress => write!(f, "EOF crate should have `to` address"),
394            Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
395            Self::AuthorizationListInvalidFields => {
396                write!(f, "authorization list tx has invalid fields")
397            }
398            Self::EmptyAuthorizationList => write!(f, "empty authorization list"),
399            #[cfg(feature = "optimism")]
400            Self::OptimismError(op_error) => op_error.fmt(f),
401        }
402    }
403}
404
405/// Errors related to misconfiguration of a [`crate::env::BlockEnv`].
406#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
407#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
408pub enum InvalidHeader {
409    /// `prevrandao` is not set for Merge and above.
410    PrevrandaoNotSet,
411    /// `excess_blob_gas` is not set for Cancun and above.
412    ExcessBlobGasNotSet,
413}
414
415#[cfg(feature = "std")]
416impl std::error::Error for InvalidHeader {}
417
418impl fmt::Display for InvalidHeader {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        match self {
421            Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
422            Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
423        }
424    }
425}
426
427/// Reason a transaction successfully completed.
428#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
429#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
430pub enum SuccessReason {
431    Stop,
432    Return,
433    SelfDestruct,
434    EofReturnContract,
435}
436
437/// Indicates that the EVM has experienced an exceptional halt. This causes execution to
438/// immediately end with all gas being consumed.
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
440#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
441pub enum HaltReason {
442    OutOfGas(OutOfGasError),
443    OpcodeNotFound,
444    InvalidFEOpcode,
445    InvalidJump,
446    NotActivated,
447    StackUnderflow,
448    StackOverflow,
449    OutOfOffset,
450    CreateCollision,
451    PrecompileError,
452    NonceOverflow,
453    /// Create init code size exceeds limit (runtime).
454    CreateContractSizeLimit,
455    /// Error on created contract that begins with EF
456    CreateContractStartingWithEF,
457    /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
458    CreateInitCodeSizeLimit,
459
460    /* Internal Halts that can be only found inside Inspector */
461    OverflowPayment,
462    StateChangeDuringStaticCall,
463    CallNotAllowedInsideStatic,
464    OutOfFunds,
465    CallTooDeep,
466
467    /// Aux data overflow, new aux data is larger than u16 max size.
468    EofAuxDataOverflow,
469    /// Aud data is smaller then already present data size.
470    EofAuxDataTooSmall,
471    /// EOF Subroutine stack overflow
472    EOFFunctionStackOverflow,
473    /// Check for target address validity is only done inside subcall.
474    InvalidEXTCALLTarget,
475
476    /* Optimism errors */
477    #[cfg(feature = "optimism")]
478    FailedDeposit,
479}
480
481#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
482#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
483pub enum OutOfGasError {
484    // Basic OOG error
485    Basic,
486    // Tried to expand past REVM limit
487    MemoryLimit,
488    // Basic OOG error from memory expansion
489    Memory,
490    // Precompile threw OOG error
491    Precompile,
492    // When performing something that takes a U256 and casts down to a u64, if its too large this would fire
493    // i.e. in `as_usize_or_fail`
494    InvalidOperand,
495}