---
title: "Inventory management"
description: "Asset-liability accounting, the fixed coverage-driven inventory skew, the withdrawal haircut and the convex coverage wall, stated exactly as implemented"
audience: tech
type: explanation
status: live
lang: en
updated: "2026-09-04"
publish: true
---
# Inventory management

AIMM books every asset separately: each leg carries its own reserves, its own LP liabilities and its own risk parameters, with no pool-wide invariant tying them together. The ratio of the two, coverage, is the single state variable the rest of the pricing stack reads. This page derives it and the three mechanisms it drives, each stated exactly as the contracts compute it.

---

## 0. Units and notation

Every quantity below is an on-chain integer. Fixed-point bases (`Constants.sol`):

| Symbol | Base | Value | Used for |
|--------|------|-------|----------|
| WAD | 1e18 | $10^{18}$ | coverage, haircut ratio, prices |
| BPS | 1e4 | $10^{4}$ | 0.01% units: kappa, spline x-domain |
| PBPS | 1e6 | $10^{6}$ | 0.0001% units: dispersion, spread |

All division is Solidity integer division, truncating toward zero. Truncation is called out where it
changes an economic outcome. Symbol collision: $\kappa$ denotes **dispersion** in
[Liquidity Shaping](/docs/1-1-2-liquidity-shaping) and [Spread & Fees](/docs/1-1-4-spread-fees); this
document writes the convex coverage-wall strength as $\kappa_{\text{cov}}$ (`kappaCovBps`) to keep the
two apart.

Live figures come from a shipped stable pool (addresses: [2. Deployments](/docs/2-1-contract-addresses))
and from the shipped reference-roster parameters (per-parameter tables in
[Parametrization](/docs/1-1-7-parametrization)). Reserves move: treat reserve-derived numbers as a
dated snapshot. The parameters are stable.

---

## 1. Overview

