diff --git a/fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs b/fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs new file mode 100644 index 0000000..8631f5c --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs @@ -0,0 +1,111 @@ +#![no_main] +//! Fuzz target: `V03State::transition_from_program_deployment_transaction`. +//! +//! The deployment path runs `ValidatedStateDiff::from_program_deployment_transaction` +//! followed by `apply_state_diff`, which means it touches WASM bytecode validation, program +//! ID derivation, and program insertion into `V03State::programs` — none of +//! which are exercised elsewhere. +//! +//! # Invariants +//! +//! 1. **NoPanic** — `transition_from_program_deployment_transaction` must never +//! panic on any arbitrary `ProgramDeploymentTransaction`, whether the bytecode +//! is valid WASM or random garbage. It must return `Ok` or `Err`. +//! +//! 2. **BalanceIsolation** — program deployment does not transfer or mint tokens. +//! Every genesis account balance must be identical before and after a +//! deployment call, regardless of whether the transaction succeeds or fails. +//! +//! 3. **StateIsolationOnFailure** — if the call returns `Err`, no genesis account +//! balance or nonce must change. This mirrors the `StateIsolationOnFailure` +//! invariant in `fuzz_state_transition` and ensures the deployment path shares +//! the same atomicity guarantee as the public-transaction path. + +use arbitrary::{Arbitrary, Unstructured}; +use fuzz_props::arbitrary_types::ArbProgramDeploymentTransaction; +use fuzz_props::generators::arbitrary_fuzz_state; +use libfuzzer_sys::fuzz_target; +use nssa::V03State; + +fuzz_target!(|data: &[u8]| { + let mut u = Unstructured::new(data); + + // 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 + .iter() + .map(|a| (a.account_id, a.balance)) + .collect(); + + // Generate an arbitrary program deployment transaction. + let tx_wrap = match ArbProgramDeploymentTransaction::arbitrary(&mut u) { + Ok(w) => w, + Err(_) => return, + }; + let tx = tx_wrap.0; + + let mut state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0); + + // Capture per-account state snapshots before the deployment attempt. + let balances_before: Vec = init_accs + .iter() + .map(|&(id, _)| state.get_account_by_id(id).balance) + .collect(); + let nonces_before: Vec = init_accs + .iter() + .map(|&(id, _)| state.get_account_by_id(id).nonce) + .collect(); + + // ── Invariant 1: NoPanic ────────────────────────────────────────────────── + // The call may return Ok or Err — it must not panic. + let result = state.transition_from_program_deployment_transaction(&tx); + + match result { + Err(_) => { + // ── Invariant 3: StateIsolationOnFailure ────────────────────────── + // On failure, all genesis account balances and nonces must be unchanged. + for (i, &(acc_id, _)) in init_accs.iter().enumerate() { + let bal_after = state.get_account_by_id(acc_id).balance; + assert_eq!( + balances_before[i], + bal_after, + "INVARIANT VIOLATION [StateIsolationOnFailure]: \ + program deployment failure changed balance of account {:?} \ + (before={}, after={})", + acc_id, + balances_before[i], + bal_after, + ); + let nonce_after = state.get_account_by_id(acc_id).nonce; + assert_eq!( + nonces_before[i], + nonce_after, + "INVARIANT VIOLATION [StateIsolationOnFailure]: \ + program deployment failure changed nonce of account {:?}", + acc_id, + ); + } + } + Ok(()) => { + // ── Invariant 2: BalanceIsolation ───────────────────────────────── + // On success, no genesis account balance may change — deployment + // only inserts a new program entry, it does not move tokens. + for (i, &(acc_id, _)) in init_accs.iter().enumerate() { + let bal_after = state.get_account_by_id(acc_id).balance; + assert_eq!( + balances_before[i], + bal_after, + "INVARIANT VIOLATION [BalanceIsolation]: \ + successful program deployment changed balance of account {:?} \ + (before={}, after={}) — deployment must not transfer tokens", + acc_id, + balances_before[i], + bal_after, + ); + } + } + } +});