Merge rust-bitcoin/rust-bitcoin#2336: units: Enable parsing Amount from `u64::MAX`

b2344e019d units: Assert roundtrip SignedAmount/str overflows (Tobin C. Harding)
baadcf4c0a units: Test that SignedAmount float conversion overflows (Tobin C. Harding)
d768f25da8 units: Remove duplicate assertion (Tobin C. Harding)
1d536ac8b2 units: Enable parsing Amount from u64::MAX (Tobin C. Harding)

Pull request description:

  Our `Amount` type uses an internal `u64` and we maintain no invariants on the inner value. Therefore we should be able to parse `u64::MAX`.

  Fix the parsing code by removing the explicit, incorrect check and fix unit tests to mirror this behaviour.

  Fix: #2297

ACKs for top commit:
  Kixunil:
    ACK b2344e019d
  apoelstra:
    ACK b2344e019d

Tree-SHA512: 944f8d0bfedc559f0444f75eca7d3fba042fbc204c4c032d09ff0edc29be280a3707f5b363dbc04f0d7bdf64701c0c4619e2e0de683d804a2663c2a20ac963f6
This commit is contained in:
Andrew Poelstra 2024-01-22 19:11:59 +00:00
commit d08d3efdfa
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 12 additions and 16 deletions

View File

@ -617,9 +617,6 @@ impl Amount {
if negative { if negative {
return Err(ParseAmountError::Negative); return Err(ParseAmountError::Negative);
} }
if satoshi > i64::MAX as u64 {
return Err(ParseAmountError::TooBig);
}
Ok(Amount::from_sat(satoshi)) Ok(Amount::from_sat(satoshi))
} }
@ -1716,19 +1713,23 @@ mod tests {
assert_eq!(f(-100.0, D::MilliSatoshi), Err(ParseAmountError::Negative)); assert_eq!(f(-100.0, D::MilliSatoshi), Err(ParseAmountError::Negative));
assert_eq!(f(11.22, D::Satoshi), Err(ParseAmountError::TooPrecise)); assert_eq!(f(11.22, D::Satoshi), Err(ParseAmountError::TooPrecise));
assert_eq!(sf(-100.0, D::MilliSatoshi), Err(ParseAmountError::TooPrecise)); assert_eq!(sf(-100.0, D::MilliSatoshi), Err(ParseAmountError::TooPrecise));
assert_eq!(sf(-100.0, D::MilliSatoshi), Err(ParseAmountError::TooPrecise));
assert_eq!(f(42.123456781, D::Bitcoin), Err(ParseAmountError::TooPrecise)); assert_eq!(f(42.123456781, D::Bitcoin), Err(ParseAmountError::TooPrecise));
assert_eq!(sf(-184467440738.0, D::Bitcoin), Err(ParseAmountError::TooBig)); assert_eq!(sf(-184467440738.0, D::Bitcoin), Err(ParseAmountError::TooBig));
assert_eq!(f(18446744073709551617.0, D::Satoshi), Err(ParseAmountError::TooBig)); assert_eq!(f(18446744073709551617.0, D::Satoshi), Err(ParseAmountError::TooBig));
assert_eq!(
f(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi), // Amount can be grater than the max SignedAmount.
Err(ParseAmountError::TooBig) assert!(f(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi).is_ok());
);
assert_eq!( assert_eq!(
f(Amount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi), f(Amount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
Err(ParseAmountError::TooBig) Err(ParseAmountError::TooBig)
); );
assert_eq!(
sf(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
Err(ParseAmountError::TooBig)
);
let btc = move |f| SignedAmount::from_btc(f).unwrap(); let btc = move |f| SignedAmount::from_btc(f).unwrap();
assert_eq!(btc(2.5).to_float_in(D::Bitcoin), 2.5); assert_eq!(btc(2.5).to_float_in(D::Bitcoin), 2.5);
assert_eq!(btc(-2.5).to_float_in(D::MilliBitcoin), -2500.0); assert_eq!(btc(-2.5).to_float_in(D::MilliBitcoin), -2500.0);
@ -1787,10 +1788,8 @@ mod tests {
// make sure satoshi > i64::MAX is checked. // make sure satoshi > i64::MAX is checked.
let amount = Amount::from_sat(i64::MAX as u64); let amount = Amount::from_sat(i64::MAX as u64);
assert_eq!(Amount::from_str_in(&amount.to_string_in(sat), sat), Ok(amount)); assert_eq!(Amount::from_str_in(&amount.to_string_in(sat), sat), Ok(amount));
assert_eq!( assert!(SignedAmount::from_str_in(&(amount + Amount(1)).to_string_in(sat), sat).is_err());
Amount::from_str_in(&(amount + Amount(1)).to_string_in(sat), sat), assert!(Amount::from_str_in(&(amount + Amount(1)).to_string_in(sat), sat).is_ok());
Err(E::TooBig)
);
assert_eq!(p("12.000", Denomination::MilliSatoshi), Err(E::TooPrecise)); assert_eq!(p("12.000", Denomination::MilliSatoshi), Err(E::TooPrecise));
// exactly 50 chars. // exactly 50 chars.
@ -2121,10 +2120,7 @@ mod tests {
ua_str(&ua_sat(1_000_000_000_000).to_string_in(D::MilliBitcoin), D::MilliBitcoin), ua_str(&ua_sat(1_000_000_000_000).to_string_in(D::MilliBitcoin), D::MilliBitcoin),
Ok(ua_sat(1_000_000_000_000)) Ok(ua_sat(1_000_000_000_000))
); );
assert_eq!( assert!(ua_str(&ua_sat(u64::MAX).to_string_in(D::MilliBitcoin), D::MilliBitcoin).is_ok());
ua_str(&ua_sat(u64::MAX).to_string_in(D::MilliBitcoin), D::MilliBitcoin),
Err(ParseAmountError::TooBig)
);
assert_eq!( assert_eq!(
sa_str(&sa_sat(-1).to_string_in(D::MicroBitcoin), D::MicroBitcoin), sa_str(&sa_sat(-1).to_string_in(D::MicroBitcoin), D::MicroBitcoin),