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))]
10pub enum InstructionResult {
11    // Success Codes
12    #[default]
13    /// Execution should continue to the next one.
14    Continue = 0x00,
15    /// Encountered a `STOP` opcode
16    Stop,
17    /// Return from the current call.
18    Return,
19    /// Self-destruct the current contract.
20    SelfDestruct,
21    /// Return a contract (used in contract creation).
22    ReturnContract,
23
24    // Revert Codes
25    /// Revert the transaction.
26    Revert = 0x10,
27    /// Exceeded maximum call depth.
28    CallTooDeep,
29    /// Insufficient funds for transfer.
30    OutOfFunds,
31    /// Revert if `CREATE`/`CREATE2` starts with `0xEF00`.
32    CreateInitCodeStartingEF00,
33    /// Invalid EVM Object Format (EOF) init code.
34    InvalidEOFInitCode,
35    /// `ExtDelegateCall` calling a non EOF contract.
36    InvalidExtDelegateCallTarget,
37
38    // Action Codes
39    /// Indicates a call or contract creation.
40    CallOrCreate = 0x20,
41
42    // Error Codes
43    /// Out of gas error.
44    OutOfGas = 0x50,
45    /// Out of gas error encountered during memory expansion.
46    MemoryOOG,
47    /// The memory limit of the EVM has been exceeded.
48    MemoryLimitOOG,
49    /// Out of gas error encountered during the execution of a precompiled contract.
50    PrecompileOOG,
51    /// Out of gas error encountered while calling an invalid operand.
52    InvalidOperandOOG,
53    /// Out of gas error encountered while checking for reentrancy sentry.
54    ReentrancySentryOOG,
55    /// Unknown or invalid opcode.
56    OpcodeNotFound,
57    /// Invalid `CALL` with value transfer in static context.
58    CallNotAllowedInsideStatic,
59    /// Invalid state modification in static call.
60    StateChangeDuringStaticCall,
61    /// An undefined bytecode value encountered during execution.
62    InvalidFEOpcode,
63    /// Invalid jump destination. Dynamic jumps points to invalid not jumpdest opcode.
64    InvalidJump,
65    /// The feature or opcode is not activated in this version of the EVM.
66    NotActivated,
67    /// Attempting to pop a value from an empty stack.
68    StackUnderflow,
69    /// Attempting to push a value onto a full stack.
70    StackOverflow,
71    /// Invalid memory or storage offset.
72    OutOfOffset,
73    /// Address collision during contract creation.
74    CreateCollision,
75    /// Payment amount overflow.
76    OverflowPayment,
77    /// Error in precompiled contract execution.
78    PrecompileError,
79    /// Nonce overflow.
80    NonceOverflow,
81    /// Exceeded contract size limit during creation.
82    CreateContractSizeLimit,
83    /// Created contract starts with invalid bytes (`0xEF`).
84    CreateContractStartingWithEF,
85    /// Exceeded init code size limit (EIP-3860:  Limit and meter initcode).
86    CreateInitCodeSizeLimit,
87    /// Fatal external error. Returned by database.
88    FatalExternalError,
89    /// `RETURNCONTRACT` called outside init EOF code.
90    ReturnContractInNotInitEOF,
91    /// Legacy contract is calling opcode that is enabled only in EOF.
92    EOFOpcodeDisabledInLegacy,
93    /// Stack overflow in EOF subroutine function calls.
94    SubRoutineStackOverflow,
95    /// Aux data overflow, new aux data is larger than `u16` max size.
96    EofAuxDataOverflow,
97    /// Aux data is smaller than already present data size.
98    EofAuxDataTooSmall,
99    /// `EXT*CALL` target address needs to be padded with 0s.
100    InvalidEXTCALLTarget,
101}
102
103impl From<TransferError> for InstructionResult {
104    fn from(e: TransferError) -> Self {
105        match e {
106            TransferError::OutOfFunds => InstructionResult::OutOfFunds,
107            TransferError::OverflowPayment => InstructionResult::OverflowPayment,
108            TransferError::CreateCollision => InstructionResult::CreateCollision,
109        }
110    }
111}
112
113impl From<SuccessReason> for InstructionResult {
114    fn from(value: SuccessReason) -> Self {
115        match value {
116            SuccessReason::Return => InstructionResult::Return,
117            SuccessReason::Stop => InstructionResult::Stop,
118            SuccessReason::SelfDestruct => InstructionResult::SelfDestruct,
119            SuccessReason::EofReturnContract => InstructionResult::ReturnContract,
120        }
121    }
122}
123
124impl From<HaltReason> for InstructionResult {
125    fn from(value: HaltReason) -> Self {
126        match value {
127            HaltReason::OutOfGas(error) => match error {
128                OutOfGasError::Basic => Self::OutOfGas,
129                OutOfGasError::InvalidOperand => Self::InvalidOperandOOG,
130                OutOfGasError::Memory => Self::MemoryOOG,
131                OutOfGasError::MemoryLimit => Self::MemoryLimitOOG,
132                OutOfGasError::Precompile => Self::PrecompileOOG,
133                OutOfGasError::ReentrancySentry => Self::ReentrancySentryOOG,
134            },
135            HaltReason::OpcodeNotFound => Self::OpcodeNotFound,
136            HaltReason::InvalidFEOpcode => Self::InvalidFEOpcode,
137            HaltReason::InvalidJump => Self::InvalidJump,
138            HaltReason::NotActivated => Self::NotActivated,
139            HaltReason::StackOverflow => Self::StackOverflow,
140            HaltReason::StackUnderflow => Self::StackUnderflow,
141            HaltReason::OutOfOffset => Self::OutOfOffset,
142            HaltReason::CreateCollision => Self::CreateCollision,
143            HaltReason::PrecompileError => Self::PrecompileError,
144            HaltReason::NonceOverflow => Self::NonceOverflow,
145            HaltReason::CreateContractSizeLimit => Self::CreateContractSizeLimit,
146            HaltReason::CreateContractStartingWithEF => Self::CreateContractStartingWithEF,
147            HaltReason::CreateInitCodeSizeLimit => Self::CreateInitCodeSizeLimit,
148            HaltReason::OverflowPayment => Self::OverflowPayment,
149            HaltReason::StateChangeDuringStaticCall => Self::StateChangeDuringStaticCall,
150            HaltReason::CallNotAllowedInsideStatic => Self::CallNotAllowedInsideStatic,
151            HaltReason::OutOfFunds => Self::OutOfFunds,
152            HaltReason::CallTooDeep => Self::CallTooDeep,
153            HaltReason::EofAuxDataOverflow => Self::EofAuxDataOverflow,
154            HaltReason::EofAuxDataTooSmall => Self::EofAuxDataTooSmall,
155            HaltReason::SubRoutineStackOverflow => Self::SubRoutineStackOverflow,
156            HaltReason::InvalidEXTCALLTarget => Self::InvalidEXTCALLTarget,
157        }
158    }
159}
160
161#[macro_export]
162macro_rules! return_ok {
163    () => {
164        $crate::InstructionResult::Continue
165            | $crate::InstructionResult::Stop
166            | $crate::InstructionResult::Return
167            | $crate::InstructionResult::SelfDestruct
168            | $crate::InstructionResult::ReturnContract
169    };
170}
171
172#[macro_export]
173macro_rules! return_revert {
174    () => {
175        $crate::InstructionResult::Revert
176            | $crate::InstructionResult::CallTooDeep
177            | $crate::InstructionResult::OutOfFunds
178            | $crate::InstructionResult::InvalidEOFInitCode
179            | $crate::InstructionResult::CreateInitCodeStartingEF00
180            | $crate::InstructionResult::InvalidExtDelegateCallTarget
181    };
182}
183
184#[macro_export]
185macro_rules! return_error {
186    () => {
187        $crate::InstructionResult::OutOfGas
188            | $crate::InstructionResult::MemoryOOG
189            | $crate::InstructionResult::MemoryLimitOOG
190            | $crate::InstructionResult::PrecompileOOG
191            | $crate::InstructionResult::InvalidOperandOOG
192            | $crate::InstructionResult::ReentrancySentryOOG
193            | $crate::InstructionResult::OpcodeNotFound
194            | $crate::InstructionResult::CallNotAllowedInsideStatic
195            | $crate::InstructionResult::StateChangeDuringStaticCall
196            | $crate::InstructionResult::InvalidFEOpcode
197            | $crate::InstructionResult::InvalidJump
198            | $crate::InstructionResult::NotActivated
199            | $crate::InstructionResult::StackUnderflow
200            | $crate::InstructionResult::StackOverflow
201            | $crate::InstructionResult::OutOfOffset
202            | $crate::InstructionResult::CreateCollision
203            | $crate::InstructionResult::OverflowPayment
204            | $crate::InstructionResult::PrecompileError
205            | $crate::InstructionResult::NonceOverflow
206            | $crate::InstructionResult::CreateContractSizeLimit
207            | $crate::InstructionResult::CreateContractStartingWithEF
208            | $crate::InstructionResult::CreateInitCodeSizeLimit
209            | $crate::InstructionResult::FatalExternalError
210            | $crate::InstructionResult::ReturnContractInNotInitEOF
211            | $crate::InstructionResult::EOFOpcodeDisabledInLegacy
212            | $crate::InstructionResult::SubRoutineStackOverflow
213            | $crate::InstructionResult::EofAuxDataTooSmall
214            | $crate::InstructionResult::EofAuxDataOverflow
215            | $crate::InstructionResult::InvalidEXTCALLTarget
216    };
217}
218
219impl InstructionResult {
220    /// Returns whether the result is a success.
221    #[inline]
222    pub const fn is_ok(self) -> bool {
223        matches!(self, crate::return_ok!())
224    }
225
226    #[inline]
227    pub const fn is_ok_or_revert(self) -> bool {
228        matches!(self, crate::return_ok!() | crate::return_revert!())
229    }
230
231    #[inline]
232    pub const fn is_continue(self) -> bool {
233        matches!(self, InstructionResult::Continue)
234    }
235
236    /// Returns whether the result is a revert.
237    #[inline]
238    pub const fn is_revert(self) -> bool {
239        matches!(self, crate::return_revert!())
240    }
241
242    /// Returns whether the result is an error.
243    #[inline]
244    pub const fn is_error(self) -> bool {
245        matches!(self, return_error!())
246    }
247}
248
249/// Internal results that are not exposed externally
250#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
251pub enum InternalResult {
252    /// Internal instruction that signals Interpreter should continue running.
253    InternalContinue,
254    /// Internal instruction that signals call or create.
255    InternalCallOrCreate,
256    /// Internal CREATE/CREATE starts with 0xEF00
257    CreateInitCodeStartingEF00,
258    /// Internal to ExtDelegateCall
259    InvalidExtDelegateCallTarget,
260}
261
262#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
263pub enum SuccessOrHalt<HaltReasonTr> {
264    Success(SuccessReason),
265    Revert,
266    Halt(HaltReasonTr),
267    FatalExternalError,
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::Continue => Self::Internal(InternalResult::InternalContinue), // used only in interpreter loop
319            InstructionResult::Stop => Self::Success(SuccessReason::Stop),
320            InstructionResult::Return => Self::Success(SuccessReason::Return),
321            InstructionResult::SelfDestruct => Self::Success(SuccessReason::SelfDestruct),
322            InstructionResult::Revert => Self::Revert,
323            InstructionResult::CreateInitCodeStartingEF00 => Self::Revert,
324            InstructionResult::CallOrCreate => Self::Internal(InternalResult::InternalCallOrCreate), // used only in interpreter loop
325            InstructionResult::CallTooDeep => Self::Halt(HaltReason::CallTooDeep.into()), // not gonna happen for first call
326            InstructionResult::OutOfFunds => Self::Halt(HaltReason::OutOfFunds.into()), // Check for first call is done separately.
327            InstructionResult::OutOfGas => {
328                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Basic).into())
329            }
330            InstructionResult::MemoryLimitOOG => {
331                Self::Halt(HaltReason::OutOfGas(OutOfGasError::MemoryLimit).into())
332            }
333            InstructionResult::MemoryOOG => {
334                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Memory).into())
335            }
336            InstructionResult::PrecompileOOG => {
337                Self::Halt(HaltReason::OutOfGas(OutOfGasError::Precompile).into())
338            }
339            InstructionResult::InvalidOperandOOG => {
340                Self::Halt(HaltReason::OutOfGas(OutOfGasError::InvalidOperand).into())
341            }
342            InstructionResult::ReentrancySentryOOG => {
343                Self::Halt(HaltReason::OutOfGas(OutOfGasError::ReentrancySentry).into())
344            }
345            InstructionResult::OpcodeNotFound | InstructionResult::ReturnContractInNotInitEOF => {
346                Self::Halt(HaltReason::OpcodeNotFound.into())
347            }
348            InstructionResult::CallNotAllowedInsideStatic => {
349                Self::Halt(HaltReason::CallNotAllowedInsideStatic.into())
350            } // first call is not static call
351            InstructionResult::StateChangeDuringStaticCall => {
352                Self::Halt(HaltReason::StateChangeDuringStaticCall.into())
353            }
354            InstructionResult::InvalidFEOpcode => Self::Halt(HaltReason::InvalidFEOpcode.into()),
355            InstructionResult::InvalidJump => Self::Halt(HaltReason::InvalidJump.into()),
356            InstructionResult::NotActivated => Self::Halt(HaltReason::NotActivated.into()),
357            InstructionResult::StackUnderflow => Self::Halt(HaltReason::StackUnderflow.into()),
358            InstructionResult::StackOverflow => Self::Halt(HaltReason::StackOverflow.into()),
359            InstructionResult::OutOfOffset => Self::Halt(HaltReason::OutOfOffset.into()),
360            InstructionResult::CreateCollision => Self::Halt(HaltReason::CreateCollision.into()),
361            InstructionResult::OverflowPayment => Self::Halt(HaltReason::OverflowPayment.into()), // Check for first call is done separately.
362            InstructionResult::PrecompileError => Self::Halt(HaltReason::PrecompileError.into()),
363            InstructionResult::NonceOverflow => Self::Halt(HaltReason::NonceOverflow.into()),
364            InstructionResult::CreateContractSizeLimit
365            | InstructionResult::CreateContractStartingWithEF => {
366                Self::Halt(HaltReason::CreateContractSizeLimit.into())
367            }
368            InstructionResult::CreateInitCodeSizeLimit => {
369                Self::Halt(HaltReason::CreateInitCodeSizeLimit.into())
370            }
371            // TODO : (EOF) Add proper Revert subtype.
372            InstructionResult::InvalidEOFInitCode => Self::Revert,
373            InstructionResult::FatalExternalError => Self::FatalExternalError,
374            InstructionResult::EOFOpcodeDisabledInLegacy => {
375                Self::Halt(HaltReason::OpcodeNotFound.into())
376            }
377            InstructionResult::SubRoutineStackOverflow => {
378                Self::Halt(HaltReason::SubRoutineStackOverflow.into())
379            }
380            InstructionResult::ReturnContract => Self::Success(SuccessReason::EofReturnContract),
381            InstructionResult::EofAuxDataOverflow => {
382                Self::Halt(HaltReason::EofAuxDataOverflow.into())
383            }
384            InstructionResult::EofAuxDataTooSmall => {
385                Self::Halt(HaltReason::EofAuxDataTooSmall.into())
386            }
387            InstructionResult::InvalidEXTCALLTarget => {
388                Self::Halt(HaltReason::InvalidEXTCALLTarget.into())
389            }
390            InstructionResult::InvalidExtDelegateCallTarget => {
391                Self::Internal(InternalResult::InvalidExtDelegateCallTarget)
392            }
393        }
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use crate::InstructionResult;
400
401    #[test]
402    fn all_results_are_covered() {
403        match InstructionResult::Continue {
404            return_error!() => {}
405            return_revert!() => {}
406            return_ok!() => {}
407            InstructionResult::CallOrCreate => {}
408        }
409    }
410
411    #[test]
412    fn test_results() {
413        let ok_results = vec![
414            InstructionResult::Continue,
415            InstructionResult::Stop,
416            InstructionResult::Return,
417            InstructionResult::SelfDestruct,
418        ];
419
420        for result in ok_results {
421            assert!(result.is_ok());
422            assert!(!result.is_revert());
423            assert!(!result.is_error());
424        }
425
426        let revert_results = vec![
427            InstructionResult::Revert,
428            InstructionResult::CallTooDeep,
429            InstructionResult::OutOfFunds,
430        ];
431
432        for result in revert_results {
433            assert!(!result.is_ok());
434            assert!(result.is_revert());
435            assert!(!result.is_error());
436        }
437
438        let error_results = vec![
439            InstructionResult::OutOfGas,
440            InstructionResult::MemoryOOG,
441            InstructionResult::MemoryLimitOOG,
442            InstructionResult::PrecompileOOG,
443            InstructionResult::InvalidOperandOOG,
444            InstructionResult::OpcodeNotFound,
445            InstructionResult::CallNotAllowedInsideStatic,
446            InstructionResult::StateChangeDuringStaticCall,
447            InstructionResult::InvalidFEOpcode,
448            InstructionResult::InvalidJump,
449            InstructionResult::NotActivated,
450            InstructionResult::StackUnderflow,
451            InstructionResult::StackOverflow,
452            InstructionResult::OutOfOffset,
453            InstructionResult::CreateCollision,
454            InstructionResult::OverflowPayment,
455            InstructionResult::PrecompileError,
456            InstructionResult::NonceOverflow,
457            InstructionResult::CreateContractSizeLimit,
458            InstructionResult::CreateContractStartingWithEF,
459            InstructionResult::CreateInitCodeSizeLimit,
460            InstructionResult::FatalExternalError,
461        ];
462
463        for result in error_results {
464            assert!(!result.is_ok());
465            assert!(!result.is_revert());
466            assert!(result.is_error());
467        }
468    }
469}