Merge rust-bitcoin/rust-bitcoin#4256: Followups to #4157: remove Amount::from_sat_unchecked
2958521117
amount: remove from_sat_unchecked (Andrew Poelstra)d0d7a15604
amount: move MIN/MAX constants and constructors inside the privacy boundary (Andrew Poelstra)004d073184
amount: return i64 from parse_signed_to_satoshi (Andrew Poelstra)3370c14d74
amount: stop using from_sat_unchecked in tests (Andrew Poelstra)05c8b043ff
tests: replace Amount::from_sat_unchecked with from_sat.unwrap (Andrew Poelstra)beaa2db7e5
amount: add from_sat_i32 and from_sat_u32 methods for small constants (Andrew Poelstra)82d9d1aeea
amount: rename `from_int_btc_const` unctions to hungarian ones (Andrew Poelstra)2ec1c2a044
units: Make from_int_btc_const take a 16 bit integer (Tobin C. Harding) Pull request description: This does some followups to #4157. It pulls in #4254, which changes the signature of `Amount::from_int_btc_const` and also replaces a `checked_mul` which can't ever fail with a `*`. It then renames `from_int_btc_const` to `from_btc_u16`/`from_btc_i16` as appropriate, since these names reflect what the function does. It also adds an analogous method for making `Amount`s from sats rather than BTC. Then it systematically removes every instance of `from_sat_unchecked`. There wind up being 7 instances in tests that I just added unwraps() to, plus one instance where we had `let sat = Amount::from_sat_unchecked` which I also added an unwrap() to. In the real code, there was one instance (`Amount::to_signed`) where I needed to add an `expect`. Every other instance I was able to remove, by refactoring logic to use the error case as appropriate. ACKs for top commit: tcharding: ACK2958521117
Kixunil: ACK2958521117
Tree-SHA512: e2c2eb19acdd60da5524f6700e66c140b8e113bec7f2418a2505aeefdbf742eff216fb580891a3ea53255156786f5a6b365b8fb6553e60e42b18d4115d705dd2
This commit is contained in:
commit
e0be90d1d3
|
@ -47,12 +47,12 @@ const BIP84_DERIVATION_PATH: &str = "m/84'/0'/0'";
|
||||||
const MASTER_FINGERPRINT: &str = "9680603f";
|
const MASTER_FINGERPRINT: &str = "9680603f";
|
||||||
|
|
||||||
// The dummy UTXO amounts we are spending.
|
// The dummy UTXO amounts we are spending.
|
||||||
const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_unchecked(20_000_000);
|
const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_u32(20_000_000);
|
||||||
const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_unchecked(10_000_000);
|
const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_u32(10_000_000);
|
||||||
|
|
||||||
// The amounts we are sending to someone, and receiving back as change.
|
// The amounts we are sending to someone, and receiving back as change.
|
||||||
const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(25_000_000);
|
const SPEND_AMOUNT: Amount = Amount::from_sat_u32(25_000_000);
|
||||||
const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(4_990_000); // 10_000 sat fee.
|
const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(4_990_000); // 10_000 sat fee.
|
||||||
|
|
||||||
// Derive the external address xpriv.
|
// Derive the external address xpriv.
|
||||||
fn get_external_address_xpriv<C: Signing>(
|
fn get_external_address_xpriv<C: Signing>(
|
||||||
|
|
|
@ -148,7 +148,7 @@ fn sighash_p2wpkh() {
|
||||||
let inp_idx = 0;
|
let inp_idx = 0;
|
||||||
//output value from the referenced vout:0 from the referenced tx:
|
//output value from the referenced vout:0 from the referenced tx:
|
||||||
//bitcoin-cli getrawtransaction 752d675b9cc0bd14e0bd23969effee0005ad6d7e550dcc832f0216c7ffd4e15c 3
|
//bitcoin-cli getrawtransaction 752d675b9cc0bd14e0bd23969effee0005ad6d7e550dcc832f0216c7ffd4e15c 3
|
||||||
let ref_out_value = Amount::from_sat_unchecked(200000000);
|
let ref_out_value = Amount::from_sat_u32(200000000);
|
||||||
|
|
||||||
println!("\nsighash_p2wpkh:");
|
println!("\nsighash_p2wpkh:");
|
||||||
compute_sighash_p2wpkh(&raw_tx, inp_idx, ref_out_value);
|
compute_sighash_p2wpkh(&raw_tx, inp_idx, ref_out_value);
|
||||||
|
@ -178,7 +178,7 @@ fn sighash_p2wsh_multisig_2x2() {
|
||||||
//For the witness transaction sighash computation, we need its referenced output's value from the original transaction:
|
//For the witness transaction sighash computation, we need its referenced output's value from the original transaction:
|
||||||
//bitcoin-cli getrawtransaction 2845399a8cd7a52733f9f9d0e0b8b6c5d1c88aea4cee09f8d8fa762912b49e1b 3
|
//bitcoin-cli getrawtransaction 2845399a8cd7a52733f9f9d0e0b8b6c5d1c88aea4cee09f8d8fa762912b49e1b 3
|
||||||
//we need vout 0 value in sats:
|
//we need vout 0 value in sats:
|
||||||
let ref_out_value = Amount::from_sat_unchecked(968240);
|
let ref_out_value = Amount::from_sat_u32(968240);
|
||||||
|
|
||||||
println!("\nsighash_p2wsh_multisig_2x2:");
|
println!("\nsighash_p2wsh_multisig_2x2:");
|
||||||
compute_sighash_p2wsh(&raw_tx, 0, ref_out_value);
|
compute_sighash_p2wsh(&raw_tx, 0, ref_out_value);
|
||||||
|
|
|
@ -13,9 +13,9 @@ use bitcoin::{
|
||||||
Txid, Witness,
|
Txid, Witness,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat_unchecked(20_000_000);
|
const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat_u32(20_000_000);
|
||||||
const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(5_000_000);
|
const SPEND_AMOUNT: Amount = Amount::from_sat_u32(5_000_000);
|
||||||
const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(14_999_000); // 1000 sat fee.
|
const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(14_999_000); // 1000 sat fee.
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
|
@ -13,9 +13,9 @@ use bitcoin::{
|
||||||
Txid, Witness,
|
Txid, Witness,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat_unchecked(20_000_000);
|
const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat_u32(20_000_000);
|
||||||
const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(5_000_000);
|
const SPEND_AMOUNT: Amount = Amount::from_sat_u32(5_000_000);
|
||||||
const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(14_999_000); // 1000 sat fee.
|
const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(14_999_000); // 1000 sat fee.
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
|
@ -45,12 +45,12 @@ const BIP86_DERIVATION_PATH: &str = "m/86'/0'/0'";
|
||||||
const MASTER_FINGERPRINT: &str = "9680603f";
|
const MASTER_FINGERPRINT: &str = "9680603f";
|
||||||
|
|
||||||
// The dummy UTXO amounts we are spending.
|
// The dummy UTXO amounts we are spending.
|
||||||
const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_unchecked(20_000_000);
|
const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_u32(20_000_000);
|
||||||
const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_unchecked(10_000_000);
|
const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_u32(10_000_000);
|
||||||
|
|
||||||
// The amounts we are sending to someone, and receiving back as change.
|
// The amounts we are sending to someone, and receiving back as change.
|
||||||
const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(25_000_000);
|
const SPEND_AMOUNT: Amount = Amount::from_sat_u32(25_000_000);
|
||||||
const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(4_990_000); // 10_000 sat fee.
|
const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(4_990_000); // 10_000 sat fee.
|
||||||
|
|
||||||
// Derive the external address xpriv.
|
// Derive the external address xpriv.
|
||||||
fn get_external_address_xpriv<C: Signing>(
|
fn get_external_address_xpriv<C: Signing>(
|
||||||
|
|
|
@ -40,7 +40,7 @@ const UTXO_SCRIPT_PUBKEY: &str =
|
||||||
"5120be27fa8b1f5278faf82cab8da23e8761f8f9bd5d5ebebbb37e0e12a70d92dd16";
|
"5120be27fa8b1f5278faf82cab8da23e8761f8f9bd5d5ebebbb37e0e12a70d92dd16";
|
||||||
const UTXO_PUBKEY: &str = "a6ac32163539c16b6b5dbbca01b725b8e8acaa5f821ba42c80e7940062140d19";
|
const UTXO_PUBKEY: &str = "a6ac32163539c16b6b5dbbca01b725b8e8acaa5f821ba42c80e7940062140d19";
|
||||||
const UTXO_MASTER_FINGERPRINT: &str = "e61b318f";
|
const UTXO_MASTER_FINGERPRINT: &str = "e61b318f";
|
||||||
const ABSOLUTE_FEES: Amount = Amount::from_sat_unchecked(1_000);
|
const ABSOLUTE_FEES: Amount = Amount::from_sat_u32(1_000);
|
||||||
|
|
||||||
// UTXO_1 will be used for spending example 1
|
// UTXO_1 will be used for spending example 1
|
||||||
const UTXO_1: P2trUtxo = P2trUtxo {
|
const UTXO_1: P2trUtxo = P2trUtxo {
|
||||||
|
|
|
@ -675,7 +675,7 @@ fn bitcoinconsensus() {
|
||||||
let spent_bytes = hex!("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d");
|
let spent_bytes = hex!("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d");
|
||||||
let spent = Script::from_bytes(&spent_bytes);
|
let spent = Script::from_bytes(&spent_bytes);
|
||||||
let spending = hex!("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000");
|
let spending = hex!("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000");
|
||||||
spent.verify(0, Amount::from_sat_unchecked(18393430), &spending).unwrap();
|
spent.verify(0, Amount::from_sat_u32(18393430), &spending).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -684,10 +684,10 @@ fn default_dust_value() {
|
||||||
// well-known scriptPubKey types.
|
// well-known scriptPubKey types.
|
||||||
let script_p2wpkh = Builder::new().push_int_unchecked(0).push_slice([42; 20]).into_script();
|
let script_p2wpkh = Builder::new().push_int_unchecked(0).push_slice([42; 20]).into_script();
|
||||||
assert!(script_p2wpkh.is_p2wpkh());
|
assert!(script_p2wpkh.is_p2wpkh());
|
||||||
assert_eq!(script_p2wpkh.minimal_non_dust(), Some(Amount::from_sat_unchecked(294)));
|
assert_eq!(script_p2wpkh.minimal_non_dust(), Some(Amount::from_sat_u32(294)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
|
script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
|
||||||
Some(Amount::from_sat_unchecked(588))
|
Some(Amount::from_sat_u32(588))
|
||||||
);
|
);
|
||||||
|
|
||||||
let script_p2pkh = Builder::new()
|
let script_p2pkh = Builder::new()
|
||||||
|
@ -698,10 +698,10 @@ fn default_dust_value() {
|
||||||
.push_opcode(OP_CHECKSIG)
|
.push_opcode(OP_CHECKSIG)
|
||||||
.into_script();
|
.into_script();
|
||||||
assert!(script_p2pkh.is_p2pkh());
|
assert!(script_p2pkh.is_p2pkh());
|
||||||
assert_eq!(script_p2pkh.minimal_non_dust(), Some(Amount::from_sat_unchecked(546)));
|
assert_eq!(script_p2pkh.minimal_non_dust(), Some(Amount::from_sat_u32(546)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
|
script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
|
||||||
Some(Amount::from_sat_unchecked(1092))
|
Some(Amount::from_sat_u32(1092))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2089,7 +2089,7 @@ mod tests {
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let spk = ScriptBuf::from_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1").unwrap();
|
let spk = ScriptBuf::from_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1").unwrap();
|
||||||
let value = Amount::from_sat_unchecked(600_000_000);
|
let value = Amount::from_sat_u32(600_000_000);
|
||||||
|
|
||||||
let mut cache = SighashCache::new(&tx);
|
let mut cache = SighashCache::new(&tx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -2130,7 +2130,7 @@ mod tests {
|
||||||
|
|
||||||
let redeem_script =
|
let redeem_script =
|
||||||
ScriptBuf::from_hex("001479091972186c449eb1ded22b78e40d009bdf0089").unwrap();
|
ScriptBuf::from_hex("001479091972186c449eb1ded22b78e40d009bdf0089").unwrap();
|
||||||
let value = Amount::from_sat_unchecked(1_000_000_000);
|
let value = Amount::from_sat_u32(1_000_000_000);
|
||||||
|
|
||||||
let mut cache = SighashCache::new(&tx);
|
let mut cache = SighashCache::new(&tx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -2180,7 +2180,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let value = Amount::from_sat_unchecked(987_654_321);
|
let value = Amount::from_sat_u32(987_654_321);
|
||||||
(tx, witness_script, value)
|
(tx, witness_script, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1429,14 +1429,14 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(99_999_699),
|
value: Amount::from_sat_u32(99_999_699),
|
||||||
script_pubkey: ScriptBuf::from_hex(
|
script_pubkey: ScriptBuf::from_hex(
|
||||||
"76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
|
"76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
},
|
},
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(100_000_000),
|
value: Amount::from_sat_u32(100_000_000),
|
||||||
script_pubkey: ScriptBuf::from_hex(
|
script_pubkey: ScriptBuf::from_hex(
|
||||||
"a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
|
"a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
|
||||||
)
|
)
|
||||||
|
@ -1502,7 +1502,7 @@ mod tests {
|
||||||
)]),
|
)]),
|
||||||
}],
|
}],
|
||||||
output: vec![TxOut {
|
output: vec![TxOut {
|
||||||
value: Amount::from_sat_unchecked(190_303_501_938),
|
value: Amount::from_sat(190_303_501_938).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex(
|
script_pubkey: ScriptBuf::from_hex(
|
||||||
"a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
|
"a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
|
||||||
)
|
)
|
||||||
|
@ -1553,7 +1553,7 @@ mod tests {
|
||||||
Input {
|
Input {
|
||||||
non_witness_utxo: Some(tx),
|
non_witness_utxo: Some(tx),
|
||||||
witness_utxo: Some(TxOut {
|
witness_utxo: Some(TxOut {
|
||||||
value: Amount::from_sat_unchecked(190_303_501_938),
|
value: Amount::from_sat(190_303_501_938).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
||||||
}),
|
}),
|
||||||
sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<PsbtSighashType>().unwrap()),
|
sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<PsbtSighashType>().unwrap()),
|
||||||
|
@ -1678,11 +1678,11 @@ mod tests {
|
||||||
],
|
],
|
||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(99_999_699),
|
value: Amount::from_sat_u32(99_999_699),
|
||||||
script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
|
||||||
},
|
},
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(100_000_000),
|
value: Amount::from_sat_u32(100_000_000),
|
||||||
script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1725,11 +1725,11 @@ mod tests {
|
||||||
],
|
],
|
||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(200_000_000),
|
value: Amount::from_sat_u32(200_000_000),
|
||||||
script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
|
||||||
},
|
},
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(190_303_501_938),
|
value: Amount::from_sat(190_303_501_938).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -2011,11 +2011,11 @@ mod tests {
|
||||||
],
|
],
|
||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(99_999_699),
|
value: Amount::from_sat_u32(99_999_699),
|
||||||
script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
|
||||||
},
|
},
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(100_000_000),
|
value: Amount::from_sat_u32(100_000_000),
|
||||||
script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -2058,11 +2058,11 @@ mod tests {
|
||||||
],
|
],
|
||||||
output: vec![
|
output: vec![
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(200_000_000),
|
value: Amount::from_sat_u32(200_000_000),
|
||||||
script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
|
||||||
},
|
},
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(190_303_501_938),
|
value: Amount::from_sat(190_303_501_938).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -2171,9 +2171,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fee() {
|
fn fee() {
|
||||||
let output_0_val = Amount::from_sat_unchecked(99_999_699);
|
let output_0_val = Amount::from_sat_u32(99_999_699);
|
||||||
let output_1_val = Amount::from_sat_unchecked(100_000_000);
|
let output_1_val = Amount::from_sat_u32(100_000_000);
|
||||||
let prev_output_val = Amount::from_sat_unchecked(200_000_000);
|
let prev_output_val = Amount::from_sat_u32(200_000_000);
|
||||||
|
|
||||||
let t = Psbt {
|
let t = Psbt {
|
||||||
unsigned_tx: Transaction {
|
unsigned_tx: Transaction {
|
||||||
|
@ -2234,7 +2234,7 @@ mod tests {
|
||||||
script_pubkey: ScriptBuf::new()
|
script_pubkey: ScriptBuf::new()
|
||||||
},
|
},
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_sat_unchecked(190_303_501_938),
|
value: Amount::from_sat(190_303_501_938).unwrap(),
|
||||||
script_pubkey: ScriptBuf::new()
|
script_pubkey: ScriptBuf::new()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -2296,7 +2296,7 @@ mod tests {
|
||||||
|
|
||||||
// First input we can spend. See comment above on key_map for why we use defaults here.
|
// First input we can spend. See comment above on key_map for why we use defaults here.
|
||||||
let txout_wpkh = TxOut {
|
let txout_wpkh = TxOut {
|
||||||
value: Amount::from_sat_unchecked(10),
|
value: Amount::from_sat_u32(10),
|
||||||
script_pubkey: ScriptBuf::new_p2wpkh(pk.wpubkey_hash().unwrap()),
|
script_pubkey: ScriptBuf::new_p2wpkh(pk.wpubkey_hash().unwrap()),
|
||||||
};
|
};
|
||||||
psbt.inputs[0].witness_utxo = Some(txout_wpkh);
|
psbt.inputs[0].witness_utxo = Some(txout_wpkh);
|
||||||
|
@ -2308,7 +2308,7 @@ mod tests {
|
||||||
// Second input is unspendable by us e.g., from another wallet that supports future upgrades.
|
// Second input is unspendable by us e.g., from another wallet that supports future upgrades.
|
||||||
let unknown_prog = WitnessProgram::new(WitnessVersion::V4, &[0xaa; 34]).unwrap();
|
let unknown_prog = WitnessProgram::new(WitnessVersion::V4, &[0xaa; 34]).unwrap();
|
||||||
let txout_unknown_future = TxOut {
|
let txout_unknown_future = TxOut {
|
||||||
value: Amount::from_sat_unchecked(10),
|
value: Amount::from_sat_u32(10),
|
||||||
script_pubkey: ScriptBuf::new_witness_program(&unknown_prog),
|
script_pubkey: ScriptBuf::new_witness_program(&unknown_prog),
|
||||||
};
|
};
|
||||||
psbt.inputs[1].witness_utxo = Some(txout_unknown_future);
|
psbt.inputs[1].witness_utxo = Some(txout_unknown_future);
|
||||||
|
|
|
@ -235,7 +235,7 @@ fn serde_regression_psbt() {
|
||||||
.unwrap()]),
|
.unwrap()]),
|
||||||
}],
|
}],
|
||||||
output: vec![TxOut {
|
output: vec![TxOut {
|
||||||
value: Amount::from_sat_unchecked(190_303_501_938),
|
value: Amount::from_sat(190_303_501_938).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587")
|
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}],
|
}],
|
||||||
|
@ -282,7 +282,7 @@ fn serde_regression_psbt() {
|
||||||
inputs: vec![Input {
|
inputs: vec![Input {
|
||||||
non_witness_utxo: Some(tx),
|
non_witness_utxo: Some(tx),
|
||||||
witness_utxo: Some(TxOut {
|
witness_utxo: Some(TxOut {
|
||||||
value: Amount::from_sat_unchecked(190_303_501_938),
|
value: Amount::from_sat(190_303_501_938).unwrap(),
|
||||||
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
|
||||||
}),
|
}),
|
||||||
sighash_type: Some(PsbtSighashType::from("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<EcdsaSighashType>().unwrap())),
|
sighash_type: Some(PsbtSighashType::from("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<EcdsaSighashType>().unwrap())),
|
||||||
|
|
|
@ -182,8 +182,10 @@ impl OutOfRangeError {
|
||||||
/// Returns true if the input value was smaller than the minimum allowed value.
|
/// Returns true if the input value was smaller than the minimum allowed value.
|
||||||
pub fn is_below_min(self) -> bool { !self.is_greater_than_max }
|
pub fn is_below_min(self) -> bool { !self.is_greater_than_max }
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
|
pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
pub(crate) fn too_small() -> Self {
|
pub(crate) fn too_small() -> Self {
|
||||||
Self {
|
Self {
|
||||||
// implied - negative() is used for the other
|
// implied - negative() is used for the other
|
||||||
|
|
|
@ -202,10 +202,12 @@ const INPUT_STRING_LEN_LIMIT: usize = 50;
|
||||||
|
|
||||||
/// Parses a decimal string in the given denomination into a satoshi value and a
|
/// Parses a decimal string in the given denomination into a satoshi value and a
|
||||||
/// [`bool`] indicator for a negative amount.
|
/// [`bool`] indicator for a negative amount.
|
||||||
|
///
|
||||||
|
/// The `bool` is only needed to distinguish -0 from 0.
|
||||||
fn parse_signed_to_satoshi(
|
fn parse_signed_to_satoshi(
|
||||||
mut s: &str,
|
mut s: &str,
|
||||||
denom: Denomination,
|
denom: Denomination,
|
||||||
) -> Result<(bool, u64), InnerParseError> {
|
) -> Result<(bool, SignedAmount), InnerParseError> {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
return Err(InnerParseError::MissingDigits(MissingDigitsError {
|
return Err(InnerParseError::MissingDigits(MissingDigitsError {
|
||||||
kind: MissingDigitsKind::Empty,
|
kind: MissingDigitsKind::Empty,
|
||||||
|
@ -237,7 +239,7 @@ fn parse_signed_to_satoshi(
|
||||||
let last_n = precision_diff.unsigned_abs().into();
|
let last_n = precision_diff.unsigned_abs().into();
|
||||||
if let Some(position) = is_too_precise(s, last_n) {
|
if let Some(position) = is_too_precise(s, last_n) {
|
||||||
match s.parse::<i64>() {
|
match s.parse::<i64>() {
|
||||||
Ok(0) => return Ok((is_negative, 0)),
|
Ok(0) => return Ok((is_negative, SignedAmount::ZERO)),
|
||||||
_ =>
|
_ =>
|
||||||
return Err(InnerParseError::TooPrecise(TooPreciseError {
|
return Err(InnerParseError::TooPrecise(TooPreciseError {
|
||||||
position: position + usize::from(is_negative),
|
position: position + usize::from(is_negative),
|
||||||
|
@ -252,14 +254,14 @@ fn parse_signed_to_satoshi(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut decimals = None;
|
let mut decimals = None;
|
||||||
let mut value: u64 = 0; // as satoshis
|
let mut value: i64 = 0; // as satoshis
|
||||||
for (i, c) in s.char_indices() {
|
for (i, c) in s.char_indices() {
|
||||||
match c {
|
match c {
|
||||||
'0'..='9' => {
|
'0'..='9' => {
|
||||||
// Do `value = 10 * value + digit`, catching overflows.
|
// Do `value = 10 * value + digit`, catching overflows.
|
||||||
match 10_u64.checked_mul(value) {
|
match 10_i64.checked_mul(value) {
|
||||||
None => return Err(InnerParseError::Overflow { is_negative }),
|
None => return Err(InnerParseError::Overflow { is_negative }),
|
||||||
Some(val) => match val.checked_add(u64::from(c as u8 - b'0')) {
|
Some(val) => match val.checked_add(i64::from(c as u8 - b'0')) {
|
||||||
None => return Err(InnerParseError::Overflow { is_negative }),
|
None => return Err(InnerParseError::Overflow { is_negative }),
|
||||||
Some(val) => value = val,
|
Some(val) => value = val,
|
||||||
},
|
},
|
||||||
|
@ -295,13 +297,18 @@ fn parse_signed_to_satoshi(
|
||||||
// Decimally shift left by `max_decimals - decimals`.
|
// Decimally shift left by `max_decimals - decimals`.
|
||||||
let scale_factor = max_decimals - decimals.unwrap_or(0);
|
let scale_factor = max_decimals - decimals.unwrap_or(0);
|
||||||
for _ in 0..scale_factor {
|
for _ in 0..scale_factor {
|
||||||
value = match 10_u64.checked_mul(value) {
|
value = match 10_i64.checked_mul(value) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return Err(InnerParseError::Overflow { is_negative }),
|
None => return Err(InnerParseError::Overflow { is_negative }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((is_negative, value))
|
let mut ret =
|
||||||
|
SignedAmount::from_sat(value).map_err(|_| InnerParseError::Overflow { is_negative })?;
|
||||||
|
if is_negative {
|
||||||
|
ret = -ret;
|
||||||
|
}
|
||||||
|
Ok((is_negative, ret))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -10,13 +10,15 @@ use core::{default, fmt};
|
||||||
#[cfg(feature = "arbitrary")]
|
#[cfg(feature = "arbitrary")]
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
|
|
||||||
use super::error::{ParseAmountErrorInner, ParseErrorInner};
|
use super::error::ParseErrorInner;
|
||||||
use super::{
|
use super::{
|
||||||
parse_signed_to_satoshi, split_amount_and_denomination, Amount, Denomination, Display,
|
parse_signed_to_satoshi, split_amount_and_denomination, Amount, Denomination, Display,
|
||||||
DisplayStyle, OutOfRangeError, ParseAmountError, ParseError,
|
DisplayStyle, OutOfRangeError, ParseAmountError, ParseError,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod encapsulate {
|
mod encapsulate {
|
||||||
|
use super::OutOfRangeError;
|
||||||
|
|
||||||
/// A signed amount.
|
/// A signed amount.
|
||||||
///
|
///
|
||||||
/// The [`SignedAmount`] type can be used to express Bitcoin amounts that support arithmetic and
|
/// The [`SignedAmount`] type can be used to express Bitcoin amounts that support arithmetic and
|
||||||
|
@ -50,12 +52,18 @@ mod encapsulate {
|
||||||
pub struct SignedAmount(i64);
|
pub struct SignedAmount(i64);
|
||||||
|
|
||||||
impl SignedAmount {
|
impl SignedAmount {
|
||||||
|
/// The maximum value of an amount.
|
||||||
|
pub const MAX: Self = Self(21_000_000 * 100_000_000);
|
||||||
|
/// The minimum value of an amount.
|
||||||
|
pub const MIN: Self = Self(-21_000_000 * 100_000_000);
|
||||||
|
|
||||||
/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
|
/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
|
||||||
///
|
///
|
||||||
/// Caller to guarantee that `satoshi` is within valid range.
|
/// Accepts an `i32` which is guaranteed to be in range for the type, but which can only
|
||||||
///
|
/// represent roughly -21.47 to 21.47 BTC.
|
||||||
/// See [`Self::MIN`] and [`Self::MAX`].
|
pub const fn from_sat_i32(satoshi: i32) -> SignedAmount {
|
||||||
pub const fn from_sat_unchecked(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
|
SignedAmount(satoshi as i64) // cannot use i64::from in a constfn
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the number of satoshis in this [`SignedAmount`].
|
/// Gets the number of satoshis in this [`SignedAmount`].
|
||||||
///
|
///
|
||||||
|
@ -66,6 +74,31 @@ mod encapsulate {
|
||||||
/// assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
|
/// assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn to_sat(self) -> i64 { self.0 }
|
pub const fn to_sat(self) -> i64 { self.0 }
|
||||||
|
|
||||||
|
/// Constructs a new [`SignedAmount`] from the given number of satoshis.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `satoshi` is outside of valid range (see [`Self::MAX_MONEY`]).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitcoin_units::{amount, SignedAmount};
|
||||||
|
/// # let sat = -100_000;
|
||||||
|
/// let amount = SignedAmount::from_sat(sat)?;
|
||||||
|
/// assert_eq!(amount.to_sat(), sat);
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
|
/// ```
|
||||||
|
pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
|
||||||
|
if satoshi < Self::MIN.to_sat() {
|
||||||
|
Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
|
||||||
|
} else if satoshi > Self::MAX_MONEY.to_sat() {
|
||||||
|
Err(OutOfRangeError { is_signed: true, is_greater_than_max: true })
|
||||||
|
} else {
|
||||||
|
Ok(Self(satoshi))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
@ -73,44 +106,15 @@ pub use encapsulate::SignedAmount;
|
||||||
|
|
||||||
impl SignedAmount {
|
impl SignedAmount {
|
||||||
/// The zero amount.
|
/// The zero amount.
|
||||||
pub const ZERO: Self = SignedAmount::from_sat_unchecked(0);
|
pub const ZERO: Self = SignedAmount::from_sat_i32(0);
|
||||||
/// Exactly one satoshi.
|
/// Exactly one satoshi.
|
||||||
pub const ONE_SAT: Self = SignedAmount::from_sat_unchecked(1);
|
pub const ONE_SAT: Self = SignedAmount::from_sat_i32(1);
|
||||||
/// Exactly one bitcoin.
|
/// Exactly one bitcoin.
|
||||||
pub const ONE_BTC: Self = SignedAmount::from_sat_unchecked(100_000_000);
|
pub const ONE_BTC: Self = SignedAmount::from_btc_i16(1);
|
||||||
/// Exactly fifty bitcoin.
|
/// Exactly fifty bitcoin.
|
||||||
pub const FIFTY_BTC: Self = SignedAmount::from_sat_unchecked(50 * 100_000_000);
|
pub const FIFTY_BTC: Self = SignedAmount::from_btc_i16(50);
|
||||||
/// The maximum value allowed as an amount. Useful for sanity checking.
|
/// The maximum value allowed as an amount. Useful for sanity checking.
|
||||||
pub const MAX_MONEY: Self = SignedAmount::from_sat_unchecked(21_000_000 * 100_000_000);
|
pub const MAX_MONEY: Self = Self::MAX;
|
||||||
/// The minimum value of an amount.
|
|
||||||
pub const MIN: Self = SignedAmount::from_sat_unchecked(-21_000_000 * 100_000_000);
|
|
||||||
/// The maximum value of an amount.
|
|
||||||
pub const MAX: Self = SignedAmount::MAX_MONEY;
|
|
||||||
|
|
||||||
/// Constructs a new [`SignedAmount`] from the given number of satoshis.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// If `satoshi` is outside of valid range (see [`Self::MAX_MONEY`]).
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use bitcoin_units::{amount, SignedAmount};
|
|
||||||
/// # let sat = -100_000;
|
|
||||||
/// let amount = SignedAmount::from_sat(sat)?;
|
|
||||||
/// assert_eq!(amount.to_sat(), sat);
|
|
||||||
/// # Ok::<_, amount::OutOfRangeError>(())
|
|
||||||
/// ```
|
|
||||||
pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
|
|
||||||
if satoshi < Self::MIN.to_sat() {
|
|
||||||
Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
|
|
||||||
} else if satoshi > Self::MAX_MONEY.to_sat() {
|
|
||||||
Err(OutOfRangeError { is_signed: true, is_greater_than_max: true })
|
|
||||||
} else {
|
|
||||||
Ok(SignedAmount::from_sat_unchecked(satoshi))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
|
/// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
|
||||||
///
|
///
|
||||||
|
@ -134,27 +138,21 @@ impl SignedAmount {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
|
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// If `whole_bitcoin` is greater than `21_000_000`.
|
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn from_int_btc<T: Into<i32>>(whole_bitcoin: T) -> Result<SignedAmount, OutOfRangeError> {
|
pub fn from_int_btc<T: Into<i16>>(whole_bitcoin: T) -> SignedAmount {
|
||||||
SignedAmount::from_int_btc_const(whole_bitcoin.into())
|
SignedAmount::from_btc_i16(whole_bitcoin.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]
|
/// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]
|
||||||
/// in const context.
|
/// in const context.
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// If `whole_bitcoin` is greater than `21_000_000`.
|
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub const fn from_int_btc_const(whole_bitcoin: i32) -> Result<SignedAmount, OutOfRangeError> {
|
pub const fn from_btc_i16(whole_bitcoin: i16) -> SignedAmount {
|
||||||
let btc = whole_bitcoin as i64; // Can't call `into` in const context.
|
let btc = whole_bitcoin as i64; // Can't call `into` in const context.
|
||||||
match btc.checked_mul(100_000_000) {
|
let sats = btc * 100_000_000;
|
||||||
Some(amount) => SignedAmount::from_sat(amount),
|
|
||||||
None => panic!("cannot overflow in i64"),
|
match SignedAmount::from_sat(sats) {
|
||||||
|
Ok(amount) => amount,
|
||||||
|
Err(_) => panic!("unreachable - 65536 BTC is within range"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,17 +165,9 @@ impl SignedAmount {
|
||||||
///
|
///
|
||||||
/// If the amount is too big (positive or negative) or too precise.
|
/// If the amount is too big (positive or negative) or too precise.
|
||||||
pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAmountError> {
|
pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAmountError> {
|
||||||
match parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(true))? {
|
parse_signed_to_satoshi(s, denom)
|
||||||
// (negative, amount)
|
.map(|(_, amount)| amount)
|
||||||
(false, sat) if sat > SignedAmount::MAX.to_sat() as u64 => Err(ParseAmountError(
|
.map_err(|error| error.convert(true))
|
||||||
ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true)),
|
|
||||||
)),
|
|
||||||
(false, sat) => Ok(SignedAmount::from_sat_unchecked(sat as i64)), // Cast ok, value in this arm does not wrap.
|
|
||||||
(true, sat) if sat > SignedAmount::MIN.to_sat().unsigned_abs() => Err(
|
|
||||||
ParseAmountError(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small())),
|
|
||||||
),
|
|
||||||
(true, sat) => Ok(SignedAmount::from_sat_unchecked(-(sat as i64))), // Cast ok, value in this arm does not wrap.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
|
/// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
|
||||||
|
|
|
@ -102,8 +102,10 @@ fn from_str_zero_without_denomination() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_int_btc() {
|
fn from_int_btc() {
|
||||||
let amt = Amount::from_int_btc_const(2).unwrap();
|
let amt = Amount::from_btc_u16(2);
|
||||||
assert_eq!(sat(200_000_000), amt);
|
assert_eq!(sat(200_000_000), amt);
|
||||||
|
let amt = SignedAmount::from_btc_i16(-2);
|
||||||
|
assert_eq!(ssat(-200_000_000), amt);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -646,9 +648,9 @@ fn from_str() {
|
||||||
fn to_from_string_in() {
|
fn to_from_string_in() {
|
||||||
use super::Denomination as D;
|
use super::Denomination as D;
|
||||||
let ua_str = Amount::from_str_in;
|
let ua_str = Amount::from_str_in;
|
||||||
let ua_sat = Amount::from_sat_unchecked;
|
let ua_sat = |n| Amount::from_sat(n).unwrap();
|
||||||
let sa_str = SignedAmount::from_str_in;
|
let sa_str = SignedAmount::from_str_in;
|
||||||
let sa_sat = SignedAmount::from_sat_unchecked;
|
let sa_sat = |n| SignedAmount::from_sat(n).unwrap();
|
||||||
|
|
||||||
assert_eq!("0.5", ua_sat(50).to_string_in(D::Bit));
|
assert_eq!("0.5", ua_sat(50).to_string_in(D::Bit));
|
||||||
assert_eq!("-0.5", sa_sat(-50).to_string_in(D::Bit));
|
assert_eq!("-0.5", sa_sat(-50).to_string_in(D::Bit));
|
||||||
|
|
|
@ -17,6 +17,8 @@ use super::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod encapsulate {
|
mod encapsulate {
|
||||||
|
use super::OutOfRangeError;
|
||||||
|
|
||||||
/// An amount.
|
/// An amount.
|
||||||
///
|
///
|
||||||
/// The [`Amount`] type can be used to express Bitcoin amounts that support arithmetic and
|
/// The [`Amount`] type can be used to express Bitcoin amounts that support arithmetic and
|
||||||
|
@ -50,10 +52,18 @@ mod encapsulate {
|
||||||
pub struct Amount(u64);
|
pub struct Amount(u64);
|
||||||
|
|
||||||
impl Amount {
|
impl Amount {
|
||||||
|
/// The maximum value of an amount.
|
||||||
|
pub const MAX: Self = Self(21_000_000 * 100_000_000);
|
||||||
|
/// The minimum value of an amount.
|
||||||
|
pub const MIN: Self = Self(0);
|
||||||
|
|
||||||
/// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
|
/// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
|
||||||
///
|
///
|
||||||
/// Caller to guarantee that `satoshi` is within valid range. See [`Self::MAX`].
|
/// Accepts an `u32` which is guaranteed to be in range for the type, but which can only
|
||||||
pub const fn from_sat_unchecked(satoshi: u64) -> Amount { Self(satoshi) }
|
/// represent roughly 0 to 42.95 BTC.
|
||||||
|
pub const fn from_sat_u32(satoshi: u32) -> Amount {
|
||||||
|
Amount(satoshi as u64) // cannot use u64::from in a constfn
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the number of satoshis in this [`Amount`].
|
/// Gets the number of satoshis in this [`Amount`].
|
||||||
///
|
///
|
||||||
|
@ -64,6 +74,29 @@ mod encapsulate {
|
||||||
/// assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
|
/// assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn to_sat(self) -> u64 { self.0 }
|
pub const fn to_sat(self) -> u64 { self.0 }
|
||||||
|
|
||||||
|
/// Constructs a new [`Amount`] from the given number of satoshis.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `satoshi` is outside of valid range (greater than [`Self::MAX_MONEY`]).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitcoin_units::{amount, Amount};
|
||||||
|
/// # let sat = 100_000;
|
||||||
|
/// let amount = Amount::from_sat(sat)?;
|
||||||
|
/// assert_eq!(amount.to_sat(), sat);
|
||||||
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
|
/// ```
|
||||||
|
pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
|
||||||
|
if satoshi > Self::MAX_MONEY.to_sat() {
|
||||||
|
Err(OutOfRangeError { is_signed: false, is_greater_than_max: true })
|
||||||
|
} else {
|
||||||
|
Ok(Self(satoshi))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
@ -71,45 +104,18 @@ pub use encapsulate::Amount;
|
||||||
|
|
||||||
impl Amount {
|
impl Amount {
|
||||||
/// The zero amount.
|
/// The zero amount.
|
||||||
pub const ZERO: Self = Amount::from_sat_unchecked(0);
|
pub const ZERO: Self = Amount::from_sat_u32(0);
|
||||||
/// Exactly one satoshi.
|
/// Exactly one satoshi.
|
||||||
pub const ONE_SAT: Self = Amount::from_sat_unchecked(1);
|
pub const ONE_SAT: Self = Amount::from_sat_u32(1);
|
||||||
/// Exactly one bitcoin.
|
/// Exactly one bitcoin.
|
||||||
pub const ONE_BTC: Self = Amount::from_sat_unchecked(100_000_000);
|
pub const ONE_BTC: Self = Amount::from_btc_u16(1);
|
||||||
/// Exactly fifty bitcoin.
|
/// Exactly fifty bitcoin.
|
||||||
pub const FIFTY_BTC: Self = Amount::from_sat_unchecked(50 * 100_000_000);
|
pub const FIFTY_BTC: Self = Amount::from_btc_u16(50);
|
||||||
/// The maximum value allowed as an amount. Useful for sanity checking.
|
/// The maximum value allowed as an amount. Useful for sanity checking.
|
||||||
pub const MAX_MONEY: Self = Amount::from_sat_unchecked(21_000_000 * 100_000_000);
|
pub const MAX_MONEY: Self = Amount::MAX;
|
||||||
/// The minimum value of an amount.
|
|
||||||
pub const MIN: Self = Amount::ZERO;
|
|
||||||
/// The maximum value of an amount.
|
|
||||||
pub const MAX: Self = Amount::MAX_MONEY;
|
|
||||||
/// The number of bytes that an amount contributes to the size of a transaction.
|
/// The number of bytes that an amount contributes to the size of a transaction.
|
||||||
pub const SIZE: usize = 8; // Serialized length of a u64.
|
pub const SIZE: usize = 8; // Serialized length of a u64.
|
||||||
|
|
||||||
/// Constructs a new [`Amount`] from the given number of satoshis.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// If `satoshi` is outside of valid range (greater than [`Self::MAX_MONEY`]).
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use bitcoin_units::{amount, Amount};
|
|
||||||
/// # let sat = 100_000;
|
|
||||||
/// let amount = Amount::from_sat(sat)?;
|
|
||||||
/// assert_eq!(amount.to_sat(), sat);
|
|
||||||
/// # Ok::<_, amount::OutOfRangeError>(())
|
|
||||||
/// ```
|
|
||||||
pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
|
|
||||||
if satoshi > Self::MAX_MONEY.to_sat() {
|
|
||||||
Err(OutOfRangeError { is_signed: false, is_greater_than_max: true })
|
|
||||||
} else {
|
|
||||||
Ok(Self::from_sat_unchecked(satoshi))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
|
/// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
@ -132,27 +138,21 @@ impl Amount {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
|
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// If `whole_bitcoin` is greater than `21_000_000`.
|
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn from_int_btc<T: Into<u32>>(whole_bitcoin: T) -> Result<Amount, OutOfRangeError> {
|
pub fn from_int_btc<T: Into<u16>>(whole_bitcoin: T) -> Amount {
|
||||||
Amount::from_int_btc_const(whole_bitcoin.into())
|
Amount::from_btc_u16(whole_bitcoin.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`]
|
/// Converts from a value expressing a whole number of bitcoin to an [`Amount`]
|
||||||
/// in const context.
|
/// in const context.
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// If `whole_bitcoin` is greater than `21_000_000`.
|
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub const fn from_int_btc_const(whole_bitcoin: u32) -> Result<Amount, OutOfRangeError> {
|
pub const fn from_btc_u16(whole_bitcoin: u16) -> Amount {
|
||||||
let btc = whole_bitcoin as u64; // Can't call `into` in const context.
|
let btc = whole_bitcoin as u64; // Can't call `into` in const context.
|
||||||
match btc.checked_mul(100_000_000) {
|
let sats = btc * 100_000_000;
|
||||||
Some(amount) => Amount::from_sat(amount),
|
|
||||||
None => panic!("cannot overflow a u64"),
|
match Amount::from_sat(sats) {
|
||||||
|
Ok(amount) => amount,
|
||||||
|
Err(_) => panic!("unreachable - 65536 BTC is within range"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,19 +165,14 @@ impl Amount {
|
||||||
///
|
///
|
||||||
/// If the amount is too precise, negative, or greater than 21,000,000.
|
/// If the amount is too precise, negative, or greater than 21,000,000.
|
||||||
pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
|
pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
|
||||||
let (negative, sats) =
|
let (is_neg, amount) =
|
||||||
parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(false))?;
|
parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(false))?;
|
||||||
if negative {
|
if is_neg {
|
||||||
return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
|
return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
|
||||||
OutOfRangeError::negative(),
|
OutOfRangeError::negative(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
if sats > Self::MAX.to_sat() {
|
Self::try_from(amount).map_err(|e| ParseAmountError(ParseAmountErrorInner::OutOfRange(e)))
|
||||||
return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
|
|
||||||
OutOfRangeError::too_big(false),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Ok(Self::from_sat_unchecked(sats))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
|
/// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
|
||||||
|
@ -398,8 +393,10 @@ impl Amount {
|
||||||
|
|
||||||
/// Converts to a signed amount.
|
/// Converts to a signed amount.
|
||||||
#[rustfmt::skip] // Moves code comments to the wrong line.
|
#[rustfmt::skip] // Moves code comments to the wrong line.
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn to_signed(self) -> SignedAmount {
|
pub fn to_signed(self) -> SignedAmount {
|
||||||
SignedAmount::from_sat_unchecked(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range.
|
SignedAmount::from_sat(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range.
|
||||||
|
.expect("range of Amount is within range of SignedAmount")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,7 +216,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_div_by_weight() {
|
fn fee_rate_div_by_weight() {
|
||||||
let fee_rate = Amount::from_sat_unchecked(329) / Weight::from_wu(381);
|
let fee_rate = Amount::from_sat_u32(329) / Weight::from_wu(381);
|
||||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
|
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ mod tests {
|
||||||
|
|
||||||
let fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
|
let fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
|
||||||
let weight = Weight::from_vb(3).unwrap();
|
let weight = Weight::from_vb(3).unwrap();
|
||||||
assert_eq!(fee_rate.to_fee(weight).unwrap(), Amount::from_sat_unchecked(6));
|
assert_eq!(fee_rate.to_fee(weight).unwrap(), Amount::from_sat_u32(6));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -237,7 +237,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.checked_mul_by_weight(weight)
|
.checked_mul_by_weight(weight)
|
||||||
.expect("expected Amount");
|
.expect("expected Amount");
|
||||||
assert_eq!(Amount::from_sat_unchecked(100), fee);
|
assert_eq!(Amount::from_sat_u32(100), fee);
|
||||||
|
|
||||||
let fee = FeeRate::from_sat_per_kwu(10).checked_mul_by_weight(Weight::MAX);
|
let fee = FeeRate::from_sat_per_kwu(10).checked_mul_by_weight(Weight::MAX);
|
||||||
assert!(fee.is_none());
|
assert!(fee.is_none());
|
||||||
|
@ -245,14 +245,14 @@ mod tests {
|
||||||
let weight = Weight::from_vb(3).unwrap();
|
let weight = Weight::from_vb(3).unwrap();
|
||||||
let fee_rate = FeeRate::from_sat_per_vb(3).unwrap();
|
let fee_rate = FeeRate::from_sat_per_vb(3).unwrap();
|
||||||
let fee = fee_rate.checked_mul_by_weight(weight).unwrap();
|
let fee = fee_rate.checked_mul_by_weight(weight).unwrap();
|
||||||
assert_eq!(Amount::from_sat_unchecked(9), fee);
|
assert_eq!(Amount::from_sat_u32(9), fee);
|
||||||
|
|
||||||
let weight = Weight::from_wu(381);
|
let weight = Weight::from_wu(381);
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(864);
|
let fee_rate = FeeRate::from_sat_per_kwu(864);
|
||||||
let fee = weight.checked_mul_by_fee_rate(fee_rate).unwrap();
|
let fee = weight.checked_mul_by_fee_rate(fee_rate).unwrap();
|
||||||
// 381 * 0.864 yields 329.18.
|
// 381 * 0.864 yields 329.18.
|
||||||
// The result is then rounded up to 330.
|
// The result is then rounded up to 330.
|
||||||
assert_eq!(fee, Amount::from_sat_unchecked(330));
|
assert_eq!(fee, Amount::from_sat_u32(330));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -260,7 +260,7 @@ mod tests {
|
||||||
fn multiply() {
|
fn multiply() {
|
||||||
let two = FeeRate::from_sat_per_vb(2).unwrap();
|
let two = FeeRate::from_sat_per_vb(2).unwrap();
|
||||||
let three = Weight::from_vb(3).unwrap();
|
let three = Weight::from_vb(3).unwrap();
|
||||||
let six = Amount::from_sat_unchecked(6);
|
let six = Amount::from_sat_u32(6);
|
||||||
|
|
||||||
assert_eq!(two * three, six.into());
|
assert_eq!(two * three, six.into());
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ mod tests {
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn amount_div_by_fee_rate() {
|
fn amount_div_by_fee_rate() {
|
||||||
// Test exact division
|
// Test exact division
|
||||||
let amount = Amount::from_sat_unchecked(1000);
|
let amount = Amount::from_sat_u32(1000);
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
||||||
let weight = amount / fee_rate;
|
let weight = amount / fee_rate;
|
||||||
assert_eq!(weight, Weight::from_wu(500_000));
|
assert_eq!(weight, Weight::from_wu(500_000));
|
||||||
|
@ -288,7 +288,7 @@ mod tests {
|
||||||
assert_eq!(weight_ref3, Weight::from_wu(500_000));
|
assert_eq!(weight_ref3, Weight::from_wu(500_000));
|
||||||
|
|
||||||
// Test truncation behavior
|
// Test truncation behavior
|
||||||
let amount = Amount::from_sat_unchecked(1000);
|
let amount = Amount::from_sat_u32(1000);
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(3);
|
let fee_rate = FeeRate::from_sat_per_kwu(3);
|
||||||
let weight = amount / fee_rate;
|
let weight = amount / fee_rate;
|
||||||
// 1000 * 1000 = 1,000,000 msats
|
// 1000 * 1000 = 1,000,000 msats
|
||||||
|
|
Loading…
Reference in New Issue