revm_context_interface/
result.rs

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