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("Not enough gas for operation")]
49    NotEnoughGas = 100,
50
51    /// The error occurs when balance is less than required by operation.
52    #[display("Not enough value for operation")]
53    NotEnoughValue = 101,
54
55    /// Overflow in 'gr_read'
56    #[display("Length is overflowed to read payload")]
57    TooBigReadLen = 103,
58
59    /// Cannot take data in payload range
60    #[display("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("Not running in reply context")]
65    NoReplyContext = 105,
66
67    /// The error occurs when functions related to signal context, used without it.
68    #[display("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("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("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("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("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("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("Message limit exceeded")]
110    OutgoingMessagesAmountLimitExceeded = 301,
111
112    /// The error occurs in case of attempt to send more than one replies.
113    #[display("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("Duplicate waking message")]
119    DuplicateWaking = 303,
120
121    /// An attempt to commit or push a payload into an already formed message.
122    #[display("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("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("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("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("In case of non-zero message gas limit must be greater than mailbox threshold")]
144    InsufficientGasLimit = 308,
145
146    /// The error occurs when program tries to create reply deposit for message
147    /// that already been created within the execution.
148    #[display("Reply deposit already exists for given message")]
149    DuplicateReplyDeposit = 309,
150
151    /// The error occurs when program tries to create reply deposit for message
152    /// that wasn't sent within the execution or for reply.
153    #[display(
154        "Reply deposit could be only created for init or handle message sent within the execution"
155    )]
156    IncorrectMessageForReplyDeposit = 310,
157
158    /// The error occurs when program tries to send messages
159    /// with total size bigger than allowed.
160    #[display("Outgoing messages bytes limit exceeded")]
161    OutgoingMessagesBytesLimitExceeded = 311,
162
163    /// The error occurs when a wrong offset of the input buffer (currently executing message payload)
164    /// is provided.
165    #[display("Offset value for the input payload is out of it's size bounds")]
166    OutOfBoundsInputSliceOffset = 312,
167
168    /// The error occurs when a too big length value to form a slice (range) of the input buffer
169    /// (currently executing message payload) is provided.
170    #[display("Too big length value is set to form a slice (range) of the input buffer")]
171    OutOfBoundsInputSliceLength = 313,
172
173    // TODO: remove after delay refactoring is done
174    /// An error occurs in attempt to charge gas for dispatch stash hold.
175    #[display("Not enough gas to hold dispatch message")]
176    InsufficientGasForDelayedSending = 399,
177}
178
179/// Reservation error.
180#[derive(
181    Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Sequence, derive_more::Display,
182)]
183#[non_exhaustive]
184#[repr(u32)]
185// TODO: refactor after multiple reservations are done
186pub enum ReservationError {
187    /// An error occurs in attempt to unreserve gas with non-existing reservation ID.
188    #[display("Invalid reservation ID")]
189    InvalidReservationId = 500,
190    /// An error occurs in attempt to reserve more times than allowed.
191    #[display("Reservation limit has reached")]
192    ReservationsLimitReached = 501,
193    /// An error occurs in attempt to create reservation for 0 blocks.
194    #[display("Reservation duration cannot be zero")]
195    ZeroReservationDuration = 502,
196    /// An error occurs in attempt to reserve zero gas.
197    #[display("Reservation amount cannot be zero")]
198    ZeroReservationAmount = 503,
199    /// An error occurs in attempt to reserve gas less than mailbox threshold.
200    #[display("Reservation amount cannot be below mailbox threshold")]
201    ReservationBelowMailboxThreshold = 504,
202}
203
204/// An error occurred in API.
205#[derive(
206    Debug,
207    Clone,
208    Copy,
209    Eq,
210    PartialEq,
211    Hash,
212    PartialOrd,
213    Ord,
214    Sequence,
215    derive_more::Display,
216    derive_more::From,
217)]
218#[non_exhaustive]
219pub enum ExtError {
220    /// Execution error.
221    #[display("Execution error: {_0}")]
222    Execution(ExecutionError),
223
224    /// Memory error.
225    #[display("Memory error: {_0}")]
226    Memory(MemoryError),
227
228    /// Message error.
229    #[display("Message error: {_0}")]
230    Message(MessageError),
231
232    /// Reservation error.
233    #[display("Reservation error: {_0}")]
234    Reservation(ReservationError),
235
236    /// There is a new error variant old program don't support.
237    Unsupported,
238}
239
240impl ExtError {
241    /// Convert error into code.
242    pub fn to_u32(self) -> u32 {
243        match self {
244            ExtError::Execution(err) => err as u32,
245            ExtError::Memory(err) => err as u32,
246            ExtError::Message(err) => err as u32,
247            ExtError::Reservation(err) => err as u32,
248            ExtError::Unsupported => u32::MAX,
249        }
250    }
251
252    /// Convert code into error.
253    pub fn from_u32(code: u32) -> Option<Self> {
254        match code {
255            100 => Some(ExecutionError::NotEnoughGas.into()),
256            101 => Some(ExecutionError::NotEnoughValue.into()),
257            103 => Some(ExecutionError::TooBigReadLen.into()),
258            104 => Some(ExecutionError::ReadWrongRange.into()),
259            105 => Some(ExecutionError::NoReplyContext.into()),
260            106 => Some(ExecutionError::NoSignalContext.into()),
261            107 => Some(ExecutionError::NoStatusCodeContext.into()),
262            108 => Some(ExecutionError::IncorrectEntryForReply.into()),
263            //
264            200 => Some(MemoryError::RuntimeAllocOutOfBounds.into()),
265            201 => Some(MemoryError::AccessOutOfBounds.into()),
266            //
267            300 => Some(MessageError::MaxMessageSizeExceed.into()),
268            301 => Some(MessageError::OutgoingMessagesAmountLimitExceeded.into()),
269            302 => Some(MessageError::DuplicateReply.into()),
270            303 => Some(MessageError::DuplicateWaking.into()),
271            304 => Some(MessageError::LateAccess.into()),
272            305 => Some(MessageError::OutOfBounds.into()),
273            306 => Some(MessageError::DuplicateInit.into()),
274            307 => Some(MessageError::InsufficientValue.into()),
275            308 => Some(MessageError::InsufficientGasLimit.into()),
276            309 => Some(MessageError::DuplicateReplyDeposit.into()),
277            310 => Some(MessageError::IncorrectMessageForReplyDeposit.into()),
278            311 => Some(MessageError::OutgoingMessagesBytesLimitExceeded.into()),
279            312 => Some(MessageError::OutOfBoundsInputSliceOffset.into()),
280            313 => Some(MessageError::OutOfBoundsInputSliceLength.into()),
281            399 => Some(MessageError::InsufficientGasForDelayedSending.into()),
282            //
283            500 => Some(ReservationError::InvalidReservationId.into()),
284            501 => Some(ReservationError::ReservationsLimitReached.into()),
285            502 => Some(ReservationError::ZeroReservationDuration.into()),
286            503 => Some(ReservationError::ZeroReservationAmount.into()),
287            504 => Some(ReservationError::ReservationBelowMailboxThreshold.into()),
288            //
289            0xffff /* SyscallUsage */ |
290            600 /* ProgramRent(ProgramRentError::MaximumBlockCountPaid) */ |
291            u32::MAX => Some(ExtError::Unsupported),
292            _ => None,
293        }
294    }
295}
296
297#[cfg(feature = "codec")]
298impl Encode for ExtError {
299    fn encode(&self) -> Vec<u8> {
300        ExtError::to_u32(*self).to_le_bytes().to_vec()
301    }
302}
303
304#[cfg(feature = "codec")]
305impl Decode for ExtError {
306    fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
307        let mut code = [0; 4];
308        input.read(&mut code)?;
309        let err =
310            ExtError::from_u32(u32::from_le_bytes(code)).ok_or("Failed to decode error code")?;
311        Ok(err)
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318    use alloc::collections::BTreeMap;
319
320    #[test]
321    fn error_codes_are_unique() {
322        let mut codes = BTreeMap::new();
323
324        for err in enum_iterator::all::<ExtError>() {
325            let code = err.to_u32();
326            if let Some(same_code_err) = codes.insert(code, err) {
327                panic!("{:?} has same code {:?} as {:?}", same_code_err, code, err);
328            }
329        }
330    }
331
332    #[test]
333    fn encode_decode() {
334        for err in enum_iterator::all::<ExtError>() {
335            let code = err.to_u32();
336            let decoded = ExtError::from_u32(code)
337                .unwrap_or_else(|| unreachable!("failed to decode error code: {}", code));
338            assert_eq!(err, decoded);
339        }
340    }
341
342    #[test]
343    fn error_code_no_specific_value() {
344        for err in enum_iterator::all::<ExtError>() {
345            let code = err.to_u32();
346            assert_ne!(code, 0); // success code
347        }
348    }
349
350    /// check forbidden error codes
351    ///
352    /// forbidden codes either:
353    /// 1. never actually used
354    /// 2. deprecated
355    ///
356    /// codes are forbidden to avoid collision in
357    /// old programs that built their logic on these error codes
358    /// if we accidentally re-use such codes
359    #[test]
360    fn error_codes_forbidden() {
361        let codes = [
362            0xffff, /* SyscallUsage */
363            600,    /* ProgramRent(ProgramRentError::MaximumBlockCountPaid) */
364        ];
365
366        // check forbidden code is `Unsupported` variant now
367        for code in codes {
368            let err = ExtError::from_u32(code);
369            assert_eq!(err, Some(ExtError::Unsupported));
370        }
371
372        // check forbidden code is never produced
373        for err in enum_iterator::all::<ExtError>() {
374            let code = err.to_u32();
375            assert!(!codes.contains(&code));
376        }
377    }
378}