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