Configuration parameters

Every knob a pool carries, with its units, the formula it feeds, and the bound that constrains it. Also the map of who may write what: four lanes with different authority and different latency, and a fence set that decides which of them can move a parameter without a timelock. Quantities that are not tunable are marked as such; several of the dials operators expect to find are protocol constants.

Per-deployment values: 2. Deployments. Units: PBPS (106), BPS (104), WAD (1018), or raw.

1. Contents


2. Who may set what

Six lanes write parameters, each with its own authority and bounds. Every parameter belongs to exactly one primary lane.

LaneAuthorityBoundLatency
Owner, immediate_onlyPoolAdminAdminParamsLib.set defensive-tighten test must hold, plus the armed minFeeHardMinPbps floor0
Owner, timelocked_onlyPoolAdminnone beyond validation1 hour to 7 days by op
Steward, boundedperms bit 18 (PERM_RISK_STEWARD) holder; the owner is excludedRiskFences hard bounds + a 24 h cumulative lowering envelope0
Steward, one-wayperms bit 18 holder, or the ownerdirection only: the write must reduce risk against live state0
Guardian, haltsperms bit 17 (PERM_GUARDIAN) holderhaltAsset / batchRiskOp halts and the one-way coop clear (haltAsset src 0x0800); no risk-config write0
Keeper, off-chainkeeper config onlynever written on chainn/a

The one-way rule. A write that only reduces risk executes instantly and carries no magnitude bound; the same field in the risk-up direction queues at the tier of its consequence: risk numbers at TUNING, listings and hooks at LISTING, structure at GOVERNANCE.

LeverRisk down: instant, byRisk up: queued
swap / liability-swap / flash switch (*_ENABLED_BIT), each on its ownclear: steward, owner (setRiskConfigTighten)set: UPDATE_RISK, TUNING
deposit gate (DEPOSIT_GATED_BIT)arm: steward, owner (setRiskConfigTighten)clear: UPDATE_RISK, TUNING
coop fill switch (COOP_ENABLED_BIT)clear: steward, owner (setRiskConfigTighten); guardian, owner, seat (haltAsset src 0x0800)set: UPDATE_RISK, TUNING
coop terms (coopDiscBps, coopRebateBps)disc down / rebate up: owner (requestAssetParams), steward inside fencesdisc up / rebate down: TUNING; steward inside maxDeltaBps/24 h; never past the coop fences
kappaCovBpsraise: steward, owner (setRiskConfigTighten)cut: UPDATE_RISK, TUNING
depositCapCode, maxLiabWeightBpslower: steward, owner (setRiskConfigTighten)raise or disable: UPDATE_RISK, TUNING
minDispersionPbpsraise: steward, owner (raiseMinDispersion)cut: UPDATE_PROFILE, TUNING
minFeePbpsraise: owner (requestAssetParams), steward inside fencescut: TUNING; steward inside maxDeltaBps/24 h
vegaBpsraise: owner (requestAssetParams), steward inside fencescut: TUNING; steward inside maxDeltaBps/24 h
refBandBps (depeg breaker)narrow, not below MIN_REF_BAND_BPS (50): steward, owner (narrowRefBand)widen: UPDATE_ANCHOR, GOVERNANCE
flowCooldownSecsraise: owner (setFlowCooldown)cut: UPDATE_FEES, TUNING
yield hookremove: steward, owner (clearAssetHook: sync, recall, drop; recall-only on a remainder)attach or replace: UPDATE_HOOK, LISTING
hook buffer / credit caplower: owner (YieldHook)raise: LISTING / TUNING
halt (asset)halt: guardian, owner, or the pool’s seatunhalt: owner, instant (deliberate exception); the seat only once the guardian’s notice (LISTING delay from the first halt) has run
halt (mark-store lane)halt: guardian, owner, steward (halt(lane, tierMask))unhalt: owner, instant

A same-asset withdraw has no switch: only a halt stops it. A cross-asset withdrawTo is a liability swap plus a same-asset exit, so it needs LIABILITY_SWAP_ENABLED_BIT on both legs. Deposits have no per-leg off bit: arming the gate restricts a leg to allowlisted depositors, instantly.

The direction test runs against state read in the same call, so there is no request-to-execute gap for it to go stale in. Two moves stay timelocked regardless of direction because neither is comparable in one dimension: a curve repoint changes shape, and a liquidity floor raise gates LP exit. A compromised steward or guardian can cause denial of service, not a loosening: it can only tighten. The owner revokes a steward instantly by clearing its bit (AccessControl.setPerms(who, PERM_RISK_STEWARD, false)), and the tightened values unwind on the ordinary queue.

Owner-immediate is not unbounded. Admin.requestAssetParams applies a write immediately only if bootstrapSealed[pool] is false, or if AdminParamsLib.set classifies it as a defensive tighten:

  • minLiquidity unchanged
  • minFeePbps non-decreasing
  • vegaBps non-decreasing
  • coopDiscBps non-increasing and coopRebateBps non-decreasing

Anything else queues at TUNING with a request-time snapshot of the five fields (minLiquidity, minFeePbps, vegaBps, coopDiscBps, coopRebateBps); at execute a field the op moves that moved underneath reverts AssetParamMoved. Independently, an armed minFeeHardMinPbps fence floors even the owner path: lowering below a fence requires two transactions, setRiskFences first.

The instant lane needs no ceiling of its own. The two-sided minFeePbps bound of §3.1 applies on every write path whichever lane the write arrives on, so it binds the untimelocked lane as tightly as the timelocked one. Raising it would need a lane-specific bound shipped with it.

A vega raise is instant on the owner lane. On an interior-capable leg live dispersion is minDispersionPbps + σ·vegaBps/BPS against the curve’s PricingLib.dispersionCap, and a route through the leg fails closed above it. PoolConfigLib.setAssetParams therefore refuses a raise that breaches the cap at the leg’s live σ, on every lane: the same write-time guard a floor raise gets from setProfile. σ can still rise after the write. A leaf leg is clamped at MAX_DISPERSION_PBPS and owes the cap nothing.

Never auto-moved. minLiquidity is owner-only: setAssetParamsBounded reverts outright on any change to it.

RiskConfig follows the same asymmetry. setRiskConfigTighten is the one-way lane above, the one entry for every RiskConfig field (a κ raise is the live config with κ swapped): steward or owner, instant when every field tightens. Any other direction is a queued UPDATE_RISK, and a queued depositCapCode raise is bounded at 10× per op (§6).

Every instant write voids the queued op that would undo it. Queued payloads are absolute, so an op authored before a tighten restores the old value on execute. setRiskConfigTighten voids a live UPDATE_RISK that is looser on any field; raiseMinDispersion a lower queued floor; setFlowCooldown a shorter queued cooldown in UPDATE_FEES; narrowRefBand clamps a wider band in a queued UPDATE_ANCHOR that keeps the live reference feed to the narrowed one in place (QueuedRefBandClamped; a reference move or a disarm is left alone), so the 7-day op keeps its eta and a steward never vetoes a re-anchor; clearAssetHook voids a queued install. A queued op at least as tight survives and the tighten still lands. The void checks read queued words raw, never abi.decode, and key on the exact length requestOp pins per op (an opType with no length entry admits only an empty payload), so an out-of-range payload cannot wedge a lane; execute decodes, so it cannot land either. A vega raise needs no void: a queued op that moves vega fails its compare-and-swap. A queued feed widen fails its own bounds check once a tighten lands under it.

Timelocked operations. All are queued by the generic Admin.requestOp(pool, opType, subject, payload) and cancelled by Admin.cancelOp(pool, opType, subject); only the execute side is named per op. opType is IPool.OpType, subject is the left-padded token address for token-keyed ops, the curve id for UPDATE_CURVE, and ignored for the four pool-wide ops (MIGRATE_BASE_TOKEN, UPDATE_TREASURY, UPDATE_FEES, UPDATE_POOL_ADMIN). Delays are deploy-time data: a packed ConstantsLib.Tier schedule handed to the AccessControl constructor, read via ConstantsLib.delayOf. ConstantsLib.PROD_DELAYS is GOVERNANCE 7d, LISTING 1d, TUNING 1h.

opTypeTierDelayExecuteEffect
UPDATE_PROFILETUNING1 hourexecuteUpdateProfilerepoint curveId and the dispersion band
UPDATE_RISKTUNING1 hourexecuteUpdateRiskRiskConfig: flags, kappaCovBps, depositCapCode, maxLiabWeightBps
UPDATE_FEESTUNING1 hourexecuteUpdateFeesprotoSharePct, flashFeePbps, flowCooldownSecs
ADD_ASSETLISTING1 dayexecuteAddAssetlist a new leg
UPDATE_CURVELISTING1 dayexecuteUpdateCurveinstall a shared curve under a new id (an installed id is immutable)
UPDATE_HOOKLISTING1 dayexecuteUpdateHookper-asset yield hook target and flags
UPDATE_TREASURYGOVERNANCE7 daysexecuteUpdateTreasuryfee-sink address
SWEEPGOVERNANCE7 daysexecuteSweeprecover an unlisted token to treasury()
UPDATE_ANCHORGOVERNANCE7 daysexecuteUpdateAnchorre-anchor, atomic with the oracle config
MIGRATE_BASE_TOKENGOVERNANCE7 daysexecuteMigrateBaseTokenbase token
BACKFILL_LEGSGOVERNANCE7 daysexecuteBackfillLegsarm pool-level solvency on a pre-roster clone
UPDATE_POOL_ADMINGOVERNANCE7 daysacceptPoolAdminpool-admin seat handover

There is no oracle repoint op: OracleConfig is written at listing and by UPDATE_ANCHOR, and names a store lane, not an address; the store moves only with the Pool implementation, through the fleet upgrade (PoolFactory.executeReferenceUpgrade, GOVERNANCE, which calls migrateFrom before any pool moves).

UPDATE_ASSET_PARAMS is queued only by requestAssetParams itself and is not requestable through requestOp; it lands via executeUpdateAssetParams.

Immediate owner functions: addAsset and setCurve (pre-seal only), requestAssetParams, setRiskConfigTighten, raiseMinDispersion, narrowRefBand, clearAssetHook, setRiskFences, haltAsset / unhaltAsset (HALT_BIT), batchRiskOp, setFlowCooldown (raise-only), collapseAnchor, sealBootstrap; treasury only: claimTreasury; steward: setAssetParamsBounded, setRiskConfigTighten, raiseMinDispersion, narrowRefBand, clearAssetHook; guardian: haltAsset, batchRiskOp (halt), collapseAnchor, cancelOp (the last two on protocol pools only; on a seated pool cancelOp is the seat’s).

Re-anchoring is never instant: a re-anchor is a re-rooting, so UPDATE_ANCHOR queues at the GOVERNANCE base-migration tier and carries the oracle config in the same inseparable payload. The only instant anchor move is the guardian’s collapseAnchor, which may move a leg toward the root only, and halts it in the same call.


3. Asset configuration

IPool.Asset, stored at PoolStorage.assets[token], written by Admin.addAsset / requestAssetParams. Three storage slots. The packing is a quote-path decision: a quote reads exactly two cold Asset slots per leg, and the whole pricing shape (band, curve, fee floor, vega, decimals) arrives in one of them.

