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.
2024-03-31 01:03:18 +00:00
#[ cfg(feature = " alloc " ) ]
use alloc ::string ::{ String , ToString } ;
2022-11-15 23:22:16 +00:00
use core ::cmp ::Ordering ;
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
use core ::fmt ::Write as _ ;
2022-11-15 23:22:16 +00:00
use core ::str ::FromStr ;
2024-03-31 01:03:18 +00:00
use core ::{ default , fmt , ops } ;
2022-11-15 23:22:16 +00:00
2023-08-08 08:47:05 +00:00
#[ cfg(feature = " serde " ) ]
use ::serde ::{ Deserialize , Serialize } ;
2023-11-20 00:52:19 +00:00
use internals ::error ::InputString ;
2023-11-17 21:01:39 +00:00
use internals ::write_err ;
2023-08-08 08:47:05 +00:00
2018-11-10 00:45:00 +00:00
/// A set of denominations in which amounts can be expressed.
2023-01-10 21:52:44 +00:00
///
/// # Examples
/// ```
/// # use core::str::FromStr;
2023-08-08 08:47:05 +00:00
/// # use bitcoin_units::Amount;
2023-01-10 21:52:44 +00:00
///
/// assert_eq!(Amount::from_str("1 BTC").unwrap(), Amount::from_sat(100_000_000));
2023-03-20 15:36:25 +00:00
/// assert_eq!(Amount::from_str("1 cBTC").unwrap(), Amount::from_sat(1_000_000));
2023-01-10 21:52:44 +00:00
/// assert_eq!(Amount::from_str("1 mBTC").unwrap(), Amount::from_sat(100_000));
/// assert_eq!(Amount::from_str("1 uBTC").unwrap(), Amount::from_sat(100));
/// assert_eq!(Amount::from_str("10 nBTC").unwrap(), Amount::from_sat(1));
/// assert_eq!(Amount::from_str("10000 pBTC").unwrap(), Amount::from_sat(1));
/// assert_eq!(Amount::from_str("1 bit").unwrap(), Amount::from_sat(100));
/// assert_eq!(Amount::from_str("1 sat").unwrap(), Amount::from_sat(1));
/// assert_eq!(Amount::from_str("1000 msats").unwrap(), Amount::from_sat(1));
/// ```
2018-11-10 00:45:00 +00:00
#[ derive(Debug, Clone, Copy, Eq, PartialEq, Hash) ]
2023-03-21 19:21:37 +00:00
#[ non_exhaustive ]
2018-11-10 00:45:00 +00:00
pub enum Denomination {
/// BTC
Bitcoin ,
2023-03-20 15:36:25 +00:00
/// cBTC
CentiBitcoin ,
2018-11-10 00:45:00 +00:00
/// mBTC
MilliBitcoin ,
/// uBTC
MicroBitcoin ,
2022-01-10 14:30:28 +00:00
/// nBTC
NanoBitcoin ,
/// pBTC
PicoBitcoin ,
2018-11-10 00:45:00 +00:00
/// bits
Bit ,
/// satoshi
Satoshi ,
/// msat
MilliSatoshi ,
}
impl Denomination {
2023-12-08 19:45:03 +00:00
/// Convenience alias for `Denomination::Bitcoin`.
pub const BTC : Self = Denomination ::Bitcoin ;
/// Convenience alias for `Denomination::Satoshi`.
pub const SAT : Self = Denomination ::Satoshi ;
2018-11-10 00:45:00 +00:00
/// The number of decimal places more than a satoshi.
2021-11-23 16:40:28 +00:00
fn precision ( self ) -> i8 {
2018-11-10 00:45:00 +00:00
match self {
Denomination ::Bitcoin = > - 8 ,
2023-03-20 15:36:25 +00:00
Denomination ::CentiBitcoin = > - 6 ,
2018-11-10 00:45:00 +00:00
Denomination ::MilliBitcoin = > - 5 ,
Denomination ::MicroBitcoin = > - 2 ,
2022-01-10 14:30:28 +00:00
Denomination ::NanoBitcoin = > 1 ,
Denomination ::PicoBitcoin = > 4 ,
2018-11-10 00:45:00 +00:00
Denomination ::Bit = > - 2 ,
Denomination ::Satoshi = > 0 ,
Denomination ::MilliSatoshi = > 3 ,
}
}
2021-11-23 16:40:28 +00:00
/// Returns stringly representation of this
fn as_str ( self ) -> & 'static str {
match self {
2018-11-10 00:45:00 +00:00
Denomination ::Bitcoin = > " BTC " ,
2023-03-20 15:36:25 +00:00
Denomination ::CentiBitcoin = > " cBTC " ,
2018-11-10 00:45:00 +00:00
Denomination ::MilliBitcoin = > " mBTC " ,
Denomination ::MicroBitcoin = > " uBTC " ,
2022-01-10 14:30:28 +00:00
Denomination ::NanoBitcoin = > " nBTC " ,
Denomination ::PicoBitcoin = > " pBTC " ,
2018-11-10 00:45:00 +00:00
Denomination ::Bit = > " bits " ,
Denomination ::Satoshi = > " satoshi " ,
Denomination ::MilliSatoshi = > " msat " ,
2021-11-23 16:40:28 +00:00
}
}
2023-03-23 11:14:11 +00:00
/// The different str forms of denominations that are recognized.
fn forms ( s : & str ) -> Option < Self > {
match s {
" BTC " | " btc " = > Some ( Denomination ::Bitcoin ) ,
" cBTC " | " cbtc " = > Some ( Denomination ::CentiBitcoin ) ,
" mBTC " | " mbtc " = > Some ( Denomination ::MilliBitcoin ) ,
" uBTC " | " ubtc " = > Some ( Denomination ::MicroBitcoin ) ,
" nBTC " | " nbtc " = > Some ( Denomination ::NanoBitcoin ) ,
" pBTC " | " pbtc " = > Some ( Denomination ::PicoBitcoin ) ,
" bit " | " bits " | " BIT " | " BITS " = > Some ( Denomination ::Bit ) ,
" SATOSHI " | " satoshi " | " SATOSHIS " | " satoshis " | " SAT " | " sat " | " SATS " | " sats " = >
Some ( Denomination ::Satoshi ) ,
" mSAT " | " msat " | " mSATs " | " msats " = > Some ( Denomination ::MilliSatoshi ) ,
_ = > None ,
}
}
2021-11-23 16:40:28 +00:00
}
2023-03-23 11:14:11 +00:00
/// These form are ambigous and could have many meanings. For example, M could denote Mega or Milli.
/// If any of these forms are used, an error type PossiblyConfusingDenomination is returned.
const CONFUSING_FORMS : [ & str ; 9 ] =
[ " Msat " , " Msats " , " MSAT " , " MSATS " , " MSat " , " MSats " , " MBTC " , " Mbtc " , " PBTC " ] ;
2021-11-23 16:40:28 +00:00
impl fmt ::Display for Denomination {
2022-11-15 23:22:16 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result { f . write_str ( self . as_str ( ) ) }
2018-11-10 00:45:00 +00:00
}
impl FromStr for Denomination {
2023-11-17 21:01:39 +00:00
type Err = ParseDenominationError ;
2018-11-10 00:45:00 +00:00
2021-12-01 01:56:16 +00:00
/// Convert from a str to Denomination.
///
2022-01-11 14:49:58 +00:00
/// Any combination of upper and/or lower case, excluding uppercase of SI(m, u, n, p) is considered valid.
2022-01-10 14:30:28 +00:00
/// - Singular: BTC, mBTC, uBTC, nBTC, pBTC
2021-12-01 01:56:16 +00:00
/// - Plural or singular: sat, satoshi, bit, msat
///
2022-01-11 14:49:58 +00:00
/// Due to ambiguity between mega and milli, pico and peta we prohibit usage of leading capital 'M', 'P'.
2018-11-10 00:45:00 +00:00
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
2023-11-17 21:01:39 +00:00
use self ::ParseDenominationError ::* ;
2021-12-01 01:56:16 +00:00
2023-03-23 11:14:11 +00:00
if CONFUSING_FORMS . contains ( & s ) {
2023-11-20 00:52:19 +00:00
return Err ( PossiblyConfusing ( PossiblyConfusingDenominationError ( s . into ( ) ) ) ) ;
2023-03-23 11:14:11 +00:00
} ;
2021-12-01 01:56:16 +00:00
2023-03-23 11:14:11 +00:00
let form = self ::Denomination ::forms ( s ) ;
2021-12-01 01:56:16 +00:00
2023-11-20 00:52:19 +00:00
form . ok_or_else ( | | Unknown ( UnknownDenominationError ( s . into ( ) ) ) )
2021-12-01 01:56:16 +00:00
}
}
2023-12-16 15:59:15 +00:00
/// An error during amount parsing amount with denomination.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub enum ParseError {
/// Invalid amount.
Amount ( ParseAmountError ) ,
/// Invalid denomination.
Denomination ( ParseDenominationError ) ,
2024-02-19 12:33:40 +00:00
/// The denomination was not identified.
MissingDenomination ( MissingDenominationError ) ,
2023-12-16 15:59:15 +00:00
}
2024-02-28 23:21:50 +00:00
internals ::impl_from_infallible! ( ParseError ) ;
2023-12-16 15:59:15 +00:00
impl From < ParseAmountError > for ParseError {
fn from ( e : ParseAmountError ) -> Self { Self ::Amount ( e ) }
}
impl From < ParseDenominationError > for ParseError {
fn from ( e : ParseDenominationError ) -> Self { Self ::Denomination ( e ) }
}
2024-01-20 22:59:04 +00:00
impl From < OutOfRangeError > for ParseError {
fn from ( e : OutOfRangeError ) -> Self { Self ::Amount ( e . into ( ) ) }
}
2024-02-19 18:01:35 +00:00
impl From < TooPreciseError > for ParseError {
fn from ( e : TooPreciseError ) -> Self { Self ::Amount ( e . into ( ) ) }
}
2024-02-19 12:33:40 +00:00
impl From < MissingDigitsError > for ParseError {
fn from ( e : MissingDigitsError ) -> Self { Self ::Amount ( e . into ( ) ) }
}
2024-02-19 13:01:11 +00:00
impl From < InputTooLargeError > for ParseError {
fn from ( e : InputTooLargeError ) -> Self { Self ::Amount ( e . into ( ) ) }
}
2024-02-19 12:33:40 +00:00
impl From < InvalidCharacterError > for ParseError {
fn from ( e : InvalidCharacterError ) -> Self { Self ::Amount ( e . into ( ) ) }
}
2023-12-16 15:59:15 +00:00
impl fmt ::Display for ParseError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
ParseError ::Amount ( error ) = > write_err! ( f , " invalid amount " ; error ) ,
ParseError ::Denomination ( error ) = > write_err! ( f , " invalid denomination " ; error ) ,
2024-02-19 12:33:40 +00:00
// We consider this to not be a source because it currently doesn't contain useful
// information
2024-03-31 01:03:18 +00:00
ParseError ::MissingDenomination ( _ ) = >
f . write_str ( " the input doesn't contain a denomination " ) ,
2023-12-16 15:59:15 +00:00
}
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for ParseError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > {
match self {
ParseError ::Amount ( error ) = > Some ( error ) ,
ParseError ::Denomination ( error ) = > Some ( error ) ,
2024-02-19 12:33:40 +00:00
// We consider this to not be a source because it currently doesn't contain useful
// information
ParseError ::MissingDenomination ( _ ) = > None ,
2023-12-16 15:59:15 +00:00
}
}
}
2018-11-10 00:45:00 +00:00
/// An error during amount parsing.
#[ derive(Debug, Clone, PartialEq, Eq) ]
2022-05-31 04:29:50 +00:00
#[ non_exhaustive ]
2018-11-10 00:45:00 +00:00
pub enum ParseAmountError {
2024-01-20 22:59:04 +00:00
/// The amount is too big or too small.
OutOfRange ( OutOfRangeError ) ,
2018-11-10 00:45:00 +00:00
/// Amount has higher precision than supported by the type.
2024-02-19 18:01:35 +00:00
TooPrecise ( TooPreciseError ) ,
2024-02-19 12:33:40 +00:00
/// A digit was expected but not found.
MissingDigits ( MissingDigitsError ) ,
2018-11-10 00:45:00 +00:00
/// Input string was too large.
2024-02-19 13:01:11 +00:00
InputTooLarge ( InputTooLargeError ) ,
2018-11-10 00:45:00 +00:00
/// Invalid character in input.
2024-02-19 12:33:40 +00:00
InvalidCharacter ( InvalidCharacterError ) ,
}
2024-02-19 18:01:35 +00:00
impl From < TooPreciseError > for ParseAmountError {
2024-03-31 01:03:18 +00:00
fn from ( value : TooPreciseError ) -> Self { Self ::TooPrecise ( value ) }
2024-02-19 18:01:35 +00:00
}
2024-02-19 12:33:40 +00:00
impl From < MissingDigitsError > for ParseAmountError {
2024-03-31 01:03:18 +00:00
fn from ( value : MissingDigitsError ) -> Self { Self ::MissingDigits ( value ) }
2024-02-19 12:33:40 +00:00
}
2024-02-19 13:01:11 +00:00
impl From < InputTooLargeError > for ParseAmountError {
2024-03-31 01:03:18 +00:00
fn from ( value : InputTooLargeError ) -> Self { Self ::InputTooLarge ( value ) }
2024-02-19 13:01:11 +00:00
}
2024-02-19 12:33:40 +00:00
impl From < InvalidCharacterError > for ParseAmountError {
2024-03-31 01:03:18 +00:00
fn from ( value : InvalidCharacterError ) -> Self { Self ::InvalidCharacter ( value ) }
2018-11-10 00:45:00 +00:00
}
2024-02-28 23:21:50 +00:00
internals ::impl_from_infallible! ( ParseAmountError ) ;
2018-11-10 00:45:00 +00:00
impl fmt ::Display for ParseAmountError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
Make error types uniform
On our way to v1.0.0 we are defining a standard for our error types,
this includes:
- Uses the following derives (unless not possible, usually because of `io::Error`)
`#[derive(Debug, Clone, PartialEq, Eq)]`
- Has `non_exhaustive` unless we really know we can commit to not adding
anything.
Furthermore, we are trying to make the codebase easy to read. Error code
is write-once-read-many (well it should be) so if we make all the error
code super uniform the users can flick to an error and quickly see what
it includes. In an effort to achieve this I have made up a style and
over recent times have change much of the error code to that new style,
this PR audits _all_ error types in the code base and enforces the
style, specifically:
- Is layed out: definition, [impl block], Display impl, error::Error impl, From impls
- `error::Error` impl matches on enum even if it returns `None` for all variants
- Display/Error impls import enum variants locally
- match uses *self and `ref e`
- error::Error variants that return `Some` come first, `None` after
Re: non_exhaustive
To make dev and review easier I have added `non_exhaustive` to _every_
error type. We can then remove it error by error as we see fit. This is
because it takes a bit of thinking to do and review where as this patch
should not take much brain power to review.
2023-10-04 02:55:45 +00:00
use ParseAmountError ::* ;
2018-11-10 00:45:00 +00:00
match * self {
2024-02-19 12:33:40 +00:00
OutOfRange ( ref error ) = > write_err! ( f , " amount out of range " ; error ) ,
2024-02-19 18:01:35 +00:00
TooPrecise ( ref error ) = > write_err! ( f , " amount has a too high precision " ; error ) ,
2024-02-19 12:33:40 +00:00
MissingDigits ( ref error ) = > write_err! ( f , " the input has too few digits " ; error ) ,
2024-02-19 13:01:11 +00:00
InputTooLarge ( ref error ) = > write_err! ( f , " the input is too large " ; error ) ,
2024-02-19 12:33:40 +00:00
InvalidCharacter ( ref error ) = > write_err! ( f , " invalid character in the input " ; error ) ,
2023-11-17 21:01:39 +00:00
}
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for ParseAmountError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > {
use ParseAmountError ::* ;
match * self {
2024-02-19 18:01:35 +00:00
TooPrecise ( ref error ) = > Some ( error ) ,
2024-02-19 13:01:11 +00:00
InputTooLarge ( ref error ) = > Some ( error ) ,
2024-01-20 22:59:04 +00:00
OutOfRange ( ref error ) = > Some ( error ) ,
2024-02-19 12:33:40 +00:00
MissingDigits ( ref error ) = > Some ( error ) ,
InvalidCharacter ( ref error ) = > Some ( error ) ,
2024-01-20 22:59:04 +00:00
}
}
}
/// Returned when a parsed amount is too big or too small.
#[ derive(Debug, Copy, Clone, Eq, PartialEq) ]
pub struct OutOfRangeError {
is_signed : bool ,
is_greater_than_max : bool ,
}
impl OutOfRangeError {
/// Returns the minimum and maximum allowed values for the type that was parsed.
///
/// This can be used to give a hint to the user which values are allowed.
pub fn valid_range ( & self ) -> ( i64 , u64 ) {
match self . is_signed {
true = > ( i64 ::MIN , i64 ::MAX as u64 ) ,
false = > ( 0 , u64 ::MAX ) ,
}
}
/// Returns true if the input value was large than the maximum allowed value.
2024-03-31 01:03:18 +00:00
pub fn is_above_max ( & self ) -> bool { self . is_greater_than_max }
2024-01-20 22:59:04 +00:00
/// Returns true if the input value was smaller than the minimum allowed value.
2024-03-31 01:03:18 +00:00
pub fn is_below_min ( & self ) -> bool { ! self . is_greater_than_max }
2024-01-20 22:59:04 +00:00
2024-03-31 01:03:18 +00:00
pub ( crate ) fn too_big ( is_signed : bool ) -> Self { Self { is_signed , is_greater_than_max : true } }
2024-01-20 22:59:04 +00:00
pub ( crate ) fn too_small ( ) -> Self {
Self {
// implied - negative() is used for the other
is_signed : true ,
is_greater_than_max : false ,
}
}
pub ( crate ) fn negative ( ) -> Self {
Self {
// implied - too_small() is used for the other
is_signed : false ,
is_greater_than_max : false ,
}
}
}
impl fmt ::Display for OutOfRangeError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
if self . is_greater_than_max {
write! ( f , " the amount is greater than {} " , self . valid_range ( ) . 1 )
} else {
write! ( f , " the amount is less than {} " , self . valid_range ( ) . 0 )
2023-11-17 21:01:39 +00:00
}
}
}
2024-01-20 22:59:04 +00:00
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for OutOfRangeError { }
impl From < OutOfRangeError > for ParseAmountError {
2024-03-31 01:03:18 +00:00
fn from ( value : OutOfRangeError ) -> Self { ParseAmountError ::OutOfRange ( value ) }
2024-01-20 22:59:04 +00:00
}
2024-02-19 18:01:35 +00:00
/// Error returned when the input string has higher precision than satoshis.
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub struct TooPreciseError {
position : usize ,
}
impl fmt ::Display for TooPreciseError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self . position {
0 = > f . write_str ( " the amount is less than 1 satoshi but it's not zero " ) ,
2024-03-31 01:03:18 +00:00
pos = > write! (
f ,
" the digits starting from position {} represent a sub-satoshi amount " ,
pos
) ,
2024-02-19 18:01:35 +00:00
}
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for TooPreciseError { }
2024-02-19 13:01:11 +00:00
/// Error returned when the input string is too large.
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub struct InputTooLargeError {
len : usize ,
}
impl fmt ::Display for InputTooLargeError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self . len - INPUT_STRING_LEN_LIMIT {
2024-03-31 01:03:18 +00:00
1 = > write! (
f ,
" the input is one character longer than the maximum allowed length ({}) " ,
INPUT_STRING_LEN_LIMIT
) ,
n = > write! (
f ,
" the input is {} characters longer than the maximum allowed length ({}) " ,
n , INPUT_STRING_LEN_LIMIT
) ,
2024-02-19 13:01:11 +00:00
}
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for InputTooLargeError { }
2024-02-19 12:33:40 +00:00
/// Error returned when digits were expected in the input but there were none.
///
/// In particular, this is currently returned when the string is empty or only contains the minus sign.
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub struct MissingDigitsError {
kind : MissingDigitsKind ,
}
impl fmt ::Display for MissingDigitsError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self . kind {
MissingDigitsKind ::Empty = > f . write_str ( " the input is empty " ) ,
2024-03-31 01:03:18 +00:00
MissingDigitsKind ::OnlyMinusSign = >
f . write_str ( " there are no digits following the minus (-) sign " ) ,
2024-02-19 12:33:40 +00:00
}
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for MissingDigitsError { }
#[ derive(Debug, Clone, Eq, PartialEq) ]
enum MissingDigitsKind {
Empty ,
OnlyMinusSign ,
}
/// Returned when the input contains an invalid character.
#[ derive(Debug, Clone, PartialEq, Eq) ]
pub struct InvalidCharacterError {
invalid_char : char ,
2024-02-24 08:37:00 +00:00
position : usize ,
2024-02-19 12:33:40 +00:00
}
impl fmt ::Display for InvalidCharacterError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self . invalid_char {
'.' = > f . write_str ( " there is more than one decimal separator (dot) in the input " ) ,
'-' = > f . write_str ( " there is more than one minus sign (-) in the input " ) ,
2024-03-31 01:03:18 +00:00
c = > write! (
f ,
" the character '{}' at position {} is not a valid digit " ,
c , self . position
) ,
2024-02-19 12:33:40 +00:00
}
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for InvalidCharacterError { }
2024-01-20 22:59:04 +00:00
2023-11-17 21:01:39 +00:00
/// An error during amount parsing.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub enum ParseDenominationError {
/// The denomination was unknown.
2023-11-20 00:52:19 +00:00
Unknown ( UnknownDenominationError ) ,
2023-11-17 21:01:39 +00:00
/// The denomination has multiple possible interpretations.
2023-11-20 00:52:19 +00:00
PossiblyConfusing ( PossiblyConfusingDenominationError ) ,
2023-11-17 21:01:39 +00:00
}
2024-02-28 23:21:50 +00:00
internals ::impl_from_infallible! ( ParseDenominationError ) ;
2023-11-17 21:01:39 +00:00
impl fmt ::Display for ParseDenominationError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
use ParseDenominationError ::* ;
match * self {
2023-11-20 00:52:19 +00:00
Unknown ( ref e ) = > write_err! ( f , " denomination parse error " ; e ) ,
PossiblyConfusing ( ref e ) = > write_err! ( f , " denomination parse error " ; e ) ,
2018-11-10 00:45:00 +00:00
}
}
}
2021-06-09 10:34:44 +00:00
#[ cfg(feature = " std " ) ]
2023-11-17 21:01:39 +00:00
impl std ::error ::Error for ParseDenominationError {
2022-06-10 02:07:46 +00:00
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > {
2023-11-17 21:01:39 +00:00
use ParseDenominationError ::* ;
2022-06-10 02:07:46 +00:00
match * self {
2023-11-17 21:01:39 +00:00
Unknown ( _ ) | PossiblyConfusing ( _ ) = > None ,
2022-06-10 02:07:46 +00:00
}
}
}
2019-08-06 14:32:51 +00:00
2024-02-19 12:33:40 +00:00
/// Error returned when the denomination is empty.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub struct MissingDenominationError ;
2023-11-20 00:52:19 +00:00
/// Parsing error, unknown denomination.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub struct UnknownDenominationError ( InputString ) ;
impl fmt ::Display for UnknownDenominationError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
self . 0. unknown_variant ( " bitcoin denomination " , f )
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for UnknownDenominationError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > { None }
}
/// Parsing error, possibly confusing denomination.
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ non_exhaustive ]
pub struct PossiblyConfusingDenominationError ( InputString ) ;
impl fmt ::Display for PossiblyConfusingDenominationError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
write! ( f , " {}: possibly confusing denomination - we intentionally do not support 'M' and 'P' so as to not confuse mega/milli and peta/pico " , self . 0. display_cannot_parse ( " bitcoin denomination " ) )
}
}
#[ cfg(feature = " std " ) ]
impl std ::error ::Error for PossiblyConfusingDenominationError {
fn source ( & self ) -> Option < & ( dyn std ::error ::Error + 'static ) > { None }
}
2024-02-19 18:01:35 +00:00
/// Returns `Some(position)` if the precision is not supported.
///
/// The position indicates the first digit that is too precise.
fn is_too_precise ( s : & str , precision : usize ) -> Option < usize > {
2023-05-07 06:45:25 +00:00
match s . find ( '.' ) {
2024-03-31 01:03:18 +00:00
Some ( pos ) if precision > = pos = > Some ( 0 ) ,
Some ( pos ) = > s [ .. pos ]
. char_indices ( )
. rev ( )
. take ( precision )
. find ( | ( _ , d ) | * d ! = '0' )
. map ( | ( i , _ ) | i )
. or_else ( | | {
s [ ( pos + 1 ) .. ] . char_indices ( ) . find ( | ( _ , d ) | * d ! = '0' ) . map ( | ( i , _ ) | i + pos + 1 )
} ) ,
None if precision > = s . len ( ) = > Some ( 0 ) ,
None = > s . char_indices ( ) . rev ( ) . take ( precision ) . find ( | ( _ , d ) | * d ! = '0' ) . map ( | ( i , _ ) | i ) ,
2023-05-07 06:45:25 +00:00
}
2019-08-06 14:32:51 +00:00
}
2024-02-19 13:01:11 +00:00
const INPUT_STRING_LEN_LIMIT : usize = 50 ;
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 ,
2024-01-20 22:59:04 +00:00
) -> Result < ( bool , u64 ) , InnerParseError > {
2019-08-05 19:41:07 +00:00
if s . is_empty ( ) {
2024-03-31 01:03:18 +00:00
return Err ( InnerParseError ::MissingDigits ( MissingDigitsError {
kind : MissingDigitsKind ::Empty ,
} ) ) ;
2018-11-10 00:45:00 +00:00
}
2024-02-19 13:01:11 +00:00
if s . len ( ) > INPUT_STRING_LEN_LIMIT {
return Err ( InnerParseError ::InputTooLarge ( s . len ( ) ) ) ;
2018-11-10 00:45:00 +00:00
}
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 {
2024-03-31 01:03:18 +00:00
return Err ( InnerParseError ::MissingDigits ( MissingDigitsError {
kind : MissingDigitsKind ::OnlyMinusSign ,
} ) ) ;
2018-11-10 00:45:00 +00:00
}
s = & s [ 1 .. ] ;
}
let max_decimals = {
// The difference in precision between native (satoshi)
// and desired denomination.
let precision_diff = - denom . precision ( ) ;
2023-05-07 06:45:25 +00:00
if precision_diff < = 0 {
2018-11-10 00:45:00 +00:00
// If precision diff is negative, this means we are parsing
// into a less precise amount. That is not allowed unless
// there are no decimals and the last digits are zeroes as
2019-08-04 19:27:36 +00:00
// many as the difference in precision.
2023-12-16 15:38:26 +00:00
let last_n = precision_diff . unsigned_abs ( ) . into ( ) ;
2024-02-19 18:01:35 +00:00
if let Some ( position ) = is_too_precise ( s , last_n ) {
2023-01-12 06:58:02 +00:00
match s . parse ::< i64 > ( ) {
2023-10-06 04:59:59 +00:00
Ok ( 0 ) = > return Ok ( ( is_negative , 0 ) ) ,
2024-03-31 01:03:18 +00:00
_ = >
return Err ( InnerParseError ::TooPrecise ( TooPreciseError {
position : position + is_negative as usize ,
} ) ) ,
2023-01-12 06:58:02 +00:00
}
2018-11-10 00:45:00 +00:00
}
2023-05-07 06:45:25 +00:00
s = & s [ 0 .. s . find ( '.' ) . unwrap_or ( s . len ( ) ) - last_n ] ;
2018-11-10 00:45:00 +00:00
0
} else {
precision_diff
}
} ;
let mut decimals = None ;
let mut value : u64 = 0 ; // as satoshis
2024-02-19 18:01:35 +00:00
for ( i , c ) in s . char_indices ( ) {
2018-11-10 00:45:00 +00:00
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 ) {
2024-01-20 22:59:04 +00:00
None = > return Err ( InnerParseError ::Overflow { is_negative } ) ,
2018-11-10 00:45:00 +00:00
Some ( val ) = > match val . checked_add ( ( c as u8 - b '0' ) as u64 ) {
2024-01-20 22:59:04 +00:00
None = > return Err ( InnerParseError ::Overflow { is_negative } ) ,
2018-11-10 00:45:00 +00:00
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 ) ,
2024-03-31 01:03:18 +00:00
_ = >
return Err ( InnerParseError ::TooPrecise ( TooPreciseError {
position : i + is_negative as usize ,
} ) ) ,
2018-11-10 00:45:00 +00:00
} ;
}
'.' = > match decimals {
2023-05-07 06:45:25 +00:00
None if max_decimals < = 0 = > break ,
2018-11-10 00:45:00 +00:00
None = > decimals = Some ( 0 ) ,
// Double decimal dot.
2024-03-31 01:03:18 +00:00
_ = >
return Err ( InnerParseError ::InvalidCharacter ( InvalidCharacterError {
invalid_char : ' . ' ,
position : i + is_negative as usize ,
} ) ) ,
2018-11-10 00:45:00 +00:00
} ,
2024-03-31 01:03:18 +00:00
c = >
return Err ( InnerParseError ::InvalidCharacter ( InvalidCharacterError {
invalid_char : c ,
position : i + is_negative as usize ,
} ) ) ,
2018-11-10 00:45:00 +00:00
}
}
// 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 ,
2024-01-20 22:59:04 +00:00
None = > return Err ( InnerParseError ::Overflow { is_negative } ) ,
2018-11-10 00:45:00 +00:00
} ;
}
2019-08-06 14:32:51 +00:00
Ok ( ( is_negative , value ) )
2018-11-10 00:45:00 +00:00
}
2024-01-20 22:59:04 +00:00
enum InnerParseError {
Overflow { is_negative : bool } ,
2024-02-19 18:01:35 +00:00
TooPrecise ( TooPreciseError ) ,
2024-02-19 12:33:40 +00:00
MissingDigits ( MissingDigitsError ) ,
2024-02-19 13:01:11 +00:00
InputTooLarge ( usize ) ,
2024-02-19 12:33:40 +00:00
InvalidCharacter ( InvalidCharacterError ) ,
2024-01-20 22:59:04 +00:00
}
2024-02-28 23:21:50 +00:00
internals ::impl_from_infallible! ( InnerParseError ) ;
2024-01-20 22:59:04 +00:00
impl InnerParseError {
fn convert ( self , is_signed : bool ) -> ParseAmountError {
match self {
2024-03-31 01:03:18 +00:00
Self ::Overflow { is_negative } = >
OutOfRangeError { is_signed , is_greater_than_max : ! is_negative } . into ( ) ,
2024-02-19 18:01:35 +00:00
Self ::TooPrecise ( error ) = > ParseAmountError ::TooPrecise ( error ) ,
2024-02-19 12:33:40 +00:00
Self ::MissingDigits ( error ) = > ParseAmountError ::MissingDigits ( error ) ,
2024-02-19 13:01:11 +00:00
Self ::InputTooLarge ( len ) = > ParseAmountError ::InputTooLarge ( InputTooLargeError { len } ) ,
2024-02-19 12:33:40 +00:00
Self ::InvalidCharacter ( error ) = > ParseAmountError ::InvalidCharacter ( error ) ,
2024-01-20 22:59:04 +00:00
}
}
}
2023-12-16 15:59:15 +00:00
fn split_amount_and_denomination ( s : & str ) -> Result < ( & str , Denomination ) , ParseError > {
2022-11-02 22:36:37 +00:00
let ( i , j ) = if let Some ( i ) = s . find ( ' ' ) {
( i , i + 1 )
} else {
2024-03-31 01:03:18 +00:00
let i = s
. find ( | c : char | c . is_alphabetic ( ) )
. ok_or ( ParseError ::MissingDenomination ( MissingDenominationError ) ) ? ;
2022-11-02 22:36:37 +00:00
( i , i )
} ;
Ok ( ( & s [ .. i ] , s [ j .. ] . parse ( ) ? ) )
2022-12-31 22:19:56 +00:00
}
2021-11-23 16:40:28 +00:00
/// Options given by `fmt::Formatter`
struct FormatOptions {
fill : char ,
align : Option < fmt ::Alignment > ,
width : Option < usize > ,
precision : Option < usize > ,
sign_plus : bool ,
sign_aware_zero_pad : bool ,
}
impl FormatOptions {
fn from_formatter ( f : & fmt ::Formatter ) -> Self {
FormatOptions {
fill : f . fill ( ) ,
align : f . align ( ) ,
width : f . width ( ) ,
precision : f . precision ( ) ,
sign_plus : f . sign_plus ( ) ,
sign_aware_zero_pad : f . sign_aware_zero_pad ( ) ,
}
}
}
impl Default for FormatOptions {
fn default ( ) -> Self {
FormatOptions {
fill : ' ' ,
align : None ,
width : None ,
precision : None ,
sign_plus : false ,
sign_aware_zero_pad : false ,
}
}
}
fn dec_width ( mut num : u64 ) -> usize {
let mut width = 1 ;
loop {
num / = 10 ;
if num = = 0 {
break ;
}
width + = 1 ;
}
width
}
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 = > {
2023-12-16 15:38:26 +00:00
let precision = precision . unsigned_abs ( ) ;
2021-11-23 16:40:28 +00:00
let divisor = 10 u64 . pow ( precision . into ( ) ) ;
num_before_decimal_point = satoshi / divisor ;
num_after_decimal_point = satoshi % divisor ;
// normalize by stripping trailing zeros
if num_after_decimal_point = = 0 {
norm_nb_decimals = 0 ;
2019-08-05 19:41:07 +00:00
} else {
2021-11-23 16:40:28 +00:00
norm_nb_decimals = usize ::from ( precision ) ;
while num_after_decimal_point % 10 = = 0 {
norm_nb_decimals - = 1 ;
num_after_decimal_point / = 10
}
2019-08-05 19:41:07 +00:00
}
2021-11-23 16:40:28 +00:00
// compute requested precision
let opt_precision = options . precision . unwrap_or ( 0 ) ;
trailing_decimal_zeros = opt_precision . saturating_sub ( norm_nb_decimals ) ;
2022-11-15 23:22:16 +00:00
}
2021-11-23 16:40:28 +00:00
Ordering ::Equal = > trailing_decimal_zeros = options . precision . unwrap_or ( 0 ) ,
}
let total_decimals = norm_nb_decimals + trailing_decimal_zeros ;
// Compute expected width of the number
let mut num_width = if total_decimals > 0 {
// 1 for decimal point
1 + total_decimals
} else {
0
} ;
num_width + = dec_width ( num_before_decimal_point ) + exp ;
if options . sign_plus | | negative {
num_width + = 1 ;
2018-11-10 00:45:00 +00:00
}
2021-11-23 16:40:28 +00:00
if show_denom {
// + 1 for space
num_width + = denom . as_str ( ) . len ( ) + 1 ;
}
let width = options . width . unwrap_or ( 0 ) ;
2022-11-18 02:09:28 +00:00
let align = options . align . unwrap_or ( fmt ::Alignment ::Right ) ;
let ( left_pad , pad_right ) = match ( num_width < width , options . sign_aware_zero_pad , align ) {
2021-11-23 16:40:28 +00:00
( false , _ , _ ) = > ( 0 , 0 ) ,
// Alignment is always right (ignored) when zero-padding
( true , true , _ ) | ( true , false , fmt ::Alignment ::Right ) = > ( width - num_width , 0 ) ,
( true , false , fmt ::Alignment ::Left ) = > ( 0 , width - num_width ) ,
// If the required padding is odd it needs to be skewed to the left
2022-11-15 23:22:16 +00:00
( true , false , fmt ::Alignment ::Center ) = >
( ( width - num_width ) / 2 , ( width - num_width + 1 ) / 2 ) ,
2021-11-23 16:40:28 +00:00
} ;
if ! options . sign_aware_zero_pad {
repeat_char ( f , options . fill , left_pad ) ? ;
}
if negative {
write! ( f , " - " ) ? ;
} else if options . sign_plus {
write! ( f , " + " ) ? ;
}
if options . sign_aware_zero_pad {
repeat_char ( f , '0' , left_pad ) ? ;
}
write! ( f , " {} " , num_before_decimal_point ) ? ;
repeat_char ( f , '0' , exp ) ? ;
if total_decimals > 0 {
write! ( f , " . " ) ? ;
}
if norm_nb_decimals > 0 {
write! ( f , " {:0width$} " , num_after_decimal_point , width = norm_nb_decimals ) ? ;
}
repeat_char ( f , '0' , trailing_decimal_zeros ) ? ;
if show_denom {
write! ( f , " {} " , denom . as_str ( ) ) ? ;
}
repeat_char ( f , options . fill , pad_right ) ? ;
2018-11-10 00:45:00 +00:00
Ok ( ( ) )
}
/// Amount
///
2022-12-19 08:32:52 +00:00
/// The [Amount] type can be used to express Bitcoin amounts that support
2019-08-04 19:27:36 +00:00
/// arithmetic and conversion to various denominations.
2018-11-10 00:45:00 +00:00
///
///
/// Warning!
///
2021-06-09 10:40:41 +00:00
/// This type implements several arithmetic operations from [core::ops].
2018-11-10 00:45:00 +00:00
/// To prevent errors due to overflow or underflow when using these operations,
/// it is advised to instead use the checked arithmetic methods whose names
2021-06-09 10:40:41 +00:00
/// start with `checked_`. The operations from [core::ops] that [Amount]
2018-11-10 00:45:00 +00:00
/// implements will panic when overflow or underflow occurs. Also note that
/// since the internal representation of amounts is unsigned, subtracting below
/// zero is considered an underflow and will cause a panic if you're not using
/// the checked arithmetic methods.
///
2019-08-05 19:44:32 +00:00
#[ derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash) ]
2023-04-24 14:47:55 +00:00
#[ cfg_attr(feature = " serde " , derive(Serialize, Deserialize)) ]
2018-11-10 00:45:00 +00:00
pub struct Amount ( u64 ) ;
impl Amount {
/// The zero amount.
pub const ZERO : Amount = Amount ( 0 ) ;
/// Exactly one satoshi.
pub const ONE_SAT : Amount = Amount ( 1 ) ;
/// Exactly one bitcoin.
2023-06-13 09:05:08 +00:00
pub const ONE_BTC : Amount = Self ::from_int_btc ( 1 ) ;
2021-12-14 16:54:39 +00:00
/// The maximum value allowed as an amount. Useful for sanity checking.
2023-06-13 09:05:08 +00:00
pub const MAX_MONEY : Amount = Self ::from_int_btc ( 21_000_000 ) ;
2023-05-02 22:14:44 +00:00
/// The minimum value of an amount.
pub const MIN : Amount = Amount ::ZERO ;
/// The maximum value of an amount.
pub const MAX : Amount = Amount ( u64 ::MAX ) ;
2023-09-17 21:51:41 +00:00
/// The number of bytes that an amount contributes to the size of a transaction.
pub const SIZE : usize = 8 ; // Serialized length of a u64.
2018-11-10 00:45:00 +00:00
/// Create an [Amount] with satoshi precision and the given number of satoshis.
2022-11-15 23:22:16 +00:00
pub const fn from_sat ( satoshi : u64 ) -> Amount { Amount ( satoshi ) }
2018-11-10 00:45:00 +00:00
2022-01-19 03:05:01 +00:00
/// Gets the number of satoshis in this [`Amount`].
2022-11-15 23:22:16 +00:00
pub fn to_sat ( self ) -> u64 { self . 0 }
2018-11-10 00:45:00 +00:00
/// Convert from a value expressing bitcoins to an [Amount].
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
pub fn from_btc ( btc : f64 ) -> Result < Amount , ParseAmountError > {
Amount ::from_float_in ( btc , Denomination ::Bitcoin )
}
2023-06-13 08:55:04 +00:00
/// Convert from a value expressing integer values of bitcoins to an [Amount]
/// in const context.
///
/// ## Panics
///
/// The function panics if the argument multiplied by the number of sats
/// per bitcoin overflows a u64 type.
pub const fn from_int_btc ( btc : u64 ) -> Amount {
match btc . checked_mul ( 100_000_000 ) {
Some ( amount ) = > Amount ::from_sat ( amount ) ,
None = > {
2024-02-01 01:37:53 +00:00
// When MSRV is 1.57+ we can use `panic!()`.
2023-06-13 08:55:04 +00:00
#[ allow(unconditional_panic) ]
#[ allow(clippy::let_unit_value) ]
2023-12-31 20:32:25 +00:00
#[ allow(clippy::out_of_bounds_indexing) ]
2023-06-13 08:55:04 +00:00
let _int_overflow_converting_btc_to_sats = [ ( ) ; 0 ] [ 1 ] ;
Amount ( 0 )
}
}
}
2018-11-10 00:45:00 +00:00
/// Parse a decimal string as a value in the given denomination.
///
/// Note: This only parses the value string. If you want to parse a value
/// with denomination, use [FromStr].
pub fn from_str_in ( s : & str , denom : Denomination ) -> Result < Amount , ParseAmountError > {
2024-03-31 01:03:18 +00:00
let ( negative , satoshi ) =
parse_signed_to_satoshi ( s , denom ) . map_err ( | error | error . convert ( false ) ) ? ;
2018-11-10 00:45:00 +00:00
if negative {
2024-01-20 22:59:04 +00:00
return Err ( ParseAmountError ::OutOfRange ( OutOfRangeError ::negative ( ) ) ) ;
2018-11-10 00:45:00 +00:00
}
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].
2023-12-16 15:59:15 +00:00
pub fn from_str_with_denomination ( s : & str ) -> Result < Amount , ParseError > {
2022-12-31 22:19:56 +00:00
let ( amt , denom ) = split_amount_and_denomination ( s ) ? ;
2023-12-16 15:59:15 +00:00
Amount ::from_str_in ( amt , denom ) . map_err ( Into ::into )
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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2019-08-05 18:52:34 +00:00
pub fn to_float_in ( self , denom : Denomination ) -> f64 {
2018-11-10 00:45:00 +00:00
f64 ::from_str ( & self . to_string_in ( denom ) ) . unwrap ( )
}
2022-01-19 03:05:01 +00:00
/// Express this [`Amount`] as a floating-point value in Bitcoin.
2018-11-10 00:45:00 +00:00
///
/// Please be aware of the risk of using floating-point numbers.
2022-01-19 03:05:01 +00:00
///
/// # Examples
/// ```
2023-08-08 08:47:05 +00:00
/// # use bitcoin_units::amount::{Amount, Denomination};
2022-01-19 03:05:01 +00:00
/// let amount = Amount::from_sat(100_000);
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin))
/// ```
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
pub fn from_float_in ( value : f64 , denom : Denomination ) -> Result < Amount , ParseAmountError > {
if value < 0.0 {
2024-01-20 22:59:04 +00:00
return Err ( OutOfRangeError ::negative ( ) . into ( ) ) ;
2018-11-10 00:45:00 +00:00
}
// 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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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.
2024-02-29 14:21:22 +00:00
///
2018-11-10 00:45:00 +00:00
/// Returns [None] if overflow occurred.
pub fn checked_add ( self , rhs : Amount ) -> Option < Amount > {
self . 0. checked_add ( rhs . 0 ) . map ( Amount )
}
/// Checked subtraction.
2024-02-29 14:21:22 +00:00
///
2018-11-10 00:45:00 +00:00
/// Returns [None] if overflow occurred.
pub fn checked_sub ( self , rhs : Amount ) -> Option < Amount > {
self . 0. checked_sub ( rhs . 0 ) . map ( Amount )
}
/// Checked multiplication.
2024-02-29 14:21:22 +00:00
///
2018-11-10 00:45:00 +00:00
/// 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.
2024-02-29 14:21:22 +00:00
///
2018-11-10 00:45:00 +00:00
/// 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.
2024-02-29 14:21:22 +00:00
///
2018-11-10 00:45:00 +00:00
/// 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
2024-02-03 11:03:49 +00:00
/// Unchecked addition.
///
/// Computes `self + rhs`. Panics in debug mode, wraps in release mode.
2024-03-31 01:03:18 +00:00
pub fn unchecked_add ( self , rhs : Amount ) -> Amount { Self ( self . 0 + rhs . 0 ) }
2024-02-03 11:03:49 +00:00
/// Unchecked subtraction.
///
/// Computes `self - rhs`. Panics in debug mode, wraps in release mode.
2024-03-31 01:03:18 +00:00
pub fn unchecked_sub ( self , rhs : Amount ) -> Amount { Self ( self . 0 - rhs . 0 ) }
2024-02-03 11:03:49 +00:00
2018-11-10 00:45:00 +00:00
/// Convert to a signed amount.
2024-02-09 12:45:37 +00:00
pub fn to_signed ( self ) -> Result < SignedAmount , OutOfRangeError > {
2023-05-02 22:16:20 +00:00
if self . to_sat ( ) > SignedAmount ::MAX . to_sat ( ) as u64 {
2024-02-09 12:45:37 +00:00
Err ( OutOfRangeError ::too_big ( true ) )
2018-11-10 00:45:00 +00:00
} 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 {
2024-03-31 01:03:18 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result { write! ( f , " {} SAT " , self . to_sat ( ) ) }
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 {
2024-03-16 13:05:06 +00:00
let satoshis = self . to_sat ( ) ;
let denomination = Denomination ::Bitcoin ;
let mut format_options = FormatOptions ::from_formatter ( f ) ;
if f . precision ( ) . is_none ( ) & & satoshis . rem_euclid ( Amount ::ONE_BTC . to_sat ( ) ) ! = 0 {
format_options . precision = Some ( 8 ) ;
}
fmt_satoshi_in ( satoshis , false , f , denomination , true , format_options )
2018-11-10 00:45:00 +00:00
}
}
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 {
2023-12-16 15:59:15 +00:00
type Err = ParseError ;
2018-11-10 00:45:00 +00:00
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
}
2024-02-09 12:45:37 +00:00
impl TryFrom < SignedAmount > for Amount {
type Error = OutOfRangeError ;
2024-03-31 01:03:18 +00:00
fn try_from ( value : SignedAmount ) -> Result < Self , Self ::Error > { value . to_unsigned ( ) }
2024-02-09 12:45:37 +00:00
}
2022-06-01 22:08:56 +00:00
impl core ::iter ::Sum for Amount {
2022-01-24 00:26:29 +00:00
fn sum < I : Iterator < Item = Self > > ( iter : I ) -> Self {
2021-06-12 14:46:04 +00:00
let sats : u64 = iter . map ( | amt | amt . 0 ) . sum ( ) ;
Amount ::from_sat ( sats )
}
}
2021-11-23 16:40:28 +00:00
/// A helper/builder that displays amount with specified settings.
///
/// This provides richer interface than `fmt::Formatter`:
///
/// * Ability to select denomination
/// * Show or hide denomination
/// * Dynamically-selected denomination - show in sats if less than 1 BTC.
///
/// However this can still be combined with `fmt::Formatter` options to precisely control zeros,
/// padding, alignment... The formatting works like floats from `core` but note that precision will
/// **never** be lossy - that means no rounding.
///
/// See [`Amount::display_in`] and [`Amount::display_dynamic`] on how to construct this.
#[ derive(Debug, Clone) ]
pub struct Display {
/// Absolute value of satoshis to display (sign is below)
sats_abs : u64 ,
/// The sign
is_negative : bool ,
/// How to display the value
style : DisplayStyle ,
}
impl Display {
/// Makes subsequent calls to `Display::fmt` display denomination.
pub fn show_denomination ( mut self ) -> Self {
match & mut self . style {
DisplayStyle ::FixedDenomination { show_denomination , .. } = > * show_denomination = true ,
// No-op because dynamic denomination is always shown
DisplayStyle ::DynamicDenomination = > ( ) ,
}
self
}
}
impl fmt ::Display for Display {
2022-11-15 23:10:54 +00:00
#[ rustfmt::skip ]
2021-11-23 16:40:28 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
let format_options = FormatOptions ::from_formatter ( f ) ;
match & self . style {
2022-11-15 23:10:54 +00:00
DisplayStyle ::FixedDenomination { show_denomination , denomination } = > {
fmt_satoshi_in ( self . sats_abs , self . is_negative , f , * denomination , * show_denomination , format_options )
} ,
2022-01-19 03:05:01 +00:00
DisplayStyle ::DynamicDenomination if self . sats_abs > = Amount ::ONE_BTC . to_sat ( ) = > {
2021-11-23 16:40:28 +00:00
fmt_satoshi_in ( self . sats_abs , self . is_negative , f , Denomination ::Bitcoin , true , format_options )
} ,
DisplayStyle ::DynamicDenomination = > {
fmt_satoshi_in ( self . sats_abs , self . is_negative , f , Denomination ::Satoshi , true , format_options )
} ,
}
}
}
#[ derive(Clone, Debug) ]
enum DisplayStyle {
2022-11-15 23:22:16 +00:00
FixedDenomination { denomination : Denomination , show_denomination : bool } ,
2021-11-23 16:40:28 +00:00
DynamicDenomination ,
}
2018-11-10 00:45:00 +00:00
/// SignedAmount
///
2022-12-19 08:32:52 +00:00
/// The [SignedAmount] type can be used to express Bitcoin amounts that support
2019-08-04 19:27:36 +00:00
/// arithmetic and conversion to various denominations.
2018-11-10 00:45:00 +00:00
///
///
/// Warning!
///
2021-06-09 10:40:41 +00:00
/// This type implements several arithmetic operations from [core::ops].
2018-11-10 00:45:00 +00:00
/// To prevent errors due to overflow or underflow when using these operations,
/// it is advised to instead use the checked arithmetic methods whose names
2021-06-09 10:40:41 +00:00
/// start with `checked_`. The operations from [core::ops] that [Amount]
2018-11-10 00:45:00 +00:00
/// implements will panic when overflow or underflow occurs.
///
2019-08-05 19:44:32 +00:00
#[ derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash) ]
2018-11-10 00:45:00 +00:00
pub struct SignedAmount ( i64 ) ;
impl SignedAmount {
/// The zero amount.
pub const ZERO : SignedAmount = SignedAmount ( 0 ) ;
/// Exactly one satoshi.
pub const ONE_SAT : SignedAmount = SignedAmount ( 1 ) ;
/// Exactly one bitcoin.
pub const ONE_BTC : SignedAmount = SignedAmount ( 100_000_000 ) ;
2021-12-14 16:54:39 +00:00
/// The maximum value allowed as an amount. Useful for sanity checking.
pub const MAX_MONEY : SignedAmount = SignedAmount ( 21_000_000 * 100_000_000 ) ;
2023-05-02 22:14:44 +00:00
/// The minimum value of an amount.
pub const MIN : SignedAmount = SignedAmount ( i64 ::MIN ) ;
/// The maximum value of an amount.
pub const MAX : SignedAmount = SignedAmount ( i64 ::MAX ) ;
2018-11-10 00:45:00 +00:00
/// Create an [SignedAmount] with satoshi precision and the given number of satoshis.
2022-11-15 23:22:16 +00:00
pub const fn from_sat ( satoshi : i64 ) -> SignedAmount { SignedAmount ( satoshi ) }
2018-11-10 00:45:00 +00:00
2022-01-19 03:05:01 +00:00
/// Gets the number of satoshis in this [`SignedAmount`].
2022-11-15 23:22:16 +00:00
pub fn to_sat ( self ) -> i64 { self . 0 }
2018-11-10 00:45:00 +00:00
/// Convert from a value expressing bitcoins to an [SignedAmount].
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
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 > {
2024-01-20 22:59:04 +00:00
match parse_signed_to_satoshi ( s , denom ) . map_err ( | error | error . convert ( true ) ) ? {
2023-12-12 10:33:44 +00:00
// (negative, amount)
2024-03-31 01:03:18 +00:00
( false , sat ) if sat > i64 ::MAX as u64 = >
Err ( ParseAmountError ::OutOfRange ( OutOfRangeError ::too_big ( true ) ) ) ,
2023-12-17 00:59:05 +00:00
( false , sat ) = > Ok ( SignedAmount ( sat as i64 ) ) ,
2024-01-20 22:44:11 +00:00
( true , sat ) if sat = = i64 ::MIN . unsigned_abs ( ) = > Ok ( SignedAmount ( i64 ::MIN ) ) ,
2024-03-31 01:03:18 +00:00
( true , sat ) if sat > i64 ::MIN . unsigned_abs ( ) = >
Err ( ParseAmountError ::OutOfRange ( OutOfRangeError ::too_small ( ) ) ) ,
2023-12-17 00:59:05 +00:00
( true , sat ) = > Ok ( SignedAmount ( - ( sat as i64 ) ) ) ,
2018-11-10 00:45:00 +00:00
}
}
/// 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].
2023-12-16 15:59:15 +00:00
pub fn from_str_with_denomination ( s : & str ) -> Result < SignedAmount , ParseError > {
2022-12-31 22:19:56 +00:00
let ( amt , denom ) = split_amount_and_denomination ( s ) ? ;
2023-12-16 15:59:15 +00:00
SignedAmount ::from_str_in ( amt , denom ) . map_err ( Into ::into )
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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
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
/// Create an object that implements [`fmt::Display`] using specified denomination.
pub fn display_in ( self , denomination : Denomination ) -> Display {
Display {
2023-12-16 15:38:26 +00:00
sats_abs : self . unsigned_abs ( ) . to_sat ( ) ,
2021-11-23 16:40:28 +00:00
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 {
2023-12-16 15:38:26 +00:00
sats_abs : self . unsigned_abs ( ) . to_sat ( ) ,
2021-11-23 16:40:28 +00:00
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 {
2023-12-16 15:38:26 +00:00
fmt_satoshi_in ( self . unsigned_abs ( ) . to_sat ( ) , 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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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
2023-12-16 15:38:26 +00:00
/// Get the absolute value of this [SignedAmount] returning `Amount`.
pub fn unsigned_abs ( self ) -> Amount { Amount ( self . 0. unsigned_abs ( ) ) }
2018-11-10 00:45:00 +00:00
/// Returns a number representing sign of this [SignedAmount].
///
/// - `0` if the amount is zero
/// - `1` if the amount is positive
/// - `-1` if the amount is negative
2022-11-15 23:22:16 +00:00
pub fn signum ( self ) -> i64 { self . 0. signum ( ) }
2018-11-10 00:45:00 +00:00
/// Returns `true` if this [SignedAmount] is positive and `false` if
/// this [SignedAmount] is zero or negative.
2022-11-15 23:22:16 +00:00
pub fn is_positive ( self ) -> bool { self . 0. is_positive ( ) }
2018-11-10 00:45:00 +00:00
/// Returns `true` if this [SignedAmount] is negative and `false` if
/// this [SignedAmount] is zero or positive.
2022-11-15 23:22:16 +00:00
pub fn is_negative ( self ) -> bool { self . 0. is_negative ( ) }
2020-01-21 17:42:44 +00:00
/// Get the absolute value of this [SignedAmount].
2023-05-02 22:16:20 +00:00
/// Returns [None] if overflow occurred. (`self == MIN`)
2022-11-15 23:22:16 +00:00
pub fn checked_abs ( self ) -> Option < SignedAmount > { self . 0. checked_abs ( ) . map ( SignedAmount ) }
2020-01-21 17:42:44 +00:00
2018-11-10 00:45:00 +00:00
/// Checked addition.
/// Returns [None] if overflow occurred.
pub fn checked_add ( self , rhs : SignedAmount ) -> Option < SignedAmount > {
self . 0. checked_add ( rhs . 0 ) . map ( SignedAmount )
}
/// Checked subtraction.
/// Returns [None] if overflow occurred.
pub fn checked_sub ( self , rhs : SignedAmount ) -> Option < SignedAmount > {
self . 0. checked_sub ( rhs . 0 ) . map ( SignedAmount )
}
/// Checked multiplication.
/// Returns [None] if overflow occurred.
pub fn checked_mul ( self , rhs : i64 ) -> Option < SignedAmount > {
self . 0. checked_mul ( rhs ) . map ( SignedAmount )
}
/// Checked integer division.
/// Be aware that integer division loses the remainder if no exact division
/// can be made.
/// Returns [None] if overflow occurred.
pub fn checked_div ( self , rhs : i64 ) -> Option < SignedAmount > {
self . 0. checked_div ( rhs ) . map ( SignedAmount )
}
/// Checked remainder.
/// Returns [None] if overflow occurred.
pub fn checked_rem ( self , rhs : i64 ) -> Option < SignedAmount > {
self . 0. checked_rem ( rhs ) . map ( SignedAmount )
}
2024-02-03 11:03:49 +00:00
/// Unchecked addition.
///
/// Computes `self + rhs`. Panics in debug mode, wraps in release mode.
2024-03-31 01:03:18 +00:00
pub fn unchecked_add ( self , rhs : SignedAmount ) -> SignedAmount { Self ( self . 0 + rhs . 0 ) }
2024-02-03 11:03:49 +00:00
/// Unchecked subtraction.
///
/// Computes `self - rhs`. Panics in debug mode, wraps in release mode.
2024-03-31 01:03:18 +00:00
pub fn unchecked_sub ( self , rhs : SignedAmount ) -> SignedAmount { Self ( self . 0 - rhs . 0 ) }
2024-02-03 11:03:49 +00:00
2018-11-10 00:45:00 +00:00
/// 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.
2024-01-20 22:59:04 +00:00
pub fn to_unsigned ( self ) -> Result < Amount , OutOfRangeError > {
2018-11-10 00:45:00 +00:00
if self . is_negative ( ) {
2024-01-20 22:59:04 +00:00
Err ( OutOfRangeError ::negative ( ) )
2018-11-10 00:45:00 +00:00
} 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 {
2024-01-27 11:46:55 +00:00
write! ( f , " SignedAmount({} SAT) " , self . to_sat ( ) )
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
}
2024-02-12 02:04:26 +00:00
impl ops ::Neg for SignedAmount {
type Output = Self ;
fn neg ( self ) -> Self ::Output { Self ( self . 0. neg ( ) ) }
}
2018-11-10 00:45:00 +00:00
impl FromStr for SignedAmount {
2023-12-16 15:59:15 +00:00
type Err = ParseError ;
2018-11-10 00:45:00 +00:00
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
}
2024-02-09 12:45:37 +00:00
impl TryFrom < Amount > for SignedAmount {
type Error = OutOfRangeError ;
2024-03-31 01:03:18 +00:00
fn try_from ( value : Amount ) -> Result < Self , Self ::Error > { value . to_signed ( ) }
2024-02-09 12:45:37 +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 {
2024-01-27 11:46:55 +00:00
use super ::{ Amount , SignedAmount } ;
2021-06-23 12:18:42 +00:00
/// Used to seal the `CheckedSum` trait
pub trait SumSeal < A > { }
2022-11-15 23:22:16 +00:00
impl < T > SumSeal < Amount > for T where T : Iterator < Item = Amount > { }
impl < T > SumSeal < SignedAmount > for T where T : Iterator < Item = SignedAmount > { }
2021-06-23 12:18:42 +00:00
}
2018-11-10 00:45:00 +00:00
#[ cfg(feature = " serde " ) ]
pub mod serde {
// methods are implementation of a standardized serde-specific signature
#![ allow(missing_docs) ]
//! This module adds serde serialization and deserialization support for Amounts.
//! Since there is not a default way to serialize and deserialize Amounts, multiple
//! ways are supported and it's up to the user to decide which serialiation to use.
//! The provided modules can be used as follows:
//!
//! ```rust,ignore
//! use serde::{Serialize, Deserialize};
2023-08-08 08:47:05 +00:00
//! use bitcoin_units::Amount;
2018-11-10 00:45:00 +00:00
//!
//! #[derive(Serialize, Deserialize)]
//! pub struct HasAmount {
2023-08-08 08:47:05 +00:00
//! #[serde(with = "bitcoin_units::amount::serde::as_btc")]
2018-11-10 00:45:00 +00:00
//! pub amount: Amount,
//! }
//! ```
2024-01-20 22:59:04 +00:00
use core ::fmt ;
2024-03-31 01:03:18 +00:00
2018-11-10 00:45:00 +00:00
use serde ::{ Deserialize , Deserializer , Serialize , Serializer } ;
2022-11-15 23:22:16 +00:00
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
use super ::Denomination ;
2024-03-31 01:03:18 +00:00
use super ::{ Amount , ParseAmountError , 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.
2024-01-27 11:46:55 +00:00
pub trait SerdeAmount : Copy + Sized {
fn ser_sat < S : Serializer > ( self , s : S , _ : private ::Token ) -> Result < S ::Ok , S ::Error > ;
fn des_sat < ' d , D : Deserializer < ' d > > ( d : D , _ : private ::Token ) -> Result < Self , D ::Error > ;
#[ cfg(feature = " alloc " ) ]
fn ser_btc < S : Serializer > ( self , s : S , _ : private ::Token ) -> Result < S ::Ok , S ::Error > ;
#[ cfg(feature = " alloc " ) ]
fn des_btc < ' d , D : Deserializer < ' d > > ( d : D , _ : private ::Token ) -> Result < Self , D ::Error > ;
2018-11-10 00:45:00 +00:00
}
2021-02-28 17:13:46 +00:00
mod private {
2024-01-27 11:46:55 +00:00
/// Controls access to the trait methods.
pub struct Token ;
2021-02-28 17:13:46 +00:00
}
/// This trait is only for internal Amount type serialization/deserialization
2024-01-27 11:46:55 +00:00
pub trait SerdeAmountForOpt : Copy + Sized + SerdeAmount {
fn type_prefix ( _ : private ::Token ) -> & 'static str ;
fn ser_sat_opt < S : Serializer > ( self , s : S , _ : private ::Token ) -> Result < S ::Ok , S ::Error > ;
#[ cfg(feature = " alloc " ) ]
fn ser_btc_opt < S : Serializer > ( self , s : S , _ : private ::Token ) -> Result < S ::Ok , S ::Error > ;
2018-11-10 00:45:00 +00:00
}
2024-01-20 22:59:04 +00:00
struct DisplayFullError ( ParseAmountError ) ;
#[ cfg(feature = " std " ) ]
impl fmt ::Display for DisplayFullError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
use std ::error ::Error ;
fmt ::Display ::fmt ( & self . 0 , f ) ? ;
let mut source_opt = self . 0. source ( ) ;
while let Some ( source ) = source_opt {
write! ( f , " : {} " , source ) ? ;
source_opt = source . source ( ) ;
}
Ok ( ( ) )
}
}
#[ cfg(not(feature = " std " )) ]
impl fmt ::Display for DisplayFullError {
2024-03-31 01:03:18 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result { fmt ::Display ::fmt ( & self . 0 , f ) }
2024-01-20 22:59:04 +00:00
}
2018-11-10 00:45:00 +00:00
impl SerdeAmount for Amount {
2024-01-27 11:46:55 +00:00
fn ser_sat < S : Serializer > ( self , s : S , _ : private ::Token ) -> 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
}
2024-01-27 11:46:55 +00:00
fn des_sat < ' d , D : Deserializer < ' d > > ( d : D , _ : private ::Token ) -> Result < Self , D ::Error > {
2018-11-10 00:45:00 +00:00
Ok ( Amount ::from_sat ( u64 ::deserialize ( d ) ? ) )
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
fn ser_btc < S : Serializer > ( self , s : S , _ : private ::Token ) -> Result < S ::Ok , S ::Error > {
2018-11-10 00:45:00 +00:00
f64 ::serialize ( & self . to_float_in ( Denomination ::Bitcoin ) , s )
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
fn des_btc < ' d , D : Deserializer < ' d > > ( d : D , _ : private ::Token ) -> Result < Self , D ::Error > {
2018-11-10 00:45:00 +00:00
use serde ::de ::Error ;
2024-01-20 22:59:04 +00:00
Amount ::from_btc ( f64 ::deserialize ( d ) ? )
. map_err ( DisplayFullError )
. 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 {
2024-01-27 11:46:55 +00:00
fn type_prefix ( _ : private ::Token ) -> & 'static str { " u " }
fn ser_sat_opt < S : Serializer > ( self , s : S , _ : private ::Token ) -> 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
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
fn ser_btc_opt < S : Serializer > ( self , s : S , _ : private ::Token ) -> 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 {
2024-01-27 11:46:55 +00:00
fn ser_sat < S : Serializer > ( self , s : S , _ : private ::Token ) -> 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
}
2024-01-27 11:46:55 +00:00
fn des_sat < ' d , D : Deserializer < ' d > > ( d : D , _ : private ::Token ) -> Result < Self , D ::Error > {
2018-11-10 00:45:00 +00:00
Ok ( SignedAmount ::from_sat ( i64 ::deserialize ( d ) ? ) )
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
fn ser_btc < S : Serializer > ( self , s : S , _ : private ::Token ) -> Result < S ::Ok , S ::Error > {
2018-11-10 00:45:00 +00:00
f64 ::serialize ( & self . to_float_in ( Denomination ::Bitcoin ) , s )
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
fn des_btc < ' d , D : Deserializer < ' d > > ( d : D , _ : private ::Token ) -> Result < Self , D ::Error > {
2018-11-10 00:45:00 +00:00
use serde ::de ::Error ;
2024-01-20 22:59:04 +00:00
SignedAmount ::from_btc ( f64 ::deserialize ( d ) ? )
. map_err ( DisplayFullError )
. 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 {
2024-01-27 11:46:55 +00:00
fn type_prefix ( _ : private ::Token ) -> & 'static str { " i " }
fn ser_sat_opt < S : Serializer > ( self , s : S , _ : private ::Token ) -> 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
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
fn ser_btc_opt < S : Serializer > ( self , s : S , _ : private ::Token ) -> 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")]`.
2024-01-27 11:46:55 +00:00
//!
2019-08-18 16:26:29 +00:00
use serde ::{ Deserializer , Serializer } ;
2022-11-15 23:22:16 +00:00
2024-03-31 01:03:18 +00:00
use super ::private ;
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 > {
2024-01-27 11:46:55 +00:00
a . ser_sat ( s , private ::Token )
2018-11-10 00:45:00 +00:00
}
pub fn deserialize < ' d , A : SerdeAmount , D : Deserializer < ' d > > ( d : D ) -> Result < A , D ::Error > {
2024-01-27 11:46:55 +00:00
A ::des_sat ( d , private ::Token )
2018-11-10 00:45:00 +00:00
}
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 } ;
2024-03-31 01:03:18 +00:00
use super ::private ;
2022-11-15 23:22:16 +00:00
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 {
2024-01-27 11:46:55 +00:00
Some ( a ) = > a . ser_sat_opt ( s , private ::Token ) ,
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 {
2024-01-27 11:46:55 +00:00
write! ( formatter , " An Option<{}64> " , X ::type_prefix ( private ::Token ) )
2021-01-14 18:45:51 +00:00
}
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
{
2024-01-27 11:46:55 +00:00
Ok ( Some ( X ::des_sat ( d , private ::Token ) ? ) )
2021-01-14 18:45:51 +00:00
}
}
d . deserialize_option ( VisitOptAmt ::< A > ( PhantomData ) )
2018-11-10 00:45:00 +00:00
}
}
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
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
2024-03-31 01:03:18 +00:00
use super ::private ;
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 > {
2024-01-27 11:46:55 +00:00
a . ser_btc ( s , private ::Token )
2018-11-10 00:45:00 +00:00
}
pub fn deserialize < ' d , A : SerdeAmount , D : Deserializer < ' d > > ( d : D ) -> Result < A , D ::Error > {
2024-01-27 11:46:55 +00:00
A ::des_btc ( d , private ::Token )
2018-11-10 00:45:00 +00:00
}
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 } ;
2024-03-31 01:03:18 +00:00
use super ::private ;
2022-11-15 23:22:16 +00:00
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 {
2024-01-27 11:46:55 +00:00
Some ( a ) = > a . ser_btc_opt ( s , private ::Token ) ,
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 > ,
{
2024-01-27 11:46:55 +00:00
Ok ( Some ( X ::des_btc ( d , private ::Token ) ? ) )
2021-01-14 18:45:51 +00:00
}
}
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 {
2024-02-09 12:45:37 +00:00
Err ( OutOfRangeError ::too_big ( true ) )
2022-11-27 18:08:28 +00:00
} ,
) ;
}
#[ 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 {
2024-03-31 01:03:18 +00:00
Err ( OutOfRangeError { is_signed : true , is_greater_than_max : false } )
2022-11-27 18:08:28 +00:00
} ,
) ;
}
#[ 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 {
2024-01-27 12:07:36 +00:00
#[ cfg(feature = " alloc " ) ]
use alloc ::format ;
2021-06-09 10:34:44 +00:00
#[ cfg(feature = " std " ) ]
2018-11-10 00:45:00 +00:00
use std ::panic ;
2022-11-15 23:22:16 +00:00
use super ::* ;
2023-01-12 06:58:02 +00:00
#[ test ]
2024-01-27 12:07:36 +00:00
#[ cfg(feature = " alloc " ) ]
2023-01-12 06:58:02 +00:00
fn from_str_zero ( ) {
2024-01-27 12:07:36 +00:00
let denoms = [ " BTC " , " mBTC " , " uBTC " , " nBTC " , " pBTC " , " bits " , " sats " , " msats " ] ;
2023-01-12 06:58:02 +00:00
for denom in denoms {
for v in & [ " 0 " , " 000 " ] {
let s = format! ( " {} {} " , v , denom ) ;
match Amount ::from_str ( & s ) {
Err ( e ) = > panic! ( " Failed to crate amount from {} : {:?} " , s , e ) ,
Ok ( amount ) = > assert_eq! ( amount , Amount ::from_sat ( 0 ) ) ,
}
}
let s = format! ( " -0 {} " , denom ) ;
match Amount ::from_str ( & s ) {
2024-03-31 01:03:18 +00:00
Err ( e ) = > assert_eq! (
e ,
ParseError ::Amount ( ParseAmountError ::OutOfRange ( OutOfRangeError ::negative ( ) ) )
) ,
2023-01-12 06:58:02 +00:00
Ok ( _ ) = > panic! ( " Unsigned amount from {} " , s ) ,
}
match SignedAmount ::from_str ( & s ) {
Err ( e ) = > panic! ( " Failed to crate amount from {} : {:?} " , s , e ) ,
Ok ( amount ) = > assert_eq! ( amount , SignedAmount ::from_sat ( 0 ) ) ,
}
}
}
2023-06-13 08:55:04 +00:00
#[ test ]
fn from_int_btc ( ) {
let amt = Amount ::from_int_btc ( 2 ) ;
assert_eq! ( Amount ::from_sat ( 200_000_000 ) , amt ) ;
}
#[ should_panic ]
#[ test ]
fn from_int_btc_panic ( ) { Amount ::from_int_btc ( u64 ::MAX ) ; }
2024-02-09 12:45:37 +00:00
#[ test ]
fn test_signed_amount_try_from_amount ( ) {
let ua_positive = Amount ::from_sat ( 123 ) ;
let sa_positive = SignedAmount ::try_from ( ua_positive ) . unwrap ( ) ;
assert_eq! ( sa_positive , SignedAmount ( 123 ) ) ;
let ua_max = Amount ::MAX ;
let result = SignedAmount ::try_from ( ua_max ) ;
2024-03-31 01:03:18 +00:00
assert_eq! ( result , Err ( OutOfRangeError { is_signed : true , is_greater_than_max : true } ) ) ;
2024-02-09 12:45:37 +00:00
}
#[ test ]
fn test_amount_try_from_signed_amount ( ) {
let sa_positive = SignedAmount ( 123 ) ;
let ua_positive = Amount ::try_from ( sa_positive ) . unwrap ( ) ;
assert_eq! ( ua_positive , Amount ::from_sat ( 123 ) ) ;
let sa_negative = SignedAmount ( - 123 ) ;
let result = Amount ::try_from ( sa_negative ) ;
2024-03-31 01:03:18 +00:00
assert_eq! ( result , Err ( OutOfRangeError { is_signed : false , is_greater_than_max : false } ) ) ;
2024-02-09 12:45:37 +00:00
}
2018-11-10 00:45:00 +00:00
#[ test ]
2022-11-27 18:08:28 +00:00
fn mul_div ( ) {
2018-11-10 00:45:00 +00:00
let sat = Amount ::from_sat ;
let ssat = SignedAmount ::from_sat ;
assert_eq! ( sat ( 14 ) * 3 , sat ( 42 ) ) ;
assert_eq! ( sat ( 14 ) / 2 , sat ( 7 ) ) ;
assert_eq! ( sat ( 14 ) % 3 , sat ( 2 ) ) ;
assert_eq! ( ssat ( - 14 ) * 3 , ssat ( - 42 ) ) ;
assert_eq! ( ssat ( - 14 ) / 2 , ssat ( - 7 ) ) ;
assert_eq! ( ssat ( - 14 ) % 3 , ssat ( - 2 ) ) ;
2022-11-27 18:08:28 +00:00
let mut b = ssat ( 30 ) ;
2018-11-10 00:45:00 +00:00
b / = 3 ;
assert_eq! ( b , ssat ( 10 ) ) ;
b % = 3 ;
assert_eq! ( b , ssat ( 1 ) ) ;
2021-06-09 10:34:44 +00:00
}
2018-11-10 00:45:00 +00:00
2021-06-09 10:34:44 +00:00
#[ cfg(feature = " std " ) ]
#[ test ]
fn test_overflows ( ) {
2018-11-10 00:45:00 +00:00
// panic on overflow
2023-05-02 22:16:20 +00:00
let result = panic ::catch_unwind ( | | Amount ::MAX + Amount ::from_sat ( 1 ) ) ;
2018-11-10 00:45:00 +00:00
assert! ( result . is_err ( ) ) ;
let result = panic ::catch_unwind ( | | Amount ::from_sat ( 8446744073709551615 ) * 3 ) ;
assert! ( result . is_err ( ) ) ;
}
#[ test ]
fn checked_arithmetic ( ) {
let sat = Amount ::from_sat ;
let ssat = SignedAmount ::from_sat ;
2023-05-02 22:16:20 +00:00
assert_eq! ( SignedAmount ::MAX . checked_add ( ssat ( 1 ) ) , None ) ;
assert_eq! ( SignedAmount ::MIN . checked_sub ( ssat ( 1 ) ) , None ) ;
assert_eq! ( Amount ::MAX . checked_add ( sat ( 1 ) ) , None ) ;
assert_eq! ( Amount ::MIN . checked_sub ( sat ( 1 ) ) , None ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( sat ( 5 ) . checked_div ( 2 ) , Some ( sat ( 2 ) ) ) ; // integer division
assert_eq! ( ssat ( - 6 ) . checked_div ( 2 ) , Some ( ssat ( - 3 ) ) ) ;
}
2024-02-03 11:03:49 +00:00
#[ test ]
#[ cfg(not(debug_assertions)) ]
fn unchecked_amount_add ( ) {
let amt = Amount ::MAX . unchecked_add ( Amount ::ONE_SAT ) ;
assert_eq! ( amt , Amount ::ZERO ) ;
}
#[ test ]
#[ cfg(not(debug_assertions)) ]
fn unchecked_signed_amount_add ( ) {
let signed_amt = SignedAmount ::MAX . unchecked_add ( SignedAmount ::ONE_SAT ) ;
assert_eq! ( signed_amt , SignedAmount ::MIN ) ;
}
#[ test ]
#[ cfg(not(debug_assertions)) ]
fn unchecked_amount_subtract ( ) {
let amt = Amount ::ZERO . unchecked_sub ( Amount ::ONE_SAT ) ;
assert_eq! ( amt , Amount ::MAX ) ;
}
#[ test ]
#[ cfg(not(debug_assertions)) ]
fn unchecked_signed_amount_subtract ( ) {
let signed_amt = SignedAmount ::MIN . unchecked_sub ( SignedAmount ::ONE_SAT ) ;
assert_eq! ( signed_amt , SignedAmount ::MAX ) ;
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
#[ 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 ) ) ) ;
2024-01-20 22:59:04 +00:00
assert_eq! ( f ( - 100.0 , D ::MilliSatoshi ) , Err ( OutOfRangeError ::negative ( ) . into ( ) ) ) ;
2024-02-19 18:01:35 +00:00
assert_eq! ( f ( 11.22 , D ::Satoshi ) , Err ( TooPreciseError { position : 3 } . into ( ) ) ) ;
assert_eq! ( sf ( - 100.0 , D ::MilliSatoshi ) , Err ( TooPreciseError { position : 1 } . into ( ) ) ) ;
assert_eq! ( f ( 42.123456781 , D ::Bitcoin ) , Err ( TooPreciseError { position : 11 } . into ( ) ) ) ;
2024-01-20 22:59:04 +00:00
assert_eq! ( sf ( - 184467440738.0 , D ::Bitcoin ) , Err ( OutOfRangeError ::too_small ( ) . into ( ) ) ) ;
2024-03-31 01:03:18 +00:00
assert_eq! (
f ( 18446744073709551617.0 , D ::Satoshi ) ,
Err ( OutOfRangeError ::too_big ( false ) . into ( ) )
) ;
2024-01-21 22:08:54 +00:00
// Amount can be grater than the max SignedAmount.
2024-01-14 21:56:57 +00:00
assert! ( f ( SignedAmount ::MAX . to_float_in ( D ::Satoshi ) + 1.0 , D ::Satoshi ) . is_ok ( ) ) ;
2018-11-10 00:45:00 +00:00
assert_eq! (
2023-05-02 22:16:20 +00:00
f ( Amount ::MAX . to_float_in ( D ::Satoshi ) + 1.0 , D ::Satoshi ) ,
2024-01-20 22:59:04 +00:00
Err ( OutOfRangeError ::too_big ( false ) . into ( ) )
2018-11-10 00:45:00 +00:00
) ;
assert_eq! (
2024-01-21 22:08:54 +00:00
sf ( SignedAmount ::MAX . to_float_in ( D ::Satoshi ) + 1.0 , D ::Satoshi ) ,
2024-01-20 22:59:04 +00:00
Err ( OutOfRangeError ::too_big ( true ) . into ( ) )
2018-11-10 00:45:00 +00:00
) ;
let btc = move | f | SignedAmount ::from_btc ( f ) . unwrap ( ) ;
assert_eq! ( btc ( 2.5 ) . to_float_in ( D ::Bitcoin ) , 2.5 ) ;
assert_eq! ( btc ( - 2.5 ) . to_float_in ( D ::MilliBitcoin ) , - 2500.0 ) ;
assert_eq! ( btc ( 2.5 ) . to_float_in ( D ::Satoshi ) , 250000000.0 ) ;
assert_eq! ( btc ( - 2.5 ) . to_float_in ( D ::MilliSatoshi ) , - 250000000000.0 ) ;
let btc = move | f | Amount ::from_btc ( f ) . unwrap ( ) ;
assert_eq! ( & btc ( 0.0012 ) . to_float_in ( D ::Bitcoin ) . to_string ( ) , " 0.0012 " )
}
#[ test ]
2022-06-07 04:29:15 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn parsing ( ) {
use super ::ParseAmountError as E ;
let btc = Denomination ::Bitcoin ;
2020-01-21 17:43:10 +00:00
let sat = Denomination ::Satoshi ;
2023-04-01 10:32:15 +00:00
let msat = Denomination ::MilliSatoshi ;
2018-11-10 00:45:00 +00:00
let p = Amount ::from_str_in ;
let sp = SignedAmount ::from_str_in ;
2024-03-31 01:03:18 +00:00
assert_eq! (
p ( " x " , btc ) ,
Err ( E ::from ( InvalidCharacterError { invalid_char : ' x ' , position : 0 } ) )
) ;
assert_eq! (
p ( " - " , btc ) ,
Err ( E ::from ( MissingDigitsError { kind : MissingDigitsKind ::OnlyMinusSign } ) )
) ;
assert_eq! (
sp ( " - " , btc ) ,
Err ( E ::from ( MissingDigitsError { kind : MissingDigitsKind ::OnlyMinusSign } ) )
) ;
assert_eq! (
p ( " -1.0x " , btc ) ,
Err ( E ::from ( InvalidCharacterError { invalid_char : ' x ' , position : 4 } ) )
) ;
assert_eq! (
p ( " 0.0 " , btc ) ,
Err ( E ::from ( InvalidCharacterError { invalid_char : ' ' , position : 3 } ) )
) ;
assert_eq! (
p ( " 0.000.000 " , btc ) ,
Err ( E ::from ( InvalidCharacterError { invalid_char : ' . ' , position : 5 } ) )
) ;
2024-01-27 12:07:36 +00:00
#[ cfg(feature = " alloc " ) ]
2023-05-02 22:16:20 +00:00
let more_than_max = format! ( " 1 {} " , Amount ::MAX ) ;
2024-01-27 12:07:36 +00:00
#[ cfg(feature = " alloc " ) ]
2024-01-20 22:59:04 +00:00
assert_eq! ( p ( & more_than_max , btc ) , Err ( OutOfRangeError ::too_big ( false ) . into ( ) ) ) ;
2024-02-19 18:01:35 +00:00
assert_eq! ( p ( " 0.000000042 " , btc ) , Err ( TooPreciseError { position : 10 } . into ( ) ) ) ;
assert_eq! ( p ( " 999.0000000 " , msat ) , Err ( TooPreciseError { position : 0 } . into ( ) ) ) ;
assert_eq! ( p ( " 1.0000000 " , msat ) , Err ( TooPreciseError { position : 0 } . into ( ) ) ) ;
assert_eq! ( p ( " 1.1 " , msat ) , Err ( TooPreciseError { position : 0 } . into ( ) ) ) ;
assert_eq! ( p ( " 1000.1 " , msat ) , Err ( TooPreciseError { position : 5 } . into ( ) ) ) ;
assert_eq! ( p ( " 1001.0000000 " , msat ) , Err ( TooPreciseError { position : 3 } . into ( ) ) ) ;
assert_eq! ( p ( " 1000.0000001 " , msat ) , Err ( TooPreciseError { position : 11 } . into ( ) ) ) ;
assert_eq! ( p ( " 1000.1000000 " , msat ) , Err ( TooPreciseError { position : 5 } . into ( ) ) ) ;
assert_eq! ( p ( " 1100.0000000 " , msat ) , Err ( TooPreciseError { position : 1 } . into ( ) ) ) ;
assert_eq! ( p ( " 10001.0000000 " , msat ) , Err ( TooPreciseError { position : 4 } . into ( ) ) ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( p ( " 1 " , btc ) , Ok ( Amount ::from_sat ( 1_000_000_00 ) ) ) ;
assert_eq! ( sp ( " -.5 " , btc ) , Ok ( SignedAmount ::from_sat ( - 500_000_00 ) ) ) ;
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2023-12-12 10:33:44 +00:00
assert_eq! ( sp ( & i64 ::MIN . to_string ( ) , sat ) , Ok ( SignedAmount ::from_sat ( i64 ::MIN ) ) ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( p ( " 1.1 " , btc ) , Ok ( Amount ::from_sat ( 1_100_000_00 ) ) ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( p ( " 100 " , sat ) , Ok ( Amount ::from_sat ( 100 ) ) ) ;
assert_eq! ( p ( " 55 " , sat ) , Ok ( Amount ::from_sat ( 55 ) ) ) ;
2022-06-23 03:54:34 +00:00
assert_eq! ( p ( " 5500000000000000000 " , sat ) , Ok ( Amount ::from_sat ( 55_000_000_000_000_000_00 ) ) ) ;
2020-01-21 17:43:10 +00:00
// Should this even pass?
2022-06-23 03:54:34 +00:00
assert_eq! ( p ( " 5500000000000000000. " , sat ) , Ok ( Amount ::from_sat ( 55_000_000_000_000_000_00 ) ) ) ;
2018-11-10 00:45:00 +00:00
assert_eq! (
p ( " 12345678901.12345678 " , btc ) ,
Ok ( Amount ::from_sat ( 12_345_678_901__123_456_78 ) )
) ;
2023-05-07 06:47:53 +00:00
assert_eq! ( p ( " 1000.0 " , msat ) , Ok ( Amount ::from_sat ( 1 ) ) ) ;
assert_eq! ( p ( " 1000.000000000000000000000000000 " , msat ) , Ok ( Amount ::from_sat ( 1 ) ) ) ;
2020-01-21 17:43:10 +00:00
2023-05-02 22:16:20 +00:00
// make sure satoshi > i64::MAX is checked.
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
{
let amount = Amount ::from_sat ( i64 ::MAX as u64 ) ;
assert_eq! ( Amount ::from_str_in ( & amount . to_string_in ( sat ) , sat ) , Ok ( amount ) ) ;
2024-03-31 01:03:18 +00:00
assert! (
SignedAmount ::from_str_in ( & ( amount + Amount ( 1 ) ) . to_string_in ( sat ) , sat ) . is_err ( )
) ;
2024-01-27 11:46:55 +00:00
assert! ( Amount ::from_str_in ( & ( amount + Amount ( 1 ) ) . to_string_in ( sat ) , sat ) . is_ok ( ) ) ;
}
2020-01-21 17:43:10 +00:00
2024-03-31 01:03:18 +00:00
assert_eq! (
p ( " 12.000 " , Denomination ::MilliSatoshi ) ,
Err ( TooPreciseError { position : 0 } . into ( ) )
) ;
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 ) ,
2024-01-20 22:59:04 +00:00
Err ( OutOfRangeError ::too_big ( false ) . into ( ) )
2022-11-15 23:22:16 +00:00
) ;
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 ) ,
2024-02-19 13:01:11 +00:00
Err ( E ::InputTooLarge ( InputTooLargeError { len : 51 } ) )
2022-11-15 23:22:16 +00:00
) ;
2018-11-10 00:45:00 +00:00
}
#[ test ]
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
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
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2021-11-23 16:40:28 +00:00
#[ 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 ]
2024-01-27 12:07:36 +00:00
#[ cfg(feature = " alloc " ) ]
2021-11-23 16:40:28 +00:00
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 ]
2024-01-27 12:07:36 +00:00
#[ cfg(feature = " alloc " ) ]
2021-11-23 16:40:28 +00:00
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 ( ) {
let sa = SignedAmount ::from_sat ;
let ua = Amount ::from_sat ;
2024-02-20 05:11:14 +00:00
assert_eq! ( Amount ::MAX . to_signed ( ) , Err ( OutOfRangeError ::too_big ( true ) ) ) ;
2023-05-02 22:16:20 +00:00
assert_eq! ( ua ( i64 ::MAX as u64 ) . to_signed ( ) , Ok ( sa ( i64 ::MAX ) ) ) ;
2024-02-20 05:11:14 +00:00
assert_eq! ( ua ( i64 ::MAX as u64 + 1 ) . to_signed ( ) , Err ( OutOfRangeError ::too_big ( true ) ) ) ;
2020-01-21 17:43:10 +00:00
2023-05-02 22:16:20 +00:00
assert_eq! ( sa ( i64 ::MAX ) . to_unsigned ( ) , Ok ( ua ( i64 ::MAX as u64 ) ) ) ;
2020-01-21 17:43:10 +00:00
2023-05-02 22:16:20 +00:00
assert_eq! ( sa ( i64 ::MAX ) . to_unsigned ( ) . unwrap ( ) . to_signed ( ) , Ok ( sa ( i64 ::MAX ) ) ) ;
2020-01-21 17:43:10 +00:00
}
2018-11-10 00:45:00 +00:00
#[ test ]
2022-06-07 04:29:15 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn from_str ( ) {
2023-11-20 00:52:19 +00:00
use ParseDenominationError ::* ;
2018-11-10 00:45:00 +00:00
use super ::ParseAmountError as E ;
2022-12-31 22:19:56 +00:00
2024-03-31 01:03:18 +00:00
assert_eq! (
Amount ::from_str ( " x BTC " ) ,
Err ( InvalidCharacterError { invalid_char : ' x ' , position : 0 } . into ( ) )
) ;
2023-11-17 21:01:39 +00:00
assert_eq! (
Amount ::from_str ( " xBTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " xBTC " . into ( ) ) ) . into ( ) ) ,
2023-11-17 21:01:39 +00:00
) ;
assert_eq! (
Amount ::from_str ( " 5 BTC BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " BTC BTC " . into ( ) ) ) . into ( ) ) ,
2023-11-17 21:01:39 +00:00
) ;
2024-03-31 01:03:18 +00:00
assert_eq! (
Amount ::from_str ( " 5BTC BTC " ) ,
Err ( E ::from ( InvalidCharacterError { invalid_char : ' B ' , position : 1 } ) . into ( ) )
) ;
2023-11-17 21:01:39 +00:00
assert_eq! (
Amount ::from_str ( " 5 5 BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " 5 BTC " . into ( ) ) ) . into ( ) ) ,
2023-11-17 21:01:39 +00:00
) ;
2022-12-31 22:19:56 +00:00
2023-03-22 21:27:10 +00:00
#[ track_caller ]
2023-12-16 15:59:15 +00:00
fn ok_case ( s : & str , expected : Amount ) {
assert_eq! ( Amount ::from_str ( s ) . unwrap ( ) , expected ) ;
assert_eq! ( Amount ::from_str ( & s . replace ( ' ' , " " ) ) . unwrap ( ) , expected ) ;
}
#[ track_caller ]
fn case ( s : & str , expected : Result < Amount , impl Into < ParseError > > ) {
let expected = expected . map_err ( Into ::into ) ;
2022-11-02 22:36:37 +00:00
assert_eq! ( Amount ::from_str ( s ) , expected ) ;
assert_eq! ( Amount ::from_str ( & s . replace ( ' ' , " " ) ) , expected ) ;
2022-12-31 22:19:56 +00:00
}
2023-03-22 21:27:10 +00:00
#[ track_caller ]
2023-12-16 15:59:15 +00:00
fn ok_scase ( s : & str , expected : SignedAmount ) {
assert_eq! ( SignedAmount ::from_str ( s ) . unwrap ( ) , expected ) ;
assert_eq! ( SignedAmount ::from_str ( & s . replace ( ' ' , " " ) ) . unwrap ( ) , expected ) ;
}
#[ track_caller ]
fn scase ( s : & str , expected : Result < SignedAmount , impl Into < ParseError > > ) {
let expected = expected . map_err ( Into ::into ) ;
2022-11-02 22:36:37 +00:00
assert_eq! ( SignedAmount ::from_str ( s ) , expected ) ;
assert_eq! ( SignedAmount ::from_str ( & s . replace ( ' ' , " " ) ) , expected ) ;
2022-12-31 22:19:56 +00:00
}
2023-12-16 15:59:15 +00:00
case ( " 5 BCH " , Err ( Unknown ( UnknownDenominationError ( " BCH " . into ( ) ) ) ) ) ;
2022-12-31 22:19:56 +00:00
2024-01-20 22:59:04 +00:00
case ( " -1 BTC " , Err ( OutOfRangeError ::negative ( ) ) ) ;
case ( " -0.0 BTC " , Err ( OutOfRangeError ::negative ( ) ) ) ;
2024-02-19 18:01:35 +00:00
case ( " 0.123456789 BTC " , Err ( TooPreciseError { position : 10 } ) ) ;
scase ( " -0.1 satoshi " , Err ( TooPreciseError { position : 3 } ) ) ;
case ( " 0.123456 mBTC " , Err ( TooPreciseError { position : 7 } ) ) ;
scase ( " -1.001 bits " , Err ( TooPreciseError { position : 5 } ) ) ;
2024-01-20 22:59:04 +00:00
scase ( " -200000000000 BTC " , Err ( OutOfRangeError ::too_small ( ) ) ) ;
case ( " 18446744073709551616 sat " , Err ( OutOfRangeError ::too_big ( false ) ) ) ;
2022-12-31 22:19:56 +00:00
2023-12-16 15:59:15 +00:00
ok_case ( " .5 bits " , Amount ::from_sat ( 50 ) ) ;
ok_scase ( " -.5 bits " , SignedAmount ::from_sat ( - 50 ) ) ;
ok_case ( " 0.00253583 BTC " , Amount ::from_sat ( 253583 ) ) ;
ok_scase ( " -5 satoshi " , SignedAmount ::from_sat ( - 5 ) ) ;
ok_case ( " 0.10000000 BTC " , Amount ::from_sat ( 100_000_00 ) ) ;
ok_scase ( " -100 bits " , SignedAmount ::from_sat ( - 10_000 ) ) ;
2024-01-27 12:07:36 +00:00
#[ cfg(feature = " alloc " ) ]
2023-12-16 15:59:15 +00:00
ok_scase ( & format! ( " {} SAT " , i64 ::MIN ) , SignedAmount ::from_sat ( i64 ::MIN ) ) ;
2018-11-10 00:45:00 +00:00
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
#[ test ]
2022-06-07 04:29:15 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2020-01-21 17:43:10 +00:00
fn to_from_string_in ( ) {
2018-11-10 00:45:00 +00:00
use super ::Denomination as D ;
2020-01-21 17:43:10 +00:00
let ua_str = Amount ::from_str_in ;
let ua_sat = Amount ::from_sat ;
let sa_str = SignedAmount ::from_str_in ;
let sa_sat = SignedAmount ::from_sat ;
2018-11-10 00:45:00 +00:00
2021-11-23 16:40:28 +00:00
assert_eq! ( " 0.5 " , Amount ::from_sat ( 50 ) . to_string_in ( D ::Bit ) ) ;
assert_eq! ( " -0.5 " , SignedAmount ::from_sat ( - 50 ) . to_string_in ( D ::Bit ) ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( " 0.00253583 " , Amount ::from_sat ( 253583 ) . to_string_in ( D ::Bitcoin ) ) ;
assert_eq! ( " -5 " , SignedAmount ::from_sat ( - 5 ) . to_string_in ( D ::Satoshi ) ) ;
2021-11-23 16:40:28 +00:00
assert_eq! ( " 0.1 " , Amount ::from_sat ( 100_000_00 ) . to_string_in ( D ::Bitcoin ) ) ;
assert_eq! ( " -100 " , SignedAmount ::from_sat ( - 10_000 ) . to_string_in ( D ::Bit ) ) ;
2022-01-10 14:30:28 +00:00
assert_eq! ( " 2535830 " , Amount ::from_sat ( 253583 ) . to_string_in ( D ::NanoBitcoin ) ) ;
assert_eq! ( " -100000 " , SignedAmount ::from_sat ( - 10_000 ) . to_string_in ( D ::NanoBitcoin ) ) ;
assert_eq! ( " 2535830000 " , Amount ::from_sat ( 253583 ) . to_string_in ( D ::PicoBitcoin ) ) ;
assert_eq! ( " -100000000 " , SignedAmount ::from_sat ( - 10_000 ) . to_string_in ( D ::PicoBitcoin ) ) ;
2021-11-23 16:40:28 +00:00
assert_eq! ( " 0.50 " , format! ( " {:.2} " , Amount ::from_sat ( 50 ) . display_in ( D ::Bit ) ) ) ;
assert_eq! ( " -0.50 " , format! ( " {:.2} " , SignedAmount ::from_sat ( - 50 ) . display_in ( D ::Bit ) ) ) ;
2022-11-15 23:22:16 +00:00
assert_eq! (
" 0.10000000 " ,
format! ( " {:.8} " , Amount ::from_sat ( 100_000_00 ) . display_in ( D ::Bitcoin ) )
) ;
2021-11-23 16:40:28 +00:00
assert_eq! ( " -100.00 " , format! ( " {:.2} " , SignedAmount ::from_sat ( - 10_000 ) . display_in ( D ::Bit ) ) ) ;
2020-01-21 17:43:10 +00:00
assert_eq! ( ua_str ( & ua_sat ( 0 ) . to_string_in ( D ::Satoshi ) , D ::Satoshi ) , Ok ( ua_sat ( 0 ) ) ) ;
assert_eq! ( ua_str ( & ua_sat ( 500 ) . to_string_in ( D ::Bitcoin ) , D ::Bitcoin ) , Ok ( ua_sat ( 500 ) ) ) ;
2022-11-15 23:22:16 +00:00
assert_eq! (
ua_str ( & ua_sat ( 21_000_000 ) . to_string_in ( D ::Bit ) , D ::Bit ) ,
Ok ( ua_sat ( 21_000_000 ) )
) ;
assert_eq! (
ua_str ( & ua_sat ( 1 ) . to_string_in ( D ::MicroBitcoin ) , D ::MicroBitcoin ) ,
Ok ( ua_sat ( 1 ) )
) ;
assert_eq! (
ua_str ( & ua_sat ( 1_000_000_000_000 ) . to_string_in ( D ::MilliBitcoin ) , D ::MilliBitcoin ) ,
Ok ( ua_sat ( 1_000_000_000_000 ) )
) ;
2024-01-14 21:56:57 +00:00
assert! ( ua_str ( & ua_sat ( u64 ::MAX ) . to_string_in ( D ::MilliBitcoin ) , D ::MilliBitcoin ) . is_ok ( ) ) ;
2020-01-21 17:43:10 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
sa_str ( & sa_sat ( - 1 ) . to_string_in ( D ::MicroBitcoin ) , D ::MicroBitcoin ) ,
Ok ( sa_sat ( - 1 ) )
) ;
2020-01-21 17:43:10 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MAX ) . to_string_in ( D ::Satoshi ) , D ::MicroBitcoin ) ,
2024-01-20 22:59:04 +00:00
Err ( OutOfRangeError ::too_big ( true ) . into ( ) )
2022-11-15 23:22:16 +00:00
) ;
2020-01-21 17:43:10 +00:00
// Test an overflow bug in `abs()`
2022-11-15 23:22:16 +00:00
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MIN ) . to_string_in ( D ::Satoshi ) , D ::MicroBitcoin ) ,
2024-01-20 22:59:04 +00:00
Err ( OutOfRangeError ::too_small ( ) . into ( ) )
2022-11-15 23:22:16 +00:00
) ;
2022-01-10 14:30:28 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
sa_str ( & sa_sat ( - 1 ) . to_string_in ( D ::NanoBitcoin ) , D ::NanoBitcoin ) ,
Ok ( sa_sat ( - 1 ) )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MAX ) . to_string_in ( D ::Satoshi ) , D ::NanoBitcoin ) ,
2024-02-19 18:01:35 +00:00
Err ( TooPreciseError { position : 18 } . into ( ) )
2022-11-15 23:22:16 +00:00
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MIN ) . to_string_in ( D ::Satoshi ) , D ::NanoBitcoin ) ,
2024-02-19 18:01:35 +00:00
Err ( TooPreciseError { position : 19 } . into ( ) )
2022-11-15 23:22:16 +00:00
) ;
2022-01-10 14:30:28 +00:00
2022-11-15 23:22:16 +00:00
assert_eq! (
sa_str ( & sa_sat ( - 1 ) . to_string_in ( D ::PicoBitcoin ) , D ::PicoBitcoin ) ,
Ok ( sa_sat ( - 1 ) )
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MAX ) . to_string_in ( D ::Satoshi ) , D ::PicoBitcoin ) ,
2024-02-19 18:01:35 +00:00
Err ( TooPreciseError { position : 18 } . into ( ) )
2022-11-15 23:22:16 +00:00
) ;
assert_eq! (
2023-05-02 22:16:20 +00:00
sa_str ( & sa_sat ( i64 ::MIN ) . to_string_in ( D ::Satoshi ) , D ::PicoBitcoin ) ,
2024-02-19 18:01:35 +00:00
Err ( TooPreciseError { position : 19 } . into ( ) )
2022-11-15 23:22:16 +00:00
) ;
2020-01-21 17:43:10 +00:00
}
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2020-01-21 17:43:10 +00:00
#[ test ]
fn to_string_with_denomination_from_str_roundtrip ( ) {
2023-11-20 00:52:19 +00:00
use ParseDenominationError ::* ;
2020-01-21 17:43:10 +00:00
use super ::Denomination as D ;
2023-11-20 00:52:19 +00:00
2018-11-10 00:45:00 +00:00
let amt = Amount ::from_sat ( 42 ) ;
let denom = Amount ::to_string_with_denomination ;
2019-08-05 18:52:34 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::Bitcoin ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::MilliBitcoin ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::MicroBitcoin ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::Bit ) ) , Ok ( amt ) ) ;
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::Satoshi ) ) , Ok ( amt ) ) ;
2022-01-10 14:30:28 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::NanoBitcoin ) ) , Ok ( amt ) ) ;
2019-08-05 18:52:34 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::MilliSatoshi ) ) , Ok ( amt ) ) ;
2022-01-10 14:30:28 +00:00
assert_eq! ( Amount ::from_str ( & denom ( amt , D ::PicoBitcoin ) ) , Ok ( amt ) ) ;
2020-01-21 17:43:10 +00:00
2022-12-31 22:19:56 +00:00
assert_eq! (
2022-11-02 22:36:37 +00:00
Amount ::from_str ( " 42 satoshi BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " satoshi BTC " . into ( ) ) ) . into ( ) ) ,
2022-12-31 22:19:56 +00:00
) ;
assert_eq! (
2022-11-02 22:36:37 +00:00
SignedAmount ::from_str ( " -42 satoshi BTC " ) ,
2023-11-20 00:52:19 +00:00
Err ( Unknown ( UnknownDenominationError ( " satoshi BTC " . into ( ) ) ) . into ( ) ) ,
2022-12-31 22:19:56 +00:00
) ;
2018-11-10 00:45:00 +00:00
}
#[ cfg(feature = " serde " ) ]
#[ test ]
fn serde_as_sat ( ) {
#[ derive(Serialize, Deserialize, PartialEq, Debug) ]
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_sat " ) ]
2018-11-10 00:45:00 +00:00
pub amt : Amount ,
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_sat " ) ]
2018-11-10 00:45:00 +00:00
pub samt : SignedAmount ,
}
serde_test ::assert_tokens (
2022-11-15 23:22:16 +00:00
& T { amt : Amount ::from_sat ( 123456789 ) , samt : SignedAmount ::from_sat ( - 123456789 ) } ,
2018-11-10 00:45:00 +00:00
& [
2022-01-24 00:31:39 +00:00
serde_test ::Token ::Struct { name : " T " , len : 2 } ,
2018-11-10 00:45:00 +00:00
serde_test ::Token ::Str ( " amt " ) ,
serde_test ::Token ::U64 ( 123456789 ) ,
serde_test ::Token ::Str ( " samt " ) ,
serde_test ::Token ::I64 ( - 123456789 ) ,
serde_test ::Token ::StructEnd ,
] ,
) ;
}
#[ cfg(feature = " serde " ) ]
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
#[ test ]
2022-06-23 03:54:34 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn serde_as_btc ( ) {
use serde_json ;
#[ derive(Serialize, Deserialize, PartialEq, Debug) ]
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_btc " ) ]
2018-11-10 00:45:00 +00:00
pub amt : Amount ,
2022-10-20 22:12:44 +00:00
#[ serde(with = " crate::amount::serde::as_btc " ) ]
2018-11-10 00:45:00 +00:00
pub samt : SignedAmount ,
}
let orig = T {
amt : Amount ::from_sat ( 21_000_000__000_000_01 ) ,
samt : SignedAmount ::from_sat ( - 21_000_000__000_000_01 ) ,
} ;
let json = " { \" amt \" : 21000000.00000001, \
\ " samt \" : -21000000.00000001} " ;
2022-07-20 02:57:30 +00:00
let t : T = serde_json ::from_str ( json ) . unwrap ( ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( t , orig ) ;
2022-07-20 02:57:30 +00:00
let value : serde_json ::Value = serde_json ::from_str ( json ) . unwrap ( ) ;
2018-11-10 00:45:00 +00:00
assert_eq! ( t , serde_json ::from_value ( value ) . unwrap ( ) ) ;
// errors
let t : Result < T , serde_json ::Error > =
serde_json ::from_str ( " { \" amt \" : 1000000.000000001, \" samt \" : 1} " ) ;
2024-03-31 01:03:18 +00:00
assert! ( t
. unwrap_err ( )
. to_string ( )
. contains ( & ParseAmountError ::TooPrecise ( TooPreciseError { position : 16 } ) . to_string ( ) ) ) ;
2018-11-10 00:45:00 +00:00
let t : Result < T , serde_json ::Error > = serde_json ::from_str ( " { \" amt \" : -1, \" samt \" : 1} " ) ;
2024-01-27 12:07:36 +00:00
assert! ( t . unwrap_err ( ) . to_string ( ) . contains ( & OutOfRangeError ::negative ( ) . to_string ( ) ) ) ;
2018-11-10 00:45:00 +00:00
}
#[ cfg(feature = " serde " ) ]
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2018-11-10 00:45:00 +00:00
#[ test ]
2022-06-23 03:54:34 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2018-11-10 00:45:00 +00:00
fn serde_as_btc_opt ( ) {
use serde_json ;
2021-01-14 18:45:51 +00:00
#[ derive(Serialize, Deserialize, PartialEq, Debug, Eq) ]
2018-11-10 00:45:00 +00:00
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_btc::opt " ) ]
2018-11-10 00:45:00 +00:00
pub amt : Option < Amount > ,
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_btc::opt " ) ]
2018-11-10 00:45:00 +00:00
pub samt : Option < SignedAmount > ,
}
let with = T {
2022-06-23 03:54:34 +00:00
amt : Some ( Amount ::from_sat ( 2_500_000_00 ) ) ,
samt : Some ( SignedAmount ::from_sat ( - 2_500_000_00 ) ) ,
2018-11-10 00:45:00 +00:00
} ;
2022-11-15 23:22:16 +00:00
let without = T { amt : None , samt : None } ;
2018-11-10 00:45:00 +00:00
2021-01-14 18:45:51 +00:00
// Test Roundtripping
for s in [ & with , & without ] . iter ( ) {
let v = serde_json ::to_string ( s ) . unwrap ( ) ;
2022-11-15 23:22:16 +00:00
let w : T = serde_json ::from_str ( & v ) . unwrap ( ) ;
2021-01-14 18:45:51 +00:00
assert_eq! ( w , * * s ) ;
}
2018-11-10 00:45:00 +00:00
let t : T = serde_json ::from_str ( " { \" amt \" : 2.5, \" samt \" : -2.5} " ) . unwrap ( ) ;
assert_eq! ( t , with ) ;
let t : T = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( t , without ) ;
let value_with : serde_json ::Value =
serde_json ::from_str ( " { \" amt \" : 2.5, \" samt \" : -2.5} " ) . unwrap ( ) ;
assert_eq! ( with , serde_json ::from_value ( value_with ) . unwrap ( ) ) ;
let value_without : serde_json ::Value = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( without , serde_json ::from_value ( value_without ) . unwrap ( ) ) ;
}
2021-01-14 18:45:51 +00:00
#[ cfg(feature = " serde " ) ]
2024-01-27 11:46:55 +00:00
#[ cfg(feature = " alloc " ) ]
2021-01-14 18:45:51 +00:00
#[ test ]
2022-06-23 03:54:34 +00:00
#[ allow(clippy::inconsistent_digit_grouping) ] // Group to show 100,000,000 sats per bitcoin.
2021-01-14 18:45:51 +00:00
fn serde_as_sat_opt ( ) {
use serde_json ;
#[ derive(Serialize, Deserialize, PartialEq, Debug, Eq) ]
struct T {
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_sat::opt " ) ]
2021-01-14 18:45:51 +00:00
pub amt : Option < Amount > ,
2022-10-20 22:12:44 +00:00
#[ serde(default, with = " crate::amount::serde::as_sat::opt " ) ]
2021-01-14 18:45:51 +00:00
pub samt : Option < SignedAmount > ,
}
let with = T {
2022-06-23 03:54:34 +00:00
amt : Some ( Amount ::from_sat ( 2_500_000_00 ) ) ,
samt : Some ( SignedAmount ::from_sat ( - 2_500_000_00 ) ) ,
2021-01-14 18:45:51 +00:00
} ;
2022-11-15 23:22:16 +00:00
let without = T { amt : None , samt : None } ;
2021-01-14 18:45:51 +00:00
// Test Roundtripping
for s in [ & with , & without ] . iter ( ) {
let v = serde_json ::to_string ( s ) . unwrap ( ) ;
2022-11-15 23:22:16 +00:00
let w : T = serde_json ::from_str ( & v ) . unwrap ( ) ;
2021-01-14 18:45:51 +00:00
assert_eq! ( w , * * s ) ;
}
let t : T = serde_json ::from_str ( " { \" amt \" : 250000000, \" samt \" : -250000000} " ) . unwrap ( ) ;
assert_eq! ( t , with ) ;
let t : T = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( t , without ) ;
let value_with : serde_json ::Value =
serde_json ::from_str ( " { \" amt \" : 250000000, \" samt \" : -250000000} " ) . unwrap ( ) ;
assert_eq! ( with , serde_json ::from_value ( value_with ) . unwrap ( ) ) ;
let value_without : serde_json ::Value = serde_json ::from_str ( " {} " ) . unwrap ( ) ;
assert_eq! ( without , serde_json ::from_value ( value_without ) . unwrap ( ) ) ;
}
2021-06-12 14:46:04 +00:00
#[ test ]
fn sum_amounts ( ) {
2024-01-27 12:07:36 +00:00
assert_eq! ( Amount ::from_sat ( 0 ) , [ ] . into_iter ( ) . sum ::< Amount > ( ) ) ;
assert_eq! ( SignedAmount ::from_sat ( 0 ) , [ ] . into_iter ( ) . sum ::< SignedAmount > ( ) ) ;
2021-06-12 14:46:04 +00:00
2024-01-27 12:07:36 +00:00
let amounts = [ 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 ) ;
2024-03-31 01:03:18 +00:00
let amounts =
[ SignedAmount ::from_sat ( - 42 ) , SignedAmount ::from_sat ( 1337 ) , 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 ( ) {
2024-01-27 12:07:36 +00:00
assert_eq! ( Some ( Amount ::from_sat ( 0 ) ) , [ ] . into_iter ( ) . checked_sum ( ) ) ;
assert_eq! ( Some ( SignedAmount ::from_sat ( 0 ) ) , [ ] . into_iter ( ) . checked_sum ( ) ) ;
2021-06-12 15:09:49 +00:00
2024-01-27 12:07:36 +00:00
let amounts = [ 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 ) ;
2024-03-31 01:03:18 +00:00
let amounts = [ Amount ::from_sat ( u64 ::MAX ) , Amount ::from_sat ( 1337 ) , Amount ::from_sat ( 21 ) ] ;
2021-06-12 15:09:49 +00:00
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( None , sum ) ;
2024-01-27 12:07:36 +00:00
let amounts = [
2023-05-02 22:16:20 +00:00
SignedAmount ::from_sat ( i64 ::MIN ) ,
2021-06-12 15:09:49 +00:00
SignedAmount ::from_sat ( - 1 ) ,
2022-11-15 23:22:16 +00:00
SignedAmount ::from_sat ( 21 ) ,
2021-06-12 15:09:49 +00:00
] ;
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( None , sum ) ;
2024-01-27 12:07:36 +00:00
let amounts = [
2023-05-02 22:16:20 +00:00
SignedAmount ::from_sat ( i64 ::MAX ) ,
2021-06-12 15:09:49 +00:00
SignedAmount ::from_sat ( 1 ) ,
2022-11-15 23:22:16 +00:00
SignedAmount ::from_sat ( 21 ) ,
2021-06-12 15:09:49 +00:00
] ;
let sum = amounts . into_iter ( ) . checked_sum ( ) ;
assert_eq! ( None , sum ) ;
2024-03-31 01:03:18 +00:00
let amounts =
[ SignedAmount ::from_sat ( 42 ) , SignedAmount ::from_sat ( 3301 ) , 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.
2024-01-27 12:07:36 +00:00
let valid = [
2023-03-23 11:14:11 +00:00
" BTC " , " btc " , " mBTC " , " mbtc " , " uBTC " , " ubtc " , " SATOSHI " , " satoshi " , " SATOSHIS " ,
" satoshis " , " SAT " , " sat " , " SATS " , " sats " , " bit " , " bits " , " nBTC " , " pBTC " ,
2022-11-15 23:22:16 +00:00
] ;
2021-12-01 01:56:16 +00:00
for denom in valid . iter ( ) {
assert! ( Denomination ::from_str ( denom ) . is_ok ( ) ) ;
}
}
#[ test ]
fn disallow_confusing_forms ( ) {
2023-08-25 02:30:04 +00:00
let confusing = [ " Msat " , " Msats " , " MSAT " , " MSATS " , " MSat " , " MSats " , " MBTC " , " Mbtc " , " PBTC " ] ;
2021-12-01 01:56:16 +00:00
for denom in confusing . iter ( ) {
2022-11-15 23:22:16 +00:00
match Denomination ::from_str ( denom ) {
2021-12-01 01:56:16 +00:00
Ok ( _ ) = > panic! ( " from_str should error for {} " , denom ) ,
2023-11-17 21:01:39 +00:00
Err ( ParseDenominationError ::PossiblyConfusing ( _ ) ) = > { }
2021-12-01 01:56:16 +00:00
Err ( e ) = > panic! ( " unexpected error: {} " , e ) ,
}
}
}
2022-01-11 14:49:58 +00:00
#[ test ]
fn disallow_unknown_denomination ( ) {
// Non-exhaustive list of unknown forms.
2023-08-25 02:30:04 +00:00
let unknown = [ " NBTC " , " UBTC " , " ABC " , " abc " , " cBtC " , " Sat " , " Sats " ] ;
2022-01-11 14:49:58 +00:00
for denom in unknown . iter ( ) {
2022-11-15 23:22:16 +00:00
match Denomination ::from_str ( denom ) {
2022-01-11 14:49:58 +00:00
Ok ( _ ) = > panic! ( " from_str should error for {} " , denom ) ,
2024-01-20 22:59:04 +00:00
Err ( ParseDenominationError ::Unknown ( _ ) ) = > ( ) ,
2022-01-11 14:49:58 +00:00
Err ( e ) = > panic! ( " unexpected error: {} " , e ) ,
}
}
}
2024-03-16 13:05:06 +00:00
#[ test ]
#[ cfg(feature = " alloc " ) ]
fn trailing_zeros_for_amount ( ) {
assert_eq! ( format! ( " {} " , Amount ::ONE_SAT ) , " 0.00000001 BTC " ) ;
assert_eq! ( format! ( " {} " , Amount ::ONE_BTC ) , " 1 BTC " ) ;
assert_eq! ( format! ( " {} " , Amount ::from_sat ( 1 ) ) , " 0.00000001 BTC " ) ;
assert_eq! ( format! ( " {} " , Amount ::from_sat ( 10 ) ) , " 0.00000010 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 10 ) ) , " 0.0000001 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 100 ) ) , " 0.000001 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 1000 ) ) , " 0.00001 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 10_000 ) ) , " 0.0001 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 100_000 ) ) , " 0.001 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 1_000_000 ) ) , " 0.01 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 10_000_000 ) ) , " 0.10 BTC " ) ;
assert_eq! ( format! ( " {:.2} " , Amount ::from_sat ( 100_000_000 ) ) , " 1.00 BTC " ) ;
assert_eq! ( format! ( " {} " , Amount ::from_sat ( 100_000_000 ) ) , " 1 BTC " ) ;
assert_eq! ( format! ( " {} " , Amount ::from_sat ( 40_000_000_000 ) ) , " 400 BTC " ) ;
2024-03-31 01:03:18 +00:00
assert_eq! ( format! ( " {:.10} " , Amount ::from_sat ( 100_000_000 ) ) , " 1.0000000000 BTC " ) ;
assert_eq! ( format! ( " {} " , Amount ::from_sat ( 400_000_000_000_010 ) ) , " 4000000.00000010 BTC " ) ;
assert_eq! ( format! ( " {} " , Amount ::from_sat ( 400_000_000_000_000 ) ) , " 4000000 BTC " ) ;
2024-03-16 13:05:06 +00:00
}
2018-11-10 00:45:00 +00:00
}