Change `Amount::MAX` to equal `MAX_MONEY`
To prevent rounding errors converting to and from f64 change `Amount::MAX` to `MAX_MONEY` which is below the limit in f64 that has issues. Add checks to `from_str_in`, `checked_add` and `checked_mul` that the result is below MAX, where previously a u64 overflow was relied on. Change tests to account for new lower MAX that is within the range of SignedAmount and does not overflow so easily Remove overflow tests `Amount::MAX` is now below `u64::MAX` and within the range of values for `SignedAmount`. These tests therefore do not overflow. In effective_value there is no error with `Amount::MAX` and the correct value is returned. In psbt the removed test is effectively the same as the previous test. Modify `Amount` tests to work with new `MAX` Tests need to be changed that checked values above the new `MAX` or `Amount::MAX` was out of range for `SignedAmount` which it isn't anymore
This commit is contained in:
parent
aa25adfc64
commit
6950c0a7b5
|
@ -1709,12 +1709,6 @@ mod tests {
|
|||
assert!(eff_value.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_value_value_does_not_overflow() {
|
||||
let eff_value = effective_value(FeeRate::ZERO, Weight::ZERO, Amount::MAX);
|
||||
assert!(eff_value.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn txin_txout_weight() {
|
||||
// [(is_segwit, tx_hex, expected_weight)]
|
||||
|
|
|
@ -2159,7 +2159,7 @@ mod tests {
|
|||
let output_1_val = Amount::from_sat(100_000_000);
|
||||
let prev_output_val = Amount::from_sat(200_000_000);
|
||||
|
||||
let mut t = Psbt {
|
||||
let t = Psbt {
|
||||
unsigned_tx: Transaction {
|
||||
version: transaction::Version::TWO,
|
||||
lock_time: absolute::LockTime::from_consensus(1257139),
|
||||
|
@ -2253,13 +2253,6 @@ mod tests {
|
|||
Error::NegativeFee => {}
|
||||
e => panic!("unexpected error: {:?}", e),
|
||||
}
|
||||
// overflow
|
||||
t.unsigned_tx.output[0].value = Amount::MAX;
|
||||
t.unsigned_tx.output[1].value = Amount::MAX;
|
||||
match t.fee().unwrap_err() {
|
||||
Error::FeeOverflow => {}
|
||||
e => panic!("unexpected error: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -80,10 +80,6 @@ fn test_signed_amount_try_from_amount() {
|
|||
let ua_positive = Amount::from_sat(123);
|
||||
let sa_positive = SignedAmount::try_from(ua_positive).unwrap();
|
||||
assert_eq!(sa_positive, SignedAmount::from_sat(123));
|
||||
|
||||
let ua_max = Amount::MAX;
|
||||
let result = SignedAmount::try_from(ua_max);
|
||||
assert_eq!(result, Err(OutOfRangeError { is_signed: true, is_greater_than_max: true }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -154,9 +150,6 @@ fn amount_checked_div_by_weight_ceil() {
|
|||
// round up to 864
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864));
|
||||
|
||||
let fee_rate = Amount::MAX.checked_div_by_weight_ceil(weight);
|
||||
assert!(fee_rate.is_none());
|
||||
|
||||
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_ceil(Weight::ZERO);
|
||||
assert!(fee_rate.is_none());
|
||||
}
|
||||
|
@ -175,9 +168,6 @@ fn amount_checked_div_by_weight_floor() {
|
|||
// round down to 863
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
|
||||
|
||||
let fee_rate = Amount::MAX.checked_div_by_weight_floor(weight);
|
||||
assert!(fee_rate.is_none());
|
||||
|
||||
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_floor(Weight::ZERO);
|
||||
assert!(fee_rate.is_none());
|
||||
}
|
||||
|
@ -230,9 +220,6 @@ fn floating_point() {
|
|||
assert_eq!(sf(-184467440738.0, D::Bitcoin), Err(OutOfRangeError::too_small().into()));
|
||||
assert_eq!(f(18446744073709551617.0, D::Satoshi), Err(OutOfRangeError::too_big(false).into()));
|
||||
|
||||
// Amount can be grater than the max SignedAmount.
|
||||
assert!(f(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi).is_ok());
|
||||
|
||||
assert_eq!(
|
||||
f(Amount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
|
||||
Err(OutOfRangeError::too_big(false).into())
|
||||
|
@ -283,7 +270,7 @@ fn parsing() {
|
|||
Err(E::from(InvalidCharacterError { invalid_char: '.', position: 5 }))
|
||||
);
|
||||
#[cfg(feature = "alloc")]
|
||||
let more_than_max = format!("1{}", Amount::MAX);
|
||||
let more_than_max = format!("{}", Amount::MAX.to_sat() + 1);
|
||||
#[cfg(feature = "alloc")]
|
||||
assert_eq!(p(&more_than_max, btc), Err(OutOfRangeError::too_big(false).into()));
|
||||
assert_eq!(p("0.000000042", btc), Err(TooPreciseError { position: 10 }.into()));
|
||||
|
@ -300,20 +287,9 @@ fn parsing() {
|
|||
assert_eq!(p("1.1", btc), Ok(Amount::from_sat(1_100_000_00)));
|
||||
assert_eq!(p("100", sat), Ok(Amount::from_sat(100)));
|
||||
assert_eq!(p("55", sat), Ok(Amount::from_sat(55)));
|
||||
assert_eq!(p("5500000000000000000", sat), Ok(Amount::from_sat(55_000_000_000_000_000_00)));
|
||||
// Should this even pass?
|
||||
assert_eq!(p("5500000000000000000.", sat), Ok(Amount::from_sat(55_000_000_000_000_000_00)));
|
||||
assert_eq!(p("12345678901.12345678", btc), Ok(Amount::from_sat(12_345_678_901__123_456_78)));
|
||||
|
||||
// make sure satoshi > i64::MAX is checked.
|
||||
#[cfg(feature = "alloc")]
|
||||
{
|
||||
let amount = Amount::from_sat(i64::MAX as u64);
|
||||
assert_eq!(Amount::from_str_in(&amount.to_string_in(sat), sat), Ok(amount));
|
||||
assert!(SignedAmount::from_str_in(&(amount + Amount::from_sat(1)).to_string_in(sat), sat)
|
||||
.is_err());
|
||||
assert!(Amount::from_str_in(&(amount + Amount::from_sat(1)).to_string_in(sat), sat).is_ok());
|
||||
}
|
||||
assert_eq!(p("2100000000000000", sat), Ok(Amount::from_sat(21_000_000__000_000_00)));
|
||||
assert_eq!(p("2100000000000000.", sat), Ok(Amount::from_sat(21_000_000__000_000_00)));
|
||||
assert_eq!(p("21000000", btc), Ok(Amount::from_sat(21_000_000__000_000_00)));
|
||||
|
||||
// exactly 50 chars.
|
||||
assert_eq!(
|
||||
|
@ -523,14 +499,14 @@ check_format_non_negative_show_denom! {
|
|||
fn test_unsigned_signed_conversion() {
|
||||
let sa = SignedAmount::from_sat;
|
||||
let ua = Amount::from_sat;
|
||||
let max_sats: u64 = Amount::MAX.to_sat();
|
||||
|
||||
assert_eq!(Amount::MAX.to_signed(), Err(OutOfRangeError::too_big(true)));
|
||||
assert_eq!(ua(i64::MAX as u64).to_signed(), Ok(sa(i64::MAX)));
|
||||
assert_eq!(ua(max_sats).to_signed(), Ok(sa(max_sats as i64)));
|
||||
assert_eq!(ua(i64::MAX as u64 + 1).to_signed(), Err(OutOfRangeError::too_big(true)));
|
||||
|
||||
assert_eq!(sa(i64::MAX).to_unsigned(), Ok(ua(i64::MAX as u64)));
|
||||
assert_eq!(sa(max_sats as i64).to_unsigned(), Ok(ua(max_sats)));
|
||||
|
||||
assert_eq!(sa(i64::MAX).to_unsigned().unwrap().to_signed(), Ok(sa(i64::MAX)));
|
||||
assert_eq!(sa(max_sats as i64).to_unsigned().unwrap().to_signed(), Ok(sa(max_sats as i64)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -638,7 +614,7 @@ fn to_from_string_in() {
|
|||
ua_str(&ua_sat(1_000_000_000_000).to_string_in(D::MilliBitcoin), D::MilliBitcoin),
|
||||
Ok(ua_sat(1_000_000_000_000))
|
||||
);
|
||||
assert!(ua_str(&ua_sat(u64::MAX).to_string_in(D::MilliBitcoin), D::MilliBitcoin).is_ok());
|
||||
assert!(ua_str(&ua_sat(Amount::MAX.to_sat()).to_string_in(D::MilliBitcoin), D::MilliBitcoin).is_ok());
|
||||
|
||||
assert_eq!(sa_str(&sa_sat(-1).to_string_in(D::MicroBitcoin), D::MicroBitcoin), Ok(sa_sat(-1)));
|
||||
|
||||
|
@ -718,12 +694,12 @@ fn serde_as_btc() {
|
|||
}
|
||||
|
||||
let orig = T {
|
||||
amt: Amount::from_sat(21_000_000__000_000_01),
|
||||
samt: SignedAmount::from_sat(-21_000_000__000_000_01),
|
||||
amt: Amount::from_sat(20_000_000__000_000_01),
|
||||
samt: SignedAmount::from_sat(-20_000_000__000_000_01),
|
||||
};
|
||||
|
||||
let json = "{\"amt\": 21000000.00000001, \
|
||||
\"samt\": -21000000.00000001}";
|
||||
let json = "{\"amt\": 20000000.00000001, \
|
||||
\"samt\": -20000000.00000001}";
|
||||
let t: T = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(t, orig);
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ impl Amount {
|
|||
/// The minimum value of an amount.
|
||||
pub const MIN: Amount = Amount::ZERO;
|
||||
/// The maximum value of an amount.
|
||||
pub const MAX: Amount = Amount(u64::MAX);
|
||||
pub const MAX: Amount = Amount::MAX_MONEY;
|
||||
/// The number of bytes that an amount contributes to the size of a transaction.
|
||||
pub const SIZE: usize = 8; // Serialized length of a u64.
|
||||
|
||||
|
@ -120,6 +120,11 @@ impl Amount {
|
|||
OutOfRangeError::negative(),
|
||||
)));
|
||||
}
|
||||
if satoshi > Self::MAX.0 {
|
||||
return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
|
||||
OutOfRangeError::too_big(false),
|
||||
)));
|
||||
}
|
||||
Ok(Amount::from_sat(satoshi))
|
||||
}
|
||||
|
||||
|
@ -214,7 +219,7 @@ impl Amount {
|
|||
pub const fn checked_add(self, rhs: Amount) -> Option<Amount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_add(rhs.0) {
|
||||
Some(res) => Some(Amount(res)),
|
||||
Some(res) => Amount(res).check_max(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +241,7 @@ impl Amount {
|
|||
pub const fn checked_mul(self, rhs: u64) -> Option<Amount> {
|
||||
// No `map()` in const context.
|
||||
match self.0.checked_mul(rhs) {
|
||||
Some(res) => Some(Amount(res)),
|
||||
Some(res) => Amount(res).check_max(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -332,6 +337,15 @@ impl Amount {
|
|||
Ok(SignedAmount::from_sat(self.to_sat() as i64))
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the amount is below the maximum value. Returns `None` if it is above.
|
||||
const fn check_max(self) -> Option<Amount> {
|
||||
if self.0 > Self::MAX.0 {
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl default::Default for Amount {
|
||||
|
|
Loading…
Reference in New Issue