test(fuzz): extend fuzz coverage to escrow_contract and threshold_window - #22
Merged
Meshmulla merged 1 commit intoJul 27, 2026
Conversation
Adds deterministic, seed-driven fuzz tests for the two critical contracts that previously had no fuzz coverage beyond the relay contract, mirroring the pattern in soroban/tests/relay_contract_fuzz.rs rather than any implementation detail of the contracts under test: - soroban/tests/threshold_window_fuzz.rs: create_window is fuzzed across valid/invalid window_id, length, and threshold_bps combinations via catch_unwind (threshold_window's functions panic directly rather than returning Result), asserting every panic message is one of the module's known validation messages; evaluate_threshold is fuzzed across signed reference/current value pairs and checked against an independently computed expected deviation; and a dedicated test drives window creation to the MAX_WINDOWS cap and back down through removal, checking the window list stays consistent throughout. - escrow_contract/tests/escrow_contract_fuzz.rs: create_escrow is fuzzed across invalid amounts (zero, negative, i128::MAX to exercise the overflow-safe checked_mul fee path) and oversized metadata via try_create_escrow; release_escrow is fuzzed to confirm released_amount never exceeds the releasable total and that a fully released escrow can never be released or refunded again; batch_release is fuzzed across uniform escrows to confirm they stay in lockstep without any one escrow being overdrawn. Iteration count defaults to a small, CI-cheap value and is overridable via the FUZZ_ITERATIONS env var for longer local campaigns; verified at FUZZ_ITERATIONS=3000 with no failures. Wires a new bounded "fuzz" CI job (.github/workflows/ci.yml) that runs the relay, threshold_window, and escrow_contract fuzz suites with a capped iteration count on every push/PR. Closes stellar-kracken#5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #5
Extends fuzz testing beyond the relay contract to the two other critical contracts called out in the issue:
escrow_contractandthreshold_window. Both new suites follow the existing pattern insoroban/tests/relay_contract_fuzz.rs(deterministic, seed-driven pseudo-fuzzing via a small xorshift PRNG, no new dependencies), but the invariants asserted are built around the guarantees each module is supposed to provide, not around mirroring the relay tests' implementation details — per the guidance in the issue thread.soroban/tests/threshold_window_fuzz.rsthreshold_window's functions are plain (non-contract) entrypoints that validate input viapanic!rather than returningResult, so these tests usecatch_unwindand assert every panic is one of the module's known validation messages — anything else would indicate a real bug.fuzz_create_window_validates_inputs_without_unexpected_panics— fuzzeswindow_id/length/threshold_bpsacross valid and invalid combinations (empty id, zero length, out-of-range bps), asserting classification coverage and that any panic is a known one.fuzz_evaluate_threshold_matches_expected_deviation_and_never_panics— fuzzes signedreference_value/current_valuepairs (bounded to avoid overflowing the module's own unchecked arithmetic) and checks the returned deviation/breach classification against an independently computed expected value.fuzz_max_windows_cap_and_removal_keep_state_consistent— drives window creation to theMAX_WINDOWScap and back down through a pseudo-random removal pattern, checking the window list stays consistent throughout.escrow_contract/tests/escrow_contract_fuzz.rsAll calls go through
try_*client methods, so a host-level panic surfaces asErr(Err(_))instead of crashing the test process — treated as a bug rather than a classification.fuzz_create_escrow_classifies_invalid_amount_and_metadata_without_panicking— fuzzes amount (zero, negative,i128::MAXto exercise the overflow-safechecked_mulfee path) and metadata length (including the 512-byte boundary), asserting each lands in the correctEscrowErrorbucket.fuzz_release_never_exceeds_balance_and_blocks_after_finalization— confirmsreleased_amountnever exceeds the releasable total (no negative/over-released balances) and that a fully released escrow can never be released or refunded again (no invalid state transitions).fuzz_batch_release_keeps_uniform_escrows_in_lockstep_without_overdraw— confirms a batch of uniform escrows stays in lockstep and no escrow is ever overdrawn.CI wiring
Iteration count defaults to a small value and is overridable via
FUZZ_ITERATIONSfor longer local campaigns (documented in each file's header). Adds a new boundedfuzzjob to.github/workflows/ci.ymlthat runs the relay, threshold_window, and escrow_contract fuzz suites with a capped iteration count (FUZZ_ITERATIONS=200) on every push/PR, keeping it fast while still exercising all three suites continuously.No bugs were found in the existing contract logic during this work, so there are no follow-up issues to file per the acceptance criteria.
Testing/validation performed
All performed locally against a fresh stable Rust toolchain (matching what CI installs):
cargo fmt --all -- --check— passescargo clippy --all-targets --all-features -- -D warnings— passes (no warnings)cargo build --release --target wasm32-unknown-unknown— passescargo test(full workspace) — all tests pass, including the 6 new fuzz testsFUZZ_ITERATIONS=3000 cargo test --test threshold_window_fuzz --test escrow_contract_fuzz— all pass, verifying the "longer local campaign" path is not just documented but actually correct at scaleIssue
#5