revm_interpreter/
instruction_result.rs

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