@@ -28,6 +28,10 @@ pub enum Denomination {
28
28
MilliBitcoin ,
29
29
/// uBTC
30
30
MicroBitcoin ,
31
+ /// nBTC
32
+ NanoBitcoin ,
33
+ /// pBTC
34
+ PicoBitcoin ,
31
35
/// bits
32
36
Bit ,
33
37
/// satoshi
@@ -43,6 +47,8 @@ impl Denomination {
43
47
Denomination :: Bitcoin => -8 ,
44
48
Denomination :: MilliBitcoin => -5 ,
45
49
Denomination :: MicroBitcoin => -2 ,
50
+ Denomination :: NanoBitcoin => 1 ,
51
+ Denomination :: PicoBitcoin => 4 ,
46
52
Denomination :: Bit => -2 ,
47
53
Denomination :: Satoshi => 0 ,
48
54
Denomination :: MilliSatoshi => 3 ,
@@ -56,6 +62,8 @@ impl fmt::Display for Denomination {
56
62
Denomination :: Bitcoin => "BTC" ,
57
63
Denomination :: MilliBitcoin => "mBTC" ,
58
64
Denomination :: MicroBitcoin => "uBTC" ,
65
+ Denomination :: NanoBitcoin => "nBTC" ,
66
+ Denomination :: PicoBitcoin => "pBTC" ,
59
67
Denomination :: Bit => "bits" ,
60
68
Denomination :: Satoshi => "satoshi" ,
61
69
Denomination :: MilliSatoshi => "msat" ,
@@ -68,22 +76,26 @@ impl FromStr for Denomination {
68
76
69
77
/// Convert from a str to Denomination.
70
78
///
71
- /// Any combination of upper and/or lower case, excluding uppercase 'M' is considered valid.
72
- /// - Singular: BTC, mBTC, uBTC
79
+ /// Any combination of upper and/or lower case, excluding uppercase of SI(m, u, n, p) is considered valid.
80
+ /// - Singular: BTC, mBTC, uBTC, nBTC, pBTC
73
81
/// - Plural or singular: sat, satoshi, bit, msat
74
82
///
75
- /// Due to ambiguity between mega and milli we prohibit usage of leading capital 'M'.
83
+ /// Due to ambiguity between mega and milli, pico and peta we prohibit usage of leading capital 'M', 'P '.
76
84
fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
77
85
use self :: ParseAmountError :: * ;
86
+ use self :: Denomination as D ;
78
87
79
- if s. starts_with ( 'M' ) {
80
- return Err ( denomination_from_str ( s) . map_or_else (
81
- || UnknownDenomination ( s. to_owned ( ) ) ,
82
- |_| PossiblyConfusingDenomination ( s. to_owned ( ) )
83
- ) ) ;
88
+ let starts_with_uppercase = || s. starts_with ( |ch : char | ch. is_uppercase ( ) ) ;
89
+ match denomination_from_str ( s) {
90
+ None => Err ( UnknownDenomination ( s. to_owned ( ) ) ) ,
91
+ Some ( D :: MilliBitcoin ) | Some ( D :: PicoBitcoin ) | Some ( D :: MilliSatoshi ) if starts_with_uppercase ( ) => {
92
+ Err ( PossiblyConfusingDenomination ( s. to_owned ( ) ) )
93
+ }
94
+ Some ( D :: NanoBitcoin ) | Some ( D :: MicroBitcoin ) if starts_with_uppercase ( ) => {
95
+ Err ( UnknownDenomination ( s. to_owned ( ) ) )
96
+ }
97
+ Some ( d) => Ok ( d) ,
84
98
}
85
-
86
- denomination_from_str ( s) . ok_or_else ( || UnknownDenomination ( s. to_owned ( ) ) )
87
99
}
88
100
}
89
101
@@ -100,6 +112,14 @@ fn denomination_from_str(mut s: &str) -> Option<Denomination> {
100
112
return Some ( Denomination :: MicroBitcoin ) ;
101
113
}
102
114
115
+ if s. eq_ignore_ascii_case ( "nBTC" ) {
116
+ return Some ( Denomination :: NanoBitcoin ) ;
117
+ }
118
+
119
+ if s. eq_ignore_ascii_case ( "pBTC" ) {
120
+ return Some ( Denomination :: PicoBitcoin ) ;
121
+ }
122
+
103
123
if s. ends_with ( 's' ) || s. ends_with ( 'S' ) {
104
124
s = & s[ ..( s. len ( ) - 1 ) ] ;
105
125
}
@@ -153,7 +173,13 @@ impl fmt::Display for ParseAmountError {
153
173
ParseAmountError :: InvalidCharacter ( c) => write ! ( f, "invalid character in input: {}" , c) ,
154
174
ParseAmountError :: UnknownDenomination ( ref d) => write ! ( f, "unknown denomination: {}" , d) ,
155
175
ParseAmountError :: PossiblyConfusingDenomination ( ref d) => {
156
- write ! ( f, "the 'M' at the beginning of {} should technically mean 'Mega' but that denomination is uncommon and maybe 'milli' was intended" , d)
176
+ let ( letter, upper, lower) = match d. chars ( ) . next ( ) {
177
+ Some ( 'M' ) => ( 'M' , "Mega" , "milli" ) ,
178
+ Some ( 'P' ) => ( 'P' , "Peta" , "pico" ) ,
179
+ // This panic could be avoided by adding enum ConfusingDenomination { Mega, Peta } but is it worth it?
180
+ _ => panic ! ( "invalid error information" ) ,
181
+ } ;
182
+ write ! ( f, "the '{}' at the beginning of {} should technically mean '{}' but that denomination is uncommon and maybe '{}' was intended" , letter, d, upper, lower)
157
183
}
158
184
}
159
185
}
@@ -1439,6 +1465,12 @@ mod tests {
1439
1465
assert_eq ! ( "-5" , SignedAmount :: from_sat( -5 ) . to_string_in( D :: Satoshi ) ) ;
1440
1466
assert_eq ! ( "0.10000000" , Amount :: from_sat( 100_000_00 ) . to_string_in( D :: Bitcoin ) ) ;
1441
1467
assert_eq ! ( "-100.00" , SignedAmount :: from_sat( -10_000 ) . to_string_in( D :: Bit ) ) ;
1468
+ assert_eq ! ( "2535830" , Amount :: from_sat( 253583 ) . to_string_in( D :: NanoBitcoin ) ) ;
1469
+ assert_eq ! ( "-100000" , SignedAmount :: from_sat( -10_000 ) . to_string_in( D :: NanoBitcoin ) ) ;
1470
+ assert_eq ! ( "2535830000" , Amount :: from_sat( 253583 ) . to_string_in( D :: PicoBitcoin ) ) ;
1471
+ assert_eq ! ( "-100000000" , SignedAmount :: from_sat( -10_000 ) . to_string_in( D :: PicoBitcoin ) ) ;
1472
+
1473
+
1442
1474
1443
1475
assert_eq ! ( ua_str( & ua_sat( 0 ) . to_string_in( D :: Satoshi ) , D :: Satoshi ) , Ok ( ua_sat( 0 ) ) ) ;
1444
1476
assert_eq ! ( ua_str( & ua_sat( 500 ) . to_string_in( D :: Bitcoin ) , D :: Bitcoin ) , Ok ( ua_sat( 500 ) ) ) ;
@@ -1453,6 +1485,15 @@ mod tests {
1453
1485
// Test an overflow bug in `abs()`
1454
1486
assert_eq ! ( sa_str( & sa_sat( i64 :: min_value( ) ) . to_string_in( D :: Satoshi ) , D :: MicroBitcoin ) , Err ( ParseAmountError :: TooBig ) ) ;
1455
1487
1488
+ assert_eq ! ( sa_str( & sa_sat( -1 ) . to_string_in( D :: NanoBitcoin ) , D :: NanoBitcoin ) , Ok ( sa_sat( -1 ) ) ) ;
1489
+ assert_eq ! ( sa_str( & sa_sat( i64 :: max_value( ) ) . to_string_in( D :: Satoshi ) , D :: NanoBitcoin ) , Err ( ParseAmountError :: TooPrecise ) ) ;
1490
+ assert_eq ! ( sa_str( & sa_sat( i64 :: min_value( ) ) . to_string_in( D :: Satoshi ) , D :: NanoBitcoin ) , Err ( ParseAmountError :: TooPrecise ) ) ;
1491
+
1492
+ assert_eq ! ( sa_str( & sa_sat( -1 ) . to_string_in( D :: PicoBitcoin ) , D :: PicoBitcoin ) , Ok ( sa_sat( -1 ) ) ) ;
1493
+ assert_eq ! ( sa_str( & sa_sat( i64 :: max_value( ) ) . to_string_in( D :: Satoshi ) , D :: PicoBitcoin ) , Err ( ParseAmountError :: TooPrecise ) ) ;
1494
+ assert_eq ! ( sa_str( & sa_sat( i64 :: min_value( ) ) . to_string_in( D :: Satoshi ) , D :: PicoBitcoin ) , Err ( ParseAmountError :: TooPrecise ) ) ;
1495
+
1496
+
1456
1497
}
1457
1498
1458
1499
#[ test]
@@ -1465,7 +1506,9 @@ mod tests {
1465
1506
assert_eq ! ( Amount :: from_str( & denom( amt, D :: MicroBitcoin ) ) , Ok ( amt) ) ;
1466
1507
assert_eq ! ( Amount :: from_str( & denom( amt, D :: Bit ) ) , Ok ( amt) ) ;
1467
1508
assert_eq ! ( Amount :: from_str( & denom( amt, D :: Satoshi ) ) , Ok ( amt) ) ;
1509
+ assert_eq ! ( Amount :: from_str( & denom( amt, D :: NanoBitcoin ) ) , Ok ( amt) ) ;
1468
1510
assert_eq ! ( Amount :: from_str( & denom( amt, D :: MilliSatoshi ) ) , Ok ( amt) ) ;
1511
+ assert_eq ! ( Amount :: from_str( & denom( amt, D :: PicoBitcoin ) ) , Ok ( amt) ) ;
1469
1512
1470
1513
assert_eq ! ( Amount :: from_str( "42 satoshi BTC" ) , Err ( ParseAmountError :: InvalidFormat ) ) ;
1471
1514
assert_eq ! ( SignedAmount :: from_str( "-42 satoshi BTC" ) , Err ( ParseAmountError :: InvalidFormat ) ) ;
@@ -1693,7 +1736,7 @@ mod tests {
1693
1736
#[ test]
1694
1737
fn denomination_string_acceptable_forms ( ) {
1695
1738
// Non-exhaustive list of valid forms.
1696
- let valid = vec ! [ "BTC" , "btc" , "mBTC" , "mbtc" , "uBTC" , "ubtc" , "SATOSHI" , "Satoshi" , "Satoshis" , "satoshis" , "SAT" , "Sat" , "sats" , "bit" , "bits" ] ;
1739
+ let valid = vec ! [ "BTC" , "btc" , "mBTC" , "mbtc" , "uBTC" , "ubtc" , "SATOSHI" , "Satoshi" , "Satoshis" , "satoshis" , "SAT" , "Sat" , "sats" , "bit" , "bits" , "nBTC" , "pBTC" ] ;
1697
1740
for denom in valid. iter ( ) {
1698
1741
assert ! ( Denomination :: from_str( denom) . is_ok( ) ) ;
1699
1742
}
@@ -1702,7 +1745,7 @@ mod tests {
1702
1745
#[ test]
1703
1746
fn disallow_confusing_forms ( ) {
1704
1747
// Non-exhaustive list of confusing forms.
1705
- let confusing = vec ! [ "Msat" , "Msats" , "MSAT" , "MSATS" , "MSat" , "MSats" , "MBTC" , "Mbtc" ] ;
1748
+ let confusing = vec ! [ "Msat" , "Msats" , "MSAT" , "MSATS" , "MSat" , "MSats" , "MBTC" , "Mbtc" , "PBTC" ] ;
1706
1749
for denom in confusing. iter ( ) {
1707
1750
match Denomination :: from_str ( denom) {
1708
1751
Ok ( _) => panic ! ( "from_str should error for {}" , denom) ,
@@ -1711,5 +1754,18 @@ mod tests {
1711
1754
}
1712
1755
}
1713
1756
}
1757
+
1758
+ #[ test]
1759
+ fn disallow_unknown_denomination ( ) {
1760
+ // Non-exhaustive list of unknown forms.
1761
+ let unknown = vec ! [ "NBTC" , "UBTC" , "ABC" , "abc" ] ;
1762
+ for denom in unknown. iter ( ) {
1763
+ match Denomination :: from_str ( denom) {
1764
+ Ok ( _) => panic ! ( "from_str should error for {}" , denom) ,
1765
+ Err ( ParseAmountError :: UnknownDenomination ( _) ) => { } ,
1766
+ Err ( e) => panic ! ( "unexpected error: {}" , e) ,
1767
+ }
1768
+ }
1769
+ }
1714
1770
}
1715
1771
0 commit comments