Ethereum contracts deployedTransactions may use real assets. Primary-market minting and instant redemption are KYC-gated; deployment does not mean every product or entry path is open. Review the legal and risk disclosures.

Protocol fee stack, ADR-0031

Internal review. This is an internal engineering review of source, not an independent external audit, and it does not by itself authorize a production launch. It reviews the source at the baseline below, which is not necessarily identical to the code deployed on any network. Forest Road Vault is live on Ethereum mainnet; its Solana curator vault remains on devnet and BSC has its own deployment status. An open finding applies only to the product, version and deployment state named by this review. Each review's scope bounds what its clean result is worth. Nothing here is a securities-law representation; token characterization is a matter for counsel.

Scope
The introduction of protocol-level management and performance fees on the senior vault: the vault itself, the waterfall, the default manager, the redemption queue, the vault interface and the configuration library.
Method
Nine independent lenses over the diff, fee arithmetic and rounding, high-water-mark lifecycle, reentrancy and callbacks, liveness under the new universal checkpoint dependency, ERC-4626 conformance, checkpoint placement in each consumer, invariant regressions, upgrade and deployment wiring, and economic timing games. Two adversarial refuters per candidate, then synthesis and a completeness critic.

Findings and remediation history

  • FEE-01HighRemediated

    The fee-free hurdle ratchet targeted a per-share rate, not an asset-preserving hurdle, so the cure of an unrealized impairment was charged as performance

    The hurdle the fee calculation consumes is proportional to share supply, but share flows move assets, and during a live impairment they move them at a basis that differs from the hurdle's, because entry prices on realized NAV and exit on conservative NAV. On exit the ratchet was a complete no-op while the hurdle shrank with supply; on deposit it fired but reset the hurdle to the conservative base plus principal, destroying the accumulated drawdown. Either way the fee-free cushion was consumed and the eventual cure booked as profit, roughly fifteen thousand units on a one-million vault carrying a three-hundred-thousand mark, with no yield earned and no loss realized. Fixed by maintaining the mark as an asset hurdle carried by exactly the principal that moves. Re-verified in Round 11.

  • FEE-02MediumRemediated

    Junior-capacity writers moved the fee base with no checkpoint and no fee-free ratchet

    The conservative NAV nets declared exposure against the live curator first-loss pool and backstop capacity, but neither of those contracts checkpointed the vault and the backstop setter had no checkpoint at all. Posting first-loss capital during a live mark raised the NAV with nothing earned, a permissionless checkpoint booked it as profit, and withdrawing the capital returned it in full, with the fee shares not clawed back. Fixed with a bracket around every junior-capacity write, gated by a new role granted to exactly three module contracts. The checkpoint half is correct; the hurdle re-anchoring the same fix added is the subject of Round 11 findings R11-01 through R11-03.

  • FEE-03LowRemediated

    Every fee setter crystallized before writing, so the recipient rotation could not escape a blocked recipient

    Because the fee checkpoint mints to the stored recipient: a recipient whose mint is denied by compliance state made the setter that exists to replace it unusable, along with every other fee-checkpointed path. Reaching the state required two privileged writes across two roles, one of which deliberately deletes the guard that keeps a list error from bricking the cascade, so it was rated Low. Fixed by writing the new recipient before crystallizing, and by enforcing the exemption invariant in the initializer rather than leaving it a deployment convention.

  • FEE-04LowRemediated

    The yield-delivery window was not closed by any vault-side lock, and the documentation claiming otherwise was false

    Between yield being minted into the vault and the vault being told to defer its recognition: the vault was transiently over-valued. The documentation credited a reentrancy guard belonging to a different contract entirely. Unreachable as wired, because the shipped points module makes no state-changing external call, so it was rated Low, but the unconditionally true defect was the false mitigation claim, which would cause a future reviewer to dismiss the window. Fixed with a real vault-side lock acquired under the same condition that later releases it, and the documentation corrected.

  • FEE-05HighRemediated

    The entire exit half of the change had no value-asserting test

    Six exit-side code sites were modified. The fee unit suite, seven hundred lines, twenty-four tests, contained no redemption, withdrawal or queue request; it never burned a share. Of every test file exercising an exit, none referenced any fee state, and the queue's stateful campaign compared the rate against a constant captured at construction so it could not observe fee dilution at all. This is line coverage without assertions on the accounting core, and it is the direct reason FEE-01 shipped green. Fixed: exit-side fee tests now exist in the unit, integration and queue-invariant tiers.

  • FEE-06HighRemediated

    The rate-integrity invariant was weakened rather than the code fixed, and the management fee was never exercised statefully

    The quantitative bound sat behind a condition that switched itself off whenever a management fee was due, so the invariant would have accepted an arbitrary collapse of the exchange rate, including to zero. No handler in any campaign ever enabled the management fee, so the branch was simultaneously dead and unbounded, the failure mode the standard names explicitly. Fixed: the management branch is bounded and a handler now fuzzes the rate. Round 11 finds the same invariant blind in a different way (R11-05).

  • FEE-07MediumRemediated

    The integration tier for the whole fee stack was a single thirty-eight-line happy path

    One test: one facility, one deposit, one repayment, one checkpoint, four assertions. No default, no past-due mark, no realized loss, no impairment source, no queue, no second depositor, and the management fee asserted to be zero and never enabled. Moving a checkpoint to the wrong side of a state change left it fully green. Fixed: the integration suite now covers the workout, queue-exit and capacity round-trip flows.

  • FEE-08MediumRemediated

    The malformed-return branch of the impairment-source probe had no mock able to reach it

    The probe distinguishes three non-readable shapes; only two were tested, because every mock in the tree returned a full word. The untested one is the branch deciding whether a source that answered can be discarded, the difference between rejecting a candidate at installation and successfully unwiring a live mark. Fixed: malformed-return mocks now exist and exercise both the installation rejection and the emergency-clear path.

  • FEE-09LowAccepted

    The emergency impairment-source clear ratchets to the un-impaired rate and permanently forgives fees on the live mark

    Clearing an unreadable source cannot checkpoint first, so the fee-free ratchet lifts the hurdle by whatever impairment the broken source was reporting; re-installing a working source that reports the same impairment leaves the vault unable to charge a fee it is owed. Opposite sign to the other findings, the protocol under-collects rather than over-collects, so it is not a user-funds risk. Accepted deliberately: the true mark is unreadable at that moment, and it is documented as an incident-only governance action requiring the waived amount to be quantified.

  • FEE-10MediumRemediated

    Every fork suite skips, including the only test routing the fee setters through the real governor and timelock

    The fork tier requires an archive endpoint and skips without one, while still reporting a passing result. The governance fork test is the only coverage of the fee setters through the real timelock and of the atomic exemption-then-rotation batch that mitigates FEE-03. Carried forward as R11-08. CLOSED alongside R11-08: the fork tier executes in full when an archive endpoint is configured, including the governance suite that routes the fee setters through the real governor and timelock.

