Skip to main content

revm_interpreter/
instruction_result.rs

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