gear_core_errors/
lib.rs

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