FieldTypeUnitsMeaning
reservesuint128token weitokens the pool holds; also the traverse depth denominator
liabilitiesuint128token weiLP claim, raised by deposits and LP fees
anchoraddress-parent in the anchor tree; 0 is not a sentinel, every listed leg has one
minLiquidityuint96token weiexecutable-reserve floor; swaps below it revert
liquidityIndexWaduint72WADshare-to-value index; LIQUIDITY_INDEX_INIT_WAD = 1e18
minDispersionPbpsuint32PBPSdispersion floor and additive base; checked at the write under the curve’s dispersionCap. There is no per-asset ceiling field (§5)
curveIduint16pointerid of the pool’s shared curve; 0 is refused at config, not a fallback
minFeePbpsuint16PBPSper-leg spread floor at σ=0
vegaBpsuint16base 10,000volatility sensitivity
depositCapCodeuint16m<<4|edeposit cap, m·10^e whole base tokens of leg notional; binds at credit time only (§6)
decimalsuint8-bound to the token at listing, not configurable; decimals != 0 is the not-configured sentinel
deadSeedPow10uint8power of tendead-share seed in the token’s own unit; 0 = decimals default, bounded at decimals + 3
flagsuint16bitfieldfeature and halt bits (§6.1)
kappaCovBpsuint16bpsconvex coverage-wall strength (§6.2)
maxLiabWeightBpsuint16bps of the claim booksoft cap on the leg’s share of the pool claim book at credit time; 0 = disabled (§6)
oracleBitsuint8bitfieldstore lane (bits 0..5), INTERNAL (bit 6), UOA (bit 7); the packed OracleConfig (§7)
refBandBpsuint16bpsreference band; bits 12..15 carry the depeg code instead (DEPEG_MASK)

flags, kappaCovBps, depositCapCode and maxLiabWeightBps are the RiskConfig quartet: IPool.RiskConfig is an ABI and memory type only, and all four fields are stored here, in Asset slot 2, not in a mapping of their own. The inventory skew takes no per-asset field (Inventory Management §3), and the peg used by INTERNAL mode is a constant in the synthetic feed rather than a stored field (§7).

3.1. Config-time validation

Enforced on every write path (PoolConfigLib.sol):

RuleRationale
minFeePbps >= MIN_FEE_PBPS (= 1)1 PBPS = 0.01 bp is the finest on-chain quantum
minFeePbps <= ONE_PCT_PBPS (= 10,000)global write bound; replaces the per-asset ceiling and the instant-lane one at once (§2)
every listed asset including the hub: 50 <= kappaCovBps <= 10000PoolConfigLib.requireNeverDepletable; _covToll is output-only so hub κ prices taking the hub out
a spoke’s κ never exceeds its hub’sPoolConfigLib.requireHubKappaDominant: the hub is the output leg of every stale-spoke drain
depositCapCode >> 4 != 0a zero mantissa is refused on every writer; 0 only survives on a pre-upgrade leg and means uncapped
maxLiabWeightBps <= 10000bps of the claim book
an asset may not be listed without a curveone pricing law per edge; there is no shapeless fallback
minDispersionPbps <= 900000 (MAX_DISPERSION_PBPS) and non-zero → default 1000keeps _calculateDispersion’s floor meaningful and the clamp branch well-defined (PoolConfigLib.sanitizeDispersion)
minDispersionPbps <= PricingLib.dispersionCap(curve) on an interior-capable leg; <= MAX_DISPERSION_PBPS otherwisechecked, never clamped: a floor past the cap configures a route outage at every σ, zero included, because an interior leg’s swing would fail closed (§5)

3.2. Liquidity floor

minLiquidity is enforced on executable liquidity (R-Rinvested), pre-outflow, against amountOut + minLiquidity:

uint256 need = q.amountOut + aOut.minLiquidity; uint256 liq = aOut.reserves > inv ? uint256(aOut.reserves) - inv : 0; // post-recall if (liq < need) revert Err.InsufficientAmount(liq, need);

A fully hook-invested asset can hold R≥ minLiquidity and still revert every swap. Full form: Invariants I-14.

3.3. Depeg bounds live on the oracle config, not on the asset

A leg’s depeg protection is OracleConfig.refBandBps: it measures the leg’s primary mark against the same lane’s reference (R) tier and halts the swap past the band (§7). Where the mark is USD-attested or the pair is meant to trade at 1, the leg’s depeg code (RiskConfig.flags bits 12 to 15, band = code x 250 bps, set through UPDATE_RISK) adds a parity test against 1.0 and reverts PriceOutsideRefBand(mark, WAD); it runs inside the reference-band guard, so it needs a nonzero refBandBps; the base’s code sets its BaseDepegged band, 2,500 bps at code 0. A parity bound on a ratio such as stETH/ETH would be a number against a moving unit of account, so such a leg keeps code 0. The discretionary “stop quoting this leg” call is the guardian halt: instant, owner-reversible, and not a number anyone has to keep current.


4. Sensitivities

4.1. Inventory skew: not a parameter

PricingLib.computeInventorySkew takes (reserves, liabilities) and nothing else, and returns an int8 in [-100,+100] by both clamp and type. There is no slope to tune and no saturation point to declare: the two arm slopes and both clamps are protocol constants, so no write can reprice a live book by moving them, and the asymmetry between the arms is the round-trip conservation bound. Formula: Inventory Management §3.1.

Why those constants. Writing the saturation points as [1/k,k], continuity requires k≤2 and round-trip conservation requires k≥2, so k=2 is the unique admissible value and there is no dial to expose. The two bounds, the measured leak across a discontinuous clamp and the real-tape evidence that the clamp region is unreachable are at Inventory Management §3.2.1.

The skew anchor. ψ maps onto the spline as x0=clamp(x*+ψ·BPS/200,0,BPS), where x* is the curve’s stored density median (§9), not the domain midpoint. The median enters as an offset at the BPS/200 slope and leaves computeInventorySkew untouched, because conservation binds the slope on both arms.

4.2. Vega: volatility sensitivity

One parameter, two outputs, both clamped. Base 10,000 = 1.0x.

Dispersion (PricingLib._calculateDispersion):

κ=min(κmin+σνBPS,MAX_DISPERSION_PBPS)

Spread (PricingLib._pathSpread):

Svol=∑ifmin,i+σpνp100·BPS,σp=∑iσi2

Vega semantics. Both σ and the spread are in PBPS and ν is in BPS, so ν=10,000 means dispersion widens by the full σ (σν/BPS=σ) while the spread’s volatility term is σ/100: the spread prices 1 % of σ30m at 1.0x, 6.5 % at the uint16 ceiling ν=65,535. The 100× gap between the two divisors is the shipped scale for the phase-1 stable core, where the fee floor fmin carries the spread and the σ term is a tail adjustment; the divisor is rescaled, if at all, together with the volatile-core fit, never on its own. In _pathSpread, νp=max(νin,νout) over the endpoints only, and it is the one risk input that is not composed per leg (every other one, σ in quadrature and the fee, CI and staleness terms by sum, is); in _calculateDispersion it is the profile asset’s own ν. Endpoint-max is correct there because ν is the pool’s own per-asset sensitivity dial, not a per-leg risk quantity, and the endpoints are the assets paid in and out.

There is no hardcoded base in either formula. The dispersion base is the per-asset minDispersionPbps, the spread base is the path fee floor. Hardcoding a 1000-PBPS dispersion base would make tight stable bands (1-6 bp) unreachable at σ≈0.

ν also scales the spread’s σ slope at a 100× smaller divisor, so cutting it to fit the swing cap (§5) takes the volatility premium down with it. One field serves two consumers; no value resolves that (§4.2.1).

4.2.1. Heterogeneous vega across assets

Nothing relates two assets’ vegaBps, and the owner and steward lanes each move one asset at a time (§2). Scopes, the inversion on composite paths and its live bound: Spread & Fees §11.2. The fence window that bounds it: §10.1.

4.3. No directional term

Every spread term is symmetric: no momentum or trend surcharge, and nothing in the fee model reads the direction of the last trade. Residual staleness is priced by the symmetric Ustale (Spread & Fees §3.4).


5. Dispersion

Dispersion is the price half-width over which liquidity is deployed, in PBPS. It rises with σ off the per-asset fitted floor κmin = minDispersionPbps, one-for-one at ν=10,000 and at ν/BPS of that elsewhere, and is clamped above by the protocol constant MAX_DISPERSION_PBPS, a read-path ceiling, not a per-asset field. Law and constant: Liquidity Shaping §6.2.

The curve’s fence cap bounds the floor at the write. PricingLib.dispersionCap is the widest dispersion whose interior mid swing the manipulation fence can bound, cap·dispRef·Q/span with cap=MAX_INTERIOR_SWING_PBPS=10,862 PBPS, and PoolConfigLib.sanitizeDispersion checks minDispersionPbps against it at both write paths (initAsset, setProfile; revert, not clamp) for an interior-capable leg; a leaf is held only under MAX_DISPERSION_PBPS until a child anchors under it. There is no unset-curve case: an asset cannot be listed without a curve. The cap follows from the curve header, so it differs by curve: 5431 PBPS where span is twice Q·dispRef, less on a wider shape. Per-deployment values are in 2. Deployments. Silently narrowing minDispersionPbps would move the asset’s own quiet-tape quote, so a floor above the cap is a config error (InvalidInput). A σ-driven κ above the cap is not prevented - it fails closed on the interior swing (Err.Overflow) instead of ever mispricing.

The cap is a peak-to-peak swing (1.0862% of mark total, not a one-sided 1%); the fence, its constants and its ceilings: Anchor Path Pricing §3.2.

The swing-cap check changes no configured value: it refuses only floors that could never quote safely. Above the floor, σ itself drives κ toward the protocol ceiling: once κ reaches the curve’s cap an interior route through the leg fails closed, and a lower ν gains headroom in that ratio (§4.2); the σ, CI and staleness terms of the spread keep widening. An installed curve id is immutable, so the cap under a validated asset never moves; a new shape is a new id, and setProfile revalidates each asset against it when it is repointed.

The deployed band is bounded below: the risk keeper enforces a hard floor on every band it emits:

κmin≥θ+q99(δ)+fmin100(bp)

θ is how far the mark may walk before a push triggers; δ is the excursion past θ measured over one push interval, so q99(δ) is its 99th percentile, how far the mark gets before the push lands; fmin is charged inside the band and therefore consumes band width. Below this sum the price leaves the active range inside one push interval and the LP carries the LVR.

The floor is expressed in minDispersionPbps PBPS by multiplying by the curve’s own scale, its dispRefPbps divided by its half-swing in bp (100 PBPS per bp for bell_100 and bell_500; keeper fence).

A second, weaker gate, Sdep≥2θ, is also checked by the keeper fence, but it binds nothing once fmin≥2θ holds, because minFee alone already covers it.

The damped step function raises every emitted band to this floor, so a tightened gate can never stall the pool short of target: it is a floor on the step, not a test the step might fail.


6. Risk configuration

IPool.RiskConfig, written at listing, by the queued UPDATE_RISK op (executeUpdateRisk, TUNING, 1 hour), and instantly by setRiskConfigTighten on the one-way lane (§2). Four fields, one word:

