Skip to content

Pool-solvency property tests (#33) and rent-aware TTL management (#35) - #89

Open
Spagero763 wants to merge 2 commits into
Vaultquest:mainfrom
Spagero763:feat/solvency-tests-and-storage
Open

Pool-solvency property tests (#33) and rent-aware TTL management (#35)#89
Spagero763 wants to merge 2 commits into
Vaultquest:mainfrom
Spagero763:feat/solvency-tests-and-storage

Conversation

@Spagero763

Copy link
Copy Markdown
Contributor

Summary

Closes #33
Closes #35

Two contract tasks — pool-solvency property tests (#33) and rent-aware TTL management (#35) — plus the build repair both depend on.

First, a build repair (the "CLI issue")

main's Soroban workspace did not compile, so every contracts CI check (fmt, clippy, test, cost budget, wasm) was red. A bad merge had dropped definitions while leaving their users in place. The first commit restores them minimally, preserving every feature already on main (the commit/reveal raffle, threshold governance, the NAV share vault):

  • ProposalStatus enum (referenced by Proposal.status).
  • 15 Error variants used across the share-vault, sweep, and governance code (MathOverflow, InvalidThreshold, BootstrapComplete, StaleEpoch, the Proposal* lifecycle errors, the Withdrawal*/Insufficient* vault errors, …).
  • 9 #72 share-vault DataKey variants.
  • The use soroban_sdk::xdr::ToXdr import behind signer-set and draw-seed hashing.
  • proxy::propose_upgrade had drifted to 11 parameters (over Soroban's 10-arg limit); restored the MigrationCheck struct the tests already pass to bundle it back to 9.
  • An untyped GovernanceEpoch read that inferred to (), latent clippy findings that -D warnings rejects, and a missing Cargo.lock (without it, soroban-env-host 21 resolves ed25519-dalek 3.0.0, whose CryptoRng bound ChaCha20Rng doesn't satisfy — pinned to 2.x).
  • Re-measured cost_thresholds.txt; the old baseline predated the participant-registry / signer-set-hash work and could never be validated while the crate didn't build.

All 93 pre-existing contract tests pass after the repair.

#33 — state-machine property tests and fuzzing for pool solvency

model.rs is an implementation-independent reference model of the pool's observable state and the outcome of every solvency entrypoint — no Env, no storage, no auth, so it is a genuine independent oracle.

model_test.rs generates constrained command sequences across four actors and ledger time and replays each command against both the model and the real contract, asserting after every step:

  • Agreement — model and contract agree on accept/reject and on the returned value.
  • Rejected calls leave state unchanged — after any rejection the contract's observable state is byte-for-byte identical.
  • Invariants on real state — conservation (total_deposited equals the sum of current participants' principal, checked on-chain too), non-negative balances, claimable <= deposited, multiplier within its tier, one-time withdrawal, reentrancy lock released.

proptest shrinks any failure to a minimal trace and prints the seed; PROPTEST_CASES raises the budget for a nightly run. Seeded traces pin the required cases: repeated claim, lock expiry, one-time withdrawal, join-before-create, and arithmetic overflow.

What it found: deposit accounting used unchecked i128 addition (a panic/abort on overflow rather than a clean error). Now guarded with checked_add returning MathOverflow, so a deposit can never wrap a participant's principal or the pool total.

Scope: the model covers the balance/solvency entrypoints (create, join, deposit, deposit_with_duration, claim, withdraw). The multisig-governance and raffle-draw state machines are their own machines with dedicated tests in test.rs.

#35 — rent-aware TTL management and verifiable state archival

ttl.rs is the pure decision layer a sweeper (on-chain or off-chain) drives:

  • Classification — a KeyClass policy table (ActiveCritical / Pending / Historical / Reconstructible) fixing each key's minimum TTL, bump target, and archive rule in one auditable place.
  • Bounded extensions, no rent exhaustiondecide returns Skip/Extend/Archive purely from policy and remaining life; every extension is clamped to a hard ceiling, and only approved classes are auto-bumpable, so no untrusted key can draw rent. Extend is idempotent (extend_ttl only raises TTL), so a crashed sweep is safe to re-run.
  • Checkpointed batch sweeperplan_batch is entry-bounded and its plan is a pure function of (total, cursor, budget), so a duplicated or retried sweep repeats work rather than skipping keys; the cursor wraps for a perpetual self-resuming loop. is_sweep_stale is the alert that fires before an active entry can expire.
  • Verifiable archive — settled records fold into a hash chain (genesis/fold/verify/contains_at); anyone can recompute the root to check integrity, and a restore tool replays records against the committed root to reconstruct.

Documented in docs/POOL_SOLVENCY_TESTS.md and docs/TTL_MANAGEMENT.md.

Verification

cargo fmt --all -- --check                                  # clean
cargo clippy --workspace --all-targets --all-features -- -D warnings   # 0
cargo test --workspace --all-targets --all-features         # 113 passed
./scripts/measure_costs.sh                                  # cost budget passes
cargo build --workspace --target wasm32v1-none --release    # builds

20 new tests (8 solvency + 12 TTL); 113 total. The diff is 11 files and touches no test_snapshots (those tracked files are left exactly as they are on main).

The Soroban workspace did not build on main, which blocked every contracts
CI check (fmt, clippy, test, cost budget, wasm). A bad merge had dropped
definitions while leaving their users in place:

- ProposalStatus enum (Pending/Executed/Cancelled/Expired), referenced by
  Proposal.status and the approve/cancel flow.
- Fifteen Error variants used across the share-vault, sweep, and governance
  code (MathOverflow, InvalidThreshold, BootstrapComplete, StaleEpoch, the
  Proposal* lifecycle errors, the Withdrawal*/Insufficient* vault errors, ...).
- Nine Vaultquest#72 share-vault DataKey variants (VaultShares, ShareBalance, ...).
- The `use soroban_sdk::xdr::ToXdr` import behind the signer-set and draw-seed
  hashing.

All restored minimally, preserving every feature already on main (the
commit/reveal raffle, threshold governance, the NAV share vault).

Also fixed to get green:
- proxy::propose_upgrade had drifted to 11 parameters, over Soroban's 10-arg
  limit. Restored the MigrationCheck struct the tests already pass to bundle
  plan_hash/state_hash/compatible back to 9 args.
- execute_upgrade compared signer_epoch against an untyped storage read that
  inferred to (); annotated it u32.
- Latent clippy findings that -D warnings rejects (needless u32 casts, a
  len()==0, an index-into-slice loop).
- No Cargo.lock was committed, so soroban-env-host 21 resolved ed25519-dalek
  3.0.0 whose CryptoRng bound ChaCha20Rng does not satisfy. Pinned to 2.x and
  committed the lockfile for reproducible builds.
- Re-measured cost_thresholds.txt against the now-compiling contract; the old
  baseline predated the participant-registry and signer-set-hash work and
  could never be validated while the crate did not build.

All 93 existing contract tests pass; fmt, clippy -D warnings, cost budget, and
the wasm release build are green.
…anagement (Vaultquest#35)

Vaultquest#33 — state-machine property tests and fuzzing for pool solvency.
model.rs is an implementation-independent reference model of the pool's
observable state and the outcome of every solvency entrypoint (no Env,
storage, or auth). model_test.rs generates constrained command sequences
across four actors and ledger time and replays each against both the model
and the real contract, asserting after every step that they agree on
accept/reject and return value, that a rejected call leaves state unchanged,
and that the invariants hold on the contract's actual state: conservation
(total_deposited equals the sum of current participants' principal),
non-negative balances, claimable bounded by principal, multiplier within its
tier, one-time withdrawal, and reentrancy lock released. proptest shrinks any
failure to a minimal trace and prints the seed; PROPTEST_CASES raises the
budget for nightly. Seeded traces cover repeated claim, lock expiry, one-time
withdrawal, join-before-create, and arithmetic overflow — the last surfaced
that deposit accounting used unchecked i128 addition, now guarded with
checked_add returning MathOverflow (see the repair commit).

Vaultquest#35 — rent-aware TTL management and verifiable state archival.
ttl.rs is the pure decision layer: a KeyClass policy table with per-class
minimum TTL, bounded extension targets clamped to a hard ceiling, and an
approved-classes gate that blocks rent-exhaustion abuse; a checkpointed batch
sweeper whose plan is a pure function of (total, cursor, budget) so a
duplicated or retried sweep repeats work rather than skipping keys, with a
staleness alert that fires before an active entry can expire; and a
hash-chained archive (genesis/fold/verify/contains_at) whose root anyone can
recompute to verify integrity and from which records can be reconstructed.

Both modules are documented in docs/POOL_SOLVENCY_TESTS.md and
docs/TTL_MANAGEMENT.md. 20 new tests (8 solvency + 12 TTL); the full contract
suite is 113 tests, with fmt, clippy -D warnings, cost budget, and the wasm
release build all green.
Copilot AI review requested due to automatic review settings July 28, 2026 02:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@BigDella

Copy link
Copy Markdown

Maintainer review: this PR currently has merge conflicts with main, so it cannot be merged yet. Please update this branch with the latest main, resolve the conflicts carefully, and push the resolution. Lint, Typecheck, Test, Build, Docs and Dependency Audit are also failing; please address them after resolving the conflicts, then reply here when GitHub reports the PR as mergeable and green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants