Skip to content
Risks

Scenarios

Twelve end-to-end scenarios with real numbers: a crash, a split, a depeg in each direction, a feed outage, a branch shutdown, a wind-down, an exploit, and an automatic activation.

These are the worked scenarios from the protocol specification, restated for a reader rather than an implementer. They are not projections; they are traces of what the code does, computed from the parameters in force.

Common assumptions unless stated otherwise. Day 120 after deployment. SPY and QQQ at capacity tier 3, so C_abs = 2.0 M each and the global ceiling is 4 M. SPY token at 500 USD with a multiplier of 1.0. Tier 1. Borrower rate 4%. Interest regime 1, meaning 90% to the Stability Pool and 10% to liquidity incentives, with the backstop funded at 100 000 USD, above its 2% target, so nothing is skimmed. Measured depth d2_eff on SPY is 1 000 000 USD. SPY branch: debt 1.5 M, pool 900 k. Total fyUSD supply 4 M, of which 1.5 M from SPY, 1.5 M from QQQ, 1 M from the PSM. PSM reserve 1 M. Reference rate 3.6%, giving a Tier 1 floor of 1.8%. The Closer is alive.


1. Open and repay

Tuesday 11:00 UTC, the branch in LIVE24. Alice deposits 20 SPY worth 10 000 USD and asks for 5 000 fyUSD at 4%, above the 1.8% floor.

Her per-address cap is min(π × d2_eff = 1 000 000, 10% × 2 000 000 = 200 000) = 200 000 — comfortable. The hourly depth slot is empty, so her open call also runs poke() on the liquidity oracle, costing roughly 200 000 gas that she pays.

mint fee = 5 000 × 4% × 7/365 = 3.84
debt     = 5 003.84
ICR      = 10 000 / 5 003.84 = 199.8%   (minimum 125%)
liquidation price = 5 003.84 × 1.15 / 20 = 287.72 USD, i.e. −42.5%

The 3.84 fee is minted to the router and split 3.46 to the SPY pool, 0.38 to liquidity incentives.

Ninety days later, with the interest clock never stopped: debt = 5 003.84 × (1 + 4% × 90/365) = 5 053.19. Over the period 49.35 fyUSD of interest was minted — 44.41 to the pool, 4.94 to incentives.

Alice approves 5 053.19 and calls close(). Exactly 5 053.19 fyUSD is burned from her address, her 20 SPY come back, and both total debt and total supply fall by the same amount.


2. A 20 % crash while the sources are degraded

SPY at 500, the branch in LIVE24. SPY positions sit anywhere from 120% upward, 300 k of debt between 120 and 140%.

Hour 0. Two of the three source groups thin out — the index feeds stop signing and the twin-token market goes quiet — so the branch drops to DEGRADED. Confidence falls, the haircut appears, and the LTV available to borrow more drops below 80%. Nobody is liquidated and nobody has to act.

Hour 2. The remaining sources read 400, down 20%. Two qualified sources of the same branch agree, so the fall is confirmed once the one-hour average has moved. The band is 4% and it is a constant, so pLiq = pRef = 480 and pRedeem = 500 for as long as the episode lasts, however deep or however long the fall runs. The basket of the other equity branches plays no part: it feeds dispersion and nothing else.

Bob:   100 SPY, 41 667 debt, 120.0% before, 115.2% at pLiq = 480
       not liquidatable while the band holds
Carol: 100 SPY, 42 373 debt, 118.0% before, 113.3% at pLiq = 480
       X       = (1.265 × 42 373 − 48 000) / (1.265 − 1.02) = 22 864 fyUSD
       seizure = 22 864 × 1.02 / 480 = 48.59 SPY, absorbed by the Stability Pool
       after:  19 509 debt, 51.41 SPY, 126.5% at 480

Carol is flaggable once the fall is confirmed and liquidatable after the longer DEGRADED confirmation delay, during which she can repay or deposit. Neither reads a price.

The DEGRADED buckets bound what can happen: 0.10×× d2_eff of debt per transaction, which is 100 k here, 0.25×× d2_eff per hour, and at most min(1.0 × d2_eff, 15% of branch debt) = 225 k across the whole episode.

When the sources come back. The composite reads 400 and the branch returns to LIVE24. pLiq = pRef = pRedeem = 400 immediately, and the quiet edge runs for 10 minutes: flags can be set at once, and the pool executes nothing until the edge expires, at 400 and never above it.