FieldTypeUnitsMeaningRange
flagsuint16bitfieldfeature, halt and depositor-gate bitsInventory Management §10.1
kappaCovBpsuint16bpsconvex coverage-wall strength[50, 10000] on every listed asset (§3.1)
depositCapCodeuint16m<<4|eleg notional cap, m·10^e whole base tokens; checked after the liability write at deposit, donate and the swapLiability output leg (ExcessiveAmount), never on mark drift or hook yield. A queued raise is bounded at 10× per opper leg
maxLiabWeightBpsuint16bpssoft cap on the leg’s share of the pool claim book at credit time (WeightCapExceeded); 0 or 10,000 = offoff (Inventory Management §5.3)

The instant lane accepts a write only when flags are identical except that DEPOSIT_GATED_BIT may be set, κ does not fall, the cap does not rise, and a live weight cap is neither cleared nor raised. Coverage reaches the quote through the fixed skew law (§4.1) and through κcov (§6.2); neither takes anything else from this struct.

6.1. Flags

Full bit layout, masks and gating: Guards Overview §1. Two flags carry meaning beyond their name:

  • TOKEN_EXOTIC_BIT: the token rebases or taxes the sender on outflow (on-top fee-on-transfer). Set at listing: PoolIOLib.push measures the pool’s balance delta and debits reserves for any excess; FLASH_ENABLED_BIT and a yield hook are refused on the leg (InvalidConfig).
  • DEPOSIT_GATED_BIT: depositor allowlist. deposit and donate require AccessControl.perms(msg.sender) to hold lane 9 (0x200, granted by setPerms); exits and swapLiability are not gated. Arming is instant, clearing is queued.
  • SWAP_GATED_BIT: swapper allowlist. swap_qe in or out of the leg requires perms lane 10 (0x400); interior hops are not gated. Arming is instant, clearing is queued.

There is one halt lever, haltAsset(pool, token, src) / unhaltAsset(pool, token, src), and src selects which bit it moves: clearing one source never clears a halt another source set.

Common values: 0x0010 swaps enabled, 0x0030 swap + liability swap, 0x0070 swap + liability swap + flash (live values: Inventory Management §10.1). 0x0005 is the whole HALT_MASK (HALT_BIT and HALT_ANCHOR_BIT) and bit 1 is reserved, so 0x0007 is rejected as an unknown flag. RiskConfig cannot move a halt bit: listing ignores them and an update preserves the live ones (PoolConfigLib); only haltAsset, unhaltAsset and the guardian collapse touch HALT_MASK.

Feature bits gate their own entrypoints. deposit + cooldown + cross-asset withdrawTo reproduces a swap at the mark cap, fee for fee, so a cross exit takes LIABILITY_SWAP_ENABLED_BIT on both legs. A same-asset exit checks HALT_MASK only: a trading pause must never trap an LP’s exit on a depleted leg. Clearing SWAP_ENABLED_BIT alone leaves liability swaps and cross exits open; clearing both stops every trading path and leaves same-asset exits; haltAsset stops the leg, exits included. A leg listed with both bits clear refuses cross-asset exits from genesis.

6.2. Coverage wall

Coverage reaches the quote through two charges and never through the depth axis (traverse denominator: Inventory Management §4): the skew anchor (§4.1) and the wall below.

κcov (kappaCovBps, bps) scales the convex coverage toll T (Spread & Fees §6). Its practical meaning is the marginal rate at zero size (Spread & Fees §6.3), a level shift on which dust pays the same first-order rate:

Toll rate at zero size, for illustrative κcov values (bps):

c6001,5001,8002,0002,500
0.996.1 bp15.2 bp18.2 bp20.2 bp25.3 bp
0.9812.230.636.740.851.0
0.9625.062.575.083.3104.2
0.9066.7166.7200.0222.2277.8
0.80150.0375.0450.0500.0625.0
0.50600.01,500.01,800.02,000.02,500.0
≥100000

How κcov is sized, and why it is not sized on recovery. _covToll reads the drained leg’s κcov and nothing else, so an arbitrageur facing a mark error e (bps) drains until the marginal toll equals it, i.e. to the equilibrium coverage

ceq=κcovκcov+e

Write ρ(c)=c(-lnc-1+c)/(1-c)2 for the fraction of that extraction the toll recovers. ρ is a function of c alone, κcov cancels, and it rises monotonically to 12: ρ(0.5)=38.6%, ρ(0.8)=46.3%, ρ(0.9)=48.2%, ρ(0.99)=49.8%. The toll can never recover more than half the loss it prices, at any κcov. A recovery target is therefore not a design input, and a low recovery ratio is not evidence the wall is under-sized. What κcov sets is ceq, and therefore the residual: net LP loss =e(1-ceq)(1-ρ(ceq)) bps of L. Sizing κcov trades that residual against benign flow: the toll on a 1 %-of-leg trade starting at the peg must not exceed the leg’s own half-spread.

Sequencing. The marginal toll rises with κcov, so raising it makes an already-under-covered leg more expensive to drain: refilling stays free, but it makes such a leg one-way until it is re-covered. Re-cover first, then raise.

Why κ has a floor. The buy-side traverse saturates, so the curve alone never stops a drain (Slippage & Price Impact §2.5). At κ = 600, for example, a leg at c=0.80 pays 150 bps of marginal toll against a spread of a few bps.

Coverage restoration takes no parameter, and neither does LP settlement: every exit settles at the pool rate C, which nets a drained leg against a filled one, with no clock, no flag and no governance-set rate (Inventory Management §5). The one writer that can lower liquidityIndexWad is Pool.hookWriteDown, which realizes an actual loss on invested reserves, sized at C and fenced to that leg.


