fluentbase_types/exit_code.rs
1use rwasm::TrapCode;
2use strum_macros::{Display, FromRepr};
3
4/// Exit codes representing various execution outcomes and error conditions.
5///
6/// This enum defines the possible exit codes that can be returned by the execution
7/// environment.
8///
9/// The codes are grouped into several categories:
10/// - Basic status codes (0 to -2)
11/// - Fluentbase-specific error codes (-1000 and below)
12/// - Trap error codes (-2000 and below)
13///
14/// **Note**: Exit codes cannot be positive, as positive values are used to represent
15/// call indices in interrupted executions.
16///
17/// Exit codes are used to represent the outcome of execution across different
18/// environments.
19/// This makes their interpretation somewhat nuanced.
20///
21/// Within applications, developers can use any exit code, but there are conventions:
22/// 1. `Ok` (0) — Indicates successful execution.
23/// 2. `Panic` (-1) — Controlled application reverts (intended error, no gas penalty).
24/// 3. Any other code is treated as an error with a gas penalty and is mapped to `Err` (-2).
25///
26/// Technically, developers *can* return trap codes, but it's generally pointless:
27/// they are replaced by the `Err` (-2) code and still incur gas penalties.
28/// If the error code cannot be determined, it is mapped to `UnknownError` (-1006),
29/// which also results in a gas fine.
30///
31/// The SDK provides helper functions such as `evm_exit` and `evm_panic` to simplify
32/// returning error codes.
33/// These produce Solidity-compatible error outputs.
34///
35/// The `exit` function remains available to all developers.
36/// In some cases, an application may want to simulate trap behavior or explicitly exit with a gas
37/// penalty to comply with gas consumption standards (for instance, EVM runtime).
38///
39/// This behavior is similar to Solidity, where only three exit codes are supported:
40/// - For legacy contracts: `Ok = 1`, `Revert = 0`, `Err = 2`
41/// - For EOF contracts: `Ok = 0`, `Revert = 1`, `Err = 2`
42///
43/// ## Basic Status Codes
44/// * `Ok` (0) - Successful execution
45/// * `Panic` (-1) - Execution panic
46/// * `Err` (-2) - General error
47///
48/// ## Fluentbase Error Codes
49/// * `RootCallOnly` - Operation restricted to root-level calls
50/// * `MalformedBuiltinParams` - Invalid parameters passed to builtin function
51/// * `CallDepthOverflow` - Call stack depth exceeded limit
52/// * `NonNegativeExitCode` - Exit code must be negative
53/// * `UnknownError` - Unspecified error condition
54/// * `InputOutputOutOfBounds` - I/O operation exceeded bounds
55/// * `PrecompileError` - Error in precompiled contract execution
56///
57/// ## Trap Error Codes
58/// * `UnreachableCodeReached` - Execution reached unreachable code
59/// * `MemoryOutOfBounds` - Memory access violation
60/// * `TableOutOfBounds` - Table access violation
61/// * `IndirectCallToNull` - Attempted call to null function pointer
62/// * `IntegerDivisionByZero` - Division by zero
63/// * `IntegerOverflow` - Integer overflow occurred
64/// * `BadConversionToInteger` - Invalid integer conversion
65/// * `StackOverflow` - Stack limit exceeded
66/// * `BadSignature` - Invalid function signature
67/// * `OutOfFuel` - Insufficient gas/fuel for execution
68/// * `GrowthOperationLimited` - Growth operation exceeded limits
69/// * `UnresolvedFunction` - Function not found
70#[derive(Default, Debug, Copy, Clone, Hash, Eq, PartialEq, Display, FromRepr)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72#[repr(i32)]
73pub enum ExitCode {
74 /* Basic Error Codes */
75 /// Execution is finished without errors
76 #[default]
77 Ok = 0,
78 /// Panic is produced by a program (aka revert)
79 Panic = -1,
80 /// An interruption created by runtime (only for system contracts)
81 InterruptionCalled = -3,
82
83 /* Fluentbase Runtime Error Codes */
84 /// Function can only be invoked as the root entry call
85 RootCallOnly = -1002,
86 /// Builtin function received malformed or invalid parameters
87 MalformedBuiltinParams = -1003,
88 /// Exceeded maximum allowed call stack depth
89 CallDepthOverflow = -1004,
90 /// Exit code must be non-negative, but a negative value was used
91 NonNegativeExitCode = -1005,
92 /// Generic catch-all error for unknown failures
93 UnknownError = -1006,
94 /// I/O operation tried to read/write outside allowed buffer bounds
95 InputOutputOutOfBounds = -1007,
96 /// An error happens inside a precompiled contract
97 PrecompileError = -1008,
98 /// Passed bytecode into executor is not supported
99 NotSupportedBytecode = -1009,
100 /// State changed inside immutable call (static=true)
101 StateChangeDuringStaticCall = -1010,
102 /// Create a contract size limit reached (limit depends on the application type)
103 CreateContractSizeLimit = -1011,
104 /// There is a collision on the contract creation (the same address is derived)
105 CreateContractCollision = -1012,
106 /// Created contract starts with invalid bytes (`0xEF`).
107 CreateContractStartingWithEF = -1013,
108 /// A program runs out of memory (max memory pages reached)
109 OutOfMemory = -1014,
110
111 /* Trap Error Codes */
112 /// Execution reached a code path marked as unreachable
113 UnreachableCodeReached = -2001,
114 /// Memory access outside the allocated memory range
115 MemoryOutOfBounds = -2002,
116 /// Table index access outside the allocated table range
117 TableOutOfBounds = -2003,
118 /// Indirect function call attempted with a null function reference
119 IndirectCallToNull = -2004,
120 /// Division or remainder by zero occurred
121 IntegerDivisionByZero = -2005,
122 /// Integer arithmetic operation overflowed the allowed range
123 IntegerOverflow = -2006,
124 /// Invalid conversion to integer (e.g., from NaN or out-of-range value)
125 BadConversionToInteger = -2007,
126 /// Stack reached its limit (overflow or underflow)
127 StackOverflow = -2008,
128 /// Function signature mismatch in a call
129 BadSignature = -2009,
130 /// Execution ran out of allocated fuel/gas
131 OutOfFuel = -2010,
132 /// Call an undefined or unregistered external function
133 UnknownExternalFunction = -2011,
134
135 /* System Error Codes */
136 /// An unexpected fatal execution failure (node should panic or terminate the execution)
137 UnexpectedFatalExecutionFailure = -3001,
138 /// Missing storage slot
139 MissingStorageSlot = -3002,
140}
141
142impl core::error::Error for ExitCode {}
143
144impl ExitCode {
145 /// Check if the exit code represents a trap code
146 pub fn is_trap_code(&self) -> bool {
147 self.into_i32() <= -2000 && self.into_i32() > -3000
148 }
149}
150
151pub trait UnwrapExitCode<T> {
152 fn unwrap_exit_code(self) -> T;
153}
154
155impl<T> UnwrapExitCode<T> for Result<T, ExitCode> {
156 fn unwrap_exit_code(self) -> T {
157 match self {
158 Ok(res) => res,
159 Err(err) => panic!("exit code: {} ({})", err, err.into_i32()),
160 }
161 }
162}
163
164impl From<i32> for ExitCode {
165 fn from(value: i32) -> Self {
166 Self::from_repr(value).unwrap_or(ExitCode::UnknownError)
167 }
168}
169
170impl From<ExitCode> for i32 {
171 fn from(value: ExitCode) -> Self {
172 value as i32
173 }
174}
175
176impl ExitCode {
177 pub fn is_ok(&self) -> bool {
178 self == &Self::Ok
179 }
180
181 /// Returns whether the result is a revert.
182 pub fn is_revert(&self) -> bool {
183 self == &Self::Panic
184 }
185
186 pub fn is_error(&self) -> bool {
187 !self.is_ok() && !self.is_revert()
188 }
189
190 /// Check if the exit code represents a fatal error
191 ///
192 /// Fatal exit code means that the application terminated unexpectedly, for example,
193 /// by having out of fuel or memory out of bounds. It means that we can't handle the output
194 /// state of this contract gracefully.
195 pub fn is_fatal_exit_code(&self) -> bool {
196 self == &ExitCode::UnexpectedFatalExecutionFailure
197 }
198
199 pub const fn into_i32(self) -> i32 {
200 self as i32
201 }
202}
203
204impl From<TrapCode> for ExitCode {
205 fn from(value: TrapCode) -> Self {
206 Self::from(&value)
207 }
208}
209
210impl From<&TrapCode> for ExitCode {
211 fn from(value: &TrapCode) -> Self {
212 match value {
213 TrapCode::UnreachableCodeReached => ExitCode::UnreachableCodeReached,
214 TrapCode::MemoryOutOfBounds => ExitCode::MemoryOutOfBounds,
215 TrapCode::TableOutOfBounds => ExitCode::TableOutOfBounds,
216 TrapCode::IndirectCallToNull => ExitCode::IndirectCallToNull,
217 TrapCode::IntegerDivisionByZero => ExitCode::IntegerDivisionByZero,
218 TrapCode::IntegerOverflow => ExitCode::IntegerOverflow,
219 TrapCode::BadConversionToInteger => ExitCode::BadConversionToInteger,
220 TrapCode::StackOverflow => ExitCode::StackOverflow,
221 TrapCode::BadSignature => ExitCode::BadSignature,
222 TrapCode::OutOfFuel => ExitCode::OutOfFuel,
223 TrapCode::UnknownExternalFunction => ExitCode::UnknownExternalFunction,
224 TrapCode::InterruptionCalled => ExitCode::InterruptionCalled,
225 _ => {
226 #[cfg(feature = "std")]
227 eprintln!("WARN: unknown trap code: {:?}", value);
228 ExitCode::UnknownError
229 }
230 }
231 }
232}
233
234pub type EvmExitCode = u32;