nimiq_primitives/
coin.rs

1use std::fmt;
2use std::io;
3use std::ops::{Add, Sub};
4use std::str::FromStr;
5
6use failure::Fail;
7
8use beserial::{Deserialize, ReadBytesExt, Serialize, SerializingError, WriteBytesExt};
9
10#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Default)]
11pub struct Coin(u64);
12
13impl Coin {
14    pub const ZERO: Coin = Coin(0u64);
15
16    // How many Lunas fit in once Coin
17    pub const LUNAS_PER_COIN: u64 = 100_000u64;
18    pub const FRAC_DIGITS: u32 = 5u32;
19
20    // JavaScript's Number.MAX_SAFE_INTEGER: 2^53 - 1
21    pub const MAX_SAFE_VALUE: u64 = 9_007_199_254_740_991u64;
22
23    // TODO: Replace by TryFrom as it becomes stable
24    #[inline]
25    pub fn from_u64(val: u64) -> Result<Coin, CoinParseError> {
26        if val <= Coin::MAX_SAFE_VALUE {
27            Ok(Coin(val))
28        } else {
29            Err(CoinParseError::Overflow)
30        }
31    }
32
33    #[inline]
34    pub fn from_u64_unchecked(val: u64) -> Coin {
35        Coin(val)
36    }
37}
38
39impl From<Coin> for u64 {
40    #[inline]
41    fn from(coin: Coin) -> Self { coin.0 }
42}
43
44impl Add<Coin> for Coin {
45    type Output = Coin;
46
47    #[inline]
48    fn add(self, rhs: Coin) -> Coin {
49        Coin(self.0 + rhs.0)
50    }
51}
52
53impl Sub<Coin> for Coin {
54    type Output = Coin;
55
56    #[inline]
57    fn sub(self, rhs: Coin) -> Coin {
58        Coin(self.0 - rhs.0)
59    }
60}
61
62impl fmt::Display for Coin {
63    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64        // NOTE: The format string has 5 decimal places hard-coded
65        let frac_part = self.0 % Coin::LUNAS_PER_COIN;
66        if frac_part == 0 {
67            write!(f, "{}", self.0 / Coin::LUNAS_PER_COIN)
68        }
69        else {
70            write!(f, "{}.{:05}", self.0 / Coin::LUNAS_PER_COIN, self.0 % Coin::LUNAS_PER_COIN)
71        }
72    }
73}
74
75impl Deserialize for Coin {
76    fn deserialize<R: ReadBytesExt>(reader: &mut R) -> Result<Self, SerializingError> {
77        let value: u64 = Deserialize::deserialize(reader)?;
78
79        // Check that the value does not exceed Javascript's Number.MAX_SAFE_INTEGER.
80        if value <= Coin::MAX_SAFE_VALUE {
81            Ok(Coin(value))
82        } else {
83            Err(io::Error::new(io::ErrorKind::InvalidData, "Coin value out of bounds").into())
84        }
85    }
86}
87
88impl Serialize for Coin {
89    fn serialize<W: WriteBytesExt>(&self, writer: &mut W) -> Result<usize, SerializingError> {
90        if self.0 <= Coin::MAX_SAFE_VALUE {
91            Ok(Serialize::serialize(&self.0, writer)?)
92        } else {
93            Err(SerializingError::Overflow)
94        }
95    }
96
97    fn serialized_size(&self) -> usize {
98        Serialize::serialized_size(&self.0)
99    }
100}
101
102
103impl Coin {
104    #[inline]
105    pub fn checked_add(self, rhs: Coin) -> Option<Coin> {
106        match self.0.checked_add(rhs.0) {
107            Some(val) => Coin::from_u64(val).ok(),
108            None => None,
109        }
110    }
111
112    #[inline]
113    pub fn checked_sub(self, rhs: Coin) -> Option<Coin> {
114        match self.0.checked_sub(rhs.0) {
115            Some(val) => Coin::from_u64(val).ok(),
116            None => None,
117        }
118    }
119
120    #[inline]
121    pub fn checked_factor(self, times: u64) -> Option<Coin> {
122        match self.0.checked_mul(times) {
123            Some(val) => Coin::from_u64(val).ok(),
124            None => None,
125        }
126    }
127}
128
129
130#[derive(Debug, Fail)]
131pub enum CoinParseError {
132    #[fail(display = "Invalid string")]
133    InvalidString,
134    #[fail(display = "Too many fractional digits")]
135    TooManyFractionalDigits,
136    #[fail(display = "Overflow or unsafe value")]
137    Overflow,
138}
139
140
141impl FromStr for Coin {
142    type Err = CoinParseError;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        // NOTE: I would like to use a RegEx here, but this needs a crate - Janosch
146        let split: Vec<&str> = s.split('.').collect();
147
148        // check that string is either 1 part or 2 parts seperated by a `.`
149        if split.len() != 1 && split.len() != 2 {
150            return Err(CoinParseError::InvalidString);
151        }
152        // try to parse integer part
153        // TODO: Do we accept int_part == "" as being 0?
154        let int_part = split[0].parse::<u64>().map_err(|_| CoinParseError::InvalidString)?;
155        // try to parse fractional part, if available
156        // TODO: Do we accept frac_part == "" as being 0?
157        let frac_part = if split.len() == 2 {
158            // check that number of digits doesn't overflow MAX_VALUE of u8
159            if split[1].len() > (std::u8::MAX as usize) {
160                return Err(CoinParseError::TooManyFractionalDigits);
161            }
162            // check how many digits we have in the fraction and multiply by 10^(DIGITS - n)
163            // This cast is safe, since we checked that it doesn't overflow an u8
164            let frac_len = split[1].len() as u32;
165            if frac_len > Coin::FRAC_DIGITS {
166                Err(CoinParseError::TooManyFractionalDigits)
167            }
168            else {
169                Ok(10u64.pow(Coin::FRAC_DIGITS - frac_len))
170            }? * split[1].parse::<u64>().map_err(|_| CoinParseError::InvalidString)?
171        }
172        else { 0u64 };
173        // check that digits out of our precision (5 digits) are all 0
174        if frac_part / Coin::LUNAS_PER_COIN > 0 {
175            return Err(CoinParseError::TooManyFractionalDigits);
176        }
177
178        // multiply int_part with LUNAS_PER_COIN and add frac_part
179        let value = int_part
180            .checked_mul(Coin::LUNAS_PER_COIN).ok_or(CoinParseError::Overflow)?
181            .checked_add(frac_part).ok_or(CoinParseError::Overflow)?;
182
183        Ok(Coin::from_u64(value)?)
184    }
185}