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 pub const LUNAS_PER_COIN: u64 = 100_000u64;
18 pub const FRAC_DIGITS: u32 = 5u32;
19
20 pub const MAX_SAFE_VALUE: u64 = 9_007_199_254_740_991u64;
22
23 #[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 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 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 let split: Vec<&str> = s.split('.').collect();
147
148 if split.len() != 1 && split.len() != 2 {
150 return Err(CoinParseError::InvalidString);
151 }
152 let int_part = split[0].parse::<u64>().map_err(|_| CoinParseError::InvalidString)?;
155 let frac_part = if split.len() == 2 {
158 if split[1].len() > (std::u8::MAX as usize) {
160 return Err(CoinParseError::TooManyFractionalDigits);
161 }
162 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 if frac_part / Coin::LUNAS_PER_COIN > 0 {
175 return Err(CoinParseError::TooManyFractionalDigits);
176 }
177
178 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}