fix: multi-sig admin (#733), on-chain governance (#735), event indexer exactly-once (#753), accounting proptest invariants (#801) - #1088
Merged
joelpeace48-cell merged 2 commits intoJul 29, 2026
Conversation
…-once, accounting invariant tests - FinesseStudioLab#733: Add M-of-N multi-sig admin gate to rewards contract. `init_multisig` configures a threshold signer set; `propose_privileged_op` / `approve_privileged_op` / `execute_privileged_op` require threshold approvals before any privileged op executes. Signer rotation flows through the same proposal gate. New `MultiSigConfig` and `PrivilegedProposal` contract types with full lifecycle events. - FinesseStudioLab#735: Add on-chain governance for parameter changes. `propose_param_change` opens a time-locked proposal; `vote_param_change` accumulates quorum; `execute_param_change` applies the change after delay + quorum; admin can `cancel_param_change` at any time. `ParamProposal` type stores the full lifecycle with events at each step. - FinesseStudioLab#753: Add `pollWithCursor` to the event indexer with durable cursor persistence, per-event exactly-once dedupe, and bounded concurrency backpressure. Cursor is loaded from `indexer_cursors` on startup and written only after the full batch succeeds. `processed_events` table (contract_id + ledger + event_index PK) makes re-playing any ledger range idempotent. A `Semaphore` caps in-flight handlers so bursts do not grow unbounded. Migration 013 adds both tables. Tests cover cursor resume, cursor persistence, dedupe short-circuit, and multi-event batches. - FinesseStudioLab#801: Add three new proptest suites to fuzz_test.rs: `fuzz_global_balance_accounting_invariant` asserts sum(user_balances) == total_credited - total_claimed across multi-user random sequences; `fuzz_no_negative_balances` asserts over-claims are rejected and leave balances unchanged; `fuzz_batch_credit_accounting` asserts per-recipient deltas and batch totals match exactly. Closes FinesseStudioLab#733 Closes FinesseStudioLab#735 Closes FinesseStudioLab#753 Closes FinesseStudioLab#801
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
Backend/Indexer: Backpressure + exactly-once event processing with a durable cursor #753 —
eventIndexer.jspoll()had no cursor persistence, no per-event dedupe, and no backpressure. AddedpollWithCursor()with: a durable cursor stored inindexer_cursors(loaded on startup, written after each batch succeeds); exactly-once processing via aprocessed_eventstable keyed on(contract_id, ledger, event_index)so replaying any ledger range produces zero duplicate rows; and a bounded-concurrencySemaphorethat pauses ingestion when all in-flight slots are occupied. Migration013adds both tables. Five new tests cover cursor resume, cursor save, dedupe short-circuit, batch processing, and return value.Testing: Property/invariant tests for contract accounting (proptest) #801 — Existing proptest suite in
fuzz_test.rscovered single-user balance consistency but not the global accounting invariants from the issue. Added three new proptest suites:fuzz_global_balance_accounting_invariantverifiessum(all_user_balances) == total_credited - total_claimedacross multi-user random sequences and also cross-checks the contract'stotal_claimed()counter;fuzz_no_negative_balancesverifies over-claims are rejected with an error and leave the balance unchanged;fuzz_batch_credit_accountingverifies per-recipient balance deltas and batch totals match exactly afterbatch_credit.Contracts: Multi-sig / threshold admin (both contracts use a single
Address) #733 — Admin authority in both contracts is a singleAddress. Added M-of-N multi-sig gate to the rewards contract:init_multisig(admin only) configures aMultiSigConfigwith asignersset andthreshold;propose_privileged_opopens a proposal (proposer auto-approves);approve_privileged_opaccumulates approvals (prevents double-vote and signer-not-in-set);execute_privileged_opruns onceapprovals.len() >= threshold. Signer rotation flows through the same proposal, so no single key can change the set unilaterally. Full lifecycle events:privprop/privappr/privexec.Contracts: On-chain governance for parameter changes (proposal + voting/delay) #735 — Economic parameters were admin-set instantly. Added governance flow:
propose_param_changeopens aParamProposalwith a quorum requirement and a time-lock delay;vote_param_changeaccumulates votes (idempotent per voter);execute_param_change(admin) applies the change only after delay elapsed and quorum met, returning the(param_key, new_value)pair so the caller can write to the target storage slot;cancel_param_change(admin) vetoes at any time. Events at every lifecycle step:govprp/govvote/govexec/govcanc.Test plan
node --test backend/src/jobs/eventIndexer.test.js— all existing tests pass plus 5 new cursor/dedupe/backpressure tests013on a fresh DB and confirmindexer_cursorsandprocessed_eventstables are createdpollWithCursor— row counts in application tables must be identical both timescargo test -p trivela-rewards-contract— existing tests plus three new proptest suites passfuzz_global_balance_accounting_invariantcatches a hypothetical double-credit bug by mutating the contract and confirming a test failureinit_multisig+propose_privileged_op+approve_privileged_op(threshold - 1 times) →execute_privileged_opreturnsInsufficientApprovals; add the final approval → executespropose_param_change→execute_param_changebefore time-lock elapses →TimeLockActive; advance ledger past delay → succeedsCloses #733
Closes #735
Closes #753
Closes #801