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}