Skip to content

feat(rebalancer): rebalance around a quarantined constituent (Section 4)#17

Merged
jayeshy14 merged 1 commit into
feat/safety-circuit-breakerfrom
feat/safety-rebalance-quarantine
Jun 30, 2026
Merged

feat(rebalancer): rebalance around a quarantined constituent (Section 4)#17
jayeshy14 merged 1 commit into
feat/safety-circuit-breakerfrom
feat/safety-rebalance-quarantine

Conversation

@jayeshy14

Copy link
Copy Markdown
Owner

Final slice of the Section 4 safety layer, stacked on #15. With Slices 1 and 2 in place, a single stale feed still reverted the whole rebalance, because the rebalancer called getWeights over every constituent. This slice lets the healthy names rebalance around a quarantined one.

What changed

  • openEpoch and maxDriftBps operate over the fresh subset. A new _freshSubset filters out quarantined (stale-feed) constituents, and getWeights is called over that subset, so a stale feed no longer reverts the trigger read or the epoch snapshot. maxDriftBps keeps a fresh-index cursor aligned to the full (degrading) holdings array.
  • A quarantined name is excluded from the epoch entirely. It is not targeted, and validateOrder's inEpoch check already rejects any order for it. This is the deliberate Section 16.5 stale-feed special case: the name cannot be priced, so it cannot be sold either (sellMinOut anchors to the very oracle that failed), so it is held marked-down rather than wound down, until the feed recovers.

Why not sell it

A quarantine differs from a wind-down here. A wind-down name has a fresh feed, so it targets zero and is sold to USDC at an oracle-anchored minimum-out. A quarantined name has no usable price, so there is no safe minimum-out to sell it against. Excluding it from the epoch and holding it (conservatively marked down in NAV via Slice 1) is the correct, bounded behavior.

Completes Section 4

Quarantine and graceful redemption (#14), NAV circuit breaker and pause (#15), and now rebalance-around-quarantine. Together: routine single-feed failures degrade-but-continue, systemic mispricing halts for review, and rebalancing keeps moving the healthy names through a single-feed outage.

Tests

3 new tests in test/RebalancerQuarantine.t.sol: the drift read no longer reverts under quarantine, openEpoch excludes the quarantined name (not targeted, not in epoch) while the fresh name carries the full target, and the all-fresh path is unchanged. 162 total, all green, forge fmt clean, no regression.

A single stale feed previously reverted the whole rebalance, because
getWeights priced every constituent. The rebalancer now weights and trades
over the FRESH subset, so the healthy names rebalance while a quarantined name
is held marked-down (Section 4) until its feed recovers.

- openEpoch and maxDriftBps build a fresh subset (constituents whose feeds are
  not quarantined) and call getWeights over it, so a stale feed no longer
  reverts the trigger or the snapshot. maxDriftBps keeps a fresh-index cursor
  aligned to the full holdings array.
- A quarantined name is excluded from the epoch entirely: not targeted, and
  validateOrder's inEpoch check rejects any order for it. This is the Section
  16.5 stale-feed special case: the name cannot be priced, so it cannot be
  sold either (sellMinOut anchors to the failed oracle), so it is held rather
  than wound down until the feed recovers.

Completes the Section 4 safety layer (quarantine, circuit breaker, rebalance
around quarantine). 3 new tests, 162 total green.
@jayeshy14 jayeshy14 merged commit c75e390 into feat/safety-circuit-breaker Jun 30, 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