Skip to content

Commit 0ca9fcf

Browse files
committed
Merge rust-bitcoin#4157: Enforce MAX_MONEY invariant in amount types
ab4ea7c 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 rust-bitcoin#620 ACKs for top commit: apoelstra: ACK ab4ea7c; successfully ran local tests Tree-SHA512: bec963d8ea69e202f399cd19bca864b06f3e86323d376c2d2126d74093598f8bbbf19792b2327dba0862ef6f0201202778014a2be7a14991f02917d8ca312afb
2 parents 2f711b0 + ab4ea7c commit 0ca9fcf

File tree

13 files changed

+250
-183
lines changed

13 files changed

+250
-183
lines changed

bitcoin/src/blockdata/script/borrowed.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ crate::internal_macros::define_extension_trait! {
413413
// Note: We ensure the division happens at the end, since Core performs the division at the end.
414414
// This will make sure none of the implicit floor operations mess with the value.
415415

416-
Some(Amount::from_sat(sats))
416+
Amount::from_sat(sats).ok()
417417
}
418418

419419
fn count_sigops_internal(&self, accurate: bool) -> usize {

bitcoin/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ pub mod amount {
198198
//! This module mainly introduces the [`Amount`] and [`SignedAmount`] types.
199199
//! We refer to the documentation on the types for more information.
200200
201-
use crate::consensus::{encode, Decodable, Encodable};
201+
use crate::consensus::{self, encode, Decodable, Encodable};
202202
use crate::io::{BufRead, Write};
203203

204204
#[rustfmt::skip] // Keep public re-exports separate.
@@ -215,7 +215,9 @@ pub mod amount {
215215
impl Decodable for Amount {
216216
#[inline]
217217
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
218-
Ok(Amount::from_sat(Decodable::consensus_decode(r)?))
218+
Amount::from_sat(Decodable::consensus_decode(r)?).map_err(|_| {
219+
consensus::parse_failed_error("amount is greater than Amount::MAX_MONEY")
220+
})
219221
}
220222
}
221223

bitcoin/src/psbt/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ mod tests {
12681268
witness: Witness::default(),
12691269
}],
12701270
output: vec![TxOut {
1271-
value: Amount::from_sat(output),
1271+
value: Amount::from_sat(output).unwrap(),
12721272
script_pubkey: ScriptBuf::from_hex(
12731273
"a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
12741274
)
@@ -1282,7 +1282,7 @@ mod tests {
12821282

12831283
inputs: vec![Input {
12841284
witness_utxo: Some(TxOut {
1285-
value: Amount::from_sat(input),
1285+
value: Amount::from_sat(input).unwrap(),
12861286
script_pubkey: ScriptBuf::from_hex(
12871287
"a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
12881288
)

bitcoin/tests/psbt-sign-taproot.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ fn create_psbt_for_taproot_key_path_spend(
205205
) -> Psbt {
206206
let send_value = 6400;
207207
let out_puts = vec![TxOut {
208-
value: Amount::from_sat(send_value),
208+
value: Amount::from_sat(send_value).unwrap(),
209209
script_pubkey: to_address.script_pubkey(),
210210
}];
211211
let prev_tx_id = "06980ca116f74c7845a897461dd0e1d15b114130176de5004957da516b4dee3a";
@@ -243,7 +243,7 @@ fn create_psbt_for_taproot_key_path_spend(
243243
let mut input = Input {
244244
witness_utxo: {
245245
let script_pubkey = from_address.script_pubkey();
246-
Some(TxOut { value: Amount::from_sat(utxo_value), script_pubkey })
246+
Some(TxOut { value: Amount::from_sat(utxo_value).unwrap(), script_pubkey })
247247
},
248248
tap_key_origins: origins,
249249
..Default::default()
@@ -283,7 +283,7 @@ fn create_psbt_for_taproot_script_path_spend(
283283
let mfp = "73c5da0a";
284284

285285
let out_puts = vec![TxOut {
286-
value: Amount::from_sat(send_value),
286+
value: Amount::from_sat(send_value).unwrap(),
287287
script_pubkey: to_address.script_pubkey(),
288288
}];
289289
let prev_tx_id = "9d7c6770fca57285babab60c51834cfcfd10ad302119cae842d7216b4ac9a376";
@@ -322,7 +322,7 @@ fn create_psbt_for_taproot_script_path_spend(
322322
let mut input = Input {
323323
witness_utxo: {
324324
let script_pubkey = from_address.script_pubkey();
325-
Some(TxOut { value: Amount::from_sat(utxo_value), script_pubkey })
325+
Some(TxOut { value: Amount::from_sat(utxo_value).unwrap(), script_pubkey })
326326
},
327327
tap_key_origins: origins,
328328
tap_scripts,

primitives/src/transaction.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ mod tests {
670670
witness: Witness::new(),
671671
};
672672

673-
let txout = TxOut { value: Amount::from_sat(123_456_789), script_pubkey: ScriptBuf::new() };
673+
let txout = TxOut { value: Amount::from_sat(123_456_789).unwrap(), script_pubkey: ScriptBuf::new() };
674674

675675
let tx_orig = Transaction {
676676
version: Version::ONE,
@@ -682,7 +682,7 @@ mod tests {
682682
// Test changing the transaction
683683
let mut tx = tx_orig.clone();
684684
tx.inputs_mut()[0].previous_output.txid = Txid::from_byte_array([0xFF; 32]);
685-
tx.outputs_mut()[0].value = Amount::from_sat(987_654_321);
685+
tx.outputs_mut()[0].value = Amount::from_sat(987_654_321).unwrap();
686686
assert_eq!(tx.inputs()[0].previous_output.txid.to_byte_array(), [0xFF; 32]);
687687
assert_eq!(tx.outputs()[0].value.to_sat(), 987_654_321);
688688

units/src/amount/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub(crate) use self::result::OptionExt;
5959
/// # Examples
6060
///
6161
/// ```
62-
/// # use bitcoin_units::Amount;
62+
/// # use bitcoin_units::{amount, Amount};
6363
///
6464
/// let equal = [
6565
/// ("1 BTC", 100_000_000),
@@ -72,9 +72,10 @@ pub(crate) use self::result::OptionExt;
7272
/// for (string, sats) in equal {
7373
/// assert_eq!(
7474
/// string.parse::<Amount>().expect("valid bitcoin amount string"),
75-
/// Amount::from_sat(sats),
75+
/// Amount::from_sat(sats)?,
7676
/// )
7777
/// }
78+
/// # Ok::<_, amount::OutOfRangeError>(())
7879
/// ```
7980
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
8081
#[non_exhaustive]

units/src/amount/result.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,9 @@ crate::internal_macros::impl_op_for_references! {
328328
impl ops::Neg for SignedAmount {
329329
type Output = Self;
330330

331-
fn neg(self) -> Self::Output { Self::from_sat(self.to_sat().neg()) }
331+
fn neg(self) -> Self::Output {
332+
Self::from_sat(self.to_sat().neg()).expect("all +ve and -ve values are valid")
333+
}
332334
}
333335

334336
impl core::iter::Sum<NumOpResult<Amount>> for NumOpResult<Amount> {

units/src/amount/serde.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ impl SerdeAmount for Amount {
9292
u64::serialize(&self.to_sat(), s)
9393
}
9494
fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
95-
Ok(Amount::from_sat(u64::deserialize(d)?))
95+
use serde::de::Error;
96+
Amount::from_sat(u64::deserialize(d)?).map_err(D::Error::custom)
9697
}
9798
#[cfg(feature = "alloc")]
9899
fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
@@ -137,7 +138,8 @@ impl SerdeAmount for SignedAmount {
137138
i64::serialize(&self.to_sat(), s)
138139
}
139140
fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
140-
Ok(SignedAmount::from_sat(i64::deserialize(d)?))
141+
use serde::de::Error;
142+
SignedAmount::from_sat(i64::deserialize(d)?).map_err(D::Error::custom)
141143
}
142144
#[cfg(feature = "alloc")]
143145
fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {

0 commit comments

Comments
 (0)