Skip to main content

bity_ic_ledger_utils/
lib.rs

1//! Module for handling Internet Computer ledger operations and account management.
2//!
3//! This module provides utilities for working with the Internet Computer's ledger system,
4//! including account identifier computation, subaccount management, and conversion between
5//! different account formats.
6//!
7//! # Example
8//! ```
9//! use candid::Principal;
10//! use ic_ledger_types::AccountIdentifier;
11//!
12//! let principal = Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap();
13//! let account_id = principal_to_legacy_account_id(principal, None);
14//! ```
15
16use candid::Principal;
17use ic_ledger_types::{AccountIdentifier, Subaccount, DEFAULT_SUBACCOUNT};
18use icrc_ledger_types::icrc1::account::Account;
19use sha2::{Digest, Sha256};
20
21/// Computes a neuron staking subaccount using SHA-256 hashing.
22///
23/// This function generates a deterministic subaccount for neuron staking by hashing
24/// the controller's principal and a nonce value.
25///
26/// # Arguments
27/// * `controller` - The principal ID of the controller
28/// * `nonce` - A unique value to ensure different subaccounts for the same controller
29///
30/// # Returns
31/// A 32-byte array representing the computed subaccount
32pub fn compute_neuron_staking_subaccount_bytes(controller: Principal, nonce: u64) -> [u8; 32] {
33    const DOMAIN: &[u8] = b"neuron-stake";
34    const DOMAIN_LENGTH: [u8; 1] = [0x0c];
35
36    let mut hasher = Sha256::new();
37    hasher.update(DOMAIN_LENGTH);
38    hasher.update(DOMAIN);
39    hasher.update(controller.as_slice());
40    hasher.update(nonce.to_be_bytes());
41    hasher.finalize().into()
42}
43
44/// Converts an ICRC-1 account to a legacy account identifier.
45///
46/// This function converts an ICRC-1 account format to the legacy account identifier format,
47/// handling the conversion of subaccounts appropriately.
48///
49/// # Arguments
50/// * `icrc_account` - The ICRC-1 account to convert
51///
52/// # Returns
53/// The corresponding legacy account identifier
54pub fn icrc_account_to_legacy_account_id(icrc_account: Account) -> AccountIdentifier {
55    let subaccount: Subaccount = icrc_account
56        .subaccount
57        .map_or(DEFAULT_SUBACCOUNT, |s| Subaccount(s));
58    AccountIdentifier::new(&icrc_account.owner, &subaccount)
59}
60
61/// Creates a legacy account identifier from a principal and optional subaccount.
62///
63/// This function creates a legacy account identifier using the provided principal
64/// and an optional subaccount. If no subaccount is provided, the default subaccount is used.
65///
66/// # Arguments
67/// * `principal` - The principal ID to use
68/// * `subaccount` - An optional subaccount to use
69///
70/// # Returns
71/// The corresponding legacy account identifier
72pub fn principal_to_legacy_account_id(
73    principal: Principal,
74    subaccount: Option<Subaccount>,
75) -> AccountIdentifier {
76    AccountIdentifier::new(&principal, &subaccount.unwrap_or(DEFAULT_SUBACCOUNT))
77}
78
79#[cfg(test)]
80mod tests {
81    use candid::Principal;
82    use icrc_ledger_types::icrc1::account::Account;
83
84    use crate::icrc_account_to_legacy_account_id;
85
86    #[test]
87    fn convert_icrc_account_to_legacy_account_id() {
88        let icrc_account = Account {
89            owner: Principal::from_text(
90                "465sx-szz6o-idcax-nrjhv-hprrp-qqx5e-7mqwr-wadib-uo7ap-lofbe-dae",
91            )
92            .unwrap(),
93            subaccount: None,
94        };
95        let result = icrc_account_to_legacy_account_id(icrc_account);
96
97        let expected_result =
98            "aacba041bbce2b03c66307a68ca2d5a704a1f87397694a1292d89ce757136f11".to_string();
99
100        assert_eq!(result.to_hex(), expected_result)
101    }
102
103    #[test]
104    fn convert_icrc_account_to_legacy_account_id_with_subaccount() {
105        let icrc_account = Account {
106            owner: Principal::from_text(
107                "465sx-szz6o-idcax-nrjhv-hprrp-qqx5e-7mqwr-wadib-uo7ap-lofbe-dae",
108            )
109            .unwrap(),
110            subaccount: Some([
111                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
112                0, 0, 0, 1,
113            ]),
114        };
115        let result = icrc_account_to_legacy_account_id(icrc_account);
116
117        let expected_result =
118            "2fab56f6af866bd4580c8bdf821849d470d5d0af6a671191c602e0c434b5e55c".to_string();
119
120        assert_eq!(result.to_hex(), expected_result)
121    }
122}