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 pricing invariant tying them together. The ratio of the two, coverage, is the single state variable the pricing stack reads. LP settlement is the one pool-wide quantity: every mint and exit settles at the pool rate (§5).
0. Units and notation
Every quantity below is an on-chain integer. Fixed-point bases (ConstantsLib.sol):
| Symbol | Base | Value | Used for |
|---|---|---|---|
| WAD | 1e18 | coverage, the pool rate , prices | |
| BPS | 1e4 | 0.01% units: kappa, spline x-domain | |
| PBPS | 1e6 | 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: denotes dispersion in
Liquidity Shaping and Spread & Fees; this
document writes the convex coverage-wall strength as (kappaCovBps) to keep the
two apart.
Per-parameter tables: Parametrization. Deployed values: pool composition.
1. Overview
AIMM keeps a reserve and a liability ledger per asset (asset-liability management):
- Reserves (
Asset.reserves, uint128, token units): tokens the pool holds. - Liabilities (
Asset.liabilities, uint128, token units): LP claims on that asset. - Coverage .
There is no pool-wide pricing invariant. Each asset carries its own 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 two points, and LP settlement at one:
| Mechanism | Function | Effect |
|---|---|---|
| Inventory skew | PricingLib.computeInventorySkew | Shifts the quote center along the spline |
| Coverage toll | PricingLib._covToll | Convex charge on draining an under-covered leg |
| In-kind exit bound | PoolLiquidityLib.exitMu | Caps a same-asset exit at min(c_leg, C) (§5) |
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 does not add depth the leg does not hold.
1.1. Beyond pool-wise invariants
Traditional AMMs enforce a pool-wise invariant: ; Curve StableSwap’s ; Balancer’s ; Orbital/CCMM’s spherical . 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 bookkeeping, settled for LPs at one pool-level rate (§5).
Adding an asset therefore adds a row, not a dimension to an invariant.
2. Coverage ratio
2.1. Definition
PricingLib.calculateCoverage returns exactly this, in WAD, so is 100% coverage. Two edge
behaviors:
- returns
type(uint256).max, not a revert. Every consumer treats that as “infinitely covered”. - The division truncates, so the stored is a floor. A leg is never reported as better covered than it is.
is the economic reserve, the full dual-ledger book
when a yield hook is invested. Pricing and coverage always use full ; executable cash for
swap/flash/push is only, checked separately at settlement
(PoolIOLib.settle, PoolIOLib.sol). See Pool Hooks.
2.2. Key thresholds
The saturation points are protocol constants, not configuration: the skew law hard-codes and (§3), identically on every leg of every pool.
| Coverage | State | Skew | Behavior |
|---|---|---|---|
| Overcollateralized | Max discount on the asset | ||
| Equilibrium | Quote centered on the mark | ||
| Undercollateralized | Max premium on 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)
maps to a position on the spline in Liquidity Shaping §5.
3.1. Exact formula
PricingLib.computeInventorySkew, in order:
with from §2.1. Four properties of that law:
- No inputs beyond coverage: no per-asset parameter, no oracle read.
- Return type is
int8, so by both clamp and type. - The magnitude truncates toward zero: a computed 14.62 becomes 14, never 15.
- Positive means undercovered: buyers of the asset pay a premium over the mark, and sellers are paid at most the mark.
The empty-liability sentinel is not a rounding case. returns , the maximum discount, not : a leg with reserves and no LP claims is surplus, so the pool quotes it at the maximum discount.
The formula prices imbalance; the roster is what makes it net out. The law charges a premium on the side that drains and a discount on the side that refills. If the discount is never taken, the leg sits at its clamp holding the asset that was sold into it. Inside a core of correlated legs the two sides usually meet: the flow that refills one leg drains another, so a single trade pays skew on the leg it takes and restores coverage on the leg it delivers. 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.
3.2. The two arms are not symmetric
The draining arm has slope 200, the filling arm slope 100. The asymmetry is a conservation bound. Merging the two into one reopens a round-trip refund.
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. Asserted by ImpactConservation.t.sol.
| Coverage | Note | |
|---|---|---|
| saturated | ||
| 75% | draining arm | |
| 90% | draining arm | |
| 100% | centered on the mark | |
| 120% | filling arm | |
| 150% | filling arm | |
| saturated |
The arms meet their clamps exactly, so the whole 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 . Two independent studies evaluated as a per-asset dial. Both rejected it.
cannot reach the draining arm at all. The round-trip conservation bound of §3.2 pins the draining slope at 200 for every . All could ever do is soften the filling arm, whose slope would be .
is the unique admissible value, pinned from both sides:
- Continuity. The draining arm reaches at . Saturating at (that is, ) clamps the arm early, at , so jumps at the saturation point. Measured leak across such a discontinuity: up to pbps.
- Conservation. A filling slope above 100 lets a ping-pong round trip refund more impact than it charged (§3.2), which requires .
is discontinuous and only weakens inventory control. is the single point satisfying both, which is why 200, 100 and the clamps at and are constants rather than writable fields.
Empirical support. is also the LVR minimum on all three real tapes (ETH, BTC, SOL). The coverage floor is fee-determined: a drain stops where the skew’s mid displacement equals the spread, which puts on a volatile leg and on a stable one independently of shock size. The clamp region is therefore unreachable in practice. Real-tape dwell below : 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. Asymmetric book
The book is asymmetric in normal operation. Legs rarely sit at , so the tradeable spline domain is split unevenly about the center before any other mechanism acts (see Liquidity Shaping §5). Each filling-arm row displaces half as far as the same distance from par displaces on the draining arm, which is the conservation bound of §3.2.
3.4. Relation to Avellaneda-Stoikov
Avellaneda-Stoikov displace the quote by , linear in inventory and scaled by their risk-aversion coefficient and variance . AIMM keeps the linearity, replaces with the coverage ratio , and moves both the risk-aversion multiplier and 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 (vega), which widens
the quoting band against , and through minFeePbps, which floors the spread.
enters two other places: the dispersion that scales the density curve
(Liquidity Shaping §6) and the volatility term
of the spread (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 for the AS reference.
4. The depth denominator
Canonical. Liquidity Shaping §7, Spread & Fees §6.5 and Slippage & Price Impact §2.1 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:
uint256 depth = reserves == 0 ? 1 : uint256(reserves); // PricingLib.quoteSwap, PricingLib._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 |
|---|---|
| wei (division guard) | |
| , any coverage |
Monotone in reserves, independent of , independent of , 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 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. LP settlement at the pool rate
Coverage is per leg; LP solvency is not. Every mint, exit and hook write-down settles at one pool-level rate. The legs share one balance sheet: a cross exit burns the full face from the source leg’s liabilities and pays out of another leg’s reserves, so a rate read off one leg is a rate the exiter moves by exiting. The only rate an exit cannot move is one whose numerator and denominator fall by the same factor.
5.1. Definition
PoolSolvencyLib.solvency:
summed over the pool’s leg roster (Pool.getLegs()), with = PricingLib.markToBaseWad (the
leg’s mark in base units) and every amount rescaled from the leg’s decimals to the base’s. WAD,
uncapped: an over-solvent pool reads and pays it.
- Physical backing cap. caps the stored book at what the pool holds, so a negative
rebase, an on-top transfer tax or an issuer clawback deflates instead of minting and paying
against units that are not there (
test_backing_cap_deflates_the_rate_on_an_unsynced_slash).investedis counted, so a hooked leg’s deployed tranche does not read as missing. - Gateable, not in band. The rate is usable only if every roster leg with a non-empty book
passes
FeedMathLib.gate(TTL, non-zero mark, confidence at or belowMAX_CONFIDENCE_HALT_BPS); one unusable mark returnsok = falseand no rate. It does not require every leg inside its reference band: one leg out of band leaves deposits and cross exits live (test_an_out_of_band_leg_does_not_freeze_the_pool). The band still guards the legs a payout prices through, at the sites that already arm it. A leg atreserves == liabilities == 0is skipped before its oracle read, so a dead feed on an emptied leg freezes nothing. - Edges. An empty roster reads with no oracle call (§5.4). A zero claim book is a rate
only over zero value:
A > 0, B == 0is a stranded book, reachable only by a pre-seed legacy leg’s full same-asset exit at , and it reads(1, ok = false)so no depositor can mint faceamtand claimA + amt. The owner’s owndepositre-seeds it at par and holds the residue (test_stranded_surplus_is_not_a_mint_rate_until_the_owner_reseeds).donaterefuses a leg with no liabilities, so it can never produce that state (testFuzz_B_zero_implies_A_zero). - Never on the swap or flash path. Quoting reads the leg’s own exactly as §2 to §4 and §6
describe. is read only by LP entrypoints and
hookWriteDown, and the per-leg wall floor is untouched. - Reads.
Pool.poolSolvencyWad()returns(cWad, ok). Every LP entrypoint on an armed pool emitsSolvencyUpdated(cWad, navBase, claimBase), numerator and denominator in base units.
5.2. Where the rate is applied
| Entrypoint | Rate | With a roster mark unusable |
|---|---|---|
deposit | mints face amt · 1e18 / C | reverts FeedUnavailable |
withdraw (same asset) | pays face · min(c_leg, C), less the exit toll | pays face · min(c_leg, C_lower), less the exit toll |
withdrawTo (cross asset) | converts face · C along the anchor path | reverts FeedUnavailable |
swapLiability | converts liabIn · C along the anchor path; the token output is credited as face amountOut · 1e18 / C | reverts FeedUnavailable |
donate | credits amt to reserves and face amt · 1e18 / C to liabilities, raises the index | reverts FeedUnavailable |
hookCreditYield | credits the realised amount and face amount · 1e18 / C, rate-bucketed | reverts FeedUnavailable |
hookWriteDown | cuts liabilities by ceil(loss · 1e18 / C) | reverts FeedUnavailable |
Exit toll. A same-asset exit pays half of (minFeePbps + staleness premium) on the payout, rounded up and split with the protocol at protoSharePct (PoolLiquidityLib._exitFee, PricingLib.exitSpreadPbps). A leg whose own feed is unusable pays the staleness premium saturated at its TTL, so going dark never cheapens the exit. A swap pays half of the full path spread, which adds the σ·ν and confidence terms (Spread & Fees §3.1), so exiting ahead of a predictable push and re-depositing after the cooldown costs less than a swap by half of those two terms. A cross exit and swapLiability pay the path spread inside their quote instead. previewWithdraw returns the net payout.
Every credit is booked at for the same reason the mint is: booked at par, a credit at buys face worth per unit paid and moves against every other leg; at it is neutral, . The outage column is the whole outage policy: no path degrades to par, and the only settlement that survives an unusable mark is the same-asset exit at the lower bound .
A deposit leaves unchanged. Mint-at- adds of value to the numerator and of value
to the denominator at rate , so the ratio holds, with floor rounding toward the pool
(testFuzz_deposit_preserves_the_rate). At the depositor buys less face than it pays; at
, more.
min(c_leg, C) is a delivery bound, not a fence. A leg cannot hand out more of its own token
than it holds. Paying face · C out of a drained leg until it empties is a run; allocating
pro-rata is race-free. When the pool is short of that token,
not of value, and the rest of the claim is reachable through withdrawTo, which sources another
token and pays that conversion’s spread. At the exit leaves unchanged; at
it leaves value behind and raises it
(testFuzz_same_asset_exit_never_lowers_the_rate). exceeds 1 when both the pool and the leg
are over-covered.
The degraded rate never pays more than the healthy one. With a roster mark unusable the same-asset
exit still settles, at min(c_leg, C_lower). is the lesser of the rate over the
usable legs and each unusable leg’s token-unit coverage : is a mediant of those numbers, so
their minimum bounds it whatever the unknown marks are. A bare 1 would pay more for an outage than
for a healthy pool whenever , and stale feeds correlate with the events that push below 1
(test_degraded_never_pays_more_than_the_good_path). A stale stamp would not do either: one taken
before a dark leg’s loss lets first movers exit ahead of it. Deposits and cross conversions have no
fallback: both credit claims against a pool whose value is unknown.
A cross exit cannot be sliced. PoolHooksLib.requireBacked runs on the source leg first. The
payout is priced off face · C, never off the source leg’s own coverage, which rises with every
slice because the cross branch burns and never touches
(test_cross_withdraw_50_slices_no_longer_beats_one_shot). The conversion is capped at the
oracle mark path, min(amountOut, fair · markPrice · 10^(d_to - d_from))
(PoolLiquidityLib._markCap): the inventory skew is one-sided on an under-covered leg and a one-way
LP conversion would otherwise monetise it. The output leg’s coverage is not charged again. One term
can still move : the output leg’s LP fee is credited as face lpFee · 1e18 / g at the cached
lastGoodC (PoolLiquidityLib._credit; undivided when , so a stale never
over-books). It is neutral to when and drifts only by the gap between the cache and the
live rate. swapLiability is the same conversion
stopping short of a payout: the quote output is a token amount and liabilities is a face book, so
the credit is amountOut · 1e18 / C, which re-denominates to
with no second haircut.
A write-down is fenced to its leg. hookWriteDown cuts liabilities by ceil(cut · 1e18 / C),
capped at the book, and scales the index with them, so
The leg whose venue lost pays and no other leg’s claim moves
(testFuzz_write_down_leaves_the_rate_unmoved). The remaining liability is floored at
ceil(liabBefore / idx), so the index stays at or above 1 and a leg survives losing its whole book
(test_total_loss_leaves_the_leg_live_and_depositable); the sliver that floor keeps lands in .
The write-down reverts FeedUnavailable while a roster mark is unusable, and the hook keeper
retries: a write-down at an unknown rate is a write-down of the wrong size.
5.3. What pooling costs, and what bounds it
A genuine NAV loss lands in and every LP in the pool pays pro-rata, stable-leg LPs included. The trade-off is a certain pro-rata loss instead of a first-exit advantage. Premium routes the same way. The coverage toll is retained in the output reserve with no liability credit (§6), so it raises for every LP, and each leg’s sizes the toll to that leg’s risk.
Two RiskConfig fields bound what one leg can put into , both checked after the liability
write at deposit, donate and the credited leg of swapLiability, and both soft: they bind
when a claim is credited and a leg’s notional drifts with its mark afterwards
(test_a_mark_move_carries_a_leg_past_its_cap_without_reverting).
maxLiabWeightBpscaps the leg’s share of the claim book (WeightCapExceeded); 0 or 10,000 disables it (a leg’s notional is part of the claim book, so its weight never exceeds 10,000).depositCapCodecaps the leg’s notional in whole base tokens, decoded asm·10^efromm<<4|e(Err.ExcessiveAmount). Every writer refusesm == 0, so0survives only on a pre-upgrade leg and means uncapped.hookCreditYieldis not capped: its per-day bucket bounds the drift and a cap read would revert syncs on a stale mark.
Both are written through RiskConfig: instantly when they tighten (setRiskConfigTighten), on the
queued UPDATE_RISK lane otherwise, with a cap raise bounded at 10× per op
(Admin §5.3). A capped leg whose own mark is unusable
refuses the credit (test_stale_mark_fails_closed_on_a_capped_leg).
Pooling also shrinks a self-drainer’s share of its own loss, because the denominator is the whole
pool. test_self_drain_then_cross_exit_never_profits,
test_mixed_self_drain_then_cross_exit_never_profits and test_fuzz_roundtrip_never_profits are
that defence.
5.4. Arming
A pool created on the current implementation is armed at initialize, and every initAsset
appends its leg, so the roster is always complete (test_roster_is_complete_by_construction).
A clone that predates the roster reads until the owner’s BACKFILL_LEGS op executes
(Admin.executeBackfillLegs, GOVERNANCE tier, one-shot, emits LegsBackfilled(legs)). The roster it
writes must name only listed assets, each once, and include the base. Unarmed, a same-asset exit
pays face · min(c_leg, 1), and cross exits and liability swaps convert full face.
6. Convex coverage wall
Canonical derivation. Spread & Fees §6 owns the settlement order and the charge semantics; this section owns the potential and the clamps.
PricingLib._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
where is the gross output before fees, in output-token units, and =
RiskConfig.kappaCovBps. Charged only when ; 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
- on both endpoints. is non-monotonic: it decreases on both sides of . A raw endpoint difference lets a drain that starts over-covered cross the peg and land below it with , paying zero toll. Clamping restricts to its increasing branch so the toll prices exactly the below-peg deficit and the over-peg portion stays free.
- Gross is capped at the argmax. Net output peaks where the leg lands on its floor , at (
PricingLib._grossCap)._settleQuoteclamps a gross up to 2% of past (COV_OVERSHOOT_BPS) to , and revertsThresholdViolation(gross, G*)beyond that or when . Swap, cross exit andswapLiabilityshare this quote, sogetSwapQuoterefuses the same sizes. With κ ≥ 50, : the arm of_covTollis reached only on a dust book. - Charge-only, no rebate ledger. A coverage-restoring trade drains the healthy leg, where and , 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 telescope to zero over any closed reserve loop at constant . Since LP
fees accrue into liabilities mid-loop (§7), the telescoping is only approximate. The residual is
pool-favorable: higher gives lower 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 .
Every listed asset, the hub included, carries ;
PoolConfigLib.requireNeverDepletable enforces it at every writer. The per-class ladder in
Parametrization §6.2 is sized so the resting coverage
holds against each class’s on-chain maxDevBps as the
worst-case single-push mark error . Values differ by deployment (pool composition); 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: a split was rejected because it would only thread need across the boundary as an
argument):
aIn.reserves += amountIn
aOut.reserves -= amountOut // both fee slices stay in reserves
aOut.liabilities += lpCredit // LP slice, liquidity index raised
aOut.liabilities += treasuryFace // protocol slice, booked as pending treasury sharesTwo consequences:
- 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).
- Liabilities are NOT unchanged on a swap.
PoolLiquidityLib._creditand_raiseIndexraiseaOut.liabilitiesby the LP fee and raise the liquidity index to match;PoolLiquidityLib.accrueTreasurybooks the protocol slice as pending treasury shares at the raised index, claimed throughAdmin.claimTreasury(Protocol Fee Collection). Both slices are output the reserve debit did not pay out, so and both rise by the fee (divided by when ) relative to a fee-free settlement, and moves accordingly.
The executable-liquidity check reads fresh at settlement
and reverts InsufficientAmount if it cannot cover
amountOut + minLiquidity.
8. ALM flows
8.1. Deposit
- Read (
PoolSolvencyLib.mintRate, which revertsFeedUnavailableon an unusable roster mark), then pull . - Face and
lpAmt = f * WAD / mintIndex(asset), less the dead-share seed. Reject iflpAmtrounds to zero: a deposit too small to mint a share would otherwise donate reserves to existing LPs at no cost. - , . is unchanged (§5.2).
Deposits are single-sided: no pairing requirement, no forced ratio. Denomination, price exposure and fee yield stay on the leg. Solvency does not: the legs share one balance sheet on every cross exit, so LPs hold a pro-rata claim on the pool’s NAV, paid in the token they chose (§5).
8.2. Withdrawal
- Compute face from LP shares and the liquidity index.
- Same asset: . Cross asset: converted along the anchor path and mark-capped (§5.2).
- , (full face). A cross exit debits from the destination leg instead, so the source leg’s rises while moves only by the output leg’s fee credit.
8.3. Swap
- Skews per §3, depth denominators per §4.
- Spline traversal per Liquidity Shaping §8.
- Coverage toll on the output leg per §6, then the spread fee.
- Settlement per §7.
- , (net of the LP-fee accrual on the output leg).
9. Coverage classification
9.1. Per asset
| Coverage | State |
|---|---|
| 200% | OVER |
| 200% 100% | ADEQUATE |
| 100% 50% | UNDER |
| 50% | CRITICAL |
Only deposits, withdrawals and swaps move a leg between these states; nothing runs on a clock (§5).
9.2. Pool level
(§5.1) is on chain and settles every LP entrypoint. It gates no swap: every swap-path check is per-asset.
10. Parameter reference
10.1. Per-asset RiskConfig (IPool.sol)
Four fields. It is an ABI and memory type only: every field is
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 | Purpose |
|---|---|---|---|
flags | uint16 | bitfield | capability, gating and halt bits (layout) |
kappaCovBps | uint16 | bps | Convex coverage wall (§6). Range [50, 10000] enforced by requireNeverDepletable at every writer; each pool’s hub leg at the maximum of its own spokes. Ladder and its derivation: Parametrization §6.2 |
depositCapCode | uint16 | m<<4|e base tokens | Soft per-leg notional cap on credited claims (§5.3) |
maxLiabWeightBps | uint16 | bps of the claim book | Soft per-leg weight cap on credited claims (§5.3) |
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 at min(c_leg, C), or another asset at face · C |
| Rebalancing incentive | Arbitrage on the invariant | Skew (level) plus coverage toll (charge) |
| Undercollateralization | Implicit as impermanent loss | Per leg, priced by skew and toll; pool-wide, shared pro-rata by every LP |
12. Security considerations
12.1. Bank-run resistance
- One settlement rate. Every exit is priced against and a same-asset exit is allocated
min(c_leg, C)pro-rata, so an earlier exit gets nothing a later exit cannot, and slicing a cross exit gains nothing (§5.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.
- The convex wall (§6) makes the marginal cost of draining an under-covered leg diverge as .
minLiquidityfloors the executable reserve as the last outflow backstop, but it is inert unless a deploy config raises it:initAssetwrites 0.
12.2. Manipulation resistance
- External-mark pricing. Quotes center on the keeper-pushed mark (
MarkWordLib.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. - 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).
- Skew bounds hard-cap the inventory-driven price displacement at one wall of the spline.
- 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.
12.3. Circuit breakers
HALT_MASK(0x0005:HALT_BITandHALT_ANCHOR_BIT, layout) halts the asset, checked on every hop of every path.FeedMathLib.gatefails closed on stale, dead, or over-confident feeds.- The base reverts
BaseDepeggedon every path through it once its mark leaves parity by more than its band: the per-asset depeg code times 250 bps (RiskConfig.flagsbits 12 to 15), 2,500 bps at code 0. - The per-asset depeg band is
OracleConfig.refBandBps, armed at settlement byPoolIOLib.priceBandGuardPath, halting the leg when its primary mark leavesrefBandBpsof the same lane’s reference (R) tier. On a USD-attested mark the leg’s depeg code adds a parity test against 1.0. A discretionary “stop quoting this leg” call is the guardian halt.
13. Related documentation
- Liquidity Shaping: the depth curve skew and depth feed into
- Anchor Path Pricing: multi-leg composition and marks
- Spread & Fees: the fee charged on top of the coverage mechanics
- Slippage & Price Impact: the full mark-to-execution decomposition
- Toxic Flow Mitigation
- Parametrization: full parameter reference
- Invariants