Skip to content

fix(H3): per-share floor (mid-term flows cannot dilute protection) - #12

Merged
jayeshy14 merged 1 commit into
mainfrom
fix/h3-per-share-floor
Jul 18, 2026
Merged

jayeshy14 merged 1 commit into
mainfrom
fix/h3-per-share-floor

Conversation

@jayeshy14

Copy link
Copy Markdown
Owner

Audit finding H3 (High) — the deepest issue

protectedAmount was stored as a whole-vault aggregate, frozen once at term start (termStartNav x P), but settleEpoch mints/burns shares mid-term as routine keeper operation and nothing rescaled it. So the thing that matters to a holder, the per-share floor (protectedAmount / totalSupply), drifted freely:

  • A mid-term deposit settling took every existing holder's per-share floor from 90% to 45%, ballooned the cushion, and let ordinary sub-1/m moves breach the floor with no attacker and no big gap — exactly the non-price mechanism the core safety claim forbids.
  • A mid-term redemption left the aggregate protectedAmount unchanged against a smaller NAV, forcing a phantom shortfall and full de-risk of the survivors.

Root tension: capital protection is a per-share promise stored over a variable supply.

Fix

Store and compute the floor per share:

  • FloorPolicy.State: protectedPerShareWad, hwmNavPerShareWad, lastFloorPerShareWad; currentFloor/previewFloor operate on navPerShare; the Step ratchet raises the per-share protected amount.
  • CPPIController: startTerm/assess/settleTerm take share supply, initialize from nav/supply, and reconstruct the aggregate floor = floorPerShare * supply for the cushion/target/drift math. New views protectedPerShareWad(), protectedAmount(supply), and lastFloorAggregate/lastFloor(); startTerm/assess revert on zero supply.
  • CPPIVault: passes totalSupply() through.

Per-share protection is now invariant to mint/burn, so mid-term flows can neither dilute existing holders' floor nor manufacture a phantom shortfall.

Tests

2 new regression tests (mid-term deposit preserves per-share floor; mid-term redeem gives zero shortfall at maturity) plus all existing floor/controller/integration/invariant/crash suites updated to the supply-threaded signatures. Suite 114 -> 116; 9/9 fork tests green locally.

Audit H3 (deepest finding): protectedAmount was a whole-vault aggregate
frozen at term start, but settleEpoch mints/burns shares mid-term as
routine keeper op. Nothing rescaled it, so per-share protection
(protectedAmount/supply) drifted: a mid-term deposit silently halved
every holder's floor (90% -> 45%) and a mid-term redeem forced a
phantom shortfall + full de-risk, breaking the core promise with no
attacker and no >1/m gap.

Fix: FloorPolicy now stores protectedPerShareWad/hwmNavPerShareWad/
lastFloorPerShareWad and operates on navPerShare; the controller threads
share supply through startTerm/assess/settleTerm and reconstructs the
aggregate floor = floorPerShare * supply. Per-share protection is now
invariant to mint/burn. Added controller.protectedPerShareWad(),
protectedAmount(supply), lastFloorAggregate; startTerm/assess revert on
zero supply. Regression tests: mid-term deposit preserves per-share
floor; mid-term redeem yields zero shortfall at maturity. 114 -> 116.
@jayeshy14
jayeshy14 merged commit 2020c30 into main Jul 18, 2026
3 checks passed
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.

1 participant