- Fork 1 — the oracle settlement-liveness deadlock, resolved to invariant I8.
- Fork 2 — the no-last-look firm-quote pickoff, resolved to invariant I9.
These were the two open items carried forward through spec revisions v3.2 (Fork 1) and v3.3 (Fork 2). Both are fixed and Foundry-fuzzed today.
Fork 1 — the oracle settlement-liveness deadlock
What the audit found
The audit flagged that v3.1’s round-at-T-plus-pinned-TWAP scheme could deadlock permanently. Both the Chainlink round and the TWAP at T are frozen historical values, so a>2% divergence at T would revert settle on every future call. A single oracle hiccup at expiry would strand both sides’ funds with no recovery path — and that directly threatens the (c) oracle/settlement liveness clause of the no-bad-debt guarantee:
The fix in three parts
1
Pin the Chainlink round-at-T (fairness, unchanged)
Settlement price is still the Chainlink round-at-T — the unique round bracketed below by
updatedAt ≤ expiry. This is what makes settlement fair: both LP and MM settle at the same canonical, on-chain-derived price, never a caller-supplied one. See price pinning for why the settlement price is derived on-chain, never from calldata.2
Lone-spike sanity check (catches glitches, not real moves)
The hard TWAP gate is replaced by a lone-spike check: the round-at-T is treated as a glitch only if it differs from both neighbouring rounds by
≥ LONE_SPIKE_BPS = 500 (5%). Real fast moves persist across consecutive rounds and therefore pass — only a single isolated print that disagrees with both sides is deferred. A missing neighbour round across a Chainlink phaseId boundary is read tolerantly via _tryRound (try/catch returning ok=false instead of reverting), so a boundary gap degrades to the backstop rather than a permanent lock.3
24h liveness backstop (the valve that makes deadlock impossible)
Even a genuine lone-spike defers only until
block.timestamp ≥ expiry + LIVENESS_WINDOW. After that 24h window the backstop accepts the round unconditionally. This is the liveness valve the v3.1 design lacked: there is always a future block at which settle succeeds.Constants and the invariant
The constants live inOracleManager; MAX_STALENESS is per-token (the Arbitrum Sepolia value is 90,000s):
The invariant statement:
I8 — Settlement liveness. For any swap, under any price path,Verified by 5 fuzz tests, ~1,280 runs:settle()succeeds byexpiry + LIVENESS_WINDOW + MAX_STALENESS + GRACE_PERIOD— the lone-spike check never permanently locks funds.
The resolution window
The I8 wording —expiry + LIVENESS_WINDOW + MAX_STALENESS + GRACE_PERIOD — reads as a generous additive sum. The actual usable window is narrower.
This is a documented design behaviour, not a bug. Surfaced in NatSpec on
OracleManager and pinned by a boundary test. The gap between the I8 wording and the real window is disclosed rather than restated to flatter the prose. The protocol behaviour (funds always settle, fairly, in bounded time) is correct.Where the deferral logic lives
Fork 2 — the no-last-look firm-quote pickoff
What the audit found
The audit flagged that firm quotes plus any clock-based validity create a one-sided stale-quote pickoff. The key insight is that signed payloads are bearer instruments: they persist beyond the off-chain engine’s cancel in any hand that copied them. When volatility gaps, the MM is short convexity and a searcher can submit the leaked bytes on-chain before the MM’s intended cancel can take effect. The conventional CeFi answer — “last look”, where the MM gets a final option to fade the fill — is forbidden by a hard rule, because it undercuts trustlessness and enables exactly the abuse pattern the audit flagged (MM fades at settlement). So the fix had to keep firm quotes and protect the MM, without any discretionary path.The fix: a deterministic oracle-anchored band
The band check is the primary defense; the short validity clock is a secondary leakage-window control.1
The quote carries an oracle anchor and a band
Every
SignedQuote includes quotePrice (the oracle price the MM observed at signing) and priceBandBps (how far the MM is willing to let the live price drift before the quote voids). The quote is per-market, never per-NFT — it carries no tokenId. See the MM rail and quote signing.2
createSwap reads the live oracle once — zero extra cost
At fill, the contract reads the live oracle price once — the same read that pins
P0 (_prepareSwap sets g.livePrice = oracle.getPrice(...)), so the band check adds no extra oracle cost. The deviation is bandDevBps = oracle.absBps(g.livePrice, quote.quotePrice).3
Auto-void on-chain, deterministically — not last-look
If
bandDevBps > quote.priceBandBps, createSwap reverts with PriceOutOfBand. There is no MM discretion at fill — the check is pure arithmetic on the live oracle and the signed quotePrice. The MM is exposed only to drifts within the band it chose (it priced for them), never to gaps that blow through it.absBps is the pure helper on OracleManager. In createSwapRouted the same logic is mirrored as a non-reverting boolean: a quote that drifts out of band is simply not usable, and the router falls back to the always-on pool rather than reverting.
Tightened validity window — the secondary control
The clock is the secondary defense, narrowing the leakage window:Why firm + band beats last-look
MM protection is therefore three deterministic on-chain mechanisms: the oracle band (kills the dominant pickoff), the short
validUntil (narrows the leakage window), and on-chain selective nonce invalidation (the MM flips one Permit2-style bitmap bit to cancel one quote — see I7).
Path A has no such vector
Path A — the cvAMM pool has no signed quote, so it has no quote-pickoff vector at all. It prices off the live oracle directly, signature-free. Fork 2 is a Path-B-only concern.
Acknowledged residuals — framed, not hidden
Where the band logic lives
testFuzz_I9_priceBandViolationReverts + testFuzz_I9_priceBandWithinBandAccepts, 256 runs each, both directions.
How the two forks relate to the rest of security
They protect liveness and fairness — not the I1 proof
Neither fork is part of the no-bad-debt proof (I1), which is structural and oracle-independent. Fork 1 guards settlement liveness (the (c) clause); Fork 2 guards MM fairness.
Both are now numbered invariants
Fork 1 -> I8, Fork 2 -> I9. Read each invariant’s precise statement, why it holds, and the Foundry fuzz test that verifies it.
Fork 2 lives inside the firm-quote / no-last-look design
The oracle band is the load-bearing reason firm quotes can be trustless without last-look. See the MM rail and quote signing.
Both depend on on-chain price pinning
The same on-chain Chainlink derivation that pins
P0 and P_T (never calldata) is what makes both the round-at-T pin and the single-read band check work.Where this is enforced — live
Network: Arbitrum Sepolia · chainId421614 · deploy block 274081134 (subgraph startBlock) · deployed 2026-06-05
Core protocol
Stylus & libraries
Oracles
Uniswap
Chainlink feeds
Demo tokens / pool / LP
Lifecycle swaps
Live create→settle lifecycle (2026-06-05, short 300s fee-500 market
0xb8bbd684f213d5833886ade7b531a6949d85522249881a2b5d46a5cc76e439c2):
- Path A (cvAMM convexity) — swapId 1, LP tokenId 3220 · V0 $270,531.28 · MaxIL $1,669.24 · premium $9.70 (0.58% of MaxIL) · settled payout $148.64 from ConvexityVault.
- Path B (MM routing) — swapId 2, LP tokenId 3221 · MM beat cvAMM ($8.93 vs $13.80) · MaxIL $3,215.65 · MM collateral locked $3,215.65 · settled payout $245.66 from MM collateral.
OracleManager (Fork 1 / I8) and InflexionCore (Fork 2 / I9). See the full deployment registry for every address.
Next: the trust model
What the off-chain matcher can and cannot do — and why Path A is immune to it entirely.