Merge rust-bitcoin/rust-bitcoin#4675: Units improvements
What changed, and why it matters
This is a routine code-quality and documentation update for the Rust Bitcoin 'units' crate. It adds a few helper methods, improves precision of fee-rate calculations, and updates documentation and warnings. There is no indication of a security vulnerability being fixed, and the changes are described by the author as 'just documentation improvements' plus minor API refinements.
No security action required. Treat as normal maintenance merge. Reviewers may want to verify the fee-rate precision changes behave as intended and that the infallible constructors are acceptable API changes.
Security signals we found
No security-relevant signals in commit message or diff
Changes are described by author as documentation improvements
No bounds-checking fixes, no panic fixes, no unsafe code changes
No incident or vulnerability disclosure references present
Evidence from the diff
The merge commit combines several small improvements to the units crate: documentation updates for amount types and the crate root, addition of const to_msat() conversion methods on Amount and SignedAmount, making FeeRate::from_per_kwu and FeeRate::from_per_kvb infallible by removing NumOpResult wrappers, improving precision of div_by_weight_* by computing in u128 and converting to sat_per_mvb, and updating tests accordingly. The changes are correctness/precision improvements and API ergonomics, not security patches.
Changed components
units/src/amount/mod.rsunits/src/amount/signed.rsunits/src/amount/tests.rsunits/src/amount/unsigned.rsunits/src/fee.rsunits/src/fee_rate/mod.rsunits/src/fee_rate/serde.rsunits/src/lib.rsInspect captured patch +206 / −96
### units/src/amount/mod.rs
@@ -2,8 +2,47 @@
//! Bitcoin amounts.
//!
-//! This module mainly introduces the [`Amount`] and [`SignedAmount`] types.
-//! We refer to the documentation on the types for more information.
+//! This module mainly introduces the [`Amount`] and [`SignedAmount`] types to express the bitcoin
+//! amounts supporting arithmetic, conversions between denomintaions and other important
+//! opertaions.
+//!
+//! # The 21M limit
+//!
+//! Since Bitcoin itself is limited to 2 100 000 000 000 000 satoshis (a bit less in practice)
+//! this type also implements the same restriction. While this may be surprising it actually
+//! provides many benefits:
+//!
+//! * Conversions from unsigned to signed are infallible.
+//! * Negation is infallible.
+//! * Absolute value is infallible (though `unsigned_abs` is usually better anyway).
+//! * Conversion to float is lossless.
+//! * Division cannot overflow, so a division error has to be div-by-zero; thus division by
+//! `NonZeroU64` is completely infallible.
+//! * Infallible conversion to `i64` allows directly storing in SQL databases.
+//! * It's possible to more efficiently sum amounts using SIMD (currently unimplemented in the
+//! library).
+//! * Subtraction of unsigned amounts producing a signed amount is infallible.
+//! * Conversion to msat is infallible.
+//!
+//! Note that the signed type also restricts the minimum to -21M BTC.
+//!
+//! While it might seem that this comes at a cost of littering the code with range checks it is not
+//! actually that bad because if the limit was not 21M btc it would've still been `u64::MAX` and
+//! require effectively the same kind of handling. This library exposes range checks as if they were
+//! overflow checks, so the calling code looks the same.
+//!
+//! Additionally, whenever an amount enters the program from outside, it already needs to be parsed
+//! or decoded, so the only thing this changes about it is the error type.
+//!
+//! # Numeric operations
+//!
+//! The types implement several arithmetic operations from [`core::ops`].
+//! To prevent errors due to an overflow or division by zero when using these operations, they
+//! return the [`NumOpResult`] type which enforces checked arithmetic. The resulting type itself
+//! implements the traits so you can write code like `a + b + c` and only check the result at the
+//! end.
+//!
+//! [`NumOpResult`]: super::NumOpResult
mod ops;
mod signed;
@@ -583,8 +622,7 @@ fn fmt_satoshi_in(
/// * Dynamically-selected denomination - show in sats if less than 1 BTC.
///
/// However, this can still be combined with [`fmt::Formatter`] options to precisely control zeros,
-/// padding, alignment... The formatting works like floats from `core` but note that precision will
-/// **never** be lossy - that means no rounding.
+/// padding, alignment... The formatting works like floats from `core`.
///
/// Note: This implementation is currently **unstable**. The only thing that we can promise is that
/// unless the precision is changed, this will display an accurate, human-readable number, and the
### units/src/amount/signed.rs
@@ -26,13 +26,9 @@ mod encapsulate {
/// 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.
+ /// The type is limited to 21 million bitcoins and provides a convenient way to handle
+ /// arithmetic errors. See the [module documentation](crate::amount) for rationale and further
+ /// guidance.
///
/// # Examples
///
@@ -49,6 +45,8 @@ mod encapsulate {
/// }
/// # }
/// ```
+ ///
+ /// [`NumOpResult`]: crate::result::NumOpResult
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SignedAmount(i64);
@@ -117,6 +115,22 @@ impl SignedAmount {
/// The maximum value allowed as an amount. Useful for sanity checking.
pub const MAX_MONEY: Self = Self::MAX;
+ /// Gets the number of millisatoshis in this [`SignedAmount`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use bitcoin_units::SignedAmount;
+ /// assert_eq!(SignedAmount::ONE_BTC.to_msat(), 100_000_000_000);
+ /// ```
+ #[inline]
+ pub const fn to_msat(self) -> i64 {
+ // Proof that overflow is impossible
+ const _: () = assert!(SignedAmount::MAX.to_sat().checked_mul(1000).is_some());
+ const _: () = assert!(SignedAmount::MIN.to_sat().checked_mul(1000).is_some());
+ self.to_sat() * 1000
+ }
+
/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
///
/// Accepts an `i32` which is guaranteed to be in range for the type, but which can only
@@ -153,7 +167,9 @@ impl SignedAmount {
///
/// If the amount is too big (positive or negative) or too precise.
///
- /// Please be aware of the risk of using floating-point numbers.
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It can be avoided by using an integer number of satoshis or string-encoded
+ /// btc in APIs that require it.
///
/// # Examples
///
@@ -230,7 +246,9 @@ impl SignedAmount {
/// Expresses this [`SignedAmount`] as a floating-point value in the given [`Denomination`].
///
- /// Please be aware of the risk of using floating-point numbers.
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It can be avoided by using an integer number of satoshis or string-encoded
+ /// btc in APIs that require it.
///
/// # Examples
///
@@ -281,7 +299,9 @@ impl SignedAmount {
/// Expresses this [`SignedAmount`] as a floating-point value in bitcoin.
///
- /// Please be aware of the risk of using floating-point numbers.
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It can be avoided by using an integer number of satoshis or string-encoded
+ /// btc in APIs that require it.
///
/// # Examples
///
@@ -297,11 +317,13 @@ impl SignedAmount {
/// Constructs a [`SignedAmount`] from floating-point notation in the given [`Denomination`].
///
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It can be avoided by using an integer number of satoshis or string-encoded btc
+ /// in APIs that require it.
+ ///
/// # Errors
///
/// If the amount is too big (positive or negative) or too precise.
- ///
- /// Please be aware of the risk of using floating-point numbers.
#[inline]
#[cfg(feature = "alloc")]
pub fn from_float_in(value: f64, denom: Denomination) -> Result<Self, ParseAmountError> {
@@ -467,7 +489,8 @@ impl SignedAmount {
///
/// Be aware that integer division loses the remainder if no exact division can be made.
///
- /// Returns [`None`] if overflow occurred.
+ /// Returns [`None`] if `rhs == 0`. Notably, overflow is impossible even when `rhs == -1`
+ /// because `self` is never `i64::MIN`.
#[inline]
#[must_use]
pub const fn checked_div(self, rhs: i64) -> Option<Self> {
@@ -478,7 +501,7 @@ impl SignedAmount {
/// Checked remainder.
///
- /// Returns [`None`] if overflow occurred.
+ /// Returns [`None`] if `rhs == 0`.
#[inline]
#[must_use]
pub const fn checked_rem(self, rhs: i64) -> Option<Self> {
### units/src/amount/tests.rs
@@ -296,9 +296,10 @@ fn amount_checked_div_by_weight_ceil() {
let weight = Weight::from_wu(381);
let fee_rate = sat(329).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));
+ // 329 sats / 381 wu = 863.517060367454 sats/kwu
+ // * 4M = 3454068.241469816
+ // round up to 3454069 because it's ceil
+ assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(3_454_069));
let fee_rate = Amount::ONE_SAT.div_by_weight_ceil(Weight::ZERO);
assert!(fee_rate.is_error());
@@ -314,9 +315,10 @@ fn amount_checked_div_by_weight_floor() {
let weight = Weight::from_wu(381);
let fee_rate = sat(329).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));
+ // 329 sats / 381 wu = 863.517060367454 sats/kwu
+ // * 4M = 3454068.241469816
+ // round down to 3454069 because it's floor
+ assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(3_454_068));
let fee_rate = Amount::ONE_SAT.div_by_weight_floor(Weight::ZERO);
assert!(fee_rate.is_error());
### units/src/amount/unsigned.rs
@@ -31,13 +31,9 @@ mod encapsulate {
/// 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.
+ /// The type is limited to 21 million bitcoins and provides a convenient way to handle
+ /// arithmetic errors. See the [module documentation](crate::amount) for rationale and further
+ /// guidance.
///
/// # Examples
///
@@ -122,6 +118,21 @@ impl Amount {
/// The number of bytes that an amount contributes to the size of a transaction.
pub const SIZE: usize = 8; // Serialized length of a u64.
+ /// Gets the number of millisatoshis in this [`Amount`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use bitcoin_units::Amount;
+ /// assert_eq!(Amount::ONE_BTC.to_msat(), 100_000_000_000);
+ /// ```
+ #[inline]
+ pub const fn to_msat(self) -> u64 {
+ // Proof that overflow is impossible
+ const _: () = assert!(Amount::MAX.to_sat().checked_mul(1000).is_some());
+ self.to_sat() * 1000
+ }
+
/// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
///
/// Accepts an `u32` which is guaranteed to be in range for the type, but which can only
@@ -139,12 +150,14 @@ impl Amount {
/// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
///
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It can be avoided by using an integer number of satoshis or string-encoded
+ /// btc in APIs that require it.
+ ///
/// # Errors
///
/// If the amount is too precise, negative, or greater than 21,000,000.
///
- /// Please be aware of the risk of using floating-point numbers.
- ///
/// # Examples
///
/// ```
@@ -225,7 +238,9 @@ impl Amount {
/// Expresses this [`Amount`] as a floating-point value in the given [`Denomination`].
///
- /// Please be aware of the risk of using floating-point numbers.
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It can be avoided by using an integer number of satoshis or string-encoded
+ /// btc in APIs that require it.
///
/// # Examples
///
@@ -244,7 +259,9 @@ impl Amount {
/// Expresses this [`Amount`] as a floating-point value in bitcoin.
///
- /// Please be aware of the risk of using floating-point numbers.
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It can be avoided by using an integer number of satoshis or string-encoded
+ /// btc in APIs that require it.
///
/// # Examples
///
@@ -260,11 +277,13 @@ impl Amount {
/// Constructs an [`Amount`] from floating-point notation in the given [`Denomination`].
///
+ /// **Warning:** due to precision loss, using floats for financial operations is generally not
+ /// recommended. It an be avoided by using an integer number of satoshis or string-encoded btc
+ /// in APIs that require it.
+ ///
/// # Errors
///
/// If the amount is too big, too precise or negative.
- ///
- /// Please be aware of the risk of using floating-point numbers.
#[inline]
#[cfg(feature = "alloc")]
pub fn from_float_in(value: f64, denom: Denomination) -> Result<Self, ParseAmountError> {
@@ -416,7 +435,7 @@ impl Amount {
///
/// Be aware that integer division loses the remainder if no exact division can be made.
///
- /// Returns [`None`] if overflow occurred.
+ /// Returns [`None`] if `rhs == 0`.
#[inline]
#[must_use]
pub const fn checked_div(self, rhs: u64) -> Option<Self> {
@@ -427,7 +446,7 @@ impl Amount {
/// Checked remainder.
///
- /// Returns [`None`] if overflow occurred.
+ /// Returns [`None`] if `rhs == 0`.
#[inline]
#[must_use]
pub const fn checked_rem(self, rhs: u64) -> Option<Self> {
@@ -462,19 +481,16 @@ impl Amount {
/// can be made. See also [`Self::div_by_weight_ceil`].
#[inline]
pub const fn div_by_weight_floor(self, weight: Weight) -> NumOpResult<FeeRate> {
- let wu = weight.to_wu();
-
- // Mul by 1,000 because we use per/kwu.
- if let Some(sats) = self.to_sat().checked_mul(1_000) {
- match sats.checked_div(wu) {
- Some(fee_rate) =>
- if let Ok(amount) = Self::from_sat(fee_rate) {
- return FeeRate::from_per_kwu(amount);
- },
- None => return R::Error(E::while_doing(MathErrorKind::DivByZero)),
- }
+ let wu = weight.to_wu() as u128;
+
+ let sats = self.to_sat() as u128;
+ match (sats * 4_000_000).checked_div(wu) {
+ Some(fee_rate) if fee_rate <= const_casts::u64_to_u128(u64::MAX) => {
+ R::Valid(FeeRate::from_sat_per_mvb(fee_rate as u64))
+ },
+ Some(_) => R::Error(E::while_doing(MathErrorKind::Overflow { op: MathOp::Div, is_negative: false })),
+ None => R::Error(E::while_doing(MathErrorKind::DivByZero)),
}
- R::Error(E::while_doing(MathErrorKind::Overflow { op: MathOp::Div, is_negative: false }))
}
/// Checked weight ceiling division.
@@ -488,38 +504,41 @@ impl Amount {
/// ```
/// # use bitcoin_units::{amount, Amount, FeeRate, Weight};
/// let amount = Amount::from_sat(10)?;
- /// let weight = Weight::from_wu(300);
+ /// let weight = Weight::from_wu(200);
/// let fee_rate = amount.div_by_weight_ceil(weight).expect("valid fee rate");
- /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
+ /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(50));
/// # Ok::<_, amount::OutOfRangeError>(())
/// ```
#[inline]
pub const fn div_by_weight_ceil(self, weight: Weight) -> NumOpResult<FeeRate> {
- let wu = weight.to_wu();
+ let wu = weight.to_wu() as u128;
if wu == 0 {
return R::Error(E::while_doing(MathErrorKind::DivByZero));
}
- // Mul by 1,000 because we use per/kwu.
- if let Some(sats) = self.to_sat().checked_mul(1_000) {
- // No need to use checked arithmetic because wu is non-zero.
- let fee_rate = sats.div_ceil(wu);
- if let Ok(amount) = Self::from_sat(fee_rate) {
- return FeeRate::from_per_kwu(amount);
- }
+ let sats = self.to_sat() as u128;
+ // No need to use checked arithmetic because wu is non-zero.
+ let fee_rate = (sats * 4_000_000).div_ceil(wu);
+ if fee_rate <= const_casts::u64_to_u128(u64::MAX) {
+ R::Valid(FeeRate::from_sat_per_mvb(fee_rate as u64))
+ } else {
+ R::Error(E::while_doing(MathErrorKind::Overflow { op: MathOp::Div, is_negative: false }))
}
- R::Error(E::while_doing(MathErrorKind::Overflow { op: MathOp::Div, is_negative: false }))
}
/// Checked fee rate floor division.
///
/// Computes the maximum weight that would result in a fee less than or equal to this amount
/// at the given `fee_rate`. Uses floor division to ensure the resulting weight doesn't cause
/// the fee to exceed the amount.
+ ///
+ /// # Errors
+ ///
+ /// This can fail only if `fee_rate` is zero, therefore an error returned from this method can
+ /// be treated as infinity.
#[inline]
pub const fn div_by_fee_rate_floor(self, fee_rate: FeeRate) -> NumOpResult<Weight> {
- debug_assert!(Self::MAX.to_sat().checked_mul(1_000).is_some());
- let msats = self.to_sat() * 1_000;
+ let msats = self.to_msat();
match msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) {
Some(wu) => R::Valid(Weight::from_wu(wu)),
None => R::Error(E::while_doing(MathErrorKind::DivByZero)),
@@ -530,6 +549,11 @@ impl Amount {
///
/// Computes the minimum weight that would result in a fee greater than or equal to this amount
/// at the given `fee_rate`. Uses ceiling division to ensure the resulting weight is sufficient.
+ ///
+ /// # Errors
+ ///
+ /// This can fail only if `fee_rate` is zero, therefore an error returned from this method can
+ /// be treated as infinity.
#[inline]
pub const fn div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> NumOpResult<Weight> {
// Use ceil because result is used as the divisor.
@@ -539,8 +563,7 @@ impl Amount {
return R::Error(E::while_doing(MathErrorKind::DivByZero));
}
- debug_assert!(Self::MAX.to_sat().checked_mul(1_000).is_some());
- let msats = self.to_sat() * 1_000;
+ let msats = self.to_msat();
NumOpResult::Valid(Weight::from_wu(msats.div_ceil(rate)))
}
}
### units/src/fee.rs
@@ -190,7 +190,7 @@ mod tests {
#[test]
fn fee_rate_div_by_weight() {
let fee_rate = (Amount::from_sat_u32(329) / Weight::from_wu(381)).unwrap();
- assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
+ assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(3_454_068));
}
#[test]
### units/src/fee_rate/mod.rs
@@ -76,15 +76,8 @@ impl FeeRate {
/// Constructs a new [`FeeRate`] from amount per 1,000 weight units.
#[inline]
- pub const fn from_per_kwu(rate: Amount) -> NumOpResult<Self> {
- // No `map()` in const context.
- match rate.checked_mul(4_000) {
- Some(per_mvb) => R::Valid(Self::from_sat_per_mvb(per_mvb.to_sat())),
- None => R::Error(E::while_doing(MathErrorKind::Overflow {
- op: MathOp::Mul,
- is_negative: false,
- })),
- }
+ pub const fn from_per_kwu(rate: Amount) -> Self {
+ Self::from_sat_per_mvb(rate.to_sat() * 4_000)
}
/// Constructs a new [`FeeRate`] from satoshis per virtual byte.
@@ -116,15 +109,8 @@ impl FeeRate {
/// Constructs a new [`FeeRate`] from amount per kilo virtual bytes (1,000 vbytes).
#[inline]
- pub const fn from_per_kvb(rate: Amount) -> NumOpResult<Self> {
- // No `map()` in const context.
- match rate.checked_mul(1_000) {
- Some(per_mvb) => R::Valid(Self::from_sat_per_mvb(per_mvb.to_sat())),
- None => R::Error(E::while_doing(MathErrorKind::Overflow {
- op: MathOp::Mul,
- is_negative: false,
- })),
- }
+ pub const fn from_per_kvb(rate: Amount) -> Self {
+ Self::from_sat_per_mvb(rate.to_sat() * 1_000)
}
/// Converts to sat/kwu rounding down.
@@ -421,6 +407,12 @@ mod tests {
assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(11_000));
}
+ #[test]
+ fn fee_rate_from_per_kvb() {
+ let fee_rate = FeeRate::from_per_kvb(Amount::from_sat_u32(11));
+ assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(11_000));
+ }
+
#[test]
fn fee_rate_to_sat_per_x() {
let fee_rate = FeeRate::from_sat_per_mvb(2_000_400);
### units/src/fee_rate/serde.rs
@@ -43,11 +43,8 @@ pub mod as_sat_per_kwu_floor {
#[inline]
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
let sat = u64::deserialize(d)?;
- FeeRate::from_per_kwu(
- Amount::from_sat(sat).map_err(|_| serde::de::Error::custom("amount out of range"))?,
- )
- .into_result()
- .map_err(|_| serde::de::Error::custom("fee rate too big for sats/kwu"))
+ let amt = Amount::from_sat(sat).map_err(|_| serde::de::Error::custom("amount out of range"))?;
+ Ok(FeeRate::from_per_kwu(amt))
}
pub mod opt {
### units/src/lib.rs
@@ -1,22 +1,57 @@
// SPDX-License-Identifier: CC0-1.0
-//! # Rust Bitcoin Unit Types
+//! Basic (numeric) types used by the Rust Bitcoin ecosystem.
//!
-//! This library provides basic types used by the Rust Bitcoin ecosystem.
+//! This crate contains basic types that have minimal requirements on the platform they run on.
+//! Specifically, they do not require an allocator and they do not require `usize` to be at least
+//! 32-bit. If you need more than this crate provides check the [`bitcoin`] crate or the
+//! [`bitcoin-primitives`] crate.
//!
-//! If you are using `rust-bitcoin` then you do not need to access this crate directly. Everything
-//! here is re-exported in `rust-bitcoin` at the same path. Also the same re-exports exist in
-//! `primitives` if you are using that crate instead of `bitcoin`.
+//! # Guidance on crate use
+//!
+//! *If you are using the `bitcoin` crate then you do not need to access this crate directly.*
+//!
+//! Everything here is re-exported in `bitcoin` at the same path. Also the same re-exports exist in
+//! `bitcoin-primitives` if you are using that crate instead of `bitcoin`.
+//!
+//! Libraries that only need the types present in this crate should depend only on this crate, not
+//! `bitcoin` or `bitcoin-primitives` so that they don't add bloat to compilation and review. It is
+//! recommended that binaries or other root crates depend on `bitcoin` during the prototyping stage
+//! and then optionally try to trim down the dependencies by using the leaf crates. However this is
+//! unlikely to be feasible for non-trivial applications.
+//!
+//! ## Features
+//!
+//! * `std` - turns on `std` integration, mainly the `std::error::Error` trait in old Rust versions.
+//! * `alloc` - turns on features that require the `alloc` crate, such as `String` interop.
+//! * `serde` - causes the crate to depend on `serde` and provide support for serializing and
+//! deserializing its types.
+//! * `arbitrary` - causes the crate to depend on `arbitrary` and implement the `Arbitrary` trait.
+//!
+//! # MSRV
+//!
+//! This crate supports Rust 1.74, however some of its dependencies may not do so or may require
+//! pinning. Similarly, some features may require newer Rust version (implicitly or explicitly).
+//!
+//! ## Policy
+//!
+//! Our MSRV policy it to only bump MSRV to the one that is available on the latest Debian stable
+//! and is at least two years old. However, we will try to be even more conservative when practical
+//! given this crate is very widely used.
//!
//! # Examples
//!
//! ```
//! // Exactly the same as `use bitcoin::{amount, Amount}`.
//! use bitcoin_units::{amount, Amount};
//!
-//! let _amount = Amount::from_sat(1_000)?;
+//! let amount = Amount::from_sat(1_000)?;
+//! # let _ = amount;
//! # Ok::<_, amount::OutOfRangeError>(())
//! ```
+//!
+//! [`bitcoin`]: https://docs.rs/bitcoin
+//! [`bitcoin-primitives`]: https://docs.rs/bitcoin-primitives
#![no_std]
// Coding conventions.Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.