Security Overview

The contract-level view of AIMM security: the adversaries the design is built against, the four layers that answer them, and which limitations are deliberate rather than pending. Every layer is specified on its own page; this one is the threat model and the routing table.

SectionPage
Who may do what, and how long each action takesAccess Control
Transaction-time guards: reentrancy, flash, JIT, depeg halt, reference bandsGuards
What is emitted, where to read it, what alertsObservability
Internal and third-party audits, perpetual auditing, disclosure intakeAudits

Operational checklists live with their audience: Pool Deployment & Curation §7 for deployers and curators, Providing Liquidity §10 for LPs.

A taker holds no checklist. The slippage bound, the TTL staleness gate and the coverage toll are enforced on the swap path by the contracts, and the one caller-supplied input is minAmountOut (Basic Operations, Quotes & Routing).


1. Defense layers

LayerFocusSpecified in
EconomicExternal mark, inventory skew bounds, coverage toll§3
Access controlTimelocked governance, asymmetric guardian haltAccess Control
OperationalAsset halt, depeg halt, flow guardsGuards
CodeTests, invariant proofs, perpetual internal auditing; third-party audits pending§4, Audits

2. Threat model

2.1. Adversary capabilities

AdversaryCapabilitiesMitigations
Flash Loan AttackerUnlimited capital for single transactionNo write-on-swap (external mark); volatility-adaptive per-push deviation band
Whale TraderLarge positions, multi-block attacksExternal keeper mark; inventory skew bounds
MEV SearcherOrdering, sandwich; oracle-push OEVSlippage protection; symmetric spread + skewed mid; minFee; private keeper relay preferred
Oracle ManipulatorCompromised keeper; stale pushk-of-n quorum + revokeSigner; deviation band; confidence halt; depeg bands
Governance AttackerMajority controlTimelocks + grace windows (Access Control)
Smart Contract ExploiterCode bugsGuardian halt; beacon upgrade is fleet-wide after the GOVERNANCE tier delay with a 7-day grace, cancellable (Admin)

2.2. Trust assumptions

Only the Owner can act destructively protocol-wide. Every other role is bounded by what the contracts let it call, not by an expectation that it behaves. Loss columns read as the worst case with that principal fully compromised. The treasury is a pointer the Owner rotates on the GOVERNANCE tier, so the Owner row covers fee custody too.

RoleTrustBreaks contractsProtocol lossUser lossBound
OwnerTrustedyesyesyesUpgrades timelocked, guardian-cancellable
GuardianLimitednononoHalt, tighten and cancel only
TreasuryUntrustednoyesnoAccrued protocol fees only
NXR signersTrustednoyesyesk-of-n quorum, per-push deviation band
Oracle relayerUntrustednononoNo signing key, TTL fails closed
Pool deployerUntrustednonoyesIts own pool only, never official ones
UsersUntrustednononoEvery input validated at the boundary
  • Owner writes risk parameters, curves, oracle config and hooks, upgrades the fleet, and un-halts. On an official pool it is the only principal that can un-halt; a seated third-party pool admin clears its own risk bit immediately and the guardian bit after the LISTING delay. Upgrades are timelocked, risk parameters deliberately are not (Risk Steward). Fee-sink rotation runs the same queue-then-execute timelock, so it is vetoable while pending (Treasury).
  • Guardian cannot un-halt, upgrade or move value; compromising it stops the protocol, never drains it. A halt shuts deposits and exits as well as swaps, so it costs users a freeze rather than a loss (Guardian).
  • Treasury calls collectProtocolFees and nothing else, pulling the protocol’s own accrued fee share to itself. Reserves, liabilities, LP positions and parameters are out of reach (Treasury).
  • NXR signers are the mark’s authority. A price is accepted because k of n registered signers signed that exact blob, never because of who submitted it (Oracle Keeper).
  • Oracle relayer holds no signing key. ExternalOracleV5.push takes no sender check, nor does V4’s pushSignedV4; only a V4 session push binds msg.sender to the granted relay. Anyone may submit a validly signed blob. Delay, withhold and reorder are its whole surface: a liveness actor, and staleness is what the chain gates on.
  • Pool deployer calls a permissionless createPool, which seasons the CREATE2 salt with the deployer’s address and seats it as that pool’s admin. The seat carries halt, un-halt and the fenced risk lane on that pool only: no authority over official pools, other pools, or protocol state. Treat an uncurated pool as operated by whoever deployed it (Pool Deployment & Curation §2).

3. Layer 1: economic security

