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 ;
2021-06-09 10:40:41 +00:00
use core ::fmt ::{ self , Write } ;
2022-11-15 23:22:16 +00:00
use core ::str ::FromStr ;
use core ::{ default , ops } ;
use crate ::prelude ::* ;
2018-11-10 00:45:00 +00:00
/// A set of denominations in which amounts can be expressed.
#[ derive(Debug, Clone, Copy, Eq, PartialEq, Hash) ]
pub enum Denomination {
/// BTC
Bitcoin ,
/// 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 ,
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 " ,
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
}
}
}
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 {
type Err = ParseAmountError ;
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 > {
2022-01-11 14:49:58 +00:00
use self ::Denomination as D ;
2022-11-15 23:22:16 +00:00
use self ::ParseAmountError ::* ;
2021-12-01 01:56:16 +00:00
2022-01-14 12:48:31 +00:00
let starts_with_uppercase = | | s . starts_with ( char ::is_uppercase ) ;
2022-01-11 14:49:58 +00:00
match denomination_from_str ( s ) {
None = > Err ( UnknownDenomination ( s . to_owned ( ) ) ) ,
2022-11-15 23:22:16 +00:00
Some ( D ::MilliBitcoin ) | Some ( D ::PicoBitcoin ) | Some ( D ::MilliSatoshi )
if starts_with_uppercase ( ) = >
Err ( PossiblyConfusingDenomination ( s . to_owned ( ) ) ) ,
Some ( D ::NanoBitcoin ) | Some ( D ::MicroBitcoin ) if starts_with_uppercase ( ) = >
Err ( UnknownDenomination ( s . to_owned ( ) ) ) ,
2022-01-11 14:49:58 +00:00
Some ( d ) = > Ok ( d ) ,
2018-11-10 00:45:00 +00:00
}
}
}
2021-12-01 01:56:16 +00:00
fn denomination_from_str ( mut s : & str ) -> Option < Denomination > {
if s . eq_ignore_ascii_case ( " BTC " ) {
return Some ( Denomination ::Bitcoin ) ;
}
if s . eq_ignore_ascii_case ( " mBTC " ) {
return Some ( Denomination ::MilliBitcoin ) ;
}
if s . eq_ignore_ascii_case ( " uBTC " ) {
return Some ( Denomination ::MicroBitcoin ) ;
}
2022-01-10 14:30:28 +00:00
if s . eq_ignore_ascii_case ( " nBTC " ) {
return Some ( Denomination ::NanoBitcoin ) ;
}
if s . eq_ignore_ascii_case ( " pBTC " ) {
return Some ( Denomination ::PicoBitcoin ) ;
}
2021-12-01 01:56:16 +00:00
if s . ends_with ( 's' ) | | s . ends_with ( 'S' ) {
s = & s [ .. ( s . len ( ) - 1 ) ] ;
}
if s . eq_ignore_ascii_case ( " bit " ) {
return Some ( Denomination ::Bit ) ;
}
if s . eq_ignore_ascii_case ( " satoshi " ) {
return Some ( Denomination ::Satoshi ) ;
}
if s . eq_ignore_ascii_case ( " sat " ) {
return Some ( Denomination ::Satoshi ) ;
}
if s . eq_ignore_ascii_case ( " msat " ) {
return Some ( Denomination ::MilliSatoshi ) ;
}
None
}
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 ) ,
/// The denomination was unknown.
UnknownDenomination ( String ) ,
2021-12-01 01:56:16 +00:00
/// The denomination has multiple possible interpretations.
2022-11-15 23:22:16 +00:00
PossiblyConfusingDenomination ( String ) ,
2018-11-10 00:45:00 +00:00
}
impl fmt ::Display for ParseAmountError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match * self {
2020-03-29 13:49:48 +00:00
ParseAmountError ::Negative = > f . write_str ( " amount is negative " ) ,
ParseAmountError ::TooBig = > f . write_str ( " amount is too big " ) ,
ParseAmountError ::TooPrecise = > f . write_str ( " amount has a too high precision " ) ,
ParseAmountError ::InvalidFormat = > f . write_str ( " invalid number format " ) ,
ParseAmountError ::InputTooLarge = > f . write_str ( " input string was too large " ) ,
ParseAmountError ::InvalidCharacter ( c ) = > write! ( f , " invalid character in input: {} " , c ) ,
2022-11-15 23:22:16 +00:00
ParseAmountError ::UnknownDenomination ( ref d ) = >
write! ( f , " unknown denomination: {} " , d ) ,
2021-12-01 01:56:16 +00:00
ParseAmountError ::PossiblyConfusingDenomination ( ref d ) = > {
2022-01-11 14:49:58 +00:00
let ( letter , upper , lower ) = match d . chars ( ) . next ( ) {
Some ( 'M' ) = > ( 'M' , " Mega " , " milli " ) ,
2022-01-24 00:26:29 +00:00
Some ( 'P' ) = > ( 'P' , " Peta " , " pico " ) ,
2022-01-11 14:49:58 +00:00
// This panic could be avoided by adding enum ConfusingDenomination { Mega, Peta } but is it worth it?
_ = > panic! ( " invalid error information " ) ,
} ;
write! ( f , " the '{}' at the beginning of {} should technically mean '{}' but that denomination is uncommon and maybe '{}' was intended " , letter , d , upper , lower )
2021-12-01 01:56:16 +00:00
}
2018-11-10 00:45:00 +00:00
}
}
}
2021-06-09 10:34:44 +00:00
#[ cfg(feature = " std " ) ]
2021-07-28 19:38:58 +00:00
#[ cfg_attr(docsrs, doc(cfg(feature = " std " ))) ]
2022-06-10 02:07:46 +00:00
impl std ::error ::Error for ParseAmountError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > {
use self ::ParseAmountError ::* ;
match * self {
Negative
2022-11-15 23:22:16 +00:00
| TooBig
| TooPrecise
| InvalidFormat
| InputTooLarge
| InvalidCharacter ( _ )
| UnknownDenomination ( _ )
| PossiblyConfusingDenomination ( _ ) = > None ,
2022-06-10 02:07:46 +00:00
}
}
}
2019-08-06 14:32:51 +00:00
fn is_too_precise ( s : & str , precision : usize ) -> bool {
2019-08-05 19:41:07 +00:00
s . contains ( '.' ) | | 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 ( ) ;
if precision_diff < 0 {
// 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 ) {
2018-11-10 00:45:00 +00:00
return Err ( ParseAmountError ::TooPrecise ) ;
}
s = & s [ 0 .. s . len ( ) - last_n ] ;
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 {
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
}
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) ]
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.
pub const ONE_BTC : Amount = Amount ( 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 : Amount = Amount ( 21_000_000 * 100_000_000 ) ;
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
/// The maximum value of an [Amount].
2022-11-15 23:22:16 +00:00
pub fn max_value ( ) -> Amount { Amount ( u64 ::max_value ( ) ) }
2018-11-10 00:45:00 +00:00
/// The minimum value of an [Amount].
2022-11-15 23:22:16 +00:00
pub fn min_value ( ) -> Amount { Amount ( u64 ::min_value ( ) ) }
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 )
}
/// 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 ) ;
}
if satoshi > i64 ::max_value ( ) as u64 {
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 > {
2019-08-05 19:41:07 +00:00
let mut split = s . splitn ( 3 , ' ' ) ;
2018-11-10 00:45:00 +00:00
let amt_str = split . next ( ) . unwrap ( ) ;
let denom_str = split . next ( ) . ok_or ( ParseAmountError ::InvalidFormat ) ? ;
if split . next ( ) . is_some ( ) {
return Err ( ParseAmountError ::InvalidFormat ) ;
}
2021-11-03 09:20:34 +00:00
Amount ::from_str_in ( amt_str , denom_str . parse ( ) ? )
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
/// ```
/// # use bitcoin::{Amount, Denomination};
/// 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 > {
2022-01-19 03:05:01 +00:00
if self . to_sat ( ) > SignedAmount ::max_value ( ) . 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 ) ;
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
/// The maximum value of an [SignedAmount].
2022-11-15 23:22:16 +00:00
pub fn max_value ( ) -> SignedAmount { SignedAmount ( i64 ::max_value ( ) ) }
2018-11-10 00:45:00 +00:00
/// The minimum value of an [SignedAmount].
2022-11-15 23:22:16 +00:00
pub fn min_value ( ) -> SignedAmount { SignedAmount ( i64 ::min_value ( ) ) }
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 ) ? ;
if satoshi > i64 ::max_value ( ) as u64 {
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 > {
2019-08-05 19:41:07 +00:00
let mut split = s . splitn ( 3 , ' ' ) ;
2018-11-10 00:45:00 +00:00
let amt_str = split . next ( ) . unwrap ( ) ;
let denom_str = split . next ( ) . ok_or ( ParseAmountError ::InvalidFormat ) ? ;
if split . next ( ) . is_some ( ) {
return Err ( ParseAmountError ::InvalidFormat ) ;
}
2021-11-03 09:20:34 +00:00
SignedAmount ::from_str_in ( amt_str , denom_str . parse ( ) ? )
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].
/// Returns [None] if overflow occurred. (`self == min_value()`)
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 " ) ]
2021-07-28 19:38:58 +00:00
#[ cfg_attr(docsrs, doc(cfg(feature = " serde " ))) ]
2018-11-10 00:45:00 +00:00
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};
//! use bitcoin::Amount;
//!
//! #[derive(Serialize, Deserialize)]
2022-05-25 06:41:59 +00:00
//! # #[serde(crate = "actual_serde")]
2018-11-10 00:45:00 +00:00
//! pub struct HasAmount {
2022-10-20 22:12:44 +00:00
//! #[serde(with = "bitcoin::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 ::* ;
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
let result = panic ::catch_unwind ( | | Amount ::max_value ( ) + Amount ::from_sat ( 1 ) ) ;
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 ;
assert_eq! ( SignedAmount ::max_value ( ) . checked_add ( ssat ( 1 ) ) , None ) ;
assert_eq! ( SignedAmount ::min_value ( ) . checked_sub ( ssat ( 1 ) ) , None ) ;
assert_eq! ( Amount ::max_value ( ) . checked_add ( sat ( 1 ) ) , None ) ;
assert_eq! ( Amount ::min_value ( ) . checked_sub ( sat ( 1 ) ) , None ) ;
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! (
f ( SignedAmount ::max_value ( ) . to_float_in ( D ::Satoshi ) + 1.0 , D ::Satoshi ) ,
Err ( ParseAmountError ::TooBig )
) ;
assert_eq! (
f ( Amount ::max_value ( ) . to_float_in ( D ::Satoshi ) + 1.0 , D ::Satoshi ) ,
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 ;
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 ) ) ;
let more_than_max = format! ( " 1 {} " , Amount ::max_value ( ) ) ;
assert_eq! ( p ( & more_than_max , btc ) , Err ( E ::TooBig ) ) ;
assert_eq! ( p ( " 0.000000042 " , btc ) , Err ( E ::TooPrecise ) ) ;
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 ) )
) ;
2020-01-21 17:43:10 +00:00
// make sure satoshi > i64::max_value() is checked.
let amount = Amount ::from_sat ( i64 ::max_value ( ) as u64 ) ;
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 ;
2022-01-24 00:26:29 +00:00
assert_eq! ( Amount ::max_value ( ) . to_signed ( ) , Err ( E ::TooBig ) ) ;
2022-11-15 23:22:16 +00:00
assert_eq! ( ua ( i64 ::max_value ( ) as u64 ) . to_signed ( ) , Ok ( sa ( i64 ::max_value ( ) ) ) ) ;
2022-01-24 00:26:29 +00:00
assert_eq! ( ua ( i64 ::max_value ( ) as u64 + 1 ) . to_signed ( ) , Err ( E ::TooBig ) ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( sa ( i64 ::max_value ( ) ) . to_unsigned ( ) , Ok ( ua ( i64 ::max_value ( ) as u64 ) ) ) ;
2022-11-15 23:22:16 +00:00
assert_eq! (
sa ( i64 ::max_value ( ) ) . to_unsigned ( ) . unwrap ( ) . to_signed ( ) ,
Ok ( sa ( i64 ::max_value ( ) ) )
) ;
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 ( ) {
use super ::ParseAmountError as E ;
let p = Amount ::from_str ;
let sp = SignedAmount ::from_str ;
assert_eq! ( p ( " x BTC " ) , Err ( E ::InvalidCharacter ( 'x' ) ) ) ;
assert_eq! ( p ( " 5 BTC BTC " ) , Err ( E ::InvalidFormat ) ) ;
assert_eq! ( p ( " 5 5 BTC " ) , Err ( E ::InvalidFormat ) ) ;
assert_eq! ( p ( " 5 BCH " ) , Err ( E ::UnknownDenomination ( " BCH " . to_owned ( ) ) ) ) ;
assert_eq! ( p ( " -1 BTC " ) , Err ( E ::Negative ) ) ;
assert_eq! ( p ( " -0.0 BTC " ) , Err ( E ::Negative ) ) ;
assert_eq! ( p ( " 0.123456789 BTC " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( sp ( " -0.1 satoshi " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 0.123456 mBTC " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( sp ( " -1.001 bits " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( sp ( " -200000000000 BTC " ) , Err ( E ::TooBig ) ) ;
assert_eq! ( p ( " 18446744073709551616 sat " ) , Err ( E ::TooBig ) ) ;
2019-08-06 14:32:51 +00:00
assert_eq! ( sp ( " 0 msat " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( sp ( " -0 msat " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( sp ( " 000 msat " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( sp ( " -000 msat " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 0 msat " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " -0 msat " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " 000 msat " ) , Err ( E ::TooPrecise ) ) ;
assert_eq! ( p ( " -000 msat " ) , Err ( E ::TooPrecise ) ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( p ( " .5 bits " ) , Ok ( Amount ::from_sat ( 50 ) ) ) ;
assert_eq! ( sp ( " -.5 bits " ) , Ok ( SignedAmount ::from_sat ( - 50 ) ) ) ;
assert_eq! ( p ( " 0.00253583 BTC " ) , Ok ( Amount ::from_sat ( 253583 ) ) ) ;
assert_eq! ( sp ( " -5 satoshi " ) , Ok ( SignedAmount ::from_sat ( - 5 ) ) ) ;
assert_eq! ( p ( " 0.10000000 BTC " ) , Ok ( Amount ::from_sat ( 100_000_00 ) ) ) ;
assert_eq! ( sp ( " -100 bits " ) , Ok ( SignedAmount ::from_sat ( - 10_000 ) ) ) ;
}
#[ 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! (
ua_str ( & ua_sat ( u64 ::max_value ( ) ) . to_string_in ( D ::MilliBitcoin ) , D ::MilliBitcoin ) ,
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! (
sa_str ( & sa_sat ( i64 ::max_value ( ) ) . to_string_in ( D ::Satoshi ) , D ::MicroBitcoin ) ,
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! (
sa_str ( & sa_sat ( i64 ::min_value ( ) ) . to_string_in ( D ::Satoshi ) , D ::MicroBitcoin ) ,
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! (
sa_str ( & sa_sat ( i64 ::max_value ( ) ) . to_string_in ( D ::Satoshi ) , D ::NanoBitcoin ) ,
Err ( ParseAmountError ::TooPrecise )
) ;
assert_eq! (
sa_str ( & sa_sat ( i64 ::min_value ( ) ) . to_string_in ( D ::Satoshi ) , D ::NanoBitcoin ) ,
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! (
sa_str ( & sa_sat ( i64 ::max_value ( ) ) . to_string_in ( D ::Satoshi ) , D ::PicoBitcoin ) ,
Err ( ParseAmountError ::TooPrecise )
) ;
assert_eq! (
sa_str ( & sa_sat ( i64 ::min_value ( ) ) . to_string_in ( D ::Satoshi ) , D ::PicoBitcoin ) ,
Err ( ParseAmountError ::TooPrecise )
) ;
2020-01-21 17:43:10 +00:00
}
#[ test ]
fn to_string_with_denomination_from_str_roundtrip ( ) {
use super ::Denomination as D ;
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
assert_eq! ( Amount ::from_str ( " 42 satoshi BTC " ) , Err ( ParseAmountError ::InvalidFormat ) ) ;
assert_eq! ( SignedAmount ::from_str ( " -42 satoshi BTC " ) , Err ( ParseAmountError ::InvalidFormat ) ) ;
2018-11-10 00:45:00 +00:00
}
#[ cfg(feature = " serde " ) ]
#[ test ]
fn serde_as_sat ( ) {
#[ derive(Serialize, Deserialize, PartialEq, Debug) ]
2022-05-25 06:41:59 +00:00
#[ serde(crate = " actual_serde " ) ]
2018-11-10 00:45:00 +00:00
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) ]
2022-05-25 06:41:59 +00:00
#[ serde(crate = " actual_serde " ) ]
2018-11-10 00:45:00 +00:00
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) ]
2022-05-25 06:41:59 +00:00
#[ serde(crate = " actual_serde " ) ]
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) ]
2022-05-25 06:41:59 +00:00
#[ serde(crate = " actual_serde " ) ]
2021-01-14 18:45:51 +00:00
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 =
vec! [ Amount ::from_sat ( u64 ::max_value ( ) ) , 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! [
SignedAmount ::from_sat ( i64 ::min_value ( ) ) ,
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 ( i64 ::max_value ( ) ) ,
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! [
" BTC " , " btc " , " mBTC " , " mbtc " , " uBTC " , " ubtc " , " SATOSHI " , " Satoshi " , " Satoshis " ,
" satoshis " , " SAT " , " Sat " , " sats " , " bit " , " bits " , " nBTC " , " pBTC " ,
] ;
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 ( ) {
// Non-exhaustive list of confusing forms.
2022-11-15 23:22:16 +00:00
let confusing =
vec! [ " 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 ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::PossiblyConfusingDenomination ( _ ) ) = > { }
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.
let unknown = vec! [ " NBTC " , " UBTC " , " ABC " , " abc " ] ;
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 ) ,
2022-11-15 23:22:16 +00:00
Err ( ParseAmountError ::UnknownDenomination ( _ ) ) = > { }
2022-01-11 14:49:58 +00:00
Err ( e ) = > panic! ( " unexpected error: {} " , e ) ,
}
}
}
2018-11-10 00:45:00 +00:00
}