Skip to main content

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    /// Invalid encoding of an instruction's immediate operand.
85    InvalidImmediateEncoding,
86}
87
88impl From<TransferError> for InstructionResult {
89    fn from(e: TransferError) -> Self {
90        match e {
91            TransferError::OutOfFunds => InstructionResult::OutOfFunds,
92            TransferError::OverflowPayment => InstructionResult::OverflowPayment,
93            TransferError::CreateCollision => InstructionResult::CreateCollision,
94        }
95    }
96}
97
98impl From<SuccessReason> for InstructionResult {
99    fn from(value: SuccessReason) -> Self {
100        match value {
101            SuccessReason::Return => InstructionResult::Return,
102            SuccessReason::Stop => InstructionResult::Stop,
103            SuccessReason::SelfDestruct => InstructionResult::SelfDestruct,
104        }
105    }
106}
107
108impl From<HaltReason> for InstructionResult {
109    fn from(value: HaltReason) -> Self {
110        match value {
111            HaltReason::OutOfGas(error) => match error {
112                OutOfGasError::Basic => Self::OutOfGas,
113                OutOfGasError::InvalidOperand => Self::InvalidOperandOOG,
114                OutOfGasError::Memory => Self::MemoryOOG,
115                OutOfGasError::MemoryLimit => Self::MemoryLimitOOG,
116                OutOfGasError::Precompile => Self::PrecompileOOG,
117                OutOfGasError::ReentrancySentry => Self::ReentrancySentryOOG,
118            },
119            HaltReason::OpcodeNotFound => Self::OpcodeNotFound,
120            HaltReason::InvalidFEOpcode => Self::InvalidFEOpcode,
121            HaltReason::InvalidJump => Self::InvalidJump,
122            HaltReason::NotActivated => Self::NotActivated,
123            HaltReason::StackOverflow => Self::StackOverflow,
124            HaltReason::StackUnderflow => Self::StackUnderflow,
125            HaltReason::OutOfOffset => Self::OutOfOffset,
126            HaltReason::CreateCollision => Self::CreateCollision,
127            HaltReason::PrecompileError => Self::PrecompileError,
128            HaltReason::PrecompileErrorWithContext(_) => Self::PrecompileError,
129            HaltReason::NonceOverflow => Self::NonceOverflow,
130            HaltReason::CreateContractSizeLimit => Self::CreateContractSizeLimit,
131            HaltReason::CreateContractStartingWithEF => Self::CreateContractStartingWithEF,
132            HaltReason::CreateInitCodeSizeLimit => Self::CreateInitCodeSizeLimit,
133            HaltReason::OverflowPayment => Self::OverflowPayment,
134            HaltReason::StateChangeDuringStaticCall => Self::StateChangeDuringStaticCall,
135            HaltReason::CallNotAllowedInsideStatic => Self::CallNotAllowedInsideStatic,
136            HaltReason::OutOfFunds => Self::OutOfFunds,
137            HaltReason::CallTooDeep => Self::CallTooDeep,
138        }
139    }
140}
141
142/// Macro that matches all successful instruction results.
143/// Used in pattern matching to handle all successful execution outcomes.
144#[macro_export]
145macro_rules! return_ok {
146    () => {
147        $crate::InstructionResult::Stop
148            | $crate::InstructionResult::Return
149            | $crate::InstructionResult::SelfDestruct
150    };
151}
152
153/// Macro that matches all revert instruction results.
154/// Used in pattern matching to handle all revert outcomes.
155#[macro_export]
156macro_rules! return_revert {
157    () => {
158        $crate::InstructionResult::Revert
159            | $crate::InstructionResult::CallTooDeep
160            | $crate::InstructionResult::OutOfFunds
161            | $crate::InstructionResult::InvalidEOFInitCode
162            | $crate::InstructionResult::CreateInitCodeStartingEF00
163            | $crate::InstructionResult::InvalidExtDelegateCallTarget
164    };
165}
166
167/// Macro that matches all error instruction results.
168/// Used in pattern matching to handle all error outcomes.
169#[macro_export]
170macro_rules! return_error {
171    () => {
172        $crate::InstructionResult::OutOfGas
173            | $crate::InstructionResult::MemoryOOG
174            | $crate::InstructionResult::MemoryLimitOOG
175            | $crate::InstructionResult::PrecompileOOG
176            | $crate::InstructionResult::InvalidOperandOOG
177            | $crate::InstructionResult::ReentrancySentryOOG
178            | $crate::InstructionResult::OpcodeNotFound
179            | $crate::InstructionResult::CallNotAllowedInsideStatic
180            | $crate::InstructionResult::StateChangeDuringStaticCall
181            | $crate::InstructionResult::InvalidFEOpcode
182            | $crate::InstructionResult::InvalidJump
183            | $crate::InstructionResult::NotActivated
184            | $crate::InstructionResult::StackUnderflow
185            | $crate::InstructionResult::StackOverflow
186            | $crate::InstructionResult::OutOfOffset
187            | $crate::InstructionResult::CreateCollision
188            | $crate::InstructionResult::OverflowPayment
189            | $crate::InstructionResult::PrecompileError
190            | $crate::InstructionResult::NonceOverflow
191            | $crate::InstructionResult::CreateContractSizeLimit
192            | $crate::InstructionResult::CreateContractStartingWithEF
193            | $crate::InstructionResult::CreateInitCodeSizeLimit
194            | $crate::InstructionResult::FatalExternalError
195            | $crate::InstructionResult::InvalidImmediateEncoding
196    };
197}
198
199impl InstructionResult {
200    /// Returns whether the result is a success.
201    #[inline]
202    pub const fn is_ok(self) -> bool {
203        matches!(self, return_ok!())
204    }
205
206    #[inline]
207    /// Returns whether the result is a success or revert (not an error).
208    pub const fn is_ok_or_revert(self) -> bool {
209        matches!(self, return_ok!() | return_revert!())
210    }
211
212    /// Returns whether the result is a revert.
213    #[inline]
214    pub const fn is_revert(self) -> bool {
215        matches!(self, return_revert!())
216    }
217
218    /// Returns whether the result is an error.
219    #[inline]
220    pub const fn is_error(self) -> bool {
221        matches!(self, return_error!())
222    }
223}
224
225/// Internal results that are not exposed externally
226#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
227pub enum InternalResult {
228    /// Internal CREATE/CREATE starts with 0xEF00
229    CreateInitCodeStartingEF00,
230    /// Internal to ExtDelegateCall
231    InvalidExtDelegateCallTarget,
232}
233
234#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
235/// Represents the outcome of instruction execution, distinguishing between
236/// success, revert, halt (error), fatal external errors, and internal results.
237pub enum SuccessOrHalt<HaltReasonTr> {
238    /// Successful execution with the specific success reason.
239    Success(SuccessReason),
240    /// Execution reverted.
241    Revert,
242    /// Execution halted due to an error.
243    Halt(HaltReasonTr),
244    /// Fatal external error occurred.
245    FatalExternalError,
246    /// Internal execution result not exposed externally.
247    Internal(InternalResult),
248}
249
250impl<HaltReasonTr> SuccessOrHalt<HaltReasonTr> {
251    /// Returns true if the transaction returned successfully without halts.
252    #[inline]
253    pub fn is_success(self) -> bool {
254        matches!(self, SuccessOrHalt::Success(_))
255    }
256
257    /// Returns the [SuccessReason] value if this a successful result
258    #[inline]
259    pub fn to_success(self) -> Option<SuccessReason> {
260        match self {
261            SuccessOrHalt::Success(reason) => Some(reason),
262            _ => None,
263        }
264    }
265
266    /// Returns true if the transaction reverted.
267    #[inline]
268    pub fn is_revert(self) -> bool {
269        matches!(self, SuccessOrHalt::Revert)
270    }
271
272    /// Returns true if the EVM has experienced an exceptional halt
273    #[inline]
274    pub fn is_halt(self) -> bool {
275        matches!(self, SuccessOrHalt::Halt(_))
276    }
277
278    /// Returns the [HaltReason] value the EVM has experienced an exceptional halt
279    #[inline]
280    pub fn to_halt(self) -> Option<HaltReasonTr> {
281        match self {
282            SuccessOrHalt::Halt(reason) => Some(reason),
283            _ => None,
284        }
285    }
286}
287
288impl<HALT: From<HaltReason>> From<HaltReason> for SuccessOrHalt<HALT> {
289    fn from(reason: HaltReason) -> Self {
290        SuccessOrHalt::Halt(reason.into())
291    }
292}
293
294impl<HaltReasonTr: From<HaltReason>> From<InstructionResult> for SuccessOrHalt<HaltReasonTr> {
295    fn from(result: InstructionResult) -> Self {
296        match result {
297            InstructionResult::Stop => Self::Success(SuccessReason::Stop),
298            InstructionResult::Return => Self::Success(SuccessReason::Return),
299            InstructionResult::SelfDestruct => Self::Success(SuccessReason::SelfDestruct),
300            InstructionResult::Revert => Self::Revert,
301            InstructionResult::CreateInitCodeStartingEF00 => Self::Revert,
302            InstructionResult::CallTooDeep => Self::Halt(HaltReason::CallTooDeep.into()), // not gonna happen for first call
303            InstructionResult::OutOfFunds => Self::Halt(HaltReason::OutOfFunds.into()), // Check for first call is done separately.
304            InstructionResult::OutOfGas => {
305                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Basic).into())
306            }
307            InstructionResult::MemoryLimitOOG => {
308                Self::Halt(HaltReason::OutOfGas(OutOfGasError::MemoryLimit).into())
309            }
310            InstructionResult::MemoryOOG => {
311                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Memory).into())
312            }
313            InstructionResult::PrecompileOOG => {
314                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Precompile).into())
315            }
316            InstructionResult::InvalidOperandOOG => {
317                Self::Halt(HaltReason::OutOfGas(OutOfGasError::InvalidOperand).into())
318            }
319            InstructionResult::ReentrancySentryOOG => {
320                Self::Halt(HaltReason::OutOfGas(OutOfGasError::ReentrancySentry).into())
321            }
322            InstructionResult::OpcodeNotFound => Self::Halt(HaltReason::OpcodeNotFound.into()),
323            InstructionResult::CallNotAllowedInsideStatic => {
324                Self::Halt(HaltReason::CallNotAllowedInsideStatic.into())
325            } // first call is not static call
326            InstructionResult::StateChangeDuringStaticCall => {
327                Self::Halt(HaltReason::StateChangeDuringStaticCall.into())
328            }
329            InstructionResult::InvalidFEOpcode => Self::Halt(HaltReason::InvalidFEOpcode.into()),
330            InstructionResult::InvalidJump => Self::Halt(HaltReason::InvalidJump.into()),
331            InstructionResult::NotActivated => Self::Halt(HaltReason::NotActivated.into()),
332            InstructionResult::StackUnderflow => Self::Halt(HaltReason::StackUnderflow.into()),
333            InstructionResult::StackOverflow => Self::Halt(HaltReason::StackOverflow.into()),
334            InstructionResult::OutOfOffset => Self::Halt(HaltReason::OutOfOffset.into()),
335            InstructionResult::CreateCollision => Self::Halt(HaltReason::CreateCollision.into()),
336            InstructionResult::OverflowPayment => Self::Halt(HaltReason::OverflowPayment.into()), // Check for first call is done separately.
337            InstructionResult::PrecompileError => Self::Halt(HaltReason::PrecompileError.into()),
338            InstructionResult::NonceOverflow => Self::Halt(HaltReason::NonceOverflow.into()),
339            InstructionResult::CreateContractSizeLimit => {
340                Self::Halt(HaltReason::CreateContractSizeLimit.into())
341            }
342            InstructionResult::CreateContractStartingWithEF => {
343                Self::Halt(HaltReason::CreateContractStartingWithEF.into())
344            }
345            InstructionResult::CreateInitCodeSizeLimit => {
346                Self::Halt(HaltReason::CreateInitCodeSizeLimit.into())
347            }
348            // TODO : (EOF) Add proper Revert subtype.
349            InstructionResult::InvalidEOFInitCode => Self::Revert,
350            InstructionResult::FatalExternalError => Self::FatalExternalError,
351            InstructionResult::InvalidExtDelegateCallTarget => {
352                Self::Internal(InternalResult::InvalidExtDelegateCallTarget)
353            }
354            InstructionResult::InvalidImmediateEncoding => {
355                Self::Halt(HaltReason::OpcodeNotFound.into())
356            }
357        }
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use crate::InstructionResult;
364
365    #[test]
366    fn exhaustiveness() {
367        match InstructionResult::Stop {
368            return_error!() => {}
369            return_revert!() => {}
370            return_ok!() => {}
371        }
372    }
373
374    #[test]
375    fn test_results() {
376        let ok_results = [
377            InstructionResult::Stop,
378            InstructionResult::Return,
379            InstructionResult::SelfDestruct,
380        ];
381        for result in ok_results {
382            assert!(result.is_ok());
383            assert!(!result.is_revert());
384            assert!(!result.is_error());
385        }
386
387        let revert_results = [
388            InstructionResult::Revert,
389            InstructionResult::CallTooDeep,
390            InstructionResult::OutOfFunds,
391        ];
392        for result in revert_results {
393            assert!(!result.is_ok());
394            assert!(result.is_revert());
395            assert!(!result.is_error());
396        }
397
398        let error_results = [
399            InstructionResult::OutOfGas,
400            InstructionResult::MemoryOOG,
401            InstructionResult::MemoryLimitOOG,
402            InstructionResult::PrecompileOOG,
403            InstructionResult::InvalidOperandOOG,
404            InstructionResult::OpcodeNotFound,
405            InstructionResult::CallNotAllowedInsideStatic,
406            InstructionResult::StateChangeDuringStaticCall,
407            InstructionResult::InvalidFEOpcode,
408            InstructionResult::InvalidJump,
409            InstructionResult::NotActivated,
410            InstructionResult::StackUnderflow,
411            InstructionResult::StackOverflow,
412            InstructionResult::OutOfOffset,
413            InstructionResult::CreateCollision,
414            InstructionResult::OverflowPayment,
415            InstructionResult::PrecompileError,
416            InstructionResult::NonceOverflow,
417            InstructionResult::CreateContractSizeLimit,
418            InstructionResult::CreateContractStartingWithEF,
419            InstructionResult::CreateInitCodeSizeLimit,
420            InstructionResult::FatalExternalError,
421        ];
422        for result in error_results {
423            assert!(!result.is_ok());
424            assert!(!result.is_revert());
425            assert!(result.is_error());
426        }
427    }
428}