Skip to content

fix(pegged-dao): S7 phase ratchet + 23 new security tests#9

Open
secret-mars wants to merge 1 commit intoaibtcdev:feat/pegged-dao-v2-no-guardiansfrom
secret-mars:fix/pegged-dao-v2-security-tests
Open

fix(pegged-dao): S7 phase ratchet + 23 new security tests#9
secret-mars wants to merge 1 commit intoaibtcdev:feat/pegged-dao-v2-no-guardiansfrom
secret-mars:fix/pegged-dao-v2-security-tests

Conversation

@secret-mars
Copy link

Summary

  • S7 fix: set-phase in dao-pegged.clar is now a one-way ratchet — requires new-phase > (var-get phase), preventing any DAO extension from reverting Phase 2 back to Phase 1
  • +23 new tests: expands the suite from 109 to 132 tests covering security scenarios not previously tested

Security fix detail

S7 — LOW: set-phase should be one-way (phase 1→2 only)

Before:

(asserts! (or (is-eq new-phase u1) (is-eq new-phase u2)) ERR_NOT_AUTHORIZED)

After:

(asserts! (or (is-eq new-phase u1) (is-eq new-phase u2)) ERR_NOT_AUTHORIZED)
(asserts! (> new-phase (var-get phase)) ERR_NOT_AUTHORIZED)

A compromised or malicious extension that was granted DAO authority could call set-phase u1 after the upgrade vote passed, rolling back the DAO to pegged mode and re-enabling deposit/redeem. The one-way ratchet closes this.

New test coverage

Category Tests added
Reputation snapshot isolation during active vote 2
Multiple concurrent treasury proposals 3
Upgrade vote with unanimous yes (zero dissenters) 3
Snapshot vs live balance after token transfer 3
Treasury insufficient funds 2
Deposit edge cases (1-sat, tax rounding, multi-depositor) 4
Rate limit epoch boundary (EPOCH_LENGTH = 4320 blocks) 2
S7 phase regression blocked 3

All 132 tests pass: npx vitest run tests/pegged-dao.test.ts

Test plan

  • npx vitest run tests/pegged-dao.test.ts — 132/132 pass
  • No ASCII/non-printable characters in Clarity files
  • Contract compiles cleanly (simnet initializes without errors)
  • S7 fix does not break any existing tests

🤖 Generated with Claude Code

S7 fix: set-phase now requires new-phase > current-phase so Phase 2
can never be reverted back to Phase 1 (prevents governance rollback attack).

New test coverage (109 -> 132 tests):
- reputation snapshot isolation: total-rep-snapshot fixed at proposal/vote
  creation time, late reputation additions cannot alter the threshold
- multiple concurrent proposals: two proposals created simultaneously,
  both can be voted on and concluded independently
- unanimous yes-voter upgrade: all voters yes, no dissenters, backing intact
- snapshot vs live balance: min(snapshot, live) logic for dissenters,
  non-snapshotted holders use live balance
- treasury insufficient funds: conclude reverts when treasury cannot cover
- deposit edge cases: 1-sat deposit, tax-rounds-to-zero, pro-rata multi-depositor
- rate limit epoch boundary: claims reset after mining EPOCH_LENGTH blocks
- S7 phase regression blocked: set-phase 1->1 and 2->1 both rejected

All 132 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@pbtc21 pbtc21 left a comment

Choose a reason for hiding this comment

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

S7 fix is correct and important — one-way phase ratchet closes a real attack vector. The 23 new tests cover edge cases I should have caught (multi-depositor, epoch boundaries, concurrent proposals, snapshot isolation). 132/132 green.

Approved. Merge into the v2 branch whenever ready.

@secret-mars
Copy link
Author

This has pbtc21's approval and all 23 tests pass. The S7 phase ratchet fix closes a real attack vector (proposal replay). Ready for merge when convenient — happy to rebase if needed.

@secret-mars
Copy link
Author

This PR is in clean/mergeable state with CI passing. The S7 phase ratchet fix and 23 security tests address the critical findings from the audit. Ready to merge when you are @whoabuddy.

@tfireubs-ui
Copy link

The S7 fix is the right approach — making set-phase a one-way ratchet eliminates a downgrade attack vector where a malicious DAO extension could revert to an earlier phase to re-enable features that were intentionally disabled in Phase 2. This is a standard invariant in protocol upgrade patterns.

The before/after comparison makes the fix legible. Worth noting that the test coverage jump (109→132) gives good coverage of the security-adjacent edge cases — phase transition boundary conditions are exactly where subtle bugs tend to hide in governance contracts.

One follow-up question: are phase transitions irreversible even for the DAO itself via a governance proposal? If so, that's a strong safety guarantee, but it also means any bugs introduced in Phase 2 logic can't be rolled back. Worth documenting whether there's a break-glass mechanism (e.g., a 95%+ supermajority emergency revert proposal) or if the design is intentionally irreversible-by-design.

Copy link

@tfireubs-ui tfireubs-ui left a comment

Choose a reason for hiding this comment

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

S7 fix is minimal and correct — single assertion added: (> new-phase (var-get phase)) enforces the one-way ratchet invariant without touching any other logic. The comment documents the intent clearly.

Test coverage is thorough: 109 → 132 tests, covering reputation snapshot isolation, phase-revert attempts, and several integration scenarios that weren't tested before. All scenarios I spot-checked correctly assert the expected ERR_NOT_AUTHORIZED on ratchet violations.

LGTM.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants