Merge rust-bitcoin/rust-bitcoin#4164: Prepare to enforce MAX_MONEY invariant
5d851f1c3e
Remove deprecated amount methods (Tobin C. Harding)76a2d70b28
Make mul weight by fee return NumOpResult (Tobin C. Harding)f9eb307953
Remove panic in dust value functions (Tobin C. Harding)13595fbe7d
Fix amount whole bitcoin constructors (Tobin C. Harding)ac71680202
Pick one - MAX or MAX_MONEY (Tobin C. Harding)6d70c77cf9
Enforce newtype sanity rules for amount types (Tobin C. Harding)e6f7b26d80
Use _unchecked in amount const types (Tobin C. Harding)ef0af8d62e
Use sat/ssat constructors throughout tests (Andrew Poelstra)8ecdc7c275
Use den_ prefix for local Denomination variable (Tobin C. Harding)938461cc65
psbt: Use Amount::ZERO in unit test (Tobin C. Harding) Pull request description: We want to start enforcing MAX_MONEY as an invariant in the amount types. There are a few more steps we can do first to make that change easier to review. ACKs for top commit: jamillambert: ACK5d851f1c3e
apoelstra: ACK 5d851f1c3e98d7d426e5897b2d734b77a299ccfb; successfully ran local tests Tree-SHA512: 9e28b273d41fc143656e3a84736b6abe477fae5721b02bce7436551bd489cc235dc7e9fc68ffafa98f75a61065470ac514570a42bea94e90fedbb31f3cd61031
This commit is contained in:
commit
3cef539aac
|
@ -261,7 +261,7 @@ crate::internal_macros::define_extension_trait! {
|
|||
/// Returns the minimum value an output with this script should have in order to be
|
||||
/// broadcastable on today’s Bitcoin network.
|
||||
#[deprecated(since = "0.32.0", note = "use `minimal_non_dust` etc. instead")]
|
||||
fn dust_value(&self) -> Amount { self.minimal_non_dust() }
|
||||
fn dust_value(&self) -> Option<Amount> { self.minimal_non_dust() }
|
||||
|
||||
/// Returns the minimum value an output with this script should have in order to be
|
||||
/// broadcastable on today's Bitcoin network.
|
||||
|
@ -272,7 +272,7 @@ crate::internal_macros::define_extension_trait! {
|
|||
/// To use a custom value, use [`minimal_non_dust_custom`].
|
||||
///
|
||||
/// [`minimal_non_dust_custom`]: Script::minimal_non_dust_custom
|
||||
fn minimal_non_dust(&self) -> Amount {
|
||||
fn minimal_non_dust(&self) -> Option<Amount> {
|
||||
self.minimal_non_dust_internal(DUST_RELAY_TX_FEE.into())
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,7 @@ crate::internal_macros::define_extension_trait! {
|
|||
/// To use the default Bitcoin Core value, use [`minimal_non_dust`].
|
||||
///
|
||||
/// [`minimal_non_dust`]: Script::minimal_non_dust
|
||||
fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> Amount {
|
||||
fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> Option<Amount> {
|
||||
self.minimal_non_dust_internal(dust_relay_fee.to_sat_per_kwu() * 4)
|
||||
}
|
||||
|
||||
|
@ -394,7 +394,7 @@ mod sealed {
|
|||
|
||||
crate::internal_macros::define_extension_trait! {
|
||||
pub(crate) trait ScriptExtPriv impl for Script {
|
||||
fn minimal_non_dust_internal(&self, dust_relay_fee: u64) -> Amount {
|
||||
fn minimal_non_dust_internal(&self, dust_relay_fee: u64) -> Option<Amount> {
|
||||
// This must never be lower than Bitcoin Core's GetDustThreshold() (as of v0.21) as it may
|
||||
// otherwise allow users to create transactions which likely can never be broadcast/confirmed.
|
||||
let sats = dust_relay_fee
|
||||
|
@ -408,13 +408,12 @@ crate::internal_macros::define_extension_trait! {
|
|||
32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core
|
||||
8 + // The serialized size of the TxOut's amount field
|
||||
self.consensus_encode(&mut sink()).expect("sinks don't error").to_u64() // The serialized size of this script_pubkey
|
||||
})
|
||||
.expect("dust_relay_fee or script length should not be absurdly large")
|
||||
})?
|
||||
/ 1000; // divide by 1000 like in Core to get value as it cancels out DEFAULT_MIN_RELAY_TX_FEE
|
||||
// Note: We ensure the division happens at the end, since Core performs the division at the end.
|
||||
// This will make sure none of the implicit floor operations mess with the value.
|
||||
|
||||
Amount::from_sat(sats)
|
||||
Some(Amount::from_sat(sats))
|
||||
}
|
||||
|
||||
fn count_sigops_internal(&self, accurate: bool) -> usize {
|
||||
|
|
|
@ -684,10 +684,10 @@ fn default_dust_value() {
|
|||
// well-known scriptPubKey types.
|
||||
let script_p2wpkh = Builder::new().push_int_unchecked(0).push_slice([42; 20]).into_script();
|
||||
assert!(script_p2wpkh.is_p2wpkh());
|
||||
assert_eq!(script_p2wpkh.minimal_non_dust(), Amount::from_sat_unchecked(294));
|
||||
assert_eq!(script_p2wpkh.minimal_non_dust(), Some(Amount::from_sat_unchecked(294)));
|
||||
assert_eq!(
|
||||
script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
|
||||
Amount::from_sat_unchecked(588)
|
||||
Some(Amount::from_sat_unchecked(588))
|
||||
);
|
||||
|
||||
let script_p2pkh = Builder::new()
|
||||
|
@ -698,10 +698,10 @@ fn default_dust_value() {
|
|||
.push_opcode(OP_CHECKSIG)
|
||||
.into_script();
|
||||
assert!(script_p2pkh.is_p2pkh());
|
||||
assert_eq!(script_p2pkh.minimal_non_dust(), Amount::from_sat_unchecked(546));
|
||||
assert_eq!(script_p2pkh.minimal_non_dust(), Some(Amount::from_sat_unchecked(546)));
|
||||
assert_eq!(
|
||||
script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
|
||||
Amount::from_sat_unchecked(1092)
|
||||
Some(Amount::from_sat_unchecked(1092))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -183,8 +183,8 @@ crate::internal_macros::define_extension_trait! {
|
|||
/// To use a custom value, use [`minimal_non_dust_custom`].
|
||||
///
|
||||
/// [`minimal_non_dust_custom`]: TxOut::minimal_non_dust_custom
|
||||
fn minimal_non_dust(script_pubkey: ScriptBuf) -> Self {
|
||||
TxOut { value: script_pubkey.minimal_non_dust(), script_pubkey }
|
||||
fn minimal_non_dust(script_pubkey: ScriptBuf) -> Option<TxOut> {
|
||||
Some(TxOut { value: script_pubkey.minimal_non_dust()?, script_pubkey })
|
||||
}
|
||||
|
||||
/// Constructs a new `TxOut` with given script and the smallest possible `value` that is **not** dust
|
||||
|
@ -198,8 +198,8 @@ crate::internal_macros::define_extension_trait! {
|
|||
/// To use the default Bitcoin Core value, use [`minimal_non_dust`].
|
||||
///
|
||||
/// [`minimal_non_dust`]: TxOut::minimal_non_dust
|
||||
fn minimal_non_dust_custom(script_pubkey: ScriptBuf, dust_relay_fee: FeeRate) -> Self {
|
||||
TxOut { value: script_pubkey.minimal_non_dust_custom(dust_relay_fee), script_pubkey }
|
||||
fn minimal_non_dust_custom(script_pubkey: ScriptBuf, dust_relay_fee: FeeRate) -> Option<TxOut> {
|
||||
Some(TxOut { value: script_pubkey.minimal_non_dust_custom(dust_relay_fee)?, script_pubkey })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2283,7 +2283,7 @@ mod tests {
|
|||
version: transaction::Version::TWO,
|
||||
lock_time: absolute::LockTime::ZERO,
|
||||
input: vec![TxIn::EMPTY_COINBASE, TxIn::EMPTY_COINBASE],
|
||||
output: vec![TxOut { value: Amount::from_sat(0), script_pubkey: ScriptBuf::new() }],
|
||||
output: vec![TxOut { value: Amount::ZERO, script_pubkey: ScriptBuf::new() }],
|
||||
};
|
||||
let mut psbt = Psbt::from_unsigned_tx(unsigned_tx).unwrap();
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ fn serde_regression_txin() {
|
|||
#[test]
|
||||
fn serde_regression_txout() {
|
||||
let txout =
|
||||
TxOut { value: Amount::MAX_MONEY, script_pubkey: ScriptBuf::from(vec![0u8, 1u8, 2u8]) };
|
||||
TxOut { value: Amount::MAX, script_pubkey: ScriptBuf::from(vec![0u8, 1u8, 2u8]) };
|
||||
let got = serialize(&txout).unwrap();
|
||||
let want = include_bytes!("data/serde/txout_bincode") as &[_];
|
||||
assert_eq!(got, want)
|
||||
|
|
|
@ -39,6 +39,7 @@ pub use self::{
|
|||
signed::SignedAmount,
|
||||
unsigned::Amount,
|
||||
};
|
||||
pub(crate) use self::result::OptionExt;
|
||||
|
||||
/// A set of denominations in which amounts can be expressed.
|
||||
///
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
//! Provides a monodic numeric result type that is used to return the result of
|
||||
//! doing mathematical operations (`core::ops`) on amount types.
|
||||
//! Provides a monodic type used when mathematical operations (`core::ops`) return an amount type.
|
||||
|
||||
use core::{fmt, ops};
|
||||
|
||||
|
@ -378,7 +377,7 @@ impl<'a> core::iter::Sum<&'a NumOpResult<SignedAmount>> for NumOpResult<SignedAm
|
|||
}
|
||||
}
|
||||
|
||||
pub(in crate::amount) trait OptionExt<T> {
|
||||
pub(crate) trait OptionExt<T> {
|
||||
fn valid_or_error(self) -> NumOpResult<T>;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,51 +16,74 @@ use super::{
|
|||
DisplayStyle, OutOfRangeError, ParseAmountError, ParseError,
|
||||
};
|
||||
|
||||
/// A signed amount.
|
||||
///
|
||||
/// The [`SignedAmount`] type can be used to express Bitcoin amounts that support arithmetic and
|
||||
/// conversion to various denominations. The [`SignedAmount`] type does not implement [`serde`]
|
||||
/// traits but we do provide modules for serializing as satoshis or bitcoin.
|
||||
///
|
||||
/// Warning!
|
||||
///
|
||||
/// This type implements several arithmetic operations from [`core::ops`].
|
||||
/// To prevent errors due to an overflow when using these operations,
|
||||
/// it is advised to instead use the checked arithmetic methods whose names
|
||||
/// start with `checked_`. The operations from [`core::ops`] that [`SignedAmount`]
|
||||
/// implements will panic when an overflow occurs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "serde")] {
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
/// use bitcoin_units::SignedAmount;
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct Foo {
|
||||
/// // If you are using `rust-bitcoin` then `bitcoin::amount::serde::as_sat` also works.
|
||||
/// #[serde(with = "bitcoin_units::amount::serde::as_sat")] // Also `serde::as_btc`.
|
||||
/// amount: SignedAmount,
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct SignedAmount(i64);
|
||||
mod encapsulate {
|
||||
/// A signed amount.
|
||||
///
|
||||
/// The [`SignedAmount`] type can be used to express Bitcoin amounts that support arithmetic and
|
||||
/// conversion to various denominations. The [`SignedAmount`] type does not implement [`serde`]
|
||||
/// traits but we do provide modules for serializing as satoshis or bitcoin.
|
||||
///
|
||||
/// Warning!
|
||||
///
|
||||
/// This type implements several arithmetic operations from [`core::ops`].
|
||||
/// To prevent errors due to an overflow when using these operations,
|
||||
/// it is advised to instead use the checked arithmetic methods whose names
|
||||
/// start with `checked_`. The operations from [`core::ops`] that [`SignedAmount`]
|
||||
/// implements will panic when an overflow occurs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "serde")] {
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
/// use bitcoin_units::SignedAmount;
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct Foo {
|
||||
/// // If you are using `rust-bitcoin` then `bitcoin::amount::serde::as_sat` also works.
|
||||
/// #[serde(with = "bitcoin_units::amount::serde::as_sat")] // Also `serde::as_btc`.
|
||||
/// amount: SignedAmount,
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct SignedAmount(i64);
|
||||
|
||||
impl SignedAmount {
|
||||
/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
|
||||
///
|
||||
/// Caller to guarantee that `satoshi` is within valid range.
|
||||
///
|
||||
/// See [`Self::MIN`] and [`Self::MAX`].
|
||||
pub const fn from_sat_unchecked(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
|
||||
|
||||
/// Gets the number of satoshis in this [`SignedAmount`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bitcoin_units::SignedAmount;
|
||||
/// assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
|
||||
/// ```
|
||||
pub const fn to_sat(self) -> i64 { self.0 }
|
||||
}
|
||||
}
|
||||
#[doc(inline)]
|
||||
pub use encapsulate::SignedAmount;
|
||||
|
||||
impl SignedAmount {
|
||||
/// The zero amount.
|
||||
pub const ZERO: Self = SignedAmount(0);
|
||||
pub const ZERO: Self = SignedAmount::from_sat_unchecked(0);
|
||||
/// Exactly one satoshi.
|
||||
pub const ONE_SAT: Self = SignedAmount(1);
|
||||
pub const ONE_SAT: Self = SignedAmount::from_sat_unchecked(1);
|
||||
/// Exactly one bitcoin.
|
||||
pub const ONE_BTC: Self = SignedAmount(100_000_000);
|
||||
pub const ONE_BTC: Self = SignedAmount::from_sat_unchecked(100_000_000);
|
||||
/// Exactly fifty bitcoin.
|
||||
pub const FIFTY_BTC: Self = Self::from_sat_unchecked(50 * 100_000_000);
|
||||
pub const FIFTY_BTC: Self = SignedAmount::from_sat_unchecked(50 * 100_000_000);
|
||||
/// The maximum value allowed as an amount. Useful for sanity checking.
|
||||
pub const MAX_MONEY: Self = SignedAmount(21_000_000 * 100_000_000);
|
||||
pub const MAX_MONEY: Self = SignedAmount::from_sat_unchecked(21_000_000 * 100_000_000);
|
||||
/// The minimum value of an amount.
|
||||
pub const MIN: Self = SignedAmount(-21_000_000 * 100_000_000);
|
||||
pub const MIN: Self = SignedAmount::from_sat_unchecked(-21_000_000 * 100_000_000);
|
||||
/// The maximum value of an amount.
|
||||
pub const MAX: Self = SignedAmount::MAX_MONEY;
|
||||
|
||||
|
@ -73,24 +96,9 @@ impl SignedAmount {
|
|||
/// let amount = SignedAmount::from_sat(-100_000);
|
||||
/// assert_eq!(amount.to_sat(), -100_000);
|
||||
/// ```
|
||||
pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
|
||||
|
||||
/// Gets the number of satoshis in this [`SignedAmount`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bitcoin_units::SignedAmount;
|
||||
/// assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
|
||||
/// ```
|
||||
pub const fn to_sat(self) -> i64 { self.0 }
|
||||
|
||||
/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
|
||||
///
|
||||
/// Caller to guarantee that `satoshi` is within valid range.
|
||||
///
|
||||
/// See [`Self::MIN`] and [`Self::MAX_MONEY`].
|
||||
pub const fn from_sat_unchecked(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
|
||||
pub const fn from_sat(satoshi: i64) -> SignedAmount {
|
||||
SignedAmount::from_sat_unchecked(satoshi)
|
||||
}
|
||||
|
||||
/// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
|
||||
///
|
||||
|
@ -113,29 +121,19 @@ impl SignedAmount {
|
|||
}
|
||||
|
||||
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The function errors if the argument multiplied by the number of sats
|
||||
/// per bitcoin overflows an `i64` type.
|
||||
pub fn from_int_btc<T: Into<i64>>(whole_bitcoin: T) -> Result<SignedAmount, OutOfRangeError> {
|
||||
match whole_bitcoin.into().checked_mul(100_000_000) {
|
||||
Some(amount) => Ok(Self::from_sat(amount)),
|
||||
None => Err(OutOfRangeError { is_signed: true, is_greater_than_max: true }),
|
||||
}
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn from_int_btc<T: Into<i32>>(whole_bitcoin: T) -> SignedAmount {
|
||||
SignedAmount::from_int_btc_const(whole_bitcoin.into())
|
||||
}
|
||||
|
||||
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]
|
||||
/// in const context.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The function panics if the argument multiplied by the number of sats
|
||||
/// per bitcoin overflows an `i64` type.
|
||||
pub const fn from_int_btc_const(whole_bitcoin: i64) -> SignedAmount {
|
||||
match whole_bitcoin.checked_mul(100_000_000) {
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub const fn from_int_btc_const(whole_bitcoin: i32) -> SignedAmount {
|
||||
let btc = whole_bitcoin as i64; // Can't call `into` in const context.
|
||||
match btc.checked_mul(100_000_000) {
|
||||
Some(amount) => SignedAmount::from_sat(amount),
|
||||
None => panic!("checked_mul overflowed"),
|
||||
None => panic!("cannot overflow in i64"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,11 +151,11 @@ impl SignedAmount {
|
|||
(false, sat) if sat > SignedAmount::MAX.to_sat() as u64 => Err(ParseAmountError(
|
||||
ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true)),
|
||||
)),
|
||||
(false, sat) => Ok(SignedAmount(sat as i64)), // Cast ok, value in this arm does not wrap.
|
||||
(false, sat) => Ok(SignedAmount::from_sat(sat as i64)), // Cast ok, value in this arm does not wrap.
|
||||
(true, sat) if sat > SignedAmount::MIN.to_sat().unsigned_abs() => Err(
|
||||
ParseAmountError(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small())),
|
||||
),
|
||||
(true, sat) => Ok(SignedAmount(-(sat as i64))), // Cast ok, value in this arm does not wrap.
|
||||
(true, sat) => Ok(SignedAmount::from_sat(-(sat as i64))), // Cast ok, value in this arm does not wrap.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,11 +298,11 @@ impl SignedAmount {
|
|||
|
||||
/// Gets the absolute value of this [`SignedAmount`].
|
||||
#[must_use]
|
||||
pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
|
||||
pub fn abs(self) -> SignedAmount { SignedAmount::from_sat(self.to_sat().abs()) }
|
||||
|
||||
/// Gets the absolute value of this [`SignedAmount`] returning [`Amount`].
|
||||
#[must_use]
|
||||
pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.0.unsigned_abs()) }
|
||||
pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.to_sat().unsigned_abs()) }
|
||||
|
||||
/// Returns a number representing sign of this [`SignedAmount`].
|
||||
///
|
||||
|
@ -312,19 +310,19 @@ impl SignedAmount {
|
|||
/// - `1` if the amount is positive
|
||||
/// - `-1` if the amount is negative
|
||||
#[must_use]
|
||||
pub fn signum(self) -> i64 { self.0.signum() }
|
||||
pub fn signum(self) -> i64 { self.to_sat().signum() }
|
||||
|
||||
/// Checks if this [`SignedAmount`] is positive.
|
||||
///
|
||||
/// Returns `true` if this [`SignedAmount`] is positive and `false` if
|
||||
/// this [`SignedAmount`] is zero or negative.
|
||||
pub fn is_positive(self) -> bool { self.0.is_positive() }
|
||||
pub fn is_positive(self) -> bool { self.to_sat().is_positive() }
|
||||
|
||||
/// Checks if this [`SignedAmount`] is negative.
|
||||
///
|
||||
/// Returns `true` if this [`SignedAmount`] is negative and `false` if
|
||||
/// this [`SignedAmount`] is zero or positive.
|
||||
pub fn is_negative(self) -> bool { self.0.is_negative() }
|
||||
pub fn is_negative(self) -> bool { self.to_sat().is_negative() }
|
||||
|
||||
/// Returns the absolute value of this [`SignedAmount`].
|
||||
///
|
||||
|
@ -334,8 +332,8 @@ impl SignedAmount {
|
|||
#[must_use]
|
||||
pub const fn checked_abs(self) -> Option<SignedAmount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_abs() {
|
||||
Some(res) => Some(SignedAmount(res)),
|
||||
match self.to_sat().checked_abs() {
|
||||
Some(res) => Some(SignedAmount::from_sat(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -346,8 +344,8 @@ impl SignedAmount {
|
|||
#[must_use]
|
||||
pub const fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_add(rhs.0) {
|
||||
Some(res) => SignedAmount(res).check_min_max(),
|
||||
match self.to_sat().checked_add(rhs.to_sat()) {
|
||||
Some(res) => SignedAmount::from_sat(res).check_min_max(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -359,8 +357,8 @@ impl SignedAmount {
|
|||
#[must_use]
|
||||
pub const fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_sub(rhs.0) {
|
||||
Some(res) => SignedAmount(res).check_min_max(),
|
||||
match self.to_sat().checked_sub(rhs.to_sat()) {
|
||||
Some(res) => SignedAmount::from_sat(res).check_min_max(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -372,8 +370,8 @@ impl SignedAmount {
|
|||
#[must_use]
|
||||
pub const fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_mul(rhs) {
|
||||
Some(res) => SignedAmount(res).check_min_max(),
|
||||
match self.to_sat().checked_mul(rhs) {
|
||||
Some(res) => SignedAmount::from_sat(res).check_min_max(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -386,8 +384,8 @@ impl SignedAmount {
|
|||
#[must_use]
|
||||
pub const fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_div(rhs) {
|
||||
Some(res) => Some(SignedAmount(res)),
|
||||
match self.to_sat().checked_div(rhs) {
|
||||
Some(res) => Some(SignedAmount::from_sat(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -398,34 +396,12 @@ impl SignedAmount {
|
|||
#[must_use]
|
||||
pub const fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_rem(rhs) {
|
||||
Some(res) => Some(SignedAmount(res)),
|
||||
match self.to_sat().checked_rem(rhs) {
|
||||
Some(res) => Some(SignedAmount::from_sat(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unchecked addition.
|
||||
///
|
||||
/// Computes `self + rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
#[must_use]
|
||||
#[deprecated(since = "TBD", note = "consider converting to u64 using `to_sat`")]
|
||||
pub fn unchecked_add(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 + rhs.0) }
|
||||
|
||||
/// Unchecked subtraction.
|
||||
///
|
||||
/// Computes `self - rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
#[must_use]
|
||||
#[deprecated(since = "TBD", note = "consider converting to u64 using `to_sat`")]
|
||||
pub fn unchecked_sub(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 - rhs.0) }
|
||||
|
||||
/// Subtraction that doesn't allow negative [`SignedAmount`]s.
|
||||
///
|
||||
/// Returns [`None`] if either `self`, `rhs` or the result is strictly negative.
|
||||
|
@ -453,7 +429,7 @@ impl SignedAmount {
|
|||
|
||||
/// Checks the amount is within the allowed range.
|
||||
const fn check_min_max(self) -> Option<SignedAmount> {
|
||||
if self.0 < Self::MIN.0 || self.0 > Self::MAX.0 {
|
||||
if self.to_sat() < Self::MIN.to_sat() || self.to_sat() > Self::MAX.to_sat() {
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
|
@ -514,6 +490,6 @@ impl From<Amount> for SignedAmount {
|
|||
impl<'a> Arbitrary<'a> for SignedAmount {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let s = i64::arbitrary(u)?;
|
||||
Ok(Self(s))
|
||||
Ok(Self::from_sat(s))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@ use super::*;
|
|||
#[cfg(feature = "alloc")]
|
||||
use crate::{FeeRate, Weight};
|
||||
|
||||
fn sat(sat: u64) -> Amount { Amount::from_sat(sat) }
|
||||
fn ssat(ssat: i64) -> SignedAmount { SignedAmount::from_sat(ssat) }
|
||||
|
||||
#[test]
|
||||
fn sanity_check() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!(ssat(-100).abs(), ssat(100));
|
||||
assert_eq!(ssat(i64::MIN + 1).checked_abs().unwrap(), ssat(i64::MAX));
|
||||
assert_eq!(ssat(-100).signum(), -1);
|
||||
|
@ -102,25 +102,22 @@ fn from_str_zero_without_denomination() {
|
|||
#[test]
|
||||
fn from_int_btc() {
|
||||
let amt = Amount::from_int_btc_const(2);
|
||||
assert_eq!(Amount::from_sat_unchecked(200_000_000), amt);
|
||||
assert_eq!(sat(200_000_000), amt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn amount_try_from_signed_amount() {
|
||||
let sa_positive = SignedAmount::from_sat_unchecked(123);
|
||||
let sa_positive = ssat(123);
|
||||
let ua_positive = Amount::try_from(sa_positive).unwrap();
|
||||
assert_eq!(ua_positive, Amount::from_sat_unchecked(123));
|
||||
assert_eq!(ua_positive, sat(123));
|
||||
|
||||
let sa_negative = SignedAmount::from_sat_unchecked(-123);
|
||||
let sa_negative = ssat(-123);
|
||||
let result = Amount::try_from(sa_negative);
|
||||
assert_eq!(result, Err(OutOfRangeError { is_signed: false, is_greater_than_max: false }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_div() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
let op_result_sat = |sat| NumOpResult::Valid(Amount::from_sat(sat));
|
||||
let op_result_ssat = |sat| NumOpResult::Valid(SignedAmount::from_sat(sat));
|
||||
|
||||
|
@ -141,17 +138,14 @@ fn neg() {
|
|||
#[cfg(feature = "std")]
|
||||
#[test]
|
||||
fn overflows() {
|
||||
let result = Amount::MAX + Amount::from_sat_unchecked(1);
|
||||
let result = Amount::MAX + sat(1);
|
||||
assert!(result.is_error());
|
||||
let result = Amount::from_sat_unchecked(8_446_744_073_709_551_615) * 3;
|
||||
let result = sat(8_446_744_073_709_551_615) * 3;
|
||||
assert!(result.is_error());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert!(sat(0) + sat(0) == sat(0).into());
|
||||
assert!(sat(127) + sat(179) == sat(306).into());
|
||||
|
||||
|
@ -164,9 +158,6 @@ fn add() {
|
|||
|
||||
#[test]
|
||||
fn sub() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert!(sat(0) - sat(0) == sat(0).into());
|
||||
assert!(sat(179) - sat(127) == sat(52).into());
|
||||
assert!((sat(127) - sat(179)).is_error());
|
||||
|
@ -180,9 +171,6 @@ fn sub() {
|
|||
|
||||
#[test]
|
||||
fn checked_arithmetic() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
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);
|
||||
|
@ -192,22 +180,8 @@ fn checked_arithmetic() {
|
|||
assert_eq!(ssat(-6).checked_div(2), Some(ssat(-3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated_in_future)]
|
||||
fn unchecked_arithmetic() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!(ssat(10).unchecked_add(ssat(20)), ssat(30));
|
||||
assert_eq!(ssat(50).unchecked_sub(ssat(10)), ssat(40));
|
||||
assert_eq!(sat(5).unchecked_add(sat(7)), sat(12));
|
||||
assert_eq!(sat(10).unchecked_sub(sat(7)), sat(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positive_sub() {
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!(ssat(10).positive_sub(ssat(7)).unwrap(), ssat(3));
|
||||
assert!(ssat(-10).positive_sub(ssat(7)).is_none());
|
||||
assert!(ssat(10).positive_sub(ssat(-7)).is_none());
|
||||
|
@ -218,12 +192,12 @@ fn positive_sub() {
|
|||
#[test]
|
||||
fn amount_checked_div_by_weight_ceil() {
|
||||
let weight = Weight::from_kwu(1).unwrap();
|
||||
let fee_rate = Amount::from_sat_unchecked(1).checked_div_by_weight_ceil(weight).unwrap();
|
||||
let fee_rate = sat(1).checked_div_by_weight_ceil(weight).unwrap();
|
||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
|
||||
|
||||
let weight = Weight::from_wu(381);
|
||||
let fee_rate = Amount::from_sat_unchecked(329).checked_div_by_weight_ceil(weight).unwrap();
|
||||
let fee_rate = sat(329).checked_div_by_weight_ceil(weight).unwrap();
|
||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||
// round up to 864
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864));
|
||||
|
@ -236,12 +210,12 @@ fn amount_checked_div_by_weight_ceil() {
|
|||
#[test]
|
||||
fn amount_checked_div_by_weight_floor() {
|
||||
let weight = Weight::from_kwu(1).unwrap();
|
||||
let fee_rate = Amount::from_sat_unchecked(1).checked_div_by_weight_floor(weight).unwrap();
|
||||
let fee_rate = sat(1).checked_div_by_weight_floor(weight).unwrap();
|
||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
|
||||
|
||||
let weight = Weight::from_wu(381);
|
||||
let fee_rate = Amount::from_sat_unchecked(329).checked_div_by_weight_floor(weight).unwrap();
|
||||
let fee_rate = sat(329).checked_div_by_weight_floor(weight).unwrap();
|
||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||
// round down to 863
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
|
||||
|
@ -253,7 +227,7 @@ fn amount_checked_div_by_weight_floor() {
|
|||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn amount_checked_div_by_fee_rate() {
|
||||
let amount = Amount::from_sat_unchecked(1000);
|
||||
let amount = sat(1000);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
||||
|
||||
// Test floor division
|
||||
|
@ -266,7 +240,7 @@ fn amount_checked_div_by_fee_rate() {
|
|||
assert_eq!(weight, Weight::from_wu(500_000)); // Same result for exact division
|
||||
|
||||
// Test truncation behavior
|
||||
let amount = Amount::from_sat_unchecked(1000);
|
||||
let amount = sat(1000);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(3);
|
||||
let floor_weight = amount.checked_div_by_fee_rate_floor(fee_rate).unwrap();
|
||||
let ceil_weight = amount.checked_div_by_fee_rate_ceil(fee_rate).unwrap();
|
||||
|
@ -287,7 +261,7 @@ fn amount_checked_div_by_fee_rate() {
|
|||
|
||||
// Test overflow case
|
||||
let tiny_fee_rate = FeeRate::from_sat_per_kwu(1);
|
||||
let large_amount = Amount::from_sat(u64::MAX);
|
||||
let large_amount = sat(u64::MAX);
|
||||
assert!(large_amount.checked_div_by_fee_rate_floor(tiny_fee_rate).is_none());
|
||||
assert!(large_amount.checked_div_by_fee_rate_ceil(tiny_fee_rate).is_none());
|
||||
}
|
||||
|
@ -298,8 +272,6 @@ 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(1_122_000_000)));
|
||||
assert_eq!(sf(-11.22, D::MilliBitcoin), Ok(ssat(-1_122_000)));
|
||||
|
@ -339,53 +311,63 @@ fn floating_point() {
|
|||
#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin.
|
||||
fn parsing() {
|
||||
use super::ParseAmountError as E;
|
||||
let btc = Denomination::Bitcoin;
|
||||
let sat = Denomination::Satoshi;
|
||||
|
||||
let den_btc = Denomination::Bitcoin;
|
||||
let den_sat = Denomination::Satoshi;
|
||||
let p = Amount::from_str_in;
|
||||
let sp = SignedAmount::from_str_in;
|
||||
|
||||
assert_eq!(p("x", btc), Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 0 })));
|
||||
assert_eq!(
|
||||
p("-", btc),
|
||||
p("x", den_btc),
|
||||
Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 0 }))
|
||||
);
|
||||
assert_eq!(
|
||||
p("-", den_btc),
|
||||
Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
|
||||
);
|
||||
assert_eq!(
|
||||
sp("-", btc),
|
||||
sp("-", den_btc),
|
||||
Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
|
||||
);
|
||||
assert_eq!(
|
||||
p("-1.0x", btc),
|
||||
p("-1.0x", den_btc),
|
||||
Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 4 }))
|
||||
);
|
||||
assert_eq!(
|
||||
p("0.0 ", btc),
|
||||
p("0.0 ", den_btc),
|
||||
Err(E::from(InvalidCharacterError { invalid_char: ' ', position: 3 }))
|
||||
);
|
||||
assert_eq!(
|
||||
p("0.000.000", btc),
|
||||
p("0.000.000", den_btc),
|
||||
Err(E::from(InvalidCharacterError { invalid_char: '.', position: 5 }))
|
||||
);
|
||||
#[cfg(feature = "alloc")]
|
||||
let more_than_max = format!("{}", Amount::MAX.to_sat() + 1);
|
||||
#[cfg(feature = "alloc")]
|
||||
assert_eq!(p(&more_than_max, btc), Err(OutOfRangeError::too_big(false).into()));
|
||||
assert_eq!(p("0.000000042", btc), Err(TooPreciseError { position: 10 }.into()));
|
||||
assert_eq!(p("1.0000000", sat), Ok(Amount::from_sat_unchecked(1)));
|
||||
assert_eq!(p("1.1", sat), Err(TooPreciseError { position: 2 }.into()));
|
||||
assert_eq!(p("1000.1", sat), Err(TooPreciseError { position: 5 }.into()));
|
||||
assert_eq!(p("1001.0000000", sat), Ok(Amount::from_sat_unchecked(1001)));
|
||||
assert_eq!(p("1000.0000001", sat), Err(TooPreciseError { position: 11 }.into()));
|
||||
assert_eq!(p(&more_than_max, den_btc), Err(OutOfRangeError::too_big(false).into()));
|
||||
assert_eq!(p("0.000000042", den_btc), Err(TooPreciseError { position: 10 }.into()));
|
||||
assert_eq!(p("1.0000000", den_sat), Ok(sat(1)));
|
||||
assert_eq!(p("1.1", den_sat), Err(TooPreciseError { position: 2 }.into()));
|
||||
assert_eq!(p("1000.1", den_sat), Err(TooPreciseError { position: 5 }.into()));
|
||||
assert_eq!(p("1001.0000000", den_sat), Ok(sat(1001)));
|
||||
assert_eq!(p("1000.0000001", den_sat), Err(TooPreciseError { position: 11 }.into()));
|
||||
|
||||
assert_eq!(p("1", btc), Ok(Amount::from_sat_unchecked(1_000_000_00)));
|
||||
assert_eq!(sp("-.5", btc), Ok(SignedAmount::from_sat_unchecked(-500_000_00)));
|
||||
assert_eq!(p("1", den_btc), Ok(sat(1_000_000_00)));
|
||||
assert_eq!(sp("-.5", den_btc), Ok(ssat(-500_000_00)));
|
||||
#[cfg(feature = "alloc")]
|
||||
assert_eq!(sp(&SignedAmount::MIN.to_sat().to_string(), sat), Ok(SignedAmount::MIN));
|
||||
assert_eq!(p("1.1", btc), Ok(Amount::from_sat_unchecked(1_100_000_00)));
|
||||
assert_eq!(p("100", sat), Ok(Amount::from_sat_unchecked(100)));
|
||||
assert_eq!(p("55", sat), Ok(Amount::from_sat_unchecked(55)));
|
||||
assert_eq!(p("2100000000000000", sat), Ok(Amount::from_sat_unchecked(21_000_000__000_000_00)));
|
||||
assert_eq!(p("2100000000000000.", sat), Ok(Amount::from_sat_unchecked(21_000_000__000_000_00)));
|
||||
assert_eq!(p("21000000", btc), Ok(Amount::from_sat_unchecked(21_000_000__000_000_00)));
|
||||
assert_eq!(sp(&SignedAmount::MIN.to_sat().to_string(), den_sat), Ok(SignedAmount::MIN));
|
||||
assert_eq!(p("1.1", den_btc), Ok(sat(1_100_000_00)));
|
||||
assert_eq!(p("100", den_sat), Ok(sat(100)));
|
||||
assert_eq!(p("55", den_sat), Ok(sat(55)));
|
||||
assert_eq!(
|
||||
p("2100000000000000", den_sat),
|
||||
Ok(sat(21_000_000__000_000_00))
|
||||
);
|
||||
assert_eq!(
|
||||
p("2100000000000000.", den_sat),
|
||||
Ok(sat(21_000_000__000_000_00))
|
||||
);
|
||||
assert_eq!(p("21000000", den_btc), Ok(sat(21_000_000__000_000_00)));
|
||||
|
||||
// exactly 50 chars.
|
||||
assert_eq!(
|
||||
|
@ -408,15 +390,12 @@ fn to_string() {
|
|||
assert_eq!(format!("{:.8}", Amount::ONE_BTC.display_in(D::Bitcoin)), "1.00000000");
|
||||
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_unchecked(-42).to_string_in(D::Bitcoin), "-0.00000042");
|
||||
assert_eq!(ssat(-42).to_string_in(D::Bitcoin), "-0.00000042");
|
||||
|
||||
assert_eq!(Amount::ONE_BTC.to_string_with_denomination(D::Bitcoin), "1 BTC");
|
||||
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_unchecked(-42).to_string_with_denomination(D::Bitcoin),
|
||||
"-0.00000042 BTC"
|
||||
);
|
||||
assert_eq!(ssat(-42).to_string_with_denomination(D::Bitcoin), "-0.00000042 BTC");
|
||||
}
|
||||
|
||||
// May help identify a problem sooner
|
||||
|
@ -593,8 +572,6 @@ check_format_non_negative_show_denom! {
|
|||
|
||||
#[test]
|
||||
fn unsigned_signed_conversion() {
|
||||
let ssat = SignedAmount::from_sat;
|
||||
let sat = Amount::from_sat;
|
||||
let max_sats: u64 = Amount::MAX.to_sat();
|
||||
|
||||
assert_eq!(sat(max_sats).to_signed(), ssat(max_sats as i64));
|
||||
|
@ -672,12 +649,12 @@ fn from_str() {
|
|||
case("21000001 BTC", Err(OutOfRangeError::too_big(false)));
|
||||
case("18446744073709551616 sat", Err(OutOfRangeError::too_big(false)));
|
||||
|
||||
ok_case(".5 bits", Amount::from_sat_unchecked(50));
|
||||
ok_scase("-.5 bits", SignedAmount::from_sat_unchecked(-50));
|
||||
ok_case("0.00253583 BTC", Amount::from_sat_unchecked(253_583));
|
||||
ok_scase("-5 satoshi", SignedAmount::from_sat_unchecked(-5));
|
||||
ok_case("0.10000000 BTC", Amount::from_sat_unchecked(100_000_00));
|
||||
ok_scase("-100 bits", SignedAmount::from_sat_unchecked(-10_000));
|
||||
ok_case(".5 bits", sat(50));
|
||||
ok_scase("-.5 bits", ssat(-50));
|
||||
ok_case("0.00253583 BTC", sat(253_583));
|
||||
ok_scase("-5 satoshi", ssat(-5));
|
||||
ok_case("0.10000000 BTC", sat(100_000_00));
|
||||
ok_scase("-100 bits", ssat(-10_000));
|
||||
ok_case("21000000 BTC", Amount::MAX);
|
||||
ok_scase("21000000 BTC", SignedAmount::MAX);
|
||||
ok_scase("-21000000 BTC", SignedAmount::MIN);
|
||||
|
@ -739,15 +716,28 @@ fn to_from_string_in() {
|
|||
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(0).to_string_in(D::Satoshi), D::Satoshi), Ok(ua_sat(0)));
|
||||
|
||||
assert!(ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::Bitcoin), D::Bitcoin).is_ok());
|
||||
assert!(ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::CentiBitcoin), D::CentiBitcoin)
|
||||
.is_ok());
|
||||
assert!(ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::MilliBitcoin), D::MilliBitcoin)
|
||||
.is_ok());
|
||||
assert!(ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::MicroBitcoin), D::MicroBitcoin)
|
||||
.is_ok());
|
||||
assert!(
|
||||
ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::Bitcoin), D::Bitcoin).is_ok()
|
||||
);
|
||||
assert!(ua_str(
|
||||
&ua_sat(Amount::MAX.to_sat()).to_string_in(D::CentiBitcoin),
|
||||
D::CentiBitcoin
|
||||
)
|
||||
.is_ok());
|
||||
assert!(ua_str(
|
||||
&ua_sat(Amount::MAX.to_sat()).to_string_in(D::MilliBitcoin),
|
||||
D::MilliBitcoin
|
||||
)
|
||||
.is_ok());
|
||||
assert!(ua_str(
|
||||
&ua_sat(Amount::MAX.to_sat()).to_string_in(D::MicroBitcoin),
|
||||
D::MicroBitcoin
|
||||
)
|
||||
.is_ok());
|
||||
assert!(ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::Bit), D::Bit).is_ok());
|
||||
assert!(ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::Satoshi), D::Satoshi).is_ok());
|
||||
assert!(
|
||||
ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::Satoshi), D::Satoshi).is_ok()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sa_str(&SignedAmount::MAX.to_string_in(D::Satoshi), D::MicroBitcoin),
|
||||
|
@ -767,7 +757,7 @@ fn to_string_with_denomination_from_str_roundtrip() {
|
|||
|
||||
use super::Denomination as D;
|
||||
|
||||
let amt = Amount::from_sat_unchecked(42);
|
||||
let amt = sat(42);
|
||||
let denom = Amount::to_string_with_denomination;
|
||||
assert_eq!(denom(amt, D::Bitcoin).parse::<Amount>(), Ok(amt));
|
||||
assert_eq!(denom(amt, D::CentiBitcoin).parse::<Amount>(), Ok(amt));
|
||||
|
@ -798,10 +788,7 @@ fn serde_as_sat() {
|
|||
}
|
||||
|
||||
serde_test::assert_tokens(
|
||||
&T {
|
||||
amt: Amount::from_sat_unchecked(123_456_789),
|
||||
samt: SignedAmount::from_sat_unchecked(-123_456_789),
|
||||
},
|
||||
&T { amt: sat(123_456_789), samt: ssat(-123_456_789) },
|
||||
&[
|
||||
serde_test::Token::Struct { name: "T", len: 2 },
|
||||
serde_test::Token::Str("amt"),
|
||||
|
@ -828,10 +815,7 @@ fn serde_as_btc() {
|
|||
pub samt: SignedAmount,
|
||||
}
|
||||
|
||||
let orig = T {
|
||||
amt: Amount::from_sat_unchecked(20_000_000__000_000_01),
|
||||
samt: SignedAmount::from_sat_unchecked(-20_000_000__000_000_01),
|
||||
};
|
||||
let orig = T { amt: sat(20_000_000__000_000_01), samt: ssat(-20_000_000__000_000_01) };
|
||||
|
||||
let json = "{\"amt\": 20000000.00000001, \
|
||||
\"samt\": -20000000.00000001}";
|
||||
|
@ -865,10 +849,7 @@ fn serde_as_str() {
|
|||
}
|
||||
|
||||
serde_test::assert_tokens(
|
||||
&T {
|
||||
amt: Amount::from_sat_unchecked(123_456_789),
|
||||
samt: SignedAmount::from_sat_unchecked(-123_456_789),
|
||||
},
|
||||
&T { amt: sat(123_456_789), samt: ssat(-123_456_789) },
|
||||
&[
|
||||
serde_test::Token::Struct { name: "T", len: 2 },
|
||||
serde_test::Token::String("amt"),
|
||||
|
@ -895,10 +876,7 @@ fn serde_as_btc_opt() {
|
|||
pub samt: Option<SignedAmount>,
|
||||
}
|
||||
|
||||
let with = T {
|
||||
amt: Some(Amount::from_sat_unchecked(2_500_000_00)),
|
||||
samt: Some(SignedAmount::from_sat_unchecked(-2_500_000_00)),
|
||||
};
|
||||
let with = T { amt: Some(sat(2_500_000_00)), samt: Some(ssat(-2_500_000_00)) };
|
||||
let without = T { amt: None, samt: None };
|
||||
|
||||
// Test Roundtripping
|
||||
|
@ -937,10 +915,7 @@ fn serde_as_sat_opt() {
|
|||
pub samt: Option<SignedAmount>,
|
||||
}
|
||||
|
||||
let with = T {
|
||||
amt: Some(Amount::from_sat_unchecked(2_500_000_00)),
|
||||
samt: Some(SignedAmount::from_sat_unchecked(-2_500_000_00)),
|
||||
};
|
||||
let with = T { amt: Some(sat(2_500_000_00)), samt: Some(ssat(-2_500_000_00)) };
|
||||
let without = T { amt: None, samt: None };
|
||||
|
||||
// Test Roundtripping
|
||||
|
@ -979,10 +954,7 @@ fn serde_as_str_opt() {
|
|||
pub samt: Option<SignedAmount>,
|
||||
}
|
||||
|
||||
let with = T {
|
||||
amt: Some(Amount::from_sat_unchecked(123_456_789)),
|
||||
samt: Some(SignedAmount::from_sat_unchecked(-123_456_789)),
|
||||
};
|
||||
let with = T { amt: Some(sat(123_456_789)), samt: Some(ssat(-123_456_789)) };
|
||||
let without = T { amt: None, samt: None };
|
||||
|
||||
// Test Roundtripping
|
||||
|
@ -1009,9 +981,6 @@ fn serde_as_str_opt() {
|
|||
|
||||
#[test]
|
||||
fn sum_amounts() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!([].iter().sum::<NumOpResult<Amount>>(), Amount::ZERO.into());
|
||||
assert_eq!([].iter().sum::<NumOpResult<SignedAmount>>(), SignedAmount::ZERO.into());
|
||||
|
||||
|
@ -1044,9 +1013,6 @@ fn sum_amounts() {
|
|||
|
||||
#[test]
|
||||
fn checked_sum_amounts() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!([].into_iter().checked_sum(), Some(Amount::ZERO));
|
||||
assert_eq!([].into_iter().checked_sum(), Some(SignedAmount::ZERO));
|
||||
|
||||
|
@ -1112,8 +1078,6 @@ fn disallow_unknown_denomination() {
|
|||
#[test]
|
||||
#[cfg(feature = "alloc")]
|
||||
fn trailing_zeros_for_amount() {
|
||||
let sat = Amount::from_sat;
|
||||
|
||||
assert_eq!(format!("{}", sat(1_000_000)), "0.01 BTC");
|
||||
assert_eq!(format!("{}", Amount::ONE_SAT), "0.00000001 BTC");
|
||||
assert_eq!(format!("{}", Amount::ONE_BTC), "1 BTC");
|
||||
|
@ -1194,8 +1158,6 @@ fn add_sub_combos() {
|
|||
|
||||
#[test]
|
||||
fn unsigned_addition() {
|
||||
let sat = Amount::from_sat;
|
||||
|
||||
assert_eq!(sat(0) + sat(0), NumOpResult::from(sat(0)));
|
||||
assert_eq!(sat(0) + sat(307), NumOpResult::from(sat(307)));
|
||||
assert_eq!(sat(307) + sat(0), NumOpResult::from(sat(307)));
|
||||
|
@ -1205,8 +1167,6 @@ fn unsigned_addition() {
|
|||
|
||||
#[test]
|
||||
fn signed_addition() {
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!(ssat(0) + ssat(0), NumOpResult::from(ssat(0)));
|
||||
assert_eq!(ssat(0) + ssat(307), NumOpResult::from(ssat(307)));
|
||||
assert_eq!(ssat(307) + ssat(0), NumOpResult::from(ssat(307)));
|
||||
|
@ -1226,8 +1186,6 @@ fn signed_addition() {
|
|||
|
||||
#[test]
|
||||
fn unsigned_subtraction() {
|
||||
let sat = Amount::from_sat;
|
||||
|
||||
assert_eq!(sat(0) - sat(0), NumOpResult::from(sat(0)));
|
||||
assert_eq!(sat(307) - sat(0), NumOpResult::from(sat(307)));
|
||||
assert_eq!(sat(461) - sat(307), NumOpResult::from(sat(154)));
|
||||
|
@ -1235,8 +1193,6 @@ fn unsigned_subtraction() {
|
|||
|
||||
#[test]
|
||||
fn signed_subtraction() {
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!(ssat(0) - ssat(0), NumOpResult::from(ssat(0)));
|
||||
assert_eq!(ssat(0) - ssat(307), NumOpResult::from(ssat(-307)));
|
||||
assert_eq!(ssat(307) - ssat(0), NumOpResult::from(ssat(307)));
|
||||
|
@ -1252,11 +1208,8 @@ fn signed_subtraction() {
|
|||
|
||||
#[test]
|
||||
fn op_int_combos() {
|
||||
let sat = Amount::from_sat;
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
let res = |sat| NumOpResult::from(Amount::from_sat(sat));
|
||||
let sres = |ssat| NumOpResult::from(SignedAmount::from_sat(ssat));
|
||||
let res = |n_sat| NumOpResult::from(sat(n_sat));
|
||||
let sres = |n_ssat| NumOpResult::from(ssat(n_ssat));
|
||||
|
||||
assert_eq!(sat(23) * 31, res(713));
|
||||
assert_eq!(ssat(23) * 31, sres(713));
|
||||
|
@ -1295,8 +1248,6 @@ fn op_int_combos() {
|
|||
|
||||
#[test]
|
||||
fn unsigned_amount_div_by_amount() {
|
||||
let sat = Amount::from_sat;
|
||||
|
||||
assert_eq!(sat(0) / sat(7), 0);
|
||||
assert_eq!(sat(1897) / sat(7), 271);
|
||||
}
|
||||
|
@ -1309,8 +1260,6 @@ fn unsigned_amount_div_by_amount_zero() {
|
|||
|
||||
#[test]
|
||||
fn signed_amount_div_by_amount() {
|
||||
let ssat = SignedAmount::from_sat;
|
||||
|
||||
assert_eq!(ssat(0) / ssat(7), 0);
|
||||
|
||||
assert_eq!(ssat(1897) / ssat(7), 271);
|
||||
|
@ -1331,7 +1280,7 @@ fn check_const() {
|
|||
assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
|
||||
assert_eq!(SignedAmount::FIFTY_BTC.to_sat(), SignedAmount::ONE_BTC.to_sat() * 50);
|
||||
assert_eq!(Amount::FIFTY_BTC.to_sat(), Amount::ONE_BTC.to_sat() * 50);
|
||||
assert_eq!(Amount::MAX_MONEY.to_sat() as i64, SignedAmount::MAX_MONEY.to_sat());
|
||||
assert_eq!(Amount::MAX.to_sat() as i64, SignedAmount::MAX.to_sat());
|
||||
}
|
||||
|
||||
// Sanity check than stdlib supports the set of reference combinations for the ops we want.
|
||||
|
|
|
@ -16,41 +16,70 @@ use super::{
|
|||
OutOfRangeError, ParseAmountError, ParseError, SignedAmount,
|
||||
};
|
||||
|
||||
/// An amount.
|
||||
///
|
||||
/// The [`Amount`] type can be used to express Bitcoin amounts that support arithmetic and
|
||||
/// conversion to various denominations. The [`Amount`] type does not implement [`serde`] traits
|
||||
/// but we do provide modules for serializing as satoshis or bitcoin.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "serde")] {
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
/// use bitcoin_units::Amount;
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct Foo {
|
||||
/// // If you are using `rust-bitcoin` then `bitcoin::amount::serde::as_sat` also works.
|
||||
/// #[serde(with = "bitcoin_units::amount::serde::as_sat")] // Also `serde::as_btc`.
|
||||
/// amount: Amount,
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Amount(u64);
|
||||
mod encapsulate {
|
||||
/// An amount.
|
||||
///
|
||||
/// The [`Amount`] type can be used to express Bitcoin amounts that support arithmetic and
|
||||
/// conversion to various denominations. The [`Amount`] type does not implement [`serde`] traits
|
||||
/// but we do provide modules for serializing as satoshis or bitcoin.
|
||||
///
|
||||
/// Warning!
|
||||
///
|
||||
/// This type implements several arithmetic operations from [`core::ops`].
|
||||
/// To prevent errors due to an overflow when using these operations,
|
||||
/// it is advised to instead use the checked arithmetic methods whose names
|
||||
/// start with `checked_`. The operations from [`core::ops`] that [`Amount`]
|
||||
/// implements will panic when an overflow occurs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "serde")] {
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
/// use bitcoin_units::Amount;
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct Foo {
|
||||
/// // If you are using `rust-bitcoin` then `bitcoin::amount::serde::as_sat` also works.
|
||||
/// #[serde(with = "bitcoin_units::amount::serde::as_sat")] // Also `serde::as_btc`.
|
||||
/// amount: Amount,
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Amount(u64);
|
||||
|
||||
impl Amount {
|
||||
/// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
|
||||
///
|
||||
/// Caller to guarantee that `satoshi` is within valid range. See [`Self::MAX`].
|
||||
pub const fn from_sat_unchecked(satoshi: u64) -> Amount { Self(satoshi) }
|
||||
|
||||
/// Gets the number of satoshis in this [`Amount`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bitcoin_units::Amount;
|
||||
/// assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
|
||||
/// ```
|
||||
pub const fn to_sat(self) -> u64 { self.0 }
|
||||
}
|
||||
}
|
||||
#[doc(inline)]
|
||||
pub use encapsulate::Amount;
|
||||
|
||||
impl Amount {
|
||||
/// The zero amount.
|
||||
pub const ZERO: Self = Amount(0);
|
||||
pub const ZERO: Self = Amount::from_sat_unchecked(0);
|
||||
/// Exactly one satoshi.
|
||||
pub const ONE_SAT: Self = Amount(1);
|
||||
pub const ONE_SAT: Self = Amount::from_sat_unchecked(1);
|
||||
/// Exactly one bitcoin.
|
||||
pub const ONE_BTC: Self = Self::from_int_btc_const(1);
|
||||
pub const ONE_BTC: Self = Amount::from_sat_unchecked(100_000_000);
|
||||
/// Exactly fifty bitcoin.
|
||||
pub const FIFTY_BTC: Self = Self::from_sat_unchecked(50 * 100_000_000);
|
||||
pub const FIFTY_BTC: Self = Amount::from_sat_unchecked(50 * 100_000_000);
|
||||
/// The maximum value allowed as an amount. Useful for sanity checking.
|
||||
pub const MAX_MONEY: Self = Self::from_int_btc_const(21_000_000);
|
||||
pub const MAX_MONEY: Self = Amount::from_sat_unchecked(21_000_000 * 100_000_000);
|
||||
/// The minimum value of an amount.
|
||||
pub const MIN: Self = Amount::ZERO;
|
||||
/// The maximum value of an amount.
|
||||
|
@ -67,22 +96,7 @@ impl Amount {
|
|||
/// let amount = Amount::from_sat(100_000);
|
||||
/// assert_eq!(amount.to_sat(), 100_000);
|
||||
/// ```
|
||||
pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) }
|
||||
|
||||
/// Gets the number of satoshis in this [`Amount`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bitcoin_units::Amount;
|
||||
/// assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
|
||||
/// ```
|
||||
pub const fn to_sat(self) -> u64 { self.0 }
|
||||
|
||||
/// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
|
||||
///
|
||||
/// Caller to guarantee that `satoshi` is within valid range. See [`Self::MAX_MONEY`].
|
||||
pub const fn from_sat_unchecked(satoshi: u64) -> Amount { Self(satoshi) }
|
||||
pub const fn from_sat(satoshi: u64) -> Amount { Amount::from_sat_unchecked(satoshi) }
|
||||
|
||||
/// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
|
||||
///
|
||||
|
@ -105,30 +119,19 @@ impl Amount {
|
|||
}
|
||||
|
||||
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The function errors if the argument multiplied by the number of sats
|
||||
/// per bitcoin overflows a `u64` type.
|
||||
pub fn from_int_btc<T: Into<u64>>(whole_bitcoin: T) -> Result<Amount, OutOfRangeError> {
|
||||
match whole_bitcoin.into().checked_mul(100_000_000) {
|
||||
Some(amount) => Ok(Self::from_sat(amount)),
|
||||
None => Err(OutOfRangeError { is_signed: false, is_greater_than_max: true }),
|
||||
}
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn from_int_btc<T: Into<u32>>(whole_bitcoin: T) -> Amount {
|
||||
Amount::from_int_btc_const(whole_bitcoin.into())
|
||||
}
|
||||
|
||||
/// Converts from a value expressing a whole number of bitcoin 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.
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub const fn from_int_btc_const(whole_bitcoin: u32) -> Amount {
|
||||
let btc = whole_bitcoin as u64; // Can't call u64::from in const context.
|
||||
let btc = whole_bitcoin as u64; // Can't call `into` in const context.
|
||||
match btc.checked_mul(100_000_000) {
|
||||
Some(amount) => Amount::from_sat(amount),
|
||||
None => panic!("checked_mul overflowed"),
|
||||
None => panic!("cannot overflow a u64"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +151,7 @@ impl Amount {
|
|||
OutOfRangeError::negative(),
|
||||
)));
|
||||
}
|
||||
if sats > Self::MAX.0 {
|
||||
if sats > Self::MAX.to_sat() {
|
||||
return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
|
||||
OutOfRangeError::too_big(false),
|
||||
)));
|
||||
|
@ -299,8 +302,8 @@ impl Amount {
|
|||
#[must_use]
|
||||
pub const fn checked_add(self, rhs: Amount) -> Option<Amount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_add(rhs.0) {
|
||||
Some(res) => Amount(res).check_max(),
|
||||
match self.to_sat().checked_add(rhs.to_sat()) {
|
||||
Some(res) => Amount::from_sat(res).check_max(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -311,8 +314,8 @@ impl Amount {
|
|||
#[must_use]
|
||||
pub const fn checked_sub(self, rhs: Amount) -> Option<Amount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_sub(rhs.0) {
|
||||
Some(res) => Some(Amount(res)),
|
||||
match self.to_sat().checked_sub(rhs.to_sat()) {
|
||||
Some(res) => Some(Amount::from_sat(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -323,8 +326,8 @@ impl Amount {
|
|||
#[must_use]
|
||||
pub const fn checked_mul(self, rhs: u64) -> Option<Amount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_mul(rhs) {
|
||||
Some(res) => Amount(res).check_max(),
|
||||
match self.to_sat().checked_mul(rhs) {
|
||||
Some(res) => Amount::from_sat(res).check_max(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -337,8 +340,8 @@ impl Amount {
|
|||
#[must_use]
|
||||
pub const fn checked_div(self, rhs: u64) -> Option<Amount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_div(rhs) {
|
||||
Some(res) => Some(Amount(res)),
|
||||
match self.to_sat().checked_div(rhs) {
|
||||
Some(res) => Some(Amount::from_sat(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -349,34 +352,12 @@ impl Amount {
|
|||
#[must_use]
|
||||
pub const fn checked_rem(self, rhs: u64) -> Option<Amount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_rem(rhs) {
|
||||
Some(res) => Some(Amount(res)),
|
||||
match self.to_sat().checked_rem(rhs) {
|
||||
Some(res) => Some(Amount::from_sat(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unchecked addition.
|
||||
///
|
||||
/// Computes `self + rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
#[must_use]
|
||||
#[deprecated(since = "TBD", note = "consider converting to u64 using `to_sat`")]
|
||||
pub fn unchecked_add(self, rhs: Amount) -> Amount { Self(self.0 + rhs.0) }
|
||||
|
||||
/// Unchecked subtraction.
|
||||
///
|
||||
/// Computes `self - rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
#[must_use]
|
||||
#[deprecated(since = "TBD", note = "consider converting to u64 using `to_sat`")]
|
||||
pub fn unchecked_sub(self, rhs: Amount) -> Amount { Self(self.0 - rhs.0) }
|
||||
|
||||
/// Converts to a signed amount.
|
||||
#[rustfmt::skip] // Moves code comments to the wrong line.
|
||||
pub fn to_signed(self) -> SignedAmount {
|
||||
|
@ -385,7 +366,7 @@ impl Amount {
|
|||
|
||||
/// Checks if the amount is below the maximum value. Returns `None` if it is above.
|
||||
const fn check_max(self) -> Option<Amount> {
|
||||
if self.0 > Self::MAX.0 {
|
||||
if self.to_sat() > Self::MAX.to_sat() {
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
|
@ -445,6 +426,6 @@ impl TryFrom<SignedAmount> for Amount {
|
|||
impl<'a> Arbitrary<'a> for Amount {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let a = u64::arbitrary(u)?;
|
||||
Ok(Self(a))
|
||||
Ok(Self::from_sat(a))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
use core::ops;
|
||||
|
||||
use crate::amount::{NumOpResult, OptionExt};
|
||||
use crate::{Amount, FeeRate, Weight};
|
||||
|
||||
impl Amount {
|
||||
|
@ -160,17 +161,15 @@ impl FeeRate {
|
|||
|
||||
/// Computes the ceiling so that the fee computation is conservative.
|
||||
impl ops::Mul<FeeRate> for Weight {
|
||||
type Output = Amount;
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
||||
fn mul(self, rhs: FeeRate) -> Self::Output {
|
||||
Amount::from_sat((rhs.to_sat_per_kwu() * self.to_wu() + 999) / 1000)
|
||||
}
|
||||
fn mul(self, rhs: FeeRate) -> Self::Output { rhs.checked_mul_by_weight(self).valid_or_error() }
|
||||
}
|
||||
|
||||
impl ops::Mul<Weight> for FeeRate {
|
||||
type Output = Amount;
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
||||
fn mul(self, rhs: Weight) -> Self::Output { rhs * self }
|
||||
fn mul(self, rhs: Weight) -> Self::Output { self.checked_mul_by_weight(rhs).valid_or_error() }
|
||||
}
|
||||
|
||||
impl ops::Div<Weight> for Amount {
|
||||
|
@ -265,7 +264,7 @@ mod tests {
|
|||
let three = Weight::from_vb(3).unwrap();
|
||||
let six = Amount::from_sat_unchecked(6);
|
||||
|
||||
assert_eq!(two * three, six);
|
||||
assert_eq!(two * three, six.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -22,10 +22,10 @@ macro_rules! check {
|
|||
|
||||
check! {
|
||||
amount_unsigned_one_sat, Amount, Amount::ONE_SAT, "0.00000001 BTC";
|
||||
amount_unsigned_max_money, Amount, Amount::MAX_MONEY, "21000000 BTC";
|
||||
amount_unsigned_max_money, Amount, Amount::MAX, "21000000 BTC";
|
||||
|
||||
amount_signed_one_sat, SignedAmount, SignedAmount::ONE_SAT, "0.00000001 BTC";
|
||||
amount_signed_max_money, SignedAmount, SignedAmount::MAX_MONEY, "21000000 BTC";
|
||||
amount_signed_max_money, SignedAmount, SignedAmount::MAX, "21000000 BTC";
|
||||
|
||||
block_height_min, BlockHeight, BlockHeight::MIN, "0";
|
||||
block_height_max, BlockHeight, BlockHeight::MAX, "4294967295";
|
||||
|
|
Loading…
Reference in New Issue