Skip to content

Commit d8a5edc

Browse files
committed
Address new mutants from cargo mutants v25.2.2
This commits adds the exclusions and mutant catches for the cargo mutants upgrade to v25.2.2
1 parent d1b5330 commit d8a5edc

File tree

5 files changed

+94
-17
lines changed

5 files changed

+94
-17
lines changed

.cargo/mutants.toml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,32 @@ exclude_re = [
1010
".*Error",
1111

1212
# ---------------------Crate-specific exculsions---------------------
13-
# Receive
14-
# src/receive/v1/mod.rs
13+
# Timeout loops
14+
# src/receive/v1/mod.rs
1515
"interleave_shuffle", # Replacing index += 1 with index *= 1 in a loop causes a timeout due to an infinite loop
16+
17+
# Trivial mutations
18+
# These exlusions are allowing code blocks to run with artithmetic invloving zero and as a result do nothing
19+
# payjoin/src/core/receive/v1/mod.rs
20+
"replace > with >= in ProvisionalProposal::apply_fee",
21+
# payjoin/src/core/send/mod.rs
22+
"replace < with <= in PsbtContext::check_outputs",
23+
"replace > with >= in PsbtContext::check_fees",
24+
# payjoin/src/core/send/mod.rs
25+
"replace < with <= in SenderBuilder<'a>::build_recommended", # clamping the fee contribution when the fee equals to the recommended fee does not do anything
26+
27+
# Async SystemTime comparison
28+
# checking if the system time is equal to the expiry is difficult to reasonably test
29+
# payjoin/src/core/receive/v2/mod.rs
30+
"replace < with <= in Receiver<Initialized>::apply_unchecked_from_payload",
31+
"replace > with >= in Receiver<Initialized>::create_poll_request",
32+
"replace > with >= in extract_err_req",
33+
# payjoin/src/core/send/v2/mod.rs
34+
"replace > with >= in Sender<WithReplyKey>::create_v2_post_request",
35+
36+
# TODO exclusions
37+
# payjoin/src/core/receive/v1/mod.rs
38+
"replace > with >= in WantsInputs::avoid_uih", # This mutation I am unsure about whether or not it is a trivial mutant and have not decided on how the best way to approach testing it is
39+
# payjoin/src/core/send/mod.rs
40+
"replace match guard proposed_txout.script_pubkey == original_output.script_pubkey with true in PsbtContext::check_outputs", # This non-deterministic mutation has a possible test to catch it
1641
]

payjoin/src/core/receive/multiparty/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,23 @@ mod test {
365365
Ok(())
366366
}
367367

