devol_accounts_kit/
dvl_error.rs

1use std::fmt::Formatter;
2use crate::errors::{AccountTag, ContractError};
3
4#[derive(Clone, Copy)]
5pub struct DvlError {
6    error: ContractError,
7    account: Option<AccountTag>,
8}
9
10impl DvlError {
11    /// Creates a new `ContractErrorDetails` with a specified error and associated account.
12    pub fn new_with_account(account: AccountTag, error: ContractError) -> Self {
13        Self {
14            error,
15            account: Some(account),
16        }
17    }
18
19    /// Creates a new `ContractErrorDetails` with a specified error without associating an account.
20    pub fn new(error: ContractError) -> Self {
21        Self {
22            error,
23            account: None,
24        }
25    }
26
27    /// Encodes the error details into a 32-bit integer for on-chain error handling.
28    ///
29    /// The error code is a 32-bit unsigned integer where:
30    /// - The most significant bit (MSB) is always set to 1 to indicate a new error system.
31    /// - The next 3 bits (from MSB) are reserved for future use and are currently set to 0.
32    /// - The following 8 bits represent the account identifier if provided, or are set to 0.
33    /// - The least significant 16 bits represent the `ContractError` code.
34    ///
35    /// # Arguments
36    ///
37    /// * `error` - A `ContractError` enumeration representing the specific error.
38    /// * `account` - An optional `Account` enumeration representing the account involved in the error, if applicable.
39    ///
40    /// # Returns
41    ///
42    /// A 32-bit unsigned integer encoding the error and account information according to the rules described.
43    ///
44    /// # Layout
45    ///
46    ///  31 30   29 25    24                           16 15                              0
47    /// +----+----+-------+------------------------------+--------------------------------+
48    /// | 1  | A  | 0 0 0 | Account ID (if provided)     | ContractError Code             |
49    /// +----+----+-------+------------------------------+--------------------------------+
50    ///
51    /// 1 = MSB, always set to indicate a new error system.
52    /// A = Account-related flag (set to 1 if the error is account-specific, 0 otherwise).
53    /// Reserved = Currently unused bits, set to 0.
54    /// Account ID = 8-bit identifier for the account, shifted into bits 23 through 16.
55    /// ContractError Code = 16-bit error code, occupying the least significant bits.
56    pub fn encode(&self) -> u32 {
57        let error_code = self.error as u32;
58        let account_code = self.account.map_or(0, |a| a as u32) << 16;
59        let sign_bit = 1 << 31;
60        let account_related_bit = if self.account.is_some() { 1 << 30 } else { 0 };
61        sign_bit | account_related_bit | account_code | error_code
62    }
63
64    pub fn from_code(code: u32) -> Self {
65        let error_code = (code & 0xFFFF) as u16;
66        let account_code = ((code >> 16) & 0xFF) as u8;
67        let has_account = (code >> 30) & 1 == 1;
68
69        let error = ContractError::from_u16(error_code);
70
71        let account = if has_account {
72            Some(AccountTag::from_u8(account_code))
73        } else {
74            None
75        };
76        Self {
77            error,
78            account,
79        }
80    }
81}
82
83impl std::fmt::Display for DvlError {
84    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85        match self.account {
86            Some(account) => write!(f, "Error: {}, Account: {:?}", self.error, account),
87            None => write!(f, "Error: {}", self.error),
88        }
89    }
90}
91
92impl std::fmt::Debug for DvlError {
93    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
94        write!(f, "DvlError {{ error: {}, account: ", self.error)?;
95        match self.account {
96            Some(account) => write!(f, "{:?}", account),
97            None => write!(f, "None"),
98        }?;
99        write!(f, " }}")
100    }
101}
102
103impl std::error::Error for DvlError {}
104
105#[cfg(test)]
106mod tests {
107    use crate::dvl_error::DvlError;
108    use super::*;
109
110    #[test]
111    fn test_sets_bits_correctly_1() {
112        for error in [
113            ContractError::NoError,
114        ].iter() {
115            for account in [
116                AccountTag::Root,
117                AccountTag::Mints,
118                // Add other AccountTag variants as needed
119            ].iter() {
120                let error_code = DvlError::new_with_account(*account, *error).encode();
121
122                assert_eq!(error_code >> 31, 1, "The most significant bit should always be set.");
123                assert_eq!((error_code >> 30) & 1, 1, "The second most significant bit should be set for account-specific errors.");
124                assert_eq!(error_code & (0xFF << 16), (*account as u32) << 16, "The account code bits 24-16 should correctly represent the account.");
125                assert_eq!(error_code & 0xFFFF, *error as u16 as u32, "The least significant 16 bits should correctly represent the error code.");
126            }
127        }
128    }
129
130    #[test]
131    fn test_sets_bits_correctly_2() {
132        for error in [
133            ContractError::NoError,
134        ].iter() {
135            let error_code = DvlError::new(*error).encode();
136
137            assert_eq!(error_code >> 31, 1, "The most significant bit should always be set.");
138            assert_eq!((error_code >> 30) & 1, 0, "The second most significant bit should not be set for non-account-specific errors.");
139            assert_eq!(error_code & (0xFF << 16), 0, "The account code bits 24-16 should be unset for non-account-specific errors.");
140            assert_eq!(error_code & 0xFFFF, *error as u16 as u32, "The least significant 16 bits should correctly represent the error code.");
141        }
142    }
143
144    #[test]
145    fn test_decode_error() {
146        let error_code = DvlError::new_with_account(AccountTag::AllWorkers, ContractError::AccountOwner).encode();
147        let dvl_error = DvlError::from_code(error_code);
148        assert_eq!(dvl_error.error, ContractError::AccountOwner);
149        assert_eq!(dvl_error.account, Some(AccountTag::AllWorkers));
150    }
151}