Manipulation resistance:

  • External keeper mark: quote source is an off-venue aggregate (NX-Rates), not pool reserve state; reserve moves cannot move the quote.
  • No write-on-swap: a swap never mutates a feed, so there is no accumulator to manipulate.
  • Inventory skew bounds: Pricing.computeInventorySkew returns a dimensionless int8 clamped to [-100,100], saturating at coverage c12 and c2. One skew unit maps to 104/200=50 bps of curve-x displacement, so the clamp caps the coverage-driven mid shift at ±5000 bps of the curve, then clamps into range. Spline depth and spread are separate terms, so this is not a cap on total price impact. The bounds are fixed in code, not operator-set.
  • Reserve floor: the per-asset minLiquidity is the only hard outflow gate: a swap, withdrawal or flash loan reverts InsufficientAmount if it would leave Rliq below it (PoolIOLib.settle; there is no exec function). There is no coverage-ratio drainage floor: coverage prices flow, it never blocks it.

Incentive alignment:

  • Coverage-aware pricing: the symmetric spread plus inventory-skew mid shift and convex coverage toll make coverage-worsening flow pay more (no directional fee term in the spread itself).
  • One LP settlement rate: every mint and exit settles at the pool-level solvency rate C, which no exit can move by exiting. A cross exit converts face · C and a same-asset exit is allocated min(c_leg, C) pro-rata, so there is no first-mover advantage and nothing to gain by slicing. A real loss on any leg is shared pro-rata by every LP in the pool; maxLiabWeightBps is the soft per-leg bound on that sharing. Coverage is never rewritten by a background process.

Layers 2 and 3 are specified elsewhere: Access Control for principals, timelock tiers and halt authority, Guards for the per-asset flag bits and risk thresholds.


4. Layer 4: code security

Solidity =0.8.36 (exact pragma), custom error types, and the build-time artifact guards that pin storage layout (ArtifactGuards.t.sol, AdminFlashUUPS.t.sol). The test suite covers unit, integration, fuzz and invariant cases; specific proofs are cited at the property they establish rather than claimed in aggregate here.

Third-party audits are pending; pre-launch assurance is internal, run as perpetual auditing (Audits).


5. Storage security

5.1. Standalone-contract storage isolation

Every contract is standalone with its own default storage layout, and cross-contract calls between distinct singletons (AdminPool, FlashPool) are standard external calls with no shared storage. Slot collisions between distinct contracts cannot occur.

Pool’s own internal DELEGATECALL targets are a separate case: the linked libraries PoolConfig, PoolLiquidity, Pricing and NUQuartic (see AIMM Overview §2.1 for the no-Diamond architecture). They take Pool’s $ as a storage parameter, so the compiler resolves the slots and there is no hand-mirrored layout to drift.

  • Each Pool is an ERC-1967 beacon proxy on PoolFactory (the factory is the beacon) with its own PoolStorage at slot 0. Cross-pool storage isolation is automatic; code is shared and swappable fleet-wide.
  • The Admin and Flash singletons are UUPS contracts behind ERC-1967 proxies (UpgradeGate, placed first so its 50 reserved slots lead the layout); their state is keyed by (pool, ...).
  • The reference Pool impl follows an append-only rule on PoolStorage: existing fields’ offsets and types are frozen across upgrades (new fields appended only). ArtifactGuards.t.sol pins it at build time, asserting Pool declares exactly one storage entry ($ at slot 0).
  • ERC-7201 namespaced storage is not used anywhere: plain default storage at slot 0 is sufficient since DELEGATECALL targets are fixed at compile time (no Diamond/module-registry pattern).

5.2. Transient storage (EIP-1153)

Transaction-scoped only, cleared automatically at the end of the transaction: reentrancy guards, oracle price caching, flash-loan state. The guards built on it, the exploit they close and their gas: Flow Guards.


6. Oracle security

AttackDefense
Flash loanNo write-on-swap (quote source is an external mark, not pool state)
Multi-blockVolatility-adaptive per-push deviation band + reference-feed halt, independent where the reference carries a disjoint signer set (Oracle Keeper)
Low liquidityThin books do not move the mark directly. The oracle is a discretionary contract: it decides its own sources and may include pool liquidity among them. Thin-book risk is carried by coverage and toll pricing, not by the mark
StalenessTTL-based freshness check (fail-closed) + staleness surcharge

Validation on the push path, all on-chain and all fail-closed:

  • A per-feed TTL revert.
  • A mandatory per-feed push clamp (maxDeviation, enforced in _checkDeviation; maxDeviation == 0 reverts at addFeed and updateFeed), volatility-adaptive and hard-capped, so a compromised signer quorum is bounded to a monitorable step per push rather than a one-shot move.
  • A k-of-n distinct-signer quorum per batch.

Multi-source aggregation happens off-chain in the keeper (NX-Rates); the chain sees a single mark. Formula, terms and quorum ceremony: Oracle Keeper.

There is one mode and no fallback switch. Degradation is a ladder of reverts:

