Skip to main content
This is the adversarial index of Inflexion’s security: a one-row-per-vector ledger of every attack the protocol has been threat-modeled against, each mapped to its mitigation and to the exact place it is enforced — a numbered invariant, never a hand-wave. It is the companion to the invariants (what is guaranteed and why), the forks (the two open audit items, now closed), and the trust model (what the off-chain matcher can and cannot do). Where a vector touches a deeper page, the row links to it. The full enforcement set is on the live deployment — see the deployment registry for addresses.
How to read this ledger. Every row follows the same shape: vector → mitigation → where enforced. “Where enforced” is a Foundry-fuzzed invariant (I1–I10). Nothing here is “trust us” — each mitigation is code, and most are also driven by a fuzz test on the invariants page.

The master ledger

The fifteen vectors below are grouped by what they attack: LP payout integrity, oracle/settlement, quote & capacity integrity, economic griefing, depositor/custody safety, and reentrancy. Audit-finding tags (F-#N) reference the external audit summarized on the audits page.
Attack vectorMitigationWhere enforced
MM / underwriter bad debt under a large price moveFULL collateral locked at MaxIL at creation + payout capped at min(realized_IL, MaxIL). The cap is the min; structural, not heuristic. Holds for any price path.I1 + I2 — payout cap and collateral locked at creation (Path A / Path B)
Lone-spike Chainlink glitch at expiry (a single bad print pins a wrong settlement price)Settlement price is the Chainlink round-at-T; a “lone spike” (a round differing from both neighbours by ≥ LONE_SPIKE_BPS = 5%) is deferred. Real fast moves persist across rounds and pass.I8 — enforced in OracleManager (Fork-1)
Permanent fund lockup if an oracle deferral never resolvesA 24h LIVENESS_WINDOW backstop accepts the round unconditionally once the window passes; a missing neighbour across a Chainlink phaseId boundary is read tolerantly (try/catch) and degrades to the backstop, never a revert.I8 — enforced in OracleManager
Stale-quote pickoff — a leaked bearer-instrument SignedQuote replayed on-chain when vol gaps (F-#7, Fork-2)Oracle-anchored band: the quote carries quotePrice + priceBandBps; createSwap auto-voids it on-chain, deterministically, if absBps(P_live, quotePrice) > priceBandBps. No MM discretion at fill ⇒ not last-look.I9 — enforced in InflexionCore (Fork-2)
LP lies about the position’s range geometry (to inflate MaxIL/premium/V0)sqrtPa/sqrtPb are derived on-chain from positions().tickLower/tickUpper via TickMath; the position’s actual (token0, token1, fee) is cross-checked against the registered marketId. The LP can lie about neither the range nor the entry.Enforced in InflexionCore (MarketMismatch)
Settler chooses the settlement price (a permissionless settler sets payout anywhere in [0, MaxIL])P_T is derived on-chain from the Chainlink round-at-T via _oracleSqrtPriceX96, never from calldata. Restores I2 and I4.I2 / I4 — enforced in InflexionCore
LP picks a favorable entry price (to set MaxIL/premium/V0 in their favor)P0 is the on-chain Chainlink round at the LP’s block, clamped to within priceBandBps of the MM quotePrice. Bounded entry-timing freedom, blunted by premium scaling with MaxIL.Enforced in InflexionCore (I9 clamp)
External increaseLiquidity on the custodied NFT (inflate computed IL post-creation, F-#2)L is snapshotted once at creation; settle passes the stored s.liquidity to computeIL, never a re-read of the NFT. An attacker can grow on-chain L between create and settle with zero effect on payout.I6 — enforced in InflexionCore
Bearer-quote replay after off-chain cancel (F-#7)Permit2-style bitmap nonces (nonce = word<<8 | bit) + selective cancelNonces([...]): an MM voids exactly one quote by flipping one bit. A cancelled bit can never fill.I7 — enforced in InflexionCore
Capacity over-consumption from concurrent fills (F-#6)Atomic check-and-increment of consumedNotional[quoteId] in PHASE 3 effects, before any external call, guarded by the + g.V0 > maxNotionalV0 revert in PHASE 2. Capacity is denominated in V0, not collateral.I7 — enforced in InflexionCore
Dust-swap MM griefing (spam sub-economic positions, F-#8/F-#13)Floors: MIN_POSITION_V0 = $100, MIN_PREMIUM = $1. Positions and premiums below the floor are rejected at the PHASE-2 dust gate.Enforced in InflexionCore
Premium under-charge from integer division (F-#8)premium = ceilDiv(...) — rounds up, never down. An attacker cannot shave a fractional unit off the premium across many fills.Enforced in CvammPricing and InflexionCore
Overcharge the LP (push premium above the fair value + max load)By construction: Path A clamps total = min(baseLoad + skews, maxLoad); Path B requires loadBps ≤ maxLoadBps. Both also cap premium at MaxIL. No MM discretion, no graceful-degradation path.I10 — enforced in CvammPricing and InflexionCore
Sequencer-downtime exploitation (settle/create against a stale L2 state)_requireSequencerHealthy enforces sequencer uptime + a 1h grace period and fails closed on a future updatedAt.Enforced in OracleManager
NFT custody hijack (deposit a fake NFT, or redirect fee claims)ILVault.onERC721Received accepts NFTs only from the canonical NonfungiblePositionManager configured at deploy; claimFees recipient is forced to the registered LP.ILVault (custody model)
Locked-collateral utilization-gated withdraw failure (rehypothecation breach, F-#3)A yield adapter must be isInstantlyRedeemable(); locked collateral is never routed to Aave/Compound. Only the idle slice may go to instantly-redeemable wrappers (sDAI / tokenized T-bills).IYieldAdapter (vault rules)
Pool run on the cvAMM (mass withdrawal drains collateral mid-swap)Withdrawals are cooldown-gated (requestWithdrawal → withdraw after withdrawalCooldown); junior cannot be drawn below totalLocked. The same locked/free accounting also drives util_skew up before the pool over-commits.Enforced in ConvexityVault (JuniorBelowLocked)
Underwriting loss hits senior depositorstotalLocked ≤ juniorAssets is enforced at every lockCollateral; the waterfall takes juniorLoss first, seniorLoss only after — and by the invariant senior is never reached. Junior-first-loss.Enforced in ConvexityVault (SeniorProtectionBreached)
Forged MM quote (fabricate a SignedQuote)Every Path-B quote is EIP-712 signed and verified via OZ SignatureChecker.isValidSignatureNow (ECDSA + EIP-1271 contract signers). An unsigned or mis-signed quote is rejected.Enforced in QuoteVerification
Re-entrant settle via a malicious oracle/ilMathStrict CEI with status-flip-first: s.status = SETTLED is hoisted to immediately after the validity checks, before any external call, so re-entry on the same swapId hits the Status.ACTIVE precondition and reverts. (The one Slither medium that was fixed.)Enforced in InflexionCore

The vectors, in a little more depth

The table is the canonical artifact; the accordions below add the why for the rows that most reward it.
These three rows share one root defense: the LP can supply neither the geometry, the entry price, nor the settlement price.
  • MM bad debt is closed structurally — collateral is MaxIL and the payout is min(realized_IL, MaxIL), so payout ≤ collateral for any price path (the no-bad-debt math). The cap is the load-bearing piece; remove it and beyond-Pb IL is unbounded.
  • Settler price choice was a real hole: a permissionless settle once computed payout from a caller-supplied sqrtP_T, so a settler could set payout anywhere in [0, MaxIL]. The fix derives P_T on-chain from the Chainlink round-at-T, restoring I2 and I4 (an LP who would have profited vs hold gets exactly zero).
  • Entry-price gaming is bounded the same way: P0 is the on-chain round at the LP’s chosen block, clamped to within the MM’s priceBandBps. The residual entry-timing freedom is bounded and blunted because premium scales with MaxIL.
See price pinning for the full closed-hole writeup.
The settlement oracle is the most subtle surface, because it must be both correct (no glitched price pins a wrong payout) and live (funds never lock permanently). Fork-1 resolves the tension:
  • A glitched lone spike — a round differing from both neighbours by ≥ 5% — is deferred, but only until the 24h LIVENESS_WINDOW elapses, after which the backstop accepts the round unconditionally.
  • A missing neighbour round across a Chainlink phaseId boundary is read tolerantly via _tryRound (try/catch returning ok=false), so it degrades to the backstop rather than reverting forever.
  • Sequencer downtime is gated separately by _requireSequencerHealthy (uptime + 1h grace, fails-closed on a future updatedAt).
Documented design observation (not a bug): the spec-literal staleness require fires before the backstop can recover the round, so the actual usable round-at-T window is [expiry + LIVENESS_WINDOW, expiry + MAX_STALENESS) ≈ 1 hour wide (90,000 − 86,400 = 3,600s on Sepolia), not the full additive sum the I8 wording suggests. Why it’s acceptable: the 1h window is symmetric for LP and MM (preserves fairness), and past expiry + MAX_STALENESS funds still settle via a newer Chainlink round — Arbitrum majors tick frequently — just not via the round-at-T. Mitigation / transparency: surfaced in the OracleManager NatSpec and covered by a boundary test, not papered over. See the invariants page for the full I8 note.
Path-B quotes are bearer instruments — signed bytes that survive in any hand that copied them, beyond the off-chain engine’s cancel. Four mechanisms, all on-chain, bound what a leaked or forged quote can do:
  • Forgery is impossible — every quote is EIP-712 signed and verified via SignatureChecker.isValidSignatureNow (ECDSA + EIP-1271).
  • Stale-quote pickoff (the dominant pickoff, Fork-2) is killed by the oracle-anchored band: createSwap auto-voids the quote if the live oracle drifted beyond priceBandBps. Deterministic, no MM discretion ⇒ not last-look (a hard protocol rule).
  • Replay after cancel is blocked by Permit2-style bitmap nonces — an MM flips one bit to void one quote; a cancelled bit can never fill.
  • Capacity over-consumption from concurrent fills is blocked by atomic check-and-increment of consumedNotional[quoteId] before any external call.
Together these are I7 + I9. The router subtlety: in createSwapRouted, only the executed rail mutates nonce/capacity/lock — a quote that loses the route consumes nothing.
Three economic-edge attacks, three deterministic clamps:
  • Dust griefing — sub-economic spam — is floored at MIN_POSITION_V0 = $100 and MIN_PREMIUM = $1.
  • Under-charge from integer division rounds the wrong way only if you let it; Inflexion uses ceilDiv so the premium always rounds up.
  • Overcharge is impossible by construction — the load is clamped at maxLoad (Path A) or loadBps ≤ maxLoadBps (Path B), and premium is additionally capped at MaxIL. This is I10, lives upstream of settle, and never touches the no-bad-debt proof. See the load stack and I10 on the no-bad-debt concept page.
  • Pool run: withdrawals are cooldown-gated and junior cannot be drawn below totalLocked. The same locked/free accounting raises util_skew before the pool over-commits.
  • Senior loss from underwriting: totalLocked ≤ juniorAssets is enforced at every lock; the junior-first waterfall absorbs all underwriting loss before senior is touched. This is senior protection, not a capital guarantee — see tranches.
  • NFT hijack: ILVault.onERC721Received accepts NFTs only from the canonical NonfungiblePositionManager; claimFees is forced to the registered LP.
  • Rehypothecation breach: locked collateral never goes to a utilization-gated venue — a yield adapter must be isInstantlyRedeemable() (idle-only, sDAI/T-bills). See collateral models.
  • Reentrant settle: status flips to SETTLED before any external call, so a malicious oracle/ilMath re-entering settle reverts on the Status.ACTIVE precondition. Full CEI on the reentrancy page.

What the matcher cannot do — the structural backstop

Several rows above (forged quote, stale-quote pickoff, capacity) are also covered by a more general fact: the off-chain matching engine exists for Path B only, and its power is strictly bounded. It can censor, reorder, or drop Path-B quotes; it cannot steal funds, forge quotes, force a stale quote past the band, or touch Path A at all (which needs no engine). Settlement is non-custodial and on-chain, against the counterparty’s own collateral. This is the structural reason the table holds even against a malicious operator: the mitigations are enforced by the contracts, not by the relayer. Read the full trust model for the can/cannot grid and the direct-to-contract fallback.

Two things the ledger does not claim

Two caveats, each with its why and its mitigation:
Vol-only moves are not caught by a spot band. A residual of Fork-2: if implied vol jumps with little spot drift, the I9 spot band does not void the quote. Why it’s a small surface: on crypto majors, large IV jumps almost always co-move with spot, which does trip the band. Mitigation: the short validUntil (default 8s, band [5s, 15s]) bounds the leakage window, and the MM bears within-band drift because it chose the band.
Depositor and MM capital is not guaranteed. None of the mitigations above protect underwriter principal — they protect the LP’s payout (Claim A) and the protocol’s integrity. The pool is a volatility seller; it can lose. Why: underwriting is a paid risk transfer, not a riskless service. Junior is first-loss; senior is protected from underwriting loss while junior buffers, but takes the systemic tail (USDC depeg, oracle failure, contract bug). Mitigation: transparent tranching, the senior-protection invariant, and full disclosure — see tranches and Claim B below.
LPs are always paid with no bad debt in FULL mode — qualified: capped payoff + solvent USDC + oracle/settlement liveness.The no-bad-debt guarantee is exact only under the full clause:
  • FULL collateralization — the underwriter has posted exactly MaxIL per swap, locked for the life of the position.
  • Capped payoff — the payout is min(realized_IL, MaxIL). The cap is load-bearing: it is what makes payout ≤ collateral hold by construction.
  • Solvent USDC — the collateral numeraire (dUSDC) holds its peg and remains redeemable.
  • Oracle / settlement liveness — the price oracle stays live and settle() runs within the protocol’s settlement window.
  • No rehypothecation breach — locked collateral stays instantly liquid; it is never routed to utilization-gated venues.
Stated without these qualifiers, the guarantee is not correct.
Depositor capital is NOT guaranteed: the junior tranche is first-loss; the senior tranche is structurally protected from underwriting loss only, not systemic tail.Underwriting capital — whether supplied to the pooled cvAMM vault (Path A) or posted directly by a market maker (Path B) — is at risk:
  • Junior tranche — first-loss. Junior depositors absorb underwriting losses first and buffer the senior tranche.
  • Senior tranche — protected from underwriting loss, exposed to the systemic tail. The senior tranche is structurally shielded from ordinary underwriting loss while junior capital buffers, but it still takes the systemic tail once the junior buffer is exhausted.
No-bad-debt is a guarantee to the LP being paid (see the FULL-mode payout clause), not a guarantee that depositor or MM capital is preserved.

Where every mitigation lives — on-chain, today

The entire ledger is enforced on the live Arbitrum Sepolia deployment. InflexionCore carries the I1–I10 enforcement and CEI settlement; OracleManager carries I8 and the sequencer gate; the vaults carry I5 and the senior-protection waterfall. Network: Arbitrum Sepolia · chainId 421614 · deploy block 274081134 (subgraph startBlock) · deployed 2026-06-05

Core protocol

Stylus & libraries

Oracles

Uniswap

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.

The ten invariants

Each invariant’s precise statement, why it holds, and the Foundry fuzz test that verifies it.

The two resolved forks

Fork-1 (oracle liveness backstop, I8) and Fork-2 (oracle-anchored band, I9) — the two open audit items, now in code.

Trust model

What the off-chain matcher can and cannot do, and why the table holds against a malicious operator.

Audit history

The external audit (12 fixes + 2 forks), the quant audit, and the two Slither passes.