Skip to main content

truthlinked_sdk/
error.rs

1//! Error handling types and utilities for contract execution.
2//!
3//! This module provides a lightweight error type based on integer error codes,
4//! suitable for `no_std` environments. Contracts return error codes to the runtime,
5//! which can be interpreted by clients.
6//!
7//! # Error Codes
8//!
9//! Error codes are `i32` values where:
10//! - `0` = Success (OK)
11//! - `1-99` = SDK/runtime errors
12//! - `100+` = Contract-specific errors
13//!
14//! # Custom Errors
15//!
16//! Use the `#[error_code]` macro to define custom error enums:
17//!
18//! ```ignore
19//! #[error_code(base = 1000)]
20//! enum TokenError {
21//!     InsufficientBalance,  // 1000
22//!     Unauthorized,         // 1001
23//!     InvalidAmount,        // 1002
24//! }
25//! ```
26//!
27//! # Guards
28//!
29//! Use the `#[require]` macro for precondition checks:
30//!
31//! ```ignore
32//! #[require(amount > 0, TokenError::InvalidAmount)]
33//! fn transfer(amount: u64) -> Result<()> {
34//!     // amount is guaranteed to be > 0
35//! }
36//! ```
37
38#![allow(clippy::module_name_repetitions)]
39
40/// Lightweight error type wrapping an `i32` error code.
41///
42/// This type is designed for `no_std` environments and minimal overhead.
43/// Contracts return error codes to the runtime, which propagates them to clients.
44///
45/// # Error Code Ranges
46///
47/// - `0`: Success (OK)
48/// - `1-9`: Core SDK errors
49/// - `10-19`: ABI/calldata errors
50/// - `20-29`: Codec/serialization errors
51/// - `30-39`: Storage errors
52/// - `40-49`: Validation errors (require)
53/// - `100+`: Contract-specific errors
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct Error(i32);
56
57/// Error code for failed `#[require]` guards without custom error.
58pub const ERR_REQUIRE: i32 = 40;
59
60impl Error {
61    /// Success code (no error).
62    pub const OK: i32 = 0;
63
64    /// Creates a new error with the specified code.
65    ///
66    /// # Arguments
67    ///
68    /// * `code` - The error code (non-zero indicates failure)
69    ///
70    /// # Example
71    ///
72    /// ```ignore
73    /// const ERR_UNAUTHORIZED: i32 = 100;
74    /// return Err(Error::new(ERR_UNAUTHORIZED));
75    /// ```
76    pub const fn new(code: i32) -> Self {
77        Self(code)
78    }
79
80    /// Returns the error code.
81    ///
82    /// # Example
83    ///
84    /// ```ignore
85    /// let err = Error::new(100);
86    /// assert_eq!(err.code(), 100);
87    /// ```
88    pub const fn code(self) -> i32 {
89        self.0
90    }
91
92    /// Checks if this error represents success (code == 0).
93    ///
94    /// # Example
95    ///
96    /// ```ignore
97    /// let ok = Error::new(0);
98    /// assert!(ok.is_ok());
99    ///
100    /// let err = Error::new(100);
101    /// assert!(!err.is_ok());
102    /// ```
103    pub const fn is_ok(self) -> bool {
104        self.0 == Self::OK
105    }
106}
107
108impl From<i32> for Error {
109    fn from(value: i32) -> Self {
110        Self::new(value)
111    }
112}
113
114/// Result type alias using `Error` as the error type.
115///
116/// This is the standard result type for all SDK and contract functions.
117///
118/// # Example
119///
120/// ```ignore
121/// fn transfer(to: AccountId, amount: u64) -> Result<()> {
122///     if amount == 0 {
123///         return Err(Error::new(ERR_INVALID_AMOUNT));
124///     }
125///     // ... perform transfer
126///     Ok(())
127/// }
128/// ```
129pub type Result<T> = core::result::Result<T, Error>;
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[crate::error_code(base = 7000)]
136    #[derive(Clone, Copy)]
137    enum ContractError {
138        NotOwner,
139        Overflow,
140    }
141
142    #[crate::require(2 + 2 == 4)]
143    fn guarded_ok() -> Result<u64> {
144        Ok(1)
145    }
146
147    #[crate::require(false, ContractError::NotOwner)]
148    fn guarded_fail() -> Result<u64> {
149        Ok(1)
150    }
151
152    #[test]
153    fn error_code_attribute_generates_codes() {
154        assert_eq!(ContractError::NotOwner.code(), 7000);
155        assert_eq!(ContractError::Overflow.code(), 7001);
156        let err: Error = ContractError::NotOwner.into();
157        assert_eq!(err.code(), 7000);
158    }
159
160    #[test]
161    fn require_attribute_guards_function() {
162        assert_eq!(guarded_ok().unwrap(), 1);
163        let err = guarded_fail().unwrap_err();
164        assert_eq!(err.code(), 7000);
165    }
166}