1use candid::{CandidType, Nat};
9use derive_more::{Add, AddAssign, Sub, SubAssign};
10use num_traits::cast::ToPrimitive;
11use serde::{Deserialize, Serialize, de::Deserializer};
12use std::{
13 fmt::{self, Display},
14 str::FromStr,
15};
16
17pub const KC: u128 = 1_000;
23pub const MC: u128 = 1_000_000;
24pub const BC: u128 = 1_000_000_000;
25pub const TC: u128 = 1_000_000_000_000;
26pub const QC: u128 = 1_000_000_000_000_000;
27
28#[derive(
35 Add,
36 AddAssign,
37 CandidType,
38 Clone,
39 Default,
40 Debug,
41 Deserialize,
42 PartialEq,
43 Eq,
44 Hash,
45 PartialOrd,
46 Ord,
47 Serialize,
48 SubAssign,
49 Sub,
50)]
51pub struct Cycles(Nat);
52
53impl Cycles {
54 #[must_use]
55 pub fn new(amount: u128) -> Self {
56 Self(amount.into())
57 }
58
59 #[must_use]
60 pub fn to_u64(&self) -> u64 {
61 self.0.0.to_u64().unwrap_or(u64::MAX)
62 }
63
64 #[must_use]
65 pub fn to_u128(&self) -> u128 {
66 self.0.0.to_u128().unwrap_or(u128::MAX)
67 }
68
69 pub fn from_config<'de, D>(deserializer: D) -> Result<Self, D::Error>
72 where
73 D: Deserializer<'de>,
74 {
75 #[derive(Deserialize)]
76 #[serde(untagged)]
77 enum Helper {
78 Str(String),
79 Num(u128),
80 }
81
82 match Helper::deserialize(deserializer)? {
83 Helper::Str(s) => s.parse::<Self>().map_err(serde::de::Error::custom),
84 Helper::Num(n) => Ok(Self::new(n)),
85 }
86 }
87}
88
89#[allow(clippy::cast_precision_loss)]
90impl Display for Cycles {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "{:.3} TC", self.to_u128() as f64 / 1_000_000_000_000f64)
94 }
95}
96
97impl From<u128> for Cycles {
98 fn from(v: u128) -> Self {
99 Self(Nat::from(v))
100 }
101}
102
103impl From<Nat> for Cycles {
104 fn from(n: Nat) -> Self {
105 Self(n)
106 }
107}
108
109impl From<Cycles> for Nat {
110 fn from(c: Cycles) -> Self {
111 c.0
112 }
113}
114
115impl FromStr for Cycles {
117 type Err = String;
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 let mut num = String::new();
120 let mut suf = String::new();
121 let mut seen_dot = false;
122
123 for ch in s.chars() {
124 if ch.is_ascii_digit() || (ch == '.' && !seen_dot) {
125 if ch == '.' {
126 seen_dot = true;
127 }
128 num.push(ch);
129 } else {
130 suf.push(ch);
131 }
132 }
133
134 let n: f64 = num
135 .parse::<f64>()
136 .map_err(|e| format!("Invalid number '{num}': {e}"))?;
137
138 let mul = match suf.as_str() {
139 "K" => 1_000_f64,
140 "M" => 1_000_000_f64,
141 "B" => 1_000_000_000_f64,
142 "T" => 1_000_000_000_000_f64,
143 "Q" => 1_000_000_000_000_000_f64,
144 "" => 1.0,
145 _ => return Err(format!("Unknown suffix '{suf}'")),
146 };
147
148 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
149 Ok(Self::new((n * mul) as u128))
150 }
151}