Skip to main content

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// }