Skip to main content

icydb_schema/
account.rs

1//! Canonical account atom without storage authority.
2
3use std::{
4    fmt::{self, Display, Formatter},
5    str::FromStr,
6};
7
8use candid::CandidType;
9use icrc_ledger_types::icrc1::account::Account as LedgerAccount;
10use serde::{Deserialize, Serialize};
11
12use crate::{Principal, Subaccount};
13
14/// Canonical ICRC account atom.
15#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
16pub struct Account {
17    owner: Principal,
18    subaccount: Option<Subaccount>,
19}
20
21impl Account {
22    const PRINCIPAL_MAX_LEN: usize = Principal::MAX_LENGTH_IN_BYTES as usize;
23    const TAG_SUBACCOUNT: u8 = 0x80;
24
25    /// Construct from convertible owner and optional subaccount values.
26    pub fn new<P: Into<Principal>, S: Into<Subaccount>>(owner: P, subaccount: Option<S>) -> Self {
27        Self {
28            owner: owner.into(),
29            subaccount: subaccount.map(Into::into),
30        }
31    }
32
33    /// Construct from canonical components.
34    #[must_use]
35    pub const fn from_owner_and_subaccount(
36        owner: Principal,
37        subaccount: Option<Subaccount>,
38    ) -> Self {
39        Self { owner, subaccount }
40    }
41
42    /// Return the owner.
43    #[must_use]
44    pub const fn owner(&self) -> Principal {
45        self.owner
46    }
47
48    /// Return the optional subaccount.
49    #[must_use]
50    pub const fn subaccount(&self) -> Option<Subaccount> {
51        self.subaccount
52    }
53
54    /// Convert to the upstream ICRC account type.
55    #[must_use]
56    pub fn to_icrc_type(self) -> LedgerAccount {
57        LedgerAccount {
58            owner: self.owner.into(),
59            subaccount: self.subaccount.map(|value| value.to_array()),
60        }
61    }
62
63    #[expect(clippy::cast_possible_truncation)]
64    fn ordering_tag(&self) -> u8 {
65        let mut tag = self.owner.as_slice().len().min(u8::MAX as usize) as u8;
66        if self.subaccount.is_some() {
67            tag |= Self::TAG_SUBACCOUNT;
68        }
69        tag
70    }
71}
72
73impl Display for Account {
74    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
75        Display::fmt(&self.to_icrc_type(), formatter)
76    }
77}
78
79impl From<Account> for LedgerAccount {
80    fn from(value: Account) -> Self {
81        value.to_icrc_type()
82    }
83}
84
85impl From<LedgerAccount> for Account {
86    fn from(value: LedgerAccount) -> Self {
87        Self {
88            owner: value.owner.into(),
89            subaccount: value.subaccount.map(Subaccount::from_array),
90        }
91    }
92}
93
94impl From<Principal> for Account {
95    fn from(owner: Principal) -> Self {
96        Self {
97            owner,
98            subaccount: None,
99        }
100    }
101}
102
103impl FromStr for Account {
104    type Err = String;
105
106    fn from_str(input: &str) -> Result<Self, Self::Err> {
107        LedgerAccount::from_str(input)
108            .map(Self::from)
109            .map_err(|error| error.to_string())
110    }
111}
112
113impl Ord for Account {
114    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
115        self.ordering_tag()
116            .cmp(&other.ordering_tag())
117            .then_with(|| {
118                let mut left = [0; Self::PRINCIPAL_MAX_LEN];
119                let left_bytes = self.owner.as_slice();
120                left[..left_bytes.len()].copy_from_slice(left_bytes);
121                let mut right = [0; Self::PRINCIPAL_MAX_LEN];
122                let right_bytes = other.owner.as_slice();
123                right[..right_bytes.len()].copy_from_slice(right_bytes);
124                left.cmp(&right)
125            })
126            .then_with(|| {
127                self.subaccount
128                    .unwrap_or(Subaccount::MIN)
129                    .cmp(&other.subaccount.unwrap_or(Subaccount::MIN))
130            })
131    }
132}
133
134impl PartialOrd for Account {
135    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
136        Some(self.cmp(other))
137    }
138}