[#554] Add stateful invariant coverage for commitment flows - #556
Open
safal207 wants to merge 1 commit into
Open
[#554] Add stateful invariant coverage for commitment flows#556safal207 wants to merge 1 commit into
safal207 wants to merge 1 commit into
Conversation
…-Org#554) Add a seeded stateful reference-model suite for commitment_core covering create/update/settle/early_exit/allocate/fee flows with per-step model-vs- contract verification, principal/fee/ownership/terminal/atomicity invariants, 40 fixed seeds x 20 commands plus hand-written regression scripts, greedy sequence minimization, and single-seed replay via CGQA_LIFECYCLE_SEED.
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.
Closes #554
Model
contracts/commitment_core/src/lifecycle_model_tests.rsadds a bounded deterministic stateful reference model for the core commitment lifecycle (Soroban, native Rust test suite — no new dependencies).Modeled state mirrors only economically relevant fields of
commitment_core: per-commitment{owner, net principal (amount), current_value, released, expires_at, max_loss, penalty, status}, plus TVL, collected fees, creation-fee bps, ledger time, token custody balances (owners / contract / fee recipient / pool), and owner-index list lengths.Commands are bound 1:1 to the real entrypoints (issue terms → actual API):
create_commitment(owner, amount, asset, rules)update_value(caller, id, new_value)violatedon max-loss breachsettle(id)now >= expires_atearly_exit(id, caller)allocate(caller, id, pool, amount)set_creation_fee_bps/set_fee_recipient/withdraw_feesAdvanceTime{days})The lifecycle graph follows
docs/commitment_core/SEMANTICS.mdexactly:active → {active, violated, settled, early_exit};violated,settled,early_exitare terminal/absorbing.Invalid variants are generated explicitly: wrong state (terminal/nonexistent slots), wrong actor (outsider vs admin/updater/allocator/treasurer/owner), duplicate terminal operations (double settle, repeat exit), boundary amounts (zero/negative, overdraft allocate, excess withdraw), insufficient funding creates, settle-before-expiry.
After every executed command — valid or invalid — the harness re-reads the full observable contract state and compares it against the model (
apply()→verify()), so any divergence is caught at the exact step where it appears.Invariants
token.balance(core) == Σ_slots (amount − released) + collected_fees, wherereleasedaccumulates settle payouts, exit returns+penalties, and allocations. A naive formbalance == Σ current_valuedoes not hold on this codebase because oracle markdowns viaupdate_valueleave principal in custody until payout; the flow form is the exact conservation law. Additionally: global supply conservation — all tracked balances always sum to the minted supply.collected_fees == Σ creation_fees + Σ early-exit penalties − withdrawals; zero-penalty truncation boundary covered.violated/settled/early_exitreject all active-only flows; repeated terminal operations move neither principal nor fees; owner-index removal happens only onsettle(mirrored exactly).Reproducibility
Every failure panics with a report containing:
seed, failing step + reason, the original command sequence, and a greedy minimized sequence (leave-one-out delta debugging over commands). Single-seed replay:No shrinking framework dependency was added (repo has no proptest/arbitrary in this crate; shared_utils' proptest is feature-gated and unused here) — minimization is implemented inline and runs only on the failure path.
ContractGraph-QA
https://github.com/safal207/ContractGraph-QA was used as an independent external lifecycle oracle; it is not a production dependency of this repo (no code, config, or lockfile from it is committed).
Three reachability models were authored strictly from the current production semantics (
lib.rs,SEMANTICS.md) — duplicate settlement, terminal resurrection, fee double-counting. Each yields a deterministic evidence path showing which guard boundary protects the invariant (terminal-status-guard,active-only-entrypoints,fee-accounting; model SHA-256 recorded in output). A negative control with no violated assumptions returnsnot_found_within_bound. Every capability path the oracle flags maps to a native regression script that proves the guard holds against the real contract:regression_duplicate_settle_pays_exactly_onceregression_terminal_states_absorbingregression_fee_accounting_conservationNo counterexample against the real contract was found by either layer.
CI budget
Deterministic and bounded: 40 seeds × 20 commands (800 generated steps, each followed by full-state verification) + 8 hand-written regression scripts + determinism/replay tests. No wall-clock, RNG, or network dependence; identical output on every run. Measured locally: the whole
lifecycle_modelfilter runs in ~50 s; total-p commitment_core --libsuite ~125 s. The suite adds no CI workflow changes and cannot flake (no unseeded randomness).Validation
Real results, Windows MSVC toolchain (
cargo +stable-x86_64-pc-windows-msvc; default GNU toolchain linker on this machine is broken):cargo test -p commitment_core --lib→ 173 passed; 3 failed — the 3 failures are pre-existing on upstream master (tests::test_create_commitment_updates_storage_layoutexpects oldc_0ids vs currentCOMMIT_0;tests::test_create_commitment_event;emergency_tests::test_emergency_mode_toggle_emits_events). Verified identical before my change (baseline run atfb8349e). All 11 new tests pass.cd tests/integration && cargo test→ 124 passed; 0 failed; 2 ignored.cargo fmt --check: repo has extensive pre-existing fmt diffs (not touched);lifecycle_model_tests.rsitself is rustfmt-clean.cargo clippy -p commitment_core --lib --tests: no warnings from the new module.Acceptance criteria
lifecycle_model_seeded_sequences_hold_invariants(40 seeded sequences), checked after every stepregression_wrong_actor_matrix_is_atomic,regression_insufficient_create_is_atomic,regression_settle_ordering_guardsfailure_report()+ greedyminimize()+CGQA_LIFECYCLE_SEEDreplay entrypointDesign choices & tradeoffs
fee_from_bps,loss_percent, penalty) instead of calling intoshared_utils, keeping the oracle independent of the code under test; ranges are chosen to avoid overflow branches, which existing fuzz shapes already cover.released) form — stricter and actually true under markdowns; documented above.Limitations