Background / Context
MaintenancePool (contracts/maintenance-pool/src/types.rs:10-17) tracks three related but independently-mutated fields: balance (current spendable amount), total_deposited (lifetime sum of all deposits), and total_withdrawn (lifetime sum of all withdrawals, tracked as the gross amount requested in withdraw, not the net payout after fee deduction — confirm this distinction is intentional by reading withdraw's update at contracts/maintenance-pool/src/lib.rs:145-146: pool.balance -= amount; pool.total_withdrawn += amount;, where amount is the pre-fee gross figure, while payout = amount - fee is what the recipient actually receives).
Problem Statement
The invariant balance == total_deposited - total_withdrawn should hold at every point given the current update logic (both balance and total_withdrawn are updated by the same gross amount), but this is never explicitly asserted anywhere, and — separately — total_withdrawn tracking the gross amount (including the portion that went to treasury as fee, not to the recipient) means total_withdrawn does not represent "total amount paid to maintainers," which could be a misleading/incorrect figure if mergefi-backend or any downstream analytics/reporting surfaces total_withdrawn to sponsors as "how much has been paid out to maintainers from this pool" (a very plausible UX use of this exact field, given the struct's purpose is described as letting "the full contribution history [be] queried" — sponsors likely want equally clear visibility into draw-down history). This needs both a rigorous invariant-fuzzing pass (mirroring the general on-chain-solvency fuzzer tracked separately, but specifically deep on this contract's three-field relationship, including interaction with repeated deposits using varying token-mismatch attempts, which should all cleanly reject without corrupting the running totals) and a product-correctness judgment call on whether total_withdrawn's gross-vs-net semantics need to change or at minimum be clarified.
Requirements
- Build a stateful, sequence-based property test (reusing the general solvency-fuzzer harness from the related issue if it exists/lands, or building a focused standalone one for this contract if not) that runs long randomized sequences of
deposit/withdraw calls (including deliberately-invalid ones expected to be rejected, like mismatched-token deposits and over-balance withdrawals) and asserts pool.balance == pool.total_deposited - pool.total_withdrawn holds after every single successful operation, with zero drift across arbitrarily long sequences.
- Separately, add a field/accessor (or, if changing
total_withdrawn's existing meaning is judged too risky/breaking, a new field) that tracks net amount actually delivered to recipients (sum(payout), i.e., gross minus fees), and document clearly in both the struct's doc comments and the README which of total_withdrawn (gross) vs. the new field (net) answers "how much have maintainers actually been paid."
- Add explicit tests distinguishing gross vs. net accounting across a sequence of withdrawals with a nonzero fee, to lock in and make unmistakable exactly what each field represents going forward.
Acceptance Criteria
Technical Notes / Hints
contracts/maintenance-pool/src/lib.rs:128-146 is the entire fee/payout/bookkeeping logic in withdraw — the gross-vs-net distinction is compact and easy to verify by inspection, but easy to overlook as a "just a documentation issue" when it actually has real reporting-accuracy implications for a field whose name (total_withdrawn) strongly implies "money that left the pool for maintainers."
Deposit records (types.rs:19-25) are per-deposit; consider whether a symmetric per-withdrawal record (there currently is none — only the aggregate total_withdrawn/balance fields track withdrawals, unlike deposits which get individual Deposit entries) is also worth adding as part of this work for full audit-trail parity, or should be scoped as a separate follow-up.
Difficulty Justification
Combines rigorous stateful invariant-fuzzing engineering (the mechanically hard part) with a subtle, easy-to-miss semantic/product-correctness issue (gross vs. net accounting in a field name that implies something more specific than what it actually tracks) that requires careful reading of exact arithmetic rather than surface-level code review to even notice.
Background / Context
MaintenancePool(contracts/maintenance-pool/src/types.rs:10-17) tracks three related but independently-mutated fields:balance(current spendable amount),total_deposited(lifetime sum of all deposits), andtotal_withdrawn(lifetime sum of all withdrawals, tracked as the grossamountrequested inwithdraw, not the netpayoutafter fee deduction — confirm this distinction is intentional by readingwithdraw's update atcontracts/maintenance-pool/src/lib.rs:145-146:pool.balance -= amount; pool.total_withdrawn += amount;, whereamountis the pre-fee gross figure, whilepayout = amount - feeis what the recipient actually receives).Problem Statement
The invariant
balance == total_deposited - total_withdrawnshould hold at every point given the current update logic (bothbalanceandtotal_withdrawnare updated by the same grossamount), but this is never explicitly asserted anywhere, and — separately —total_withdrawntracking the gross amount (including the portion that went totreasuryas fee, not to therecipient) meanstotal_withdrawndoes not represent "total amount paid to maintainers," which could be a misleading/incorrect figure ifmergefi-backendor any downstream analytics/reporting surfacestotal_withdrawnto sponsors as "how much has been paid out to maintainers from this pool" (a very plausible UX use of this exact field, given the struct's purpose is described as letting "the full contribution history [be] queried" — sponsors likely want equally clear visibility into draw-down history). This needs both a rigorous invariant-fuzzing pass (mirroring the general on-chain-solvency fuzzer tracked separately, but specifically deep on this contract's three-field relationship, including interaction with repeateddeposits using varyingtoken-mismatch attempts, which should all cleanly reject without corrupting the running totals) and a product-correctness judgment call on whethertotal_withdrawn's gross-vs-net semantics need to change or at minimum be clarified.Requirements
deposit/withdrawcalls (including deliberately-invalid ones expected to be rejected, like mismatched-token deposits and over-balance withdrawals) and assertspool.balance == pool.total_deposited - pool.total_withdrawnholds after every single successful operation, with zero drift across arbitrarily long sequences.total_withdrawn's existing meaning is judged too risky/breaking, a new field) that tracks net amount actually delivered to recipients (sum(payout), i.e., gross minus fees), and document clearly in both the struct's doc comments and the README which oftotal_withdrawn(gross) vs. the new field (net) answers "how much have maintainers actually been paid."Acceptance Criteria
balance/total_deposited/total_withdrawninvariant, including rejected-operation interleavingstotal_withdrawnexplicitly clarified via doc comments, README, and if warranted, a new tracked net-payout fieldcargo test --workspacepassestotal_withdrawn's existing meaning unless explicitly justified and called out as intentionalTechnical Notes / Hints
contracts/maintenance-pool/src/lib.rs:128-146is the entire fee/payout/bookkeeping logic inwithdraw— the gross-vs-net distinction is compact and easy to verify by inspection, but easy to overlook as a "just a documentation issue" when it actually has real reporting-accuracy implications for a field whose name (total_withdrawn) strongly implies "money that left the pool for maintainers."Depositrecords (types.rs:19-25) are per-deposit; consider whether a symmetric per-withdrawal record (there currently is none — only the aggregatetotal_withdrawn/balancefields track withdrawals, unlike deposits which get individualDepositentries) is also worth adding as part of this work for full audit-trail parity, or should be scoped as a separate follow-up.Difficulty Justification
Combines rigorous stateful invariant-fuzzing engineering (the mechanically hard part) with a subtle, easy-to-miss semantic/product-correctness issue (gross vs. net accounting in a field name that implies something more specific than what it actually tracks) that requires careful reading of exact arithmetic rather than surface-level code review to even notice.