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