2026-04-15 15:47:01 +08:00
|
|
|
#![no_main]
|
|
|
|
|
//! Fuzz target: state diff isolation.
|
|
|
|
|
//!
|
|
|
|
|
//! Invariant: `ValidatedStateDiff::from_public_transaction` must only produce
|
|
|
|
|
//! account changes for accounts that appear in `tx.affected_public_account_ids()`.
|
|
|
|
|
//!
|
|
|
|
|
//! A diff that modifies an account outside that set would allow a transaction
|
|
|
|
|
//! to silently corrupt unrelated accounts' balances.
|
2026-05-12 13:18:18 +08:00
|
|
|
//!
|
|
|
|
|
//! The initial state is generated from the fuzz input (rather than a fixed
|
|
|
|
|
//! testnet genesis) so that state-dependent diff bugs — those triggered only by
|
|
|
|
|
//! specific account shapes such as zero balance or `u128::MAX` — are reachable.
|
2026-04-15 15:47:01 +08:00
|
|
|
|
2026-05-12 13:18:18 +08:00
|
|
|
use arbitrary::{Arbitrary, Unstructured};
|
2026-04-15 15:47:01 +08:00
|
|
|
use fuzz_props::arbitrary_types::ArbPublicTransaction;
|
2026-05-12 13:18:18 +08:00
|
|
|
use fuzz_props::generators::arbitrary_fuzz_state;
|
2026-04-15 15:47:01 +08:00
|
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
|
use nssa::{V03State, ValidatedStateDiff};
|
|
|
|
|
|
2026-05-12 13:18:18 +08:00
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
|
|
|
let mut u = Unstructured::new(data);
|
2026-04-15 15:47:01 +08:00
|
|
|
|
2026-05-12 13:18:18 +08:00
|
|
|
// Generate a fuzz-driven initial state.
|
|
|
|
|
let fuzz_accs = match arbitrary_fuzz_state(&mut u) {
|
|
|
|
|
Ok(accs) => accs,
|
|
|
|
|
Err(_) => return,
|
|
|
|
|
};
|
|
|
|
|
let init_accs: Vec<(nssa::AccountId, u128)> = fuzz_accs
|
2026-04-15 15:47:01 +08:00
|
|
|
.iter()
|
|
|
|
|
.map(|a| (a.account_id, a.balance))
|
|
|
|
|
.collect();
|
|
|
|
|
let state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
|
|
|
|
|
2026-05-12 13:18:18 +08:00
|
|
|
// Generate the public transaction from remaining fuzz bytes.
|
|
|
|
|
let pub_tx = match ArbPublicTransaction::arbitrary(&mut u) {
|
|
|
|
|
Ok(w) => w.0,
|
|
|
|
|
Err(_) => return,
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-15 15:47:01 +08:00
|
|
|
// Collect the set of accounts the transaction claims to touch.
|
|
|
|
|
let affected = pub_tx.affected_public_account_ids();
|
|
|
|
|
|
|
|
|
|
match ValidatedStateDiff::from_public_transaction(&pub_tx, &state, 1, 0) {
|
|
|
|
|
Ok(diff) => {
|
|
|
|
|
// INVARIANT: every key in the public diff must be in `affected`.
|
|
|
|
|
let public_diff = diff.public_diff();
|
|
|
|
|
for changed_id in public_diff.keys() {
|
|
|
|
|
assert!(
|
|
|
|
|
affected.contains(changed_id),
|
|
|
|
|
"INVARIANT VIOLATION: diff modified account {:?} which is not in \
|
|
|
|
|
affected_public_account_ids() {:?}",
|
|
|
|
|
changed_id,
|
|
|
|
|
affected
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(_) => {
|
|
|
|
|
// Validation failure is expected for structurally or semantically invalid inputs.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|