revm_interpreter/
instruction_result.rs

1use context_interface::{
2    journaled_state::TransferError,
3    result::{HaltReason, OutOfGasError, SuccessReason},
4};
5use core::fmt::Debug;
6
7/// Result of executing an EVM instruction.
8///
9/// This enum represents all possible outcomes when executing an instruction,
10/// including successful execution, reverts, and various error conditions.
11#[repr(u8)]
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub enum InstructionResult {
15    /// Encountered a `STOP` opcode
16    #[default]
17    Stop = 1, // Start at 1 so that `Result<(), _>::Ok(())` is 0.
18    /// Return from the current call.
19    Return,
20    /// Self-destruct the current contract.
21    SelfDestruct,
22
23    // Revert Codes
24    /// Revert the transaction.
25    Revert = 0x10,
26    /// Exceeded maximum call depth.
27    CallTooDeep,
28    /// Insufficient funds for transfer.
29    OutOfFunds,
30    /// Revert if `CREATE`/`CREATE2` starts with `0xEF00`.
31    CreateInitCodeStartingEF00,
32    /// Invalid EVM Object Format (EOF) init code.
33    InvalidEOFInitCode,
34    /// `ExtDelegateCall` calling a non EOF contract.
35    InvalidExtDelegateCallTarget,
36
37    // Error Codes
38    /// Out of gas error.
39    OutOfGas = 0x20,
40    /// Out of gas error encountered during memory expansion.
41    MemoryOOG,
42    /// The memory limit of the EVM has been exceeded.
43    MemoryLimitOOG,
44    /// Out of gas error encountered during the execution of a precompiled contract.
45    PrecompileOOG,
46    /// Out of gas error encountered while calling an invalid operand.
47    InvalidOperandOOG,
48    /// Out of gas error encountered while checking for reentrancy sentry.
49    ReentrancySentryOOG,
50    /// Unknown or invalid opcode.
51    OpcodeNotFound,
52    /// Invalid `CALL` with value transfer in static context.
53    CallNotAllowedInsideStatic,
54    /// Invalid state modification in static call.
55    StateChangeDuringStaticCall,
56    /// An undefined bytecode value encountered during execution.
57    InvalidFEOpcode,
58    /// Invalid jump destination. Dynamic jumps points to invalid not jumpdest opcode.
59    InvalidJump,
60    /// The feature or opcode is not activated in this version of the EVM.
61    NotActivated,
62    /// Attempting to pop a value from an empty stack.
63    StackUnderflow,
64    /// Attempting to push a value onto a full stack.
65    StackOverflow,
66    /// Invalid memory or storage offset.
67    OutOfOffset,
68    /// Address collision during contract creation.
69    CreateCollision,
70    /// Payment amount overflow.
71    OverflowPayment,
72    /// Error in precompiled contract execution.
73    PrecompileError,
74    /// Nonce overflow.
75    NonceOverflow,
76    /// Exceeded contract size limit during creation.
77    CreateContractSizeLimit,
78    /// Created contract starts with invalid bytes (`0xEF`).
79    CreateContractStartingWithEF,
80    /// Exceeded init code size limit (EIP-3860:  Limit and meter initcode).
81    CreateInitCodeSizeLimit,
82    /// Fatal external error. Returned by database.
83    FatalExternalError,
84}
85
86impl From<TransferError> for InstructionResult {
87    fn from(e: TransferError) -> Self {
88        match e {
89            TransferError::OutOfFunds => InstructionResult::OutOfFunds,
90            TransferError::OverflowPayment => InstructionResult::OverflowPayment,
91            TransferError::CreateCollision => InstructionResult::CreateCollision,
92        }
93    }
94}
95
96impl From<SuccessReason> for InstructionResult {
97    fn from(value: SuccessReason) -> Self {
98        match value {
99            SuccessReason::Return => InstructionResult::Return,
100            SuccessReason::Stop => InstructionResult::Stop,
101            SuccessReason::SelfDestruct => InstructionResult::SelfDestruct,
102        }
103    }
104}
105
106impl From<HaltReason> for InstructionResult {
107    fn from(value: HaltReason) -> Self {
108        match value {
109            HaltReason::OutOfGas(error) => match error {
110                OutOfGasError::Basic => Self::OutOfGas,
111                OutOfGasError::InvalidOperand => Self::InvalidOperandOOG,
112                OutOfGasError::Memory => Self::MemoryOOG,
113                OutOfGasError::MemoryLimit => Self::MemoryLimitOOG,
114                OutOfGasError::Precompile => Self::PrecompileOOG,
115                OutOfGasError::ReentrancySentry => Self::ReentrancySentryOOG,
116            },
117            HaltReason::OpcodeNotFound => Self::OpcodeNotFound,
118            HaltReason::InvalidFEOpcode => Self::InvalidFEOpcode,
119            HaltReason::InvalidJump => Self::InvalidJump,
120            HaltReason::NotActivated => Self::NotActivated,
121            HaltReason::StackOverflow => Self::StackOverflow,
122            HaltReason::StackUnderflow => Self::StackUnderflow,
123            HaltReason::OutOfOffset => Self::OutOfOffset,
124            HaltReason::CreateCollision => Self::CreateCollision,
125            HaltReason::PrecompileError => Self::PrecompileError,
126            HaltReason::NonceOverflow => Self::NonceOverflow,
127            HaltReason::CreateContractSizeLimit => Self::CreateContractSizeLimit,
128            HaltReason::CreateContractStartingWithEF => Self::CreateContractStartingWithEF,
129            HaltReason::CreateInitCodeSizeLimit => Self::CreateInitCodeSizeLimit,
130            HaltReason::OverflowPayment => Self::OverflowPayment,
131            HaltReason::StateChangeDuringStaticCall => Self::StateChangeDuringStaticCall,
132            HaltReason::CallNotAllowedInsideStatic => Self::CallNotAllowedInsideStatic,
133            HaltReason::OutOfFunds => Self::OutOfFunds,
134            HaltReason::CallTooDeep => Self::CallTooDeep,
135        }
136    }
137}
138
139/// Macro that matches all successful instruction results.
140/// Used in pattern matching to handle all successful execution outcomes.
141#[macro_export]
142macro_rules! return_ok {
143    () => {
144        $crate::InstructionResult::Stop
145            | $crate::InstructionResult::Return
146            | $crate::InstructionResult::SelfDestruct
147    };
148}
149
150/// Macro that matches all revert instruction results.
151/// Used in pattern matching to handle all revert outcomes.
152#[macro_export]
153macro_rules! return_revert {
154    () => {
155        $crate::InstructionResult::Revert
156            | $crate::InstructionResult::CallTooDeep
157            | $crate::InstructionResult::OutOfFunds
158            | $crate::InstructionResult::InvalidEOFInitCode
159            | $crate::InstructionResult::CreateInitCodeStartingEF00
160            | $crate::InstructionResult::InvalidExtDelegateCallTarget
161    };
162}
163
164/// Macro that matches all error instruction results.
165/// Used in pattern matching to handle all error outcomes.
166#[macro_export]
167macro_rules! return_error {
168    () => {
169        $crate::InstructionResult::OutOfGas
170            | $crate::InstructionResult::MemoryOOG
171            | $crate::InstructionResult::MemoryLimitOOG
172            | $crate::InstructionResult::PrecompileOOG
173            | $crate::InstructionResult::InvalidOperandOOG
174            | $crate::InstructionResult::ReentrancySentryOOG
175            | $crate::InstructionResult::OpcodeNotFound
176            | $crate::InstructionResult::CallNotAllowedInsideStatic
177            | $crate::InstructionResult::StateChangeDuringStaticCall
178            | $crate::InstructionResult::InvalidFEOpcode
179            | $crate::InstructionResult::InvalidJump
180            | $crate::InstructionResult::NotActivated
181            | $crate::InstructionResult::StackUnderflow
182            | $crate::InstructionResult::StackOverflow
183            | $crate::InstructionResult::OutOfOffset
184            | $crate::InstructionResult::CreateCollision
185            | $crate::InstructionResult::OverflowPayment
186            | $crate::InstructionResult::PrecompileError
187            | $crate::InstructionResult::NonceOverflow
188            | $crate::InstructionResult::CreateContractSizeLimit
189            | $crate::InstructionResult::CreateContractStartingWithEF
190            | $crate::InstructionResult::CreateInitCodeSizeLimit
191            | $crate::InstructionResult::FatalExternalError
192    };
193}
194
195impl InstructionResult {
196    /// Returns whether the result is a success.
197    #[inline]
198    pub const fn is_ok(self) -> bool {
199        matches!(self, return_ok!())
200    }
201
202    #[inline]
203    /// Returns whether the result is a success or revert (not an error).
204    pub const fn is_ok_or_revert(self) -> bool {
205        matches!(self, return_ok!() | return_revert!())
206    }
207
208    /// Returns whether the result is a revert.
209    #[inline]
210    pub const fn is_revert(self) -> bool {
211        matches!(self, return_revert!())
212    }
213
214    /// Returns whether the result is an error.
215    #[inline]
216    pub const fn is_error(self) -> bool {
217        matches!(self, return_error!())
218    }
219}
220
221/// Internal results that are not exposed externally
222#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
223pub enum InternalResult {
224    /// Internal CREATE/CREATE starts with 0xEF00
225    CreateInitCodeStartingEF00,
226    /// Internal to ExtDelegateCall
227    InvalidExtDelegateCallTarget,
228}
229
230#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
231/// Represents the outcome of instruction execution, distinguishing between
232/// success, revert, halt (error), fatal external errors, and internal results.
233pub enum SuccessOrHalt<HaltReasonTr> {
234    /// Successful execution with the specific success reason.
235    Success(SuccessReason),
236    /// Execution reverted.
237    Revert,
238    /// Execution halted due to an error.
239    Halt(HaltReasonTr),
240    /// Fatal external error occurred.
241    FatalExternalError,
242    /// Internal execution result not exposed externally.
243    Internal(InternalResult),
244}
245
246impl<HaltReasonTr> SuccessOrHalt<HaltReasonTr> {
247    /// Returns true if the transaction returned successfully without halts.
248    #[inline]
249    pub fn is_success(self) -> bool {
250        matches!(self, SuccessOrHalt::Success(_))
251    }
252
253    /// Returns the [SuccessReason] value if this a successful result
254    #[inline]
255    pub fn to_success(self) -> Option<SuccessReason> {
256        match self {
257            SuccessOrHalt::Success(reason) => Some(reason),
258            _ => None,
259        }
260    }
261
262    /// Returns true if the transaction reverted.
263    #[inline]
264    pub fn is_revert(self) -> bool {
265        matches!(self, SuccessOrHalt::Revert)
266    }
267
268    /// Returns true if the EVM has experienced an exceptional halt
269    #[inline]
270    pub fn is_halt(self) -> bool {
271        matches!(self, SuccessOrHalt::Halt(_))
272    }
273
274    /// Returns the [HaltReason] value the EVM has experienced an exceptional halt
275    #[inline]
276    pub fn to_halt(self) -> Option<HaltReasonTr> {
277        match self {
278            SuccessOrHalt::Halt(reason) => Some(reason),
279            _ => None,
280        }
281    }
282}
283
284impl<HALT: From<HaltReason>> From<HaltReason> for SuccessOrHalt<HALT> {
285    fn from(reason: HaltReason) -> Self {
286        SuccessOrHalt::Halt(reason.into())
287    }
288}
289
290impl<HaltReasonTr: From<HaltReason>> From<InstructionResult> for SuccessOrHalt<HaltReasonTr> {
291    fn from(result: InstructionResult) -> Self {
292        match result {
293            InstructionResult::Stop => Self::Success(SuccessReason::Stop),
294            InstructionResult::Return => Self::Success(SuccessReason::Return),
295            InstructionResult::SelfDestruct => Self::Success(SuccessReason::SelfDestruct),
296            InstructionResult::Revert => Self::Revert,
297            InstructionResult::CreateInitCodeStartingEF00 => Self::Revert,
298            InstructionResult::CallTooDeep => Self::Halt(HaltReason::CallTooDeep.into()), // not gonna happen for first call
299            InstructionResult::OutOfFunds => Self::Halt(HaltReason::OutOfFunds.into()), // Check for first call is done separately.
300            InstructionResult::OutOfGas => {
301                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Basic).into())
302            }
303            InstructionResult::MemoryLimitOOG => {
304                Self::Halt(HaltReason::OutOfGas(OutOfGasError::MemoryLimit).into())
305            }
306            InstructionResult::MemoryOOG => {
307                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Memory).into())
308            }
309            InstructionResult::PrecompileOOG => {
310                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Precompile).into())
311            }
312            InstructionResult::InvalidOperandOOG => {
313                Self::Halt(HaltReason::OutOfGas(OutOfGasError::InvalidOperand).into())
314            }
315            InstructionResult::ReentrancySentryOOG => {
316                Self::Halt(HaltReason::OutOfGas(OutOfGasError::ReentrancySentry).into())
317            }
318            InstructionResult::OpcodeNotFound => Self::Halt(HaltReason::OpcodeNotFound.into()),
319            InstructionResult::CallNotAllowedInsideStatic => {
320                Self::Halt(HaltReason::CallNotAllowedInsideStatic.into())
321            } // first call is not static call
322            InstructionResult::StateChangeDuringStaticCall => {
323                Self::Halt(HaltReason::StateChangeDuringStaticCall.into())
324            }
325            InstructionResult::InvalidFEOpcode => Self::Halt(HaltReason::InvalidFEOpcode.into()),
326            InstructionResult::InvalidJump => Self::Halt(HaltReason::InvalidJump.into()),
327            InstructionResult::NotActivated => Self::Halt(HaltReason::NotActivated.into()),
328            InstructionResult::StackUnderflow => Self::Halt(HaltReason::StackUnderflow.into()),
329            InstructionResult::StackOverflow => Self::Halt(HaltReason::StackOverflow.into()),
330            InstructionResult::OutOfOffset => Self::Halt(HaltReason::OutOfOffset.into()),
331            InstructionResult::CreateCollision => Self::Halt(HaltReason::CreateCollision.into()),
332            InstructionResult::OverflowPayment => Self::Halt(HaltReason::OverflowPayment.into()), // Check for first call is done separately.
333            InstructionResult::PrecompileError => Self::Halt(HaltReason::PrecompileError.into()),
334            InstructionResult::NonceOverflow => Self::Halt(HaltReason::NonceOverflow.into()),
335            InstructionResult::CreateContractSizeLimit
336            | InstructionResult::CreateContractStartingWithEF => {
337                Self::Halt(HaltReason::CreateContractSizeLimit.into())
338            }
339            InstructionResult::CreateInitCodeSizeLimit => {
340                Self::Halt(HaltReason::CreateInitCodeSizeLimit.into())
341            }
342            // TODO : (EOF) Add proper Revert subtype.
343            InstructionResult::InvalidEOFInitCode => Self::Revert,
344            InstructionResult::FatalExternalError => Self::FatalExternalError,
345            InstructionResult::InvalidExtDelegateCallTarget => {
346                Self::Internal(InternalResult::InvalidExtDelegateCallTarget)
347            }
348        }
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use crate::InstructionResult;
355
356    #[test]
357    fn exhaustiveness() {
358        match InstructionResult::Stop {
359            return_error!() => {}
360            return_revert!() => {}
361            return_ok!() => {}
362        }
363    }
364
365    #[test]
366    fn test_results() {
367        let ok_results = [
368            InstructionResult::Stop,
369            InstructionResult::Return,
370            InstructionResult::SelfDestruct,
371        ];
372        for result in ok_results {
373            assert!(result.is_ok());
374            assert!(!result.is_revert());
375            assert!(!result.is_error());
376        }
377
378        let revert_results = [
379            InstructionResult::Revert,
380            InstructionResult::CallTooDeep,
381            InstructionResult::OutOfFunds,
382        ];
383        for result in revert_results {
384            assert!(!result.is_ok());
385            assert!(result.is_revert());
386            assert!(!result.is_error());
387        }
388
389        let error_results = [
390            InstructionResult::OutOfGas,
391            InstructionResult::MemoryOOG,
392            InstructionResult::MemoryLimitOOG,
393            InstructionResult::PrecompileOOG,
394            InstructionResult::InvalidOperandOOG,
395            InstructionResult::OpcodeNotFound,
396            InstructionResult::CallNotAllowedInsideStatic,
397            InstructionResult::StateChangeDuringStaticCall,
398            InstructionResult::InvalidFEOpcode,
399            InstructionResult::InvalidJump,
400            InstructionResult::NotActivated,
401            InstructionResult::StackUnderflow,
402            InstructionResult::StackOverflow,
403            InstructionResult::OutOfOffset,
404            InstructionResult::CreateCollision,
405            InstructionResult::OverflowPayment,
406            InstructionResult::PrecompileError,
407            InstructionResult::NonceOverflow,
408            InstructionResult::CreateContractSizeLimit,
409            InstructionResult::CreateContractStartingWithEF,
410            InstructionResult::CreateInitCodeSizeLimit,
411            InstructionResult::FatalExternalError,
412        ];
413        for result in error_results {
414            assert!(!result.is_ok());
415            assert!(!result.is_revert());
416            assert!(result.is_error());
417        }
418    }
419}