Liquidatable by the pool: ICR(400) < 115%. The target is 126.5%, throttled by the 1.0×× d2_eff hourly bucket, counted in debt burned.

This is the cliff, and it is carried by the pool. Bob, protected by the band while the episode lasted, reads 96% at the composite: below 100%, so the pool absorbs what it can at a loss and 1 667 fyUSD of his debt becomes bad debt against the backstop. badDebtCumul > 0 then permanently locks SPY's capacity at the tier in force.

What that says about the thresholds. At a 115% threshold with a 6.09% solvency cushion, a −20% gap consumes the entire distance between the minimum ratio to borrow and insolvency. The protocol behaves correctly throughout — nothing is liquidated on an unconfirmed estimate, the buckets hold — and it still ends with bad debt. That is a property of the thresholds, not of the 24/7 machinery, and it is the reason the ratios on this page are marked as pending confirmation.


3. A manipulated pool while the sources are thin

An attacker wants Carol's collateral. She is at 118%, so a fall of about 2.5% in pLiq puts her under 115%.

What they must do. Push the one-hour average of every qualified SPY pool below 500 × (1 − 0.04) and hold it there. Sixty minutes for the average to move at all, then the DEGRADED confirmation delay between the flag and the liquidation — a long stretch of public, on-chain visibility during which Carol can repay or deposit, neither of which reads a price.

And they must prove it. Confirmation needs a second qualified source of the same branch, or three hours of persistence on one. The basket no longer confirms anything, so pushing QQQ buys nothing on SPY. On the measured depths, moving SPY's v3 pool 4% takes roughly 350 k of capital and a few thousand in slippage, and holding it there against arbitrageurs who see SPY 4% below the composite on no news costs 8 to 20 k. An attacker who is also a liquidity provider in those pools lowers that by pulling their own liquidity first, which is why the working figure is 30 to 60 k for two pools over nine hours.

What they can win. Only positions within the band of their threshold become reachable, because the band is a constant and holding the fall longer opens nothing further. The gross gain is 1.02 / 0.96 − 1 = 6.25% of the debt taken, and that window holds 3 to 5% of a branch's debt in an ordinary distribution. At the phase-1 cap of 1 M per branch: 2 to 3 k of gross gain against 30 to 60 k of cost.

And it does not scale. The cost is paid per pool, the gain is collected per branch. Four equity branches at 9 M of debt yield 17 to 28 k in total, and each one requires its own qualified pools to be pushed. There is no arrangement of branches in which this pays.

And the ceiling. The per-episode bucket caps the take at 15% of the branch's debt, counted in debt burned. The worst case for borrowers, as a group, is 15% × 6.25% ≈ 0.9% of branch debt across one degraded episode.

Risk

A branch whose qualified on-chain depth has collapsed is cheaper to push, though it does not pay more: the reachable window is the same 3 to 5% of debt. The bucket bounds the outcome; nothing eliminates it. What the attacker can never do is exceed the bucket, act without the persistence and confirmation windows running in public on-chain, or move pLiq further than the recognition band of 4%.


4. A 10-for-1 split during a position

QQQ at 600 USD per token, multiplier 1.0. Dave holds 10 raw QQQ worth 6 000 with 3 000 of debt, ICR 200%.

Hour 0, the issuer calls pauseOracle(). Five minutes later a poke() — from the first branch operation or from a keeper — records PAUSED with degradedSince = 18:05, and takes a snapshot of the last round before 18:00 by walking getRoundData backwards: 600. Borrowing, withdrawal, liquidation and redemption on QQQ are blocked; repayment and collateral addition stay open; the redemption router excludes QQQ. The banner reads that a split is in progress and no liquidation is possible.

Two hours later the issuer calls updateMultiplier(10, effectiveAt). Every round published during the pause is ignored; the price stays 600. The pause lasts 15 hours, under 24, so the interest clock runs: 3 000 × 4% × 15/8 760 = 0.21 fyUSD.

Fifteen hours later, unpauseOracle(). The adapter requires three fresh rounds, impliedShare = 600 / 10 = 60 inside the automatically recalibrated bounds of [20, 180], and uiMultiplier == newUIMultiplier. The branch is LIVE24 again five minutes after that, with no fixed waiting period. A quiet edge runs for the next 10 minutes because it left FROZEN. The price applies in one step and the pool executes nothing during that window.

