tronz_primitives/
amount.rs1use core::{
7 fmt,
8 ops::{Add, Sub},
9};
10
11use serde::{Deserialize, Serialize};
12
13use crate::error::AmountError;
14
15pub const SUN_PER_TRX: i64 = 1_000_000;
17
18#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
20#[serde(transparent)]
21pub struct Trx(i64);
22
23impl Trx {
24 pub const ZERO: Trx = Trx(0);
26
27 pub const fn from_sun_unchecked(sun: i64) -> Self {
33 Self(sun)
34 }
35
36 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 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 pub const fn as_sun(self) -> i64 {
61 self.0
62 }
63
64 pub fn as_trx(self) -> f64 {
66 self.0 as f64 / SUN_PER_TRX as f64
67 }
68
69 pub fn checked_add(self, rhs: Trx) -> Option<Trx> {
71 self.0.checked_add(rhs.0).map(Trx)
72 }
73
74 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}