shuttle_core/
amount.rs

1use std::result;
2use std::str::FromStr;
3use std::ops::Mul;
4use num_bigint::BigInt;
5use bigdecimal::BigDecimal;
6use num_traits::cast::{FromPrimitive, ToPrimitive};
7use error::{Error, Result};
8
9const STELLAR_SCALE: i64 = 7;
10
11/// Amount in XLM.
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
13pub struct Amount {
14    inner: BigDecimal,
15}
16
17impl Amount {
18    /// Create from amount specified in stroops.
19    pub fn from_stroops(stroops: Stroops) -> Result<Amount> {
20        let data = BigInt::from_i64(stroops.0).ok_or(Error::InvalidStroopsAmount)?;
21        let inner = BigDecimal::new(data, STELLAR_SCALE);
22        Ok(Amount { inner })
23    }
24
25    /// Convert to stroops.
26    pub fn as_stroops(&self) -> Result<Stroops> {
27        self.clone().into_stroops()
28    }
29
30    /// Convert into stroops.
31    pub fn into_stroops(self) -> Result<Stroops> {
32        let (data, exp) = self.inner.into_bigint_and_exponent();
33        if exp != STELLAR_SCALE {
34            return Err(Error::InvalidAmountScale);
35        }
36        match data.to_i64() {
37            Some(stroops) => Ok(Stroops::new(stroops)),
38            None => Err(Error::InvalidStroopsAmount),
39        }
40    }
41}
42
43impl FromStr for Amount {
44    type Err = Error;
45
46    fn from_str(s: &str) -> result::Result<Amount, Error> {
47        let inner = BigDecimal::from_str(&s)?;
48        // Check we don't lose precision
49        let (_, scale) = inner.as_bigint_and_exponent();
50        if scale > STELLAR_SCALE {
51            Err(Error::InvalidAmountScale)
52        } else {
53            let scaled_inner = inner.with_scale(STELLAR_SCALE);
54            Ok(Amount {
55                inner: scaled_inner,
56            })
57        }
58    }
59}
60
61/// Amount in stroops.
62#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
63pub struct Stroops(pub i64);
64
65impl Stroops {
66    /// Create from stroops.
67    pub fn new(amount: i64) -> Stroops {
68        Stroops(amount)
69    }
70}
71
72impl Mul<usize> for Stroops {
73    type Output = Self;
74
75    fn mul(self, rhs: usize) -> Self {
76        Stroops(self.0 * rhs as i64)
77    }
78}
79
80/// Price in fractional representation.
81#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
82pub struct Price {
83    numerator: i32,
84    denominator: i32,
85}
86
87impl Price {
88    /// Create from numerator and denominator.
89    pub fn new(numerator: i32, denominator: i32) -> Price {
90        Price {
91            numerator,
92            denominator,
93        }
94    }
95
96    /// Return the price numerator.
97    pub fn numerator(&self) -> i32 {
98        self.numerator
99    }
100
101    /// Return the price denominator.
102    pub fn denominator(&self) -> i32 {
103        self.denominator
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use std::str;
110    use super::{Amount, Stroops};
111
112    #[test]
113    fn test_amount_from_str() {
114        let amount1 = str::parse::<Amount>("123.4567891").unwrap();
115        let amount2 = str::parse::<Amount>("123.4567891").unwrap();
116        let amount3 = str::parse::<Amount>("123.4567890").unwrap();
117
118        assert_eq!(amount1, amount2);
119        assert_ne!(amount1, amount3);
120        assert!(amount3 < amount1);
121    }
122
123    #[test]
124    fn test_error_too_many_decimals() {
125        let res = str::parse::<Amount>("123.45678901");
126        assert!(res.is_err());
127    }
128
129    #[test]
130    fn test_amount_as_stroops() {
131        let amount = str::parse::<Amount>("123.45678").unwrap();
132        let stroops = amount.as_stroops().unwrap();
133        assert_eq!(stroops, Stroops::new(1234567800));
134    }
135}