stellar_base/
amount.rs

1//! Represent monetary values and prices.
2use crate::error::{Error, Result};
3use crate::xdr;
4use num_rational::Ratio;
5use num_traits::cast::ToPrimitive;
6use rust_decimal::Decimal;
7use std::convert::TryFrom;
8use std::fmt;
9use std::io::{Read, Write};
10use std::str::FromStr;
11
12const STELLAR_SCALE: u32 = 7;
13
14/// Amount in base units. For example, 2 XLM.
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
16pub struct Amount(pub(crate) Decimal);
17
18/// Amount in stroops. This is the smallest amount unit.
19#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
20pub struct Stroops(pub(crate) i64);
21
22/// Price in fractional representation.
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
24pub struct Price(Ratio<i32>);
25
26impl Amount {
27    pub(crate) fn from_decimal(decimal: Decimal) -> Amount {
28        Amount(decimal)
29    }
30
31    /// Creates from amount specified in stroops.
32    pub fn from_stroops(stroops: &Stroops) -> Result<Amount> {
33        let inner = Decimal::new(stroops.0, STELLAR_SCALE);
34        Ok(Amount(inner))
35    }
36
37    /// Checked addition. Computes `self + other`, returning None if overflow occurred.
38    pub fn checked_add(&self, other: &Amount) -> Option<Amount> {
39        self.0.checked_add(other.0).map(Amount)
40    }
41
42    /// Checked subtraction. Computes `self - other`, returning None if overflow occurred.
43    pub fn checked_sub(&self, other: &Amount) -> Option<Amount> {
44        self.0.checked_sub(other.0).map(Amount)
45    }
46
47    /// Checked multiplication. Computes `self * other`, returning None if overflow occurred.
48    pub fn checked_mul(&self, other: &Amount) -> Option<Amount> {
49        self.0.checked_mul(other.0).map(Amount)
50    }
51
52    /// Checked division. Computes `self / other`, returning None if overflow occurred or `other == 0.0`.
53    pub fn checked_div(&self, other: &Amount) -> Option<Amount> {
54        self.0.checked_div(other.0).map(Amount)
55    }
56
57    /// Checked division. Computes `self % other`, returning None if overflow occurred or `other == 0.0`.
58    pub fn checked_rem(&self, other: &Amount) -> Option<Amount> {
59        self.0.checked_rem(other.0).map(Amount)
60    }
61
62    /// Returns the equivalent amount in stroops.
63    pub fn to_stroops(&self) -> Result<Stroops> {
64        let scale = self.0.scale();
65        if scale != STELLAR_SCALE {
66            return Err(Error::InvalidAmountScale);
67        }
68        let res = self.0 * Decimal::new(100_000_000, 1);
69        match res.to_i64() {
70            Some(stroops) => Ok(Stroops::new(stroops)),
71            None => Err(Error::InvalidStroopsAmount),
72        }
73    }
74}
75
76impl FromStr for Amount {
77    type Err = Error;
78
79    fn from_str(s: &str) -> Result<Amount> {
80        let mut inner = Decimal::from_str(s)?;
81        // Check we don't lose precision
82        let scale = inner.scale();
83        if scale > STELLAR_SCALE {
84            Err(Error::InvalidAmountScale)
85        } else {
86            inner.rescale(STELLAR_SCALE);
87            Ok(Amount::from_decimal(inner))
88        }
89    }
90}
91
92impl fmt::Display for Amount {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(f, "{}", self.0)
95    }
96}
97
98impl Stroops {
99    /// Creates with stroops amount.
100    pub fn new(amount: i64) -> Stroops {
101        Stroops(amount)
102    }
103
104    /// Creates with maximum amount of stroops.
105    pub fn max() -> Stroops {
106        Stroops(i64::MAX)
107    }
108
109    /// Returns the stroops amount as `i64`.
110    pub fn to_i64(&self) -> i64 {
111        self.0
112    }
113
114    /// Checked addition. Computes `self + other`, returning None if overflow occurred.
115    pub fn checked_add(&self, other: &Stroops) -> Option<Stroops> {
116        self.0.checked_add(other.0).map(Stroops)
117    }
118
119    /// Checked subtraction. Computes `self - other`, returning None if overflow occurred.
120    pub fn checked_sub(&self, other: &Stroops) -> Option<Stroops> {
121        self.0.checked_sub(other.0).map(Stroops)
122    }
123
124    /// Checked multiplication. Computes `self * other`, returning None if overflow occurred.
125    pub fn checked_mul(&self, other: &Stroops) -> Option<Stroops> {
126        self.0.checked_mul(other.0).map(Stroops)
127    }
128
129    /// Checked division. Computes `self / other`, returning None if overflow occurred or `other == 0.0`.
130    pub fn checked_div(&self, other: &Stroops) -> Option<Stroops> {
131        self.0.checked_div(other.0).map(Stroops)
132    }
133
134    /// Checked division. Computes `self % other`, returning None if overflow occurred or `other == 0.0`.
135    pub fn checked_rem(&self, other: &Stroops) -> Option<Stroops> {
136        self.0.checked_rem(other.0).map(Stroops)
137    }
138
139    /// Returns stroops amount as xdr object.
140    pub fn to_xdr_int64(&self) -> Result<xdr::Int64> {
141        Ok(self.0)
142    }
143
144    /// Returns stroops amount as xdr object.
145    pub fn to_xdr_uint32(&self) -> Result<xdr::Uint32> {
146        if self.0 >= 0 {
147            Ok(self.0 as u32)
148        } else {
149            Err(Error::NegativeStroops)
150        }
151    }
152
153    /// Creates from xdr object.
154    pub fn from_xdr_int64(x: xdr::Int64) -> Result<Stroops> {
155        Ok(Stroops(x))
156    }
157
158    /// Creates from xdr object.
159    pub fn from_xdr_uint32(x: xdr::Uint32) -> Result<Stroops> {
160        Ok(Stroops::new(x as i64))
161    }
162}
163
164impl Price {
165    /// Creates price from numerator and denominator.
166    pub fn new(numerator: i32, denominator: i32) -> Price {
167        let inner = Ratio::new_raw(numerator, denominator);
168        Price(inner)
169    }
170
171    /// Retrieves the price numerator.
172    pub fn numerator(&self) -> i32 {
173        *self.0.numer()
174    }
175
176    /// Retries the price denominator.
177    pub fn denominator(&self) -> i32 {
178        *self.0.denom()
179    }
180
181    /// Returns a reduced copy.
182    pub fn reduced(&self) -> Price {
183        let inner = self.0.reduced();
184        Price(inner)
185    }
186
187    /// Returns price as xdr object.
188    pub fn to_xdr(&self) -> Result<xdr::Price> {
189        Ok(xdr::Price {
190            n: self.numerator(),
191            d: self.denominator(),
192        })
193    }
194
195    /// Creates price from xdr object.
196    pub fn from_xdr(x: &xdr::Price) -> Result<Price> {
197        Ok(Price::new(x.n, x.d))
198    }
199}
200
201impl FromStr for Price {
202    type Err = Error;
203
204    fn from_str(s: &str) -> Result<Price> {
205        if s.is_empty() {
206            return Err(Error::ParsePriceError);
207        }
208        let max_i32 = Decimal::new(i32::MAX as i64, 0);
209        let mut number = Decimal::from_str(s).map_err(|_| Error::ParsePriceError)?;
210        let zero = Decimal::new(0, 0);
211        let one = Decimal::new(1, 0);
212
213        let mut fractions = vec![(zero, one), (one, zero)];
214        let mut i = 2;
215        loop {
216            if number > max_i32 {
217                break;
218            }
219
220            let whole = number.floor();
221            let fract = number - whole;
222            let h = whole * fractions[i - 1].0 + fractions[i - 2].0;
223            let k = whole * fractions[i - 1].1 + fractions[i - 2].1;
224            if (k >= max_i32) || (h >= max_i32) {
225                break;
226            }
227            fractions.push((h, k));
228            if fract == zero {
229                break;
230            }
231            number = one / fract;
232            i += 1;
233        }
234        match fractions.last() {
235            None => Err(Error::ParsePriceError),
236            Some((num, den)) => {
237                let num = num.to_i32();
238                let den = den.to_i32();
239                match (num, den) {
240                    (Some(0), _) => Err(Error::ParsePriceError),
241                    (_, Some(0)) => Err(Error::ParsePriceError),
242                    (Some(num), Some(den)) => Ok(Price::new(num, den)),
243                    _ => Err(Error::ParsePriceError),
244                }
245            }
246        }
247    }
248}
249
250impl TryFrom<Amount> for Stroops {
251    type Error = Error;
252
253    fn try_from(amount: Amount) -> std::result::Result<Self, Self::Error> {
254        amount.to_stroops()
255    }
256}
257
258impl TryFrom<Stroops> for Amount {
259    type Error = Error;
260
261    fn try_from(stroops: Stroops) -> std::result::Result<Self, Self::Error> {
262        Amount::from_stroops(&stroops)
263    }
264}
265
266impl xdr::WriteXdr for Price {
267    fn write_xdr<W: Write>(&self, w: &mut xdr::Limited<W>) -> xdr::Result<()> {
268        let xdr_price = self.to_xdr().map_err(|_| xdr::Error::Invalid)?;
269        xdr_price.write_xdr(w)
270    }
271}
272
273impl xdr::ReadXdr for Price {
274    fn read_xdr<R: Read>(r: &mut xdr::Limited<R>) -> xdr::Result<Self> {
275        let xdr_result = xdr::Price::read_xdr(r)?;
276        Self::from_xdr(&xdr_result).map_err(|_| xdr::Error::Invalid)
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::{Amount, Price, Stroops};
283    use crate::xdr::{XDRDeserialize, XDRSerialize};
284    use std::str;
285    use std::str::FromStr;
286
287    #[test]
288    fn test_amount_from_str() {
289        let amount1 = str::parse::<Amount>("123.4567891").unwrap();
290        let amount2 = str::parse::<Amount>("123.4567891").unwrap();
291        let amount3 = str::parse::<Amount>("123.4567890").unwrap();
292
293        assert_eq!(amount1, amount2);
294        assert_ne!(amount1, amount3);
295        assert!(amount3 < amount1);
296    }
297
298    #[test]
299    fn test_error_too_many_decimals() {
300        let res = str::parse::<Amount>("123.45678901");
301        assert!(res.is_err());
302    }
303
304    #[test]
305    fn test_amount_to_stroops() {
306        let amount = str::parse::<Amount>("123.45678").unwrap();
307        let stroops = amount.to_stroops().unwrap();
308        assert_eq!(stroops, Stroops::new(1234567800));
309    }
310
311    #[test]
312    fn test_price_from_str() {
313        let one_22 = "1".repeat(22);
314        let one_big = "1".repeat(1000000);
315        // let zero_one = "0.".to_string() + &"1".repeat(1000);
316        let test_cases = vec![
317            ("0.1", Some((1, 10))),
318            ("0.01", Some((1, 100))),
319            ("0.001", Some((1, 1000))),
320            ("543.017930", Some((54301793, 100000))),
321            ("319.69983", Some((31969983, 100000))),
322            ("0.93", Some((93, 100))),
323            ("0.5", Some((1, 2))),
324            ("1.730", Some((173, 100))),
325            ("0.85334384", Some((5333399, 6250000))),
326            ("5.5", Some((11, 2))),
327            ("2.72783", Some((272783, 100000))),
328            ("638082.0", Some((638082, 1))),
329            ("58.04", Some((1451, 25))),
330            ("41.265", Some((8253, 200))),
331            ("5.1476", Some((12869, 2500))),
332            ("95.14", Some((4757, 50))),
333            ("0.74580", Some((3729, 5000))),
334            ("4119.0", Some((4119, 1))),
335            // Expensive imputs
336            (&one_22, None),
337            (&one_big, None),
338            // (&zero_one, None),
339            ("1E9223372036854775807", None),
340            ("1e9223372036854775807", None),
341        ];
342
343        for (test_str, expected_res) in test_cases {
344            let res = Price::from_str(test_str);
345            match expected_res {
346                None => {
347                    assert!(res.is_err());
348                }
349                Some((num, den)) => {
350                    let price = res.unwrap();
351                    assert_eq!(num, price.numerator());
352                    assert_eq!(den, price.denominator());
353                }
354            }
355        }
356    }
357
358    #[test]
359    fn test_price_xdr_ser() {
360        let price = Price::new(123, 456);
361        let xdr = price.xdr_base64().unwrap();
362        assert_eq!("AAAAewAAAcg=", xdr);
363    }
364
365    #[test]
366    fn test_price_xdr_de() {
367        let expected = Price::new(123, 456);
368        let price = Price::from_xdr_base64("AAAAewAAAcg=").unwrap();
369        assert_eq!(expected, price);
370    }
371}