368+
#[test]
369+
fn test_build_multiparty() -> Result<(), BoxError> {
370+
let proposal_one = v2::UncheckedProposal {
371+
v1: multiparty_proposals()[0].clone(),
372+
context: SHARED_CONTEXT.clone(),
373+
};
374+
let proposal_two = v2::UncheckedProposal {
375+
v1: multiparty_proposals()[1].clone(),
376+
context: SHARED_CONTEXT_TWO.clone(),
377+
};
378+
let mut multiparty = UncheckedProposalBuilder::new();
379+
multiparty.add(v2::Receiver { state: proposal_one })?;
380+
multiparty.add(v2::Receiver { state: proposal_two })?;
381+
assert!(multiparty.build().is_ok());
382+
Ok(())
383+
}
384+
368385
#[test]
369386
fn test_duplicate_context_multiparty() -> Result<(), BoxError> {
370387
let proposal_one = v2::UncheckedProposal {

payjoin/src/core/receive/v1/mod.rs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -973,19 +973,25 @@ pub(crate) mod test {
973973
}
974974

975975
#[test]
976-
fn unchecked_proposal_below_min_fee() {
976+
fn unchecked_proposal_min_fee() {
977977
let proposal = unchecked_proposal_from_test_vector();
978+
979+
let min_fee_rate = proposal.psbt_fee_rate().expect("Feerate calculation should not fail");
980+
let _ = proposal
981+
.clone()
982+
.check_broadcast_suitability(Some(min_fee_rate), |_| Ok(true))
983+
.expect("Broadcast suitability check with appropriate min_fee_rate should succeed");
984+
assert_eq!(proposal.clone().psbt_fee_rate().unwrap(), min_fee_rate);
985+
978986
let min_fee_rate = FeeRate::MAX;
979-
match proposal.clone().check_broadcast_suitability(Some(min_fee_rate), |_| Ok(true)) {
980-
Err(ReplyableError::Payload(PayloadError(InternalPayloadError::PsbtBelowFeeRate(
981-
proposal_rate,
982-
min_rate,
983-
)))) => {
984-
assert_eq!(proposal_rate, proposal.clone().psbt_fee_rate().unwrap());
985-
assert_eq!(min_rate, min_fee_rate);
986-
},
987-
_ => panic!("Broadcast suitability check should fail due to being below the min fee rate or unexpected error type"),
988-
};
987+
let expected_err = ReplyableError::Payload(PayloadError(
988+
InternalPayloadError::PsbtBelowFeeRate(proposal.psbt_fee_rate().unwrap(), min_fee_rate),
989+
));
990+
let proposal_below_min_fee = proposal
991+
.clone()
992+
.check_broadcast_suitability(Some(min_fee_rate), |_| Ok(true))
993+
.expect_err("Broadcast suitability with min_fee_rate below minimum should fail");
994+
assert_eq!(proposal_below_min_fee.to_string(), expected_err.to_string());
989995
}
990996

991997
#[test]
@@ -1193,6 +1199,17 @@ pub(crate) mod test {
11931199
[wants_outputs.change_vout]
11941200
.script_pubkey;
11951201

1202+
let output_value =
1203+
wants_outputs.original_psbt.unsigned_tx.output[wants_outputs.change_vout].value;
1204+
let outputs = vec![TxOut { value: output_value, script_pubkey: script_pubkey.clone() }];
1205+
let unchanged_amount =
1206+
wants_outputs.clone().replace_receiver_outputs(outputs, script_pubkey.as_script());
1207+
assert!(
1208+
unchanged_amount.is_ok(),
1209+
"Not touching the receiver output amount is always allowed"
1210+
);
1211+
assert_ne!(wants_outputs.payjoin_psbt, unchanged_amount.unwrap().payjoin_psbt);
1212+
11961213
let output_value =
11971214
wants_outputs.original_psbt.unsigned_tx.output[wants_outputs.change_vout].value
11981215
+ Amount::ONE_SAT;
@@ -1201,7 +1218,7 @@ pub(crate) mod test {
12011218
wants_outputs.clone().replace_receiver_outputs(outputs, script_pubkey.as_script());
12021219
assert!(
12031220
increased_amount.is_ok(),
1204-
"Increasing the receiver output amount should always be allowed"
1221+
"Increasing the receiver output amount is always allowed"
12051222
);
12061223
assert_ne!(wants_outputs.payjoin_psbt, increased_amount.unwrap().payjoin_psbt);
12071224

payjoin/src/core/send/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,16 @@ mod test {
772772
fee_contribution.err(),
773773
Some(InternalBuildSenderError::FeeOutputValueLowerThanFeeContribution)
774774
);
775+
// This thests the max allowed fee contribution of the given input amount
776+
let fee_contribution = determine_fee_contribution(
777+
&PARSED_ORIGINAL_PSBT,
778+
Script::from_bytes(&<Vec<u8> as FromHex>::from_hex(
779+
"0014b60943f60c3ee848828bdace7474a92e81f3fcdd",
780+
)?),
781+
Some((Amount::from_sat(95983068), None)),
782+
false,
783+
);
784+
assert!(fee_contribution.is_ok());
775785
Ok(())
776786
}
777787

payjoin/src/core/send/v1.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,14 @@ mod test {
350350

351351
#[test]
352352
fn process_response_invalid_utf8() {
353-
// In UTF-8, 0xF0 represents the start of a 4-byte sequence, so 0xF0 by itself is invalid
354-
let invalid_utf8 = &[0xF0];
353+
// A PSBT expects an exact match so padding with null bytes for the from_str method is
354+
// invaalid
355+
let mut invalid_utf8_padding = PAYJOIN_PROPOSAL.as_bytes().to_vec();
356+
invalid_utf8_padding
357+
.extend(std::iter::repeat(0x00).take(MAX_CONTENT_LENGTH - invalid_utf8_padding.len()));
355358

356359
let ctx = create_v1_context();
357-
let response = ctx.process_response(invalid_utf8);
360+
let response = ctx.process_response(&invalid_utf8_padding);
358361
match response {
359362
Ok(_) => panic!("Invalid UTF-8 should have caused an error"),
360363
Err(error) => match error {
@@ -389,4 +392,9 @@ mod test {
389392
},
390393
}
391394
}
395+
396+
#[test]
397+
fn test_non_witness_input_weight_const() {
398+
assert_eq!(NON_WITNESS_INPUT_WEIGHT, bitcoin::Weight::from_wu(160));
399+
}
392400
}

0 commit comments

Comments
 (0)