handshake_types/amount.rs
1// Rust Bitcoin Developers 2018-2019
2// Rust Handshake Developers 2018-2019
3//
4// To the extent possible under law, the author(s) have dedicated all
5// copyright and related and neighboring rights to this software to
6// the public domain worldwide. This software is distributed without
7// any warranty.
8//
9// You should have received a copy of the CC0 Public Domain Dedication
10// along with this software.
11// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
12//
13
14//TODO fix here.
15// use crate::protocol::consensus::max_coin;
16use std::error;
17use std::fmt;
18use std::fmt::Write;
19use std::str::FromStr;
20
21/// A set of denominations in which an Amount can be expressed.
22#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
23pub enum Denomination {
24 /// HNS
25 Handshake,
26 // dollarydoo
27 DollaryDoo,
28}
29
30impl Denomination {
31 /// The number of decimal places more than a dollarydoo.
32 fn precision(self) -> u32 {
33 match self {
34 Denomination::DollaryDoo => 0,
35 Denomination::Handshake => 6,
36 }
37 }
38}
39
40impl fmt::Display for Denomination {
41 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42 f.write_str(match *self {
43 Denomination::DollaryDoo => "dollarydoo",
44 Denomination::Handshake => "HNS",
45 })
46 }
47}
48
49impl FromStr for Denomination {
50 type Err = ParseAmountError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 match s {
54 "dollarydoo" => Ok(Denomination::DollaryDoo),
55 "HNS" => Ok(Denomination::Handshake),
56 d => Err(ParseAmountError::UnknownDenomination(d.to_owned())),
57 }
58 }
59}
60
61/// An error during [Amount] parsing.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum ParseAmountError {
64 /// Amount is too big to fit in an [Amount].
65 TooBig,
66 /// Amount has higher precision than supported by [Amount].
67 TooPrecise,
68 /// Invalid number format.
69 InvalidFormat,
70 /// Input string was too large.
71 InputTooLarge,
72 /// Invalid character in input.
73 InvalidCharacter(char),
74 /// The denomination was unknown.
75 UnknownDenomination(String),
76}
77
78impl fmt::Display for ParseAmountError {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 let desc = ::std::error::Error::description(self);
81 match *self {
82 ParseAmountError::InvalidCharacter(c) => write!(f, "{}: {}", desc, c),
83 ParseAmountError::UnknownDenomination(ref d) => write!(f, "{}: {}", desc, d),
84 _ => f.write_str(desc),
85 }
86 }
87}
88
89impl error::Error for ParseAmountError {
90 fn cause(&self) -> Option<&error::Error> {
91 None
92 }
93
94 fn description(&self) -> &'static str {
95 match *self {
96 ParseAmountError::TooBig => "amount is too big",
97 ParseAmountError::TooPrecise => "amount has a too high precision",
98 ParseAmountError::InvalidFormat => "invalid number format",
99 ParseAmountError::InputTooLarge => "input string was too large",
100 ParseAmountError::InvalidCharacter(_) => "invalid character in input",
101 ParseAmountError::UnknownDenomination(_) => "unknown denomination",
102 }
103 }
104}
105
106#[derive(Copy, Clone, Eq, PartialEq, Debug)]
107pub struct Amount(u64);
108
109impl Amount {
110 /// The zero amount.
111 pub const ZERO: Amount = Amount(0);
112 /// Exactly one satoshi.
113 pub const ONE_DOO: Amount = Amount(1);
114 /// Exactly one bitcoin.
115 pub const ONE_HNS: Amount = Amount(1_000_000);
116
117 /// Create an [Amount] with dollary doo precision and the given number of dollary doos.
118 pub fn from_doos(dollary_doo: u64) -> Amount {
119 Amount(dollary_doo)
120 }
121
122 /// Get the number of satoshis in this [Amount].
123 pub fn as_doos(self) -> u64 {
124 self.0
125 }
126
127 /// The maximum value of an [Amount].
128 // TODO implement
129 // pub fn max_value() -> Amount {
130 // Amount(max_coin())
131 // }
132
133 /// The minimum value of an [Amount].
134 pub fn min_value() -> Amount {
135 Amount(u64::min_value())
136 }
137
138 // Don't use the Inner type in the methods below.
139 // Always use [Amount::from_sat] and [Amount::as_sat] instead.
140
141 /// Convert from a value expressing bitcoins to an [Amount].
142 pub fn from_hns(hns: f64) -> Result<Amount, ParseAmountError> {
143 Amount::from_float_in(hns, Denomination::Handshake)
144 }
145
146 /// Parse a decimal string as a value in the given denomination.
147 ///
148 /// Note: This only parses the value string. If you want to parse a value
149 /// with denomination, use [FromStr].
150 pub fn from_str_in(mut s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
151 if s.len() == 0 {
152 return Err(ParseAmountError::InvalidFormat);
153 }
154 if s.len() > 50 {
155 return Err(ParseAmountError::InputTooLarge);
156 }
157
158 // let negative = s.chars().next().unwrap() == '-';
159 // if negative {
160 // if s.len() == 1 {
161 // return Err(ParseAmountError::InvalidFormat);
162 // }
163 // s = &s[1..];
164 // }
165
166 let max_decimals = {
167 // The difference in precision between native (satoshi)
168 // and desired denomination.
169 let precision_diff = denom.precision();
170 if precision_diff > 0 {
171 // If precision diff is negative, this means we are parsing
172 // into a less precise amount. That is not allowed unless
173 // there are no decimals and the last digits are zeroes as
174 // many as the diffence in precision.
175 let last_n = precision_diff as usize;
176 if s.contains(".") || s.chars().rev().take(last_n).any(|d| d != '0') {
177 return Err(ParseAmountError::TooPrecise);
178 }
179 s = &s[0..s.len() - last_n];
180 0
181 } else {
182 precision_diff
183 }
184 };
185
186 let mut decimals = None;
187 let mut value: u64 = 0; // as satoshis
188 for c in s.chars() {
189 match c {
190 '0'...'9' => {
191 // Do `value = 10 * value + digit`, catching overflows.
192 match 10_u64.checked_mul(value) {
193 None => return Err(ParseAmountError::TooBig),
194 Some(val) => match val.checked_add((c as u8 - b'0') as u64) {
195 None => return Err(ParseAmountError::TooBig),
196 Some(val) => value = val,
197 },
198 }
199 // Increment the decimal digit counter if past decimal.
200 decimals = match decimals {
201 None => None,
202 Some(d) if d < max_decimals => Some(d + 1),
203 _ => return Err(ParseAmountError::TooPrecise),
204 };
205 }
206 '.' => match decimals {
207 None => decimals = Some(0),
208 // Double decimal dot.
209 _ => return Err(ParseAmountError::InvalidFormat),
210 },
211 c => return Err(ParseAmountError::InvalidCharacter(c)),
212 }
213 }
214
215 // Decimally shift left by `max_decimals - decimals`.
216 let scale_factor = max_decimals - decimals.unwrap_or(0);
217 for _ in 0..scale_factor {
218 value = match 10_u64.checked_mul(value) {
219 Some(v) => v,
220 None => return Err(ParseAmountError::TooBig),
221 };
222 }
223
224 // if negative {
225 // value *= -1;
226 // }
227 Ok(Amount::from_doos(value))
228 }
229
230 /// Parses amounts with denomination suffix like they are produced with
231 /// [to_string_with_denomination] or with [fmt::Display].
232 /// If you want to parse only the amount without the denomination,
233 /// use [from_str_in].
234 pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseAmountError> {
235 let mut split = s.splitn(3, " ");
236 let amt_str = split.next().unwrap();
237 let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
238 if split.next().is_some() {
239 return Err(ParseAmountError::InvalidFormat);
240 }
241
242 Ok(Amount::from_str_in(amt_str, denom_str.parse()?)?)
243 }
244
245 /// Express this [Amount] as a floating-point value in the given denomination.
246 ///
247 /// Please be aware of the risk of using floating-point numbers.
248 pub fn to_float_in(&self, denom: Denomination) -> f64 {
249 (self.as_doos() as f64) * 10_f64.powi(denom.precision() as i32)
250 }
251
252 /// Express this [Amount] as a floating-point value in Bitcoin.
253 ///
254 /// Equivalent to `to_float_in(Denomination::Bitcoin)`.
255 ///
256 /// Please be aware of the risk of using floating-point numbers.
257 pub fn as_btc(&self) -> f64 {
258 self.to_float_in(Denomination::Handshake)
259 }
260
261 /// Convert this [Amount] in floating-point notation with a given
262 /// denomination.
263 /// Can return error if the amount is too big, too precise or negative.
264 ///
265 /// Please be aware of the risk of using floating-point numbers.
266 pub fn from_float_in(value: f64, denom: Denomination) -> Result<Amount, ParseAmountError> {
267 // This is inefficient, but the safest way to deal with this. The parsing logic is safe.
268 // Any performance-critical application should not be dealing with floats.
269 Amount::from_str_in(&value.to_string(), denom)
270 }
271
272 /// Format the value of this [Amount] in the given denomination.
273 ///
274 /// Does not include the denomination.
275 pub fn fmt_value_in(&self, f: &mut fmt::Write, denom: Denomination) -> fmt::Result {
276 if denom.precision() > 0 {
277 // add zeroes in the end
278 let width = denom.precision() as usize;
279 write!(f, "{}{:0width$}", self.as_doos(), 0, width = width)?;
280 // } else if denom.precision() < 0 {
281 // // need to inject a comma in the number
282
283 // // let sign = match self.is_negative() {
284 // // true => "-",
285 // // false => "",
286 // // };
287 // let sign = "";
288 // let nb_decimals = denom.precision() as usize;
289 // let real = format!("{:0width$}", self.as_doo(), width = nb_decimals);
290 // if real.len() == nb_decimals {
291 // write!(f, "{}0.{}", sign, &real[real.len() - nb_decimals..])?;
292 // } else {
293 // write!(
294 // f,
295 // "{}{}.{}",
296 // sign,
297 // &real[0..(real.len() - nb_decimals)],
298 // &real[real.len() - nb_decimals..]
299 // )?;
300 // }
301 } else {
302 // denom.precision() == 0
303 write!(f, "{}", self.as_doos())?;
304 }
305 Ok(())
306 }
307
308 /// Get a string number of this [Amount] in the given denomination.
309 ///
310 /// Does not include the denomination.
311 pub fn to_string_in(&self, denom: Denomination) -> String {
312 let mut buf = String::new();
313 self.fmt_value_in(&mut buf, denom).unwrap();
314 buf
315 }
316
317 /// Get a formatted string of this [Amount] in the given denomination,
318 /// suffixed with the abbreviation for the denomination.
319 pub fn to_string_with_denomination(&self, denom: Denomination) -> String {
320 let mut buf = String::new();
321 self.fmt_value_in(&mut buf, denom).unwrap();
322 write!(buf, " {}", denom).unwrap();
323 buf
324 }
325
326 // Some arithmethic that doesn't fit in `std::ops` traits.
327
328 /// Get the absolute value of this [Amount].
329 pub fn abs(self) -> Amount {
330 Amount(self.0)
331 }
332
333 // /// Returns a number representing sign of this [Amount].
334 // ///
335 // /// - `0` if the Amount is zero
336 // /// - `1` if the Amount is positive
337 // /// - `-1` if the Amount is negative
338 // pub fn signum(self) -> i64 {
339 // self.0.signum()
340 // }
341
342 // /// Returns `true` if this [Amount] is positive and `false` if
343 // /// this [Amount] is zero or negative.
344 // pub fn is_positive(self) -> bool {
345 // self.0.is_positive()
346 // }
347
348 // /// Returns `true` if this [Amount] is negative and `false` if
349 // /// this [Amount] is zero or positive.
350 // pub fn is_negative(self) -> bool {
351 // self.0.is_negative()
352 // }
353
354 /// Checked addition.
355 /// Returns [None] if overflow occurred.
356 pub fn checked_add(self, rhs: Amount) -> Option<Amount> {
357 self.0.checked_add(rhs.0).map(Amount)
358 }
359
360 /// Checked subtraction.
361 /// Returns [None] if overflow occurred.
362 pub fn checked_sub(self, rhs: Amount) -> Option<Amount> {
363 self.0.checked_sub(rhs.0).map(Amount)
364 }
365
366 /// Checked multiplication.
367 /// Returns [None] if overflow occurred.
368 pub fn checked_mul(self, rhs: u64) -> Option<Amount> {
369 self.0.checked_mul(rhs).map(Amount)
370 }
371
372 /// Checked integer division.
373 /// Be aware that integer division loses the remainder if no exact division
374 /// can be made.
375 /// Returns [None] if overflow occurred.
376 pub fn checked_div(self, rhs: u64) -> Option<Amount> {
377 self.0.checked_div(rhs).map(Amount)
378 }
379
380 /// Checked remainder.
381 /// Returns [None] if overflow occurred.
382 pub fn checked_rem(self, rhs: u64) -> Option<Amount> {
383 self.0.checked_rem(rhs).map(Amount)
384 }
385
386 // /// Subtraction that doesn't allow negative [Amount]s.
387 // /// Returns [None] if either [self], [rhs] or the result is strictly negative.
388 // pub fn positive_sub(self, rhs: Amount) -> Option<Amount> {
389 // if self.is_negative() || rhs.is_negative() || rhs > self {
390 // None
391 // } else {
392 // self.checked_sub(rhs)
393 // }
394 // }
395}
396
397#[cfg(test)]
398mod tests {
399 // Import everything used above
400 use super::*;
401
402 #[test]
403 fn test_denomination_from_string() {
404 let denom = Denomination::from_str("HNS").unwrap();
405 assert_eq!(denom, Denomination::Handshake);
406 let denom = Denomination::from_str("dollarydoo").unwrap();
407 assert_eq!(denom, Denomination::DollaryDoo);
408 }
409}
410
411// impl Denomination {
412// /// The number of decimal places more than a dollarydoo.
413// fn precision(self) -> u32 {
414// match self {
415// Denomination::DollaryDoo => 0,
416// Denomination::Handshake => 6,
417// }
418// }
419// }