ConditionEffect
age past half the TTLstaleness surcharge widens the spread
age past the TTLrevert StaleData (fail-closed)
confidence above MAX_CONFIDENCE_HALT_BPS (1,000 bps, strict)revert ThresholdViolation
base-token depeg past BASE_DEPEG_HALT_BPSrevert BaseDepegged (hub halt)
spoke mark outside refBandBps of its referencerevert PriceOutsideRefBand

Thresholds and readers: Oracle Keeper, Flow Guards.


7. Upgrade security

Pools are not per-instance immutable. Upgrades happen by swapping the implementation on the shared beacon at PoolFactory, which re-points every live pool at once, third-party pools included, with no opt-out and no version pinning. Admin and Flash upgrade separately as UUPS singletons under the same GOVERNANCE tier. Procedure, grace window, cancel authority and the storage-layout obligation: Admin.

The oracle is one immutable OracleProxy address per chain behind OracleBeacon; its implementation moves through a LISTING-tier (1 day) beacon upgrade, guardian-cancellable, and no per-leg repoint op exists. PoolFactory, OracleBeacon, the LPToken implementation and the linked libraries have no upgrade path at all: none sits behind a proxy (Admin).


8. Emergency procedures

Runbook and authority: Access Control for the lever list and delays, Guardian Routines for the procedure. Disclosure intake: Audits.


9. Known limitations

9.1. Economic bounds

LimitationBoundImplication
Max skew±100 (dimensionless)the skew offset saturates at the band edge; sign only, no separate premium parameter; see §3 for the mapping to curve bps
Anchor depthMAX_DEPTH = 4 (enforced, general)Each asset may anchor to any non-base parent within 4 steps and price against that parent’s mark - the tree is general, not fixed at one level. Whether a given pool uses that depth is configuration: there is no feature flag, depth is whatever the anchor column says, and a deep edge is one timelocked UPDATE_ANCHOR op away once its cross mark exists
Max pathLmax=2Dmax+1=9 nodes (8 legs)Unique tree path via the LCA; only the two endpoints settle. Depth bounds one walk, a path is two
Worst-case trade costNot bounded by config, by design. A spread widened by σ, confidence or staleness is the honest price of that risk; capping it would sell an underpriced quote and cap the pool’s defense exactly when it is most needed. The bound is the caller’s minAmountOut, exact and per tradeAn integrator must quote and enforce, not read a ceiling off config. The uint16 saturation of SwapQuote.spreadPbps is a field width, not a policy

9.2. Oracle limitations

The mark is an off-chain aggregate, signed by a k-of-n attester set and relayed by an unpermissioned submitter. Three attackable places, not interchangeable: the on-chain guards answer the second and third well and the first barely at all. Push-path mechanics: Oracle Keeper.

SurfaceAttacker must controlWhat the contracts doResidual
1. The source. The aggregate is computed faithfully; its inputs are notA majority of the volume-weighted venues behind one NX Rates composite, simultaneously, for at least one push intervalNothing that can detect it. The signature covers a correctly computed aggregate of manipulated inputs, so every check passes. FeedMathLib.deviationBand bounds how fast the mark may move; PoolIOLib.priceBandGuard compares it against a reference built from the same aggregationThe largest of the three. Bounded economically and off-chain, never cryptographically
2. The price provider. A signed price that existed on no venuek of n granted attester keys (k=2, n=3 on current deployments), or coercion of the replicas holding themNxrSignerSet._verifyQuorum: k distinct granted signers over one verifyingContract-bound EIP-712 digest. Magnitude is then clamped per push by FeedMathLib.deviationBand, σ is floored at the realized |Δp|/p, confidenceBps > MAX_CONFIDENCE_HALT_BPS (1,000) fails closed, sourceTs must strictly advance, and a sourceTs more than SOURCE_TS_FUTURE_SKEW_SECS = 5 ahead is rejected. Guardian revokeSigner and pauseFeed are immediateThe band caps the step, not the sum. The cumulative bound is the reference band, and it collapses wherever the reference shares the primary’s signer set, Arc today
3. The keeper. Withhold, delay, reorder, submit selectivelyThe keeper host and its funded EOA. No signing key: push authority comes from the signatures, msg.sender is unpermissionedIt cannot forge or edit: the digest commits to the whole blob. Reorder and replay die on the monotonic sourceTs (V1 reverts; V2 skips the record; the live V4 steps over the whole slot, silently, and its guard is one accepted write per slot per source second, not one per feed per block). Relabeling an old blob as fresh dies on FeedMathLib.obsAt =min( attested sourceTsMs, landing updatedAtSecs ): on V4 those are the same number, and the defense is instead the 6 h MAX_RECON_AGE acceptance window, applied on the read side as well. V4 has no maxRelayLagSecs. Withholding hits the TTL and fails closedWithholding is a halt, and a halt is still a win for a griefer. Delay inside the premium-free grace, min(ttlSecs/2,30s), is free

