feat(vault): constituent quarantine and graceful redemption (Section 4)#14
Merged
Conversation
A single stale constituent feed previously halted NAV and therefore every redemption, a liveness cliff that worsens at index-100 where 'all feeds fresh' is fragile. The vault now degrades instead of halting. - AssetRegistry: add a non-reverting getPriceUsdStatus that surfaces a stale feed's last-good answer and freshness, with the feed normalization factored out so the reverting and non-reverting paths share one implementation. - NAV degradation: a fresh constituent values at full price; a stale one at its last-good price times a mark factor that starts at (1 - haircut) and decays linearly to zero over a window; a dead feed values at zero. totalAssets and getHoldings degrade together; the USDC numeraire feed stays fail-closed. - Exits bend, entries stop: sync buffer redemptions survive a stale feed at the haircut NAV, while mint, sync deposit, and async settle fail closed under quarantine, since minting against a conservative NAV would over-issue and dilute the holders who stay. A zero-balance stale constituent does not block. Deferred to later slices: the NAV-per-share circuit breaker and guardian pause, and the rebalancer dropping quarantined names from target weights. 10 new tests, 149 total green.
This was referenced Jun 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First slice of the Section 4 safety layer, the seam the constituent-governance work (PR #10) explicitly deferred and that Sections 16.5, 17.3, and invariant X2 depend on.
The problem
A single stale constituent feed halted NAV and therefore every redemption. That liveness cliff worsens at index-100, where the probability that at least one of N feeds is stale at any moment is not small. The vault now degrades instead of halting.
What changed
AssetRegistry: a non-revertinggetPriceUsdStatus(token)that surfaces a stale feed's last-good answer, itsupdatedAt, and freshness, instead of reverting. The feed normalization is factored out so the reverting (getPriceUsd) and non-reverting paths share one implementation.1 - haircutand decays linearly to zero overquarantineDecayWindow; a dead feed (non-positive answer) values at zero.totalAssetsandgetHoldingsdegrade together and stay consistent. The USDC numeraire feed deliberately stays fail-closed, since it is the unit NAV is quoted in, not a constituent.mint, syncdeposit, and the whole asyncsettlefail closed under quarantine, because minting against a conservative NAV would over-issue shares and dilute the holders who stay. A stale feed on a constituent the vault does not actually hold does not block entries.quarantineHaircutBpsdefault 10%,quarantineDecayWindowdefault 7 days) are owner-set.Deferred to later slices
Tests
10 new tests in
test/IndexVaultQuarantine.t.sol: NAV degrades instead of reverting (exact haircut value asserted), dead feed marks to zero, decay to zero over the window, the headline buffer-redeem-survives-a-stale-feed guarantee, mint and settle fail closed, a zero-balance stale constituent does not block entries, and the param validation. 149 total, all green,forge fmtclean, no regression.