proptest (property-based testing) — already a dev-dependency in contracts/stellar-save/Cargo.toml. Runs under stable Rust with cargo test, no nightly or external toolchain required.
Echidna and Foundry are EVM-specific and do not apply to Soroban/Rust contracts. cargo-fuzz (libFuzzer) requires nightly and a separate build target; proptest provides equivalent coverage for invariant testing with simpler CI integration.
contracts/stellar-save/src/fuzz_tests.rs — registered as a #[cfg(test)] module in lib.rs.
- Positive amounts are stored exactly (no mutation)
- Zero or negative amounts always panic (enforced by
assert!) - Summing valid amounts never overflows
i128
- Terminal states (
Completed,Cancelled) reject all transitions Pendingcannot skip toPausedorCompletedActivecan reachPaused,Completed, andCancelledPausedcannot jump directly toCompleted
min_contribution > max_contribution→ invalidmin_members < 2→ invalidmin_contribution == 0→ invalid- Well-formed config always passes
validate()
- Payout position bounds invariant
- Pool arithmetic:
payout_per_cycle == contribution_amount × member_count - Cycle number lifecycle invariant
# Default (256 cases per test)
cargo test --manifest-path contracts/stellar-save/Cargo.toml --lib fuzz_tests
# Extended run (10 000 cases)
PROPTEST_CASES=10000 cargo test --manifest-path contracts/stellar-save/Cargo.toml --lib fuzz_tests.github/workflows/fuzzing.yml runs on:
- Every push/PR touching
contracts/ - Nightly schedule at 02:00 UTC (10 000 cases)
- Manual
workflow_dispatchwith configurable case count
| Trigger | Cases | Timeout |
|---|---|---|
| Push / PR | 256 | 30 min |
| Nightly | 10 000 | 30 min |
| Manual | configurable | 30 min |
When proptest finds a failing input it writes a minimal reproduction to contracts/stellar-save/.proptest-regressions/. This directory is uploaded as a CI artifact and should be committed to the repo so failures are always replayed on future runs.
Add new property tests to fuzz_tests.rs following the pattern:
proptest! {
#[test]
fn prop_my_invariant(input in my_strategy()) {
// arrange + act
prop_assert!(invariant_holds);
}
}Focus on:
- Arithmetic overflow/underflow in payout calculations
- State transition edge cases
- Boundary values for
max_members,max_cycle_duration - Multi-member contribution ordering independence