Skip to content

fix(H1/H2): high-water-mark performance fee + minimum term - #13

Merged
jayeshy14 merged 1 commit into
fix/h3-per-share-floorfrom
fix/h1-h2-fee-basis
Jul 18, 2026
Merged

jayeshy14 merged 1 commit into
fix/h3-per-share-floorfrom
fix/h1-h2-fee-basis

Conversation

@jayeshy14

Copy link
Copy Markdown
Owner

Audit findings H1 & H2 (High) — stacked on #12 (merge #12 first)

H1: the performance fee was charged on nav - protectedAmount, where protectedAmount is the 90% floor, not principal or a high-water mark. So a flat term (NAV 100 vs floor 90) paid a fee on 10, and a losing term (NAV 95) still paid a 'performance' fee on 5.

H2: every new term re-strikes the floor at 0.9 x current nav, so immediately after startTerm the vault always shows nav > protectedAmount. With no minimum term, a keeper could loop settleTerm -> startTerm and mint ~perfBps x (1-P) of NAV in fee shares every cycle, with no market move.

Fix (one mechanism for both)

Switch to a proper per-share high-water mark:

  • The performance fee is charged only on the rise in navPerShare above highWaterPerShareWad, which then ratchets up. Flat/losing terms and re-struck terms where navPerShare has not advanced pay zero; cross-term gains are never double-charged. HWM is seeded at the first startTerm's entry price.
  • Composes with H3: deposits mint at price and never move navPerShare, so the per-share fee basis is clean.
  • Defense-in-depth: a 7-day MIN_TERM_DURATION in CPPIController.startTerm (well below the 12-month product term) so a compromised keeper cannot loop tiny terms even setting the fee aside.

Tests

Existing perf-fee test rewritten to assert the fee is 10% of real profit (not nav-above-floor). New regressions: no fee on a flat term, no fee on a losing term, HWM holds across terms (no double-charge), and min-term blocks a 1-second churn. Suite 116 -> 120.

Audit H1/H2: the performance fee was charged on (nav - protectedAmount),
i.e. NAV above the 90% floor, so a flat term (nav 100 vs floor 90) or a
losing term (nav 95) still minted a 'performance' fee; and because every
new term re-strikes the floor at 0.9x current nav with no minimum term,
a keeper could loop settleTerm->startTerm to mint ~perfBps*(1-P) of NAV
per cycle indefinitely.

Fix: the performance fee is now charged only on the rise in navPerShare
above a per-share high-water mark, which then ratchets up. Flat/losing
terms and re-struck terms where navPerShare has not advanced pay zero,
and gains are never double-charged across terms. HWM is seeded at the
first startTerm's entry price. Also added a 7-day MIN_TERM_DURATION in
the controller as anti-churn defense-in-depth. Composes with the H3
per-share design (deposits never move navPerShare, so the basis is
clean). Regression tests: no fee on flat/losing terms, HWM holds across
terms, min-term blocks 1-second churn. 116 -> 120.
@jayeshy14
jayeshy14 merged commit 19e66c4 into fix/h3-per-share-floor 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