1use alloc::string::{String, ToString};
5use alloc::vec::Vec;
6use core::num::ParseIntError;
7
8use miden_lib::AuthScheme;
9use miden_lib::account::faucets::BasicFungibleFaucet;
10use miden_lib::account::interface::AccountInterface;
11use miden_objects::Word;
12use miden_objects::account::Account;
13pub use miden_tx::utils::sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard};
14pub use miden_tx::utils::{
15 ByteReader,
16 ByteWriter,
17 Deserializable,
18 DeserializationError,
19 Serializable,
20 ToHex,
21 bytes_to_hex_string,
22 hex_to_bytes,
23};
24
25use crate::alloc::borrow::ToOwned;
26
27pub fn base_units_to_tokens(units: u64, decimals: u8) -> String {
31 let units_str = units.to_string();
32 let len = units_str.len();
33
34 if decimals == 0 {
35 return units_str;
36 }
37
38 if decimals as usize >= len {
39 "0.".to_owned() + &"0".repeat(decimals as usize - len) + &units_str
41 } else {
42 let integer_part = &units_str[..len - decimals as usize];
44 let fractional_part = &units_str[len - decimals as usize..];
45 format!("{integer_part}.{fractional_part}")
46 }
47}
48
49#[derive(thiserror::Error, Debug)]
52pub enum TokenParseError {
53 #[error("Number of decimals {0} must be less than or equal to {max_decimals}", max_decimals = BasicFungibleFaucet::MAX_DECIMALS)]
54 MaxDecimals(u8),
55 #[error("More than one decimal point")]
56 MultipleDecimalPoints,
57 #[error("Failed to parse u64")]
58 ParseU64(#[source] ParseIntError),
59 #[error("Amount has more than {0} decimal places")]
60 TooManyDecimals(u8),
61}
62
63pub fn tokens_to_base_units(decimal_str: &str, n_decimals: u8) -> Result<u64, TokenParseError> {
66 if n_decimals > BasicFungibleFaucet::MAX_DECIMALS {
67 return Err(TokenParseError::MaxDecimals(n_decimals));
68 }
69
70 let parts: Vec<&str> = decimal_str.split('.').collect();
72
73 if parts.len() > 2 {
74 return Err(TokenParseError::MultipleDecimalPoints);
75 }
76
77 for part in &parts {
79 part.parse::<u64>().map_err(TokenParseError::ParseU64)?;
80 }
81
82 let integer_part = parts[0];
84
85 let mut fractional_part = if parts.len() > 1 {
87 parts[1].trim_end_matches('0').to_string()
88 } else {
89 String::new()
90 };
91
92 if fractional_part.len() > n_decimals.into() {
94 return Err(TokenParseError::TooManyDecimals(n_decimals));
95 }
96
97 while fractional_part.len() < n_decimals.into() {
99 fractional_part.push('0');
100 }
101
102 let combined = format!("{}{}", integer_part, &fractional_part[0..n_decimals.into()]);
104
105 combined.parse::<u64>().map_err(TokenParseError::ParseU64)
107}
108
109pub fn get_public_keys_from_account(account: &Account) -> Vec<Word> {
115 let mut words = vec![];
116 let interface: AccountInterface = account.into();
117
118 for auth in interface.auth() {
119 match auth {
120 AuthScheme::NoAuth | AuthScheme::Unknown => {},
121 AuthScheme::RpoFalcon512 { pub_key } => words.push(Word::from(*pub_key)),
122 AuthScheme::RpoFalcon512Multisig { pub_keys, .. } => {
123 words.extend(pub_keys.iter().map(|k| Word::from(*k)));
124 },
125 }
126 }
127
128 words
129}
130
131#[cfg(test)]
135mod tests {
136 use crate::utils::{TokenParseError, base_units_to_tokens, tokens_to_base_units};
137
138 #[test]
139 fn convert_tokens_to_base_units() {
140 assert_eq!(tokens_to_base_units("18446744.073709551615", 12).unwrap(), u64::MAX);
141 assert_eq!(tokens_to_base_units("7531.2468", 8).unwrap(), 753_124_680_000);
142 assert_eq!(tokens_to_base_units("7531.2468", 4).unwrap(), 75_312_468);
143 assert_eq!(tokens_to_base_units("0", 3).unwrap(), 0);
144 assert_eq!(tokens_to_base_units("0", 3).unwrap(), 0);
145 assert_eq!(tokens_to_base_units("0", 3).unwrap(), 0);
146 assert_eq!(tokens_to_base_units("1234", 8).unwrap(), 123_400_000_000);
147 assert_eq!(tokens_to_base_units("1", 0).unwrap(), 1);
148 assert!(matches!(
149 tokens_to_base_units("1.1", 0),
150 Err(TokenParseError::TooManyDecimals(0))
151 ),);
152 assert!(matches!(
153 tokens_to_base_units("18446744.073709551615", 11),
154 Err(TokenParseError::TooManyDecimals(11))
155 ),);
156 assert!(matches!(tokens_to_base_units("123u3.23", 4), Err(TokenParseError::ParseU64(_))),);
157 assert!(matches!(tokens_to_base_units("2.k3", 4), Err(TokenParseError::ParseU64(_))),);
158 assert_eq!(tokens_to_base_units("12.345000", 4).unwrap(), 123_450);
159 assert!(tokens_to_base_units("0.0001.00000001", 12).is_err());
160 }
161
162 #[test]
163 fn convert_base_units_to_tokens() {
164 assert_eq!(base_units_to_tokens(u64::MAX, 12), "18446744.073709551615");
165 assert_eq!(base_units_to_tokens(753_124_680_000, 8), "7531.24680000");
166 assert_eq!(base_units_to_tokens(75_312_468, 4), "7531.2468");
167 assert_eq!(base_units_to_tokens(75_312_468, 0), "75312468");
168 }
169}