Merge rust-bitcoin/rust-bitcoin#4157: Enforce MAX_MONEY invariant in amount types
ab4ea7c13d
Enforce the MAX_MONEY invariant in amount types (Tobin C. Harding)
Pull request description:
Enforcing the `MAX_MONEY` invariant is quite involved because it means multiple things:
- Constructing amounts is now fallible
- Converting from unsigned to signed is now infallible
- Taking the absolute value is now infallible
- Integer overflow is eliminated in various places
Details:
- Update `from_sat` to check the invariant
- Fix all docs including examples
- Use the unchecked constructor in test code
- Comment any other use of the unchecked constructor
- Deprecate `unchecked_abs`
- Fail serde (using the horrible string error variant)
- Try not to use the unchecked constructor in rustdocs, no need to encourage unsuspecting users to use it.
- Use `?` in rustdoc examples (required by Rust API guidlines)
- Remove `TryFrom<Amount> for SignedAmount` because the conversion is now infallible. Add a `From` impl.
- Fix the arbitrary impls
- Maintain correct formatting
- Remove private `check_max` function as its no longer needed
Close #620
ACKs for top commit:
apoelstra:
ACK ab4ea7c13d08411044bd5f9c17457e926c80ed4d; successfully ran local tests
Tree-SHA512: bec963d8ea69e202f399cd19bca864b06f3e86323d376c2d2126d74093598f8bbbf19792b2327dba0862ef6f0201202778014a2be7a14991f02917d8ca312afb
This commit is contained in:
commit
0ca9fcfd0e
|
@ -413,7 +413,7 @@ crate::internal_macros::define_extension_trait! {
|
||||||
// Note: We ensure the division happens at the end, since Core performs the division at the end.
|
// 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.
|
// This will make sure none of the implicit floor operations mess with the value.
|
||||||
|
|
||||||
Some(Amount::from_sat(sats))
|
Amount::from_sat(sats).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_sigops_internal(&self, accurate: bool) -> usize {
|
fn count_sigops_internal(&self, accurate: bool) -> usize {
|
||||||
|
|
|
@ -198,7 +198,7 @@ pub mod amount {
|
||||||
//! This module mainly introduces the [`Amount`] and [`SignedAmount`] types.
|
//! This module mainly introduces the [`Amount`] and [`SignedAmount`] types.
|
||||||
//! We refer to the documentation on the types for more information.
|
//! We refer to the documentation on the types for more information.
|
||||||
|
|
||||||
use crate::consensus::{encode, Decodable, Encodable};
|
use crate::consensus::{self, encode, Decodable, Encodable};
|
||||||
use crate::io::{BufRead, Write};
|
use crate::io::{BufRead, Write};
|
||||||
|
|
||||||
#[rustfmt::skip] // Keep public re-exports separate.
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
|
@ -215,7 +215,9 @@ pub mod amount {
|
||||||
impl Decodable for Amount {
|
impl Decodable for Amount {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
|
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
|
||||||
Ok(Amount::from_sat(Decodable::consensus_decode(r)?))
|
Amount::from_sat(Decodable::consensus_decode(r)?).map_err(|_| {
|
||||||
|
consensus::parse_failed_error("amount is greater than Amount::MAX_MONEY")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1268,7 +1268,7 @@ mod tests {
|
||||||
witness: Witness::default(),
|
witness: Witness::default(),
|
||||||
}],
|
}],
|
||||||
output: vec![TxOut {
|
output: vec![TxOut {
|
||||||
value: Amount::from_sat(output),
|
value: Amount::from_sat(output).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex(
|
script_pubkey: ScriptBuf::from_hex(
|
||||||
"a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
|
"a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
|
||||||
)
|
)
|
||||||
|
@ -1282,7 +1282,7 @@ mod tests {
|
||||||
|
|
||||||
inputs: vec![Input {
|
inputs: vec![Input {
|
||||||
witness_utxo: Some(TxOut {
|
witness_utxo: Some(TxOut {
|
||||||
value: Amount::from_sat(input),
|
value: Amount::from_sat(input).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex(
|
script_pubkey: ScriptBuf::from_hex(
|
||||||
"a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
|
"a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
|
||||||
)
|
)
|
||||||
|
|
|
@ -205,7 +205,7 @@ fn create_psbt_for_taproot_key_path_spend(
|
||||||
) -> Psbt {
|
) -> Psbt {
|
||||||
let send_value = 6400;
|
let send_value = 6400;
|
||||||
let out_puts = vec![TxOut {
|
let out_puts = vec![TxOut {
|
||||||
value: Amount::from_sat(send_value),
|
value: Amount::from_sat(send_value).unwrap(),
|
||||||
script_pubkey: to_address.script_pubkey(),
|
script_pubkey: to_address.script_pubkey(),
|
||||||
}];
|
}];
|
||||||
let prev_tx_id = "06980ca116f74c7845a897461dd0e1d15b114130176de5004957da516b4dee3a";
|
let prev_tx_id = "06980ca116f74c7845a897461dd0e1d15b114130176de5004957da516b4dee3a";
|
||||||
|
@ -243,7 +243,7 @@ fn create_psbt_for_taproot_key_path_spend(
|
||||||
let mut input = Input {
|
let mut input = Input {
|
||||||
witness_utxo: {
|
witness_utxo: {
|
||||||
let script_pubkey = from_address.script_pubkey();
|
let script_pubkey = from_address.script_pubkey();
|
||||||
Some(TxOut { value: Amount::from_sat(utxo_value), script_pubkey })
|
Some(TxOut { value: Amount::from_sat(utxo_value).unwrap(), script_pubkey })
|
||||||
},
|
},
|
||||||
tap_key_origins: origins,
|
tap_key_origins: origins,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -283,7 +283,7 @@ fn create_psbt_for_taproot_script_path_spend(
|
||||||
let mfp = "73c5da0a";
|
let mfp = "73c5da0a";
|
||||||
|
|
||||||
let out_puts = vec![TxOut {
|
let out_puts = vec![TxOut {
|
||||||
value: Amount::from_sat(send_value),
|
value: Amount::from_sat(send_value).unwrap(),
|
||||||
script_pubkey: to_address.script_pubkey(),
|
script_pubkey: to_address.script_pubkey(),
|
||||||
}];
|
}];
|
||||||
let prev_tx_id = "9d7c6770fca57285babab60c51834cfcfd10ad302119cae842d7216b4ac9a376";
|
let prev_tx_id = "9d7c6770fca57285babab60c51834cfcfd10ad302119cae842d7216b4ac9a376";
|
||||||
|
@ -322,7 +322,7 @@ fn create_psbt_for_taproot_script_path_spend(
|
||||||
let mut input = Input {
|
let mut input = Input {
|
||||||
witness_utxo: {
|
witness_utxo: {
|
||||||
let script_pubkey = from_address.script_pubkey();
|
let script_pubkey = from_address.script_pubkey();
|
||||||
Some(TxOut { value: Amount::from_sat(utxo_value), script_pubkey })
|
Some(TxOut { value: Amount::from_sat(utxo_value).unwrap(), script_pubkey })
|
||||||
},
|
},
|
||||||
tap_key_origins: origins,
|
tap_key_origins: origins,
|
||||||
tap_scripts,
|
tap_scripts,
|
||||||
|
|
|
@ -670,7 +670,7 @@ mod tests {
|
||||||
witness: Witness::new(),
|
witness: Witness::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let txout = TxOut { value: Amount::from_sat(123_456_789), script_pubkey: ScriptBuf::new() };
|
let txout = TxOut { value: Amount::from_sat(123_456_789).unwrap(), script_pubkey: ScriptBuf::new() };
|
||||||
|
|
||||||
let tx_orig = Transaction {
|
let tx_orig = Transaction {
|
||||||
version: Version::ONE,
|
version: Version::ONE,
|
||||||
|
@ -682,7 +682,7 @@ mod tests {
|
||||||
// Test changing the transaction
|
// Test changing the transaction
|
||||||
let mut tx = tx_orig.clone();
|
let mut tx = tx_orig.clone();
|
||||||
tx.inputs_mut()[0].previous_output.txid = Txid::from_byte_array([0xFF; 32]);
|
tx.inputs_mut()[0].previous_output.txid = Txid::from_byte_array([0xFF; 32]);
|
||||||
tx.outputs_mut()[0].value = Amount::from_sat(987_654_321);
|
tx.outputs_mut()[0].value = Amount::from_sat(987_654_321).unwrap();
|
||||||
assert_eq!(tx.inputs()[0].previous_output.txid.to_byte_array(), [0xFF; 32]);
|
assert_eq!(tx.inputs()[0].previous_output.txid.to_byte_array(), [0xFF; 32]);
|
||||||
assert_eq!(tx.outputs()[0].value.to_sat(), 987_654_321);
|
assert_eq!(tx.outputs()[0].value.to_sat(), 987_654_321);
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub(crate) use self::result::OptionExt;
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::Amount;
|
/// # use bitcoin_units::{amount, Amount};
|
||||||
///
|
///
|
||||||
/// let equal = [
|
/// let equal = [
|
||||||
/// ("1 BTC", 100_000_000),
|
/// ("1 BTC", 100_000_000),
|
||||||
|
@ -72,9 +72,10 @@ pub(crate) use self::result::OptionExt;
|
||||||
/// for (string, sats) in equal {
|
/// for (string, sats) in equal {
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// string.parse::<Amount>().expect("valid bitcoin amount string"),
|
/// string.parse::<Amount>().expect("valid bitcoin amount string"),
|
||||||
/// Amount::from_sat(sats),
|
/// Amount::from_sat(sats)?,
|
||||||
/// )
|
/// )
|
||||||
/// }
|
/// }
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|
|
@ -328,7 +328,9 @@ crate::internal_macros::impl_op_for_references! {
|
||||||
impl ops::Neg for SignedAmount {
|
impl ops::Neg for SignedAmount {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn neg(self) -> Self::Output { Self::from_sat(self.to_sat().neg()) }
|
fn neg(self) -> Self::Output {
|
||||||
|
Self::from_sat(self.to_sat().neg()).expect("all +ve and -ve values are valid")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::iter::Sum<NumOpResult<Amount>> for NumOpResult<Amount> {
|
impl core::iter::Sum<NumOpResult<Amount>> for NumOpResult<Amount> {
|
||||||
|
|
|
@ -92,7 +92,8 @@ impl SerdeAmount for Amount {
|
||||||
u64::serialize(&self.to_sat(), s)
|
u64::serialize(&self.to_sat(), s)
|
||||||
}
|
}
|
||||||
fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
|
fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
|
||||||
Ok(Amount::from_sat(u64::deserialize(d)?))
|
use serde::de::Error;
|
||||||
|
Amount::from_sat(u64::deserialize(d)?).map_err(D::Error::custom)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
|
fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
|
||||||
|
@ -137,7 +138,8 @@ impl SerdeAmount for SignedAmount {
|
||||||
i64::serialize(&self.to_sat(), s)
|
i64::serialize(&self.to_sat(), s)
|
||||||
}
|
}
|
||||||
fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
|
fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
|
||||||
Ok(SignedAmount::from_sat(i64::deserialize(d)?))
|
use serde::de::Error;
|
||||||
|
SignedAmount::from_sat(i64::deserialize(d)?).map_err(D::Error::custom)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
|
fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
|
||||||
|
|
|
@ -87,17 +87,29 @@ impl SignedAmount {
|
||||||
/// The maximum value of an amount.
|
/// The maximum value of an amount.
|
||||||
pub const MAX: Self = SignedAmount::MAX_MONEY;
|
pub const MAX: Self = SignedAmount::MAX_MONEY;
|
||||||
|
|
||||||
/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
|
/// Constructs a new [`SignedAmount`] from the given number of satoshis.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `satoshi` is outside of valid range (see [`Self::MAX_MONEY`]).
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::SignedAmount;
|
/// # use bitcoin_units::{amount, SignedAmount};
|
||||||
/// let amount = SignedAmount::from_sat(-100_000);
|
/// # let sat = -100_000;
|
||||||
/// assert_eq!(amount.to_sat(), -100_000);
|
/// let amount = SignedAmount::from_sat(sat)?;
|
||||||
|
/// assert_eq!(amount.to_sat(), sat);
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn from_sat(satoshi: i64) -> SignedAmount {
|
pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
|
||||||
SignedAmount::from_sat_unchecked(satoshi)
|
if satoshi < Self::MIN.to_sat() {
|
||||||
|
Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
|
||||||
|
} else if satoshi > Self::MAX_MONEY.to_sat() {
|
||||||
|
Err(OutOfRangeError { is_signed: true, is_greater_than_max: true })
|
||||||
|
} else {
|
||||||
|
Ok(SignedAmount::from_sat_unchecked(satoshi))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
|
/// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
|
||||||
|
@ -111,9 +123,10 @@ impl SignedAmount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::SignedAmount;
|
/// # use bitcoin_units::{amount, SignedAmount};
|
||||||
/// let amount = SignedAmount::from_btc(-0.01).expect("we know 0.01 is valid");
|
/// let amount = SignedAmount::from_btc(-0.01)?;
|
||||||
/// assert_eq!(amount.to_sat(), -1_000_000);
|
/// assert_eq!(amount.to_sat(), -1_000_000);
|
||||||
|
/// # Ok::<_, amount::ParseAmountError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
|
pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
|
||||||
|
@ -121,15 +134,23 @@ impl SignedAmount {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
|
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `whole_bitcoin` is greater than `21_000_000`.
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn from_int_btc<T: Into<i32>>(whole_bitcoin: T) -> SignedAmount {
|
pub fn from_int_btc<T: Into<i32>>(whole_bitcoin: T) -> Result<SignedAmount, OutOfRangeError> {
|
||||||
SignedAmount::from_int_btc_const(whole_bitcoin.into())
|
SignedAmount::from_int_btc_const(whole_bitcoin.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]
|
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]
|
||||||
/// in const context.
|
/// in const context.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `whole_bitcoin` is greater than `21_000_000`.
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub const fn from_int_btc_const(whole_bitcoin: i32) -> SignedAmount {
|
pub const fn from_int_btc_const(whole_bitcoin: i32) -> Result<SignedAmount, OutOfRangeError> {
|
||||||
let btc = whole_bitcoin as i64; // Can't call `into` in const context.
|
let btc = whole_bitcoin as i64; // Can't call `into` in const context.
|
||||||
match btc.checked_mul(100_000_000) {
|
match btc.checked_mul(100_000_000) {
|
||||||
Some(amount) => SignedAmount::from_sat(amount),
|
Some(amount) => SignedAmount::from_sat(amount),
|
||||||
|
@ -151,11 +172,11 @@ impl SignedAmount {
|
||||||
(false, sat) if sat > SignedAmount::MAX.to_sat() as u64 => Err(ParseAmountError(
|
(false, sat) if sat > SignedAmount::MAX.to_sat() as u64 => Err(ParseAmountError(
|
||||||
ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true)),
|
ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true)),
|
||||||
)),
|
)),
|
||||||
(false, sat) => Ok(SignedAmount::from_sat(sat as i64)), // Cast ok, value in this arm does not wrap.
|
(false, sat) => Ok(SignedAmount::from_sat_unchecked(sat as i64)), // Cast ok, value in this arm does not wrap.
|
||||||
(true, sat) if sat > SignedAmount::MIN.to_sat().unsigned_abs() => Err(
|
(true, sat) if sat > SignedAmount::MIN.to_sat().unsigned_abs() => Err(
|
||||||
ParseAmountError(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small())),
|
ParseAmountError(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small())),
|
||||||
),
|
),
|
||||||
(true, sat) => Ok(SignedAmount::from_sat(-(sat as i64))), // Cast ok, value in this arm does not wrap.
|
(true, sat) => Ok(SignedAmount::from_sat_unchecked(-(sat as i64))), // Cast ok, value in this arm does not wrap.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +194,7 @@ impl SignedAmount {
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::{amount, SignedAmount};
|
/// # use bitcoin_units::{amount, SignedAmount};
|
||||||
/// let amount = SignedAmount::from_str_with_denomination("0.1 BTC")?;
|
/// let amount = SignedAmount::from_str_with_denomination("0.1 BTC")?;
|
||||||
/// assert_eq!(amount, SignedAmount::from_sat(10_000_000));
|
/// assert_eq!(amount, SignedAmount::from_sat(10_000_000)?);
|
||||||
/// # Ok::<_, amount::ParseError>(())
|
/// # Ok::<_, amount::ParseError>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseError> {
|
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseError> {
|
||||||
|
@ -188,9 +209,10 @@ impl SignedAmount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{SignedAmount, Denomination};
|
/// # use bitcoin_units::amount::{self, SignedAmount, Denomination};
|
||||||
/// let amount = SignedAmount::from_sat(100_000);
|
/// let amount = SignedAmount::from_sat(100_000)?;
|
||||||
/// assert_eq!(amount.to_float_in(Denomination::Bitcoin), 0.001)
|
/// assert_eq!(amount.to_float_in(Denomination::Bitcoin), 0.001);
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
@ -205,9 +227,10 @@ impl SignedAmount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{SignedAmount, Denomination};
|
/// # use bitcoin_units::amount::{self, SignedAmount, Denomination};
|
||||||
/// let amount = SignedAmount::from_sat(100_000);
|
/// let amount = SignedAmount::from_sat(100_000)?;
|
||||||
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin))
|
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin));
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
|
pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
|
||||||
|
@ -236,13 +259,13 @@ impl SignedAmount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{SignedAmount, Denomination};
|
/// # use bitcoin_units::amount::{self, SignedAmount, Denomination};
|
||||||
/// # use std::fmt::Write;
|
/// # use std::fmt::Write;
|
||||||
/// let amount = SignedAmount::from_sat(10_000_000);
|
/// let amount = SignedAmount::from_sat(10_000_000)?;
|
||||||
/// let mut output = String::new();
|
/// let mut output = String::new();
|
||||||
/// write!(&mut output, "{}", amount.display_in(Denomination::Bitcoin))?;
|
/// let _ = write!(&mut output, "{}", amount.display_in(Denomination::Bitcoin));
|
||||||
/// assert_eq!(output, "0.1");
|
/// assert_eq!(output, "0.1");
|
||||||
/// # Ok::<(), std::fmt::Error>(())
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn display_in(self, denomination: Denomination) -> Display {
|
pub fn display_in(self, denomination: Denomination) -> Display {
|
||||||
|
@ -274,9 +297,10 @@ impl SignedAmount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{SignedAmount, Denomination};
|
/// # use bitcoin_units::amount::{self, SignedAmount, Denomination};
|
||||||
/// let amount = SignedAmount::from_sat(10_000_000);
|
/// let amount = SignedAmount::from_sat(10_000_000)?;
|
||||||
/// assert_eq!(amount.to_string_in(Denomination::Bitcoin), "0.1")
|
/// assert_eq!(amount.to_string_in(Denomination::Bitcoin), "0.1");
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
|
pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
|
||||||
|
@ -287,9 +311,10 @@ impl SignedAmount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{SignedAmount, Denomination};
|
/// # use bitcoin_units::amount::{self, SignedAmount, Denomination};
|
||||||
/// let amount = SignedAmount::from_sat(10_000_000);
|
/// let amount = SignedAmount::from_sat(10_000_000)?;
|
||||||
/// assert_eq!(amount.to_string_with_denomination(Denomination::Bitcoin), "0.1 BTC")
|
/// assert_eq!(amount.to_string_with_denomination(Denomination::Bitcoin), "0.1 BTC");
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
|
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
|
||||||
|
@ -297,12 +322,23 @@ impl SignedAmount {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the absolute value of this [`SignedAmount`].
|
/// Gets the absolute value of this [`SignedAmount`].
|
||||||
|
///
|
||||||
|
/// This function never overflows or panics, unlike `i64::abs()`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn abs(self) -> SignedAmount { SignedAmount::from_sat(self.to_sat().abs()) }
|
pub const fn abs(self) -> SignedAmount {
|
||||||
|
// `i64::abs()` can never overflow because SignedAmount::MIN == -MAX_MONEY.
|
||||||
|
match Self::from_sat(self.to_sat().abs()) {
|
||||||
|
Ok(amount) => amount,
|
||||||
|
Err(_) => panic!("a positive signed amount is always valid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the absolute value of this [`SignedAmount`] returning [`Amount`].
|
/// Gets the absolute value of this [`SignedAmount`] returning [`Amount`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.to_sat().unsigned_abs()) }
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
pub fn unsigned_abs(self) -> Amount {
|
||||||
|
self.abs().to_unsigned().expect("a positive signed amount is always valid")
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a number representing sign of this [`SignedAmount`].
|
/// Returns a number representing sign of this [`SignedAmount`].
|
||||||
///
|
///
|
||||||
|
@ -330,13 +366,9 @@ impl SignedAmount {
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if overflow occurred. (`self == i64::MIN`)
|
/// Returns [`None`] if overflow occurred. (`self == i64::MIN`)
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_abs(self) -> Option<SignedAmount> {
|
#[deprecated(since = "TBD", note = "Never returns none, use `abs()` instead")]
|
||||||
// No `map()` in const context.
|
#[allow(clippy::unnecessary_wraps)] // To match stdlib function definition.
|
||||||
match self.to_sat().checked_abs() {
|
pub const fn checked_abs(self) -> Option<SignedAmount> { Some(self.abs()) }
|
||||||
Some(res) => Some(SignedAmount::from_sat(res)),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checked addition.
|
/// Checked addition.
|
||||||
///
|
///
|
||||||
|
@ -345,7 +377,10 @@ impl SignedAmount {
|
||||||
pub const fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
pub const fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_add(rhs.to_sat()) {
|
match self.to_sat().checked_add(rhs.to_sat()) {
|
||||||
Some(res) => SignedAmount::from_sat(res).check_min_max(),
|
Some(res) => match SignedAmount::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,7 +393,10 @@ impl SignedAmount {
|
||||||
pub const fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
pub const fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_sub(rhs.to_sat()) {
|
match self.to_sat().checked_sub(rhs.to_sat()) {
|
||||||
Some(res) => SignedAmount::from_sat(res).check_min_max(),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -371,7 +409,10 @@ impl SignedAmount {
|
||||||
pub const fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
|
pub const fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_mul(rhs) {
|
match self.to_sat().checked_mul(rhs) {
|
||||||
Some(res) => SignedAmount::from_sat(res).check_min_max(),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -385,7 +426,10 @@ impl SignedAmount {
|
||||||
pub const fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
|
pub const fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_div(rhs) {
|
match self.to_sat().checked_div(rhs) {
|
||||||
Some(res) => Some(SignedAmount::from_sat(res)),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None, // Unreachable because of checked_div above.
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,7 +441,10 @@ impl SignedAmount {
|
||||||
pub const fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
|
pub const fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_rem(rhs) {
|
match self.to_sat().checked_rem(rhs) {
|
||||||
Some(res) => Some(SignedAmount::from_sat(res)),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None, // Unreachable because of checked_rem above.
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,20 +466,14 @@ impl SignedAmount {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If the amount is negative.
|
/// If the amount is negative.
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn to_unsigned(self) -> Result<Amount, OutOfRangeError> {
|
pub fn to_unsigned(self) -> Result<Amount, OutOfRangeError> {
|
||||||
if self.is_negative() {
|
if self.is_negative() {
|
||||||
Err(OutOfRangeError::negative())
|
Err(OutOfRangeError::negative())
|
||||||
} else {
|
} else {
|
||||||
Ok(Amount::from_sat(self.to_sat() as u64)) // Cast ok, checked not negative above.
|
// Cast ok, checked not negative above.
|
||||||
}
|
Ok(Amount::from_sat(self.to_sat() as u64)
|
||||||
}
|
.expect("a positive signed amount is always valid"))
|
||||||
|
|
||||||
/// Checks the amount is within the allowed range.
|
|
||||||
const fn check_min_max(self) -> Option<SignedAmount> {
|
|
||||||
if self.to_sat() < Self::MIN.to_sat() || self.to_sat() > Self::MAX.to_sat() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,14 +523,14 @@ impl FromStr for SignedAmount {
|
||||||
impl From<Amount> for SignedAmount {
|
impl From<Amount> for SignedAmount {
|
||||||
fn from(value: Amount) -> Self {
|
fn from(value: Amount) -> Self {
|
||||||
let v = value.to_sat() as i64; // Cast ok, signed amount and amount share positive range.
|
let v = value.to_sat() as i64; // Cast ok, signed amount and amount share positive range.
|
||||||
Self::from_sat_unchecked(v)
|
Self::from_sat(v).expect("all amounts are valid signed amounts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "arbitrary")]
|
#[cfg(feature = "arbitrary")]
|
||||||
impl<'a> Arbitrary<'a> for SignedAmount {
|
impl<'a> Arbitrary<'a> for SignedAmount {
|
||||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||||
let s = i64::arbitrary(u)?;
|
let sats = u.int_in_range(Self::MIN.to_sat()..=Self::MAX.to_sat())?;
|
||||||
Ok(Self::from_sat(s))
|
Ok(Self::from_sat(sats).expect("range is valid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,18 +16,19 @@ use super::*;
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
use crate::{FeeRate, Weight};
|
use crate::{FeeRate, Weight};
|
||||||
|
|
||||||
fn sat(sat: u64) -> Amount { Amount::from_sat(sat) }
|
#[track_caller]
|
||||||
fn ssat(ssat: i64) -> SignedAmount { SignedAmount::from_sat(ssat) }
|
fn sat(sat: u64) -> Amount { Amount::from_sat(sat).unwrap() }
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn ssat(ssat: i64) -> SignedAmount { SignedAmount::from_sat(ssat).unwrap() }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sanity_check() {
|
fn sanity_check() {
|
||||||
assert_eq!(ssat(-100).abs(), ssat(100));
|
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);
|
assert_eq!(ssat(-100).signum(), -1);
|
||||||
assert_eq!(ssat(0).signum(), 0);
|
assert_eq!(ssat(0).signum(), 0);
|
||||||
assert_eq!(ssat(100).signum(), 1);
|
assert_eq!(ssat(100).signum(), 1);
|
||||||
assert_eq!(SignedAmount::from(sat(100)), ssat(100));
|
assert_eq!(SignedAmount::from(sat(100)), ssat(100));
|
||||||
assert!(ssat(i64::MIN).checked_abs().is_none());
|
|
||||||
assert!(!ssat(-100).is_positive());
|
assert!(!ssat(-100).is_positive());
|
||||||
assert!(ssat(100).is_positive());
|
assert!(ssat(100).is_positive());
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ fn from_str_zero_without_denomination() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_int_btc() {
|
fn from_int_btc() {
|
||||||
let amt = Amount::from_int_btc_const(2);
|
let amt = Amount::from_int_btc_const(2).unwrap();
|
||||||
assert_eq!(sat(200_000_000), amt);
|
assert_eq!(sat(200_000_000), amt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,8 +119,8 @@ fn amount_try_from_signed_amount() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mul_div() {
|
fn mul_div() {
|
||||||
let op_result_sat = |sat| NumOpResult::Valid(Amount::from_sat(sat));
|
let op_result_sat = |a| NumOpResult::Valid(sat(a));
|
||||||
let op_result_ssat = |sat| NumOpResult::Valid(SignedAmount::from_sat(sat));
|
let op_result_ssat = |a| NumOpResult::Valid(ssat(a));
|
||||||
|
|
||||||
assert_eq!(sat(14) * 3, op_result_sat(42));
|
assert_eq!(sat(14) * 3, op_result_sat(42));
|
||||||
assert_eq!(sat(14) / 2, op_result_sat(7));
|
assert_eq!(sat(14) / 2, op_result_sat(7));
|
||||||
|
@ -131,19 +132,10 @@ fn mul_div() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn neg() {
|
fn neg() {
|
||||||
let amount = -SignedAmount::from_sat_unchecked(2);
|
let amount = -ssat(2);
|
||||||
assert_eq!(amount.to_sat(), -2);
|
assert_eq!(amount.to_sat(), -2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
#[test]
|
|
||||||
fn overflows() {
|
|
||||||
let result = Amount::MAX + sat(1);
|
|
||||||
assert!(result.is_error());
|
|
||||||
let result = sat(8_446_744_073_709_551_615) * 3;
|
|
||||||
assert!(result.is_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add() {
|
fn add() {
|
||||||
assert!(sat(0) + sat(0) == sat(0).into());
|
assert!(sat(0) + sat(0) == sat(0).into());
|
||||||
|
@ -258,12 +250,6 @@ fn amount_checked_div_by_fee_rate() {
|
||||||
let weight = max_amount.checked_div_by_fee_rate_floor(small_fee_rate).unwrap();
|
let weight = max_amount.checked_div_by_fee_rate_floor(small_fee_rate).unwrap();
|
||||||
// 21_000_000_0000_0000 sats / (1 sat/kwu) = 2_100_000_000_000_000_000 wu
|
// 21_000_000_0000_0000 sats / (1 sat/kwu) = 2_100_000_000_000_000_000 wu
|
||||||
assert_eq!(weight, Weight::from_wu(2_100_000_000_000_000_000));
|
assert_eq!(weight, Weight::from_wu(2_100_000_000_000_000_000));
|
||||||
|
|
||||||
// Test overflow case
|
|
||||||
let tiny_fee_rate = FeeRate::from_sat_per_kwu(1);
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
|
@ -417,8 +403,8 @@ macro_rules! check_format_non_negative {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn $test_name() {
|
fn $test_name() {
|
||||||
assert_eq!(format!($format_string, Amount::from_sat($val).display_in(Denomination::$denom)), $expected);
|
assert_eq!(format!($format_string, sat($val).display_in(Denomination::$denom)), $expected);
|
||||||
assert_eq!(format!($format_string, SignedAmount::from_sat($val as i64).display_in(Denomination::$denom)), $expected);
|
assert_eq!(format!($format_string, ssat($val as i64).display_in(Denomination::$denom)), $expected);
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
|
@ -430,8 +416,8 @@ macro_rules! check_format_non_negative_show_denom {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
fn $test_name() {
|
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, 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));
|
assert_eq!(format!($format_string, ssat($val as i64).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
|
@ -1020,7 +1006,7 @@ fn checked_sum_amounts() {
|
||||||
let sum = amounts.into_iter().checked_sum();
|
let sum = amounts.into_iter().checked_sum();
|
||||||
assert_eq!(sum, Some(sat(1400)));
|
assert_eq!(sum, Some(sat(1400)));
|
||||||
|
|
||||||
let amounts = [sat(u64::MAX), sat(1337), sat(21)];
|
let amounts = [Amount::MAX_MONEY, sat(1337), sat(21)];
|
||||||
let sum = amounts.into_iter().checked_sum();
|
let sum = amounts.into_iter().checked_sum();
|
||||||
assert_eq!(sum, None);
|
assert_eq!(sum, None);
|
||||||
|
|
||||||
|
@ -1119,7 +1105,7 @@ fn add_sub_combos() {
|
||||||
macro_rules! check_res {
|
macro_rules! check_res {
|
||||||
($($amount:ident, $op:tt, $lhs:literal, $rhs:literal, $ans:literal);* $(;)?) => {
|
($($amount:ident, $op:tt, $lhs:literal, $rhs:literal, $ans:literal);* $(;)?) => {
|
||||||
$(
|
$(
|
||||||
let amt = |sat| $amount::from_sat(sat);
|
let amt = |sat| $amount::from_sat(sat).unwrap();
|
||||||
|
|
||||||
let sat_lhs = amt($lhs);
|
let sat_lhs = amt($lhs);
|
||||||
let sat_rhs = amt($rhs);
|
let sat_rhs = amt($rhs);
|
||||||
|
@ -1255,7 +1241,7 @@ fn unsigned_amount_div_by_amount() {
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "attempt to divide by zero")]
|
#[should_panic(expected = "attempt to divide by zero")]
|
||||||
fn unsigned_amount_div_by_amount_zero() {
|
fn unsigned_amount_div_by_amount_zero() {
|
||||||
let _ = Amount::from_sat(1897) / Amount::ZERO;
|
let _ = sat(1897) / Amount::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1271,7 +1257,7 @@ fn signed_amount_div_by_amount() {
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "attempt to divide by zero")]
|
#[should_panic(expected = "attempt to divide by zero")]
|
||||||
fn signed_amount_div_by_amount_zero() {
|
fn signed_amount_div_by_amount_zero() {
|
||||||
let _ = SignedAmount::from_sat(1897) / SignedAmount::ZERO;
|
let _ = ssat(1897) / SignedAmount::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1307,8 +1293,8 @@ fn sanity_all_ops() {
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)] // We are explicitly testing the references work with ops.
|
#[allow(clippy::op_ref)] // We are explicitly testing the references work with ops.
|
||||||
fn num_op_result_ops() {
|
fn num_op_result_ops() {
|
||||||
let sat = Amount::from_sat(1);
|
let sat = Amount::from_sat(1).unwrap();
|
||||||
let ssat = SignedAmount::from_sat(1);
|
let ssat = SignedAmount::from_sat(1).unwrap();
|
||||||
|
|
||||||
// Explicit type as sanity check.
|
// Explicit type as sanity check.
|
||||||
let res: NumOpResult<Amount> = sat + sat;
|
let res: NumOpResult<Amount> = sat + sat;
|
||||||
|
@ -1358,8 +1344,8 @@ fn num_op_result_ops() {
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)] // We are explicitly testing the references work with ops.
|
#[allow(clippy::op_ref)] // We are explicitly testing the references work with ops.
|
||||||
fn num_op_result_ops_integer() {
|
fn num_op_result_ops_integer() {
|
||||||
let sat = Amount::from_sat(1);
|
let sat = Amount::from_sat(1).unwrap();
|
||||||
let ssat = SignedAmount::from_sat(1);
|
let ssat = SignedAmount::from_sat(1).unwrap();
|
||||||
|
|
||||||
// Explicit type as sanity check.
|
// Explicit type as sanity check.
|
||||||
let res: NumOpResult<Amount> = sat + sat;
|
let res: NumOpResult<Amount> = sat + sat;
|
||||||
|
@ -1401,8 +1387,8 @@ fn num_op_result_ops_integer() {
|
||||||
fn amount_op_result_neg() {
|
fn amount_op_result_neg() {
|
||||||
// TODO: Implement Neg all round.
|
// TODO: Implement Neg all round.
|
||||||
|
|
||||||
// let sat = Amount::from_sat(1);
|
// let sat = Amount::from_sat(1).unwrap();
|
||||||
let ssat = SignedAmount::from_sat(1);
|
let ssat = SignedAmount::from_sat(1).unwrap();
|
||||||
|
|
||||||
// let _ = -sat;
|
// let _ = -sat;
|
||||||
let _ = -ssat;
|
let _ = -ssat;
|
||||||
|
@ -1413,7 +1399,7 @@ fn amount_op_result_neg() {
|
||||||
// Verify we have implemented all `Sum` for the `NumOpResult` type.
|
// Verify we have implemented all `Sum` for the `NumOpResult` type.
|
||||||
#[test]
|
#[test]
|
||||||
fn amount_op_result_sum() {
|
fn amount_op_result_sum() {
|
||||||
let res = Amount::from_sat(1) + Amount::from_sat(1);
|
let res = Amount::from_sat(1).unwrap() + Amount::from_sat(1).unwrap();
|
||||||
let amounts = [res, res];
|
let amounts = [res, res];
|
||||||
let amount_refs = [&res, &res];
|
let amount_refs = [&res, &res];
|
||||||
|
|
||||||
|
|
|
@ -87,31 +87,44 @@ impl Amount {
|
||||||
/// The number of bytes that an amount contributes to the size of a transaction.
|
/// The number of bytes that an amount contributes to the size of a transaction.
|
||||||
pub const SIZE: usize = 8; // Serialized length of a u64.
|
pub const SIZE: usize = 8; // Serialized length of a u64.
|
||||||
|
|
||||||
/// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
|
/// Constructs a new [`Amount`] from the given number of satoshis.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `satoshi` is outside of valid range (greater than [`Self::MAX_MONEY`]).
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::Amount;
|
/// # use bitcoin_units::{amount, Amount};
|
||||||
/// let amount = Amount::from_sat(100_000);
|
/// # let sat = 100_000;
|
||||||
/// assert_eq!(amount.to_sat(), 100_000);
|
/// let amount = Amount::from_sat(sat)?;
|
||||||
|
/// assert_eq!(amount.to_sat(), sat);
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn from_sat(satoshi: u64) -> Amount { Amount::from_sat_unchecked(satoshi) }
|
pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
|
||||||
|
if satoshi > Self::MAX_MONEY.to_sat() {
|
||||||
|
Err(OutOfRangeError { is_signed: false, is_greater_than_max: true })
|
||||||
|
} else {
|
||||||
|
Ok(Self::from_sat_unchecked(satoshi))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
|
/// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If the amount is too big, too precise or negative.
|
/// If the amount is too precise, negative, or greater than 21,000,000.
|
||||||
///
|
///
|
||||||
/// Please be aware of the risk of using floating-point numbers.
|
/// Please be aware of the risk of using floating-point numbers.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::Amount;
|
/// # use bitcoin_units::{amount, Amount};
|
||||||
/// let amount = Amount::from_btc(0.01).expect("we know 0.01 is valid");
|
/// let amount = Amount::from_btc(0.01)?;
|
||||||
/// assert_eq!(amount.to_sat(), 1_000_000);
|
/// assert_eq!(amount.to_sat(), 1_000_000);
|
||||||
|
/// # Ok::<_, amount::ParseAmountError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn from_btc(btc: f64) -> Result<Amount, ParseAmountError> {
|
pub fn from_btc(btc: f64) -> Result<Amount, ParseAmountError> {
|
||||||
|
@ -119,15 +132,23 @@ impl Amount {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
|
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `whole_bitcoin` is greater than `21_000_000`.
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn from_int_btc<T: Into<u32>>(whole_bitcoin: T) -> Amount {
|
pub fn from_int_btc<T: Into<u32>>(whole_bitcoin: T) -> Result<Amount, OutOfRangeError> {
|
||||||
Amount::from_int_btc_const(whole_bitcoin.into())
|
Amount::from_int_btc_const(whole_bitcoin.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`]
|
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`]
|
||||||
/// in const context.
|
/// in const context.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `whole_bitcoin` is greater than `21_000_000`.
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub const fn from_int_btc_const(whole_bitcoin: u32) -> Amount {
|
pub const fn from_int_btc_const(whole_bitcoin: u32) -> Result<Amount, OutOfRangeError> {
|
||||||
let btc = whole_bitcoin as u64; // Can't call `into` in const context.
|
let btc = whole_bitcoin as u64; // Can't call `into` in const context.
|
||||||
match btc.checked_mul(100_000_000) {
|
match btc.checked_mul(100_000_000) {
|
||||||
Some(amount) => Amount::from_sat(amount),
|
Some(amount) => Amount::from_sat(amount),
|
||||||
|
@ -142,7 +163,7 @@ impl Amount {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If the amount is too big, too precise or negative.
|
/// If the amount is too precise, negative, or greater than 21,000,000.
|
||||||
pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
|
pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
|
||||||
let (negative, sats) =
|
let (negative, sats) =
|
||||||
parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(false))?;
|
parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(false))?;
|
||||||
|
@ -156,7 +177,7 @@ impl Amount {
|
||||||
OutOfRangeError::too_big(false),
|
OutOfRangeError::too_big(false),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Ok(Self::from_sat(sats))
|
Ok(Self::from_sat_unchecked(sats))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
|
/// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
|
||||||
|
@ -173,7 +194,7 @@ impl Amount {
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::{amount, Amount};
|
/// # use bitcoin_units::{amount, Amount};
|
||||||
/// let amount = Amount::from_str_with_denomination("0.1 BTC")?;
|
/// let amount = Amount::from_str_with_denomination("0.1 BTC")?;
|
||||||
/// assert_eq!(amount, Amount::from_sat(10_000_000));
|
/// assert_eq!(amount, Amount::from_sat(10_000_000)?);
|
||||||
/// # Ok::<_, amount::ParseError>(())
|
/// # Ok::<_, amount::ParseError>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseError> {
|
pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseError> {
|
||||||
|
@ -188,9 +209,10 @@ impl Amount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{Amount, Denomination};
|
/// # use bitcoin_units::amount::{self, Amount, Denomination};
|
||||||
/// let amount = Amount::from_sat(100_000);
|
/// let amount = Amount::from_sat(100_000)?;
|
||||||
/// assert_eq!(amount.to_float_in(Denomination::Bitcoin), 0.001)
|
/// assert_eq!(amount.to_float_in(Denomination::Bitcoin), 0.001);
|
||||||
|
/// # Ok::<_, amount::ParseError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
@ -205,9 +227,10 @@ impl Amount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{Amount, Denomination};
|
/// # use bitcoin_units::amount::{self, Amount, Denomination};
|
||||||
/// let amount = Amount::from_sat(100_000);
|
/// let amount = Amount::from_sat(100_000)?;
|
||||||
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin))
|
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin));
|
||||||
|
/// # Ok::<_, amount::ParseError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
|
pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
|
||||||
|
@ -236,13 +259,13 @@ impl Amount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{Amount, Denomination};
|
/// # use bitcoin_units::amount::{self, Amount, Denomination};
|
||||||
/// # use std::fmt::Write;
|
/// # use std::fmt::Write;
|
||||||
/// let amount = Amount::from_sat(10_000_000);
|
/// let amount = Amount::from_sat(10_000_000)?;
|
||||||
/// let mut output = String::new();
|
/// let mut output = String::new();
|
||||||
/// write!(&mut output, "{}", amount.display_in(Denomination::Bitcoin))?;
|
/// let _ = write!(&mut output, "{}", amount.display_in(Denomination::Bitcoin));
|
||||||
/// assert_eq!(output, "0.1");
|
/// assert_eq!(output, "0.1");
|
||||||
/// # Ok::<(), std::fmt::Error>(())
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn display_in(self, denomination: Denomination) -> Display {
|
pub fn display_in(self, denomination: Denomination) -> Display {
|
||||||
|
@ -274,9 +297,10 @@ impl Amount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{Amount, Denomination};
|
/// # use bitcoin_units::amount::{self, Amount, Denomination};
|
||||||
/// let amount = Amount::from_sat(10_000_000);
|
/// let amount = Amount::from_sat(10_000_000)?;
|
||||||
/// assert_eq!(amount.to_string_in(Denomination::Bitcoin), "0.1")
|
/// assert_eq!(amount.to_string_in(Denomination::Bitcoin), "0.1");
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
|
pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
|
||||||
|
@ -287,9 +311,10 @@ impl Amount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::amount::{Amount, Denomination};
|
/// # use bitcoin_units::amount::{self, Amount, Denomination};
|
||||||
/// let amount = Amount::from_sat(10_000_000);
|
/// let amount = Amount::from_sat(10_000_000)?;
|
||||||
/// assert_eq!(amount.to_string_with_denomination(Denomination::Bitcoin), "0.1 BTC")
|
/// assert_eq!(amount.to_string_with_denomination(Denomination::Bitcoin), "0.1 BTC");
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
|
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
|
||||||
|
@ -302,9 +327,10 @@ impl Amount {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_add(self, rhs: Amount) -> Option<Amount> {
|
pub const fn checked_add(self, rhs: Amount) -> Option<Amount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_add(rhs.to_sat()) {
|
// Unchecked add ok, adding two values less than `MAX_MONEY` cannot overflow an `i64`.
|
||||||
Some(res) => Amount::from_sat(res).check_max(),
|
match Self::from_sat(self.to_sat() + rhs.to_sat()) {
|
||||||
None => None,
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +341,10 @@ impl Amount {
|
||||||
pub const fn checked_sub(self, rhs: Amount) -> Option<Amount> {
|
pub const fn checked_sub(self, rhs: Amount) -> Option<Amount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_sub(rhs.to_sat()) {
|
match self.to_sat().checked_sub(rhs.to_sat()) {
|
||||||
Some(res) => Some(Amount::from_sat(res)),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None, // Unreachable because of checked_sub above.
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,7 +356,10 @@ impl Amount {
|
||||||
pub const fn checked_mul(self, rhs: u64) -> Option<Amount> {
|
pub const fn checked_mul(self, rhs: u64) -> Option<Amount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_mul(rhs) {
|
match self.to_sat().checked_mul(rhs) {
|
||||||
Some(res) => Amount::from_sat(res).check_max(),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,7 +373,10 @@ impl Amount {
|
||||||
pub const fn checked_div(self, rhs: u64) -> Option<Amount> {
|
pub const fn checked_div(self, rhs: u64) -> Option<Amount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_div(rhs) {
|
match self.to_sat().checked_div(rhs) {
|
||||||
Some(res) => Some(Amount::from_sat(res)),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None, // Unreachable because of checked_div above.
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,7 +388,10 @@ impl Amount {
|
||||||
pub const fn checked_rem(self, rhs: u64) -> Option<Amount> {
|
pub const fn checked_rem(self, rhs: u64) -> Option<Amount> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat().checked_rem(rhs) {
|
match self.to_sat().checked_rem(rhs) {
|
||||||
Some(res) => Some(Amount::from_sat(res)),
|
Some(res) => match Self::from_sat(res) {
|
||||||
|
Ok(amount) => Some(amount),
|
||||||
|
Err(_) => None, // Unreachable because of checked_div above.
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,15 +401,6 @@ impl Amount {
|
||||||
pub fn to_signed(self) -> SignedAmount {
|
pub fn to_signed(self) -> SignedAmount {
|
||||||
SignedAmount::from_sat_unchecked(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range.
|
SignedAmount::from_sat_unchecked(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the amount is below the maximum value. Returns `None` if it is above.
|
|
||||||
const fn check_max(self) -> Option<Amount> {
|
|
||||||
if self.to_sat() > Self::MAX.to_sat() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl default::Default for Amount {
|
impl default::Default for Amount {
|
||||||
|
@ -425,7 +454,7 @@ impl TryFrom<SignedAmount> for Amount {
|
||||||
#[cfg(feature = "arbitrary")]
|
#[cfg(feature = "arbitrary")]
|
||||||
impl<'a> Arbitrary<'a> for Amount {
|
impl<'a> Arbitrary<'a> for Amount {
|
||||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||||
let a = u64::arbitrary(u)?;
|
let sats = u.int_in_range(Self::MIN.to_sat()..=Self::MAX.to_sat())?;
|
||||||
Ok(Self::from_sat(a))
|
Ok(Self::from_sat(sats).expect("range is valid"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,17 +23,21 @@ use super::*;
|
||||||
fn u_amount_homomorphic() {
|
fn u_amount_homomorphic() {
|
||||||
let n1 = kani::any::<u64>();
|
let n1 = kani::any::<u64>();
|
||||||
let n2 = kani::any::<u64>();
|
let n2 = kani::any::<u64>();
|
||||||
// Assume we don't overflow in the actual tests.
|
|
||||||
kani::assume(n1.checked_add(n2).is_some()); // Adding u64s doesn't overflow.
|
|
||||||
let a1 = Amount::from_sat(n1); // TODO: If from_sat enforces invariant assume this `is_ok()`.
|
|
||||||
let a2 = Amount::from_sat(n2);
|
|
||||||
kani::assume(a1.checked_add(a2).is_some()); // Adding amounts doesn't overflow.
|
|
||||||
|
|
||||||
assert_eq!(Amount::from_sat(n1) + Amount::from_sat(n2), Amount::from_sat(n1 + n2).into());
|
// Assume the values are within range.
|
||||||
|
kani::assume(Amount::from_sat(n1).is_ok());
|
||||||
|
kani::assume(Amount::from_sat(n2).is_ok());
|
||||||
|
|
||||||
|
let sat = |sat| Amount::from_sat(sat).unwrap();
|
||||||
|
|
||||||
|
// Assume sum is within range.
|
||||||
|
kani::assume(sat(n1).checked_add(sat(n2)).is_some());
|
||||||
|
|
||||||
|
assert_eq!(sat(n1) + sat(n2), sat(n1 + n2).into());
|
||||||
|
|
||||||
let max = cmp::max(n1, n2);
|
let max = cmp::max(n1, n2);
|
||||||
let min = cmp::min(n1, n2);
|
let min = cmp::min(n1, n2);
|
||||||
assert_eq!(Amount::from_sat(max) - Amount::from_sat(min), Amount::from_sat(max - min).into());
|
assert_eq!(sat(max) - sat(min), sat(max - min).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kani::unwind(4)]
|
#[kani::unwind(4)]
|
||||||
|
@ -41,20 +45,16 @@ fn u_amount_homomorphic() {
|
||||||
fn s_amount_homomorphic() {
|
fn s_amount_homomorphic() {
|
||||||
let n1 = kani::any::<i64>();
|
let n1 = kani::any::<i64>();
|
||||||
let n2 = kani::any::<i64>();
|
let n2 = kani::any::<i64>();
|
||||||
// Assume we don't overflow in the actual tests.
|
|
||||||
kani::assume(n1.checked_add(n2).is_some()); // Adding i64s doesn't overflow.
|
|
||||||
kani::assume(n1.checked_sub(n2).is_some()); // Subbing i64s doesn't overflow.
|
|
||||||
let a1 = SignedAmount::from_sat(n1); // TODO: If from_sat enforces invariant assume this `is_ok()`.
|
|
||||||
let a2 = SignedAmount::from_sat(n2);
|
|
||||||
kani::assume(a1.checked_add(a2).is_some()); // Adding amounts doesn't overflow.
|
|
||||||
kani::assume(a1.checked_sub(a2).is_some()); // Subbing amounts doesn't overflow.
|
|
||||||
|
|
||||||
assert_eq!(
|
// Assume the values are within range.
|
||||||
SignedAmount::from_sat(n1) + SignedAmount::from_sat(n2),
|
kani::assume(SignedAmount::from_sat(n1).is_ok());
|
||||||
SignedAmount::from_sat(n1 + n2).into()
|
kani::assume(SignedAmount::from_sat(n2).is_ok());
|
||||||
);
|
|
||||||
assert_eq!(
|
let ssat = |ssat| SignedAmount::from_sat(ssat).unwrap();
|
||||||
SignedAmount::from_sat(n1) - SignedAmount::from_sat(n2),
|
|
||||||
SignedAmount::from_sat(n1 - n2).into()
|
kani::assume(ssat(n1).checked_add(ssat(n2)).is_some()); // Adding amounts doesn't overflow.
|
||||||
);
|
kani::assume(ssat(n1).checked_sub(ssat(n2)).is_some()); // Subbing amounts doesn't overflow.
|
||||||
|
|
||||||
|
assert_eq!(ssat(n1) + ssat(n2), ssat(n1 + n2).into());
|
||||||
|
assert_eq!(ssat(n1) - ssat(n2), ssat(n1 - n2).into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,12 @@ impl Amount {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitcoin_units::{Amount, FeeRate, Weight};
|
/// # use bitcoin_units::{amount, Amount, FeeRate, Weight};
|
||||||
/// let amount = Amount::from_sat(10);
|
/// let amount = Amount::from_sat(10)?;
|
||||||
/// let weight = Weight::from_wu(300);
|
/// let weight = Weight::from_wu(300);
|
||||||
/// let fee_rate = amount.checked_div_by_weight_ceil(weight).expect("Division by weight failed");
|
/// let fee_rate = amount.checked_div_by_weight_ceil(weight).expect("Division by weight failed");
|
||||||
/// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
|
/// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
|
pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
|
||||||
|
@ -151,7 +152,10 @@ impl FeeRate {
|
||||||
// No `?` operator in const context.
|
// No `?` operator in const context.
|
||||||
match self.to_sat_per_kwu().checked_mul(weight.to_wu()) {
|
match self.to_sat_per_kwu().checked_mul(weight.to_wu()) {
|
||||||
Some(mul_res) => match mul_res.checked_add(999) {
|
Some(mul_res) => match mul_res.checked_add(999) {
|
||||||
Some(add_res) => Some(Amount::from_sat(add_res / 1000)),
|
Some(add_res) => match Amount::from_sat(add_res / 1000) {
|
||||||
|
Ok(fee) => Some(fee),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
|
|
Loading…
Reference in New Issue