Skip to main content

gear_core_errors/
simple.rs

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