Dave still holds 10 raw tokens at 600, worth 6 000, at 200%. The interface shows "100 QQQ". No accounting entry was made anywhere.

Failure case. If the feed resumed at 60 USD per token — the multiplier not yet applied — then impliedShare = 6, outside the bounds, and the branch goes to CIRCUIT. The collateral is never valued at 600 per token when it is worth 60, no liquidation occurs, and the state clears automatically after three consistent rounds at 600.


5. fyUSD at 0.97

baseRate at zero, the branch in LIVE24. Three arbitrages run at once.

(a) The PSM. Buy 1 M fyUSD at 0.97 for 970 k USDG, swapOut to 998 k USDG: a profit of 28 k. The reserve falls to 2 k, so the floor is lifted until it is refilled. The 2 k fee is minted in fyUSD and routed to the pools.

(b) Redemption. Buy 200 k at 0.97 for 194 k, redeem at a 0.5% fee for 199 k of SPY at the oracle price: a profit of 5 k, or 2.6%. baseRate rises by 200 k / 4 M = 5%, so the fee becomes 5.5% and break-even falls to 0.945. The half-life brings it to 2.75% by 17:00 and 1.4% by 23:00.

(c) Borrowers. Buy back 100 k of their own debt for 97 k.

The price recovers above 0.99 within hours. In DEGRADED all three legs still run: leg (b) simply costs more, because the fee carries up to 2% of low-confidence premium and the collateral comes out at the higher of two prices. The 5 to 10% reserve target remains, because the PSM is still the deepest leg.

With a simultaneous USDG depeg, leg (a) fails. During the Closer's life, freeze(PSM, MINT, ≤ 72 h) blocks entry. After month twelve, nothing: the exposure is bounded by the 30% cap, and legs (b) and (c) work at any hour.


6. fyUSD at 1.03 while confidence is low

The branch in DEGRADED. Supply 4 M, minted[PSM] 1 M, so intake capacity is about 286 k — 30% of the post-mint supply.

An arbitrageur runs swapIn(250 000) for 249 875 fyUSD and sells at 1.03 for 257 371 USDG: a profit of 7 400. Pool depositors withdraw immediately and sell at 1.03. Wrapper holders use Router.withdrawToUSDG, which hands back fyUSD if the reserve is short, and they sell that on the pool.

A borrower who wants to repay 10 000 uses repayWithUSDG: it costs 10 005 USDG whatever the market premium is. Borrowing works at the haircut LTV, and redemption works at the low-confidence fee — neither is switched off, they are simply dearer.

And at any hour: borrow at 1.00 against collateral, sell into the premium, for 0.03% of fees. When the sources come back, confidence returns, the haircut disappears, and the borrowing capacity that was held back comes back with it.

A persistent premium requires the PSM saturated and the caps full at once, because borrowing never stopped. There is no function to raise capShare. The premium lasts until the caps loosen.


7. A whale exits, after day 90

SPY at tier 4, C_abs = 3.0 M, debt 3.0 M, pool 1.7 M. The per-address cap is min(1.0 M, 300 k) = 300 k, so the whale has opened four positions of 300 k from four addresses — 1.2 M of debt, 40% of the branch, visible on-chain — and holds 700 k in the pool, the per-address pool cap having lapsed at day 90.

She withdraws 700 k from the pool immediately, claiming her SPY gains, then runs repayWithUSDG for 1.2 M in four calls. The 600 USD of fees are minted in fyUSD and routed to the SPY pool. The reserve rises by 1.2 M, exempt from the cap.

After: debt 1.8 M, pool 1.0 M. The pool yield in regime 1 is 0.9 × 5% × 1.8 / 1.0 = 8.1%unchanged, because it is a ratio, not a level. Of the supply, 2.2 M is now backed by USDG: over-collateralised, and every holder can exit one for one at the PSM.

What breaks: nothing mechanical. Before day 90 the 50 000 USD per-address cap would have required twenty-four addresses.


8. A three-day feed outage

Hour 0 is the last round from SPY's remaining source group. Four hours later the age passes 4 hours, that group stops counting and the branch moves to DEGRADED: everything still works, at a haircut that grows with the square root of elapsed time. At hour 26 the source has missed its heartbeat over 26 hours and, with too few independent groups left, the branch reads FROZEN with degradedSince set. Borrowing, withdrawal, liquidation and redemption on SPY are frozen; repayment, collateral addition and closure stay open; QQQ is unaffected; the router excludes SPY.

