trait_keyless/
types.rs

1use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
2use scale_info::TypeInfo;
3
4// Types of identifiers
5pub type AppAgentId = u32;
6pub type TransactionalId = u32;
7
8// identifiers for account type
9pub type AddressIdentifierType = u8;
10pub const APP_AGENT_ADDRESS_IDENTIFIER: AddressIdentifierType = 1;
11pub const TRANSACTIONAL_ADDRESS_IDENTIFIER: AddressIdentifierType = 2;
12pub const NAMED_ADDRESS_IDENTIFIER: AddressIdentifierType = 3;
13
14/// An opaque 32-byte cryptographic identifier.
15#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
16#[cfg_attr(feature = "std", derive(Hash))]
17pub struct AccountId32([u8; 32]);
18
19impl AccountId32 {
20    /// Create a new instance from its raw inner byte value.
21    ///
22    /// Equivalent to this types `From<[u8; 32]>` implementation. For the lack of const
23    /// support in traits we have this constructor.
24    pub const fn new(inner: [u8; 32]) -> Self {
25        Self(inner)
26    }
27
28    pub fn inner(&self) -> &[u8; 32] {
29        &self.0
30    }
31}
32
33impl AsRef<[u8; 32]> for AccountId32 {
34    fn as_ref(&self) -> &[u8; 32] {
35        &self.0
36    }
37}
38
39impl From<[u8; 32]> for AccountId32 {
40    fn from(x: [u8; 32]) -> Self {
41        Self::new(x)
42    }
43}
44
45impl From<AccountId32> for [u8; 32] {
46    fn from(x: AccountId32) -> [u8; 32] {
47        x.0
48    }
49}
50
51#[derive(
52    Clone,
53    Encode,
54    Decode,
55    Debug,
56    Eq,
57    PartialEq,
58    Ord,
59    PartialOrd,
60    Default,
61    Copy,
62    TypeInfo,
63    MaxEncodedLen,
64)]
65pub struct AddressName([u8; 10]);
66
67impl AddressName {
68    const ALLOWED_CHARS: &'static [u8] =
69        b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-#";
70
71    pub fn new(input: &[u8]) -> Result<Self, &'static str> {
72        if input.len() != 10 {
73            return Err("Input must be exactly 10 bytes long");
74        }
75        if input
76            .iter()
77            .all(|&b| AddressName::ALLOWED_CHARS.contains(&b))
78        {
79            let mut array = [0u8; 10];
80            array.copy_from_slice(input);
81            Ok(AddressName(array))
82        } else {
83            Err("Input contains invalid characters")
84        }
85    }
86
87    pub fn as_bytes(&self) -> &[u8; 10] {
88        &self.0
89    }
90
91    pub fn starts_with(&self, prefix: &[u8]) -> bool {
92        self.0.starts_with(prefix)
93    }
94}
95
96impl TryFrom<&[u8]> for AddressName {
97    type Error = &'static str;
98
99    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
100        AddressName::new(value)
101    }
102}
103
104/// Represents the type of a blockchain account.
105///
106/// Variants:
107/// - `Regular`: A standard blockchain account.
108/// - `AppAgent`: An account associated with an application agent.
109/// - `Transactional`: An account used for transactional purposes.
110/// - `Named`: An account that has a specific name associated with it.
111#[derive(Eq, PartialEq, Debug, Clone, Copy)]
112pub enum AddressType {
113    Regular,
114    AppAgent,
115    Transactional,
116    Named,
117}
118
119/// Contains information related to a blockchain account.
120///
121/// Fields:
122/// - `account_id`: The account ID associated with the blockchain address, represented as a vector of bytes.
123/// - `address_type`: The type of the blockchain account.
124/// - `app_agent_id`: The application agent ID associated with the account, if applicable.
125/// - `ta_id`: The transactional account ID, if applicable.
126/// - `account_name`: The name of the account, if applicable.
127#[derive(Eq, PartialEq, Debug)]
128pub struct BlockchainAddressInfo {
129    pub account_id: AccountId32,
130    pub address_type: AddressType,
131    pub app_agent_id: Option<AppAgentId>,
132    pub ta_id: Option<TransactionalId>,
133    pub address_name: Option<AddressName>,
134}
135
136impl BlockchainAddressInfo {
137    /// Helper function to create a new `BlockchainAddressInfo` with the given account_id.
138    /// The `address_type` will be set to `AddressType::Regular`, and other optional fields will be set to `None`.
139    pub fn from_regular_account(account_id: AccountId32) -> Self {
140        Self {
141            account_id,
142            address_type: AddressType::Regular,
143            app_agent_id: None,
144            ta_id: None,
145            address_name: None,
146        }
147    }
148
149    /// Helper function to create a new `BlockchainAddressInfo` with the given account_id, and app_agent_id.
150    /// The `address_type` will be set to `AddressType::AppAgent`, and other optional fields will be set to `None`.
151    pub fn from_app_agent_account(account_id: AccountId32, app_agent_id: AppAgentId) -> Self {
152        Self {
153            account_id,
154            address_type: AddressType::AppAgent,
155            app_agent_id: Some(app_agent_id),
156            ta_id: None,
157            address_name: None,
158        }
159    }
160
161    /// Helper function to create a new `BlockchainAddressInfo` with the given account_id, ta_id and app_agent_id.
162    /// The `address_type` will be set to `AddressType::Transactional`, and other optional fields will be set to `None`.
163    pub fn from_ta_account(
164        account_id: AccountId32,
165        app_agent_id: AppAgentId,
166        ta_id: TransactionalId,
167    ) -> Self {
168        Self {
169            account_id,
170            address_type: AddressType::Transactional,
171            app_agent_id: Some(app_agent_id),
172            ta_id: Some(ta_id),
173            address_name: None,
174        }
175    }
176
177    /// Helper function to create a new `BlockchainAddressInfo` with the given account_id, name and app_agent_id.
178    /// The `address_type` will be set to `AddressType::Named`, and other optional fields will be set to `None`.
179    pub fn from_named_account(
180        account_id: AccountId32,
181        app_agent_id: AppAgentId,
182        name: AddressName,
183    ) -> Self {
184        Self {
185            account_id,
186            address_type: AddressType::Named,
187            app_agent_id: Some(app_agent_id),
188            ta_id: None,
189            address_name: Some(name),
190        }
191    }
192
193    /// Returns true if the address is a keyless address, false otherwise.
194    pub fn is_keyless(&self) -> bool {
195        self.address_type != AddressType::Regular
196    }
197}
198
199/// Internal enum to carry the type and IDs of account
200pub(super) enum BlockchainAccountIds {
201    Regular,
202    AppAgent(AppAgentId),
203    Transactional((AppAgentId, TransactionalId)),
204    Named((AppAgentId, AddressName)),
205}
206
207#[cfg(test)]
208mod tests_address_name {
209    use super::*;
210
211    #[test]
212    fn test_valid_address_name() {
213        let valid_address = b"abcdEF1234";
214        assert!(AddressName::new(valid_address).is_ok());
215    }
216
217    #[test]
218    fn test_invalid_length() {
219        let short_address = b"abcdEF123";
220        let long_address = b"abcdEF12345";
221        assert_eq!(
222            AddressName::new(short_address).unwrap_err(),
223            "Input must be exactly 10 bytes long"
224        );
225        assert_eq!(
226            AddressName::new(long_address).unwrap_err(),
227            "Input must be exactly 10 bytes long"
228        );
229    }
230
231    #[test]
232    fn test_invalid_characters() {
233        let invalid_address1 = b"abcdEF@123";
234        let invalid_address2 = b"abcdEF*123";
235        let invalid_address3 = b"abcdEF/123";
236        assert_eq!(
237            AddressName::new(invalid_address1).unwrap_err(),
238            "Input contains invalid characters"
239        );
240        assert_eq!(
241            AddressName::new(invalid_address2).unwrap_err(),
242            "Input contains invalid characters"
243        );
244        assert_eq!(
245            AddressName::new(invalid_address3).unwrap_err(),
246            "Input contains invalid characters"
247        );
248    }
249
250    #[test]
251    fn test_edge_cases() {
252        let valid_address1 = b"0000000000";
253        let valid_address2 = b"##########";
254        let valid_address3 = b"abcdEFghij";
255        let valid_address4 = b"1234567890";
256
257        assert!(AddressName::new(valid_address1).is_ok());
258        assert!(AddressName::new(valid_address2).is_ok());
259        assert!(AddressName::new(valid_address3).is_ok());
260        assert!(AddressName::new(valid_address4).is_ok());
261    }
262}