Fuzz Testing Guide
This document describes the fuzz testing infrastructure for the AgenC Coordination Protocol.
Fuzz Testing Guide
This document describes the fuzz testing infrastructure for the AgenC Coordination Protocol.
Overview
The fuzz testing suite uses property-based testing with proptest to verify protocol invariants as documented in THREAT_MODEL.md.
Why Property-Based Testing?
Traditional cargo-fuzz (libFuzzer) doesn't work well with Anchor/Solana programs because:
- Requires LLVM instrumentation incompatible with BPF target
- Can't run on-chain code directly
- Limited integration with Anchor's account validation
Property-based testing provides:
Directory Structure
programs/agenc-coordination/fuzz/
βββ Cargo.toml # Fuzz crate configuration
βββ src/
β βββ lib.rs # Library root
β βββ arbitrary.rs # Input generators
β βββ invariants.rs # Invariant checking functions
β βββ scenarios.rs # Simulation scenarios
β βββ main.rs # Fuzz test runner
βββ fuzz_targets/
βββ mod.rs # Target module declarations
βββ claim_task.rs # claim_task instruction tests
βββ complete_task.rs # complete_task instruction tests
βββ vote_dispute.rs # vote_dispute instruction tests
βββ resolve_dispute.rs # resolve_dispute instruction tests
βββ dependency_graph.rs # Dependent task DAG tests
βββ dispute_lifecycle.rs # Full dispute lifecycle tests
βββ dispute_timing.rs # Dispute deadline and timing tests
βββ task_lifecycle.rs # Full task lifecycle testsRunning Fuzz Tests
Quick Start
cd programs/agenc-coordination/fuzz
# Run all fuzz tests (100 iterations each)
cargo run --release
# Run property-based tests
cargo test --release
# Run specific target
cargo test --release claim_task
# Run with more iterations
PROPTEST_CASES=10000 cargo test --releaseConfiguration
Set environment variables to control test behavior:
| Variable | Default | Description |
|---|---|---|
PROPTEST_CASES | 256 | Number of test cases per property |
PROPTEST_MAX_SHRINK_ITERS | 10000 | Max shrinking iterations |
PROPTEST_VERBOSE | 0 | Verbosity level (0-2) |
Example:
PROPTEST_CASES=5000 PROPTEST_VERBOSE=1 cargo test --releaseTested Invariants
Escrow Invariants (E1-E5)
| ID | Invariant | Tested In |
|---|---|---|
| E1 | Balance Conservation | complete_task, resolve_dispute |
| E2 | Monotonic Distribution | complete_task |
| E3 | Distribution Bounded by Deposit | complete_task, resolve_dispute |
| E4 | Single Closure | complete_task, resolve_dispute |
| E5 | Escrow-Task Binding | Account validation |
Task State Machine Invariants (T1-T5)
| ID | Invariant | Tested In |
|---|---|---|
| T1 | Valid State Transitions | All task instructions |
| T2 | Terminal State Immutability | complete_task |
| T3 | Worker Count Consistency | claim_task |
| T4 | Completion Count Bounded | complete_task |
| T5 | Deadline Enforcement | claim_task |
Reputation Invariants (R1-R4)
| ID | Invariant | Tested In |
|---|---|---|
| R1 | Reputation Bounds (0-10000) | complete_task |
| R2 | Initial Reputation (5000) | register_agent |
| R3 | Increment Rules (+100, capped) | complete_task |
| R4 | Single Application Per Completion | Claim tracking |
Dispute Invariants (D1-D5)
| ID | Invariant | Tested In |
|---|---|---|
| D1 | Dispute State Machine | resolve_dispute |
| D2 | Single Vote Per Arbiter | PDA uniqueness |
| D3 | Voting Window Enforcement | vote_dispute, resolve_dispute |
| D4 | Threshold-Based Resolution | resolve_dispute |
| D5 | Disputable State Requirement | initiate_dispute |
Authority Invariants (A1-A5)
| ID | Invariant | Tested In |
|---|---|---|
| A4 | Arbiter Capability Requirement | vote_dispute |
| S1 | Arbiter Stake Threshold | vote_dispute |
Edge Cases Tested
Arithmetic Boundaries
u64::MAX reward amountsState Boundaries
Race Conditions
Interpreting Results
Success Output
=== AgenC Coordination Protocol Fuzz Testing ===
Running claim_task fuzz tests...
claim_task: 100 passed, 0 failed
Running complete_task fuzz tests...
complete_task: 100 passed, 0 failed
...
=== Fuzz Testing Complete ===
Total tests: 450
Passed: 450
Failed: 0
Duration: 1.234sFailure Output
Running claim_task fuzz tests...
[FAIL] Iteration 42: InvariantViolation("T3: Worker count 6 exceeds max 5")
claim_task: 99 passed, 1 failedProptest Failure
When proptest finds a failure, it shows:
- Minimal failing input (after shrinking)
- Seed for reproduction
- Failure message
thread 'claim_task::fuzz_claim_task' panicked at 'Test failed:
Invariant violation: T3 violated: current_workers 6 > max_workers 5
minimal failing input: ClaimTaskInput {
task_max_workers: 5,
task_current_workers: 5,
...
}
seed: [1, 2, 3, 4, ...]'Reproducing Failures
Save the seed and re-run:
PROPTEST_CASES=1 cargo test --release claim_task -- --nocaptureOr use the proptest-regressions file that's automatically created.
CI Integration
The fuzz tests run in CI on every PR to main. See .github/workflows/ci.yml:
fuzz_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Run fuzz tests
working-directory: programs/agenc-coordination/fuzz
run: |
PROPTEST_CASES=500 cargo test --release
cargo run --releaseCoverage Targets
| Target | Minimum Iterations | CI Iterations |
|---|---|---|
claim_task | 100 | 500 |
complete_task | 100 | 500 |
vote_dispute | 100 | 500 |
resolve_dispute | 100 | 500 |
dependency_graph | 100 | 500 |
dispute_lifecycle | 100 | 500 |
dispute_timing | 100 | 500 |
task_lifecycle | 100 | 500 |
| Edge cases | 10 | 10 |
| Race conditions | 50 | 50 |
Adding New Fuzz Targets
1. Create Input Generator
In src/arbitrary.rs:
#[derive(Debug, Clone)]
pub struct NewInstructionInput {
pub field1: u64,
pub field2: [u8; 32],
}
impl Arbitrary for NewInstructionInput {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
(arb_reward_amount(), arb_id())
.prop_map(|(field1, field2)| NewInstructionInput { field1, field2 })
.boxed()
}
}2. Add Simulation Function
In src/scenarios.rs:
pub fn simulate_new_instruction(
input: &NewInstructionInput,
state: &mut SimulatedState,
) -> SimulationResult {
// Pre-condition checks
if !precondition_met {
return SimulationResult::Error("PreconditionFailed".to_string());
}
// Execute logic
// ...
// Post-condition invariant checks
if invariant_violated {
return SimulationResult::InvariantViolation("E3 violated".to_string());
}
SimulationResult::Success
}3. Create Fuzz Target
In fuzz_targets/new_instruction.rs:
use agenc_coordination_fuzz::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn fuzz_new_instruction(input in any::<NewInstructionInput>()) {
let result = simulate_new_instruction(&input, &mut state);
prop_assert!(!result.is_invariant_violation());
}
}4. Register in `fuzz_targets/mod.rs`
pub mod new_instruction;Troubleshooting
Tests Run Too Slowly
--release flagPROPTEST_CASEScargo flamegraphOut of Memory
Proptest stores history for shrinking. Reduce:
PROPTEST_MAX_SHRINK_ITERS=1000 cargo test --releaseNon-Deterministic Failures
Check for:
Compilation Errors
Ensure you're in the fuzz directory:
cd programs/agenc-coordination/fuzz
cargo build