Skip to content

test(security): chaos failure-injection lifecycle test (#468) + large-history treasury load test (#467) - #514

Merged
misrasamuelisiguzor-oss merged 2 commits into
WHEELBACK:mainfrom
Tisan1000:test/treasury-load-and-lifecycle-chaos
Aug 31, 2026
Merged

test(security): chaos failure-injection lifecycle test (#468) + large-history treasury load test (#467)#514
misrasamuelisiguzor-oss merged 2 commits into
WHEELBACK:mainfrom
Tisan1000:test/treasury-load-and-lifecycle-chaos

Conversation

@Tisan1000

Copy link
Copy Markdown
Contributor

Closes #467. Closes #468.

What this delivers

#468 — chaos-style failure-injection test across the full settlement lifecycle

contracts/treasury/tests/lifecycle_chaos_test.rs

Replays issue #83's full end-to-end lifecycle
(create_invoice → mark_paid → propose_settlement → approve_settlement → compliance.is_allowed → execute_settlement → release_escrow) once per
cross-contract call boundary, deliberately forcing exactly that one boundary to
fail each time, parameterized by a FailurePoint enum rather than rewriting the
flow:

Injection How it fails
ComplianceGate merchant never allowed → is_allowed returns false
ExecuteThresholdNotMet second approval skipped → execute_settlementThresholdNotMet
ExecuteTokenNotAllowed non-empty allowlist without the settlement token → TokenNotAllowed
ExecuteSettlementOnHold dispute raised first → execute_settlementSettlementOnHold
ReleaseEscrow invoice contract paused → release_escrowContractPaused
None control — the happy path still completes

After every run it asserts the multi-contract system is left consistent,
whichever boundary failed:

  1. Token conservation — treasury_balance + merchant_balance == minted total
  2. All-or-nothing payout — merchant holds the whole amount or nothing
  3. Merchant paid iff settlement reached Executed
  4. Settlement status is always Pending / OnHold / Executed — never a limbo value
  5. Invoice status is always Paid or Released — a failed release_escrow leaves it retryable, not wedged
  6. Escrow released only when the settlement executed

The ReleaseEscrow case additionally proves recovery: unpause + retry completes
the lifecycle. No inconsistent state was found at any boundary.

#467 — large-scale load test: treasury with 1000+ historical settlements

contracts/treasury/tests/large_history_load_test.rs

#97 (settlement_pagination_test.rs) covers pagination correctness, but its
largest real corpus is 50 settlements. This builds a treasury with 1000+
settlements and records measured instruction costs for the key read/scan
entrypoints at that scale:

  • get_pending_settlements_page stays correct at 1000+ entries — first page,
    deep mid-history page, window overrunning the end, start past the end,
    executed entries interspersed and skipped.
  • Cost vs history size — whole-history scan for a 50-entry tail page,
    measured at 500 and 1000 settlements (~2x data, ~3x cost on the test
    host — linear scan with per-read overhead that itself grows slowly; a loose
    runaway-regression bound, plus the numbers are printed with --nocapture).
  • resolve_dispute cost measured with 1000+ settlements as background load.

Finding (documented, not silently patched)

get_pending_settlements_page early-breaks once the requested page is full, so
a shallow page is cheap (~854K instr.) while a deep page over a 1000-entry
history costs ~O(start + limit) and approaches a full scan (~102M instr.,
~120× the shallow cost). Paginating a UI to the end of a large settlement
history is therefore not cheap — the same "cost scales with total accumulated
history" shape as the resolve_dispute concern in resolve_dispute_dos_test.rs.
page_scan_cost_grows_with_offset_depth pins the current behaviour.
Suggested follow-up: maintain a compacted pending-settlement index so
get_pending_settlements_page is O(limit) regardless of offset.

Prerequisite repair (first commit)

main did not compile — three of the four contract crates referenced
symbols that were never added or were dropped in a merge. The first commit
restores them so tests under contracts/treasury/tests/ can build:

Verification

  • cargo build --workspace — passes
  • cargo clippy --workspace -- -D warnings — passes
  • cargo test -p comebackhere-treasury — passes (all 38 test files, incl. the
    two new ones: 12 tests), except the pre-existing failures below

Pre-existing breakage NOT addressed here (unrelated to #467/#468)

These were already broken on main (masked by the crates not compiling); left
for separate follow-ups:

  1. contracts/treasury/tests/record_approval_duplicate_benchmark_test.rs (3 tests) — the test assumes propose_settlement doesn't pre-record the proposer's approval; the implementation does.
  2. tests/tests/release_escrow_settlement_ordering_test.rs::settlement_proposed_before_release_still_executes_correctly_aftercreate_invoice(5_000_000, 5_100_000, …) is rejected with AmountPrecision.
  3. contracts/settlement-workflow/tests/settlement_workflow_test.rs — needs a SettlementWorkflowError type + pause/unpause that were reverted out of the lib.
  4. contracts/settlement-workflow/tests/divergent_admin_test.rs — not Soroban code (tokio/chrono/HashMap); has never compiled.
  5. cargo fmt --all -- --check — 26 files carry pre-existing formatting drift.

🤖 Generated with Claude Code

Tisan1000 and others added 2 commits August 29, 2026 13:15
…lement-workflow)

Several feature PRs landed on main with code that references symbols that
were never added (or were dropped in a later merge), leaving three of the
four contract crates unable to compile. This restores the missing pieces
so the workspace builds again, which is a prerequisite for adding any new
tests under contracts/treasury/tests/ (issues WHEELBACK#467 / WHEELBACK#468).

crates/multisig:
  - Append the three TreasuryError variants that landed code already
    depends on but the enum never gained (append-only, per
    scripts/check-enum-ordering.sh):
      WithdrawalLimitExceeded = 35  (WHEELBACK#455, referenced by the withdrawal
        velocity limit)
      InvalidSplitRatio       = 36  (WHEELBACK#456, panicked by resolve_dispute_split)
      ForceCancelNotAllowed   = 37  (WHEELBACK#457, panicked by force_cancel_settlement)

contracts/compliance:
  - Add DataKey::LastBulkAllow / LastBulkBlock, keyed per admin, which the
    bulk-op cooldown code (WHEELBACK#454) already reads and writes.

contracts/treasury:
  - deposits.rs: fix DataKey::Balance(..) call in deposit_one that passed
    one key component instead of (holder, token_contract), and add the
    missing enforce_withdrawal_limit helper (no-op when unset/<=0, rolling
    per-address window otherwise) that withdraw_all already calls.
  - disputes.rs: resolve_dispute was missing its trailing Ok(()); add it.
    resolve_dispute_split transferred the split but never marked the
    dispute ResolvedSplit / recorded claimant_share_bps / emitted
    dispute_resolved_split / released the settlement hold - complete it to
    match its own doc comment so a split resolution does not leave the
    settlement stuck OnHold forever.

contracts/settlement-workflow:
  - Merge the two conflicting `enum DataKey` definitions (ComplianceId /
    TreasuryId from WHEELBACK#364, ExecutedSettlements from WHEELBACK#373) into one.
  - Re-add the `multisig` dependency (dropped in a merge) that src/lib.rs
    needs for `use multisig::TreasuryError`; it carries no #[contractimpl]
    so it links no foreign wasm exports.

tests (fallout of the enum change):
  - multisig_version_lock_test.rs: pin the new discriminants (34..=37) and
    add the new variants to the exhaustive match.
  - multisig_quorum_property_test.rs: proptest's prop_assert_eq! message
    arg does not support inline format captures; pass them positionally.

cargo build --workspace, cargo clippy --workspace -- -D warnings, and the
full treasury test suite pass. Pre-existing breakage NOT addressed here
(unrelated to WHEELBACK#467/WHEELBACK#468): settlement-workflow's two test files, a couple
of stale treasury/integration tests, and repo-wide `cargo fmt --check`
drift across 26 files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1wdxpKibGJsVzbB4EEnMg
… chaos test (WHEELBACK#468)

WHEELBACK#467 - contracts/treasury/tests/large_history_load_test.rs
Builds a treasury instance carrying 1000+ historical settlements and
records measured instruction costs for the key read/scan entrypoints at
that scale, complementing WHEELBACK#97's pagination-correctness coverage (whose
largest real corpus is 50 settlements):
  - get_pending_settlements_page stays correct at 1000+ entries: first
    page, deep mid-history page, window overrunning the end, start past
    the end, and executed entries interspersed and skipped.
  - Cost vs. history size: full-history scan for a 50-entry tail page
    measured at 500 and 1000 settlements; asserts a loose runaway-
    regression bound and prints the numbers.
  - Finding recorded (not asserted away): the page scan early-breaks once
    the page is full, so a deep offset costs ~O(start+limit) and
    approaches a full-history scan - the same "cost scales with total
    accumulated history" shape as resolve_dispute_dos_test.rs. A
    compacted pending index would remove it; filed as a follow-up rather
    than fixed here.
  - resolve_dispute cost measured with 1000+ settlements as background
    load (the dimension the signer-rotation race issue cares about).

WHEELBACK#468 - contracts/treasury/tests/lifecycle_chaos_test.rs
Replays WHEELBACK#83's full lifecycle (create_invoice -> mark_paid ->
propose_settlement -> approve_settlement -> compliance.is_allowed ->
execute_settlement -> release_escrow) once per cross-contract call
boundary, forcing exactly that boundary to fail each time:
  ComplianceGate, ExecuteThresholdNotMet, ExecuteTokenNotAllowed,
  ExecuteSettlementOnHold, ReleaseEscrow, plus a None control.
After every run it asserts the multi-contract system is left consistent:
token conservation, all-or-nothing payout, merchant paid iff settlement
Executed, settlement status never a limbo value, invoice never wedged
(always Paid or Released), and escrow released only when the settlement
executed. The release_escrow case also proves recovery: unpause + retry
completes the lifecycle. No inconsistent state was found.

cargo test -p comebackhere-treasury passes (both files: 12 tests).
cargo clippy --workspace -- -D warnings passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1wdxpKibGJsVzbB4EEnMg
@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

@Tisan1000 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@misrasamuelisiguzor-oss
misrasamuelisiguzor-oss merged commit d0b6367 into WHEELBACK:main Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants