gear_core_errors/
simple.rs

1// Copyright (C) 2023-2025 Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Simple errors being used for status codes
18
19use enum_iterator::Sequence;
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22#[cfg(feature = "codec")]
23use {
24    parity_scale_codec::{Decode, Encode},
25    scale_decode::DecodeAsType,
26    scale_encode::EncodeAsType,
27    scale_info::TypeInfo,
28};
29
30/// Enum representing reply code with reason of its creation.
31#[repr(u8)]
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
33#[cfg_attr(
34    feature = "codec",
35    derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
36)]
37#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
38pub enum ReplyCode {
39    /// Success reply.
40    #[error("Success reply sent due to {0}")]
41    Success(#[from] SuccessReplyReason) = 0,
42
43    /// Error reply.
44    #[error("Error reply sent due to {0}")]
45    Error(#[from] ErrorReplyReason) = 1,
46
47    /// Unsupported code.
48    /// Variant exists for backward compatibility.
49    #[error("<unsupported reply code>")]
50    Unsupported = 255,
51}
52
53impl ReplyCode {
54    fn discriminant(&self) -> u8 {
55        // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union`
56        // between `repr(C)` structs, each of which has the `u8` discriminant as its first
57        // field, so we can read the discriminant without offsetting the pointer.
58        unsafe { *<*const _>::from(self).cast::<u8>() }
59    }
60
61    /// Converts `ReplyCode` to 4 bytes array.
62    pub fn to_bytes(self) -> [u8; 4] {
63        let mut bytes = [self.discriminant(), 0, 0, 0];
64
65        match self {
66            Self::Success(reason) => bytes[1..].copy_from_slice(&reason.to_bytes()),
67            Self::Error(reason) => bytes[1..].copy_from_slice(&reason.to_bytes()),
68            Self::Unsupported => {}
69        }
70
71        bytes
72    }
73
74    /// Parses 4 bytes array to `ReplyCode`.
75    pub fn from_bytes(bytes: [u8; 4]) -> Self {
76        match bytes[0] {
77            b if Self::Success(SuccessReplyReason::Unsupported).discriminant() == b => {
78                let reason_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
79                Self::Success(SuccessReplyReason::from_bytes(reason_bytes))
80            }
81            b if Self::Error(ErrorReplyReason::Unsupported).discriminant() == b => {
82                let reason_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
83                Self::Error(ErrorReplyReason::from_bytes(reason_bytes))
84            }
85            _ => Self::Unsupported,
86        }
87    }
88
89    /// Constructs `ReplyCode::Error(_)` variant from underlying reason.
90    pub fn error(reason: impl Into<ErrorReplyReason>) -> Self {
91        Self::Error(reason.into())
92    }
93
94    /// Returns bool, defining if `ReplyCode` represents success reply.
95    pub fn is_success(&self) -> bool {
96        matches!(self, Self::Success(_))
97    }
98
99    /// Returns bool, defining if `ReplyCode` represents error reply.
100    pub fn is_error(&self) -> bool {
101        matches!(self, Self::Error(_))
102    }
103
104    /// Returns bool, defining if `ReplyCode` represents unsupported reason.
105    pub fn is_unsupported(&self) -> bool {
106        matches!(self, Self::Unsupported)
107    }
108}
109
110/// Reason of success reply creation.
111#[repr(u8)]
112#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
113#[cfg_attr(
114    feature = "codec",
115    derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
116)]
117#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
118pub enum SuccessReplyReason {
119    /// Success reply was created by system automatically.
120    #[error("automatic sending")]
121    Auto = 0,
122
123    /// Success reply was created by actor manually.
124    #[error("manual sending")]
125    Manual = 1,
126
127    /// Unsupported reason of success reply.
128    /// Variant exists for backward compatibility.
129    #[error("<unsupported reason>")]
130    Unsupported = 255,
131}
132
133impl SuccessReplyReason {
134    fn to_bytes(self) -> [u8; 3] {
135        [self as u8, 0, 0]
136    }
137
138    fn from_bytes(bytes: [u8; 3]) -> Self {
139        match bytes[0] {
140            b if Self::Auto as u8 == b => Self::Auto,
141            b if Self::Manual as u8 == b => Self::Manual,
142            _ => Self::Unsupported,
143        }
144    }
145}
146
147/// Reason of error reply creation.
148// NOTE: Adding new variants to this enum you must also update `ErrorReplyReason::to_bytes` and
149// `ErrorReplyReason::from_bytes` methods.
150#[repr(u8)]
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
152#[cfg_attr(
153    feature = "codec",
154    derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
155)]
156#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
157pub enum ErrorReplyReason {
158    /// Error reply was created due to underlying execution error.
159    #[error("execution error ({0})")]
160    Execution(#[from] SimpleExecutionError) = 0,
161
162    /// Destination actor is unavailable, so it can't process the message.
163    #[error("destination actor is unavailable ({0})")]
164    UnavailableActor(#[from] SimpleUnavailableActorError) = 2,
165
166    /// Message has died in Waitlist as out of rent one.
167    #[error("removal from waitlist")]
168    RemovedFromWaitlist = 3,
169
170    /// Unsupported reason of error reply.
171    /// Variant exists for backward compatibility.
172    #[error("<unsupported reason>")]
173    Unsupported = 255,
174}
175
176impl ErrorReplyReason {
177    /// Returns bool indicating if self is UnavailableActor::ProgramExited variant.
178    pub fn is_exited(&self) -> bool {
179        matches!(
180            self,
181            Self::UnavailableActor(SimpleUnavailableActorError::ProgramExited)
182        )
183    }
184
185    /// Returns bool indicating if self is Execution::UserspacePanic variant.
186    pub fn is_userspace_panic(&self) -> bool {
187        matches!(self, Self::Execution(SimpleExecutionError::UserspacePanic))
188    }
189
190    fn discriminant(&self) -> u8 {
191        // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union`
192        // between `repr(C)` structs, each of which has the `u8` discriminant as its first
193        // field, so we can read the discriminant without offsetting the pointer.
194        unsafe { *<*const _>::from(self).cast::<u8>() }
195    }
196
197    fn to_bytes(self) -> [u8; 3] {
198        let mut bytes = [self.discriminant(), 0, 0];
199
200        match self {
201            Self::Execution(error) => bytes[1..].copy_from_slice(&error.to_bytes()),
202            Self::UnavailableActor(error) => bytes[1..].copy_from_slice(&error.to_bytes()),
203            Self::RemovedFromWaitlist | Self::Unsupported => {}
204        }
205
206        bytes
207    }
208
209    fn from_bytes(bytes: [u8; 3]) -> Self {
210        match bytes[0] {
211            1 /* removed `FailedToCreateProgram` variant */ |
212            4 /* moved `ReinstrumentationFailure` variant */ => Self::Unsupported,
213            b if Self::Execution(SimpleExecutionError::Unsupported).discriminant() == b => {
214                let err_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
215                Self::Execution(SimpleExecutionError::from_bytes(err_bytes))
216            }
217            b if Self::UnavailableActor(SimpleUnavailableActorError::Unsupported).discriminant() == b => {
218                let err_bytes = bytes[1..].try_into().unwrap_or_else(|_| unreachable!());
219                Self::UnavailableActor(SimpleUnavailableActorError::from_bytes(err_bytes))
220            }
221            b if Self::RemovedFromWaitlist.discriminant() == b => Self::RemovedFromWaitlist,
222            _ => Self::Unsupported,
223        }
224    }
225}
226
227/// Simplified error occurred during execution.
228#[repr(u8)]
229#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
230#[cfg_attr(
231    feature = "codec",
232    derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
233)]
234#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
235pub enum SimpleExecutionError {
236    /// Message ran out of gas while executing.
237    #[error("Message ran out of gas")]
238    RanOutOfGas = 0,
239
240    /// Program has reached memory limit while executing.
241    #[error("Program reached memory limit")]
242    MemoryOverflow = 1,
243
244    /// Execution failed with backend error that couldn't been caught.
245    #[error("Message ran into uncatchable error")]
246    BackendError = 2,
247
248    /// Execution failed with userspace panic.
249    ///
250    /// **PAYLOAD**: Arbitrary payload given by the program as `gr_panic` argument.
251    #[error("Message panicked")]
252    UserspacePanic = 3,
253
254    /// Execution failed with `unreachable` instruction call.
255    #[error("Program called WASM `unreachable` instruction")]
256    UnreachableInstruction = 4,
257
258    /// Program has reached stack limit while executing.
259    #[error("Program reached stack limit")]
260    StackLimitExceeded = 5,
261
262    /// Unsupported reason of execution error.
263    /// Variant exists for backward compatibility.
264    #[error("<unsupported error>")]
265    Unsupported = 255,
266}
267
268impl SimpleExecutionError {
269    fn to_bytes(self) -> [u8; 2] {
270        [self as u8, 0]
271    }
272
273    fn from_bytes(bytes: [u8; 2]) -> Self {
274        match bytes[0] {
275            b if Self::RanOutOfGas as u8 == b => Self::RanOutOfGas,
276            b if Self::MemoryOverflow as u8 == b => Self::MemoryOverflow,
277            b if Self::BackendError as u8 == b => Self::BackendError,
278            b if Self::UserspacePanic as u8 == b => Self::UserspacePanic,
279            b if Self::UnreachableInstruction as u8 == b => Self::UnreachableInstruction,
280            b if Self::StackLimitExceeded as u8 == b => Self::StackLimitExceeded,
281            _ => Self::Unsupported,
282        }
283    }
284}
285
286/// Simplified error occurred because of actor unavailability.
287#[repr(u8)]
288#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
289#[cfg_attr(
290    feature = "codec",
291    derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
292)]
293#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
294pub enum SimpleUnavailableActorError {
295    /// Program called `gr_exit` syscall.
296    ///
297    /// **PAYLOAD**: `ActorId` of the exited program's inheritor (`gr_exit` argument).
298    #[error("Program exited")]
299    ProgramExited = 0,
300
301    /// Program was terminated due to failed initialization.
302    #[error("Program was terminated due failed initialization")]
303    InitializationFailure = 1,
304
305    /// Program is not initialized yet.
306    #[error("Program is not initialized yet")]
307    Uninitialized = 2,
308
309    /// Program was not created.
310    #[error("Program was not created")]
311    ProgramNotCreated = 3,
312
313    /// Program re-instrumentation failed.
314    #[error("Program re-instrumentation failed")]
315    ReinstrumentationFailure = 4,
316
317    /// Unsupported reason of inactive actor error.
318    /// Variant exists for backward compatibility.
319    #[error("<unsupported error>")]
320    Unsupported = 255,
321}
322
323impl SimpleUnavailableActorError {
324    fn to_bytes(self) -> [u8; 2] {
325        [self as u8, 0]
326    }
327
328    fn from_bytes(bytes: [u8; 2]) -> Self {
329        match bytes[0] {
330            b if Self::ProgramExited as u8 == b => Self::ProgramExited,
331            b if Self::InitializationFailure as u8 == b => Self::InitializationFailure,
332            b if Self::Uninitialized as u8 == b => Self::Uninitialized,
333            b if Self::ProgramNotCreated as u8 == b => Self::ProgramNotCreated,
334            b if Self::ReinstrumentationFailure as u8 == b => Self::ReinstrumentationFailure,
335            _ => Self::Unsupported,
336        }
337    }
338}
339
340/// Enum representing signal code and reason of its creation.
341///
342/// # Testing
343/// See [this document](../signal-code-testing.md).
344#[repr(u8)]
345#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
346#[cfg_attr(
347    feature = "codec",
348    derive(Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)
349)]
350#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
351pub enum SignalCode {
352    /// Signal was sent due to some execution errors.
353    #[error("Signal message sent due to execution error ({0})")]
354    Execution(#[from] SimpleExecutionError),
355
356    /// Signal was sent due to removal from waitlist as out of rent.
357    #[error("Signal message sent due to removal from waitlist")]
358    RemovedFromWaitlist,
359}
360
361impl SignalCode {
362    /// Converts `SignalCode` into `u32`.
363    pub const fn to_u32(self) -> u32 {
364        match self {
365            Self::Execution(SimpleExecutionError::UserspacePanic) => 100,
366            Self::Execution(SimpleExecutionError::RanOutOfGas) => 101,
367            Self::Execution(SimpleExecutionError::BackendError) => 102,
368            Self::Execution(SimpleExecutionError::MemoryOverflow) => 103,
369            Self::Execution(SimpleExecutionError::UnreachableInstruction) => 104,
370            Self::Execution(SimpleExecutionError::StackLimitExceeded) => 105,
371            Self::RemovedFromWaitlist => 200,
372            // Must be unreachable.
373            Self::Execution(SimpleExecutionError::Unsupported) => u32::MAX,
374        }
375    }
376
377    /// Parses `SignalCode` from `u32` if possible.
378    pub const fn from_u32(num: u32) -> Option<Self> {
379        let res = match num {
380            v if Self::Execution(SimpleExecutionError::UserspacePanic).to_u32() == v => {
381                Self::Execution(SimpleExecutionError::UserspacePanic)
382            }
383            v if Self::Execution(SimpleExecutionError::RanOutOfGas).to_u32() == v => {
384                Self::Execution(SimpleExecutionError::RanOutOfGas)
385            }
386            v if Self::Execution(SimpleExecutionError::BackendError).to_u32() == v => {
387                Self::Execution(SimpleExecutionError::BackendError)
388            }
389            v if Self::Execution(SimpleExecutionError::MemoryOverflow).to_u32() == v => {
390                Self::Execution(SimpleExecutionError::MemoryOverflow)
391            }
392            v if Self::Execution(SimpleExecutionError::UnreachableInstruction).to_u32() == v => {
393                Self::Execution(SimpleExecutionError::UnreachableInstruction)
394            }
395            v if Self::Execution(SimpleExecutionError::StackLimitExceeded).to_u32() == v => {
396                Self::Execution(SimpleExecutionError::StackLimitExceeded)
397            }
398            v if Self::Execution(SimpleExecutionError::Unsupported).to_u32() == v => {
399                Self::Execution(SimpleExecutionError::Unsupported)
400            }
401            v if Self::RemovedFromWaitlist.to_u32() == v => Self::RemovedFromWaitlist,
402            _ => return None,
403        };
404
405        Some(res)
406    }
407}
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412
413    #[test]
414    fn test_forbidden_codes() {
415        let codes = [
416            1, // `FailedToCreateProgram` variant
417            4, // `ReinstrumentationFailure` variant
418        ];
419
420        // check forbidden code is `Unsupported` variant now
421        for code in codes {
422            let err = ErrorReplyReason::from_bytes([code, 0, 0]);
423            assert_eq!(err, ErrorReplyReason::Unsupported);
424        }
425
426        // check forbidden code is never produced
427        for code in enum_iterator::all::<ErrorReplyReason>() {
428            let bytes = code.to_bytes();
429            assert!(!codes.contains(&bytes[0]));
430        }
431    }
432
433    #[test]
434    fn test_reply_code_encode_decode() {
435        for code in enum_iterator::all::<ReplyCode>() {
436            let bytes = code.to_bytes();
437            assert_eq!(code, ReplyCode::from_bytes(bytes));
438        }
439    }
440
441    #[test]
442    fn test_signal_code_encode_decode() {
443        for signal in enum_iterator::all::<SignalCode>() {
444            let code = signal.to_u32();
445            assert_eq!(signal, SignalCode::from_u32(code).unwrap());
446        }
447    }
448}