What was in scope

This round reviewed a single change rather than the whole system: the introduction of protocol-level management and performance fees on the senior vault. The change touched sUSDfr, WaterfallEngine, DefaultManager, RedemptionQueue, the IsUSDfr interface and the configuration library, and it added roughly four hundred lines to the vault alone.

It deserved its own round for two reasons. The first is that it inserts a permissionless fee checkpoint onto the critical path of nearly every value-moving operation in the protocol, vault entry and exit, queue admission and settlement, waterfall distribution, and five separate paths through the default manager including the one the code documents as never pausable. Any new way for that checkpoint to revert is a protocol-wide liveness defect, so availability was treated as a first-class severity rather than an afterthought.

The second is that the fee is measured against the conservative marked NAV, the same base exits price against, net of declared-but-unrealized senior impairment, while vault entry still prices at the realized NAV. That asymmetry is deliberate and load-bearing, and it is exactly the kind of place where a correct-looking accounting rule can be wrong in one direction only.

How it was run

Nine reviewers worked independent lenses over the diff: fee arithmetic and rounding, the high-water-mark lifecycle, reentrancy and callback safety, liveness under the new universal checkpoint dependency, ERC-4626 conformance, the placement of each checkpoint in its consumer contract, regressions against the documented invariant list, upgrade and deployment wiring, and economic timing games.

Every candidate then faced two adversarial refuters with distinct briefs, one attacking reachability, one redoing the arithmetic from source, with instructions to default to refuted where they could not reconstruct the defect themselves. A synthesis pass merged duplicates and dropped survivors that did not hold up on re-inspection, and a completeness critic was asked what the round had missed.

Twenty-nine candidates were raised. Fifteen survived refutation. After merging, four code defects remained, alongside six findings about the adequacy of the tests.

The round ran read-only: no proof-of-concept tests were written, so several magnitudes below are static derivations from the source rather than executed results. That limitation is stated again where it matters.

What was refuted, and why that matters

