1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::result;
use std::str::FromStr;
use std::ops::Mul;
use num_bigint::BigInt;
use bigdecimal::BigDecimal;
use num_traits::cast::{FromPrimitive, ToPrimitive};
use error::{Error, Result};

const STELLAR_SCALE: i64 = 7;

/// Amount in XLM.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Amount {
    inner: BigDecimal,
}

impl Amount {
    /// Create from amount specified in stroops.
    pub fn from_stroops(stroops: Stroops) -> Result<Amount> {
        let data = BigInt::from_i64(stroops.0).ok_or(Error::InvalidStroopsAmount)?;
        let inner = BigDecimal::new(data, STELLAR_SCALE);
        Ok(Amount { inner })
    }

    /// Convert to stroops.
    pub fn as_stroops(&self) -> Result<Stroops> {
        self.clone().into_stroops()
    }

    /// Convert into stroops.
    pub fn into_stroops(self) -> Result<Stroops> {
        let (data, exp) = self.inner.into_bigint_and_exponent();
        if exp != STELLAR_SCALE {
            return Err(Error::InvalidAmountScale);
        }
        match data.to_i64() {
            Some(stroops) => Ok(Stroops::new(stroops)),
            None => Err(Error::InvalidStroopsAmount),
        }
    }
}

impl FromStr for Amount {
    type Err = Error;

    fn from_str(s: &str) -> result::Result<Amount, Error> {
        let inner = BigDecimal::from_str(&s)?;
        // Check we don't lose precision
        let (_, scale) = inner.as_bigint_and_exponent();
        if scale > STELLAR_SCALE {
            Err(Error::InvalidAmountScale)
        } else {
            let scaled_inner = inner.with_scale(STELLAR_SCALE);
            Ok(Amount {
                inner: scaled_inner,
            })
        }
    }
}

/// Amount in stroops.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Stroops(pub i64);

impl Stroops {
    /// Create from stroops.
    pub fn new(amount: i64) -> Stroops {
        Stroops(amount)
    }
}

impl Mul<usize> for Stroops {
    type Output = Self;

    fn mul(self, rhs: usize) -> Self {
        Stroops(self.0 * rhs as i64)
    }
}

/// Price in fractional representation.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Price {
    numerator: i32,
    denominator: i32,
}

impl Price {
    /// Create from numerator and denominator.
    pub fn new(numerator: i32, denominator: i32) -> Price {
        Price {
            numerator,
            denominator,
        }
    }

    /// Return the price numerator.
    pub fn numerator(&self) -> i32 {
        self.numerator
    }

    /// Return the price denominator.
    pub fn denominator(&self) -> i32 {
        self.denominator
    }
}

#[cfg(test)]
mod tests {
    use std::str;
    use super::{Amount, Stroops};

    #[test]
    fn test_amount_from_str() {
        let amount1 = str::parse::<Amount>("123.4567891").unwrap();
        let amount2 = str::parse::<Amount>("123.4567891").unwrap();
        let amount3 = str::parse::<Amount>("123.4567890").unwrap();

        assert_eq!(amount1, amount2);
        assert_ne!(amount1, amount3);
        assert!(amount3 < amount1);
    }

    #[test]
    fn test_error_too_many_decimals() {
        let res = str::parse::<Amount>("123.45678901");
        assert!(res.is_err());
    }

    #[test]
    fn test_amount_as_stroops() {
        let amount = str::parse::<Amount>("123.45678").unwrap();
        let stroops = amount.as_stroops().unwrap();
        assert_eq!(stroops, Stroops::new(1234567800));
    }
}