Skip to main content

gear_core_errors/
lib.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4//! Gear core errors.
5
6#![no_std]
7#![warn(missing_docs)]
8#![doc(html_logo_url = "https://gear-tech.io/logo.png")]
9#![doc(html_favicon_url = "https://gear-tech.io/favicon.ico")]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11
12extern crate alloc;
13
14use core::fmt::Debug;
15use enum_iterator::Sequence;
16#[cfg(feature = "codec")]
17use {
18    alloc::vec::Vec,
19    parity_scale_codec::{Decode, Encode, Error, Input},
20};
21
22pub use simple::*;
23
24mod simple;
25
26/// Execution error.
27#[repr(u32)]
28#[non_exhaustive]
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
30pub enum ExecutionError {
31    /// An error occurs in attempt to charge more gas than available for operation.
32    #[error("Not enough gas for operation")]
33    NotEnoughGas = 100,
34
35    /// The error occurs when balance is less than required by operation.
36    #[error("Not enough value for operation")]
37    NotEnoughValue = 101,
38
39    /// Overflow in 'gr_read'
40    #[error("Length is overflowed to read payload")]
41    TooBigReadLen = 103,
42
43    /// Cannot take data in payload range
44    #[error("Cannot take data in payload range from message with size")]
45    ReadWrongRange = 104,
46
47    /// The error occurs when functions related to reply context, used without it.
48    #[error("Not running in reply context")]
49    NoReplyContext = 105,
50
51    /// The error occurs when functions related to signal context, used without it.
52    #[error("Not running in signal context")]
53    NoSignalContext = 106,
54
55    /// The error occurs when functions related to status code, used without required context.
56    #[error("No status code in reply/signal context")]
57    NoStatusCodeContext = 107,
58
59    /// An error occurs in attempt to send or push reply while reply function is banned.
60    #[error("Reply sending is only allowed in `init` and `handle` functions")]
61    IncorrectEntryForReply = 108,
62}
63
64/// Memory error.
65#[repr(u32)]
66#[non_exhaustive]
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
68pub enum MemoryError {
69    /// The error occurs, when program tries to allocate in block-chain runtime more memory than allowed.
70    #[error("Trying to allocate more memory in block-chain runtime than allowed")]
71    RuntimeAllocOutOfBounds = 200,
72    /// The error occurs in attempt to access memory outside wasm program memory.
73    #[error("Trying to access memory outside wasm program memory")]
74    AccessOutOfBounds = 201,
75}
76
77/// Error using messages.
78#[repr(u32)]
79#[non_exhaustive]
80#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
81pub enum MessageError {
82    /// Message has bigger then allowed one message size
83    #[error("Max message size exceed")]
84    MaxMessageSizeExceed = 300,
85
86    /// The error "Message limit exceeded" occurs when a program attempts to
87    /// send more than the maximum amount of messages allowed within a single
88    /// execution (current setting - 1024).
89    #[error("Message limit exceeded")]
90    OutgoingMessagesAmountLimitExceeded = 301,
91
92    /// The error occurs in case of attempt to send more than one replies.
93    #[error("Duplicate reply message")]
94    DuplicateReply = 302,
95
96    /// The error occurs in attempt to get the same message from the waitlist
97    /// again (which is waked already).
98    #[error("Duplicate waking message")]
99    DuplicateWaking = 303,
100
101    /// An attempt to commit or push a payload into an already formed message.
102    #[error("An attempt to commit or push a payload into an already formed message")]
103    LateAccess = 304,
104
105    /// The error occurs in case of not valid identifier specified.
106    #[error("Message with given handle is not found")]
107    OutOfBounds = 305,
108
109    /// The error occurs in attempt to initialize the same program twice within
110    /// a single execution.
111    #[error("Duplicated program initialization message")]
112    DuplicateInit = 306,
113
114    /// Everything less than existential deposit but greater than 0 is not considered as available balance and not saved in DB.
115    /// Value between 0 and existential deposit cannot be sent in message.
116    #[error("In case of non-zero message value must be greater than existential deposit")]
117    InsufficientValue = 307,
118
119    /// Everything less than mailbox threshold but greater than 0 is not considered as available gas limit and
120    /// not inserted in mailbox.
121    ///
122    /// Gas limit between 0 and mailbox threshold cannot be inserted in mailbox.
123    #[error("In case of non-zero message gas limit must be greater than mailbox threshold")]
124    InsufficientGasLimit = 308,
125
126    /// The error occurs when program tries to create reply deposit for message
127    /// that already been created within the execution.
128    #[error("Reply deposit already exists for given message")]
129    DuplicateReplyDeposit = 309,
130
131    /// The error occurs when program tries to create reply deposit for message
132    /// that wasn't sent within the execution or for reply.
133    #[error(
134        "Reply deposit could be only created for init or handle message sent within the execution"
135    )]
136    IncorrectMessageForReplyDeposit = 310,
137
138    /// The error occurs when program tries to send messages
139    /// with total size bigger than allowed.
140    #[error("Outgoing messages bytes limit exceeded")]
141    OutgoingMessagesBytesLimitExceeded = 311,
142
143    /// The error occurs when a wrong offset of the input buffer (currently executing message payload)
144    /// is provided.
145    #[error("Offset value for the input payload is out of it's size bounds")]
146    OutOfBoundsInputSliceOffset = 312,
147
148    /// The error occurs when a too big length value to form a slice (range) of the input buffer
149    /// (currently executing message payload) is provided.
150    #[error("Too big length value is set to form a slice (range) of the input buffer")]
151    OutOfBoundsInputSliceLength = 313,
152
153    // TODO: remove after delay refactoring is done
154    /// An error occurs in attempt to charge gas for dispatch stash hold.
155    #[error("Not enough gas to hold dispatch message")]
156    InsufficientGasForDelayedSending = 399,
157}
158
159/// Reservation error.
160// TODO: refactor after multiple reservations are done
161#[repr(u32)]
162#[non_exhaustive]
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
164pub enum ReservationError {
165    /// An error occurs in attempt to unreserve gas with non-existing reservation ID.
166    #[error("Invalid reservation ID")]
167    InvalidReservationId = 500,
168    /// An error occurs in attempt to reserve more times than allowed.
169    #[error("Reservation limit has reached")]
170    ReservationsLimitReached = 501,
171    /// An error occurs in attempt to create reservation for 0 blocks.
172    #[error("Reservation duration cannot be zero")]
173    ZeroReservationDuration = 502,
174    /// An error occurs in attempt to reserve zero gas.
175    #[error("Reservation amount cannot be zero")]
176    ZeroReservationAmount = 503,
177    /// An error occurs in attempt to reserve gas less than mailbox threshold.
178    #[error("Reservation amount cannot be below mailbox threshold")]
179    ReservationBelowMailboxThreshold = 504,
180}
181
182/// An error occurred in API.
183#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Sequence, thiserror::Error)]
184#[non_exhaustive]
185pub enum ExtError {
186    /// Execution error.
187    #[error("Execution error: {0}")]
188    Execution(#[from] ExecutionError),
189
190    /// Memory error.
191    #[error("Memory error: {0}")]
192    Memory(#[from] MemoryError),
193
194    /// Message error.
195    #[error("Message error: {0}")]
196    Message(#[from] MessageError),
197
198    /// Reservation error.
199    #[error("Reservation error: {0}")]
200    Reservation(#[from] ReservationError),
201
202    /// There is a new error variant old program don't support.
203    #[error("Unsupported error")]
204    Unsupported,
205}
206
207impl ExtError {
208    /// Convert error into code.
209    pub fn to_u32(self) -> u32 {
210        match self {
211            ExtError::Execution(err) => err as u32,
212            ExtError::Memory(err) => err as u32,
213            ExtError::Message(err) => err as u32,
214            ExtError::Reservation(err) => err as u32,
215            ExtError::Unsupported => u32::MAX,
216        }
217    }
218
219    /// Convert code into error.
220    pub fn from_u32(code: u32) -> Option<Self> {
221        match code {
222            100 => Some(ExecutionError::NotEnoughGas.into()),
223            101 => Some(ExecutionError::NotEnoughValue.into()),
224            103 => Some(ExecutionError::TooBigReadLen.into()),
225            104 => Some(ExecutionError::ReadWrongRange.into()),
226            105 => Some(ExecutionError::NoReplyContext.into()),
227            106 => Some(ExecutionError::NoSignalContext.into()),
228            107 => Some(ExecutionError::NoStatusCodeContext.into()),
229            108 => Some(ExecutionError::IncorrectEntryForReply.into()),
230            //
231            200 => Some(MemoryError::RuntimeAllocOutOfBounds.into()),
232            201 => Some(MemoryError::AccessOutOfBounds.into()),
233            //
234            300 => Some(MessageError::MaxMessageSizeExceed.into()),
235            301 => Some(MessageError::OutgoingMessagesAmountLimitExceeded.into()),
236            302 => Some(MessageError::DuplicateReply.into()),
237            303 => Some(MessageError::DuplicateWaking.into()),
238            304 => Some(MessageError::LateAccess.into()),
239            305 => Some(MessageError::OutOfBounds.into()),
240            306 => Some(MessageError::DuplicateInit.into()),
241            307 => Some(MessageError::InsufficientValue.into()),
242            308 => Some(MessageError::InsufficientGasLimit.into()),
243            309 => Some(MessageError::DuplicateReplyDeposit.into()),
244            310 => Some(MessageError::IncorrectMessageForReplyDeposit.into()),
245            311 => Some(MessageError::OutgoingMessagesBytesLimitExceeded.into()),
246            312 => Some(MessageError::OutOfBoundsInputSliceOffset.into()),
247            313 => Some(MessageError::OutOfBoundsInputSliceLength.into()),
248            399 => Some(MessageError::InsufficientGasForDelayedSending.into()),
249            //
250            500 => Some(ReservationError::InvalidReservationId.into()),
251            501 => Some(ReservationError::ReservationsLimitReached.into()),
252            502 => Some(ReservationError::ZeroReservationDuration.into()),
253            503 => Some(ReservationError::ZeroReservationAmount.into()),
254            504 => Some(ReservationError::ReservationBelowMailboxThreshold.into()),
255            //
256            0xffff /* SyscallUsage */ |
257            600 /* ProgramRent(ProgramRentError::MaximumBlockCountPaid) */ |
258            u32::MAX => Some(ExtError::Unsupported),
259            _ => None,
260        }
261    }
262}
263
264#[cfg(feature = "codec")]
265impl Encode for ExtError {
266    fn encode(&self) -> Vec<u8> {
267        ExtError::to_u32(*self).to_le_bytes().to_vec()
268    }
269}
270
271#[cfg(feature = "codec")]
272impl Decode for ExtError {
273    fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
274        let mut code = [0; 4];
275        input.read(&mut code)?;
276        let err =
277            ExtError::from_u32(u32::from_le_bytes(code)).ok_or("Failed to decode error code")?;
278        Ok(err)
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use alloc::collections::BTreeMap;
286
287    #[test]
288    fn error_codes_are_unique() {
289        let mut codes = BTreeMap::new();
290
291        for err in enum_iterator::all::<ExtError>() {
292            let code = err.to_u32();
293            if let Some(same_code_err) = codes.insert(code, err) {
294                panic!("{:?} has same code {:?} as {:?}", same_code_err, code, err);
295            }
296        }
297    }
298
299    #[test]
300    fn encode_decode() {
301        for err in enum_iterator::all::<ExtError>() {
302            let code = err.to_u32();
303            let decoded = ExtError::from_u32(code)
304                .unwrap_or_else(|| unreachable!("failed to decode error code: {}", code));
305            assert_eq!(err, decoded);
306        }
307    }
308
309    #[test]
310    fn error_code_no_specific_value() {
311        for err in enum_iterator::all::<ExtError>() {
312            let code = err.to_u32();
313            assert_ne!(code, 0); // success code
314        }
315    }
316
317    /// check forbidden error codes
318    ///
319    /// forbidden codes either:
320    /// 1. never actually used
321    /// 2. deprecated
322    ///
323    /// codes are forbidden to avoid collision in
324    /// old programs that built their logic on these error codes
325    /// if we accidentally re-use such codes
326    #[test]
327    fn error_codes_forbidden() {
328        let codes = [
329            0xffff, /* SyscallUsage */
330            600,    /* ProgramRent(ProgramRentError::MaximumBlockCountPaid) */
331        ];
332
333        // check forbidden code is `Unsupported` variant now
334        for code in codes {
335            let err = ExtError::from_u32(code);
336            assert_eq!(err, Some(ExtError::Unsupported));
337        }
338
339        // check forbidden code is never produced
340        for err in enum_iterator::all::<ExtError>() {
341            let code = err.to_u32();
342            assert!(!codes.contains(&code));
343        }
344    }
345}