diff --git a/Cargo.toml b/Cargo.toml
index c3e830c9..ea6694d1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ path = "src/lib.rs"
fuzztarget = ["secp256k1/fuzztarget", "bitcoin_hashes/fuzztarget"]
serde-decimal = ["use-serde", "strason"]
unstable = []
-use-serde = ["serde", "serde_test", "bitcoin_hashes/serde"]
+use-serde = ["serde", "bitcoin_hashes/serde"]
[dependencies]
bitcoin-bech32 = "0.9.0"
@@ -27,11 +27,9 @@ rand = "0.3"
bitcoin_hashes = "0.3"
bitcoinconsensus = { version = "0.16", optional = true }
-[dev-dependencies]
-tempfile = "3"
-
[dependencies.serde]
version = "1"
+features = ["derive"]
optional = true
[dependencies.serde_test]
@@ -49,3 +47,9 @@ version = "=0.3.2"
[dependencies.secp256k1]
version = "0.12"
features = [ "rand" ]
+
+[dev-dependencies]
+serde_derive = "1"
+serde_json = "1"
+serde_test = "1"
+tempfile = "3"
diff --git a/src/lib.rs b/src/lib.rs
index f6487395..590c4c91 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -49,8 +49,10 @@ extern crate hex;
extern crate rand;
extern crate secp256k1;
#[cfg(feature = "serde")] extern crate serde;
-#[cfg(feature = "serde_test")] extern crate serde_test;
#[cfg(feature = "strason")] extern crate strason;
+#[cfg(all(test, feature = "serde"))] #[macro_use] extern crate serde_derive; // for 1.22.0 compat
+#[cfg(all(test, feature = "serde"))] extern crate serde_json;
+#[cfg(all(test, feature = "serde"))] extern crate serde_test;
#[cfg(all(test, feature = "unstable"))] extern crate test;
#[cfg(feature="bitcoinconsensus")] extern crate bitcoinconsensus;
@@ -77,6 +79,8 @@ pub use consensus::encode::VarInt;
pub use network::constants::Network;
pub use util::Error;
pub use util::address::Address;
+pub use util::amount::Amount;
+pub use util::amount::SignedAmount;
pub use util::hash::BitcoinHash;
pub use util::key::PrivateKey;
pub use util::key::PublicKey;
diff --git a/src/util/amount.rs b/src/util/amount.rs
new file mode 100644
index 00000000..866b3d40
--- /dev/null
+++ b/src/util/amount.rs
@@ -0,0 +1,1336 @@
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this software to
+// the public domain worldwide. This software is distributed without
+// any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication
+// along with this software.
+// If not, see .
+//
+
+//! Amounts
+//!
+//! This module mainly introduces the [Amount] and [SignedAmount] types.
+//! We refer to the documentation on the types for more information.
+//!
+
+use std::default;
+use std::error;
+use std::fmt::{self, Write};
+use std::ops;
+use std::str::FromStr;
+
+/// A set of denominations in which amounts can be expressed.
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+pub enum Denomination {
+ /// BTC
+ Bitcoin,
+ /// mBTC
+ MilliBitcoin,
+ /// uBTC
+ MicroBitcoin,
+ /// bits
+ Bit,
+ /// satoshi
+ Satoshi,
+ /// msat
+ MilliSatoshi,
+}
+
+impl Denomination {
+ /// The number of decimal places more than a satoshi.
+ fn precision(self) -> i32 {
+ match self {
+ Denomination::Bitcoin => -8,
+ Denomination::MilliBitcoin => -5,
+ Denomination::MicroBitcoin => -2,
+ Denomination::Bit => -2,
+ Denomination::Satoshi => 0,
+ Denomination::MilliSatoshi => 3,
+ }
+ }
+}
+
+impl fmt::Display for Denomination {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(match *self {
+ Denomination::Bitcoin => "BTC",
+ Denomination::MilliBitcoin => "mBTC",
+ Denomination::MicroBitcoin => "uBTC",
+ Denomination::Bit => "bits",
+ Denomination::Satoshi => "satoshi",
+ Denomination::MilliSatoshi => "msat",
+ })
+ }
+}
+
+impl FromStr for Denomination {
+ type Err = ParseAmountError;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "BTC" => Ok(Denomination::Bitcoin),
+ "mBTC" => Ok(Denomination::MilliBitcoin),
+ "uBTC" => Ok(Denomination::MicroBitcoin),
+ "bits" => Ok(Denomination::Bit),
+ "satoshi" => Ok(Denomination::Satoshi),
+ "sat" => Ok(Denomination::Satoshi),
+ "msat" => Ok(Denomination::MilliSatoshi),
+ d => Err(ParseAmountError::UnknownDenomination(d.to_owned())),
+ }
+ }
+}
+
+/// An error during amount parsing.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ParseAmountError {
+ /// Amount is negative.
+ Negative,
+ /// Amount is too big to fit inside the type.
+ TooBig,
+ /// Amount has higher precision than supported by the type.
+ TooPrecise,
+ /// Invalid number format.
+ InvalidFormat,
+ /// Input string was too large.
+ InputTooLarge,
+ /// Invalid character in input.
+ InvalidCharacter(char),
+ /// The denomination was unknown.
+ UnknownDenomination(String),
+}
+
+impl fmt::Display for ParseAmountError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let desc = ::std::error::Error::description(self);
+ match *self {
+ ParseAmountError::InvalidCharacter(c) => write!(f, "{}: {}", desc, c),
+ ParseAmountError::UnknownDenomination(ref d) => write!(f, "{}: {}", desc, d),
+ _ => f.write_str(desc),
+ }
+ }
+}
+
+impl error::Error for ParseAmountError {
+ fn cause(&self) -> Option<&error::Error> {
+ None
+ }
+
+ fn description(&self) -> &'static str {
+ match *self {
+ ParseAmountError::Negative => "amount is negative",
+ ParseAmountError::TooBig => "amount is too big",
+ ParseAmountError::TooPrecise => "amount has a too high precision",
+ ParseAmountError::InvalidFormat => "invalid number format",
+ ParseAmountError::InputTooLarge => "input string was too large",
+ ParseAmountError::InvalidCharacter(_) => "invalid character in input",
+ ParseAmountError::UnknownDenomination(_) => "unknown denomination",
+ }
+ }
+}
+
+/// Parse decimal string in the given denomination into a satoshi value and a
+/// bool indicator for a negative amount.
+fn parse_signed_to_satoshi(
+ mut s: &str,
+ denom: Denomination,
+) -> Result<(bool, u64), ParseAmountError> {
+ if s.len() == 0 {
+ return Err(ParseAmountError::InvalidFormat);
+ }
+ if s.len() > 50 {
+ return Err(ParseAmountError::InputTooLarge);
+ }
+
+ let negative = s.chars().next().unwrap() == '-';
+ if negative {
+ if s.len() == 1 {
+ return Err(ParseAmountError::InvalidFormat);
+ }
+ s = &s[1..];
+ }
+
+ let max_decimals = {
+ // The difference in precision between native (satoshi)
+ // and desired denomination.
+ let precision_diff = -denom.precision();
+ if precision_diff < 0 {
+ // If precision diff is negative, this means we are parsing
+ // into a less precise amount. That is not allowed unless
+ // there are no decimals and the last digits are zeroes as
+ // many as the diffence in precision.
+ let last_n = precision_diff.abs() as usize;
+ if s.contains(".") || s.chars().rev().take(last_n).any(|d| d != '0') {
+ return Err(ParseAmountError::TooPrecise);
+ }
+ s = &s[0..s.len() - last_n];
+ 0
+ } else {
+ precision_diff
+ }
+ };
+
+ let mut decimals = None;
+ let mut value: u64 = 0; // as satoshis
+ for c in s.chars() {
+ match c {
+ '0'...'9' => {
+ // Do `value = 10 * value + digit`, catching overflows.
+ match 10_u64.checked_mul(value) {
+ None => return Err(ParseAmountError::TooBig),
+ Some(val) => match val.checked_add((c as u8 - b'0') as u64) {
+ None => return Err(ParseAmountError::TooBig),
+ Some(val) => value = val,
+ },
+ }
+ // Increment the decimal digit counter if past decimal.
+ decimals = match decimals {
+ None => None,
+ Some(d) if d < max_decimals => Some(d + 1),
+ _ => return Err(ParseAmountError::TooPrecise),
+ };
+ }
+ '.' => match decimals {
+ None => decimals = Some(0),
+ // Double decimal dot.
+ _ => return Err(ParseAmountError::InvalidFormat),
+ },
+ c => return Err(ParseAmountError::InvalidCharacter(c)),
+ }
+ }
+
+ // Decimally shift left by `max_decimals - decimals`.
+ let scale_factor = max_decimals - decimals.unwrap_or(0);
+ for _ in 0..scale_factor {
+ value = match 10_u64.checked_mul(value) {
+ Some(v) => v,
+ None => return Err(ParseAmountError::TooBig),
+ };
+ }
+
+ Ok((negative, value))
+}
+
+/// Format the given satoshi amount in the given denomination.
+///
+/// Does not include the denomination.
+fn fmt_satoshi_in(
+ satoshi: u64,
+ negative: bool,
+ f: &mut fmt::Write,
+ denom: Denomination,
+) -> fmt::Result {
+ if negative {
+ f.write_str("-")?;
+ }
+
+ if denom.precision() > 0 {
+ // add zeroes in the end
+ let width = denom.precision() as usize;
+ write!(f, "{}{:0width$}", satoshi, 0, width = width)?;
+ } else if denom.precision() < 0 {
+ // need to inject a comma in the number
+ let nb_decimals = denom.precision().abs() as usize;
+ let real = format!("{:0width$}", satoshi, width = nb_decimals);
+ if real.len() == nb_decimals {
+ write!(f, "0.{}", &real[real.len() - nb_decimals..])?;
+ } else {
+ write!(
+ f,
+ "{}.{}",
+ &real[0..(real.len() - nb_decimals)],
+ &real[real.len() - nb_decimals..]
+ )?;
+ }
+ } else {
+ // denom.precision() == 0
+ write!(f, "{}", satoshi)?;
+ }
+ Ok(())
+}
+
+/// Amount
+///
+/// The [Amount] type can be used to express Bitcoin amounts that supports
+/// arithmetic and convertion to various denominations.
+///
+///
+/// Warning!
+///
+/// This type implements several arithmetic operations from [std::ops].
+/// To prevent errors due to overflow or underflow when using these operations,
+/// it is advised to instead use the checked arithmetic methods whose names
+/// start with `checked_`. The operations from [std::ops] that [Amount]
+/// implements will panic when overflow or underflow occurs. Also note that
+/// since the internal representation of amounts is unsigned, subtracting below
+/// zero is considered an underflow and will cause a panic if you're not using
+/// the checked arithmetic methods.
+///
+#[derive(Copy, Clone, Hash)]
+pub struct Amount(u64);
+
+impl Amount {
+ /// The zero amount.
+ pub const ZERO: Amount = Amount(0);
+ /// Exactly one satoshi.
+ pub const ONE_SAT: Amount = Amount(1);
+ /// Exactly one bitcoin.
+ pub const ONE_BTC: Amount = Amount(100_000_000);
+
+ /// Create an [Amount] with satoshi precision and the given number of satoshis.
+ pub fn from_sat(satoshi: u64) -> Amount {
+ Amount(satoshi)
+ }
+
+ /// Get the number of satoshis in this [Amount].
+ pub fn as_sat(self) -> u64 {
+ self.0
+ }
+
+ /// The maximum value of an [Amount].
+ pub fn max_value() -> Amount {
+ Amount(u64::max_value())
+ }
+
+ /// The minimum value of an [Amount].
+ pub fn min_value() -> Amount {
+ Amount(u64::min_value())
+ }
+
+ /// Convert from a value expressing bitcoins to an [Amount].
+ pub fn from_btc(btc: f64) -> Result {
+ Amount::from_float_in(btc, Denomination::Bitcoin)
+ }
+
+ /// Parse a decimal string as a value in the given denomination.
+ ///
+ /// Note: This only parses the value string. If you want to parse a value
+ /// with denomination, use [FromStr].
+ pub fn from_str_in(s: &str, denom: Denomination) -> Result {
+ let (negative, satoshi) = parse_signed_to_satoshi(s, denom)?;
+ if negative {
+ return Err(ParseAmountError::Negative);
+ }
+ if satoshi > i64::max_value() as u64 {
+ return Err(ParseAmountError::TooBig);
+ }
+ Ok(Amount::from_sat(satoshi))
+ }
+
+ /// Parses amounts with denomination suffix like they are produced with
+ /// [to_string_with_denomination] or with [fmt::Display].
+ /// If you want to parse only the amount without the denomination,
+ /// use [from_str_in].
+ pub fn from_str_with_denomination(s: &str) -> Result {
+ let mut split = s.splitn(3, " ");
+ let amt_str = split.next().unwrap();
+ let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
+ if split.next().is_some() {
+ return Err(ParseAmountError::InvalidFormat);
+ }
+
+ Ok(Amount::from_str_in(amt_str, denom_str.parse()?)?)
+ }
+
+ /// Express this [Amount] as a floating-point value in the given denomination.
+ ///
+ /// Please be aware of the risk of using floating-point numbers.
+ pub fn to_float_in(&self, denom: Denomination) -> f64 {
+ f64::from_str(&self.to_string_in(denom)).unwrap()
+ }
+
+ /// Express this [Amount] as a floating-point value in Bitcoin.
+ ///
+ /// Equivalent to `to_float_in(Denomination::Bitcoin)`.
+ ///
+ /// Please be aware of the risk of using floating-point numbers.
+ pub fn as_btc(&self) -> f64 {
+ self.to_float_in(Denomination::Bitcoin)
+ }
+
+ /// Convert this [Amount] in floating-point notation with a given
+ /// denomination.
+ /// Can return error if the amount is too big, too precise or negative.
+ ///
+ /// Please be aware of the risk of using floating-point numbers.
+ pub fn from_float_in(value: f64, denom: Denomination) -> Result {
+ if value < 0.0 {
+ return Err(ParseAmountError::Negative);
+ }
+ // This is inefficient, but the safest way to deal with this. The parsing logic is safe.
+ // Any performance-critical application should not be dealing with floats.
+ Amount::from_str_in(&value.to_string(), denom)
+ }
+
+ /// Format the value of this [Amount] in the given denomination.
+ ///
+ /// Does not include the denomination.
+ pub fn fmt_value_in(&self, f: &mut fmt::Write, denom: Denomination) -> fmt::Result {
+ fmt_satoshi_in(self.as_sat(), false, f, denom)
+ }
+
+ /// Get a string number of this [Amount] in the given denomination.
+ ///
+ /// Does not include the denomination.
+ pub fn to_string_in(&self, denom: Denomination) -> String {
+ let mut buf = String::new();
+ self.fmt_value_in(&mut buf, denom).unwrap();
+ buf
+ }
+
+ /// Get a formatted string of this [Amount] in the given denomination,
+ /// suffixed with the abbreviation for the denomination.
+ pub fn to_string_with_denomination(&self, denom: Denomination) -> String {
+ let mut buf = String::new();
+ self.fmt_value_in(&mut buf, denom).unwrap();
+ write!(buf, " {}", denom).unwrap();
+ buf
+ }
+
+ // Some arithmethic that doesn't fit in `std::ops` traits.
+
+ /// Checked addition.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_add(self, rhs: Amount) -> Option {
+ self.0.checked_add(rhs.0).map(Amount)
+ }
+
+ /// Checked subtraction.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_sub(self, rhs: Amount) -> Option {
+ self.0.checked_sub(rhs.0).map(Amount)
+ }
+
+ /// Checked multiplication.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_mul(self, rhs: u64) -> Option {
+ self.0.checked_mul(rhs).map(Amount)
+ }
+
+ /// Checked integer division.
+ /// Be aware that integer division loses the remainder if no exact division
+ /// can be made.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_div(self, rhs: u64) -> Option {
+ self.0.checked_div(rhs).map(Amount)
+ }
+
+ /// Checked remainder.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_rem(self, rhs: u64) -> Option {
+ self.0.checked_rem(rhs).map(Amount)
+ }
+
+ /// Convert to a signed amount.
+ pub fn to_signed(self) -> Result {
+ if self.as_sat() > SignedAmount::max_value().as_sat() as u64 {
+ Err(ParseAmountError::TooBig)
+ } else {
+ Ok(SignedAmount::from_sat(self.as_sat() as i64))
+ }
+ }
+}
+
+impl default::Default for Amount {
+ fn default() -> Self {
+ Amount::ZERO
+ }
+}
+
+impl PartialEq for Amount {
+ fn eq(&self, other: &Amount) -> bool {
+ PartialEq::eq(&self.0, &other.0)
+ }
+}
+impl Eq for Amount {}
+
+impl PartialOrd for Amount {
+ fn partial_cmp(&self, other: &Amount) -> Option<::std::cmp::Ordering> {
+ PartialOrd::partial_cmp(&self.0, &other.0)
+ }
+}
+
+impl Ord for Amount {
+ fn cmp(&self, other: &Amount) -> ::std::cmp::Ordering {
+ Ord::cmp(&self.0, &other.0)
+ }
+}
+
+impl fmt::Debug for Amount {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Amount({} satoshi)", self.as_sat())
+ }
+}
+
+// No one should depend on a binding contract for Display for this type.
+// Just using Bitcoin denominated string.
+impl fmt::Display for Amount {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.fmt_value_in(f, Denomination::Bitcoin)?;
+ write!(f, " {}", Denomination::Bitcoin)
+ }
+}
+
+impl ops::Add for Amount {
+ type Output = Amount;
+
+ fn add(self, rhs: Amount) -> Self::Output {
+ self.checked_add(rhs).expect("Amount addition error")
+ }
+}
+
+impl ops::AddAssign for Amount {
+ fn add_assign(&mut self, other: Amount) {
+ *self = *self + other
+ }
+}
+
+impl ops::Sub for Amount {
+ type Output = Amount;
+
+ fn sub(self, rhs: Amount) -> Self::Output {
+ self.checked_sub(rhs).expect("Amount subtraction error")
+ }
+}
+
+impl ops::SubAssign for Amount {
+ fn sub_assign(&mut self, other: Amount) {
+ *self = *self - other
+ }
+}
+
+impl ops::Rem for Amount {
+ type Output = Amount;
+
+ fn rem(self, modulus: u64) -> Self {
+ self.checked_rem(modulus).expect("Amount remainder error")
+ }
+}
+
+impl ops::RemAssign for Amount {
+ fn rem_assign(&mut self, modulus: u64) {
+ *self = *self % modulus
+ }
+}
+
+impl ops::Mul for Amount {
+ type Output = Amount;
+
+ fn mul(self, rhs: u64) -> Self::Output {
+ self.checked_mul(rhs).expect("Amount multiplication error")
+ }
+}
+
+impl ops::MulAssign for Amount {
+ fn mul_assign(&mut self, rhs: u64) {
+ *self = *self * rhs
+ }
+}
+
+impl ops::Div for Amount {
+ type Output = Amount;
+
+ fn div(self, rhs: u64) -> Self::Output {
+ self.checked_div(rhs).expect("Amount division error")
+ }
+}
+
+impl ops::DivAssign for Amount {
+ fn div_assign(&mut self, rhs: u64) {
+ *self = *self / rhs
+ }
+}
+
+impl FromStr for Amount {
+ type Err = ParseAmountError;
+
+ fn from_str(s: &str) -> Result {
+ Amount::from_str_with_denomination(s)
+ }
+}
+
+/// SignedAmount
+///
+/// The [SignedAmount] type can be used to express Bitcoin amounts that supports
+/// arithmetic and convertion to various denominations.
+///
+///
+/// Warning!
+///
+/// This type implements several arithmetic operations from [std::ops].
+/// To prevent errors due to overflow or underflow when using these operations,
+/// it is advised to instead use the checked arithmetic methods whose names
+/// start with `checked_`. The operations from [std::ops] that [Amount]
+/// implements will panic when overflow or underflow occurs.
+///
+#[derive(Copy, Clone, Hash)]
+pub struct SignedAmount(i64);
+
+impl SignedAmount {
+ /// The zero amount.
+ pub const ZERO: SignedAmount = SignedAmount(0);
+ /// Exactly one satoshi.
+ pub const ONE_SAT: SignedAmount = SignedAmount(1);
+ /// Exactly one bitcoin.
+ pub const ONE_BTC: SignedAmount = SignedAmount(100_000_000);
+
+ /// Create an [SignedAmount] with satoshi precision and the given number of satoshis.
+ pub fn from_sat(satoshi: i64) -> SignedAmount {
+ SignedAmount(satoshi)
+ }
+
+ /// Get the number of satoshis in this [SignedAmount].
+ pub fn as_sat(self) -> i64 {
+ self.0
+ }
+
+ /// The maximum value of an [SignedAmount].
+ pub fn max_value() -> SignedAmount {
+ SignedAmount(i64::max_value())
+ }
+
+ /// The minimum value of an [SignedAmount].
+ pub fn min_value() -> SignedAmount {
+ SignedAmount(i64::min_value())
+ }
+
+ /// Convert from a value expressing bitcoins to an [SignedAmount].
+ pub fn from_btc(btc: f64) -> Result {
+ SignedAmount::from_float_in(btc, Denomination::Bitcoin)
+ }
+
+ /// Parse a decimal string as a value in the given denomination.
+ ///
+ /// Note: This only parses the value string. If you want to parse a value
+ /// with denomination, use [FromStr].
+ pub fn from_str_in(s: &str, denom: Denomination) -> Result {
+ let (negative, satoshi) = parse_signed_to_satoshi(s, denom)?;
+ if satoshi > i64::max_value() as u64 {
+ return Err(ParseAmountError::TooBig);
+ }
+ Ok(match negative {
+ true => SignedAmount(-1 * satoshi as i64),
+ false => SignedAmount(satoshi as i64),
+ })
+ }
+
+ /// Parses amounts with denomination suffix like they are produced with
+ /// [to_string_with_denomination] or with [fmt::Display].
+ /// If you want to parse only the amount without the denomination,
+ /// use [from_str_in].
+ pub fn from_str_with_denomination(s: &str) -> Result {
+ let mut split = s.splitn(3, " ");
+ let amt_str = split.next().unwrap();
+ let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
+ if split.next().is_some() {
+ return Err(ParseAmountError::InvalidFormat);
+ }
+
+ Ok(SignedAmount::from_str_in(amt_str, denom_str.parse()?)?)
+ }
+
+ /// Express this [SignedAmount] as a floating-point value in the given denomination.
+ ///
+ /// Please be aware of the risk of using floating-point numbers.
+ pub fn to_float_in(&self, denom: Denomination) -> f64 {
+ f64::from_str(&self.to_string_in(denom)).unwrap()
+ }
+
+ /// Express this [SignedAmount] as a floating-point value in Bitcoin.
+ ///
+ /// Equivalent to `to_float_in(Denomination::Bitcoin)`.
+ ///
+ /// Please be aware of the risk of using floating-point numbers.
+ pub fn as_btc(&self) -> f64 {
+ self.to_float_in(Denomination::Bitcoin)
+ }
+
+ /// Convert this [SignedAmount] in floating-point notation with a given
+ /// denomination.
+ /// Can return error if the amount is too big, too precise or negative.
+ ///
+ /// Please be aware of the risk of using floating-point numbers.
+ pub fn from_float_in(
+ value: f64,
+ denom: Denomination,
+ ) -> Result {
+ // This is inefficient, but the safest way to deal with this. The parsing logic is safe.
+ // Any performance-critical application should not be dealing with floats.
+ SignedAmount::from_str_in(&value.to_string(), denom)
+ }
+
+ /// Format the value of this [SignedAmount] in the given denomination.
+ ///
+ /// Does not include the denomination.
+ pub fn fmt_value_in(&self, f: &mut fmt::Write, denom: Denomination) -> fmt::Result {
+ fmt_satoshi_in(self.as_sat().abs() as u64, self.is_negative(), f, denom)
+ }
+
+ /// Get a string number of this [SignedAmount] in the given denomination.
+ ///
+ /// Does not include the denomination.
+ pub fn to_string_in(&self, denom: Denomination) -> String {
+ let mut buf = String::new();
+ self.fmt_value_in(&mut buf, denom).unwrap();
+ buf
+ }
+
+ /// Get a formatted string of this [SignedAmount] in the given denomination,
+ /// suffixed with the abbreviation for the denomination.
+ pub fn to_string_with_denomination(&self, denom: Denomination) -> String {
+ let mut buf = String::new();
+ self.fmt_value_in(&mut buf, denom).unwrap();
+ write!(buf, " {}", denom).unwrap();
+ buf
+ }
+
+ // Some arithmethic that doesn't fit in `std::ops` traits.
+
+ /// Get the absolute value of this [SignedAmount].
+ pub fn abs(self) -> SignedAmount {
+ SignedAmount(self.0.abs())
+ }
+
+ /// Returns a number representing sign of this [SignedAmount].
+ ///
+ /// - `0` if the amount is zero
+ /// - `1` if the amount is positive
+ /// - `-1` if the amount is negative
+ pub fn signum(self) -> i64 {
+ self.0.signum()
+ }
+
+ /// 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()
+ }
+
+ /// 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()
+ }
+
+ /// Checked addition.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_add(self, rhs: SignedAmount) -> Option {
+ self.0.checked_add(rhs.0).map(SignedAmount)
+ }
+
+ /// Checked subtraction.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_sub(self, rhs: SignedAmount) -> Option {
+ self.0.checked_sub(rhs.0).map(SignedAmount)
+ }
+
+ /// Checked multiplication.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_mul(self, rhs: i64) -> Option {
+ self.0.checked_mul(rhs).map(SignedAmount)
+ }
+
+ /// Checked integer division.
+ /// Be aware that integer division loses the remainder if no exact division
+ /// can be made.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_div(self, rhs: i64) -> Option {
+ self.0.checked_div(rhs).map(SignedAmount)
+ }
+
+ /// Checked remainder.
+ /// Returns [None] if overflow occurred.
+ pub fn checked_rem(self, rhs: i64) -> Option {
+ self.0.checked_rem(rhs).map(SignedAmount)
+ }
+
+ /// Subtraction that doesn't allow negative [SignedAmount]s.
+ /// Returns [None] if either [self], [rhs] or the result is strictly negative.
+ pub fn positive_sub(self, rhs: SignedAmount) -> Option {
+ if self.is_negative() || rhs.is_negative() || rhs > self {
+ None
+ } else {
+ self.checked_sub(rhs)
+ }
+ }
+
+ /// Convert to an unsigned amount.
+ pub fn to_unsigned(self) -> Result {
+ if self.is_negative() {
+ Err(ParseAmountError::Negative)
+ } else {
+ Ok(Amount::from_sat(self.as_sat() as u64))
+ }
+ }
+}
+
+impl default::Default for SignedAmount {
+ fn default() -> Self {
+ SignedAmount::ZERO
+ }
+}
+
+impl PartialEq for SignedAmount {
+ fn eq(&self, other: &SignedAmount) -> bool {
+ PartialEq::eq(&self.0, &other.0)
+ }
+}
+impl Eq for SignedAmount {}
+
+impl PartialOrd for SignedAmount {
+ fn partial_cmp(&self, other: &SignedAmount) -> Option<::std::cmp::Ordering> {
+ PartialOrd::partial_cmp(&self.0, &other.0)
+ }
+}
+
+impl Ord for SignedAmount {
+ fn cmp(&self, other: &SignedAmount) -> ::std::cmp::Ordering {
+ Ord::cmp(&self.0, &other.0)
+ }
+}
+
+impl fmt::Debug for SignedAmount {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "SignedAmount({} satoshi)", self.as_sat())
+ }
+}
+
+// No one should depend on a binding contract for Display for this type.
+// Just using Bitcoin denominated string.
+impl fmt::Display for SignedAmount {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.fmt_value_in(f, Denomination::Bitcoin)?;
+ write!(f, " {}", Denomination::Bitcoin)
+ }
+}
+
+impl ops::Add for SignedAmount {
+ type Output = SignedAmount;
+
+ fn add(self, rhs: SignedAmount) -> Self::Output {
+ self.checked_add(rhs).expect("SignedAmount addition error")
+ }
+}
+
+impl ops::AddAssign for SignedAmount {
+ fn add_assign(&mut self, other: SignedAmount) {
+ *self = *self + other
+ }
+}
+
+impl ops::Sub for SignedAmount {
+ type Output = SignedAmount;
+
+ fn sub(self, rhs: SignedAmount) -> Self::Output {
+ self.checked_sub(rhs).expect("SignedAmount subtraction error")
+ }
+}
+
+impl ops::SubAssign for SignedAmount {
+ fn sub_assign(&mut self, other: SignedAmount) {
+ *self = *self - other
+ }
+}
+
+impl ops::Rem for SignedAmount {
+ type Output = SignedAmount;
+
+ fn rem(self, modulus: i64) -> Self {
+ self.checked_rem(modulus).expect("SignedAmount remainder error")
+ }
+}
+
+impl ops::RemAssign for SignedAmount {
+ fn rem_assign(&mut self, modulus: i64) {
+ *self = *self % modulus
+ }
+}
+
+impl ops::Mul for SignedAmount {
+ type Output = SignedAmount;
+
+ fn mul(self, rhs: i64) -> Self::Output {
+ self.checked_mul(rhs).expect("SignedAmount multiplication error")
+ }
+}
+
+impl ops::MulAssign for SignedAmount {
+ fn mul_assign(&mut self, rhs: i64) {
+ *self = *self * rhs
+ }
+}
+
+impl ops::Div for SignedAmount {
+ type Output = SignedAmount;
+
+ fn div(self, rhs: i64) -> Self::Output {
+ self.checked_div(rhs).expect("SignedAmount division error")
+ }
+}
+
+impl ops::DivAssign for SignedAmount {
+ fn div_assign(&mut self, rhs: i64) {
+ *self = *self / rhs
+ }
+}
+
+impl FromStr for SignedAmount {
+ type Err = ParseAmountError;
+
+ fn from_str(s: &str) -> Result {
+ SignedAmount::from_str_with_denomination(s)
+ }
+}
+
+#[cfg(feature = "serde")]
+pub mod serde {
+ // methods are implementation of a standardized serde-specific signature
+ #![allow(missing_docs)]
+
+ //! This module adds serde serialization and deserialization support for Amounts.
+ //! Since there is not a default way to serialize and deserialize Amounts, multiple
+ //! ways are supported and it's up to the user to decide which serialiation to use.
+ //! The provided modules can be used as follows:
+ //!
+ //! ```rust,ignore
+ //! use serde::{Serialize, Deserialize};
+ //! use bitcoin::Amount;
+ //!
+ //! #[derive(Serialize, Deserialize)]
+ //! pub struct HasAmount {
+ //! #[serde(with = "bitcoin::util::amount::serde::as_btc")]
+ //! pub amount: Amount,
+ //! }
+ //! ```
+
+ use serde::{Deserialize, Deserializer, Serialize, Serializer};
+ use util::amount::{Amount, Denomination, SignedAmount};
+
+ /// This trait is used only to avoid code duplication and naming collisions
+ /// of the different serde serialization crates.
+ pub trait SerdeAmount: Copy + Sized {
+ fn ser_sat(self, s: S) -> Result;
+ fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result;
+ fn ser_btc(self, s: S) -> Result;
+ fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result;
+ }
+
+ impl SerdeAmount for Amount {
+ fn ser_sat(self, s: S) -> Result {
+ u64::serialize(&self.as_sat(), s)
+ }
+ fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result {
+ Ok(Amount::from_sat(u64::deserialize(d)?))
+ }
+ fn ser_btc(self, s: S) -> Result {
+ f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
+ }
+ fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result {
+ use serde::de::Error;
+ Ok(Amount::from_btc(f64::deserialize(d)?).map_err(D::Error::custom)?)
+ }
+ }
+
+ impl SerdeAmount for SignedAmount {
+ fn ser_sat(self, s: S) -> Result {
+ i64::serialize(&self.as_sat(), s)
+ }
+ fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result {
+ Ok(SignedAmount::from_sat(i64::deserialize(d)?))
+ }
+ fn ser_btc(self, s: S) -> Result {
+ f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
+ }
+ fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result {
+ use serde::de::Error;
+ Ok(SignedAmount::from_btc(f64::deserialize(d)?).map_err(D::Error::custom)?)
+ }
+ }
+
+ pub mod as_sat {
+ //! Serialize and deserialize [Amount] as real numbers denominated in satoshi.
+ //! Use with `#[serde(with = "amount::serde::as_sat")]`.
+
+ use serde::{Deserialize, Deserializer, Serialize, Serializer};
+ use util::amount::serde::SerdeAmount;
+
+ pub fn serialize(a: &A, s: S) -> Result {
+ a.ser_sat(s)
+ }
+
+ pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result {
+ A::des_sat(d)
+ }
+
+ pub mod opt {
+ //! Serialize and deserialize [Optoin] as real numbers denominated in satoshi.
+ //! Use with `#[serde(default, with = "amount::serde::as_sat::opt")]`.
+
+ use serde::{Deserialize, Deserializer, Serializer};
+ use util::amount::serde::SerdeAmount;
+
+ pub fn serialize(
+ a: &Option,
+ s: S,
+ ) -> Result {
+ match *a {
+ Some(a) => a.ser_sat(s),
+ None => s.serialize_none(),
+ }
+ }
+
+ pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(
+ d: D,
+ ) -> Result