canic_cdk/types/
cycles.rs1use crate::{
2 candid::{CandidType, Nat},
3 structures::{Storable, storable::Bound},
4};
5use serde::{Deserialize, Serialize, de::Deserializer};
6use std::{
7 borrow::Cow,
8 fmt::{self, Display},
9 ops::{Add, AddAssign, Sub, SubAssign},
10 str::FromStr,
11};
12
13pub const KC: u128 = 1_000;
19pub const MC: u128 = 1_000_000;
20pub const BC: u128 = 1_000_000_000;
21pub const TC: u128 = 1_000_000_000_000;
22pub const QC: u128 = 1_000_000_000_000_000;
23
24#[derive(
31 CandidType, Clone, Default, Debug, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize,
32)]
33pub struct Cycles(u128);
34
35impl Cycles {
36 #[must_use]
37 pub const fn new(n: u128) -> Self {
38 Self(n)
39 }
40
41 #[must_use]
42 pub fn to_u64(&self) -> u64 {
43 u64::try_from(self.0).unwrap_or(u64::MAX)
44 }
45
46 #[must_use]
47 pub const fn to_u128(&self) -> u128 {
48 self.0
49 }
50
51 pub fn from_config<'de, D>(deserializer: D) -> Result<Self, D::Error>
54 where
55 D: Deserializer<'de>,
56 {
57 #[derive(Deserialize)]
58 #[serde(untagged)]
59 enum Helper {
60 Str(String),
61 Num(u128),
62 }
63
64 match Helper::deserialize(deserializer)? {
65 Helper::Str(s) => s.parse::<Self>().map_err(serde::de::Error::custom),
66 Helper::Num(n) => Ok(Self::new(n)),
67 }
68 }
69}
70
71#[expect(clippy::cast_precision_loss)]
72impl Display for Cycles {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 write!(f, "{:.3} TC", self.to_u128() as f64 / 1_000_000_000_000f64)
76 }
77}
78
79impl From<Nat> for Cycles {
80 fn from(n: Nat) -> Self {
81 Self(u128::try_from(n.0).unwrap_or(0))
82 }
83}
84
85impl From<u128> for Cycles {
86 fn from(v: u128) -> Self {
87 Self(v)
88 }
89}
90
91impl From<Cycles> for u128 {
92 fn from(c: Cycles) -> Self {
93 c.0
94 }
95}
96
97impl Add for Cycles {
98 type Output = Self;
99
100 fn add(self, rhs: Self) -> Self::Output {
102 Self(self.0 + rhs.0)
103 }
104}
105
106impl AddAssign for Cycles {
107 fn add_assign(&mut self, rhs: Self) {
109 self.0 += rhs.0;
110 }
111}
112
113impl Sub for Cycles {
114 type Output = Self;
115
116 fn sub(self, rhs: Self) -> Self::Output {
118 Self(self.0 - rhs.0)
119 }
120}
121
122impl SubAssign for Cycles {
123 fn sub_assign(&mut self, rhs: Self) {
125 self.0 -= rhs.0;
126 }
127}
128
129#[expect(clippy::cast_precision_loss)]
131#[expect(clippy::cast_sign_loss)]
132#[expect(clippy::cast_possible_truncation)]
133impl FromStr for Cycles {
134 type Err = String;
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
136 let mut num = String::new();
137 let mut suf = String::new();
138 let mut suf_count = 0;
139 for c in s.chars() {
140 if c.is_ascii_digit() || c == '.' {
141 if suf_count > 0 {
142 return Err("invalid suffix".to_string());
143 }
144 num.push(c);
145 } else if suf_count >= 2 {
146 return Err("invalid suffix".to_string());
147 } else {
148 suf.push(c);
149 suf_count += 1;
150 }
151 }
152
153 let mut n = num.parse::<f64>().map_err(|e| e.to_string())?;
154 match suf.as_str() {
155 "" => {}
156 "K" => n *= KC as f64,
157 "M" => n *= MC as f64,
158 "B" => n *= BC as f64,
159 "T" => n *= TC as f64,
160 "Q" => n *= QC as f64,
161 _ => return Err("invalid suffix".to_string()),
162 }
163
164 Ok(Self::new(n as u128))
165 }
166}
167
168impl Storable for Cycles {
169 const BOUND: Bound = Bound::Bounded {
171 max_size: 16,
172 is_fixed_size: true,
173 };
174
175 fn to_bytes(&self) -> Cow<'_, [u8]> {
176 Cow::Owned(self.0.to_be_bytes().to_vec())
177 }
178
179 fn into_bytes(self) -> Vec<u8> {
180 self.0.to_be_bytes().to_vec()
181 }
182
183 fn from_bytes(bytes: Cow<[u8]>) -> Self {
184 let b = bytes.as_ref();
185
186 if b.len() != 16 {
188 return Self::default();
189 }
190
191 let mut arr = [0u8; 16];
192 arr.copy_from_slice(b);
193
194 Self(u128::from_be_bytes(arr))
195 }
196}