2022-06-29 04:05:31 +00:00
// SPDX-License-Identifier: CC0-1.0
2018-11-10 00:45:00 +00:00
2021-11-05 21:58:18 +00:00
//! Bitcoin amounts.
2018-11-10 00:45:00 +00:00
//!
//! This module mainly introduces the [Amount] and [SignedAmount] types.
//! We refer to the documentation on the types for more information.
2022-11-15 23:22:16 +00:00
use core ::cmp ::Ordering ;
2023-12-07 22:48:00 +00:00
use core ::fmt ::{ self , Write as _ } ;
2022-11-15 23:22:16 +00:00
use core ::str ::FromStr ;
use core ::{ default , ops } ;
2023-08-08 08:47:05 +00:00
#[ cfg(feature = " serde " ) ]
use ::serde ::{ Deserialize , Serialize } ;
2023-11-20 00:52:19 +00:00
use internals ::error ::InputString ;
2023-11-17 21:01:39 +00:00
use internals ::write_err ;
2023-08-08 08:47:05 +00:00
#[ cfg(feature = " alloc " ) ]
use crate ::prelude ::{ String , ToString } ;
2018-11-10 00:45:00 +00:00
/// A set of denominations in which amounts can be expressed.
2023-01-10 21:52:44 +00:00
///
/// # Examples
/// ```
/// # use core::str::FromStr;
2023-08-08 08:47:05 +00:00
/// # use bitcoin_units::Amount;
2023-01-10 21:52:44 +00:00
///
/// assert_eq!(Amount::from_str("1 BTC").unwrap(), Amount::from_sat(100_000_000));
2023-03-20 15:36:25 +00:00
/// assert_eq!(Amount::from_str("1 cBTC").unwrap(), Amount::from_sat(1_000_000));
2023-01-10 21:52:44 +00:00
/// assert_eq!(Amount::from_str("1 mBTC").unwrap(), Amount::from_sat(100_000));
/// assert_eq!(Amount::from_str("1 uBTC").unwrap(), Amount::from_sat(100));
/// assert_eq!(Amount::from_str("10 nBTC").unwrap(), Amount::from_sat(1));
/// assert_eq!(Amount::from_str("10000 pBTC").unwrap(), Amount::from_sat(1));
/// assert_eq!(Amount::from_str("1 bit").unwrap(), Amount::from_sat(100));
/// assert_eq!(Amount::from_str("1 sat").unwrap(), Amount::from_sat(1));
/// assert_eq!(Amount::from_str("1000 msats").unwrap(), Amount::from_sat(1));
/// ```
2018-11-10 00:45:00 +00:00
#[ derive(Debug, Clone, Copy, Eq, PartialEq, Hash) ]
2023-03-21 19:21:37 +00:00
#[ non_exhaustive ]
2018-11-10 00:45:00 +00:00
pub enum Denomination {
/// BTC
Bitcoin ,
2023-03-20 15:36:25 +00:00
/// cBTC
CentiBitcoin ,
2018-11-10 00:45:00 +00:00
/// mBTC
MilliBitcoin ,
/// uBTC
MicroBitcoin ,
2022-01-10 14:30:28 +00:00
/// nBTC
NanoBitcoin ,
/// pBTC
PicoBitcoin ,
2018-11-10 00:45:00 +00:00
/// bits
Bit ,
/// satoshi
Satoshi ,
/// msat
MilliSatoshi ,
}
impl Denomination {
/// The number of decimal places more than a satoshi.
2021-11-23 16:40:28 +00:00
fn precision ( self ) -> i8 {
2018-11-10 00:45:00 +00:00
match self {
Denomination ::Bitcoin = > - 8 ,
2023-03-20 15:36:25 +00:00
Denomination ::CentiBitcoin = > - 6 ,
2018-11-10 00:45:00 +00:00
Denomination ::MilliBitcoin = > - 5 ,
Denomination ::MicroBitcoin = > - 2 ,
2022-01-10 14:30:28 +00:00
Denomination ::NanoBitcoin = > 1 ,
Denomination ::PicoBitcoin = > 4 ,
2018-11-10 00:45:00 +00:00
Denomination ::Bit = > - 2 ,
Denomination ::Satoshi = > 0 ,
Denomination ::MilliSatoshi = > 3 ,
}
}
2021-11-23 16:40:28 +00:00
/// Returns stringly representation of this
fn as_str ( self ) -> & 'static str {
match self {
2018-11-10 00:45:00 +00:00
Denomination ::Bitcoin = > " BTC " ,
2023-03-20 15:36:25 +00:00
Denomination ::CentiBitcoin = > " cBTC " ,
2018-11-10 00:45:00 +00:00
Denomination ::MilliBitcoin = > " mBTC " ,
Denomination ::MicroBitcoin = > " uBTC " ,
2022-01-10 14:30:28 +00:00
Denomination ::NanoBitcoin = > " nBTC " ,
Denomination ::PicoBitcoin = > " pBTC " ,
2018-11-10 00:45:00 +00:00
Denomination ::Bit = > " bits " ,
Denomination ::Satoshi = > " satoshi " ,
Denomination ::MilliSatoshi = > " msat " ,
2021-11-23 16:40:28 +00:00
}
}
2023-03-23 11:14:11 +00:00
/// The different str forms of denominations that are recognized.
fn forms ( s : & str ) -> Option < Self > {
match s {
" BTC " | " btc " = > Some ( Denomination ::Bitcoin ) ,
" cBTC " | " cbtc " = > Some ( Denomination ::CentiBitcoin ) ,
" mBTC " | " mbtc " = > Some ( Denomination ::MilliBitcoin ) ,
" uBTC " | " ubtc " = > Some ( Denomination ::MicroBitcoin ) ,
" nBTC " | " nbtc " = > Some ( Denomination ::NanoBitcoin ) ,
" pBTC " | " pbtc " = > Some ( Denomination ::PicoBitcoin ) ,
" bit " | " bits " | " BIT " | " BITS " = > Some ( Denomination ::Bit ) ,
" SATOSHI " | " satoshi " | " SATOSHIS " | " satoshis " | " SAT " | " sat " | " SATS " | " sats " = >
Some ( Denomination ::Satoshi ) ,
" mSAT " | " msat " | " mSATs " | " msats " = > Some ( Denomination ::MilliSatoshi ) ,
_ = > None ,
}
}
2021-11-23 16:40:28 +00:00
}
2023-03-23 11:14:11 +00:00
/// These form are ambigous and could have many meanings. For example, M could denote Mega or Milli.
/// If any of these forms are used, an error type PossiblyConfusingDenomination is returned.
const CONFUSING_FORMS : [ & str ; 9 ] =
[ " Msat " , " Msats " , " MSAT " , " MSATS " , " MSat " , " MSats " , " MBTC " , " Mbtc " , " PBTC " ] ;
2021-11-23 16:40:28 +00:00
impl fmt ::Display for Denomination {
2022-11-15 23:22:16 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result { f . write_str ( self . as_str ( ) ) }
2018-11-10 00:45:00 +00:00
}
impl FromStr for Denomination {
2023-11-17 21:01:39 +00:00
type Err = ParseDenominationError ;
2018-11-10 00:45:00 +00:00
2021-12-01 01:56:16 +00:00
/// Convert from a str to Denomination.
///
2022-01-11 14:49:58 +00:00
/// Any combination of upper and/or lower case, excluding uppercase of SI(m, u, n, p) is considered valid.
2022-01-10 14:30:28 +00:00
/// - Singular: BTC, mBTC, uBTC, nBTC, pBTC
2021-12-01 01:56:16 +00:00
/// - Plural or singular: sat, satoshi, bit, msat
///
2022-01-11 14:49:58 +00:00
/// Due to ambiguity between mega and milli, pico and peta we prohibit usage of leading capital 'M', 'P'.
2018-11-10 00:45:00 +00:00
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
2023-11-17 21:01:39 +00:00
use self ::ParseDenominationError ::* ;
2021-12-01 01:56:16 +00:00
2023-03-23 11:14:11 +00:00
if CONFUSING_FORMS . contains ( & s ) {
2023-11-20 00:52:19 +00:00
return Err ( PossiblyConfusing ( PossiblyConfusingDenominationError ( s . into ( ) ) ) ) ;
2023-03-23 11:14:11 +00:00
} ;
2021-12-01 01:56:16 +00:00
2023-03-23 11:14:11 +00:00
let form = self ::Denomination ::forms ( s ) ;
2021-12-01 01:56:16 +00:00
2023-11-20 00:52:19 +00:00
form . ok_or_else ( | | Unknown ( UnknownDenominationError ( s . into ( ) ) ) )
2021-12-01 01:56:16 +00:00
}
}
2018-11-10 00:45:00 +00:00
/// An error during amount parsing.
#[ derive(Debug, Clone, PartialEq, Eq) ]
2022-05-31 04:29:50 +00:00
#[ non_exhaustive ]
2018-11-10 00:45:00 +00:00
pub enum ParseAmountError {
/// Amount is negative.
Negative ,
/// Amount is too big to fit inside the type.
TooBig ,
/// Amount has higher precision than supported by the type.
TooPrecise ,
/// Invalid number format.
InvalidFormat ,
/// Input string was too large.
InputTooLarge ,
/// Invalid character in input.
InvalidCharacter ( char ) ,
2023-11-17 21:01:39 +00:00
/// Invalid denomination.
InvalidDenomination ( ParseDenominationError ) ,
2018-11-10 00:45:00 +00:00
}
impl fmt ::Display for ParseAmountError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
Make error types uniform
On our way to v1.0.0 we are defining a standard for our error types,
this includes:
- Uses the following derives (unless not possible, usually because of `io::Error`)
`#[derive(Debug, Clone, PartialEq, Eq)]`
- Has `non_exhaustive` unless we really know we can commit to not adding
anything.
Furthermore, we are trying to make the codebase easy to read. Error code
is write-once-read-many (well it should be) so if we make all the error
code super uniform the users can flick to an error and quickly see what
it includes. In an effort to achieve this I have made up a style and
over recent times have change much of the error code to that new style,
this PR audits _all_ error types in the code base and enforces the
style, specifically:
- Is layed out: definition, [impl block], Display impl, error::Error impl, From impls
- `error::Error` impl matches on enum even if it returns `None` for all variants
- Display/Error impls import enum variants locally
- match uses *self and `ref e`
- error::Error variants that return `Some` come first, `None` after
Re: non_exhaustive
To make dev and review easier I have added `non_exhaustive` to _every_
error type. We can then remove it error by error as we see fit. This is
because it takes a bit of thinking to do and review where as this patch
should not take much brain power to review.
2023-10-04 02:55:45 +00:00
use ParseAmountError ::* ;
2018-11-10 00:45:00 +00:00
match * self {
Make error types uniform
On our way to v1.0.0 we are defining a standard for our error types,
this includes:
- Uses the following derives (unless not possible, usually because of `io::Error`)
`#[derive(Debug, Clone, PartialEq, Eq)]`
- Has `non_exhaustive` unless we really know we can commit to not adding
anything.
Furthermore, we are trying to make the codebase easy to read. Error code
is write-once-read-many (well it should be) so if we make all the error
code super uniform the users can flick to an error and quickly see what
it includes. In an effort to achieve this I have made up a style and
over recent times have change much of the error code to that new style,
this PR audits _all_ error types in the code base and enforces the
style, specifically:
- Is layed out: definition, [impl block], Display impl, error::Error impl, From impls
- `error::Error` impl matches on enum even if it returns `None` for all variants
- Display/Error impls import enum variants locally
- match uses *self and `ref e`
- error::Error variants that return `Some` come first, `None` after
Re: non_exhaustive
To make dev and review easier I have added `non_exhaustive` to _every_
error type. We can then remove it error by error as we see fit. This is
because it takes a bit of thinking to do and review where as this patch
should not take much brain power to review.
2023-10-04 02:55:45 +00:00
Negative = > f . write_str ( " amount is negative " ) ,
TooBig = > f . write_str ( " amount is too big " ) ,
TooPrecise = > f . write_str ( " amount has a too high precision " ) ,
InvalidFormat = > f . write_str ( " invalid number format " ) ,
InputTooLarge = > f . write_str ( " input string was too large " ) ,
InvalidCharacter ( c ) = > write! ( f , " invalid character in input: {} " , c ) ,
2023-11-17 21:01:39 +00:00
InvalidDenomination ( ref e ) = > write_err! ( f , " invalid denomination " ; e ) ,
}
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for ParseAmountError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > {
use ParseAmountError ::* ;
match * self {
Negative | TooBig | TooPrecise | InvalidFormat | InputTooLarge
| InvalidCharacter ( _ ) = > None ,
InvalidDenomination ( ref e ) = > Some ( e ) ,
}
}
}
impl From < ParseDenominationError > for ParseAmountError {
fn from ( e : ParseDenominationError ) -> Self { Self ::InvalidDenomination ( e ) }
}
/// An error during amount parsing.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub enum ParseDenominationError {
/// The denomination was unknown.
2023-11-20 00:52:19 +00:00
Unknown ( UnknownDenominationError ) ,
2023-11-17 21:01:39 +00:00
/// The denomination has multiple possible interpretations.
2023-11-20 00:52:19 +00:00
PossiblyConfusing ( PossiblyConfusingDenominationError ) ,
2023-11-17 21:01:39 +00:00
}
impl fmt ::Display for ParseDenominationError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
use ParseDenominationError ::* ;
match * self {
2023-11-20 00:52:19 +00:00
Unknown ( ref e ) = > write_err! ( f , " denomination parse error " ; e ) ,
PossiblyConfusing ( ref e ) = > write_err! ( f , " denomination parse error " ; e ) ,
2018-11-10 00:45:00 +00:00
}
}
}
2021-06-09 10:34:44 +00:00
#[ cfg(feature = " std " ) ]
2023-11-17 21:01:39 +00:00
impl std ::error ::Error for ParseDenominationError {
2022-06-10 02:07:46 +00:00
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > {
2023-11-17 21:01:39 +00:00
use ParseDenominationError ::* ;
2022-06-10 02:07:46 +00:00
match * self {
2023-11-17 21:01:39 +00:00
Unknown ( _ ) | PossiblyConfusing ( _ ) = > None ,
2022-06-10 02:07:46 +00:00
}
}
}
2019-08-06 14:32:51 +00:00
2023-11-20 00:52:19 +00:00
/// Parsing error, unknown denomination.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub struct UnknownDenominationError ( InputString ) ;
impl fmt ::Display for UnknownDenominationError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
self . 0. unknown_variant ( " bitcoin denomination " , f )
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for UnknownDenominationError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > { None }
}
/// Parsing error, possibly confusing denomination.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub struct PossiblyConfusingDenominationError ( InputString ) ;
impl fmt ::Display for PossiblyConfusingDenominationError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
write! ( f , " {}: possibly confusing denomination - we intentionally do not support 'M' and 'P' so as to not confuse mega/milli and peta/pico " , self . 0. display_cannot_parse ( " bitcoin denomination " ) )
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for PossiblyConfusingDenominationError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > { None }
}
2019-08-06 14:32:51 +00:00
fn is_too_precise ( s : & str , precision : usize ) -> bool {
2023-05-07 06:45:25 +00:00
match s . find ( '.' ) {
Some ( pos ) = >
s [ ( pos + 1 ) .. ] . chars ( ) . any ( | d | d ! = '0' )
| | precision > = pos
| | s [ .. pos ] . chars ( ) . rev ( ) . take ( precision ) . any ( | d | d ! = '0' ) ,
None = > precision > = s . len ( ) | | s . chars ( ) . rev ( ) . take ( precision ) . any ( | d | d ! = '0' ) ,
}
2019-08-06 14:32:51 +00:00
}
2018-11-10 00:45:00 +00:00
/// Parse decimal string in the given denomination into a satoshi value and a
/// bool indicator for a negative amount.
fn parse_signed_to_satoshi (
mut s : & str ,
denom : Denomination ,
) -> Result < ( bool , u64 ) , ParseAmountError > {
2019-08-05 19:41:07 +00:00
if s . is_empty ( ) {
2018-11-10 00:45:00 +00:00
return Err ( ParseAmountError ::InvalidFormat ) ;
}
if s . len ( ) > 50 {
return Err ( ParseAmountError ::InputTooLarge ) ;
}
2019-08-05 19:41:07 +00:00
let is_negative = s . starts_with ( '-' ) ;
2019-08-06 14:32:51 +00:00
if is_negative {
2018-11-10 00:45:00 +00:00
if s . len ( ) = = 1 {
return Err ( ParseAmountError ::InvalidFormat ) ;
}
s = & s [ 1 .. ] ;
}
let max_decimals = {
// The difference in precision between native (satoshi)
// and desired denomination.
let precision_diff = - denom . precision ( ) ;
2023-05-07 06:45:25 +00:00
if precision_diff < = 0 {
2018-11-10 00:45:00 +00:00
// If precision diff is negative, this means we are parsing
// into a less precise amount. That is not allowed unless
// there are no decimals and the last digits are zeroes as
2019-08-04 19:27:36 +00:00
// many as the difference in precision.
2021-11-23 16:40:28 +00:00
let last_n = unsigned_abs ( precision_diff ) . into ( ) ;
2019-08-06 14:32:51 +00:00
if is_too_precise ( s , last_n ) {
2023-01-12 06:58:02 +00:00
match s . parse ::< i64 > ( ) {
2023-10-06 04:59:59 +00:00
Ok ( 0 ) = > return Ok ( ( is_negative , 0 ) ) ,
2023-01-12 06:58:02 +00:00
_ = > return Err ( ParseAmountError ::TooPrecise ) ,
}
2018-11-10 00:45:00 +00:00
}
2023-05-07 06:45:25 +00:00
s = & s [ 0 .. s . find ( '.' ) . unwrap_or ( s . len ( ) ) - last_n ] ;
2018-11-10 00:45:00 +00:00
0
} else {
precision_diff
}
} ;
let mut decimals = None ;
let mut value : u64 = 0 ; // as satoshis
for c in s . chars ( ) {
match c {
2020-09-14 14:51:16 +00:00
'0' ..= '9' = > {
2018-11-10 00:45:00 +00:00
// Do `value = 10 * value + digit`, catching overflows.
match 10_ u64 . checked_mul ( value ) {
None = > return Err ( ParseAmountError ::TooBig ) ,
Some ( val ) = > match val . checked_add ( ( c as u8 - b '0' ) as u64 ) {
None = > return Err ( ParseAmountError ::TooBig ) ,
Some ( val ) = > value = val ,
} ,
}
// Increment the decimal digit counter if past decimal.
decimals = match decimals {
None = > None ,
Some ( d ) if d < max_decimals = > Some ( d + 1 ) ,
_ = > return Err ( ParseAmountError ::TooPrecise ) ,
} ;
}
'.' = > match decimals {
2023-05-07 06:45:25 +00:00
None if max_decimals < = 0 = > break ,
2018-11-10 00:45:00 +00:00
None = > decimals = Some ( 0 ) ,
// Double decimal dot.
_ = > return Err ( ParseAmountError ::InvalidFormat ) ,
} ,
c = > return Err ( ParseAmountError ::InvalidCharacter ( c ) ) ,
}
}
// Decimally shift left by `max_decimals - decimals`.
let scale_factor = max_decimals - decimals . unwrap_or ( 0 ) ;
for _ in 0 .. scale_factor {
value = match 10_ u64 . checked_mul ( value ) {
Some ( v ) = > v ,
None = > return Err ( ParseAmountError ::TooBig ) ,
} ;
}
2019-08-06 14:32:51 +00:00
Ok ( ( is_negative , value ) )
2018-11-10 00:45:00 +00:00
}
2022-12-31 22:19:56 +00:00
fn split_amount_and_denomination ( s : & str ) -> Result < ( & str , Denomination ) , ParseAmountError > {
2022-11-02 22:36:37 +00:00
let ( i , j ) = if let Some ( i ) = s . find ( ' ' ) {
( i , i + 1 )
} else {
let i = s . find ( | c : char | c . is_alphabetic ( ) ) . ok_or ( ParseAmountError ::InvalidFormat ) ? ;
( i , i )
} ;
Ok ( ( & s [ .. i ] , s [ j .. ] . parse ( ) ? ) )
2022-12-31 22:19:56 +00:00
}
2021-11-23 16:40:28 +00:00
/// Options given by `fmt::Formatter`
struct FormatOptions {
fill : char ,
align : Option < fmt ::Alignment > ,
width : Option < usize > ,
precision : Option < usize > ,
sign_plus : bool ,
sign_aware_zero_pad : bool ,
}
impl FormatOptions {
fn from_formatter ( f : & fmt ::Formatter ) -> Self {
FormatOptions {
fill : f . fill ( ) ,
align : f . align ( ) ,
width : f . width ( ) ,
precision : f . precision ( ) ,
sign_plus : f . sign_plus ( ) ,
sign_aware_zero_pad : f . sign_aware_zero_pad ( ) ,
}
}
}
impl Default for FormatOptions {
fn default ( ) -> Self {
FormatOptions {
fill : ' ' ,
align : None ,
width : None ,
precision : None ,
sign_plus : false ,
sign_aware_zero_pad : false ,
}
}
}
fn dec_width ( mut num : u64 ) -> usize {
let mut width = 1 ;
loop {
num / = 10 ;
if num = = 0 {
break ;
}
width + = 1 ;
}
width
}
2022-07-22 03:52:09 +00:00
// NIH due to MSRV, impl copied from `core::i8::unsigned_abs` (introduced in Rust 1.51.1).
2022-11-15 23:22:16 +00:00
fn unsigned_abs ( x : i8 ) -> u8 { x . wrapping_abs ( ) as u8 }
2021-11-23 16:40:28 +00:00
fn repeat_char ( f : & mut dyn fmt ::Write , c : char , count : usize ) -> fmt ::Result {
for _ in 0 .. count {
f . write_char ( c ) ? ;
}
Ok ( ( ) )
}
2018-11-10 00:45:00 +00:00
/// Format the given satoshi amount in the given denomination.
fn fmt_satoshi_in (
satoshi : u64 ,
negative : bool ,
2020-09-14 14:55:42 +00:00
f : & mut dyn fmt ::Write ,
2018-11-10 00:45:00 +00:00
denom : Denomination ,
2021-11-23 16:40:28 +00:00
show_denom : bool ,
options : FormatOptions ,
2018-11-10 00:45:00 +00:00
) -> fmt ::Result {
2019-08-05 19:41:07 +00:00
let precision = denom . precision ( ) ;
2021-11-23 16:40:28 +00:00
// First we normalize the number:
// {num_before_decimal_point}{:0exp}{"." if nb_decimals > 0}{:0nb_decimals}{num_after_decimal_point}{:0trailing_decimal_zeros}
let mut num_after_decimal_point = 0 ;
let mut norm_nb_decimals = 0 ;
let mut num_before_decimal_point = satoshi ;
let trailing_decimal_zeros ;
let mut exp = 0 ;
2019-08-05 19:41:07 +00:00
match precision . cmp ( & 0 ) {
2021-11-23 16:40:28 +00:00
// We add the number of zeroes to the end
2019-08-05 19:41:07 +00:00
Ordering ::Greater = > {
2021-11-23 16:40:28 +00:00
if satoshi > 0 {
exp = precision as usize ;
}
trailing_decimal_zeros = options . precision . unwrap_or ( 0 ) ;
2022-11-15 23:22:16 +00:00
}
2019-08-05 19:41:07 +00:00
Ordering ::Less = > {
2021-11-23 16:40:28 +00:00
let precision = unsigned_abs ( precision ) ;
let divisor = 10 u64 . pow ( precision . into ( ) ) ;
num_before_decimal_point = satoshi / divisor ;
num_after_decimal_point = satoshi % divisor ;
// normalize by stripping trailing zeros
if num_after_decimal_point = = 0 {
norm_nb_decimals = 0 ;
2019-08-05 19:41:07 +00:00
} else {
2021-11-23 16:40:28 +00:00
norm_nb_decimals = usize ::from ( precision ) ;
while num_after_decimal_point % 10 = = 0 {
norm_nb_decimals - = 1 ;
num_after_decimal_point / = 10
}
2019-08-05 19:41:07 +00:00
}
2021-11-23 16:40:28 +00:00
// compute requested precision
let opt_precision = options . precision . unwrap_or ( 0 ) ;
trailing_decimal_zeros = opt_precision . saturating_sub ( norm_nb_decimals ) ;
2022-11-15 23:22:16 +00:00
}
2021-11-23 16:40:28 +00:00
Ordering ::Equal = > trailing_decimal_zeros = options . precision . unwrap_or ( 0 ) ,
}
let total_decimals = norm_nb_decimals + trailing_decimal_zeros ;
// Compute expected width of the number
let mut num_width = if total_decimals > 0 {
// 1 for decimal point
1 + total_decimals
} else {
0
} ;
num_width + = dec_width ( num_before_decimal_point ) + exp ;
if options . sign_plus | | negative {
num_width + = 1 ;
2018-11-10 00:45:00 +00:00
}
2021-11-23 16:40:28 +00:00
if show_denom {
// + 1 for space
num_width + = denom . as_str ( ) . len ( ) + 1 ;
}
let width = options . width . unwrap_or ( 0 ) ;
2022-11-18 02:09:28 +00:00
let align = options . align . unwrap_or ( fmt ::Alignment ::Right ) ;
let ( left_pad , pad_right ) = match ( num_width < width , options . sign_aware_zero_pad , align ) {
2021-11-23 16:40:28 +00:00
( false , _ , _ ) = > ( 0 , 0 ) ,
// Alignment is always right (ignored) when zero-padding
( true , true , _ ) | ( true , false , fmt ::Alignment ::Right ) = > ( width - num_width , 0 ) ,
( true , false , fmt ::Alignment ::Left ) = > ( 0 , width - num_width ) ,
// If the required padding is odd it needs to be skewed to the left
2022-11-15 23:22:16 +00:00
( true , false , fmt ::Alignment ::Center ) = >
( ( width - num_width ) / 2 , ( width - num_width + 1 ) / 2 ) ,
2021-11-23 16:40:28 +00:00
} ;
if ! options . sign_aware_zero_pad {
repeat_char ( f , options . fill , left_pad ) ? ;
}
if negative {
write! ( f , " - " ) ? ;
} else if options . sign_plus {
write! ( f , " + " ) ? ;
}
if options . sign_aware_zero_pad {
repeat_char ( f , '0' , left_pad ) ? ;
}
write! ( f , " {} " , num_before_decimal_point ) ? ;
repeat_char ( f , '0' , exp ) ? ;
if total_decimals > 0 {
write! ( f , " . " ) ? ;
}
if norm_nb_decimals > 0 {
write! ( f , " {:0width$} " , num_after_decimal_point , width = norm_nb_decimals ) ? ;
}
repeat_char ( f , '0' , trailing_decimal_zeros ) ? ;
if show_denom {
write! ( f , " {} " , denom . as_str ( ) ) ? ;
}
repeat_char ( f , options . fill , pad_right ) ? ;
2018-11-10 00:45:00 +00:00
Ok ( ( ) )
}
/// Amount
///
2022-12-19 08:32:52 +00:00
/// The [Amount] type can be used to express Bitcoin amounts that support
2019-08-04 19:27:36 +00:00
/// arithmetic and conversion to various denominations.
2018-11-10 00:45:00 +00:00
///
///
/// Warning!
///
2021-06-09 10:40:41 +00:00
/// This type implements several arithmetic operations from [core::ops].
2018-11-10 00:45:00 +00:00
/// To prevent errors due to overflow or underflow when using these operations,
/// it is advised to instead use the checked arithmetic methods whose names
2021-06-09 10:40:41 +00:00
/// start with `checked_`. The operations from [core::ops] that [Amount]
2018-11-10 00:45:00 +00:00
/// implements will panic when overflow or underflow occurs. Also note that
/// since the internal representation of amounts is unsigned, subtracting below
/// zero is considered an underflow and will cause a panic if you're not using
/// the checked arithmetic methods.
///
2019-08-05 19:44:32 +00:00
#[ derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash) ]
2023-04-24 14:47:55 +00:00
#[ cfg_attr(feature = " serde " , derive(Serialize, Deserialize)) ]
2018-11-10 00:45:00 +00:00
pub struct Amount ( u64 ) ;
impl Amount {
/// The zero amount.
pub const ZERO : Amount = Amount ( 0 ) ;
/// Exactly one satoshi.
pub const ONE_SAT : Amount = Amount ( 1 ) ;
/// Exactly one bitcoin.
2023-06-13 09:05:08 +00:00
pub const ONE_BTC : Amount = Self ::from_int_btc ( 1 ) ;
2021-12-14 16:54:39 +00:00
/// The maximum value allowed as an amount. Useful for sanity checking.
2023-06-13 09:05:08 +00:00
pub const MAX_MONEY : Amount = Self ::from_int_btc ( 21_000_000 ) ;
2023-05-02 22:14:44 +00:00
/// The minimum value of an amount.
pub const MIN : Amount = Amount ::ZERO ;
/// The maximum value of an amount.
pub const MAX : Amount = Amount ( u64 ::MAX ) ;
2023-09-17 21:51:41 +00:00
/// The number of bytes that an amount contributes to the size of a transaction.
pub const SIZE : usize = 8 ; // Serialized length of a u64.
2018-11-10 00:45:00 +00:00
/// Create an [Amount] with satoshi precision and the given number of satoshis.
2022-11-15 23:22:16 +00:00
pub const fn from_sat ( satoshi : u64 ) -> Amount { Amount ( satoshi ) }
2018-11-10 00:45:00 +00:00
2022-01-19 03:05:01 +00:00
/// Gets the number of satoshis in this [`Amount`].
2022-11-15 23:22:16 +00:00
pub fn to_sat ( self ) -> u64 { self . 0 }
2018-11-10 00:45:00 +00:00
/// Convert from a value expressing bitcoins to an [Amount].
pub fn from_btc ( btc : f64 ) -> Result < Amount , ParseAmountError > {
Amount ::from_float_in ( btc , Denomination ::Bitcoin )
}
2023-06-13 08:55:04 +00:00
/// Convert from a value expressing integer values of bitcoins to an [Amount]
/// in const context.
///
/// ## Panics
///
/// The function panics if the argument multiplied by the number of sats
/// per bitcoin overflows a u64 type.
pub const fn from_int_btc ( btc : u64 ) -> Amount {
// TODO replace whith unwrap() when available in const context.
match btc . checked_mul ( 100_000_000 ) {
Some ( amount ) = > Amount ::from_sat ( amount ) ,
None = > {
// TODO replace with panic!() when MSRV = 1.57+
#[ allow(unconditional_panic) ]
// disabling this lint until panic!() can be used.
#[ allow(clippy::let_unit_value) ]
let _int_overflow_converting_btc_to_sats = [ ( ) ; 0 ] [ 1 ] ;
Amount ( 0 )
}
}
}
2018-11-10 00:45:00 +00:00
/// Parse a decimal string as a value in the given denomination.
///
/// Note: This only parses the value string. If you want to parse a value
/// with denomination, use [FromStr].
pub fn from_str_in ( s : & str , denom : Denomination ) -> Result < Amount , ParseAmountError > {
let ( negative , satoshi ) = parse_signed_to_satoshi ( s , denom ) ? ;
if negative {
return Err ( ParseAmountError ::Negative ) ;
}
2023-05-02 22:16:20 +00:00
if satoshi > i64 ::MAX as u64 {
2018-11-10 00:45:00 +00:00
return Err ( ParseAmountError ::TooBig ) ;
}
Ok ( Amount ::from_sat ( satoshi ) )
}
/// Parses amounts with denomination suffix like they are produced with
2021-05-03 09:41:58 +00:00
/// [Self::to_string_with_denomination] or with [fmt::Display].
2018-11-10 00:45:00 +00:00
/// If you want to parse only the amount without the denomination,
2021-05-03 09:41:58 +00:00
/// use [Self::from_str_in].
2018-11-10 00:45:00 +00:00
pub fn from_str_with_denomination ( s : & str ) -> Result < Amount , ParseAmountError > {
2022-12-31 22:19:56 +00:00
let ( amt , denom ) = split_amount_and_denomination ( s ) ? ;
Amount ::from_str_in ( amt , denom )
2018-11-10 00:45:00 +00:00
}
/// Express this [Amount] as a floating-point value in the given denomination.
///
/// Please be aware of the risk of using floating-point numbers.
2019-08-05 18:52:34 +00:00
pub fn to_float_in ( self , denom : Denomination ) -> f64 {
2018-11-10 00:45:00 +00:00
f64 ::from_str ( & self . to_string_in ( denom ) ) . unwrap ( )
}
2022-01-19 03:05:01 +00:00
/// Express this [`Amount`] as a floating-point value in Bitcoin.
2018-11-10 00:45:00 +00:00
///
/// Please be aware of the risk of using floating-point numbers.
2022-01-19 03:05:01 +00:00
///
/// # Examples
/// ```
2023-08-08 08:47:05 +00:00
/// # use bitcoin_units::amount::{Amount, Denomination};
2022-01-19 03:05:01 +00:00
/// let amount = Amount::from_sat(100_000);
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin))
/// ```
2022-11-15 23:22:16 +00:00
pub fn to_btc ( self ) -> f64 { self . to_float_in ( Denomination ::Bitcoin ) }
2018-11-10 00:45:00 +00:00
/// Convert this [Amount] in floating-point notation with a given
/// denomination.
/// Can return error if the amount is too big, too precise or negative.
///
/// Please be aware of the risk of using floating-point numbers.
pub fn from_float_in ( value : f64 , denom : Denomination ) -> Result < Amount , ParseAmountError > {
if value < 0.0 {
return Err ( ParseAmountError ::Negative ) ;
}
// This is inefficient, but the safest way to deal with this. The parsing logic is safe.
// Any performance-critical application should not be dealing with floats.
Amount ::from_str_in ( & value . to_string ( ) , denom )
}
2021-11-23 16:40:28 +00:00
/// Create an object that implements [`fmt::Display`] using specified denomination.
pub fn display_in ( self , denomination : Denomination ) -> Display {
Display {
2022-01-19 03:05:01 +00:00
sats_abs : self . to_sat ( ) ,
2021-11-23 16:40:28 +00:00
is_negative : false ,
2022-11-15 23:22:16 +00:00
style : DisplayStyle ::FixedDenomination { denomination , show_denomination : false } ,
2021-11-23 16:40:28 +00:00
}
}
/// Create an object that implements [`fmt::Display`] dynamically selecting denomination.
///
/// This will use BTC for values greater than or equal to 1 BTC and satoshis otherwise. To
/// avoid confusion the denomination is always shown.
pub fn display_dynamic ( self ) -> Display {
Display {
2022-01-19 03:05:01 +00:00
sats_abs : self . to_sat ( ) ,
2021-11-23 16:40:28 +00:00
is_negative : false ,
style : DisplayStyle ::DynamicDenomination ,
}
}
2018-11-10 00:45:00 +00:00
/// Format the value of this [Amount] in the given denomination.
///
/// Does not include the denomination.
2022-11-15 23:10:54 +00:00
#[ rustfmt::skip ]
2020-09-14 14:55:42 +00:00
pub fn fmt_value_in ( self , f : & mut dyn fmt ::Write , denom : Denomination ) -> fmt ::Result {
2022-01-19 03:05:01 +00:00
fmt_satoshi_in ( self . to_sat ( ) , false , f , denom , false , FormatOptions ::default ( ) )
2018-11-10 00:45:00 +00:00
}
/// Get a string number of this [Amount] in the given denomination.
///
/// Does not include the denomination.
2019-08-05 18:52:34 +00:00
pub fn to_string_in ( self , denom : Denomination ) -> String {
2018-11-10 00:45:00 +00:00
let mut buf = String ::new ( ) ;
self . fmt_value_in ( & mut buf , denom ) . unwrap ( ) ;
buf
}
/// Get a formatted string of this [Amount] in the given denomination,
/// suffixed with the abbreviation for the denomination.
2019-08-05 18:52:34 +00:00
pub fn to_string_with_denomination ( self , denom : Denomination ) -> String {
2018-11-10 00:45:00 +00:00
let mut buf = String ::new ( ) ;
self . fmt_value_in ( & mut buf , denom ) . unwrap ( ) ;
write! ( buf , " {} " , denom ) . unwrap ( ) ;
buf
}
2021-06-09 10:40:41 +00:00
// Some arithmetic that doesn't fit in `core::ops` traits.
2018-11-10 00:45:00 +00:00
/// Checked addition.
/// Returns [None] if overflow occurred.
pub fn checked_add ( self , rhs : Amount ) -> Option < Amount > {
self . 0. checked_add ( rhs . 0 ) . map ( Amount )
}
/// Checked subtraction.
/// Returns [None] if overflow occurred.
pub fn checked_sub ( self , rhs : Amount ) -> Option < Amount > {
self . 0. checked_sub ( rhs . 0 ) . map ( Amount )
}
/// Checked multiplication.
/// Returns [None] if overflow occurred.
2022-11-15 23:22:16 +00:00
pub fn checked_mul ( self , rhs : u64 ) -> Option < Amount > { self . 0. checked_mul ( rhs ) . map ( Amount ) }
2018-11-10 00:45:00 +00:00
/// Checked integer division.
/// Be aware that integer division loses the remainder if no exact division
/// can be made.
/// Returns [None] if overflow occurred.
2022-11-15 23:22:16 +00:00
pub fn checked_div ( self , rhs : u64 ) -> Option < Amount > { self . 0. checked_div ( rhs ) . map ( Amount ) }
2018-11-10 00:45:00 +00:00
/// Checked remainder.
/// Returns [None] if overflow occurred.
2022-11-15 23:22:16 +00:00
pub fn checked_rem ( self , rhs : u64 ) -> Option < Amount > { self . 0. checked_rem ( rhs ) . map ( Amount ) }
2018-11-10 00:45:00 +00:00
/// Convert to a signed amount.
pub fn to_signed ( self ) -> Result < SignedAmount , ParseAmountError > {
2023-05-02 22:16:20 +00:00
if self . to_sat ( ) > SignedAmount ::MAX . to_sat ( ) as u64 {
2018-11-10 00:45:00 +00:00
Err ( ParseAmountError ::TooBig )
} else {
2022-01-19 03:05:01 +00:00
Ok ( SignedAmount ::from_sat ( self . to_sat ( ) as i64 ) )
2018-11-10 00:45:00 +00:00
}
}
}
impl default ::Default for Amount {
2022-11-15 23:22:16 +00:00
fn default ( ) -> Self { Amount ::ZERO }
2018-11-10 00:45:00 +00:00
}
impl fmt ::Debug for Amount {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2022-01-19 03:05:01 +00:00
write! ( f , " Amount({:.8} BTC) " , self . to_btc ( ) )
2018-11-10 00:45:00 +00:00
}
}
// No one should depend on a binding contract for Display for this type.
// Just using Bitcoin denominated string.
impl fmt ::Display for Amount {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
self . fmt_value_in ( f , Denomination ::Bitcoin ) ? ;
write! ( f , " {} " , Denomination ::Bitcoin )
}
}
impl ops ::Add for Amount {
type Output = Amount ;
fn add ( self , rhs : Amount ) -> Self ::Output {
self . checked_add ( rhs ) . expect ( " Amount addition error " )
}
}
impl ops ::AddAssign for Amount {
2022-11-15 23:22:16 +00:00
fn add_assign ( & mut self , other : Amount ) { * self = * self + other }
2018-11-10 00:45:00 +00:00
}
impl ops ::Sub for Amount {
type Output = Amount ;
fn sub ( self , rhs : Amount ) -> Self ::Output {
self . checked_sub ( rhs ) . expect ( " Amount subtraction error " )
}
}
impl ops ::SubAssign for Amount {
2022-11-15 23:22:16 +00:00
fn sub_assign ( & mut self , other : Amount ) { * self = * self - other }
2018-11-10 00:45:00 +00:00
}
impl ops ::Rem < u64 > for Amount {
type Output = Amount ;
fn rem ( self , modulus : u64 ) -> Self {
self . checked_rem ( modulus ) . expect ( " Amount remainder error " )
}
}
impl ops ::RemAssign < u64 > for Amount {
2022-11-15 23:22:16 +00:00
fn rem_assign ( & mut self , modulus : u64 ) { * self = * self % modulus }
2018-11-10 00:45:00 +00:00
}
impl ops ::Mul < u64 > for Amount {
type Output = Amount ;
fn mul ( self , rhs : u64 ) -> Self ::Output {
self . checked_mul ( rhs ) . expect ( " Amount multiplication error " )
}
}
impl ops ::MulAssign < u64 > for Amount {
2022-11-15 23:22:16 +00:00
fn mul_assign ( & mut self , rhs : u64 ) { * self = * self * rhs }
2018-11-10 00:45:00 +00:00
}
impl ops ::Div < u64 > for Amount {
type Output = Amount ;
2022-11-15 23:22:16 +00:00
fn div ( self , rhs : u64 ) -> Self ::Output { self . checked_div ( rhs ) . expect ( " Amount division error " ) }
2018-11-10 00:45:00 +00:00
}
impl ops ::DivAssign < u64 > for Amount {
2022-11-15 23:22:16 +00:00
fn div_assign ( & mut self , rhs : u64 ) { * self = * self / rhs }
2018-11-10 00:45:00 +00:00
}
impl FromStr for Amount {
type Err = ParseAmountError ;
2022-11-15 23:22:16 +00:00
fn from_str ( s : & str ) -> Result < Self , Self ::Err > { Amount ::from_str_with_denomination ( s ) }
2018-11-10 00:45:00 +00:00
}
2022-06-01 22:08:56 +00:00
impl core ::iter ::Sum for Amount {
2022-01-24 00:26:29 +00:00
fn sum < I : Iterator < Item = Self > > ( iter : I ) -> Self {
2021-06-12 14:46:04 +00:00
let sats : u64 = iter . map ( | amt | amt . 0 ) . sum ( ) ;
Amount ::from_sat ( sats )
}
}
2021-11-23 16:40:28 +00:00
/// A helper/builder that displays amount with specified settings.
///
/// This provides richer interface than `fmt::Formatter`:
///
/// * Ability to select denomination
/// * Show or hide denomination
/// * Dynamically-selected denomination - show in sats if less than 1 BTC.
///
/// However this can still be combined with `fmt::Formatter` options to precisely control zeros,
/// padding, alignment... The formatting works like floats from `core` but note that precision will
/// **never** be lossy - that means no rounding.
///
/// See [`Amount::display_in`] and [`Amount::display_dynamic`] on how to construct this.
#[ derive(Debug, Clone) ]
pub struct Display {
/// Absolute value of satoshis to display (sign is below)
sats_abs : u64 ,
/// The sign
is_negative : bool ,
/// How to display the value
style : DisplayStyle ,
}
impl Display {
/// Makes subsequent calls to `Display::fmt` display denomination.
pub fn show_denomination ( mut self ) -> Self {
match & mut self . style {
DisplayStyle ::FixedDenomination { show_denomination , .. } = > * show_denomination = true ,
// No-op because dynamic denomination is always shown
DisplayStyle ::DynamicDenomination = > ( ) ,
}
self
}
}
impl fmt ::Display for Display {
2022-11-15 23:10:54 +00:00
#[ rustfmt::skip ]
2021-11-23 16:40:28 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
let format_options = FormatOptions ::from_formatter ( f ) ;
match & self . style {
2022-11-15 23:10:54 +00:00
DisplayStyle ::FixedDenomination { show_denomination , denomination } = > {
fmt_satoshi_in ( self . sats_abs , self . is_negative , f , * denomination , * show_denomination , format_options )
} ,
2022-01-19 03:05:01 +00:00
DisplayStyle ::DynamicDenomination if self . sats_abs > = Amount ::ONE_BTC . to_sat ( ) = > {
2021-11-23 16:40:28 +00:00
fmt_satoshi_in ( self . sats_abs , self . is_negative , f , Denomination ::Bitcoin , true , format_options )
} ,
DisplayStyle ::DynamicDenomination = > {
fmt_satoshi_in ( self . sats_abs , self . is_negative , f , Denomination ::Satoshi , true , format_options )
} ,
}
}
}
#[ derive(Clone, Debug) ]
enum DisplayStyle {
2022-11-15 23:22:16 +00:00
FixedDenomination { denomination : Denomination , show_denomination : bool } ,
2021-11-23 16:40:28 +00:00
DynamicDenomination ,
}
2018-11-10 00:45:00 +00:00
/// SignedAmount
///
2022-12-19 08:32:52 +00:00
/// The [SignedAmount] type can be used to express Bitcoin amounts that support
2019-08-04 19:27:36 +00:00
/// arithmetic and conversion to various denominations.
2018-11-10 00:45:00 +00:00
///
///
/// Warning!
///
2021-06-09 10:40:41 +00:00
/// This type implements several arithmetic operations from [core::ops].
2018-11-10 00:45:00 +00:00
/// To prevent errors due to overflow or underflow when using these operations,
/// it is advised to instead use the checked arithmetic methods whose names
2021-06-09 10:40:41 +00:00
/// start with `checked_`. The operations from [core::ops] that [Amount]
2018-11-10 00:45:00 +00:00
/// implements will panic when overflow or underflow occurs.
///
2019-08-05 19:44:32 +00:00
#[ derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash) ]
2018-11-10 00:45:00 +00:00
pub struct SignedAmount ( i64 ) ;
impl SignedAmount {
/// The zero amount.
pub const ZERO : SignedAmount = SignedAmount ( 0 ) ;
/// Exactly one satoshi.
pub const ONE_SAT : SignedAmount = SignedAmount ( 1 ) ;
/// Exactly one bitcoin.
pub const ONE_BTC : SignedAmount = SignedAmount ( 100_000_000 ) ;
2021-12-14 16:54:39 +00:00
/// The maximum value allowed as an amount. Useful for sanity checking.
pub const MAX_MONEY : SignedAmount = SignedAmount ( 21_000_000 * 100_000_000 ) ;
2023-05-02 22:14:44 +00:00
/// The minimum value of an amount.
pub const MIN : SignedAmount = SignedAmount ( i64 ::MIN ) ;
/// The maximum value of an amount.
pub const MAX : SignedAmount = SignedAmount ( i64 ::MAX ) ;
2018-11-10 00:45:00 +00:00
/// Create an [SignedAmount] with satoshi precision and the given number of satoshis.
2022-11-15 23:22:16 +00:00
pub const fn from_sat ( satoshi : i64 ) -> SignedAmount { SignedAmount ( satoshi ) }
2018-11-10 00:45:00 +00:00
2022-01-19 03:05:01 +00:00
/// Gets the number of satoshis in this [`SignedAmount`].
2022-11-15 23:22:16 +00:00
pub fn to_sat ( self ) -> i64 { self . 0 }
2018-11-10 00:45:00 +00:00
/// Convert from a value expressing bitcoins to an [SignedAmount].
pub fn from_btc ( btc : f64 ) -> Result < SignedAmount , ParseAmountError > {
SignedAmount ::from_float_in ( btc , Denomination ::Bitcoin )
}
/// Parse a decimal string as a value in the given denomination.
///
/// Note: This only parses the value string. If you want to parse a value
/// with denomination, use [FromStr].
pub fn from_str_in ( s : & str , denom : Denomination ) -> Result < SignedAmount , ParseAmountError > {
let ( negative , satoshi ) = parse_signed_to_satoshi ( s , denom ) ? ;
2023-05-02 22:16:20 +00:00
if satoshi > i64 ::MAX as u64 {
2018-11-10 00:45:00 +00:00
return Err ( ParseAmountError ::TooBig ) ;
}
Ok ( match negative {
2019-08-05 19:41:07 +00:00
true = > SignedAmount ( - ( satoshi as i64 ) ) ,
2018-11-10 00:45:00 +00:00
false = > SignedAmount ( satoshi as i64 ) ,
} )
}
/// Parses amounts with denomination suffix like they are produced with
2021-05-03 09:41:58 +00:00
/// [Self::to_string_with_denomination] or with [fmt::Display].
2018-11-10 00:45:00 +00:00
/// If you want to parse only the amount without the denomination,
2021-05-03 09:41:58 +00:00
/// use [Self::from_str_in].
2018-11-10 00:45:00 +00:00
pub fn from_str_with_denomination ( s : & str ) -> Result < SignedAmount , ParseAmountError > {
2022-12-31 22:19:56 +00:00
let ( amt , denom ) = split_amount_and_denomination ( s ) ? ;
SignedAmount ::from_str_in ( amt , denom )
2018-11-10 00:45:00 +00:00
}
/// Express this [SignedAmount] as a floating-point value in the given denomination.
///
/// Please be aware of the risk of using floating-point numbers.
2019-08-05 18:52:34 +00:00
pub fn to_float_in ( self , denom : Denomination ) -> f64 {
2018-11-10 00:45:00 +00:00
f64 ::from_str ( & self . to_string_in ( denom ) ) . unwrap ( )
}
2022-01-19 03:05:01 +00:00
/// Express this [`SignedAmount`] as a floating-point value in Bitcoin.
///
/// Equivalent to `to_float_in(Denomination::Bitcoin)`.
///
/// Please be aware of the risk of using floating-point numbers.
2022-11-15 23:22:16 +00:00
pub fn to_btc ( self ) -> f64 { self . to_float_in ( Denomination ::Bitcoin ) }
2018-11-10 00:45:00 +00:00
/// Convert this [SignedAmount] in floating-point notation with a given
/// denomination.
/// Can return error if the amount is too big, too precise or negative.
///
/// Please be aware of the risk of using floating-point numbers.
pub fn from_float_in (
value : f64 ,
denom : Denomination ,
) -> Result < SignedAmount , ParseAmountError > {
// This is inefficient, but the safest way to deal with this. The parsing logic is safe.
// Any performance-critical application should not be dealing with floats.
SignedAmount ::from_str_in ( & value . to_string ( ) , denom )
}
2021-11-23 16:40:28 +00:00
/// Returns the absolute value as satoshis.
///
/// This is the implementation of `unsigned_abs()` copied from `core` to support older MSRV.
2022-11-15 23:22:16 +00:00
fn to_sat_abs ( self ) -> u64 { self . to_sat ( ) . wrapping_abs ( ) as u64 }
2021-11-23 16:40:28 +00:00
/// Create an object that implements [`fmt::Display`] using specified denomination.
pub fn display_in ( self , denomination : Denomination ) -> Display {
Display {
sats_abs : self . to_sat_abs ( ) ,
is_negative : self . is_negative ( ) ,
2022-11-15 23:22:16 +00:00
style : DisplayStyle ::FixedDenomination { denomination , show_denomination : false } ,
2021-11-23 16:40:28 +00:00
}
}
/// Create an object that implements [`fmt::Display`] dynamically selecting denomination.
///
/// This will use BTC for values greater than or equal to 1 BTC and satoshis otherwise. To
/// avoid confusion the denomination is always shown.
pub fn display_dynamic ( self ) -> Display {
Display {
sats_abs : self . to_sat_abs ( ) ,
is_negative : self . is_negative ( ) ,
style : DisplayStyle ::DynamicDenomination ,
}
}
2018-11-10 00:45:00 +00:00
/// Format the value of this [SignedAmount] in the given denomination.
///
/// Does not include the denomination.
2022-11-15 23:10:54 +00:00
#[ rustfmt::skip ]
2020-09-14 14:55:42 +00:00
pub fn fmt_value_in ( self , f : & mut dyn fmt ::Write , denom : Denomination ) -> fmt ::Result {
2021-11-23 16:40:28 +00:00
fmt_satoshi_in ( self . to_sat_abs ( ) , self . is_negative ( ) , f , denom , false , FormatOptions ::default ( ) )
2018-11-10 00:45:00 +00:00
}
/// Get a string number of this [SignedAmount] in the given denomination.
///
/// Does not include the denomination.
2019-08-05 18:52:34 +00:00
pub fn to_string_in ( self , denom : Denomination ) -> String {
2018-11-10 00:45:00 +00:00
let mut buf = String ::new ( ) ;
self . fmt_value_in ( & mut buf , denom ) . unwrap ( ) ;
buf
}
/// Get a formatted string of this [SignedAmount] in the given denomination,
/// suffixed with the abbreviation for the denomination.
2019-08-05 18:52:34 +00:00
pub fn to_string_with_denomination ( self , denom : Denomination ) -> String {
2018-11-10 00:45:00 +00:00
let mut buf = String ::new ( ) ;
self . fmt_value_in ( & mut buf , denom ) . unwrap ( ) ;
write! ( buf , " {} " , denom ) . unwrap ( ) ;
buf
}
2021-06-09 10:40:41 +00:00
// Some arithmetic that doesn't fit in `core::ops` traits.
2018-11-10 00:45:00 +00:00
/// Get the absolute value of this [SignedAmount].
2022-11-15 23:22:16 +00:00
pub fn abs ( self ) -> SignedAmount { SignedAmount ( self . 0. abs ( ) ) }
2018-11-10 00:45:00 +00:00
/// Returns a number representing sign of this [SignedAmount].
///
/// - `0` if the amount is zero
/// - `1` if the amount is positive
/// - `-1` if the amount is negative
2022-11-15 23:22:16 +00:00
pub fn signum ( self ) -> i64 { self . 0. signum ( ) }
2018-11-10 00:45:00 +00:00
/// Returns `true` if this [SignedAmount] is positive and `false` if
/// this [SignedAmount] is zero or negative.
2022-11-15 23:22:16 +00:00
pub fn is_positive ( self ) -> bool { self . 0. is_positive ( ) }
2018-11-10 00:45:00 +00:00
/// Returns `true` if this [SignedAmount] is negative and `false` if
/// this [SignedAmount] is zero or positive.
2022-11-15 23:22:16 +00:00
pub fn is_negative ( self ) -> bool { self . 0. is_negative ( ) }
2020-01-21 17:42:44 +00:00
/// Get the absolute value of this [SignedAmount].
2023-05-02 22:16:20 +00:00
/// Returns [None] if overflow occurred. (`self == MIN`)
2022-11-15 23:22:16 +00:00
pub fn checked_abs ( self ) -> Option < SignedAmount > { self . 0. checked_abs ( ) . map ( SignedAmount ) }
2020-01-21 17:42:44 +00:00
2018-11-10 00:45:00 +00:00
/// Checked addition.
/// Returns [None] if overflow occurred.
pub fn checked_add ( self , rhs : SignedAmount ) -> Option < SignedAmount > {
self . 0. checked_add ( rhs . 0 ) . map ( SignedAmount )
}
/// Checked subtraction.
/// Returns [None] if overflow occurred.
pub fn checked_sub ( self , rhs : SignedAmount ) -> Option < SignedAmount > {
self . 0. checked_sub ( rhs . 0 ) . map ( SignedAmount )
}
/// Checked multiplication.
/// Returns [None] if overflow occurred.
pub fn checked_mul ( self , rhs : i64 ) -> Option < SignedAmount > {
self . 0. checked_mul ( rhs ) . map ( SignedAmount )
}
/// Checked integer division.
/// Be aware that integer division loses the remainder if no exact division
/// can be made.
/// Returns [None] if overflow occurred.
pub fn checked_div ( self , rhs : i64 ) -> Option < SignedAmount > {
self . 0. checked_div ( rhs ) . map ( SignedAmount )
}
/// Checked remainder.
/// Returns [None] if overflow occurred.
pub fn checked_rem ( self , rhs : i64 ) -> Option < SignedAmount > {
self . 0. checked_rem ( rhs ) . map ( SignedAmount )
}
/// Subtraction that doesn't allow negative [SignedAmount]s.
2021-05-03 09:41:58 +00:00
/// Returns [None] if either [self], `rhs` or the result is strictly negative.
2018-11-10 00:45:00 +00:00
pub fn positive_sub ( self , rhs : SignedAmount ) -> Option < SignedAmount > {
if self . is_negative ( ) | | rhs . is_negative ( ) | | rhs > self {
None
} else {
self . checked_sub ( rhs )
}
}
/// Convert to an unsigned amount.
pub fn to_unsigned ( self ) -> Result < Amount , ParseAmountError > {
if self . is_negative ( ) {
Err ( ParseAmountError ::Negative )
} else {
2022-01-19 03:05:01 +00:00
Ok ( Amount ::from_sat ( self . to_sat ( ) as u64 ) )
2018-11-10 00:45:00 +00:00
}
}
}
impl default ::Default for SignedAmount {
2022-11-15 23:22:16 +00:00
fn default ( ) -> Self { SignedAmount ::ZERO }
2018-11-10 00:45:00 +00:00
}
impl fmt ::Debug for SignedAmount {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2022-01-19 03:05:01 +00:00
write! ( f , " SignedAmount({:.8} BTC) " , self . to_btc ( ) )
2018-11-10 00:45:00 +00:00
}
}
// No one should depend on a binding contract for Display for this type.
// Just using Bitcoin denominated string.
impl fmt ::Display for SignedAmount {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
self . fmt_value_in ( f , Denomination ::Bitcoin ) ? ;
write! ( f , " {} " , Denomination ::Bitcoin )
}
}
impl ops ::Add for SignedAmount {
type Output = SignedAmount ;
fn add ( self , rhs : SignedAmount ) -> Self ::Output {
self . checked_add ( rhs ) . expect ( " SignedAmount addition error " )
}
}
impl ops ::AddAssign for SignedAmount {
2022-11-15 23:22:16 +00:00
fn add_assign ( & mut self , other : SignedAmount ) { * self = * self + other }
2018-11-10 00:45:00 +00:00
}
impl ops ::Sub for SignedAmount {
type Output = SignedAmount ;
fn sub ( self , rhs : SignedAmount ) -> Self ::Output {
self . checked_sub ( rhs ) . expect ( " SignedAmount subtraction error " )
}
}
impl ops ::SubAssign for SignedAmount {
2022-11-15 23:22:16 +00:00
fn sub_assign ( & mut self , other : SignedAmount ) { * self = * self - other }
2018-11-10 00:45:00 +00:00
}
impl ops ::Rem < i64 > for SignedAmount {
type Output = SignedAmount ;
fn rem ( self , modulus : i64 ) -> Self {
self . checked_rem ( modulus ) . expect ( " SignedAmount remainder error " )
}
}
impl ops ::RemAssign < i64 > for SignedAmount {
2022-11-15 23:22:16 +00:00
fn rem_assign ( & mut self , modulus : i64 ) { * self = * self % modulus }
2018-11-10 00:45:00 +00:00
}
impl ops ::Mul < i64 > for SignedAmount {
type Output = SignedAmount ;
fn mul ( self , rhs : i64 ) -> Self ::Output {
self . checked_mul ( rhs ) . expect ( " SignedAmount multiplication error " )
}
}
impl ops ::MulAssign < i64 > for SignedAmount {
2022-11-15 23:22:16 +00:00
fn mul_assign ( & mut self , rhs : i64 ) { * self = * self * rhs }
2018-11-10 00:45:00 +00:00
}
impl ops ::Div < i64 > for SignedAmount {
type Output = SignedAmount ;
fn div ( self , rhs : i64 ) -> Self ::Output {
self . checked_div ( rhs ) . expect ( " SignedAmount division error " )
}
}
impl ops ::DivAssign < i64 > for SignedAmount {
2022-11-15 23:22:16 +00:00
fn div_assign ( & mut self , rhs : i64 ) { * self = * self / rhs }
2018-11-10 00:45:00 +00:00
}
impl FromStr for SignedAmount {
type Err = ParseAmountError ;
2022-11-15 23:22:16 +00:00
fn from_str ( s : & str ) -> Result < Self , Self ::Err > { SignedAmount ::from_str_with_denomination ( s ) }
2018-11-10 00:45:00 +00:00
}
2022-06-01 22:08:56 +00:00
impl core ::iter ::Sum for SignedAmount {
2022-01-24 00:26:29 +00:00
fn sum < I : Iterator < Item = Self > > ( iter : I ) -> Self {
2021-06-12 14:46:04 +00:00
let sats : i64 = iter . map ( | amt | amt . 0 ) . sum ( ) ;
SignedAmount ::from_sat ( sats )
}
}
2021-06-12 15:09:49 +00:00
/// Calculate the sum over the iterator using checked arithmetic.
2021-06-23 12:18:42 +00:00
pub trait CheckedSum < R > : private ::SumSeal < R > {
2021-06-12 15:09:49 +00:00
/// Calculate the sum over the iterator using checked arithmetic. If an over or underflow would
/// happen it returns `None`.
fn checked_sum ( self ) -> Option < R > ;
}
2022-11-15 23:22:16 +00:00
impl < T > CheckedSum < Amount > for T
where
T : Iterator < Item = Amount > ,
{
2021-06-12 15:09:49 +00:00
fn checked_sum ( mut self ) -> Option < Amount > {
let first = Some ( self . next ( ) . unwrap_or_default ( ) ) ;
2022-11-15 23:22:16 +00:00
self . fold ( first , | acc , item | acc . and_then ( | acc | acc . checked_add ( item ) ) )
2021-06-12 15:09:49 +00:00
}
}
2022-11-15 23:22:16 +00:00
impl < T > CheckedSum < SignedAmount > for T
where
T : Iterator < Item = SignedAmount > ,
{
2021-06-12 15:09:49 +00:00
fn checked_sum ( mut self ) -> Option < SignedAmount > {
let first = Some ( self . next ( ) . unwrap_or_default ( ) ) ;
2022-01-24 00:31:39 +00:00
self . fold ( first , | acc , item | acc . and_then ( | acc | acc . checked_add ( item ) ) )
2021-06-12 15:09:49 +00:00
}
}
2021-06-23 12:18:42 +00:00
mod private {
2022-05-02 22:13:57 +00:00
use crate ::{ Amount , SignedAmount } ;
2021-06-23 12:18:42 +00:00
/// Used to seal the `CheckedSum` trait
pub trait SumSeal < A > { }
2022-11-15 23:22:16 +00:00
impl < T > SumSeal < Amount > for T where T : Iterator < Item = Amount > { }
impl < T > SumSeal < SignedAmount > for T where T : Iterator < Item = SignedAmount > { }
2021-06-23 12:18:42 +00:00
}
2018-11-10 00:45:00 +00:00
#[ cfg(feature = " serde " ) ]
pub mod serde {
// methods are implementation of a standardized serde-specific signature
#![ allow(missing_docs) ]
//! This module adds serde serialization and deserialization support for Amounts.
//! Since there is not a default way to serialize and deserialize Amounts, multiple
//! ways are supported and it's up to the user to decide which serialiation to use.
//! The provided modules can be used as follows:
//!
//! ```rust,ignore
//! use serde::{Serialize, Deserialize};
2023-08-08 08:47:05 +00:00
//! use bitcoin_units::Amount;
2018-11-10 00:45:00 +00:00
//!
//! #[derive(Serialize, Deserialize)]
//! pub struct HasAmount {
2023-08-08 08:47:05 +00:00
//! #[serde(with = "bitcoin_units::amount::serde::as_btc")]
2018-11-10 00:45:00 +00:00
//! pub amount: Amount,
//! }
//! ```
use serde ::{ Deserialize , Deserializer , Serialize , Serializer } ;
2022-11-15 23:22:16 +00:00
2022-10-20 22:12:44 +00:00
use crate ::amount ::{ Amount , Denomination , SignedAmount } ;
2018-11-10 00:45:00 +00:00
/// This trait is used only to avoid code duplication and naming collisions
/// of the different serde serialization crates.
2022-09-01 06:52:29 +00:00
pub trait SerdeAmount : Copy + Sized + private ::Sealed {
2018-11-10 00:45:00 +00:00
fn ser_sat < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > ;
fn des_sat < ' d , D : Deserializer < ' d > > ( d : D ) -> Result < Self , D ::Error > ;
fn ser_btc < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > ;
fn des_btc < ' d , D : Deserializer < ' d > > ( d : D ) -> Result < Self , D ::Error > ;
}
2021-02-28 17:13:46 +00:00
mod private {
/// add this as a trait bound to traits which consumers of this library
/// should not be able to implement.
pub trait Sealed { }
impl Sealed for super ::Amount { }
impl Sealed for super ::SignedAmount { }
}
/// This trait is only for internal Amount type serialization/deserialization
pub trait SerdeAmountForOpt : Copy + Sized + SerdeAmount + private ::Sealed {
2021-01-14 18:45:51 +00:00
fn type_prefix ( ) -> & 'static str ;
fn ser_sat_opt < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > ;
fn ser_btc_opt < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > ;
2018-11-10 00:45:00 +00:00
}
impl SerdeAmount for Amount {
fn ser_sat < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
2022-01-19 03:05:01 +00:00
u64 ::serialize ( & self . to_sat ( ) , s )
2018-11-10 00:45:00 +00:00
}
fn des_sat < ' d , D : Deserializer < ' d > > ( d : D ) -> Result < Self , D ::Error > {
Ok ( Amount ::from_sat ( u64 ::deserialize ( d ) ? ) )
}
fn ser_btc < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
f64 ::serialize ( & self . to_float_in ( Denomination ::Bitcoin ) , s )
}
fn des_btc < ' d , D : Deserializer < ' d > > ( d : D ) -> Result < Self , D ::Error > {
use serde ::de ::Error ;
2022-06-23 03:58:29 +00:00
Amount ::from_btc ( f64 ::deserialize ( d ) ? ) . map_err ( D ::Error ::custom )
2018-11-10 00:45:00 +00:00
}
}
2021-02-28 17:13:46 +00:00
impl SerdeAmountForOpt for Amount {
2022-11-15 23:22:16 +00:00
fn type_prefix ( ) -> & 'static str { " u " }
2021-01-14 18:45:51 +00:00
fn ser_sat_opt < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
2022-01-19 03:05:01 +00:00
s . serialize_some ( & self . to_sat ( ) )
2021-01-14 18:45:51 +00:00
}
fn ser_btc_opt < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
2022-01-19 03:05:01 +00:00
s . serialize_some ( & self . to_btc ( ) )
2021-01-14 18:45:51 +00:00
}
2018-11-10 00:45:00 +00:00
}
impl SerdeAmount for SignedAmount {
fn ser_sat < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
2022-01-19 03:05:01 +00:00
i64 ::serialize ( & self . to_sat ( ) , s )
2018-11-10 00:45:00 +00:00
}
fn des_sat < ' d , D : Deserializer < ' d > > ( d : D ) -> Result < Self , D ::Error > {
Ok ( SignedAmount ::from_sat ( i64 ::deserialize ( d ) ? ) )
}
fn ser_btc < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
f64 ::serialize ( & self . to_float_in ( Denomination ::Bitcoin ) , s )
}
fn des_btc < ' d , D : Deserializer < ' d > > ( d : D ) -> Result < Self , D ::Error > {
use serde ::de ::Error ;
2022-06-23 03:58:29 +00:00
SignedAmount ::from_btc ( f64 ::deserialize ( d ) ? ) . map_err ( D ::Error ::custom )
2018-11-10 00:45:00 +00:00
}
}
2021-02-28 17:13:46 +00:00
impl SerdeAmountForOpt for SignedAmount {
2022-11-15 23:22:16 +00:00
fn type_prefix ( ) -> & 'static str { " i " }
2021-01-14 18:45:51 +00:00
fn ser_sat_opt < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
2022-01-19 03:05:01 +00:00
s . serialize_some ( & self . to_sat ( ) )
2021-01-14 18:45:51 +00:00
}
fn ser_btc_opt < S : Serializer > ( self , s : S ) -> Result < S ::Ok , S ::Error > {
2022-01-19 03:05:01 +00:00
s . serialize_some ( & self . to_btc ( ) )
2021-01-14 18:45:51 +00:00
}
2018-11-10 00:45:00 +00:00
}
pub mod as_sat {
2021-07-28 19:38:58 +00:00
//! Serialize and deserialize [`Amount`](crate::Amount) as real numbers denominated in satoshi.
2018-11-10 00:45:00 +00:00
//! Use with `#[serde(with = "amount::serde::as_sat")]`.
2019-08-18 16:26:29 +00:00
use serde ::{ Deserializer , Serializer } ;
2022-11-15 23:22:16 +00:00
2022-10-20 22:12:44 +00:00
use crate ::amount ::serde ::SerdeAmount ;
2018-11-10 00:45:00 +00:00
pub fn serialize < A : SerdeAmount , S : Serializer > ( a : & A , s : S ) -> Result < S ::Ok , S ::Error > {
a . ser_sat ( s )
}
pub fn deserialize < ' d , A : SerdeAmount , D : Deserializer < ' d > > ( d : D ) -> Result < A , D ::Error > {
A ::des_sat ( d )
}
pub mod opt {
2021-07-28 19:38:58 +00:00
//! Serialize and deserialize [`Option<Amount>`](crate::Amount) as real numbers denominated in satoshi.
2018-11-10 00:45:00 +00:00
//! Use with `#[serde(default, with = "amount::serde::as_sat::opt")]`.
2021-06-09 10:34:44 +00:00
use core ::fmt ;
use core ::marker ::PhantomData ;
2018-11-10 00:45:00 +00:00
2022-11-15 23:22:16 +00:00
use serde ::{ de , Deserializer , Serializer } ;
use crate ::amount ::serde ::SerdeAmountForOpt ;
2021-02-28 17:13:46 +00:00
pub fn serialize < A : SerdeAmountForOpt , S : Serializer > (
2018-11-10 00:45:00 +00:00
a : & Option < A > ,
s : S ,
) -> Result < S ::Ok , S ::Error > {
match * a {
2021-01-14 18:45:51 +00:00
Some ( a ) = > a . ser_sat_opt ( s ) ,
2018-11-10 00:45:00 +00:00
None = > s . serialize_none ( ) ,
}
}
2021-02-28 17:13:46 +00:00
pub fn deserialize < ' d , A : SerdeAmountForOpt , D : Deserializer < ' d > > (
2018-11-10 00:45:00 +00:00
d : D ,
) -> Result < Option < A > , D ::Error > {
2021-01-14 18:45:51 +00:00
struct VisitOptAmt < X > ( PhantomData < X > ) ;
2021-02-28 17:13:46 +00:00
impl < ' de , X : SerdeAmountForOpt > de ::Visitor < ' de > for VisitOptAmt < X > {
2021-01-14 18:45:51 +00:00
type Value = Option < X > ;
fn expecting ( & self , formatter : & mut fmt ::Formatter ) -> fmt ::Result {
write! ( formatter , " An Option<{}64> " , X ::type_prefix ( ) )
}
fn visit_none < E > ( self ) -> Result < Self ::Value , E >
where
2022-01-24 00:33:03 +00:00
E : de ::Error ,
{
2021-01-14 18:45:51 +00:00
Ok ( None )
}
fn visit_some < D > ( self , d : D ) -> Result < Self ::Value , D ::Error >
where
2022-01-24 00:33:03 +00:00
D : Deserializer < ' de > ,
2021-01-14 18:45:51 +00:00
{
Ok ( Some ( X ::des_sat ( d ) ? ) )
}
}
d . deserialize_option ( VisitOptAmt ::< A > ( PhantomData ) )
2018-11-10 00:45:00 +00:00
}
}
}
pub mod as_btc {
2021-07-28 19:38:58 +00:00
//! Serialize and deserialize [`Amount`](crate::Amount) as JSON numbers denominated in BTC.
2018-11-10 00:45:00 +00:00
//! Use with `#[serde(with = "amount::serde::as_btc")]`.
2019-08-18 16:26:29 +00:00
use serde ::{ Deserializer , Serializer } ;
2022-11-15 23:22:16 +00:00
2022-10-20 22:12:44 +00:00
use crate ::amount ::serde ::SerdeAmount ;
2018-11-10 00:45:00 +00:00
pub fn serialize < A : SerdeAmount , S : Serializer > ( a : & A , s : S ) -> Result < S ::Ok , S ::Error > {
a . ser_btc ( s )
}
pub fn deserialize < ' d , A : SerdeAmount , D : Deserializer < ' d > > ( d : D ) -> Result < A , D ::Error > {
A ::des_btc ( d )
}
pub mod opt {
2022-11-20 22:27:16 +00:00
//! Serialize and deserialize `Option<Amount>` as JSON numbers denominated in BTC.
2018-11-10 00:45:00 +00:00
//! Use with `#[serde(default, with = "amount::serde::as_btc::opt")]`.
2021-06-09 10:34:44 +00:00
use core ::fmt ;
use core ::marker ::PhantomData ;
2018-11-10 00:45:00 +00:00
2022-11-15 23:22:16 +00:00
use serde ::{ de , Deserializer , Serializer } ;
use crate ::amount ::serde ::SerdeAmountForOpt ;
2021-02-28 17:13:46 +00:00
pub fn serialize < A : SerdeAmountForOpt , S : Serializer > (
2018-11-10 00:45:00 +00:00
a : & Option < A > ,
s : S ,
) -> Result < S ::Ok , S ::Error > {
match * a {
2021-01-14 18:45:51 +00:00
Some ( a ) = > a . ser_btc_opt ( s ) ,
2018-11-10 00:45:00 +00:00
None = > s . serialize_none ( ) ,
}
}
2021-02-28 17:13:46 +00:00
pub fn deserialize < ' d , A : SerdeAmountForOpt , D : Deserializer < ' d > > (
2018-11-10 00:45:00 +00:00
d : D ,
) -> Result < Option < A > , D ::Error > {
2021-01-14 18:45:51 +00:00
struct VisitOptAmt < X > ( PhantomData < X > ) ;
2022-01-24 00:26:29 +00:00
impl < ' de , X : SerdeAmountForOpt > de ::Visitor < ' de > for VisitOptAmt < X > {
2021-01-14 18:45:51 +00:00
type Value = Option < X > ;
fn expecting ( & self , formatter : & mut fmt ::Formatter ) -> fmt ::Result {
write! ( formatter , " An Option<f64> " )
}
fn visit_none < E > ( self ) -> Result < Self ::Value , E >
where
2022-01-24 00:33:03 +00:00
E : de ::Error ,
{
2021-01-14 18:45:51 +00:00
Ok ( None )
}
fn visit_some < D > ( self , d : D ) -> Result < Self ::Value , D ::Error >
where
D : Deserializer < ' de > ,
{
Ok ( Some ( X ::des_btc ( d ) ? ) )
}
}
d . deserialize_option ( VisitOptAmt ::< A > ( PhantomData ) )
2018-11-10 00:45:00 +00:00
}
}
}
}
2022-11-27 18:08:28 +00:00
#[ cfg(kani) ]
mod verification {
use std ::cmp ;
use std ::convert ::TryInto ;
2022-11-18 03:06:57 +00:00
2022-11-27 18:08:28 +00:00
use super ::* ;
// Note regarding the `unwind` parameter: this defines how many iterations
// of loops kani will unwind before handing off to the SMT solver. Basically
// it should be set as low as possible such that Kani still succeeds (doesn't
// return "undecidable").
//
// There is more info here: https://model-checking.github.io/kani/tutorial-loop-unwinding.html
//
// Unfortunately what it means to "loop" is pretty opaque ... in this case
// there appear to be loops in memcmp, which I guess comes from assert_eq!,
// though I didn't see any failures until I added the to_signed() test.
// Further confusing the issue, a value of 2 works fine on my system, but on
// CI it fails, so we need to set it higher.
#[ kani::unwind(4) ]
#[ kani::proof ]
fn u_amount_add_homomorphic ( ) {
let n1 = kani ::any ::< u64 > ( ) ;
let n2 = kani ::any ::< u64 > ( ) ;
kani ::assume ( n1 . checked_add ( n2 ) . is_some ( ) ) ; // assume we don't overflow in the actual test
assert_eq! ( Amount ::from_sat ( n1 ) + Amount ::from_sat ( n2 ) , Amount ::from_sat ( n1 + n2 ) ) ;
let mut amt = Amount ::from_sat ( n1 ) ;
amt + = Amount ::from_sat ( n2 ) ;
assert_eq! ( amt , Amount ::from_sat ( n1 + n2 ) ) ;
let max = cmp ::max ( n1 , n2 ) ;
let min = cmp ::min ( n1 , n2 ) ;
assert_eq! ( Amount ::from_sat ( max ) - Amount ::from_sat ( min ) , Amount ::from_sat ( max - min ) ) ;
let mut amt = Amount ::from_sat ( max ) ;
amt - = Amount ::from_sat ( min ) ;
assert_eq! ( amt , Amount ::from_sat ( max - min ) ) ;
assert_eq! (
Amount ::from_sat ( n1 ) . to_signed ( ) ,
if n1 < = i64 ::MAX as u64 {
Ok ( SignedAmount ::from_sat ( n1 . try_into ( ) . unwrap ( ) ) )
} else {
Err ( ParseAmountError ::TooBig )
} ,
) ;
}
#[ kani::unwind(4) ]
#[ kani::proof ]
fn u_amount_add_homomorphic_checked ( ) {
let n1 = kani ::any ::< u64 > ( ) ;
let n2 = kani ::any ::< u64 > ( ) ;
assert_eq! (
Amount ::from_sat ( n1 ) . checked_add ( Amount ::from_sat ( n2 ) ) ,
n1 . checked_add ( n2 ) . map ( Amount ::from_sat ) ,
) ;
assert_eq! (
Amount ::from_sat ( n1 ) . checked_sub ( Amount ::from_sat ( n2 ) ) ,
n1 . checked_sub ( n2 ) . map ( Amount ::from_sat ) ,
) ;
}
#[ kani::unwind(4) ]
#[ kani::proof ]
fn s_amount_add_homomorphic ( ) {
let n1 = kani ::any ::< i64 > ( ) ;
let n2 = kani ::any ::< i64 > ( ) ;
kani ::assume ( n1 . checked_add ( n2 ) . is_some ( ) ) ; // assume we don't overflow in the actual test
kani ::assume ( n1 . checked_sub ( n2 ) . is_some ( ) ) ; // assume we don't overflow in the actual test
2022-10-19 16:51:41 +00:00
assert_eq! (
SignedAmount ::from_sat ( n1 ) + SignedAmount ::from_sat ( n2 ) ,
SignedAmount ::from_sat ( n1 + n2 )
) ;
assert_eq! (
SignedAmount ::from_sat ( n1 ) - SignedAmount ::from_sat ( n2 ) ,
SignedAmount ::from_sat ( n1 - n2 )
) ;
2022-11-27 18:08:28 +00:00
let mut amt = SignedAmount ::from_sat ( n1 ) ;
amt + = SignedAmount ::from_sat ( n2 ) ;
assert_eq! ( amt , SignedAmount ::from_sat ( n1 + n2 ) ) ;
let mut amt = SignedAmount ::from_sat ( n1 ) ;
amt - = SignedAmount ::from_sat ( n2 ) ;
assert_eq! ( amt , SignedAmount ::from_sat ( n1 - n2 ) ) ;
assert_eq! (
SignedAmount ::from_sat ( n1 ) . to_unsigned ( ) ,
if n1 > = 0 {
Ok ( Amount ::from_sat ( n1 . try_into ( ) . unwrap ( ) ) )
} else {
Err ( ParseAmountError ::Negative )
} ,
) ;
}
#[ kani::unwind(4) ]
#[ kani::proof ]
fn s_amount_add_homomorphic_checked ( ) {
let n1 = kani ::any ::< i64 > ( ) ;
let n2 = kani ::any ::< i64 > ( ) ;
assert_eq! (
SignedAmount ::from_sat ( n1 ) . checked_add ( SignedAmount ::from_sat ( n2 ) ) ,
n1 . checked_add ( n2 ) . map ( SignedAmount ::from_sat ) ,
) ;
assert_eq! (
SignedAmount ::from_sat ( n1 ) . checked_sub ( SignedAmount ::from_sat ( n2 ) ) ,
n1 . checked_sub ( n2 ) . map ( SignedAmount ::from_sat ) ,
) ;
assert_eq! (
SignedAmount ::from_sat ( n1 ) . positive_sub ( SignedAmount ::from_sat ( n2 ) ) ,
2022-10-19 16:51:41 +00:00
if n1 > = 0 & & n2 > = 0 & & n1 > = n2 {
Some ( SignedAmount ::from_sat ( n1 - n2 ) )
} else {
None
} ,
2022-11-27 18:08:28 +00:00
) ;
}
}
2018-11-10 00:45:00 +00:00
#[ cfg(test) ]
mod tests {
2022-11-15 23:22:16 +00:00
use core ::str ::FromStr ;
2021-06-09 10:34:44 +00:00
#[ cfg(feature = " std " ) ]
2018-11-10 00:45:00 +00:00
use std ::panic ;
#[ cfg(feature = " serde " ) ]
use serde_test ;
2022-11-15 23:22:16 +00:00
use super ::* ;
2023-01-12 06:58:02 +00:00
#[ test ]
fn from_str_zero ( ) {
let denoms = vec! [ " BTC " , " mBTC " , " uBTC " , " nBTC " , " pBTC " , " bits " , " sats " , " msats " ] ;
for denom in denoms {
for v in & [ " 0 " , " 000 " ] {
let s = format! ( " {} {} " , v , denom ) ;
match Amount ::from_str ( & s ) {
Err ( e ) = > panic! ( " Failed to crate amount from {} : {:?} " , s , e ) ,
Ok ( amount ) = > assert_eq! ( amount , Amount ::from_sat ( 0 ) ) ,
}
}
let s = format! ( " -0 {} " , denom ) ;
match Amount ::from_str ( & s ) {
Err ( e ) = > assert_eq! ( e , ParseAmountError ::Negative ) ,
Ok ( _ ) = > panic! ( " Unsigned amount from {} " , s ) ,
}
match SignedAmount ::from_str ( & s ) {
Err ( e ) = > panic! ( " Failed to crate amount from {} : {:?} " , s , e ) ,
Ok ( amount ) = > assert_eq! ( amount , SignedAmount ::from_sat ( 0 ) ) ,
}
}
}
2023-06-13 08:55:04 +00:00
#[ test ]
fn from_int_btc ( ) {
let amt = Amount ::from_int_btc ( 2 ) ;
assert_eq! ( Amount ::from_sat ( 200_000_000 ) , amt ) ;
}
#[ should_panic ]
#[ test ]
fn from_int_btc_panic ( ) { Amount ::from_int_btc ( u64 ::MAX ) ; }
2018-11-10 00:45:00 +00:00
#[ test ]
2022-11-27 18:08:28 +00:00
fn mul_div ( ) {
2018-11-10 00:45:00 +00:00
let sat = Amount ::from_sat ;
let ssat = SignedAmount ::from_sat ;
assert_eq! ( sat ( 14 ) * 3 , sat ( 42 ) ) ;
assert_eq! ( sat ( 14 ) / 2 , sat ( 7 ) ) ;
assert_eq! ( sat ( 14 ) % 3 , sat ( 2 ) ) ;
assert_eq! ( ssat ( - 14 ) * 3 , ssat ( - 42 ) ) ;
assert_eq! ( ssat ( - 14 ) / 2 , ssat ( - 7 ) ) ;
assert_eq! ( ssat ( - 14 ) % 3 , ssat ( - 2 ) ) ;
2022-11-27 18:08:28 +00:00
let mut b = ssat ( 30 ) ;
2018-11-10 00:45:00 +00:00
b / = 3 ;
assert_eq! ( b , ssat ( 10 ) ) ;
b % = 3 ;
assert_eq! ( b , ssat ( 1 ) ) ;
2021-06-09 10:34:44 +00:00
}
2018-11-10 00:45:00 +00:00
2021-06-09 10:34:44 +00:00
#[ cfg(feature = " std " ) ]
#[ test ]
fn test_overflows ( ) {
2018-11-10 00:45:00 +00:00
// panic on overflow
2023-05-02 22:16:20 +00:00
let result = panic ::catch_unwind ( | | Amount ::MAX + Amount ::from_sat ( 1 ) ) ;
2018-11-10 00:45:00 +00:00
assert! ( result . is_err ( ) ) ;
let result = panic ::catch_unwind ( | | Amount ::from_sat ( 8446744073709551615 ) * 3 ) ;
assert! ( result . is_err ( ) ) ;
}
#[ test ]
fn checked_arithmetic ( ) {
let sat = Amount ::from_sat ;
let ssat = SignedAmount ::from_sat ;
2023-05-02 22:16:20 +00:00
assert_eq! ( SignedAmount ::MAX . checked_add ( ssat ( 1 ) ) , None ) ;
assert_eq! ( SignedAmount ::MIN . checked_sub ( ssat ( 1 ) ) , None ) ;
assert_eq! ( Amount ::MAX . checked_add ( sat ( 1 ) ) , None ) ;
assert_eq! ( Amount ::MIN . checked_sub ( sat ( 1 ) ) , None ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( sat ( 5 ) . checked_div ( 2 ) , Some ( sat ( 2 ) ) ) ; // integer division
assert_eq! ( ssat ( - 6 ) . checked_div ( 2 ) , Some ( ssat ( - 3 ) ) ) ;
}
#[ test ]
fn floating_point ( ) {
use super ::Denomination as D ;
let f = Amount ::from_float_in ;
let sf = SignedAmount ::from_float_in ;
let sat = Amount ::from_sat ;
let ssat = SignedAmount ::from_sat ;
assert_eq! ( f ( 11.22 , D ::Bitcoin ) , Ok ( sat ( 1122000000 ) ) ) ;
assert_eq! ( sf ( - 11.22 , D ::MilliBitcoin ) , Ok ( ssat ( - 1122000 ) ) ) ;
assert_eq! ( f ( 11.22 , D ::Bit ) , Ok ( sat ( 1122 ) ) ) ;
assert_eq! ( sf ( - 1000.0 , D ::MilliSatoshi ) , Ok ( ssat ( - 1 ) ) ) ;
assert_eq! ( f ( 0.0001234 , D ::Bitcoin ) , Ok ( sat ( 12340 ) ) ) ;
assert_eq! ( sf ( - 0.00012345 , D ::Bitcoin ) , Ok ( ssat ( - 12345 ) ) ) ;
assert_eq! ( f ( - 100.0 , D ::MilliSatoshi ) , Err ( ParseAmountError ::Negative ) ) ;
assert_eq! ( f ( 11.22 , D ::Satoshi ) , Err ( ParseAmountError ::TooPrecise ) ) ;
assert_eq! ( sf ( - 100.0 , D ::MilliSatoshi ) , Err ( ParseAmountError ::TooPrecise ) ) ;
assert_eq! ( sf ( - 100.0 , D ::MilliSatoshi ) , Err ( ParseAmountError ::TooPrecise ) ) ;
assert_eq! ( f ( 42.123456781 , D ::Bitcoin ) , Err ( ParseAmountError ::TooPrecise ) ) ;
assert_eq! ( sf ( - 184467440738.0 , D ::Bitcoin ) , Err ( ParseAmountError ::TooBig ) ) ;
assert_eq! ( f ( 18446744073709551617.0 , D ::Satoshi ) , Err ( ParseAmountError ::TooBig ) ) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
f ( SignedAmount ::MAX . to_float_in ( D ::Satoshi ) + 1.0 , D ::Satoshi ) ,
2018-11-10 00:45:00 +00:00
Err ( ParseAmountError ::TooBig )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
f ( Amount ::MAX . to_float_in ( D ::Satoshi ) + 1.0 , D ::Satoshi ) ,
2018-11-10 00:45:00 +00:00
Err ( ParseAmountError ::TooBig )
) ;
let btc = move | f | SignedAmount ::from_btc ( f ) . unwrap ( ) ;
assert_eq! ( btc ( 2.5 ) . to_float_in ( D ::Bitcoin ) , 2.5 ) ;
assert_eq! ( btc ( - 2.5 ) . to_float_in ( D ::MilliBitcoin ) , - 2500.0 ) ;
assert_eq! ( btc ( 2.5 ) . to_float_in ( D ::Satoshi ) , 250000000.0 ) ;
assert_eq! ( btc ( - 2.5 ) . to_float_in ( D ::MilliSatoshi ) , - 250000000000.0 ) ;
let btc = move | f | Amount ::from_btc ( f ) . unwrap ( ) ;
assert_eq! ( & btc ( 0.0012 ) . to_float_in ( D ::Bitcoin ) . to_string ( ) , " 0.0012 " )
}
#[ test ]
2022-06-07 04:29:15 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn parsing ( ) {
use super ::ParseAmountError as E ;
let btc = Denomination ::Bitcoin ;
2020-01-21 17:43:10 +00:00
let sat = Denomination ::Satoshi ;
2023-04-01 10:32:15 +00:00
let msat = Denomination ::MilliSatoshi ;
2018-11-10 00:45:00 +00:00
let p = Amount ::from_str_in ;
let sp = SignedAmount ::from_str_in ;
assert_eq! ( p ( " x " , btc ) , Err ( E ::InvalidCharacter ( 'x' ) ) ) ;
assert_eq! ( p ( " - " , btc ) , Err ( E ::InvalidFormat ) ) ;
assert_eq! ( sp ( " - " , btc ) , Err ( E ::InvalidFormat ) ) ;
assert_eq! ( p ( " -1.0x " , btc ) , Err ( E ::InvalidCharacter ( 'x' ) ) ) ;
assert_eq! ( p ( " 0.0 " , btc ) , Err ( ParseAmountError ::InvalidCharacter ( ' ' ) ) ) ;
assert_eq! ( p ( " 0.000.000 " , btc ) , Err ( E ::InvalidFormat ) ) ;
2023-05-02 22:16:20 +00:00
let more_than_max = format! ( " 1 {} " , Amount ::MAX ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( p ( & more_than_max , btc ) , Err ( E ::TooBig ) ) ;
assert_eq! ( p ( " 0.000000042 " , btc ) , Err ( E ::TooPrecise ) ) ;
2023-04-01 10:32:15 +00:00
assert_eq! ( p ( " 999.0000000 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 1.0000000 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 1.1 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 1000.1 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 1001.0000000 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 1000.0000001 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 1000.1000000 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 1100.0000000 " , msat ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 10001.0000000 " , msat ) , Err ( E ::TooPrecise ) ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( p ( " 1 " , btc ) , Ok ( Amount ::from_sat ( 1_000_000_00 ) ) ) ;
assert_eq! ( sp ( " -.5 " , btc ) , Ok ( SignedAmount ::from_sat ( - 500_000_00 ) ) ) ;
assert_eq! ( p ( " 1.1 " , btc ) , Ok ( Amount ::from_sat ( 1_100_000_00 ) ) ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( p ( " 100 " , sat ) , Ok ( Amount ::from_sat ( 100 ) ) ) ;
assert_eq! ( p ( " 55 " , sat ) , Ok ( Amount ::from_sat ( 55 ) ) ) ;
2022-06-23 03:54:34 +00:00
assert_eq! ( p ( " 5500000000000000000 " , sat ) , Ok ( Amount ::from_sat ( 55_000_000_000_000_000_00 ) ) ) ;
2020-01-21 17:43:10 +00:00
// Should this even pass?
2022-06-23 03:54:34 +00:00
assert_eq! ( p ( " 5500000000000000000. " , sat ) , Ok ( Amount ::from_sat ( 55_000_000_000_000_000_00 ) ) ) ;
2018-11-10 00:45:00 +00:00
assert_eq! (
p ( " 12345678901.12345678 " , btc ) ,
Ok ( Amount ::from_sat ( 12_345_678_901__123_456_78 ) )
) ;
2023-05-07 06:47:53 +00:00
assert_eq! ( p ( " 1000.0 " , msat ) , Ok ( Amount ::from_sat ( 1 ) ) ) ;
assert_eq! ( p ( " 1000.000000000000000000000000000 " , msat ) , Ok ( Amount ::from_sat ( 1 ) ) ) ;
2020-01-21 17:43:10 +00:00
2023-05-02 22:16:20 +00:00
// make sure satoshi > i64::MAX is checked.
let amount = Amount ::from_sat ( i64 ::MAX as u64 ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( Amount ::from_str_in ( & amount . to_string_in ( sat ) , sat ) , Ok ( amount ) ) ;
2022-11-15 23:22:16 +00:00
assert_eq! (
Amount ::from_str_in ( & ( amount + Amount ( 1 ) ) . to_string_in ( sat ) , sat ) ,
Err ( E ::TooBig )
) ;
2020-01-21 17:43:10 +00:00
2018-11-10 00:45:00 +00:00
assert_eq! ( p ( " 12.000 " , Denomination ::MilliSatoshi ) , Err ( E ::TooPrecise ) ) ;
2020-01-21 17:43:10 +00:00
// exactly 50 chars.
2022-11-15 23:22:16 +00:00
assert_eq! (
p ( " 100000000000000.0000000000000000000000000000000000 " , Denomination ::Bitcoin ) ,
Err ( E ::TooBig )
) ;
2020-01-21 17:43:10 +00:00
// more than 50 chars.
2022-11-15 23:22:16 +00:00
assert_eq! (
p ( " 100000000000000.00000000000000000000000000000000000 " , Denomination ::Bitcoin ) ,
Err ( E ::InputTooLarge )
) ;
2018-11-10 00:45:00 +00:00
}
#[ test ]
fn to_string ( ) {
use super ::Denomination as D ;
2021-11-23 16:40:28 +00:00
assert_eq! ( Amount ::ONE_BTC . to_string_in ( D ::Bitcoin ) , " 1 " ) ;
assert_eq! ( format! ( " {:.8} " , Amount ::ONE_BTC . display_in ( D ::Bitcoin ) ) , " 1.00000000 " ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( Amount ::ONE_BTC . to_string_in ( D ::Satoshi ) , " 100000000 " ) ;
assert_eq! ( Amount ::ONE_SAT . to_string_in ( D ::Bitcoin ) , " 0.00000001 " ) ;
assert_eq! ( SignedAmount ::from_sat ( - 42 ) . to_string_in ( D ::Bitcoin ) , " -0.00000042 " ) ;
2021-11-23 16:40:28 +00:00
assert_eq! ( Amount ::ONE_BTC . to_string_with_denomination ( D ::Bitcoin ) , " 1 BTC " ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( Amount ::ONE_SAT . to_string_with_denomination ( D ::MilliSatoshi ) , " 1000 msat " ) ;
assert_eq! (
SignedAmount ::ONE_BTC . to_string_with_denomination ( D ::Satoshi ) ,
" 100000000 satoshi "
) ;
assert_eq! ( Amount ::ONE_SAT . to_string_with_denomination ( D ::Bitcoin ) , " 0.00000001 BTC " ) ;
assert_eq! (
SignedAmount ::from_sat ( - 42 ) . to_string_with_denomination ( D ::Bitcoin ) ,
" -0.00000042 BTC "
) ;
}
2021-11-23 16:40:28 +00:00
// May help identify a problem sooner
#[ test ]
fn test_repeat_char ( ) {
let mut buf = String ::new ( ) ;
repeat_char ( & mut buf , '0' , 0 ) . unwrap ( ) ;
assert_eq! ( buf . len ( ) , 0 ) ;
repeat_char ( & mut buf , '0' , 42 ) . unwrap ( ) ;
assert_eq! ( buf . len ( ) , 42 ) ;
assert! ( buf . chars ( ) . all ( | c | c = = '0' ) ) ;
}
// Creates individual test functions to make it easier to find which check failed.
macro_rules ! check_format_non_negative {
2022-05-27 00:50:02 +00:00
( $denom :ident ; $( $test_name :ident , $val :literal , $format_string :literal , $expected :literal ) ; * $( ; ) ? ) = > {
2021-11-23 16:40:28 +00:00
$(
#[ test ]
fn $test_name ( ) {
assert_eq! ( format! ( $format_string , Amount ::from_sat ( $val ) . display_in ( Denomination ::$denom ) ) , $expected ) ;
assert_eq! ( format! ( $format_string , SignedAmount ::from_sat ( $val as i64 ) . display_in ( Denomination ::$denom ) ) , $expected ) ;
}
) *
}
}
macro_rules ! check_format_non_negative_show_denom {
2022-05-27 00:50:02 +00:00
( $denom :ident , $denom_suffix :literal ; $( $test_name :ident , $val :literal , $format_string :literal , $expected :literal ) ; * $( ; ) ? ) = > {
2021-11-23 16:40:28 +00:00
$(
#[ test ]
fn $test_name ( ) {
assert_eq! ( format! ( $format_string , Amount ::from_sat ( $val ) . display_in ( Denomination ::$denom ) . show_denomination ( ) ) , concat! ( $expected , $denom_suffix ) ) ;
assert_eq! ( format! ( $format_string , SignedAmount ::from_sat ( $val as i64 ) . display_in ( Denomination ::$denom ) . show_denomination ( ) ) , concat! ( $expected , $denom_suffix ) ) ;
}
) *
}
}
check_format_non_negative! {
Satoshi ;
sat_check_fmt_non_negative_0 , 0 , " {} " , " 0 " ;
sat_check_fmt_non_negative_1 , 0 , " {:2} " , " 0 " ;
sat_check_fmt_non_negative_2 , 0 , " {:02} " , " 00 " ;
sat_check_fmt_non_negative_3 , 0 , " {:.1} " , " 0.0 " ;
sat_check_fmt_non_negative_4 , 0 , " {:4.1} " , " 0.0 " ;
sat_check_fmt_non_negative_5 , 0 , " {:04.1} " , " 00.0 " ;
sat_check_fmt_non_negative_6 , 1 , " {} " , " 1 " ;
sat_check_fmt_non_negative_7 , 1 , " {:2} " , " 1 " ;
sat_check_fmt_non_negative_8 , 1 , " {:02} " , " 01 " ;
sat_check_fmt_non_negative_9 , 1 , " {:.1} " , " 1.0 " ;
sat_check_fmt_non_negative_10 , 1 , " {:4.1} " , " 1.0 " ;
sat_check_fmt_non_negative_11 , 1 , " {:04.1} " , " 01.0 " ;
sat_check_fmt_non_negative_12 , 10 , " {} " , " 10 " ;
sat_check_fmt_non_negative_13 , 10 , " {:2} " , " 10 " ;
sat_check_fmt_non_negative_14 , 10 , " {:02} " , " 10 " ;
sat_check_fmt_non_negative_15 , 10 , " {:3} " , " 10 " ;
sat_check_fmt_non_negative_16 , 10 , " {:03} " , " 010 " ;
sat_check_fmt_non_negative_17 , 10 , " {:.1} " , " 10.0 " ;
sat_check_fmt_non_negative_18 , 10 , " {:5.1} " , " 10.0 " ;
sat_check_fmt_non_negative_19 , 10 , " {:05.1} " , " 010.0 " ;
sat_check_fmt_non_negative_20 , 1 , " {:<2} " , " 1 " ;
sat_check_fmt_non_negative_21 , 1 , " {:<02} " , " 01 " ;
sat_check_fmt_non_negative_22 , 1 , " {:<3.1} " , " 1.0 " ;
sat_check_fmt_non_negative_23 , 1 , " {:<4.1} " , " 1.0 " ;
}
check_format_non_negative_show_denom! {
Satoshi , " satoshi " ;
sat_check_fmt_non_negative_show_denom_0 , 0 , " {} " , " 0 " ;
sat_check_fmt_non_negative_show_denom_1 , 0 , " {:2} " , " 0 " ;
sat_check_fmt_non_negative_show_denom_2 , 0 , " {:02} " , " 0 " ;
sat_check_fmt_non_negative_show_denom_3 , 0 , " {:9} " , " 0 " ;
sat_check_fmt_non_negative_show_denom_4 , 0 , " {:09} " , " 0 " ;
sat_check_fmt_non_negative_show_denom_5 , 0 , " {:10} " , " 0 " ;
sat_check_fmt_non_negative_show_denom_6 , 0 , " {:010} " , " 00 " ;
sat_check_fmt_non_negative_show_denom_7 , 0 , " {:.1} " , " 0.0 " ;
sat_check_fmt_non_negative_show_denom_8 , 0 , " {:11.1} " , " 0.0 " ;
sat_check_fmt_non_negative_show_denom_9 , 0 , " {:011.1} " , " 0.0 " ;
sat_check_fmt_non_negative_show_denom_10 , 0 , " {:12.1} " , " 0.0 " ;
sat_check_fmt_non_negative_show_denom_11 , 0 , " {:012.1} " , " 00.0 " ;
sat_check_fmt_non_negative_show_denom_12 , 1 , " {} " , " 1 " ;
sat_check_fmt_non_negative_show_denom_13 , 1 , " {:10} " , " 1 " ;
sat_check_fmt_non_negative_show_denom_14 , 1 , " {:010} " , " 01 " ;
sat_check_fmt_non_negative_show_denom_15 , 1 , " {:.1} " , " 1.0 " ;
sat_check_fmt_non_negative_show_denom_16 , 1 , " {:12.1} " , " 1.0 " ;
sat_check_fmt_non_negative_show_denom_17 , 1 , " {:012.1} " , " 01.0 " ;
sat_check_fmt_non_negative_show_denom_18 , 10 , " {} " , " 10 " ;
sat_check_fmt_non_negative_show_denom_19 , 10 , " {:10} " , " 10 " ;
sat_check_fmt_non_negative_show_denom_20 , 10 , " {:010} " , " 10 " ;
sat_check_fmt_non_negative_show_denom_21 , 10 , " {:11} " , " 10 " ;
sat_check_fmt_non_negative_show_denom_22 , 10 , " {:011} " , " 010 " ;
}
check_format_non_negative! {
Bitcoin ;
btc_check_fmt_non_negative_0 , 0 , " {} " , " 0 " ;
btc_check_fmt_non_negative_1 , 0 , " {:2} " , " 0 " ;
btc_check_fmt_non_negative_2 , 0 , " {:02} " , " 00 " ;
btc_check_fmt_non_negative_3 , 0 , " {:.1} " , " 0.0 " ;
btc_check_fmt_non_negative_4 , 0 , " {:4.1} " , " 0.0 " ;
btc_check_fmt_non_negative_5 , 0 , " {:04.1} " , " 00.0 " ;
btc_check_fmt_non_negative_6 , 1 , " {} " , " 0.00000001 " ;
btc_check_fmt_non_negative_7 , 1 , " {:2} " , " 0.00000001 " ;
btc_check_fmt_non_negative_8 , 1 , " {:02} " , " 0.00000001 " ;
btc_check_fmt_non_negative_9 , 1 , " {:.1} " , " 0.00000001 " ;
btc_check_fmt_non_negative_10 , 1 , " {:11} " , " 0.00000001 " ;
btc_check_fmt_non_negative_11 , 1 , " {:11.1} " , " 0.00000001 " ;
btc_check_fmt_non_negative_12 , 1 , " {:011.1} " , " 00.00000001 " ;
btc_check_fmt_non_negative_13 , 1 , " {:.9} " , " 0.000000010 " ;
btc_check_fmt_non_negative_14 , 1 , " {:11.9} " , " 0.000000010 " ;
btc_check_fmt_non_negative_15 , 1 , " {:011.9} " , " 0.000000010 " ;
btc_check_fmt_non_negative_16 , 1 , " {:12.9} " , " 0.000000010 " ;
btc_check_fmt_non_negative_17 , 1 , " {:012.9} " , " 00.000000010 " ;
btc_check_fmt_non_negative_18 , 100_000_000 , " {} " , " 1 " ;
btc_check_fmt_non_negative_19 , 100_000_000 , " {:2} " , " 1 " ;
btc_check_fmt_non_negative_20 , 100_000_000 , " {:02} " , " 01 " ;
btc_check_fmt_non_negative_21 , 100_000_000 , " {:.1} " , " 1.0 " ;
btc_check_fmt_non_negative_22 , 100_000_000 , " {:4.1} " , " 1.0 " ;
btc_check_fmt_non_negative_23 , 100_000_000 , " {:04.1} " , " 01.0 " ;
btc_check_fmt_non_negative_24 , 110_000_000 , " {} " , " 1.1 " ;
btc_check_fmt_non_negative_25 , 100_000_001 , " {} " , " 1.00000001 " ;
btc_check_fmt_non_negative_26 , 100_000_001 , " {:1} " , " 1.00000001 " ;
btc_check_fmt_non_negative_27 , 100_000_001 , " {:.1} " , " 1.00000001 " ;
btc_check_fmt_non_negative_28 , 100_000_001 , " {:10} " , " 1.00000001 " ;
btc_check_fmt_non_negative_29 , 100_000_001 , " {:11} " , " 1.00000001 " ;
btc_check_fmt_non_negative_30 , 100_000_001 , " {:011} " , " 01.00000001 " ;
btc_check_fmt_non_negative_31 , 100_000_001 , " {:.8} " , " 1.00000001 " ;
btc_check_fmt_non_negative_32 , 100_000_001 , " {:.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_33 , 100_000_001 , " {:11.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_34 , 100_000_001 , " {:12.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_35 , 100_000_001 , " {:012.9} " , " 01.000000010 " ;
btc_check_fmt_non_negative_36 , 100_000_001 , " {:+011.8} " , " +1.00000001 " ;
btc_check_fmt_non_negative_37 , 100_000_001 , " {:+12.8} " , " +1.00000001 " ;
btc_check_fmt_non_negative_38 , 100_000_001 , " {:+012.8} " , " +01.00000001 " ;
btc_check_fmt_non_negative_39 , 100_000_001 , " {:+12.9} " , " +1.000000010 " ;
btc_check_fmt_non_negative_40 , 100_000_001 , " {:+012.9} " , " +1.000000010 " ;
btc_check_fmt_non_negative_41 , 100_000_001 , " {:+13.9} " , " +1.000000010 " ;
btc_check_fmt_non_negative_42 , 100_000_001 , " {:+013.9} " , " +01.000000010 " ;
btc_check_fmt_non_negative_43 , 100_000_001 , " {:<10} " , " 1.00000001 " ;
btc_check_fmt_non_negative_44 , 100_000_001 , " {:<11} " , " 1.00000001 " ;
btc_check_fmt_non_negative_45 , 100_000_001 , " {:<011} " , " 01.00000001 " ;
btc_check_fmt_non_negative_46 , 100_000_001 , " {:<11.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_47 , 100_000_001 , " {:<12.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_48 , 100_000_001 , " {:<12} " , " 1.00000001 " ;
btc_check_fmt_non_negative_49 , 100_000_001 , " {:^11} " , " 1.00000001 " ;
btc_check_fmt_non_negative_50 , 100_000_001 , " {:^11.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_51 , 100_000_001 , " {:^12.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_52 , 100_000_001 , " {:^12} " , " 1.00000001 " ;
btc_check_fmt_non_negative_53 , 100_000_001 , " {:^12.9} " , " 1.000000010 " ;
btc_check_fmt_non_negative_54 , 100_000_001 , " {:^13.9} " , " 1.000000010 " ;
}
check_format_non_negative_show_denom! {
Bitcoin , " BTC " ;
btc_check_fmt_non_negative_show_denom_0 , 1 , " {:14.1} " , " 0.00000001 " ;
btc_check_fmt_non_negative_show_denom_1 , 1 , " {:14.8} " , " 0.00000001 " ;
btc_check_fmt_non_negative_show_denom_2 , 1 , " {:15} " , " 0.00000001 " ;
btc_check_fmt_non_negative_show_denom_3 , 1 , " {:015} " , " 00.00000001 " ;
btc_check_fmt_non_negative_show_denom_4 , 1 , " {:.9} " , " 0.000000010 " ;
btc_check_fmt_non_negative_show_denom_5 , 1 , " {:15.9} " , " 0.000000010 " ;
btc_check_fmt_non_negative_show_denom_6 , 1 , " {:16.9} " , " 0.000000010 " ;
btc_check_fmt_non_negative_show_denom_7 , 1 , " {:016.9} " , " 00.000000010 " ;
}
check_format_non_negative_show_denom! {
Bitcoin , " BTC " ;
btc_check_fmt_non_negative_show_denom_align_0 , 1 , " {:<15} " , " 0.00000001 " ;
btc_check_fmt_non_negative_show_denom_align_1 , 1 , " {:^15} " , " 0.00000001 " ;
btc_check_fmt_non_negative_show_denom_align_2 , 1 , " {:^16} " , " 0.00000001 " ;
}
check_format_non_negative! {
MilliSatoshi ;
msat_check_fmt_non_negative_0 , 0 , " {} " , " 0 " ;
msat_check_fmt_non_negative_1 , 1 , " {} " , " 1000 " ;
msat_check_fmt_non_negative_2 , 1 , " {:5} " , " 1000 " ;
msat_check_fmt_non_negative_3 , 1 , " {:05} " , " 01000 " ;
msat_check_fmt_non_negative_4 , 1 , " {:.1} " , " 1000.0 " ;
msat_check_fmt_non_negative_5 , 1 , " {:6.1} " , " 1000.0 " ;
msat_check_fmt_non_negative_6 , 1 , " {:06.1} " , " 1000.0 " ;
msat_check_fmt_non_negative_7 , 1 , " {:7.1} " , " 1000.0 " ;
msat_check_fmt_non_negative_8 , 1 , " {:07.1} " , " 01000.0 " ;
}
2020-01-21 17:43:10 +00:00
#[ test ]
fn test_unsigned_signed_conversion ( ) {
use super ::ParseAmountError as E ;
let sa = SignedAmount ::from_sat ;
let ua = Amount ::from_sat ;
2023-05-02 22:16:20 +00:00
assert_eq! ( Amount ::MAX . to_signed ( ) , Err ( E ::TooBig ) ) ;
assert_eq! ( ua ( i64 ::MAX as u64 ) . to_signed ( ) , Ok ( sa ( i64 ::MAX ) ) ) ;
assert_eq! ( ua ( i64 ::MAX as u64 + 1 ) . to_signed ( ) , Err ( E ::TooBig ) ) ;
2020-01-21 17:43:10 +00:00
2023-05-02 22:16:20 +00:00
assert_eq! ( sa ( i64 ::MAX ) . to_unsigned ( ) , Ok ( ua ( i64 ::MAX as u64 ) ) ) ;
2020-01-21 17:43:10 +00:00
2023-05-02 22:16:20 +00:00
assert_eq! ( sa ( i64 ::MAX ) . to_unsigned ( ) . unwrap ( ) . to_signed ( ) , Ok ( sa ( i64 ::MAX ) ) ) ;
2020-01-21 17:43:10 +00:00
}
2018-11-10 00:45:00 +00:00
#[ test ]
2022-06-07 04:29:15 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn from_str ( ) {
2023-11-20 00:52:19 +00:00
use ParseDenominationError ::* ;
2018-11-10 00:45:00 +00:00
use super ::ParseAmountError as E ;
2022-12-31 22:19:56 +00:00
assert_eq! ( Amount ::from_str ( " x BTC " ) , Err ( E ::InvalidCharacter ( 'x' ) ) ) ;
2023-11-17 21:01:39 +00:00
assert_eq! (
Amount ::from_str ( " xBTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " xBTC " . into ( ) ) ) . into ( ) ) ,
2023-11-17 21:01:39 +00:00
) ;
assert_eq! (
Amount ::from_str ( " 5 BTC BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " BTC BTC " . into ( ) ) ) . into ( ) ) ,
2023-11-17 21:01:39 +00:00
) ;
2022-12-31 22:19:56 +00:00
assert_eq! ( Amount ::from_str ( " 5BTC BTC " ) , Err ( E ::InvalidCharacter ( 'B' ) ) ) ;
2023-11-17 21:01:39 +00:00
assert_eq! (
Amount ::from_str ( " 5 5 BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " 5 BTC " . into ( ) ) ) . into ( ) ) ,
2023-11-17 21:01:39 +00:00
) ;
2022-12-31 22:19:56 +00:00
2023-03-22 21:27:10 +00:00
#[ track_caller ]
2022-12-31 22:19:56 +00:00
fn case ( s : & str , expected : Result < Amount , ParseAmountError > ) {
2022-11-02 22:36:37 +00:00
assert_eq! ( Amount ::from_str ( s ) , expected ) ;
assert_eq! ( Amount ::from_str ( & s . replace ( ' ' , " " ) ) , expected ) ;
2022-12-31 22:19:56 +00:00
}
2023-03-22 21:27:10 +00:00
#[ track_caller ]
2022-12-31 22:19:56 +00:00
fn scase ( s : & str , expected : Result < SignedAmount , ParseAmountError > ) {
2022-11-02 22:36:37 +00:00
assert_eq! ( SignedAmount ::from_str ( s ) , expected ) ;
assert_eq! ( SignedAmount ::from_str ( & s . replace ( ' ' , " " ) ) , expected ) ;
2022-12-31 22:19:56 +00:00
}
2023-11-20 00:52:19 +00:00
case ( " 5 BCH " , Err ( Unknown ( UnknownDenominationError ( " BCH " . into ( ) ) ) . into ( ) ) ) ;
2022-12-31 22:19:56 +00:00
case ( " -1 BTC " , Err ( E ::Negative ) ) ;
case ( " -0.0 BTC " , Err ( E ::Negative ) ) ;
case ( " 0.123456789 BTC " , Err ( E ::TooPrecise ) ) ;
scase ( " -0.1 satoshi " , Err ( E ::TooPrecise ) ) ;
case ( " 0.123456 mBTC " , Err ( E ::TooPrecise ) ) ;
scase ( " -1.001 bits " , Err ( E ::TooPrecise ) ) ;
scase ( " -200000000000 BTC " , Err ( E ::TooBig ) ) ;
case ( " 18446744073709551616 sat " , Err ( E ::TooBig ) ) ;
case ( " .5 bits " , Ok ( Amount ::from_sat ( 50 ) ) ) ;
scase ( " -.5 bits " , Ok ( SignedAmount ::from_sat ( - 50 ) ) ) ;
case ( " 0.00253583 BTC " , Ok ( Amount ::from_sat ( 253583 ) ) ) ;
scase ( " -5 satoshi " , Ok ( SignedAmount ::from_sat ( - 5 ) ) ) ;
case ( " 0.10000000 BTC " , Ok ( Amount ::from_sat ( 100_000_00 ) ) ) ;
scase ( " -100 bits " , Ok ( SignedAmount ::from_sat ( - 10_000 ) ) ) ;
2018-11-10 00:45:00 +00:00
}
#[ test ]
2022-06-07 04:29:15 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2020-01-21 17:43:10 +00:00
fn to_from_string_in ( ) {
2018-11-10 00:45:00 +00:00
use super ::Denomination as D ;
2020-01-21 17:43:10 +00:00
let ua_str = Amount ::from_str_in ;
let ua_sat = Amount ::from_sat ;
let sa_str = SignedAmount ::from_str_in ;
let sa_sat = SignedAmount ::from_sat ;
2018-11-10 00:45:00 +00:00
2021-11-23 16:40:28 +00:00
assert_eq! ( " 0.5 " , Amount ::from_sat ( 50 ) . to_string_in ( D ::Bit ) ) ;
assert_eq! ( " -0.5 " , SignedAmount ::from_sat ( - 50 ) . to_string_in ( D ::Bit ) ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( " 0.00253583 " , Amount ::from_sat ( 253583 ) . to_string_in ( D ::Bitcoin ) ) ;
assert_eq! ( " -5 " , SignedAmount ::from_sat ( - 5 ) . to_string_in ( D ::Satoshi ) ) ;
2021-11-23 16:40:28 +00:00
assert_eq! ( " 0.1 " , Amount ::from_sat ( 100_000_00 ) . to_string_in ( D ::Bitcoin ) ) ;
assert_eq! ( " -100 " , SignedAmount ::from_sat ( - 10_000 ) . to_string_in ( D ::Bit ) ) ;
2022-01-10 14:30:28 +00:00
assert_eq! ( " 2535830 " , Amount ::from_sat ( 253583 ) . to_string_in ( D ::NanoBitcoin ) ) ;
assert_eq! ( " -100000 " , SignedAmount ::from_sat ( - 10_000 ) . to_string_in ( D ::NanoBitcoin ) ) ;
assert_eq! ( " 2535830000 " , Amount ::from_sat ( 253583 ) . to_string_in ( D ::PicoBitcoin ) ) ;
assert_eq! ( " -100000000 " , SignedAmount ::from_sat ( - 10_000 ) . to_string_in ( D ::PicoBitcoin ) ) ;
2021-11-23 16:40:28 +00:00
assert_eq! ( " 0.50 " , format! ( " {:.2} " , Amount ::from_sat ( 50 ) . display_in ( D ::Bit ) ) ) ;
assert_eq! ( " -0.50 " , format! ( " {:.2} " , SignedAmount ::from_sat ( - 50 ) . display_in ( D ::Bit ) ) ) ;
2022-11-15 23:22:16 +00:00
assert_eq! (
" 0.10000000 " ,
format! ( " {:.8} " , Amount ::from_sat ( 100_000_00 ) . display_in ( D ::Bitcoin ) )
) ;
2021-11-23 16:40:28 +00:00
assert_eq! ( " -100.00 " , format! ( " {:.2} " , SignedAmount ::from_sat ( - 10_000 ) . display_in ( D ::Bit ) ) ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( ua_str ( & ua_sat ( 0 ) . to_string_in ( D ::Satoshi ) , D ::Satoshi ) , Ok ( ua_sat ( 0 ) ) ) ;
assert_eq! ( ua_str ( & ua_sat ( 500 ) . to_string_in ( D ::Bitcoin ) , D ::Bitcoin ) , Ok ( ua_sat ( 500 ) ) ) ;
2022-11-15 23:22:16 +00:00
assert_eq! (
ua_str ( & ua_sat ( 21_000_000 ) . to_string_in ( D ::Bit ) , D ::Bit ) ,
Ok ( ua_sat ( 21_000_000 ) )
) ;
assert_eq! (
ua_str ( & ua_sat ( 1 ) . to_string_in ( D ::MicroBitcoin ) , D ::MicroBitcoin ) ,
Ok ( ua_sat ( 1 ) )
) ;
assert_eq! (
ua_str ( & ua_sat ( 1_000_000_000_000 ) . to_string_in ( D ::MilliBitcoin ) , D ::MilliBitcoin ) ,
Ok ( ua_sat ( 1_000_000_000_000 ) )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
ua_str ( & ua_sat ( u64 ::MAX ) . to_string_in ( D ::MilliBitcoin ) , D ::MilliBitcoin ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::TooBig )
) ;
2020-01-21 17:43:10 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
sa_str ( & sa_sat ( - 1 ) . to_string_in ( D ::MicroBitcoin ) , D ::MicroBitcoin ) ,
Ok ( sa_sat ( - 1 ) )
) ;
2020-01-21 17:43:10 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MAX ) . to_string_in ( D ::Satoshi ) , D ::MicroBitcoin ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::TooBig )
) ;
2020-01-21 17:43:10 +00:00
// Test an overflow bug in `abs()`
2022-11-15 23:22:16 +00:00
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MIN ) . to_string_in ( D ::Satoshi ) , D ::MicroBitcoin ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::TooBig )
) ;
2022-01-10 14:30:28 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
sa_str ( & sa_sat ( - 1 ) . to_string_in ( D ::NanoBitcoin ) , D ::NanoBitcoin ) ,
Ok ( sa_sat ( - 1 ) )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MAX ) . to_string_in ( D ::Satoshi ) , D ::NanoBitcoin ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::TooPrecise )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MIN ) . to_string_in ( D ::Satoshi ) , D ::NanoBitcoin ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::TooPrecise )
) ;
2022-01-10 14:30:28 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
sa_str ( & sa_sat ( - 1 ) . to_string_in ( D ::PicoBitcoin ) , D ::PicoBitcoin ) ,
Ok ( sa_sat ( - 1 ) )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MAX ) . to_string_in ( D ::Satoshi ) , D ::PicoBitcoin ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::TooPrecise )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MIN ) . to_string_in ( D ::Satoshi ) , D ::PicoBitcoin ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::TooPrecise )
) ;
2020-01-21 17:43:10 +00:00
}
#[ test ]
fn to_string_with_denomination_from_str_roundtrip ( ) {
2023-11-20 00:52:19 +00:00
use ParseDenominationError ::* ;
2020-01-21 17:43:10 +00:00
use super ::Denomination as D ;
2023-11-20 00:52:19 +00:00
2018-11-10 00:45:00 +00:00
let amt = Amount ::from_sat ( 42 ) ;
let denom = Amount ::to_string_with_denomination ;
2019-08-05 18:52:34 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::Bitcoin ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::MilliBitcoin ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::MicroBitcoin ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::Bit ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::Satoshi ) ) , Ok ( amt ) ) ;
2022-01-10 14:30:28 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::NanoBitcoin ) ) , Ok ( amt ) ) ;
2019-08-05 18:52:34 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::MilliSatoshi ) ) , Ok ( amt ) ) ;
2022-01-10 14:30:28 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::PicoBitcoin ) ) , Ok ( amt ) ) ;
2020-01-21 17:43:10 +00:00
2022-12-31 22:19:56 +00:00
assert_eq! (
2022-11-02 22:36:37 +00:00
Amount ::from_str ( " 42 satoshi BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " satoshi BTC " . into ( ) ) ) . into ( ) ) ,
2022-12-31 22:19:56 +00:00
) ;
assert_eq! (
2022-11-02 22:36:37 +00:00
SignedAmount ::from_str ( " -42 satoshi BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " satoshi BTC " . into ( ) ) ) . into ( ) ) ,
2022-12-31 22:19:56 +00:00
) ;
2018-11-10 00:45:00 +00:00
}
#[ cfg(feature = " serde " ) ]
#[ test ]
fn serde_as_sat ( ) {
#[ derive(Serialize, Deserialize, PartialEq, Debug) ]
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_sat " ) ]
2018-11-10 00:45:00 +00:00
pub amt : Amount ,
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_sat " ) ]
2018-11-10 00:45:00 +00:00
pub samt : SignedAmount ,
}
serde_test ::assert_tokens (
2022-11-15 23:22:16 +00:00
& T { amt : Amount ::from_sat ( 123456789 ) , samt : SignedAmount ::from_sat ( - 123456789 ) } ,
2018-11-10 00:45:00 +00:00
& [
2022-01-24 00:31:39 +00:00
serde_test ::Token ::Struct { name : " T " , len : 2 } ,
2018-11-10 00:45:00 +00:00
serde_test ::Token ::Str ( " amt " ) ,
serde_test ::Token ::U64 ( 123456789 ) ,
serde_test ::Token ::Str ( " samt " ) ,
serde_test ::Token ::I64 ( - 123456789 ) ,
serde_test ::Token ::StructEnd ,
] ,
) ;
}
#[ cfg(feature = " serde " ) ]
#[ test ]
2022-06-23 03:54:34 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn serde_as_btc ( ) {
use serde_json ;
#[ derive(Serialize, Deserialize, PartialEq, Debug) ]
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_btc " ) ]
2018-11-10 00:45:00 +00:00
pub amt : Amount ,
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_btc " ) ]
2018-11-10 00:45:00 +00:00
pub samt : SignedAmount ,
}
let orig = T {
amt : Amount ::from_sat ( 21_000_000__000_000_01 ) ,
samt : SignedAmount ::from_sat ( - 21_000_000__000_000_01 ) ,
} ;
let json = " { \" amt \" : 21000000.00000001, \
\ " samt \" : -21000000.00000001} " ;
2022-07-20 02:57:30 +00:00
let t : T = serde_json ::from_str ( json ) . unwrap ( ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( t , orig ) ;
2022-07-20 02:57:30 +00:00
let value : serde_json ::Value = serde_json ::from_str ( json ) . unwrap ( ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( t , serde_json ::from_value ( value ) . unwrap ( ) ) ;
// errors
let t : Result < T , serde_json ::Error > =
serde_json ::from_str ( " { \" amt \" : 1000000.000000001, \" samt \" : 1} " ) ;
assert! ( t . unwrap_err ( ) . to_string ( ) . contains ( & ParseAmountError ::TooPrecise . to_string ( ) ) ) ;
let t : Result < T , serde_json ::Error > = serde_json ::from_str ( " { \" amt \" : -1, \" samt \" : 1} " ) ;
assert! ( t . unwrap_err ( ) . to_string ( ) . contains ( & ParseAmountError ::Negative . to_string ( ) ) ) ;
}
#[ cfg(feature = " serde " ) ]
#[ test ]
2022-06-23 03:54:34 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn serde_as_btc_opt ( ) {
use serde_json ;
2021-01-14 18:45:51 +00:00
#[ derive(Serialize, Deserialize, PartialEq, Debug, Eq) ]
2018-11-10 00:45:00 +00:00
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_btc::opt " ) ]
2018-11-10 00:45:00 +00:00
pub amt : Option < Amount > ,
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_btc::opt " ) ]
2018-11-10 00:45:00 +00:00
pub samt : Option < SignedAmount > ,
}
let with = T {
2022-06-23 03:54:34 +00:00
amt : Some ( Amount ::from_sat ( 2_500_000_00 ) ) ,
samt : Some ( SignedAmount ::from_sat ( - 2_500_000_00 ) ) ,
2018-11-10 00:45:00 +00:00
} ;
2022-11-15 23:22:16 +00:00
let without = T { amt : None , samt : None } ;
2018-11-10 00:45:00 +00:00
2021-01-14 18:45:51 +00:00
// Test Roundtripping
for s in [ & with , & without ] . iter ( ) {
let v = serde_json ::to_string ( s ) . unwrap ( ) ;
2022-11-15 23:22:16 +00:00
let w : T = serde_json ::from_str ( & v ) . unwrap ( ) ;
2021-01-14 18:45:51 +00:00
assert_eq! ( w , * * s ) ;
}
2018-11-10 00:45:00 +00:00
let t : T = serde_json ::from_str ( " { \" amt \" : 2.5, \" samt \" : -2.5} " ) . unwrap ( ) ;
assert_eq! ( t , with ) ;
let t : T = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( t , without ) ;
let value_with : serde_json ::Value =
serde_json ::from_str ( " { \" amt \" : 2.5, \" samt \" : -2.5} " ) . unwrap ( ) ;
assert_eq! ( with , serde_json ::from_value ( value_with ) . unwrap ( ) ) ;
let value_without : serde_json ::Value = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( without , serde_json ::from_value ( value_without ) . unwrap ( ) ) ;
}
2021-01-14 18:45:51 +00:00
#[ cfg(feature = " serde " ) ]
#[ test ]
2022-06-23 03:54:34 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2021-01-14 18:45:51 +00:00
fn serde_as_sat_opt ( ) {
use serde_json ;
#[ derive(Serialize, Deserialize, PartialEq, Debug, Eq) ]
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_sat::opt " ) ]
2021-01-14 18:45:51 +00:00
pub amt : Option < Amount > ,
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_sat::opt " ) ]
2021-01-14 18:45:51 +00:00
pub samt : Option < SignedAmount > ,
}
let with = T {
2022-06-23 03:54:34 +00:00
amt : Some ( Amount ::from_sat ( 2_500_000_00 ) ) ,
samt : Some ( SignedAmount ::from_sat ( - 2_500_000_00 ) ) ,
2021-01-14 18:45:51 +00:00
} ;
2022-11-15 23:22:16 +00:00
let without = T { amt : None , samt : None } ;
2021-01-14 18:45:51 +00:00
// Test Roundtripping
for s in [ & with , & without ] . iter ( ) {
let v = serde_json ::to_string ( s ) . unwrap ( ) ;
2022-11-15 23:22:16 +00:00
let w : T = serde_json ::from_str ( & v ) . unwrap ( ) ;
2021-01-14 18:45:51 +00:00
assert_eq! ( w , * * s ) ;
}
let t : T = serde_json ::from_str ( " { \" amt \" : 250000000, \" samt \" : -250000000} " ) . unwrap ( ) ;
assert_eq! ( t , with ) ;
let t : T = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( t , without ) ;
let value_with : serde_json ::Value =
serde_json ::from_str ( " { \" amt \" : 250000000, \" samt \" : -250000000} " ) . unwrap ( ) ;
assert_eq! ( with , serde_json ::from_value ( value_with ) . unwrap ( ) ) ;
let value_without : serde_json ::Value = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( without , serde_json ::from_value ( value_without ) . unwrap ( ) ) ;
}
2021-06-12 14:46:04 +00:00
#[ test ]
fn sum_amounts ( ) {
assert_eq! ( Amount ::from_sat ( 0 ) , vec! [ ] . into_iter ( ) . sum ::< Amount > ( ) ) ;
assert_eq! ( SignedAmount ::from_sat ( 0 ) , vec! [ ] . into_iter ( ) . sum ::< SignedAmount > ( ) ) ;
2022-11-15 23:22:16 +00:00
let amounts = vec! [ Amount ::from_sat ( 42 ) , Amount ::from_sat ( 1337 ) , Amount ::from_sat ( 21 ) ] ;
2021-06-12 14:46:04 +00:00
let sum = amounts . into_iter ( ) . sum ::< Amount > ( ) ;
assert_eq! ( Amount ::from_sat ( 1400 ) , sum ) ;
let amounts = vec! [
SignedAmount ::from_sat ( - 42 ) ,
SignedAmount ::from_sat ( 1337 ) ,
2022-11-15 23:22:16 +00:00
SignedAmount ::from_sat ( 21 ) ,
2021-06-12 14:46:04 +00:00
] ;
let sum = amounts . into_iter ( ) . sum ::< SignedAmount > ( ) ;
assert_eq! ( SignedAmount ::from_sat ( 1316 ) , sum ) ;
}
2021-06-12 15:09:49 +00:00
#[ test ]
fn checked_sum_amounts ( ) {
assert_eq! ( Some ( Amount ::from_sat ( 0 ) ) , vec! [ ] . into_iter ( ) . checked_sum ( ) ) ;
assert_eq! ( Some ( SignedAmount ::from_sat ( 0 ) ) , vec! [ ] . into_iter ( ) . checked_sum ( ) ) ;
2022-11-15 23:22:16 +00:00
let amounts = vec! [ Amount ::from_sat ( 42 ) , Amount ::from_sat ( 1337 ) , Amount ::from_sat ( 21 ) ] ;
2021-06-12 15:09:49 +00:00
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( Some ( Amount ::from_sat ( 1400 ) ) , sum ) ;
2022-11-15 23:22:16 +00:00
let amounts =
2023-05-02 22:16:20 +00:00
vec! [ Amount ::from_sat ( u64 ::MAX ) , Amount ::from_sat ( 1337 ) , Amount ::from_sat ( 21 ) ] ;
2021-06-12 15:09:49 +00:00
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( None , sum ) ;
let amounts = vec! [
2023-05-02 22:16:20 +00:00
SignedAmount ::from_sat ( i64 ::MIN ) ,
2021-06-12 15:09:49 +00:00
SignedAmount ::from_sat ( - 1 ) ,
2022-11-15 23:22:16 +00:00
SignedAmount ::from_sat ( 21 ) ,
2021-06-12 15:09:49 +00:00
] ;
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( None , sum ) ;
let amounts = vec! [
2023-05-02 22:16:20 +00:00
SignedAmount ::from_sat ( i64 ::MAX ) ,
2021-06-12 15:09:49 +00:00
SignedAmount ::from_sat ( 1 ) ,
2022-11-15 23:22:16 +00:00
SignedAmount ::from_sat ( 21 ) ,
2021-06-12 15:09:49 +00:00
] ;
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( None , sum ) ;
let amounts = vec! [
SignedAmount ::from_sat ( 42 ) ,
SignedAmount ::from_sat ( 3301 ) ,
2022-11-15 23:22:16 +00:00
SignedAmount ::from_sat ( 21 ) ,
2021-06-12 15:09:49 +00:00
] ;
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( Some ( SignedAmount ::from_sat ( 3364 ) ) , sum ) ;
}
2021-12-01 01:56:16 +00:00
#[ test ]
fn denomination_string_acceptable_forms ( ) {
// Non-exhaustive list of valid forms.
2022-11-15 23:22:16 +00:00
let valid = vec! [
2023-03-23 11:14:11 +00:00
" BTC " , " btc " , " mBTC " , " mbtc " , " uBTC " , " ubtc " , " SATOSHI " , " satoshi " , " SATOSHIS " ,
" satoshis " , " SAT " , " sat " , " SATS " , " sats " , " bit " , " bits " , " nBTC " , " pBTC " ,
2022-11-15 23:22:16 +00:00
] ;
2021-12-01 01:56:16 +00:00
for denom in valid . iter ( ) {
assert! ( Denomination ::from_str ( denom ) . is_ok ( ) ) ;
}
}
#[ test ]
fn disallow_confusing_forms ( ) {
2023-08-25 02:30:04 +00:00
let confusing = [ " Msat " , " Msats " , " MSAT " , " MSATS " , " MSat " , " MSats " , " MBTC " , " Mbtc " , " PBTC " ] ;
2021-12-01 01:56:16 +00:00
for denom in confusing . iter ( ) {
2022-11-15 23:22:16 +00:00
match Denomination ::from_str ( denom ) {
2021-12-01 01:56:16 +00:00
Ok ( _ ) = > panic! ( " from_str should error for {} " , denom ) ,
2023-11-17 21:01:39 +00:00
Err ( ParseDenominationError ::PossiblyConfusing ( _ ) ) = > { }
2021-12-01 01:56:16 +00:00
Err ( e ) = > panic! ( " unexpected error: {} " , e ) ,
}
}
}
2022-01-11 14:49:58 +00:00
#[ test ]
fn disallow_unknown_denomination ( ) {
// Non-exhaustive list of unknown forms.
2023-08-25 02:30:04 +00:00
let unknown = [ " NBTC " , " UBTC " , " ABC " , " abc " , " cBtC " , " Sat " , " Sats " ] ;
2022-01-11 14:49:58 +00:00
for denom in unknown . iter ( ) {
2022-11-15 23:22:16 +00:00
match Denomination ::from_str ( denom ) {
2022-01-11 14:49:58 +00:00
Ok ( _ ) = > panic! ( " from_str should error for {} " , denom ) ,
2023-11-17 21:01:39 +00:00
Err ( ParseDenominationError ::Unknown ( _ ) ) = > { }
2022-01-11 14:49:58 +00:00
Err ( e ) = > panic! ( " unexpected error: {} " , e ) ,
}
}
}
2018-11-10 00:45:00 +00:00
}