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    /// Precompile error
204    Precompile(String),
205}
206
207impl<DBError: DBErrorMarker, TX> From<DBError> for EVMError<DBError, TX> {
208    fn from(value: DBError) -> Self {
209        Self::Database(value)
210    }
211}
212
213pub trait FromStringError {
214    fn from_string(value: String) -> Self;
215}
216
217impl<DB, TX> FromStringError for EVMError<DB, TX> {
218    fn from_string(value: String) -> Self {
219        Self::Custom(value)
220    }
221}
222
223impl<DB, TXE: From<InvalidTransaction>> From<InvalidTransaction> for EVMError<DB, TXE> {
224    fn from(value: InvalidTransaction) -> Self {
225        Self::Transaction(TXE::from(value))
226    }
227}
228
229impl<DBError, TransactionValidationErrorT> EVMError<DBError, TransactionValidationErrorT> {
230    /// Maps a `DBError` to a new error type using the provided closure, leaving other variants unchanged.
231    pub fn map_db_err<F, E>(self, op: F) -> EVMError<E, TransactionValidationErrorT>
232    where
233        F: FnOnce(DBError) -> E,
234    {
235        match self {
236            Self::Transaction(e) => EVMError::Transaction(e),
237            Self::Header(e) => EVMError::Header(e),
238            Self::Database(e) => EVMError::Database(op(e)),
239            Self::Precompile(e) => EVMError::Precompile(e),
240            Self::Custom(e) => EVMError::Custom(e),
241        }
242    }
243}
244
245impl<DBError, TransactionValidationErrorT> core::error::Error
246    for EVMError<DBError, TransactionValidationErrorT>
247where
248    DBError: core::error::Error + 'static,
249    TransactionValidationErrorT: core::error::Error + 'static,
250{
251    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
252        match self {
253            Self::Transaction(e) => Some(e),
254            Self::Header(e) => Some(e),
255            Self::Database(e) => Some(e),
256            Self::Precompile(_) | Self::Custom(_) => None,
257        }
258    }
259}
260
261impl<DBError, TransactionValidationErrorT> fmt::Display
262    for EVMError<DBError, TransactionValidationErrorT>
263where
264    DBError: fmt::Display,
265    TransactionValidationErrorT: fmt::Display,
266{
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        match self {
269            Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
270            Self::Header(e) => write!(f, "header validation error: {e}"),
271            Self::Database(e) => write!(f, "database error: {e}"),
272            Self::Precompile(e) | Self::Custom(e) => f.write_str(e),
273        }
274    }
275}
276
277impl<DBError, TransactionValidationErrorT> From<InvalidHeader>
278    for EVMError<DBError, TransactionValidationErrorT>
279{
280    fn from(value: InvalidHeader) -> Self {
281        Self::Header(value)
282    }
283}
284
285/// Transaction validation error.
286#[derive(Debug, Clone, PartialEq, Eq, Hash)]
287#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
288pub enum InvalidTransaction {
289    /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields:
290    /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee.
291    /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip".
292    ///
293    /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`.
294    PriorityFeeGreaterThanMaxFee,
295    /// EIP-1559: `gas_price` is less than `basefee`.
296    GasPriceLessThanBasefee,
297    /// `gas_limit` in the tx is bigger than `block_gas_limit`.
298    CallerGasLimitMoreThanBlock,
299    /// Initial gas for a Call is bigger than `gas_limit`.
300    ///
301    /// Initial gas for a Call contains:
302    /// - initial stipend gas
303    /// - gas for access list and input data
304    CallGasCostMoreThanGasLimit,
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    /// EIP-3607 Reject transactions from senders with deployed code
311    RejectCallerWithCode,
312    /// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
313    LackOfFundForMaxFee {
314        fee: Box<U256>,
315        balance: Box<U256>,
316    },
317    /// Overflow payment in transaction.
318    OverflowPaymentInTransaction,
319    /// Nonce overflows in transaction.
320    NonceOverflowInTransaction,
321    NonceTooHigh {
322        tx: u64,
323        state: u64,
324    },
325    NonceTooLow {
326        tx: u64,
327        state: u64,
328    },
329    /// EIP-3860: Limit and meter initcode
330    CreateInitCodeSizeLimit,
331    /// Transaction chain id does not match the config chain id.
332    InvalidChainId,
333    /// Access list is not supported for blocks before the Berlin hardfork.
334    AccessListNotSupported,
335    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
336    MaxFeePerBlobGasNotSupported,
337    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun hardfork.
338    BlobVersionedHashesNotSupported,
339    /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
340    BlobGasPriceGreaterThanMax,
341    /// There should be at least one blob in Blob transaction.
342    EmptyBlobs,
343    /// Blob transaction can't be a create transaction.
344    ///
345    /// `to` must be present
346    BlobCreateTransaction,
347    /// Transaction has more then `max` blobs
348    TooManyBlobs {
349        max: usize,
350        have: usize,
351    },
352    /// Blob transaction contains a versioned hash with an incorrect version
353    BlobVersionNotSupported,
354    /// EOF crate should have `to` address
355    EofCrateShouldHaveToAddress,
356    /// EIP-7702 is not enabled.
357    AuthorizationListNotSupported,
358    /// EIP-7702 transaction has invalid fields set.
359    AuthorizationListInvalidFields,
360    /// Empty Authorization List is not allowed.
361    EmptyAuthorizationList,
362    /// EIP-2930 is not supported.
363    Eip2930NotSupported,
364    /// EIP-1559 is not supported.
365    Eip1559NotSupported,
366    /// EIP-4844 is not supported.
367    Eip4844NotSupported,
368    /// EIP-7702 is not supported.
369    Eip7702NotSupported,
370}
371
372impl TransactionError for InvalidTransaction {}
373
374impl core::error::Error for InvalidTransaction {}
375
376impl fmt::Display for InvalidTransaction {
377    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378        match self {
379            Self::PriorityFeeGreaterThanMaxFee => {
380                write!(f, "priority fee is greater than max fee")
381            }
382            Self::GasPriceLessThanBasefee => {
383                write!(f, "gas price is less than basefee")
384            }
385            Self::CallerGasLimitMoreThanBlock => {
386                write!(f, "caller gas limit exceeds the block gas limit")
387            }
388            Self::CallGasCostMoreThanGasLimit => {
389                write!(f, "call gas cost exceeds the gas limit")
390            }
391            Self::GasFloorMoreThanGasLimit => {
392                write!(f, "gas floor exceeds the gas limit")
393            }
394            Self::RejectCallerWithCode => {
395                write!(f, "reject transactions from senders with deployed code")
396            }
397            Self::LackOfFundForMaxFee { fee, balance } => {
398                write!(f, "lack of funds ({balance}) for max fee ({fee})")
399            }
400            Self::OverflowPaymentInTransaction => {
401                write!(f, "overflow payment in transaction")
402            }
403            Self::NonceOverflowInTransaction => {
404                write!(f, "nonce overflow in transaction")
405            }
406            Self::NonceTooHigh { tx, state } => {
407                write!(f, "nonce {tx} too high, expected {state}")
408            }
409            Self::NonceTooLow { tx, state } => {
410                write!(f, "nonce {tx} too low, expected {state}")
411            }
412            Self::CreateInitCodeSizeLimit => {
413                write!(f, "create initcode size limit")
414            }
415            Self::InvalidChainId => write!(f, "invalid chain ID"),
416            Self::AccessListNotSupported => write!(f, "access list not supported"),
417            Self::MaxFeePerBlobGasNotSupported => {
418                write!(f, "max fee per blob gas not supported")
419            }
420            Self::BlobVersionedHashesNotSupported => {
421                write!(f, "blob versioned hashes not supported")
422            }
423            Self::BlobGasPriceGreaterThanMax => {
424                write!(f, "blob gas price is greater than max fee per blob gas")
425            }
426            Self::EmptyBlobs => write!(f, "empty blobs"),
427            Self::BlobCreateTransaction => write!(f, "blob create transaction"),
428            Self::TooManyBlobs { max, have } => {
429                write!(f, "too many blobs, have {have}, max {max}")
430            }
431            Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
432            Self::EofCrateShouldHaveToAddress => write!(f, "EOF crate should have `to` address"),
433            Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
434            Self::AuthorizationListInvalidFields => {
435                write!(f, "authorization list tx has invalid fields")
436            }
437            Self::EmptyAuthorizationList => write!(f, "empty authorization list"),
438            Self::Eip2930NotSupported => write!(f, "Eip2930 is not supported"),
439            Self::Eip1559NotSupported => write!(f, "Eip1559 is not supported"),
440            Self::Eip4844NotSupported => write!(f, "Eip4844 is not supported"),
441            Self::Eip7702NotSupported => write!(f, "Eip7702 is not supported"),
442        }
443    }
444}
445
446/// Errors related to misconfiguration of a [`crate::Block`].
447#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
448#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
449pub enum InvalidHeader {
450    /// `prevrandao` is not set for Merge and above.
451    PrevrandaoNotSet,
452    /// `excess_blob_gas` is not set for Cancun and above.
453    ExcessBlobGasNotSet,
454}
455
456impl core::error::Error for InvalidHeader {}
457
458impl fmt::Display for InvalidHeader {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        match self {
461            Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
462            Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
463        }
464    }
465}
466
467/// Reason a transaction successfully completed.
468#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
469#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
470pub enum SuccessReason {
471    Stop,
472    Return,
473    SelfDestruct,
474    EofReturnContract,
475}
476
477/// Indicates that the EVM has experienced an exceptional halt.
478///
479/// This causes execution to immediately end with all gas being consumed.
480#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
481#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
482pub enum HaltReason {
483    OutOfGas(OutOfGasError),
484    OpcodeNotFound,
485    InvalidFEOpcode,
486    InvalidJump,
487    NotActivated,
488    StackUnderflow,
489    StackOverflow,
490    OutOfOffset,
491    CreateCollision,
492    PrecompileError,
493    NonceOverflow,
494    /// Create init code size exceeds limit (runtime).
495    CreateContractSizeLimit,
496    /// Error on created contract that begins with EF
497    CreateContractStartingWithEF,
498    /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
499    CreateInitCodeSizeLimit,
500
501    /* Internal Halts that can be only found inside Inspector */
502    OverflowPayment,
503    StateChangeDuringStaticCall,
504    CallNotAllowedInsideStatic,
505    OutOfFunds,
506    CallTooDeep,
507
508    /// Aux data overflow, new aux data is larger than [u16] max size.
509    EofAuxDataOverflow,
510    /// Aud data is smaller then already present data size.
511    EofAuxDataTooSmall,
512    /// EOF Subroutine stack overflow
513    SubRoutineStackOverflow,
514    /// Check for target address validity is only done inside subcall.
515    InvalidEXTCALLTarget,
516}
517
518#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
519#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
520pub enum OutOfGasError {
521    // Basic OOG error
522    Basic,
523    // Tried to expand past REVM limit
524    MemoryLimit,
525    // Basic OOG error from memory expansion
526    Memory,
527    // Precompile threw OOG error
528    Precompile,
529    // When performing something that takes a U256 and casts down to a u64, if its too large this would fire
530    // i.e. in `as_usize_or_fail`
531    InvalidOperand,
532    // When performing SSTORE the gasleft is less than or equal to 2300
533    ReentrancySentry,
534}