7. Oracle configuration

IPool.OracleConfig, written at listing (AddAssetPayload.oracleCfg) and by UPDATE_ANCHOR. It names a lane, not an address: the chain has one mark store, in the Pool implementation, carrying a primary (P) and a reference (R) tier on every lane. There is no repoint op; the store moves only with the implementation at a fleet upgrade.

FieldTypeMeaning
laneuint8store lane (< 64) carrying the leg’s P mark and its R reference; must be registered
refBandBpsuint16symmetric tolerance of the P mark against the lane’s R tier (200 = ±2%); mandatory non-zero on every spoke (PoolConfigLib.requireExternalSpokeBound) and capped at MAX_EXTERNAL_REF_BAND_BPS = 500; must be 0 on the base, which carries the parity halt instead
modeuint80 = EXTERNAL (the lane’s P mark), 1 = INTERNAL (cash-collateral 1.0 peg)
quoteUnituint80 = QUOTE_UNIT_ANCHOR (the norm), 1 = QUOTE_UNIT_UOA (unit-of-account bridge)

Under QUOTE_UNIT_ANCHOR the feed is already attested in the asset’s own anchor’s units and the pool re-denominates nothing, which is the only dimensionally sound option past depth 1 (Anchor Path Pricing §6). QUOTE_UNIT_UOA is the unit-of-account bridge, for legs whose mark can only be signed as <TOKEN>-USD; the pool divides out the base’s own USD price at consumption, and it is legal only while the asset anchors directly to the base, enforced at config time.

mode selects where the mark comes from; the two modes are specified in Mark Relayer. The config-time bound is INTERNAL eligibility:

  • lane must be registered.
  • refBandBps must be armed.
  • refBandBps must be at most MAX_STABLE_DEPEG_BAND_BPS = 50, so a loosely or variably pegged unit (rebaser, FX, yield-bearing) cannot fit the band and is rejected.

There is no Asset.pegB64 field: the peg is a constant in the synthetic feed, not stored per asset.

Global oracle halts (FeedMathLib.gate), all fail-closed:

ConditionConstantError
guardian fast-freeze bit setFEED_HALT_BITFeatureDisabled(Err.Resource.FEED)
age > ttlper feedStaleData
mark = 0-ZeroValue
confidence > 1000 bpsMAX_CONFIDENCE_HALT_BPSThresholdViolation
base mark off parity by more than its band (depeg code x 250 bps, 2,500 at code 0)PoolConstantsLib.DEPEG_STEP_BPSBaseDepegged

Per-push acceptance band on the writer side, canonical statement: Mark Relayer. publishMarks accepts a segment only from an allowlisted relayer and only within 120 s of its source second. Kept facts: band=maxDevBps+min(6σdtsource/1800/100,9·maxDevBps)≤10·maxDevBps; Z=6 (6σ sanity cap), T=1800 s (the off-chain mark pipeline’s Parkinson window), X=9; σ = stored prior (never the incoming push’s own, else a signer inflates its own band); stored σ floored at realized move, hence the cap (uncapped self-widens ~6x per max-band push).


8. Fee parameters

IPool.FeeParams, per pool, written by the timelocked UPDATE_FEES op (executeUpdateFees).

FieldTypeUnitsMeaning
protoSharePctuint8base 100protocol’s share of the fee pot
flashFeePbpsuint16PBPSflash-loan rate, MIN_FLASH_FEE_PBPS = 10 (0.001%) to MAX_FLASH_FEE_PBPS = 10,000 (1%)
getFeeParams() returns the triple protoSharePct, flashFeePbps, flowCooldownSecs, and UPDATE_FEES writes the whole struct, so an op must restate every field it does not intend to move.

There is no zero default to fall back on. Pool.initialize routes its FeeParams through the same PoolConfigLib.setFeeParams the admin lane uses, and that write floors flashFeePbps at MIN_FLASH_FEE_PBPS = 10 (0.001%) and flowCooldownSecs at MIN_FLOW_COOLDOWN_SECS = 1, so a pool handed a zero-initialised struct reverts at creation rather than launching with flash free.

The spread S, the order the contract evaluates the toll and the fee in, and why nothing caps S from above: Spread & Fees §3 and §5.1.


9. Pricing curves

Shared curves, one immutable SSTORE2 blob per curveId (NUQuarticLib.pack). Assets point in via Asset.curveId.

FieldTypeMeaningConstraint
curveIduint16id of the blob0 = no shape, refused at assign; there is no fallback quote
wQint256[]control weights, PBPS times Qnondecreasing, non-flat
interioruint256[]interior knots on the depth axisstrictly increasing in (0, 10000), ≤ 14 segments
dispRefPbpsuint16reference dispersion of the fit (PBPS)non-zero; quotes y-scale by dispersion/dispRefPbps
flagsuint8opaque codebook key stamped by the fitnever read on chain; a curveId is written once (PoolConfigLib.setCurve)
medianuint16derived, not an input: density median x*, the curve’s own zerocomputed by NUQuarticLib.pack from the stored segments and packed into the header; _skewToDepth anchors zero skew on it

Axes: x is cumulative depth [0,10000] = 0-100%, y is price offset from the mark, scaled linearly by dispersion/dispRefPbps. Basis: clamped quartic I-spline, monotone by Δw≥0, C2 at every knot, exact O(1) range integration via stored prefix integrals.

Install validation (NUQuarticLib._validate):

  • weights nondecreasing
  • wQ[last] > wQ[0] (a flat curve carries no impact)
  • knots strictly increasing inside the open interval
  • segments ≤ 14
  • dispRefPbps != 0

9.1. Installed presets

