canic_cdk/types/
cycles.rs

1use crate::{
2    candid::{CandidType, Nat},
3    structures::{Storable, storable::Bound},
4};
5use derive_more::{Add, AddAssign, Sub, SubAssign};
6use num_traits::cast::ToPrimitive;
7use serde::{Deserialize, Serialize, de::Deserializer};
8use std::{
9    borrow::Cow,
10    fmt::{self, Display},
11    str::FromStr,
12};
13
14///
15/// Constants
16/// Cycle unit shorthands for configs and logs
17///
18
19pub const KC: u128 = 1_000;
20pub const MC: u128 = 1_000_000;
21pub const BC: u128 = 1_000_000_000;
22pub const TC: u128 = 1_000_000_000_000;
23pub const QC: u128 = 1_000_000_000_000_000;
24
25///
26/// Cycles
27/// Thin wrapper around `Nat` that carries helper traits and serializers for
28/// arithmetic on cycle balances.
29///
30
31#[derive(
32    Add,
33    AddAssign,
34    CandidType,
35    Clone,
36    Default,
37    Debug,
38    Deserialize,
39    PartialEq,
40    Eq,
41    Hash,
42    PartialOrd,
43    Ord,
44    Serialize,
45    SubAssign,
46    Sub,
47)]
48pub struct Cycles(u128);
49
50impl Cycles {
51    #[must_use]
52    pub const fn new(n: u128) -> Self {
53        Self(n)
54    }
55
56    #[must_use]
57    pub fn to_u64(&self) -> u64 {
58        self.0.to_u64().unwrap_or(u64::MAX)
59    }
60
61    #[must_use]
62    pub const fn to_u128(&self) -> u128 {
63        self.0
64    }
65
66    // from_config
67    // accepts the short hand 10T format or a number
68    pub fn from_config<'de, D>(deserializer: D) -> Result<Self, D::Error>
69    where
70        D: Deserializer<'de>,
71    {
72        #[derive(Deserialize)]
73        #[serde(untagged)]
74        enum Helper {
75            Str(String),
76            Num(u128),
77        }
78
79        match Helper::deserialize(deserializer)? {
80            Helper::Str(s) => s.parse::<Self>().map_err(serde::de::Error::custom),
81            Helper::Num(n) => Ok(Self::new(n)),
82        }
83    }
84}
85
86#[allow(clippy::cast_precision_loss)]
87impl Display for Cycles {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        // default format in TeraCycles
90        write!(f, "{:.3} TC", self.to_u128() as f64 / 1_000_000_000_000f64)
91    }
92}
93
94impl From<Nat> for Cycles {
95    fn from(n: Nat) -> Self {
96        Self(n.0.to_u128().unwrap_or(0))
97    }
98}
99
100impl From<u128> for Cycles {
101    fn from(v: u128) -> Self {
102        Self(v)
103    }
104}
105
106impl From<Cycles> for u128 {
107    fn from(c: Cycles) -> Self {
108        c.0
109    }
110}
111
112// Human-input parser: "10K", "1.5T", etc.
113#[allow(clippy::cast_precision_loss)]
114#[allow(clippy::cast_sign_loss)]
115#[allow(clippy::cast_possible_truncation)]
116impl 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 suf_count = 0;
122        for c in s.chars() {
123            if c.is_ascii_digit() || c == '.' {
124                if suf_count > 0 {
125                    return Err("invalid suffix".to_string());
126                }
127                num.push(c);
128            } else if suf_count >= 2 {
129                return Err("invalid suffix".to_string());
130            } else {
131                suf.push(c);
132                suf_count += 1;
133            }
134        }
135
136        let mut n = num.parse::<f64>().map_err(|e| e.to_string())?;
137        match suf.as_str() {
138            "" => {}
139            "K" => n *= KC as f64,
140            "M" => n *= MC as f64,
141            "B" => n *= BC as f64,
142            "T" => n *= TC as f64,
143            "Q" => n *= QC as f64,
144            _ => return Err("invalid suffix".to_string()),
145        }
146
147        Ok(Self::new(n as u128))
148    }
149}
150
151impl Storable for Cycles {
152    // u128 is exactly 16 bytes, fixed-size
153    const BOUND: Bound = Bound::Bounded {
154        max_size: 16,
155        is_fixed_size: true,
156    };
157
158    fn to_bytes(&self) -> Cow<'_, [u8]> {
159        Cow::Owned(self.0.to_be_bytes().to_vec())
160    }
161
162    fn into_bytes(self) -> Vec<u8> {
163        self.0.to_be_bytes().to_vec()
164    }
165
166    fn from_bytes(bytes: Cow<[u8]>) -> Self {
167        let b = bytes.as_ref();
168
169        // Defensive decode: never panic on corrupted data
170        if b.len() != 16 {
171            return Self::default();
172        }
173
174        let mut arr = [0u8; 16];
175        arr.copy_from_slice(b);
176
177        Self(u128::from_be_bytes(arr))
178    }
179}