rtvm_primitives/
result.rs

1use crate::{Address, Bytes, Log, State, 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: State,
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}
152
153#[cfg(feature = "std")]
154impl<DBError: std::error::Error + 'static> std::error::Error for EVMError<DBError> {
155    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
156        match self {
157            Self::Transaction(e) => Some(e),
158            Self::Header(e) => Some(e),
159            Self::Database(e) => Some(e),
160            Self::Custom(_) => None,
161        }
162    }
163}
164
165impl<DBError: fmt::Display> fmt::Display for EVMError<DBError> {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            Self::Transaction(e) => write!(f, "transaction validation error: {e}"),
169            Self::Header(e) => write!(f, "header validation error: {e}"),
170            Self::Database(e) => write!(f, "database error: {e}"),
171            Self::Custom(e) => f.write_str(e),
172        }
173    }
174}
175
176impl<DBError> From<InvalidTransaction> for EVMError<DBError> {
177    fn from(value: InvalidTransaction) -> Self {
178        Self::Transaction(value)
179    }
180}
181
182impl<DBError> From<InvalidHeader> for EVMError<DBError> {
183    fn from(value: InvalidHeader) -> Self {
184        Self::Header(value)
185    }
186}
187
188/// Transaction validation error.
189#[derive(Debug, Clone, PartialEq, Eq, Hash)]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191pub enum InvalidTransaction {
192    /// When using the EIP-1559 fee model introduced in the London upgrade, transactions specify two primary fee fields:
193    /// - `gas_max_fee`: The maximum total fee a user is willing to pay, inclusive of both base fee and priority fee.
194    /// - `gas_priority_fee`: The extra amount a user is willing to give directly to the miner, often referred to as the "tip".
195    ///
196    /// Provided `gas_priority_fee` exceeds the total `gas_max_fee`.
197    PriorityFeeGreaterThanMaxFee,
198    /// EIP-1559: `gas_price` is less than `basefee`.
199    GasPriceLessThanBasefee,
200    /// `gas_limit` in the tx is bigger than `block_gas_limit`.
201    CallerGasLimitMoreThanBlock,
202    /// Initial gas for a Call is bigger than `gas_limit`.
203    ///
204    /// Initial gas for a Call contains:
205    /// - initial stipend gas
206    /// - gas for access list and input data
207    CallGasCostMoreThanGasLimit,
208    /// EIP-3607 Reject transactions from senders with deployed code
209    RejectCallerWithCode,
210    /// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
211    LackOfFundForMaxFee {
212        fee: Box<U256>,
213        balance: Box<U256>,
214    },
215    /// Overflow payment in transaction.
216    OverflowPaymentInTransaction,
217    /// Nonce overflows in transaction.
218    NonceOverflowInTransaction,
219    NonceTooHigh {
220        tx: u64,
221        state: u64,
222    },
223    NonceTooLow {
224        tx: u64,
225        state: u64,
226    },
227    /// EIP-3860: Limit and meter initcode
228    CreateInitCodeSizeLimit,
229    /// Transaction chain id does not match the config chain id.
230    InvalidChainId,
231    /// Access list is not supported for blocks before the Berlin hardfork.
232    AccessListNotSupported,
233    /// `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.
234    MaxFeePerBlobGasNotSupported,
235    /// `blob_hashes`/`blob_versioned_hashes` is not supported for blocks before the Cancun hardfork.
236    BlobVersionedHashesNotSupported,
237    /// Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas` after Cancun.
238    BlobGasPriceGreaterThanMax,
239    /// There should be at least one blob in Blob transaction.
240    EmptyBlobs,
241    /// Blob transaction can't be a create transaction.
242    /// `to` must be present
243    BlobCreateTransaction,
244    /// Transaction has more then [`crate::MAX_BLOB_NUMBER_PER_BLOCK`] blobs
245    TooManyBlobs,
246    /// Blob transaction contains a versioned hash with an incorrect version
247    BlobVersionNotSupported,
248    /// EOF TxCreate transaction is not supported before Prague hardfork.
249    EofInitcodesNotSupported,
250    /// EOF TxCreate transaction max initcode number reached.
251    EofInitcodesNumberLimit,
252    /// EOF initcode in TXCreate is too large.
253    EofInitcodesSizeLimit,
254    /// EOF crate should have `to` address
255    EofCrateShouldHaveToAddress,
256    /// System transactions are not supported post-regolith hardfork.
257    ///
258    /// Before the Regolith hardfork, there was a special field in the `Deposit` transaction
259    /// type that differentiated between `system` and `user` deposit transactions. This field
260    /// was deprecated in the Regolith hardfork, and this error is thrown if a `Deposit` transaction
261    /// is found with this field set to `true` after the hardfork activation.
262    ///
263    /// In addition, this error is internal, and bubbles up into a [HaltReason::FailedDeposit] error
264    /// in the `rtvm` handler for the consumer to easily handle. This is due to a state transition
265    /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction
266    /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and
267    /// special gas accounting rules are applied. Normally on L1, [EVMError::Transaction] errors
268    /// are cause for non-inclusion, so a special [HaltReason] variant was introduced to handle this
269    /// case for failed deposit transactions.
270    #[cfg(feature = "optimism")]
271    DepositSystemTxPostRegolith,
272    /// Deposit transaction haults bubble up to the global main return handler, wiping state and
273    /// only increasing the nonce + persisting the mint value.
274    ///
275    /// This is a catch-all error for any deposit transaction that is results in a [HaltReason] error
276    /// post-regolith hardfork. This allows for a consumer to easily handle special cases where
277    /// a deposit transaction fails during validation, but must still be included in the block.
278    ///
279    /// In addition, this error is internal, and bubbles up into a [HaltReason::FailedDeposit] error
280    /// in the `rtvm` handler for the consumer to easily handle. This is due to a state transition
281    /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction
282    /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and
283    /// special gas accounting rules are applied. Normally on L1, [EVMError::Transaction] errors
284    /// are cause for non-inclusion, so a special [HaltReason] variant was introduced to handle this
285    /// case for failed deposit transactions.
286    #[cfg(feature = "optimism")]
287    HaltedDepositPostRegolith,
288}
289
290#[cfg(feature = "std")]
291impl std::error::Error for InvalidTransaction {}
292
293impl fmt::Display for InvalidTransaction {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        match self {
296            Self::PriorityFeeGreaterThanMaxFee => {
297                write!(f, "priority fee is greater than max fee")
298            }
299            Self::GasPriceLessThanBasefee => {
300                write!(f, "gas price is less than basefee")
301            }
302            Self::CallerGasLimitMoreThanBlock => {
303                write!(f, "caller gas limit exceeds the block gas limit")
304            }
305            Self::CallGasCostMoreThanGasLimit => {
306                write!(f, "call gas cost exceeds the gas limit")
307            }
308            Self::RejectCallerWithCode => {
309                write!(f, "reject transactions from senders with deployed code")
310            }
311            Self::LackOfFundForMaxFee { fee, balance } => {
312                write!(f, "lack of funds ({balance}) for max fee ({fee})")
313            }
314            Self::OverflowPaymentInTransaction => {
315                write!(f, "overflow payment in transaction")
316            }
317            Self::NonceOverflowInTransaction => {
318                write!(f, "nonce overflow in transaction")
319            }
320            Self::NonceTooHigh { tx, state } => {
321                write!(f, "nonce {tx} too high, expected {state}")
322            }
323            Self::NonceTooLow { tx, state } => {
324                write!(f, "nonce {tx} too low, expected {state}")
325            }
326            Self::CreateInitCodeSizeLimit => {
327                write!(f, "create initcode size limit")
328            }
329            Self::InvalidChainId => write!(f, "invalid chain ID"),
330            Self::AccessListNotSupported => write!(f, "access list not supported"),
331            Self::MaxFeePerBlobGasNotSupported => {
332                write!(f, "max fee per blob gas not supported")
333            }
334            Self::BlobVersionedHashesNotSupported => {
335                write!(f, "blob versioned hashes not supported")
336            }
337            Self::BlobGasPriceGreaterThanMax => {
338                write!(f, "blob gas price is greater than max fee per blob gas")
339            }
340            Self::EmptyBlobs => write!(f, "empty blobs"),
341            Self::BlobCreateTransaction => write!(f, "blob create transaction"),
342            Self::TooManyBlobs => write!(f, "too many blobs"),
343            Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
344            Self::EofInitcodesNotSupported => write!(f, "EOF initcodes not supported"),
345            Self::EofCrateShouldHaveToAddress => write!(f, "EOF crate should have `to` address"),
346            Self::EofInitcodesSizeLimit => write!(f, "EOF initcodes size limit"),
347            Self::EofInitcodesNumberLimit => write!(f, "EOF initcodes number limit"),
348            #[cfg(feature = "optimism")]
349            Self::DepositSystemTxPostRegolith => {
350                write!(
351                    f,
352                    "deposit system transactions post regolith hardfork are not supported"
353                )
354            }
355            #[cfg(feature = "optimism")]
356            Self::HaltedDepositPostRegolith => {
357                write!(
358                    f,
359                    "deposit transaction halted post-regolith; error will be bubbled up to main return handler"
360                )
361            }
362        }
363    }
364}
365
366/// Errors related to misconfiguration of a [`crate::env::BlockEnv`].
367#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
368#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
369pub enum InvalidHeader {
370    /// `prevrandao` is not set for Merge and above.
371    PrevrandaoNotSet,
372    /// `excess_blob_gas` is not set for Cancun and above.
373    ExcessBlobGasNotSet,
374}
375
376#[cfg(feature = "std")]
377impl std::error::Error for InvalidHeader {}
378
379impl fmt::Display for InvalidHeader {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        match self {
382            Self::PrevrandaoNotSet => write!(f, "`prevrandao` not set"),
383            Self::ExcessBlobGasNotSet => write!(f, "`excess_blob_gas` not set"),
384        }
385    }
386}
387
388/// Reason a transaction successfully completed.
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
390#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
391pub enum SuccessReason {
392    Stop,
393    Return,
394    SelfDestruct,
395}
396
397/// Indicates that the EVM has experienced an exceptional halt. This causes execution to
398/// immediately end with all gas being consumed.
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
400#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
401pub enum HaltReason {
402    OutOfGas(OutOfGasError),
403    OpcodeNotFound,
404    InvalidFEOpcode,
405    InvalidJump,
406    NotActivated,
407    StackUnderflow,
408    StackOverflow,
409    OutOfOffset,
410    CreateCollision,
411    PrecompileError,
412    NonceOverflow,
413    /// Create init code size exceeds limit (runtime).
414    CreateContractSizeLimit,
415    /// Error on created contract that begins with EF
416    CreateContractStartingWithEF,
417    /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
418    CreateInitCodeSizeLimit,
419
420    /* Internal Halts that can be only found inside Inspector */
421    OverflowPayment,
422    StateChangeDuringStaticCall,
423    CallNotAllowedInsideStatic,
424    OutOfFunds,
425    CallTooDeep,
426
427    /* Optimism errors */
428    #[cfg(feature = "optimism")]
429    FailedDeposit,
430}
431
432#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
433#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
434pub enum OutOfGasError {
435    // Basic OOG error
436    Basic,
437    // Tried to expand past RTVM limit
438    MemoryLimit,
439    // Basic OOG error from memory expansion
440    Memory,
441    // Precompile threw OOG error
442    Precompile,
443    // When performing something that takes a U256 and casts down to a u64, if its too large this would fire
444    // i.e. in `as_usize_or_fail`
445    InvalidOperand,
446}