Shapes, weights and costs of every installed preset: Curve Presets. Preset ids are per deployment: pool composition.

9.2. Why the table exists: a quantized density codebook

An asset’s observed depth density is fitted off chain and then mapped to the nearest entry already in the table. Asset.curveId is a pointer, so re-pointing a leg at a different entry as its measured density drifts is cheap and frequent (UPDATE_PROFILE, one asset), while writing a curve is expensive and rare (UPDATE_CURVE; an installed id is immutable, so a refit is a new id the legs are repointed to). Splitting the two is what lets shaping track the tape without a governance action per leg.

The cost of the quantization is the residual between an asset’s own fit and its nearest entry. The continuous dispersion / dispRefPbps y-scale absorbs the scale part of that residual at quote time, so the table only has to span shape.

flags is an opaque off-chain codebook key: nothing on chain reads it, and the coverage wall is unconditional, so no leg lacks a wall for an entry to be mis-assigned to. Matching entry to leg is the fit’s job and the keeper pre-submit fence’s.


10. The fence set

Risk parameters carry no timelock on the steward path, so they can adapt. The one-way lane of §2 is unbounded in size because it is bounded in sign. The fences are the safety net, in three layers.

10.1. Layer 1: on-chain hard fences (IAdmin.RiskFences)

Owner-set, per (pool, token). setAssetParamsBounded fails closed when maxDeltaBps == 0, so an unseeded leg is not steward-writable. Eight uint16 fields, covering the parameters the steward lane can write:

FenceMeaningArmed value (deploy scripts)
minFeeHardMinPbpsabsolute floor; also floors the owner paththe leg’s 2θ fee floor
minFeeHardMaxPbpsabsolute ceiling on a minFeePbps raisea manifest multiple of the live minFeePbps
vegaHardMinBps / vegaHardMaxBpsvega windowthe live vegaBps / a manifest multiple of it
maxDeltaBpscumulative lowering envelope per 24 h windowa manifest word, per deployment
maxCoopDiscBpsceiling on coopDiscBps; binds every lane, owner included0 admits only a zero coop discount
minCoopRebateBps / maxCoopRebateBpsrebate window; binds every lane, owner included0 admits only a zero rebate

setRiskFences itself validates maxDeltaBps in (0,BPS], minFeeHardMinPbps non-zero and not above minFeeHardMaxPbps, vegaHardMinBps <= vegaHardMaxBps, and minCoopRebateBps <= maxCoopRebateBps <= BPS. Re-arming a fee, vega or maxDeltaBps fence deletes the leg’s steward window.

The deploy scripts arm the fences per leg after listing and re-arm after every TUNING op (Pools.armFences, Pools.rearmFences). Check Admin.riskFences(pool, token) rather than assuming: an unseeded leg is fail-closed rather than permissive, because setAssetParamsBounded refuses to run at all on maxDeltaBps == 0 (Invariants §12.1).

The fences bracket the shipped parameters; they are not targets. minFeeHardMinPbps sits below each leg’s live minFee: a fence at the leg’s own floor would leave no room to move down after a tape refit and would re-brick the keeper on any leg that has to. The 2θ invariant is enforced by the keeper’s startup gate and the coupling gates, not by minFeeHardMinPbps.

10.2. Layer 2: on-chain lowering envelope

A lowering of minFeePbps or vegaBps may not take the field below

vanchor·(1-maxDeltaBps/104)

where vanchor is the value at the start of the current 24 h window, ratcheted up by any raise inside it (AdminParamsLib._loosenOk, StewardWindow). The bound is on the displacement, not the step: N calls in one block compose to one window. A raise is exempt and is bounded by the hard fences alone. minLiquidity is not steward-writable at all: any change reverts.

10.3. Layer 3: off-chain coupling gates

Invariants the chain does not enforce, checked pre-submit so a violating plan is never broadcast:

GateRuleWhy the chain cannot check it
theta coveragefmin≥200θ (PBPS)θ exists only in keeper config
halfwidthminDispersionPbps ≥ (θ + q99 drift + fmin) times the cell scaleneeds the measured drift distribution
vega headroomminDispersionPbps + vegaHardMax·σ/1e4 <= dispersionCap(curve), σ per 1800 s bar = max(live sigmaPbps, keeper’s unfloored 14d σ)the chain checks each vega write against the cap at the live print, never the fence’s max. σ is a typical level, not a stressed one: a pass leaves (cap − minDispersionPbps)·10⁴/vegaHardMax of σ headroom; a spike past it fails interior routes through the leg closed

10.4. What is not fenced

ParameterStatusBound
minDispersionPbpsno steward fenceraise instant via raiseMinDispersion at the live curveId, cut queued on UPDATE_PROFILE; non-zero (0 → 1000 default), the keeper’s own halfwidth + vega-headroom floor, and a hard revert if it exceeds the curve’s dispersionCap (§5)
σ-driven κ ceilingnothing to fenceclamped in _calculateDispersion at a protocol constant (§5); not a writable field
curveIdno fencetimelocked (UPDATE_PROFILE) in both directions, a shape change being comparable in neither; the repoint re-checks curve existence and the leg’s dispersion floor against the new curve’s cap
kappaCovBpsno fenceraise instant via setRiskConfigTighten, cut queued on UPDATE_RISK; [50, 10000] and hub-dominance (§3.1)
minLiquiditynot steward-writableowner lane only; any steward change reverts
protoSharePct, flashFeePbpsno fence beyond MAX_FLASH_FEE_PBPStimelocked
anchorno fencetimelocked at the GOVERNANCE base-migration tier, atomic with the oracle config (UPDATE_ANCHOR); guardian collapseAnchor may move a leg toward the root only, and halts it
vegaBps spread across assetsno fence at alleach asset’s vega is fenced individually; nothing relates two assets’ values, and a large gap between two endpoints of a live route carries a bounded routing cost (§4.2.1)
the inventory skewnothing to fenceit takes no parameter (§4.1)

