fix: mutants harness

This commit is contained in:
Roman 2026-06-22 12:22:05 +08:00
parent 9c431c98ac
commit 3b2665994c
No known key found for this signature in database
GPG Key ID: 583BDF43C238B83E
4 changed files with 62 additions and 6 deletions

View File

@ -74,6 +74,21 @@ pub fn arbitrary_fuzz_state(u: &mut Unstructured<'_>) -> arbitrary::Result<Vec<F
.collect()
}
/// Reduce raw fuzzer draws into a *biased-valid* `(nonce, amount)` pair.
///
/// The nonce is mapped into `0..=3` (near the genesis value) and the amount into
/// `0..=balance`, so the success path is actually reached. Extracted as a pure
/// function so the reduction arithmetic is unit-testable.
pub(crate) fn biased_valid_nonce_amount(
nonce_byte: u8,
amount_raw: u128,
balance: u128,
) -> (u128, u128) {
let nonce = u128::from(nonce_byte) % 4; // 0..=3
let amount = amount_raw % balance.saturating_add(1); // 0..=balance
(nonce, amount)
}
/// Generate a native-transfer [`LeeTransaction`] between two accounts chosen
/// from `accounts`.
///
@ -102,9 +117,7 @@ pub fn arb_fuzz_native_transfer(
let (nonce, amount) = if bool::arbitrary(u)? {
// Biased valid: nonce near the genesis value, amount within balance.
let nonce = u128::from(u8::arbitrary(u)?) % 4; // 0..=3
let amount = u128::arbitrary(u)? % from.balance.saturating_add(1); // 0..=balance
(nonce, amount)
biased_valid_nonce_amount(u8::arbitrary(u)?, u128::arbitrary(u)?, from.balance)
} else {
// Adversarial: full range drives the rejection paths.
(u128::arbitrary(u)?, u128::arbitrary(u)?)

View File

@ -1,5 +1,5 @@
mod arbitrary_types_test;
mod generators_test;
mod arbitrary_types;
mod generators;
mod invariants;
mod privacy;
mod replay_proptest;

View File

@ -4,7 +4,8 @@ use arbitrary::Unstructured;
use nssa::{AccountId, PrivateKey};
use crate::generators::{
FuzzAccount, arb_fuzz_native_transfer, arbitrary_fuzz_state, signer_account_ids, test_accounts,
FuzzAccount, arb_fuzz_native_transfer, arbitrary_fuzz_state, biased_valid_nonce_amount,
signer_account_ids, test_accounts,
};
/// Verifies that `signer_account_ids` returns a **non-empty** list for a properly signed
@ -133,3 +134,45 @@ fn native_transfer_index_uses_modulo_not_div_add() {
mutation: `% accounts.len()` replaced by `/ accounts.len()` or `+ accounts.len()`"
);
}
#[test]
fn biased_nonce_is_always_in_genesis_range() {
// Every possible nonce byte must reduce into 0..=3. This rules out the
// `/` and `+` variants of the `% 4` reduction, which escape that range.
for byte in 0..=u8::MAX {
let (nonce, _) = biased_valid_nonce_amount(byte, 0, 0);
assert!(
nonce <= 3,
"byte {byte} produced out-of-range nonce {nonce}"
);
}
}
#[test]
fn biased_nonce_wraps_modulo_four() {
// Pin specific residues so `/ 4` (→1, →63) and `+ 4` (→8, →259) both fail.
assert_eq!(biased_valid_nonce_amount(4, 0, 0).0, 0);
assert_eq!(biased_valid_nonce_amount(255, 0, 0).0, 3);
assert_eq!(biased_valid_nonce_amount(7, 0, 0).0, 3);
}
#[test]
fn biased_amount_never_exceeds_balance() {
for balance in [0_u128, 1, 100, u128::MAX] {
for amount_raw in [0_u128, 1, balance, balance.wrapping_add(1), u128::MAX] {
let (_, amount) = biased_valid_nonce_amount(0, amount_raw, balance);
assert!(
amount <= balance,
"amount {amount} exceeded balance {balance} (raw {amount_raw})"
);
}
}
}
#[test]
fn biased_amount_wraps_modulo_balance_plus_one() {
// `10 % 101 == 10` but `10 / 101 == 0`, so this kills the `/` variant.
assert_eq!(biased_valid_nonce_amount(0, 10, 100).1, 10);
// balance 0 → modulus 1 → amount always 0.
assert_eq!(biased_valid_nonce_amount(0, u128::MAX, 0).1, 0);
}