Twenty-four hours into the frozen episode, the interest clock stops and emits InterestClockPaused. Interest accrued over the episode: 1.5 M × 5% × 1/365 = 205 fyUSD, then zero. There is no governance alert to raise, because there is no governance: the watcher publishes and the Closer has nothing to do.

Three days in, rounds resume at 425, down 15% from 500. The deviation guard computes dt = 4 320 minutes, so the budget is min(0.6% × 4 320 + 0.5%, 12%) = 12%, less than the 15% move — the branch goes to CIRCUIT. Either 3 consistent rounds and 10 minutes, or 2 and 60 minutes, clears it. The accepted price becomes 425 and pLiq is 425 immediately; the quiet edge runs for 10 minutes, so the first pool liquidation lands after it, at 425. InterestClockResumed fires.

This is the case that killed the return ramp. Under it, pLiq slid from 500 to 425 over fifteen minutes with no edge at all, so the pool could buy SPY at 493 that was worth 425: a 13.8% loss per unit, up to roughly 200 k on a 900 k pool, paid straight to the borrower being liquidated.

An hour later the branch is LIVE24 again and the haircut is back to zero.

Had the outage lasted seven days: the following Tuesday at 11:00, anyone could call shutdown(). The last good price is 500, urgent redemptions open immediately at 500 / 1.02 worth of collateral per fyUSD, the interest clock freezes for good, and settlement becomes available thirty days later.


9. A branch shutdown

QQQ: debt 1.5 M, pool 600 k, TCR 148%. The branch had been in DEGRADED and the on-chain price had followed to −20%, so with the constant 6% band pLiq sat at 0.94 × the last good composite throughout.

The sources then come back at −30%. The deviation guard's doubled budget is 24%, less than 30%, so the branch goes to FROZEN(CIRCUIT) rather than accepting it. It clears by 3 consistent rounds and 10 minutes if the sources publish, or by 2 and 60 minutes at the latest. With only the first path, a quiet source could have held the branch frozen for hours at exactly the wrong moment.

On the exit, pLiq is 0.70 × the last good composite immediately, taking the TCR to 103.6%, below the 110% shutdown ratio. A quiet edge runs for the next 10 minutes.

Ten minutes after the exit: liquidations run. The pool absorbs 600 k, at a loss on positions below 1 + bonus, and empties. Direct liquidations proceed only on positions above that. The TCR stays around 105%.

Immediately: flagShutdown(), since the TCR is under the ratio from the moment the print is accepted. One hour later: shutdown(), the persistence satisfied. The last good price is 420, the clock freezes, borrowing, withdrawal and liquidation are refused, and urgentFrom is the shutdown timestamp because the cause is mechanical.

Holders call urgentRedeem(QQQ, 100 000, [best-collateralised positions]) and receive 100 000 × 1.02 / 420 = 242.9 QQQ. Borrowers repay and close. Pool depositors withdraw and claim. Positions below 100% are redeemed until their collateral runs out, with the residue becoming bad debt and badDebtCumul going positive; the backstop covers what it can, and 72 hours later redistribute(QQQ) becomes callable by anyone against the remaining QQQ positions.

Thirty days after urgentFrom, settleAfterShutdown(user) clears any remaining position at the last good price with no bonus, with the surplus claimable forever.

No relisting. QQQ returns only in a future version. And if a second activated branch ever shuts down, flagSunset() and enterSunset() start the wind-down automatically.


10. Sunset

Supply 4 M, debt 3.5 M, PSM 0.5 M. Triggered either by shutdownAll() during the Closer's life — with at least seven days of public notice outside an emergency — or automatically.

Immediately: borrowing disabled everywhere; PSM fees to zero and its cap to 100%; 100% of interest to the pools; urgentRedeem open on every non-shut branch at the oracle price with a 2% bonus and no fee, whenever the market allows; liquidations and market states unchanged; dormant branches permanently unable to activate.

Day 0 to day 90: borrowers repay in fyUSD or in USDG at par; holders exit either into USDG at par or into collateral at a 2% premium; the pool yield falls with the debt.

Day 90: the interest clock freezes across all branches.

Terminal: debt at zero, reserve == minted[PSM], residual collateral claimable without limit. No role can block any of it — the contracts are immutable and Sunset is one-way.


11. An exploit in progress during the first phase

