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