tronz_primitives/
amount.rs1use core::fmt;
7use core::ops::{Add, Sub};
8
9use serde::{Deserialize, Serialize};
10
11use crate::error::AmountError;
12
13pub const SUN_PER_TRX: i64 = 1_000_000;
15
16#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
18#[serde(transparent)]
19pub struct Trx(i64);
20
21impl Trx {
22 pub const ZERO: Trx = Trx(0);
24
25 pub const fn from_sun_unchecked(sun: i64) -> Self {
31 Self(sun)
32 }
33
34 pub fn from_sun(sun: i64) -> Result<Self, AmountError> {
36 if sun < 0 {
37 return Err(AmountError::Negative(sun));
38 }
39 Ok(Self(sun))
40 }
41
42 pub fn from_trx(trx: f64) -> Result<Self, AmountError> {
47 if !trx.is_finite() || trx < 0.0 {
48 return Err(AmountError::OutOfRange(trx));
49 }
50 let sun = trx * SUN_PER_TRX as f64;
51 if sun > i64::MAX as f64 {
52 return Err(AmountError::OutOfRange(trx));
53 }
54 Ok(Self(sun.round() as i64))
55 }
56
57 pub const fn as_sun(self) -> i64 {
59 self.0
60 }
61
62 pub fn as_trx(self) -> f64 {
64 self.0 as f64 / SUN_PER_TRX as f64
65 }
66
67 pub fn checked_add(self, rhs: Trx) -> Option<Trx> {
69 self.0.checked_add(rhs.0).map(Trx)
70 }
71
72 pub fn checked_sub(self, rhs: Trx) -> Option<Trx> {
74 self.0.checked_sub(rhs.0).map(Trx)
75 }
76}
77
78impl Add for Trx {
79 type Output = Trx;
80 fn add(self, rhs: Trx) -> Trx {
81 Trx(self.0 + rhs.0)
82 }
83}
84
85impl Sub for Trx {
86 type Output = Trx;
87 fn sub(self, rhs: Trx) -> Trx {
88 Trx(self.0 - rhs.0)
89 }
90}
91
92impl fmt::Display for Trx {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(f, "{} TRX", self.as_trx())
95 }
96}
97
98impl fmt::Debug for Trx {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 write!(f, "Trx({} sun)", self.0)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn conversions() {
110 assert_eq!(Trx::from_trx(1.0).unwrap().as_sun(), 1_000_000);
111 assert_eq!(Trx::from_trx(1.5).unwrap().as_sun(), 1_500_000);
112 assert_eq!(Trx::from_sun(2_500_000).unwrap().as_trx(), 2.5);
113 }
114
115 #[test]
116 fn rejects_negative() {
117 assert!(Trx::from_sun(-1).is_err());
118 assert!(Trx::from_trx(-1.0).is_err());
119 assert!(Trx::from_trx(f64::NAN).is_err());
120 }
121
122 #[test]
123 fn unchecked_allows_negative() {
124 assert_eq!(Trx::from_sun_unchecked(-5).as_sun(), -5);
125 }
126
127 #[test]
128 fn arithmetic() {
129 let a = Trx::from_trx(1.0).unwrap();
130 let b = Trx::from_trx(0.5).unwrap();
131 assert_eq!((a + b).as_sun(), 1_500_000);
132 assert_eq!((a - b).as_sun(), 500_000);
133 assert_eq!(a.checked_add(b), Some(Trx::from_sun(1_500_000).unwrap()));
134 }
135}