The fenced set is what the untimelocked steward lane can move; everything else already pays a timelock.


11. The fee floor covers 2 theta

11.1. Derivation

The pick-off bound S≥2θ, i.e. fmin≥200θ (PBPS), and why the floor and not the runtime spread must carry it: Toxic Flow Mitigation §2.2.

A QUOTE_UNIT_UOA leg’s mark divides out the base’s USD price, so its stale gap spans two push thresholds, the leg’s own and the base reference lane’s:

fmin≥2(θleg+θbase)(PBPS)

Example: a UOA leg at θ = 70 PBPS over a base lane at 45 needs 230; at 75, 240.

11.2. Enforcement

The predicate is minfee_covers_theta (Toxic Flow Mitigation §2.3), applied at four points:

  1. Startup, hard fail. At boot the oracle keeper reads getAsset(token) for every configured pool times feed and refuses to start if any listed leg violates it. The keeper will not push into a pool it would make extractable.
  2. Pre-submit. Any risk plan emitting a violating minFee is rejected before broadcast.
  3. Parameter generation. Parameters are generated with S_dep = max(q70, 2θ, feeQ99).
  4. Deploy manifest. The listing ceremony refuses a row whose minFee is under 2θ, or under 2(θleg+θbase) on a UOA leg.

The boundary is pinned by tests: minfee_covers_theta(50, 0.25) passes, (49, 0.25) fails, (1000, 5.0) passes.

Where a leg sits exactly at 2θ, any θ increase must be paired with a minFee increase in the same change, or the keeper hard-fails at its next restart.


12. Reference parameter table

Per-leg values (κcov, ν, minFeePbps, minDispersionPbps, curveId) are deployment data: read getAsset(token) or see pool composition. The bounds every value must satisfy are in §3.1 and §6.

12.1. Topology configuration

The anchor column of a pool’s risk-param file is the sole expression of that pool’s topology: an empty column keeps every leg base-anchored, and filling a cell deepens that pool’s tree and puts the interior fence in the path of every route through it. What the depth bound is, what a deep edge requires and what gates the first fill are stated once at Anchor Path Pricing §1.1.

The parameter consequence is what belongs here: a non-base anchor gives that pair its own edge, its own feed, its own σ and its own fee floor, so several fee tiers coexist inside one pool instead of everything being priced off the base mark. Every sum-over-legs budget in this reference is sized against the full depth bound; nothing special-cases a shallower tree.

The interior fence’s second-order margin has no market tape until deep routes run; until then it is covered by tests.


13. Units reference

QuantityBasePrecisionExample
Fees (minFeePbps, flashFeePbps)1060.0001%100 = 1 bp, 10,000 = 1%
Volatility (sigmaPbps)106 = 100%0.0001%10,000 = 1%; cap 108
Dispersion (minDispersionPbps, κ)1060.0001%1000 = 0.1%; ceiling 900,000 (MAX_DISPERSION_PBPS)
Coverage wall (kappaCovBps), confidence, refBandBps1040.01%100 = 1%
Multiplier (ν)1040.01%10,000 = 1.0x
Protocol share (protoSharePct)1001%10 = 10%
PricesWAD 101818 dp1e18 mark from the oracle
Path spread (SwapQuote.spreadPbps)1060.0001%uint16, saturating at 65,535 = 6.55%

13.1. Why PBPS

Standard BPS (104) cannot express a stablecoin fee below 1 bp:

FeeStandardPBPSIn bp
0.001%inexpressible100.1
0.005%inexpressible500.5
0.01%11001
0.0001%inexpressible10.01 (MIN_FEE_PBPS)

Stable fee floors sit at fractions of a bp up to a few bp (read per-leg values from getAsset(token).minFeePbps). Standard BPS would round each to a whole bp (1.65 bp to 2, 0.9 bp to 1), and on a stable book a 0.5 bp against a 1 bp floor decides whether the pool wins the flow.

True 0.001 bp would need fractional PBPS. minFeePbps is integer PBPS, so 1 PBPS = 0.01 bp is the finest representable floor.


14. Known gaps

GapImpactReference
RiskFences can be left unseeded on a fresh deployment. Until they are seeded, maxDeltaBps == 0 and setAssetParamsBounded fails closed on every leg, so the risk keeper cannot act at all.risk adaptation stays offline; parameters are frozen at their deploy values until an owner transaction seeds the fencesAdmin.setAssetParamsBounded
Impact conservation is exact only at par. The traverse denominator is reserves, so it is c·L while the skew step is denominated on L, and no single pair of arm slopes conserves at every coverage. The residual is trader-pays (LP-safe): +2 PBPS at 1% of depth, +29 at 25%.a round trip off par over-charges slightly; the LP is never the loserInventory Management §3.2. Full fix (traverse redenominated on L) scoped, not built
vegaBps uniformity across assets has no fence at all. Each asset’s vega is fenced individually and nothing relates them, while _pathSpread prices the whole path’s σp at the endpoint maximum. The skew scales with νmax/νleg, bounded only by the fence window. Either lane can introduce heterogeneity one asset at a time: the owner lane at once on a raise and after the TUNING delay on a cut, the steward lane instantly inside its fences.bounded routing cost: Spread & Fees §11.2§4.2.1
The interior fence is unexercised until deep routes run. With every leg base-anchored no route carries an interior leg.the fence’s second-order margin has no tape until deep routes run; until then it is covered by tests only§12.1