Fourteen candidates were dismissed, and the pattern in them is worth recording because it describes the failure mode of this kind of review.

An early candidate held that a small token donation to the empty vault before its seed deposit, followed by a permissionless checkpoint, would pin the high-water mark astronomically high and disable the performance fee forever. The arithmetic was right. The finding was still wrong: the deployment seeds a permanently locked position atomically, and no third party can hold the underlying asset at that point in the sequence, so the state is unreachable.

Several others claimed arithmetic faults, a division underflow in the fee-share inversion, an overflow in the combined mint, an underflow in the performance attribution, that all dissolve once the actual bounds are derived. And a cluster of candidates mistook the par marked rate, reasoning against a scale a million times off because the vault's decimals are the underlying's plus the virtual-share offset. Refuters caught each of these.

The lesson is that a lens which reads carefully but does not do the algebra produces confident, specific, wrong findings. Every survivor below was reconstructed independently by at least one refuter working from source.

The findings

The headline defect is the one the asymmetry above predicts.

The vault protects holders from being charged a performance fee on mechanical accounting movements by ratcheting the fee hurdle upward, fee-free, after share flows. But the hurdle the fee calculation actually consumes is proportional to share supply, while share flows move assets: and during a live impairment they move them at a basis that differs from the hurdle's, because entry prices on realized NAV and exit prices on conservative NAV. The ratchet, keyed on a per-share rate, was therefore not asset-preserving in either direction.

On exit it was a complete no-op: a conservative-priced redemption leaves the marked rate where it was, so the ratchet never fires, yet the hurdle shrinks with the supply while less than a proportional share of assets leaves. On deposit it fired but reset the hurdle to the current conservative base plus the principal delivered, destroying whatever drawdown had accumulated below it. Either way the fee-free cushion that a live impairment is supposed to create was silently consumed, and when the mark later cured without ever being realized, the recovery was booked as ordinary profit and charged.

Worked through with exact integers on a one-million-unit vault carrying a three-hundred- thousand mark, a routine queue settlement followed by an ordinary cure charged roughly fifteen thousand units of fee against a vault that had earned no yield and realized no loss. The realized-loss path is genuinely unaffected, because a realized loss drops assets and the impairment together.

The second finding is the same class of error one layer out. The conservative NAV nets declared exposure against live junior loss-absorbing capacity, the curator first-loss pool and the backstop reserve, but none of the contracts that write those balances checkpointed the vault or ratcheted the hurdle. Posting first-loss capital during a live mark raises the marked NAV with nothing earned; a permissionless checkpoint books it as profit; withdrawing the capital returns it in full. The fee shares minted in between are not clawed back.

The remaining two are lower and both concern liveness rather than value. Every fee setter crystallized before writing its new state, which meant the setter that exists to rotate away from a fee recipient whose mint has been blocked was itself unusable in precisely that state. And the window between yield being delivered into the vault and the vault being told to defer its recognition was not closed by any vault-side lock, despite documentation asserting that it was, the guard it credited belongs to a different contract entirely.

What the tests did not cover

The more uncomfortable half of this round is not the code.

The entire exit half of the change had no value-asserting test. Six exit-side code sites were modified. The fee unit suite, seven hundred lines and twenty-four tests, contained no occurrence of a redemption, a withdrawal or a queue request, it never burned a share. Of every test file that exercised an exit, not one referenced any fee state. The queue's own stateful campaign ran with the launch performance fee live but compared the exchange rate against a constant captured at construction, so it could not observe fee dilution at all.

That is line coverage without assertions, on exactly the accounting core where the standard demands the most, and it is the direct reason the headline defect shipped green.

The rate-integrity invariant had been weakened rather than the code fixed. The quantitative bound was placed behind a condition that switched itself off whenever a management fee was due, so the invariant would have accepted an arbitrary collapse in the exchange rate, including to zero. And no handler in any campaign ever enabled the management fee, so the branch was simultaneously dead and unbounded.

Alongside those: the integration tier for the whole fee stack was a single thirty-eight-line happy path with no default, no queue and no second depositor; every fork test skipped, including the only test that routes the fee setters through the real governor and timelock; and the one branch of the new impairment-source probe that decides whether a source which answered can be discarded had no mock capable of reaching it.

Disposition

All four code findings and the test gaps were remediated. That remediation was then given its own round, which is the honest thing to do with a fix of this size, and which found that one of the four repairs had reintroduced the same defect class it was fixing, on a different axis. See the remediation re-check.

Earlier roundFull-system audit, round nine→