Surface 1 has no cryptographic answer; the defense is off-chain and statistical. NX Rates refuses to sign a composite carrying fewer than min_accepted_providers accepted venues (2 on mainnet), too few genuinely ticking legs, or a composite uncertainty above its class ceiling (20 bps pegged, 150 bps volatile). Per-source weight is capped at clamp(HHIwinsorized,·), so no single venue carries the mark. Each of those catches a minority venue moving: dispersion widens the composite confidence interval and the quote is refused. Under a coordinated majority move the legs agree, the interval stays tight, and the composite is signable and correctly signed. The deviation band and the reference band bound the rate of change, not correctness, and the reference is built from the same aggregation. What is left is the capital cost of moving a majority of the weighted book, plus monitoring. That cost is low for a thin asset, which is why listing is a curation decision (Pool Deployment & Curation §7).

Stale and off-market prices are refused before signing. Each NX Rates replica countersigns a peer-proposed blob only after re-validating every record against its own live market view: price within the feed’s cosign_tolerance_bps (per-feed, bounded to [0.01,5] bps); sourceTs skew and age bounds; agreement with its own provider-observation timestamp; σ within an understatement floor and an overstatement ceiling; confidence below the on-chain halt threshold. Below quorum nothing is served, so one compromised or lagging replica cannot get a bad price signed. The chain cannot verify that any of it happened: on-chain a batch is a valid k-of-n signature and a TTL. Co-signing defends against a minority compromise, the surface 2 guards against the quorum itself.

Where the reference oracle shares the primary’s signer set, surface 2’s cumulative bound is gone. One compromised quorum signs mark and reference together, refBand walks in lockstep and never trips, and only the per-push rate limit and the guardian levers survive. Per-chain deploy property, not a protocol property: verify by comparing SignerGranted logs on the two oracle addresses. Arc shares a set today (Oracle Keeper).

The keeper’s lever is choice, not content. The blob is public and push takes its authority from the signatures, so anyone holding a valid blob can land it: that bounds censorship, not timing. Within the TTL a relayer picks which fresh blob lands and when, and up to the grace window the quote carries no staleness premium, so a small deliberate delay is free. Same push-ordering OEV a public mempool grants any observer, which is why the keeper prefers a private relay (§2.1). No on-chain mitigation exists; the bound is operational:

  • Leader election with failover across a configured keeper set.
  • A per-feed staleness alarm at max(2·heartbeat,ttlSecs/2).
  • A periodic manifest-parity check against the NX Rates roster.

Cadence and outage bound how well the pool tracks the market and when it stops trading. They are not paths to a wrong price.

ConditionWhat happens
Low keeper cadence, stale markPriced first, then refused: the spread carries a staleness premium growing with στ past a grace of min(ttlSecs/2,30s), and past the feed’s TTL FeedMathLib.gate reverts StaleData. Shorter TTLs for faster feeds
Fast price movementWider spreads via σ. The per-push band widens with the attested Δt, so a genuine gap move ladders in rather than wedging the feed; past the 10dmax ceiling it does wedge, and the release is the owner’s timelocked requestFeedWidenexecuteFeedWiden; on the Arc V4 pair, which predates it, the V5 implementation upgrade through OracleBeacon (LISTING tier, 1 day); there is no per-leg repoint (Oracle Keeper)
Keeper or feed outageFail-closed: swaps revert past TTL, halt beating bleed. Multi-venue aggregation removes the single-venue outage, not the single-provider one
Reference feed parkedEvery armed spoke band fails closed once the reference passes its own TTL, so the reference must be relayed on the same discipline as the primary

PageRole
Access ControlPrincipals, guardian surface, halt authority, timelock table (SSoT)
DeployerDeploy order, CREATE3 addresses, handover
AdminOwner multisig: upgrades, listings, treasury rotation, emergency lanes
Risk StewardFenced param lane: clamps, fences, what fails closed
GuardianGuardian key: levers, argument traps, escalation
Oracle KeeperExternal mark, push triggers, signer quorum, TTL and bands, reference tier
TreasuryFee claims, untrusted, bounds
GuardsHalt bits, per-asset risk thresholds
Flow GuardsReentrancy, flash, JIT cooldown, base depeg halt, spoke reference bands
Guardian RoutinesOperational runbooks and release delays
ObservabilityWhat is emitted, where to read it, what alerts
AuditsInternal and external audits, perpetual auditing, bug bounty
Internal Audit 2026-09Pre-launch internal audit report
Admin modulePer-pool admin ops
Risk DisclaimerLegal risk surface