Day 45. The Closer is alive, the on-call rotation is in place, the global ceiling is 2 M.

Tuesday 14:00 ET: the open-source watcher detects on SPY a divergence of 40 000 fyUSD between the supply and the sum of debt, bad debt and PSM minting — and it is growing. The pattern is an anomalous mint through an open/redeem sequence exploiting a hypothetical rounding defect. Each cycle nets the attacker about 8 000 and is bounded by the hourly bucket and the 50 000 USD per-address cap.

14:12: two signatories execute Closer.freeze(SPY, {MINT, WITHDRAW, REDEEM}, 72 h) and freezeLiquidation(SPY, 24 h) in one Safe transaction. The front end shows the freeze and its end. The cumulative budget consumed is 72 hours of 30 days, and one liquidation freeze of three. Total drain: roughly 100 000 fyUSD, bounded by twelve minutes of human latency rather than by the attacker's speed.

What stays open during the freeze: repay, addCollateral, close, pool withdrawals, claimCollateral, swapOut, sfyUSD.redeem, Router.withdrawToUSDG. QQQ works normally. So does the PSM.

20:00: the flaw is confirmed in the per-branch Branch contract. Closer.shutdown(SPY) sets the last good price to 500, freezes the clock, and sets urgentFrom to seven days later — giving borrowers a week to close before holders can redeem against them. Settlement follows at day 37. The 100 000 of fyUSD without debt behind it is recorded as bad debt at settlement and covered by the 100 000 backstop, then by redistribution if needed.

False-positive variant. At 20:00 the analysis concludes the watcher was wrong. unfreeze(SPY). Cost: six hours of freeze and 72 hours of the cumulative budget.

What the key could not do: recover the 100 000, fix the code in place, or freeze repay. After month twelve the same scenario plays out with no freeze at all — the drain continues until the buckets, the caps and voluntary user exit stop it, followed by automatic shutdown if the TCR falls far enough.


12. A dormant branch activating itself

Day 0: the SLV branch is deployed with activatedAt = 0, its adapter, its liquidity oracle across two v4 pools and one v3 pool, its Stability Pool, its sfyUSD-SLV wrapper and its CollateralSale. It is a registered fyUSD minter with a ceiling of zero. Depositors seed 200 k fyUSD into its pool — dormant deposits, earning nothing.

Days 0 to 90: anyone calls LiquidityOracle.poke() on SLV; the reference front end does it on every visit to the SLV page, and the watcher hourly. The buffer fills and d2P25 rises from 200 k to 640 k. The adapter runs cleanly, with longestDegraded = 3 h from one sequencer hiccup.

Day 90, 10:00 ET: canActivate() returns criterion 8 failing — active pools total 1.4 M against a 4 M supply, or 35%, below the 40% required for tier 2. Nobody can force it.

Day 104: after a 300 k entry into the SPY pool through Router.depositUSDG, the ratio reaches 41%. A SLV depositor calls Branch.activate(). All eight criteria pass: date, d2P25 = 640 k ≥ 500 k, bufferFill = 151 ≥ 84, two pools above 250 k, on-chain market cap 6.2 M above 4 M, the adapter deployed 104 days with no long frozen episode, regime LIVE24 with no pending action, and 41% pool depth. activatedAt is set and Activated fires.

Days 104 to 118: C_abs = 100 k for the observation window, and

D_max = min(2.5 × 640 k = 1.6 M, 10% × 6.2 M = 620 k, 100 k,
            max(25%, 1/3) × (2.0 M + 2.0 M + 0.1 M) = 1.37 M) = 100 k

First positions open at 133.33% with a tier 2 rate floor of max(3.0%, 0.9 × 3.6%) = 3.24%. The 200 k of dormant deposits start receiving interest; redemptions and routed PSM fees now include SLV pro rata its uncovered debt.

Day 118: tier 2 at 500 k. Day 194: tier 3 at 1.0 M if no bad debt. Day 284: tier 4 at 1.5 M if additionally the branch TCR on pRef, without the haircut, is at or above 200% at each mint. The Closer expires at day 365, before SLV reaches its final tier. No human event occurs anywhere in this sequence.

Variant. If d2P25 on SLV stalls at 300 k and never reaches 500 k, the branch stays dormant indefinitely, its 200 k of pool deposits stay withdrawable at any moment, and nothing breaks.

Last reviewed: 2026-09-07 · Spec v0.4