Skip to main content

tronz_primitives/
amount.rs

1//! TRX amount type.
2//!
3//! TRON denominates value in *sun*, where `1 TRX = 1_000_000 sun`. [`Trx`]
4//! wraps an `i64` sun value to match the protobuf `sint64` representation.
5
6use core::{
7    fmt,
8    ops::{Add, Sub},
9};
10
11use serde::{Deserialize, Serialize};
12
13use crate::error::AmountError;
14
15/// Number of sun in one TRX.
16pub const SUN_PER_TRX: i64 = 1_000_000;
17
18/// An amount of TRX, stored internally as `i64` sun.
19#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
20#[serde(transparent)]
21pub struct Trx(i64);
22
23impl Trx {
24    /// Zero TRX.
25    pub const ZERO: Trx = Trx(0);
26
27    /// Construct directly from a sun value without validation.
28    ///
29    /// Negative values are representable here because malformed on-chain data
30    /// must round-trip without panicking; prefer [`Trx::from_sun`] for
31    /// user-facing input.
32    pub const fn from_sun_unchecked(sun: i64) -> Self {
33        Self(sun)
34    }
35
36    /// Construct from a sun value, rejecting negatives.
37    pub fn from_sun(sun: i64) -> Result<Self, AmountError> {
38        if sun < 0 {
39            return Err(AmountError::Negative(sun));
40        }
41        Ok(Self(sun))
42    }
43
44    /// Construct from a floating-point TRX value (e.g. `1.5` TRX).
45    ///
46    /// Rejects negative and non-finite values, and values that overflow the
47    /// `i64` sun range.
48    pub fn from_trx(trx: f64) -> Result<Self, AmountError> {
49        if !trx.is_finite() || trx < 0.0 {
50            return Err(AmountError::OutOfRange(trx));
51        }
52        let sun = trx * SUN_PER_TRX as f64;
53        if sun > i64::MAX as f64 {
54            return Err(AmountError::OutOfRange(trx));
55        }
56        Ok(Self(sun.round() as i64))
57    }
58
59    /// The raw sun value.
60    pub const fn as_sun(self) -> i64 {
61        self.0
62    }
63
64    /// The value expressed as floating-point TRX (lossy for large amounts).
65    pub fn as_trx(self) -> f64 {
66        self.0 as f64 / SUN_PER_TRX as f64
67    }
68
69    /// Checked addition, returning `None` on `i64` overflow.
70    pub fn checked_add(self, rhs: Trx) -> Option<Trx> {
71        self.0.checked_add(rhs.0).map(Trx)
72    }
73
74    /// Checked subtraction, returning `None` on `i64` overflow.
75    pub fn checked_sub(self, rhs: Trx) -> Option<Trx> {
76        self.0.checked_sub(rhs.0).map(Trx)
77    }
78}
79
80impl Add for Trx {
81    type Output = Trx;
82    fn add(self, rhs: Trx) -> Trx {
83        Trx(self.0.saturating_add(rhs.0))
84    }
85}
86
87impl Sub for Trx {
88    type Output = Trx;
89    fn sub(self, rhs: Trx) -> Trx {
90        Trx(self.0.saturating_sub(rhs.0))
91    }
92}
93
94impl fmt::Display for Trx {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(f, "{} TRX", self.as_trx())
97    }
98}
99
100impl fmt::Debug for Trx {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        write!(f, "Trx({} sun)", self.0)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn conversions() {
112        assert_eq!(Trx::from_trx(1.0).unwrap().as_sun(), 1_000_000);
113        assert_eq!(Trx::from_trx(1.5).unwrap().as_sun(), 1_500_000);
114        assert_eq!(Trx::from_sun(2_500_000).unwrap().as_trx(), 2.5);
115    }
116
117    #[test]
118    fn rejects_negative() {
119        assert!(Trx::from_sun(-1).is_err());
120        assert!(Trx::from_trx(-1.0).is_err());
121        assert!(Trx::from_trx(f64::NAN).is_err());
122    }
123
124    #[test]
125    fn unchecked_allows_negative() {
126        assert_eq!(Trx::from_sun_unchecked(-5).as_sun(), -5);
127    }
128
129    #[test]
130    fn arithmetic() {
131        let a = Trx::from_trx(1.0).unwrap();
132        let b = Trx::from_trx(0.5).unwrap();
133        assert_eq!((a + b).as_sun(), 1_500_000);
134        assert_eq!((a - b).as_sun(), 500_000);
135        assert_eq!(a.checked_add(b), Some(Trx::from_sun(1_500_000).unwrap()));
136    }
137}