Phase 5 Security Hardening: Three-Layer Mint Routing, Stability-Gated Circuit Breakers, and TWAP Stall Recovery
Seven changes to the protocol's safety surface, what each one prevents, and the exact constants we settled on. If you operate a vault, hold $GNDX, or audit DeFi, this is the deep dive.
Most protocol "security updates" are either patch notes nobody reads or marketing fluff with the word hardening in the title. This is neither. Phase 5 was a structured, pre-mainnet pass through every place the protocol could be gamed, stalled, or brittled by an oracle outage. Seven changes shipped. Each one removes a specific failure mode we could write a sentence about.
We're publishing the constants and the reasoning because we think the right way to evaluate a protocol's safety claims is to read them, not trust them.
1. Three-layer mint routing
Originally, mints above a $50,000 threshold went through a TWAP execution to limit market impact. The single threshold turned out to be too crude — a $50,001 deposit shouldn't behave dramatically differently from a $49,999 one — and the global rate had no ceiling.
Phase 5 replaces the single switch with three layers:
- Below $25,000 (INSTANT_FLOOR_USDC): always instant. No TWAP, no chunking. The protocol absorbs the trivial market impact.
- $25,000 to $50,000 (the fuzzy zone): quadratic probability of being routed through TWAP. P(TWAP) = (position in band)². A $26K deposit is almost certainly instant; a $48K deposit is almost certainly TWAP. This eliminates the cliff at the threshold.
- Above $50,000: always TWAP, with chunk count and interval scaled to order size.
The dynamic TWAP scaling is the second half of the change. Chunks are computed as clamp(ceil(orderSize / $25K), 4, 24), and intervals scale from 5 minutes (small chunks) to 20 minutes (large chunks). A $60K order completes in about 20 minutes; a $1M order takes about six hours. Interval is a function of chunk size, not order size — large orders aren't penalized with waits longer than necessary.
On top of the per-order routing, two global brakes were added:
- Per-address 24-hour cap: a single address can't bypass routing by sliced deposits within a rolling 24-hour window.
- Global instant budget: $500,000 per hour across all minters. If the budget is exhausted, the next instant mint queues to TWAP regardless of size. This protects against a coordinated stress event.
2. Stability-gated circuit breaker re-entry
The NAV oracle clamps any price sample that diverges more than 30% from the recent TWAP. When that happens, the affected token is excluded from minting until the price stabilizes. Previously, "stabilizes" meant "the next sample passes." That was too permissive — a single quiet sample could re-admit a token that was still in the middle of an active manipulation attempt.
The new rule requires three consecutive clean samples before a token re-enters the mint set. A "clean" sample is one that lands without triggering the circuit breaker. The counter resets to zero on any trigger. In practice this means a token must show at least 15 minutes of orderly behavior before we're willing to price it again for new mints.
To prevent a hostile actor from holding a token in exclusion forever by triggering the breaker repeatedly, there's a 24-hour safety valve. After 24 hours of exclusion, the token re-enters automatically even without three clean samples. The assumption is that 24 hours is long enough for the protocol's keepers and governance to have intervened if something is genuinely wrong.
3. TWAP stall recovery
TWAP mint orders depend on a keeper executing each chunk on schedule. If the keeper goes down, a depositor's USDC could in principle be locked indefinitely. We didn't think that was acceptable.
Two new flows give depositors agency:
- forceCompleteStalledOrder: after the order's expected completion time plus a 2-hour grace period, the depositor can finalize the order with whatever has been deployed so far, minting GNDX for the executed chunks and refunding the remaining USDC.
- cancelOrder (post-grace): after the same grace window, the depositor can cancel an order that has already started executing, taking a partial refund and the proportional GNDX for the chunks that completed.
The chunk count and interval are stored per-order at submission. That matters for stall recovery because expected-completion math has to match the order's actual schedule, not a global default that may have changed since submission.
4. Oracle outage resilience
What happens when half your Chainlink feeds go stale at the same time? Previously, redemption simply reverted, which is correct but not graceful — users with legitimate exit needs got stuck.
The new behavior is per-redemption-mode:
- redeemForBasket: still executes, but applies the maximum allowed redeem fee (50 bps) instead of the normal fee. The user gets out; the protocol charges its ceiling fee for the privilege of redeeming during low-confidence pricing.
- redeemForUsdc: reverts. Swapping basket tokens to USDC requires reliable pricing — without it, slippage tolerances are unenforceable.
- redeemOverweight: reverts. The whole purpose of the overweight bonus is to nudge specific weights down, which requires comparing live prices to targets.
This is the right asymmetry: basket redemption is always safe because it just hands you proportional shares of what's actually in the vault. The other two modes need pricing the protocol doesn't currently have.
5. Overweight rate limiting
The overweight redemption bonus is meant to incentivize a small, gradual rebalance — a few percent here, a few percent there. It was never meant to drain a single token in one transaction. Phase 5 enforces that intent: MAX_OVERWEIGHT_DRAIN_BPS = 1000, meaning a single overweight redemption can take at most 10% of the vault's balance of the overweight token. Larger rebalances have to come from the keeper-driven RebalanceController, which respects the tier bands and routes through the swap adapter.
6. ISwapAdapter abstraction
Direct calls to Uniswap V3 are gone. Every DEX swap — in MintEngine, RedeemEngine, FeeCollector, and RebalanceController — goes through an ISwapAdapter. Two implementations ship with the protocol:
- UniswapV3Adapter: wraps the V3 router for standard exact-input swaps.
- AggregatorAdapter: forwards pre-computed calldata to 1inch, Odos, or ParaSwap routers, allowing the protocol to take advantage of multi-pool routing without the swap engine knowing anything about it.
The point isn't to support every DEX on day one. The point is that switching execution venues — when liquidity migrates, when an aggregator outperforms, when V4 ships — is now a governance proposal, not a contract migration.
7. Crisis fee with auto-expiry
If a basket token's TWAP declines more than 7% in a single 5-minute window, the oracle emits a velocity alert. The vault responds by activating a crisis fee — the maximum exit fee (50 bps) — for four hours. The intent is to slow exit pressure during a panic without locking it.
Two design choices worth flagging:
- The crisis fee fires only on declining TWAP. Sharp upward moves don't trigger it — those are price discovery, not flight.
- The fee auto-expires after four hours with no governance intervention required. If the situation is still bad after four hours, governance can extend; if it's resolved, the protocol returns to normal fees automatically.
What this means for users
Most of these changes you'll never notice — which is the point. If you mint $30K, you'll get instant fills. If you mint $300K, you'll get a TWAP that completes in well under two hours. If a basket token goes haywire mid-mint, you won't be silently exposed; the oracle will exclude it and the engine will route around it. If a keeper goes down, you'll have a path to your USDC. If half the oracle feeds stale at once, you'll still be able to redeem for the basket.
If you do notice a change, it'll probably be the mint receipt for a $26K deposit telling you it's instant — small enough that the protocol absorbs the friction so you don't have to.
The full constants are in the project's GitHub repo, the audit notes are in AUDIT_NOTES.md, and the on-chain code is verified against the same. As always, Discord for questions.
More posts
All articlesThe Seeding Window: How GNDX Onboards New Tokens
When governance approves a new token for the basket, it doesn't appear fully-allocated overnight. A bounded 14-day seeding window funds it in measured chunks, suppresses alarm noise, and keeps redemption fair while the basket transitions. Here's the mechanic and why it works the same at $1M, $100M, or $1B TVL.
Mint, Burn, NAV: How GNDX Pricing Actually Works
Most DeFi tokens move on sentiment. An index moves on math. This is the plain-language version of how a $GNDX token gets priced, what happens when you mint or redeem, and why the price can't drift away from the basket.
Why 10%? The Math Behind GNDX's Hardcoded Weight Ceiling
The most consequential number in the protocol is also the one that looks the most arbitrary. We modeled blow-up scenarios from 5% to 25% caps to figure out where the real cliff is — and why the answer had to be in code, not in a vote.