AIMM uses **Asset-Liability Management (ALM)** in the lineage of
[Wombat](https://docs.wombat.exchange/concepts/coverage-ratio) and
[Platypus](https://medium.com/platypus-finance/platypuss-asset-liability-management-eli5-92a1ee85b17):

- **Reserves** $R$ (`Asset.reserves`, uint128, token units): tokens the pool holds.
- **Liabilities** $L$ (`Asset.liabilities`, uint128, token units): LP claims on that asset.
- **Coverage** $c = R/L$.

There is no pool-wide invariant. Each asset carries its own $(R, L)$ and its own risk parameters, and
the price is anchored to an external oracle mark rather than to a reserve ratio. Coverage enters
pricing at exactly three points:

| Mechanism | Function | Effect |
|-----------|----------|--------|
| Inventory skew $\psi$ | `Pricing.computeInventorySkew` | Shifts the quote center along the spline |
| Coverage toll $t$ | `Pricing._covToll` | Convex charge on draining an under-covered leg |
| Withdrawal haircut $h$ | `PoolLiquidity.applyHaircut` | Prices an LP exit out of a deficit |

It does **not** enter the depth axis: the denominator converting trade size into spline domain is the
leg's raw reserves (§4). Coverage moves the quote center and charges the drain; it never manufactures
depth the leg does not hold.

### 1.1. Beyond pool-wise invariants

Traditional AMMs enforce a pool-wise invariant: $x \cdot y = k$; Curve StableSwap's
$A n^n \sum_i x_i + D = A D n^n + D^{n+1}/(n^n \prod_i x_i)$; Balancer's $\prod_i x_i^{w_i} = k$;
Orbital/CCMM's spherical $\sum_i (r - x_i)^2 = r^2$. Each of those is simultaneously the pricing rule
and the solvency rule, so N-asset generalization requires new geometry. AIMM separates them:

- **Pricing** comes from the oracle mark, the inventory skew, and a chosen density profile.
- **Solvency** is per-asset $(R, L)$ bookkeeping.

Adding an asset therefore adds a row, not a dimension to an invariant.

---

## 2. Coverage ratio

### 2.1. Definition

$$c = \frac{R \cdot \text{WAD}}{L}$$

`Pricing.calculateCoverage` returns exactly this, in WAD, so $c = 10^{18}$ is 100% coverage. Two edge
behaviors:

- $L = 0$ returns `type(uint256).max`, not a revert. Every consumer treats that as "infinitely
  covered".
- The division truncates, so the stored $c$ is a floor. A leg is never reported as better covered than
  it is.

$R$ is the **economic** reserve, the full dual-ledger book $R = R_{\mathrm{liq}} + R_{\mathrm{inv}}$
when a yield hook is invested. Pricing and coverage always use full $R$; executable cash for
swap/flash/push is $R_{\mathrm{liq}}$ only, checked separately at settlement
(`PoolIOLib.settle`, `PoolIOLib.sol`). See [Pool Hooks](/docs/5-1-3-hooks).

### 2.2. Key thresholds

The saturation points are **protocol constants, not configuration**: the skew law hard-codes
$c = 1/2$ and $c = 2$ (§3), identically on every leg of every pool.

| Coverage | State | Skew | Behavior |
|----------|-------|------|-----------|
| $c \ge 2$ | Overcollateralized | $-100$ | Max discount, pool wants to sell the asset |
| $c = 1$ | Equilibrium | $0$ | Quote centered on the mark |
| $c \le 1/2$ | Undercollateralized | $+100$ | Max premium, pool wants to buy the asset |

Both are constants, so no write can move the slope or reprice an open book by shifting where the
arms saturate. Why those two points are the only admissible ones: §3.2.1.

---

## 3. Inventory skew (coverage → skew)

> $\psi$ maps to a position on the spline in
> [Liquidity Shaping §5](/docs/1-1-2-liquidity-shaping#5-inventory-skew-mapping-skew---depth).

### 3.1. Exact formula

`Pricing.computeInventorySkew`, in order:

$$
\psi =
\begin{cases}
-100 & L = 0 \\
+100 & c \le 1/2 \\
-100 & c \ge 2 \\
+\left\lfloor 200\,(1 - c) \right\rfloor & 1/2 < c < 1 \quad \text{(draining arm)} \\
-\left\lfloor 100\,(c - 1) \right\rfloor & 1 \le c < 2 \quad \text{(filling arm)}
\end{cases}
$$

with $c = R\cdot\text{WAD}/L$ from §2.1. Four properties of that law:

- **No inputs beyond coverage**: no per-asset parameter, no oracle read.
- **Return type is `int8`**, so $\psi \in [-100, +100]$ by both clamp and type.
- **The magnitude truncates toward zero**: a computed 14.62 becomes 14, never 15.
- **Positive $\psi$ means undercovered**, and the pool quotes the asset at a premium so it is bought
  back.

**The empty-liability sentinel is not a rounding case.** $L = 0$ returns $-100$, the maximum discount,
not $0$: a leg with reserves and no LP claims is pure surplus and the pool should give it away.

**What makes the skew earn rather than merely protect is the roster, not the formula.** The law only
prices imbalance: a premium on the side that drains, a discount on the side that refills. If the
discount is never taken, the leg sits at its clamp holding whatever the market wanted to sell. Inside
a core of correlated legs the two sides usually meet, because the flow that refills one leg is flow
that drains another: a single trade pays skew on the leg it takes while restoring coverage on the leg
it delivers, so inventory mean-reverts instead of accumulating. Correlation makes that likely, not
certain: legs that move together can leave the whole core displaced the same way at once. Why cores
are cut by asset class: [Pool Composition](/docs/2-3-pool-composition#why-several-cores-rather-than-one).

### 3.2. The two arms are not symmetric

The draining arm has slope 200, the filling arm slope 100. That is a conservation bound, not an
oversight: the two must not be tidied into one $200(1-c)$.

Both slopes come from the same requirement: a closed round trip must not refund more impact than the
outbound leg charged. The skew step and the volume traverse advance the same spline x-coordinate, so
the skew slope is pinned against the traverse's step. On the draining side the traverse already
out-steps and 200 is admissible. On the filling side it under-steps, and a 200 slope there would put
the return leg of a ping-pong ahead of the outbound one by exactly the gap, which a trader extracts
from LPs. Pinned by `ImpactConservation.t.sol`, not by a comment.

| Coverage | $\psi$ | Note |
|----------|--------|------|
| $\le 50\%$ | $+100$ | saturated |
| 75% | $+50$ | draining arm |
| 90% | $+20$ | draining arm |
| 100% | $0$ | centered on the mark |
| 120% | $-20$ | filling arm |
| 150% | $-50$ | filling arm |
| $\ge 200\%$ | $-100$ | saturated |

The arms meet their clamps exactly, so the whole $[0.5, 2.0]$ range carries information and neither
arm has a dead zone.

#### 3.2.1. Why saturation sits at 1/2 and 2

Write the saturation points as $[1/k,\ k]$. Two independent studies asked whether $k$ should be a
per-asset dial; both closed the question the same way.

**$k$ cannot reach the draining arm at all.** The round-trip conservation bound of §3.2 pins the
draining slope at 200 for every $k$. All $k$ could ever do is soften the filling arm, whose slope
would be $100/(k-1)$.

**$k = 2$ is the unique admissible value**, pinned from both sides:

- *Continuity.* The draining arm reaches $\psi = +100$ at $c = 1/2$. Saturating at $1/k > 1/2$
  (that is, $k < 2$) clamps the arm early, at $200(1 - 1/k) < 100$, so $\psi$ **jumps** at the
  saturation point. Measured leak across such a discontinuity: up to $+583$ pbps.
- *Conservation.* A filling slope $100/(k-1)$ above 100 lets a ping-pong round trip refund more
  impact than it charged (§3.2), which requires $k \ge 2$.

$k < 2$ is discontinuous and $k > 2$ only weakens inventory control. $k = 2$ is the single point
satisfying both, which is why 200, 100 and the clamps at $c \le 1/2$ and $c \ge 2$ are constants
rather than writable fields.

**The empirics agree.** $k = 2$ is also the LVR minimum on all three real tapes (ETH, BTC, SOL). More
importantly, the **coverage floor is fee-determined**: a drain stops where the skew's mid
displacement equals the spread, which puts
$c_{\text{floor}} \approx 0.85$ on a volatile leg and $0.82$ on a stable one **independently of shock
size**. The $[0.5, 2]$ clamp region is therefore unreachable in practice. Real-tape dwell below
$c = 0.5$: **0.14% to 0.70%** of the time for an unwalled volatile leg, and **0.000%** of 3.5 years
for a walled stable. A per-asset saturation point would be a dial on a region the pool does not
visit.

### 3.3. Measured skew, stable pool

$R$ and $L$ are a dated on-chain snapshot of a **pre-Arc reference roster**; $\psi$ is recomputed from
§3.1 against that snapshot rather than read back from chain, so the rows are worked examples of the two
arms, not verification. DAI, RLUSD and USDG are not listed on the Arc fleet - its stable core is USDC,
USDT, USDS, USD1 and PYUSD (`deployments/arc-risk-params.json`). Other
pages quote coverages captured at other times. The toll example in
[Spread & Fees §6.3](/docs/1-1-4-spread-fees#63-the-marginal-toll-a-level-shift-not-a-large-trade-wall)
reads RLUSD at $c = 0.9575$, from an earlier snapshot than this table. Reserves move; only the
parameters are stable.

| Asset | $R$ | $L$ | $c$ | Arm | $\psi$ |
|-------|-----|-----|-----|-----|--------|
| USDC (base) | 66,369.84 | 50,154.23 | 1.32332 | filling | $-32$ |
| USDT | 60,035.65 | 50,130.65 | 1.19758 | filling | $-19$ |
| DAI | 52,714.57 | 50,104.15 | 1.05210 | filling | $-5$ |
| RLUSD | 48,212.31 | 50,041.02 | 0.96346 | draining | $+7$ |
| USDG | 47,805.89 | 50,081.42 | 0.95456 | draining | $+9$ |

**The book is asymmetric in normal operation.** No leg sits at $\psi = 0$, so the tradeable spline
domain is split unevenly about the center before any other mechanism acts (see
[Liquidity Shaping §5](/docs/1-1-2-liquidity-shaping#5-inventory-skew-mapping-skew---depth)). Each
filling-arm row displaces half as far as the same distance from par displaces on the draining arm:
the conservation bound of §3.2 doing its work.

### 3.4. Relation to Avellaneda-Stoikov

Avellaneda-Stoikov displace the quote by $\delta_{\text{AS}} = \gamma q \sigma^2$, linear in
inventory $q$ and scaled by their risk-aversion coefficient $\gamma$ and variance $\sigma^2$. AIMM
keeps the linearity, replaces $q$ with the coverage ratio $c$ (the Platypus/Wombat imbalance metric),
and moves both the risk-aversion multiplier and $\sigma$ out of the skew entirely. What remains is an
inventory-driven *mid shift*, first instantiated on-chain by DODO PMM's oracle-anchored proactive
maker, driven by a coverage metric rather than by a slippage-on-invariant.

Risk aversion is not a dial on the skew: the round-trip conservation bound of §3.2 determines both
slopes uniquely. AIMM expresses it instead through `vegaBps`, which widens the quoting band against
$\sigma$, and through `minFeePbps`, which floors the spread.

$\sigma$ enters two other places: the dispersion that scales the density curve
([Liquidity Shaping §6](/docs/1-1-2-liquidity-shaping#6-dispersion-dynamics)) and the volatility term
of the spread ([Spread & Fees](/docs/1-1-4-spread-fees)). The separation lets the level shift and the
width be tuned independently, and keeps `computeInventorySkew` free of any oracle read.

See [Foundations §2.4](/docs/foundations) for the AS reference.

---

## 4. The depth denominator

> Canonical. [Liquidity Shaping §7](/docs/1-1-2-liquidity-shaping#7-the-depth-denominator),
> [Spread & Fees §6.5](/docs/1-1-4-spread-fees#65-coupled-configuration) and
> [Slippage & Price Impact §2.1](/docs/1-1-5-slippage-price-impact#21-aimm) restate the result only.

### 4.1. What it is

The denominator converting a trade size into a fraction of the spline domain is the leg's **raw
reserves**, with a zero-guard and nothing else:

```solidity
uint256 depth = reserves == 0 ? 1 : uint256(reserves);   // Pricing.quoteSwap, Pricing._priceEdgeHop
volumeFraction = amountIn * BPS / depth;
```

It is expressed in the profile asset's **own token units**, so the x-axis is anchor-free: it depends
neither on which asset the leg is anchored to nor on any price. The guard exists because
`_traverseCurve` divides by it; 1 wei is not a liquidity claim, it is a division floor.

### 4.2. Properties

| Point | Value |
|-------|-------|
| $R = 0$ | $D = 1$ wei (division guard) |
| $R > 0$, any coverage | $D = R$ |

Monotone in reserves, independent of $L$, independent of $c$, and constant per swap. The real depth
lever is the reserve balance itself.

Coverage does not reach this axis: an under-covered leg gets no virtual depth. Subsidizing the
denominator on a leg an informed flow is draining would shrink the traversed spline interval and so
**reduce** the impact charged on the drain, paying a trader to cycle exactly the leg that needs
reserves. Coverage is priced by the two mechanisms that charge instead: the skew shifts the mid
against the deficit (§3) and the convex wall tolls the drain (§6).

---

## 5. Withdrawal haircut

The haircut stops an LP exiting an under-covered leg at face value and leaving the deficit to whoever
stays.

### 5.1. Flow

```mermaid
graph TD
    Start[User requests withdrawal] --> CalcC[Read R, L]
    CalcC -->|R >= L or L == 0| NoHaircut[haircut = 0, pay face]
    CalcC -->|R < L| CalcH[deficit = R minus L over L]
    CalcH --> CalcF[factor from suppressor]
    CalcF --> CalcY[haircut = ceil of amount times ratio]
    NoHaircut --> UpdateR[Update reserves]
    CalcY --> UpdateR
    UpdateR --> UpdateL[Burn LP for FULL face]
    UpdateL --> Receive[User receives amount minus haircut]
```

### 5.2. Exact formula

`PoolLiquidity.applyHaircut`:

$$
d = \left\lfloor \frac{(L - R)\cdot\text{WAD}}{L} \right\rfloor
\qquad
\phi = \text{WAD} - \left\lfloor \dfrac{\eta \cdot \text{WAD}}{\eta_{\max}} \right\rfloor
\qquad
\rho = \min\!\left(\text{WAD},\ \left\lfloor \frac{d\,\phi}{\text{WAD}} \right\rfloor\right)
$$

$$
h = \left\lceil \frac{x \rho}{\text{WAD}} \right\rceil
\qquad
y = x - h
$$

with $d$ the coverage deficit, $\phi$ the suppressor factor, $\rho$ the haircut ratio, $h$ the haircut
and $y$ the payout; $x$ is the face amount, $\eta$ = `Asset.haircutSuppressorBps` (uint16) and
$\eta_{\max} = 20000$ is `PoolConstantsLib.HAIRCUT_SUPPRESSOR_FULL_BPS`.

The haircut is **linear** in the coverage deficit: no power-law exponent, no separate severity curve.

Four implementation facts:

- **Early exit at $R \ge L$ or $L = 0$** returns $(x, 0)$: full face, no haircut, no reads.
- **There is no $\eta \ge \eta_{\max} \Rightarrow \phi = 0$ branch.** `setAssetParams` rejects that
  range outright and `initAsset` seeds BPS, so no writer can reach it. Out-of-range stored state
  underflows here rather than silently zeroing the haircut: fail closed, not open.
- **The haircut rounds up** (ceil-div) so the payout rounds down. Without it a
  withdrawer could over-draw an under-covered reserve by up to 1 wei per call.
- **Liabilities burn at full face $x$**, not at $y$. The retained $x - y$ is exactly the exiting LP's
  pro-rata share of the deficit at $\eta = 0$, so a same-asset exit leaves $c$ where it found it
  rather than raising it; §5.5 does that arithmetic.

### 5.3. Suppressor

| $\eta$ | factor | Effect |
|--------|--------|--------|
| 0 | 1.00 | Full linear haircut |
| 10000 | 0.50 | Half haircut |
| 15000 | 0.25 | Quarter haircut |
| $\ge$ 20000 | - | **No haircut is possible**: the writer reverts - `setAssetParams` rejects this range outright (`PoolConfig`), so no zeroing branch exists |

Worked, at $c = 0.8$: $d = 0.2$. With $\eta = 0$, $h = 20\%$. With $\eta = 10000$,
$h = 10\%$. A 1000-unit exit therefore returns 800 or 900 units respectively, and burns LP for 1000
in both cases.

**Only the $\eta = 0$ row is reachable on a listed asset.** Two predicates compose to pin it:
`PoolConfig.requireNeverDepletable` rejects $\kappa_{\text{cov}} = 0$ at every writer, and
`PoolConfig.requireWallOk` rejects $\kappa_{\text{cov}} \ne 0$ paired with $\eta \ne 0$ at the same
writers. `initAsset` seeds $\eta = 10000$ and `setupOracleAndConfig` zeroes it the moment a walled
asset is listed. Every leg of the Arc fleet carries $\eta = 0$
(`deployments/arc-risk-params.json`); $\eta > 0$ is a dead field, and the non-zero rows above are the
arithmetic of a value the write path will not admit.

### 5.4. Cross-asset exit

`PoolLiquidity._quoteWithdrawCross` applies the haircut **twice**; the order is a security property:

1. Haircut on the **source** asset first, before the mark conversion. Skipping this would let an LP in
   an under-covered leg exit at full face out of a healthy leg, dumping the deficit on that leg's LPs.
2. Route the haircut face through `Pricing.anchorPathQuoteLp` (the normal swap path, including spread
   and coverage toll).
3. **Mark cap**: the payout is capped at the haircut face converted through the path's oracle
   **mark**, `min(amountOut, fair · markPrice · 10^(d_to - d_from))` (`PoolLiquidity._markCap`). The
   inventory skew is a level, and it is one-sided on an under-covered child (draining slope 200
   against filling 100), so a one-way LP conversion would monetize it out of the healthy destination
   leg. Trader swaps are immune - the level cancels on paired crossings - so only this path needs the
   cap. The decimal factor is carried explicitly because `markPrice` is a whole-unit WAD ratio while
   the amount walk is rescaled per leg; without it the cap misses by $10^{d_{to} - d_{from}}$ on a
   mixed-decimal pool, binding to dust one way and never binding the other.
4. Haircut again on the **destination** asset, because that leg's own coverage now applies.

Both depeg breakers run after the quote, which is state-identical to running them before in an
all-or-nothing transaction, and lets them hit the primed transient feed cache.

### 5.5. How coverage restores

Restoration is priced, never scheduled. The haircut acts on the event that matters, an LP leaving an
under-covered leg; what it does to coverage depends on which exit is taken.

**A same-asset withdrawal at $\eta = 0$ leaves $c$ exactly unchanged.** With $\phi = \text{WAD}$ the
haircut ratio is the deficit itself, $\rho = d = (L - R)/L$, so the payout is the withdrawer's own
pro-rata share of the reserve, $y = x(1 - d) = x\,R/L$. Settlement burns $\Delta R = -y$ and
$\Delta L = -x$ (`PoolLiquidity._applyWithdraw`, same-asset branch), giving

$$c' = \frac{R - x\,R/L}{L - x} = \frac{R\,(L-x)}{L\,(L-x)} = \frac{R}{L} = c$$

The exiting LP takes its share of the deficit with it and leaves the ratio where it found it. Only the
ceil-div dust the haircut keeps with the pool moves $c$, by at most 1 wei of reserve per call. Pinned
by `test_withdraw_coverage_neutral_when_suppressor_zero`. A suppressed haircut ($\eta > 0$) would make
this exit coverage-**raising**, at the LP's expense; $\eta = 0$ is the only value a listed asset can
hold (§5.3), so the coverage-neutral case is the only one on the fleet.

**Two exits do raise the source leg's $c$, and both are cross-asset**: `withdrawTo` into a different
token (§5.4) and `swapLiability`. Each burns the full face $x$ from the **source** leg's liabilities
and leaves the source leg's reserves untouched - the payout is debited from the destination leg
instead (`PoolLiquidity._applyWithdraw` cross branch; `PoolLiquidity.swapLiability`). The source leg
therefore moves to $c' = R/(L - x) > c$. The source-side haircut on those paths is not what restores
coverage: it exists so the exiting LP converts only $x\,R/L$ of face and cannot dump its deficit on
the destination leg's LPs.

The convex wall (§6) is the other priced restorer, tolling any swap that drains a walled leg further:
the toll is withheld from the gross output with no matching liability credit, so it raises $R$ against
unchanged $L$. Deposits move $c$ toward 1 from below (§8.1). Neither the haircut nor the wall reduces
liabilities on its own, and no background process rewrites coverage.

The one path that can lower `liquidityIndexWad` is `Pool.hookWriteDown`, which realizes an actual
loss on invested reserves.

---

## 6. Convex coverage wall

> Canonical derivation. [Spread & Fees §6](/docs/1-1-4-spread-fees#6-the-coverage-toll) owns the
> settlement order and the charge semantics; this section owns the potential and the clamps.

`Pricing._covToll` is the second coverage mechanism. Unlike the skew it is a **charge**, not a level
shift. It mirrors the reference simulator's `cov_q`.

### 6.1. Potential and toll

$$
Q(c) = \ln c - c + 1 \qquad (\le 0,\ \text{max } 0 \text{ at } c = 1,\ \text{concave, } \to -\infty \text{ as } c \to 0)
$$

$$
c_0 = \min\!\left(1, \frac{R\cdot\text{WAD}}{L}\right),
\qquad
c_1 = \min\!\left(1, \frac{(R - g)\cdot\text{WAD}}{L}\right),
\qquad
t = \min\!\left(g,\ \left\lfloor \frac{(Q(c_0) - Q(c_1))\cdot\kappa_{\text{cov}}\cdot L}{\text{BPS}\cdot\text{WAD}} \right\rfloor\right)
$$

where $g$ is the gross output before fees, in output-token units, and $\kappa_{\text{cov}}$ =
`RiskConfig.kappaCovBps`. Charged only when $Q(c_0) - Q(c_1) > 0$; the toll is withheld from the gross
output and retained in the output reserve, so it accrues to LPs and is never a mark shift.

### 6.2. Why the clamps are there

- **$\min(c, 1)$ on both endpoints.** $Q$ is non-monotonic: it decreases on **both** sides of
  $c = 1$. A raw endpoint difference lets a drain that starts over-covered cross the
  peg and land below it with $\Delta Q \le 0$, paying zero toll. Clamping restricts $Q$ to its
  increasing branch so the toll prices exactly the below-peg deficit and the over-peg portion stays
  free.
- **$g \ge R$ short-circuits to $t = g$**: a fill that fully drains the leg is tolled to zero output.
  `Pricing.swap` then reverts rather than settling a zero-delivery swap.
- **Charge-only, no rebate ledger.** A coverage-restoring trade drains the *healthy* leg, where
  $c \approx 1$ and $Q \approx 0$, so it pays approximately nothing. A round trip therefore strictly
  loses, which is the LP-safety property.

### 6.3. Round-trip behavior

Finite differences of $Q$ telescope to zero over any closed reserve loop **at constant $L$**. Since LP
fees accrue into `liabilities` mid-loop (§7), the telescoping is only approximate. The residual is
pool-favorable: higher $L$ gives lower $c$ gives more toll.

The wall is **output-only**: a same-pool spoke→spoke swap never outputs the hub, while a cross-pool
hop that takes the hub out pays hub $\kappa_{\text{cov}}$.

The protocol rule is that **every listed asset, the hub included, carries $\kappa_{\text{cov}} > 0$**;
`PoolConfig.requireNeverDepletable` rejects a zero at every writer. The Arc fleet carries
$\kappa_{\text{cov}}$ = 300 on stables, crypto and metals, 400 on FX and 600 on equities, sized so the
resting coverage $c_{\text{eq}} = \kappa/(\kappa + e)$ holds against each class's on-chain
`maxDeviationBps` as the worst-case single-push mark error $e$. Per-chain values live in
[2. Deployments](/docs/2-overview); they differ by deployment and nothing on this page should be read
as a fleet-wide statement. Read the live value with `getAsset`, never off a params file.

---

## 7. Settlement: what actually moves

`PoolIOLib.settle` is the only place reserves and liabilities change on a swap (there is no separate
`exec`: the split was considered and deliberately not made, since it would only thread `need` across
the boundary as an argument):

```text
aIn.reserves  += amountIn
aOut.reserves -= amountOut + protoFee
protocolFees[tokenOut] += protoFee
accrueLpFee(aOut, lpFee)         // aOut.liabilities += lpFee, index raised
```

Two consequences that are commonly stated wrong:

- **Settlement is endpoint-only.** Interior hops of a multi-leg path do not move any reserve. Only the
  two endpoint legs settle. This is what makes per-leg impact charging on interior hops incorrect (see
  [Anchor Path Pricing](/docs/1-1-3-anchor-path-pricing)).
- **Liabilities are NOT unchanged on a swap.** `PoolLiquidity.accrueLpFee`
  raises `aOut.liabilities` by the LP fee and raises the liquidity index
  to match. The LP fee is the output the reserve debit deliberately did not pay out, so $R_{out}$ and
  $L_{out}$ both rise by `lpFee` relative to a fee-free settlement, and $c_{out}$ moves accordingly.

The executable-liquidity check reads $R_{\mathrm{liq}} = R - \text{invested}$ fresh at settlement
and reverts `InsufficientAmount` if it cannot cover
`amountOut + protoFee + minLiquidity`.

---

## 8. ALM flows

### 8.1. Deposit

1. Pull $x$, compute `lpAmt = x * WAD / mintIndex(asset)`.
2. Reject if `lpAmt` rounds to fewer shares than the dead-share seed requires: a deposit too small to
   mint a share would otherwise donate reserves to existing LPs for free.
3. $R \mathrel{+}= x$, $L \mathrel{+}= x$. Coverage unchanged when the index is at parity.

Deposits are single-sided by construction: each asset's $(R, L)$ is independent, so there is no
pairing requirement and no forced ratio.

### 8.2. Withdrawal

1. Compute face $x$ from LP shares and the liquidity index.
2. $h$ per §5, $y = x - h$.
3. $R \mathrel{-}= y$, $L \mathrel{-}= x$ (full face). At $\eta = 0$ the gap is the withdrawer's own
   share of the deficit and $c$ is unchanged (§5.5). A cross exit debits $y$ from the **destination**
   leg instead, so the source leg's $c$ rises.

### 8.3. Swap

1. Skews $\psi_{in}, \psi_{out}$ per §3, depth denominators per §4.
2. Spline traversal per [Liquidity Shaping §8](/docs/1-1-2-liquidity-shaping#8-traversal).
3. Coverage toll on the output leg per §6, then the spread fee.
4. Settlement per §7.
5. $c_{in} \uparrow$, $c_{out} \downarrow$ (net of the LP-fee accrual on the output leg).

```mermaid
graph LR
    subgraph User Actions
        Dep[Deposit]
        With[Withdraw]
        Swap[Swap]
    end
    subgraph Pool State
        R[Reserves]
        L[Liabilities]
    end
    Dep -->|+x, +x| R
    Dep -->|+x, +x| L
    With -->|-y| R
    With -->|-x face| L
    Swap -->|+in, -out-protoFee| R
    Swap -->|+lpFee on out| L
```

---

## 9. Coverage classification

### 9.1. Per asset

| Coverage | State |
|----------|-------|
| $c >$ 200% | OVER |
| 200% $\ge c >$ 100% | ADEQUATE |
| 100% $\ge c >$ 50% | UNDER |
| $c \le$ 50% | CRITICAL |

Only deposits, withdrawals and swaps move a leg between these states; nothing runs on a clock (§5.5).

```mermaid
graph LR
    Over[OVER] -->|withdrawals| Eq[ADEQUATE]
    Eq -->|deposits| Over
    Eq -->|withdrawals| Under[UNDER]
    Under -->|deposits| Eq
    Under -->|coverage drops| Crit[CRITICAL]
    Crit -->|deposit, cross exit, or coverage toll| Under
```

### 9.2. Pool level

$$C = \frac{\sum_j R_j p_j}{\sum_j L_j p_j}$$

with $p_j$ the mark of asset $j$ in the pool's unit of account. This is an off-chain reporting
aggregate. No on-chain code path consumes $C$: every gate in the contract is per-asset.

---

## 10. Parameter reference

### 10.1. Per-asset `RiskConfig` (`IPool.sol`)

Two fields, and that is the whole struct. It is an ABI and memory type only: both fields are
**stored in `Asset` slot 2**, not in a mapping of their own, which is why a risk write and an asset
write touch the same word.

| Field | Type | Unit | Deployed (Arc) | Purpose |
|-------|------|------|----------------|---------|
| `flags` | uint16 | bitfield | `0x06` (`SWAP_ENABLED_BIT` \| `LIABILITY_SWAP_ENABLED_BIT`; `FLASH_ENABLED_BIT` is set on no leg) | `SWAP_ENABLED_BIT`, `LIABILITY_SWAP_ENABLED_BIT`, `FLASH_ENABLED_BIT`, the two halt bits |
| `kappaCovBps` | uint16 | bps | 600 stables, 1,500 FX, 2,000 crypto majors, 1,800 metals, 2,500 equities; each pool's hub leg at the maximum of its own spokes (κ=0 is rejected by `requireNeverDepletable` at every writer) | Convex coverage wall (§6). Ladder and its derivation: [Parametrization §6.2](/docs/1-1-7-parametrization#62-coverage-wall) |

### 10.2. Per-asset coverage sensitivity (`IPool.Asset`)

| Field | Type | Unit | Deployed (Arc) | Purpose |
|-------|------|------|----------------|---------|
| `haircutSuppressorBps` | uint16 | basis 20000 | 0 on every leg | Withdrawal haircut gentleness (§5.3); forced to 0 on a $\kappa$-walled leg, and every listed leg is walled |

The inventory skew carries **no** per-asset field: it is the fixed law of §3, identical on every leg.
Neither does the depth denominator, which is the leg's own reserves (§4).

---

## 11. Traditional AMM vs AIMM ALM

| Aspect | Traditional AMM | AIMM ALM |
|--------|-----------------|----------|
| Price source | Reserve ratio via invariant | Oracle mark, shifted by inventory skew |
| LP accounting | Share of the whole pool | Per-asset liability + liquidity index |
| Deposit | Both sides required | Single-sided |
| Withdrawal | Pro-rata across assets | Named asset, haircut on deficit |
| Rebalancing incentive | Arbitrage on the invariant | Skew (level) plus coverage toll (charge) |
| Undercollateralization | Implicit as impermanent loss | Explicit as $c < 1$, priced by skew, toll and exit haircut |

---

## 12. Security considerations

### 12.1. Bank-run resistance

1. **Withdrawal haircuts** make an exit from a deficit cost the exiting LP the deficit share (§5),
   removing the first-mover advantage that drives a run.
2. **Mid skew** gives a coverage-restoring trade the favorable side of the same shift a
   coverage-worsening trade pays (§3), so flow is priced toward equilibrium continuously.
3. **The convex wall** (§6) makes the marginal cost of draining an under-covered leg diverge, so the
   last of the reserve is never cheap.
4. **`minLiquidity`** floors the executable reserve, so a leg cannot be drained to zero even where
   $\kappa_{\text{cov}} = 0$ leaves no wall (§6). It is the last outflow backstop, but it is inert
   unless a deploy config raises it: `initAsset` writes 0.

### 12.2. Manipulation resistance

1. **External-mark pricing.** Quotes center on the keeper-pushed mark (`FeedMathLib.mark`). No quote
   input is derived from the pool's own reserves or its own trade history, so a flash-loan-and-swap
   cannot move the quote center within a block. Only realized inventory skew moves, and skew is a
   level: it cancels exactly on a round trip.
2. **The skew law takes no configuration.** A sign-inverted or over-steep skew is not a rejected
   write, it is an unrepresentable state: both slopes and both saturation points are constants
   (§3.2.1).
3. **Skew bounds** $\pm 100$ hard-cap the inventory-driven price displacement at one wall of the
   spline.
4. **Flow Guard** locks freshly minted LP receipt shares for `flowCooldownSecs`, against transfer and
   against burn alike (`LPToken._beforeTokenTransfer`), so JIT liquidity around
   a single block is impractical. There is no staking surface. See
   [Flow Guards](/docs/3-3-flow-guards#layer-2-flow-guard-block-level-mev-protection).

### 12.3. Circuit breakers

- `HALT_MASK` (`HALT_RISK_BIT | HALT_GUARDIAN_BIT` = `0x0041`) halts the asset, checked on every hop
  of every path.
- `FeedMathLib.gate` fails closed on stale, dead, or over-confident feeds.
- `BASE_DEPEG_HALT_BPS = 500` reverts the whole pool if the base mark leaves $\pm 5\%$ of parity.
- The per-asset depeg band is **feed-relative**: `OracleConfig.refFeedId` + `refBandBps`, armed at
  settlement by `PoolIOLib.priceBandGuardPath`, halting the leg when its mark leaves `refBandBps` of an
  independent reference feed's price. A discretionary "stop quoting this leg" call is the guardian
  halt, not a stored bound.

---

## 13. Related documentation

- [Liquidity Shaping](/docs/1-1-2-liquidity-shaping): the depth curve skew and depth feed into
- [Anchor Path Pricing](/docs/1-1-3-anchor-path-pricing): multi-leg composition and marks
- [Spread & Fees](/docs/1-1-4-spread-fees): the fee charged on top of the coverage mechanics
- [Slippage & Price Impact](/docs/1-1-5-slippage-price-impact): the full mark-to-execution decomposition
- [Toxic Flow Mitigation](/docs/1-1-6-toxic-flow-mitigation)
- [Parametrization](/docs/1-1-7-parametrization): full parameter reference
- [Invariants](/docs/1-1-8-invariants)
