---
title: "Base-Token Depeg Halt"
description: "Circuit breakers on the numéraire and on every spoke: the base parity halt and the mandatory feed-relative reference band."
audience: tech
type: reference
status: live
lang: en
updated: "2026-09-03"
publish: true
---
# Base-token depeg halt

The base token is the pool's unit of account: it is what reserves, liabilities and every
`QUOTE_UNIT_UOA` mark resolve into. If the base itself loses its peg, each leg's mark stays correct
in isolation and any quote that passes through the base is wrong by the size of the depeg. This page covers the two circuit breakers that
close that gap: the parity halt the base carries against its nominal 1e18 reference, and the
feed-relative reference band every spoke must arm before it can be listed.

---

## 1. Motivation

AIMM prices **every** asset off a fresh **external keeper mark** (deviation $\theta$ + heartbeat), not an internal TWAP or pool-reserve state (see [Oracles §1](/docs/3-4-oracles#1-overview)). Quoting off the fresh mark removes classical curve LVR; the comparison against slow Chainlink-primary designs is [§4.3](#43-vs-slow-chainlink-primary-quoting). Residual risks are **push-latency LVR** and **OEV** around the push itself ([Oracles §8.4](/docs/3-4-oracles#84-known-risks-lvr--oev)).

But a `QUOTE_UNIT_UOA` mark is divided by the base's own price to reach anchor units, and on a flat roster the base is the interior node of every cross. If the pool's numéraire (e.g. a stablecoin) silently moves from $1.00 to $0.95, every quote that resolves through it is implicitly mispriced by 5% - the pool's fresh spoke marks cannot by themselves tell that the unit of account has shifted. This is most acute on single-sequencer L2s, where a sequencer-aware adversary can pin the base-token price across multiple blocks. It applies to any deployment target with a single block producer, not to a particular named chain.

The solution: the **base token** carries a depeg feed compared against nominal `1e18` parity, gating every quote, while each asset continues to quote off its own external mark. The base depeg feed acts as a **circuit-breaker**, not the price source.

---

## 2. Mechanism

### 2.1. Configuration: there is no separate base-oracle authority

The base token is priced through its **normal, timelocked `OracleConfig`**, exactly like every spoke. There is no `Admin.setBaseTokenOracle`, no per-pool "base oracle" slot, no 1e18-pinned mode, and no untimelocked halt knob (no such function exists anywhere in the deployed contract set; `PoolConstantsLib.sol` states the rule in-source).

Concretely:

- `Pricing._readBasePriceOrHalt` reads `$.oracleConfigs[$.baseToken]` and reverts `NotConfigured(ORACLE, base)` when `cfg.primary == address(0)` (`Pricing.sol`). A pool whose base has no oracle cannot quote at all. The halt is not opt-in.
- The base **must** be EXTERNAL mode. `PoolConfig` rejects an INTERNAL base at both config time (`validateOracleMode`, `Err.InvalidInput`) and base-migration time (`Err.BadConfig`), which is the selector to expect in each place: the halt reads the base mark against the outside world, so the base cannot be this pool quoting itself.
- Replacing the base oracle uses the same timelocked path as any spoke: `Admin.requestOp(pool, UPDATE_ORACLE, subject, payload)` then `executeOracleUpdate` after the `BASE` tier delay. There is no faster lane.

### 2.2. Halt threshold

`Constants.BASE_DEPEG_HALT_BPS = 500` (5%), `PoolConstantsLib.sol`. This is the canonical statement of the band; every other page quoting 500 bps refers here.

With $p_b$ the live base mark decoded to 1e18 and $W = 10^{18}$:

$$\delta_b = \frac{\lvert p_b - W \rvert \cdot 10^{4}}{W} > 500 \;\Longrightarrow\; \texttt{revert BaseDepegged}(p_b, \delta_b)$$

The whole implementation is one reader, `Pricing._readBasePriceOrHalt` (`Pricing.sol`):

```solidity
(bool found, IOracle.FeedData memory feed) = TCache.tryLoadFeed(TCache.TYPE_ORACLE_FEED, base);
if (found) {
  basePrice = FeedMathLib.mark(feed);              // gated at prime time, same tx, same verdict
} else {
  IPool.OracleConfig storage cfg = $.oracleConfigs[base];
  if (cfg.primary == address(0)) revert Err.NotConfigured(Err.Resource.ORACLE, base);
  feed = IOracle(cfg.primary).getFeed(cfg.feedId);
  basePrice = FeedMathLib.gate(feed);              // ORC-10: PAUSED + STALE + DEAD + UNCERTAIN
}
uint256 deviation = basePrice > SC.WAD ? basePrice - SC.WAD : SC.WAD - basePrice;
uint256 devBps = (deviation * SC.BPS) / SC.WAD;
if (devBps > uint256(C.BASE_DEPEG_HALT_BPS)) revert Err.BaseDepegged(basePrice, devBps);
```

**One reader, three call sites.** Having exactly one is the invariant, not an implementation detail. Any second base-price reader that open-codes the decode without the deviation check would let a depegged base silently rescale whatever it feeds.

| Call site | Why |
|---|---|
| `Pricing._cacheEndpoint` | The base is a swap endpoint. |
| `Pricing._quotePath` | The base is an interior hop. Cannot hardcode 1e18 when a real oracle is pinned. |
| `Pricing._legMarkAndFees` | A `QUOTE_UNIT_UOA` leg mark is divided by $p_b$ inline to reach anchor units. A stale, dead, uncertain or depegged base halts before it can be a denominator. |

`PoolIOLib.priceBandGuard` needs no base price: it compares the primary mark against the reference mark raw against raw, and by construction both share the primary's catalog unit. Nothing is re-denominated there, so nothing can be rescaled wrongly.

**Fail-closed ordering matters.** `FeedMathLib.gate` runs before the deviation arithmetic (`Pricing.sol`), so a paused, stale, zero or over-uncertain base feed reverts on its own terms rather than being read as an in-band price. The transient-cache hit path skips the re-gate deliberately: the feed was gated at prime time in the same transaction, and `block.timestamp` does not move inside a transaction, so the verdict is identical.

### 2.3. Failure modes

| Scenario | Behavior |
|---|---|
| Base feed paused (guardian fast-freeze) | Revert `FeatureDisabled(Err.Resource.FEED)` in `FeedMathLib.gate`, regardless of freshness |
| Base feed stale ($a > \tau$) | Revert `StaleData`, on the observed-at clock of [Oracles §8.2](/docs/3-4-oracles#82-staleness-protection). The base staleness gate is that TTL alone: no sequencer-uptime feed sits in the **quote** path |
| Base mark decodes to 0 | Revert `ZeroValue` |
| Base feed confidence over the halt threshold | Revert `ThresholdViolation` |
| Base oracle unconfigured | Revert `NotConfigured(ORACLE, base)` (`Pricing.sol`). Not a bypass |
| $\delta_b \le 500$ bps | Quote proceeds; each asset prices off its own external mark |
| $\delta_b > 500$ bps | Every swap that reads the base (an endpoint, an interior hop, or a `QUOTE_UNIT_UOA` leg, which on a flat roster is every swap) reverts `BaseDepegged` until the depeg resolves or governance re-points the base oracle through the timelock ([§2.5](#25-coverage-at-depth-configuration-versus-capability)) |

There is no configuration in which the base depeg halt is off while swaps remain live.

Test coverage: `PoolBaseDepeg.t.sol` (within-band, out-of-band, and the interior base-hub branch); `PoolMarkDenomination.t.sol` (the UOA division path).

### 2.4. Per-asset price band (depeg guard for spokes)

The base halt covers the numeraire. Every **spoke** carries its own price band, armed on **both** endpoint marks and every interior hop by `PoolIOLib.priceBandGuardAll` — one definition, called from every value-moving path (`settle`, cross `withdrawTo`, `swapLiability`) rather than pasted per site. Endpoint order is immaterial: both must pass, and the guards are pure reverts over a transaction-frozen feed cache. There is exactly **one** form, and this is its canonical statement.

**Feed-relative.** With $p$ the primary mark, $p_{ref}$ the reference mark and $b_{ref}$ = `refBandBps`, the guard halts when

$$\frac{\lvert p - p_{ref} \rvert}{p_{ref}} \cdot 10^{4} > b_{ref}$$

`refFeedId` is `keccak256(abi.encodePacked(base, quote))` on the reference `ExternalOracle`, not a MITCH u64; the keeper maps MITCH tickers to keccak feed ids off-chain. Example uses: WBTC against the BTC feed, XAUT against a gold feed. The revert selector is `PriceOutsideRefBand(uint256 markWad, uint256 refWad)`.

**One breaker, one code path.** No per-asset absolute price bound exists: a spoke is bounded relative to an independent attestation of the same pair, never against a number frozen at listing time. A relative band is symmetric by construction, needs no re-denomination through the base price on UOA legs, and leaves nothing to keep in agreement with a second breaker. Policy halts that an absolute bound would express belong to the guardian halt, a human decision on a fast on-chain lever.

**Comparisons are numeric.** Both marks are the oracle's 1e18 WAD (`getFeed` returns a decoded mark, `mark1e18`), so the comparison is a plain numeric one; there is no packed-integer ordering hazard to decode around.

**Denomination.** The ref band compares **raw against raw**. That is a config-time contract, not luck: the reference mark shares the primary's catalog unit by construction (DEN-03), USD when `quoteUnit = UNIT_OF_ACCOUNT` and the anchor cross otherwise. Because the comparison is same-unit, the guard needs no base-price division and therefore no base reader at all.

**Reference independence is mandatory when armed.** `PoolConfig.validateOracleConfig` requires `refFeedId != 0`, `refPrimary != 0` and `refPrimary != primary`, and calls `getFeed` on both to prove reachability. The reference is gated by `FeedMathLib.gate` on every read, so a dead or over-uncertain reference fails closed rather than anchoring the band to a corpse price. Address inequality is the on-chain floor only; signer and admin disjointness is a deployment obligation nothing on-chain checks, and the deployments that satisfy it are enumerated in [3.6 §4.6](/docs/3-6-oracle-price-push-security#46-independent-reference-the-deploy-disjointness-preflight).

**Every spoke must be bounded, in both modes.** `PoolConfig.requireExternalSpokeBound` is a single-clause predicate: `refBandBps == 0` reverts `NotConfigured(ORACLE, token)`. An armed ref band is therefore a listing precondition for every non-base leg, EXTERNAL and INTERNAL alike. Only the base is exempt, and only because it carries the parity halt of [§2.2](#22-halt-threshold) instead.

This is the spoke-side complement to per-spoke coverage skew: skew *widens* on a drift, the band *halts* past a hard limit.

Tests: `AimmInvariants.t.sol::test_refBand_halts_out_of_band_swap`, `::test_refBand_halts_input_asset_swap`, `::test_refBand_stale_reference_feed_fails_closed`, `::test_refBand_halts_cross_withdraw`, `::test_refBand_halts_cross_withdraw_input_asset`, `::test_refBand_stale_reference_feed_halts_cross_withdraw`, `::test_refPrimary_independent_reference_bounds_walked_mark`, `::test_refPrimary_zero_failClosed`.

### 2.5. Coverage at depth (configuration versus capability)

**Coverage tracks topology, and topology is configuration.** The tree is general to `MAX_DEPTH = 4`. On a pool whose `anchor` column is empty every leg is base-anchored, so a route has at most one interior node and that node is always the base: the parity halt on that one node plus the two endpoint ref bands cover every node on every route. Once anchor cells are filled, `priceBandGuardPath` supplies the interior coverage - it is live code whether or not a route exercises it yet, and its band behavior has market tape only once deep routes run.

At `MAX_DEPTH = 4` a route may traverse interior pivots that are neither the base nor an endpoint. Those pivots are **banded at settlement**: `PoolIOLib.priceBandGuardPath` loops every interior hop and runs the ref band on each one except the base, which is covered by its own parity halt instead (`PoolIOLib.sol`).

**The ref band is what generalises across the path; the parity halt does not.** The two guards scale in opposite directions and the source says so. `Pricing._quotePath` walks the interior hops but calls `_readBasePriceOrHalt` only `if (hop == $.baseToken)` (`Pricing.sol`), so parity tests exactly one mark, the numeraire's, and at most once per route, however deep the route runs. `priceBandGuardPath` runs on every interior hop there is. The base parity halt is the **depth-1 special case** of "the pivot must be sound", correct when the base was the only interior node a route could have; multi-anchor is what turns that into "every node the path prices through must be sound", and the ref band is the guard that carries it. An interior node's mark multiplies straight into the composed rate, so a depegged wrapper mid-chain is picked off exactly as an endpoint one would be, which is why endpoint-only guarding is not enough and `priceBandGuardPath` exists.

What the ref band does **not** do is become an *absolute* depeg breaker, at any depth. That is a different axis from the path generalisation above: the band covers every node, but on each node it measures agreement, not distance from par. `refFeedId` is a same-unit agreement check between two independent attestations of the same pair ([§2.4](#24-per-asset-price-band-depeg-guard-for-spokes)), and the band measures how far the two disagree. An anchored pair therefore references the anchored pair, never an absolute leg. Pointing an anchored `stETH/ETH` primary ($\approx 1.15$) at a `stETH/USD` reference ($\approx 4000$) gives a relative deviation of $\approx 99.97\%$, past any `uint16` band: the pool halts on the first swap and stays halted. Making a parent depeg visible needs a NEW field, `absFeedId` plus `absBandBps`, or a split of `refFeedId` into an agreement band and an absolute-depeg band. **Neither exists.** So an anchored child would inherit its parent's depeg risk with no automatic breaker, and the only lever is the guardian halt (`haltAsset`, or `collapseAnchor` to re-root the child toward the base and halt it in the same write). This is a gating constraint on activating any anchored child, not a limitation that deeper operation would grow out of. See [Invariants I-17](/docs/1-1-8-invariants#i-17-interior-legs).

---

## 3. Watcher / pause composition

The depeg halt composes with the on-chain incident levers; authority and delays for every lever are in [Access Control & Roles §4](/docs/3-1-access-control-roles-emergency-powers#4-halt-authority).

- **The halt is automatic and on-chain** - no off-chain watcher required. Every quote re-reads the base price ([§2.2](#22-halt-threshold), three call sites).
- **The operator levers are per-asset**, not pool-wide. `Admin.batchRiskOp` sweeps halt/unhalt across many (pool, token) pairs in one transaction with per-leg try/catch, so a whole-pool halt is atomic; it just enumerates assets (off-chain via `PoolFactory.getPoolTokens`) instead of flipping one flag. The distinction affects how the sweep is built, not whether it lands.
- **No watcher is required to keep the pool safe**, but one *should* still monitor for `BaseDepegged` reverts so the operator can decide whether to re-point the oracle, widen the band through governance, or migrate the base token.

---

## 4. Comparison vs other designs

### 4.1. vs OrbSwap (CCMM / Orbital)

OrbSwap's sphere invariant gives **intrinsic geometric depeg isolation**: a depegged asset drains asymmetrically through pure curve geometry, no oracle is involved. Within OrbSwap's domain (pegged-only baskets) this is strictly better than the base depeg halt: the curve does the work an oracle would do, with zero external trust assumption.

AIMM cannot match this for two reasons:
1. AIMM serves **mixed-volatility** baskets (stables + LSTs + majors). Volatile assets have no natural peg; the sphere invariant is undefined.
2. AIMM's price source is decoupled from reserves (external-mark-driven, not invariant-driven). The reserve geometry that gives OrbSwap its isolation does not exist in AIMM by design - that decoupling is what gives AIMM regime-adaptive quoting in the first place.

For a stables-only deployment, OrbSwap's intrinsic isolation is the better tool. AIMM's base depeg halt is the next-best alternative when mixed-volatility makes the sphere unavailable.

### 4.2. vs Curve V1 amplification penalty

Curve V1's stableswap invariant uses an amplification coefficient $A$ that flattens the curve near the equal-balance point and reverts to constant-product geometry as imbalance grows. A depegged asset organically drains the pool at progressively worse prices, similar in spirit to OrbSwap but with a 1D rather than n-sphere geometry.

Curve V1's mechanism is **gradual** (the price impact rises smoothly with imbalance) where the base depeg halt is **binary** (quote-or-halt at the 500 bps line). Curve loses LPs to depeg arb continuously up to the amplification cliff; AIMM stops the bleeding hard at 500 bps but cannot price the band-edge regime gracefully.

**Tradeoff**: Curve V1 preserves availability at the cost of LP losses; AIMM's halt preserves LP value at the cost of availability.

### 4.3. vs slow Chainlink-primary quoting

External-only AMMs (some DODO and Swaap variants, am-AMM) quote off a **slow** Chainlink feed for *every* price, on every asset. An internal oracle review estimated this produces a meaningful annual LP-value leak under typical Chainlink heartbeat configuration - Chainlink updates on a deviation/heartbeat schedule, so arbitrageurs predictably extract value in the lag windows (internal analysis; specific magnitudes are deployment-dependent).

AIMM is external-mark-primary too, but its mark is a **fast keeper push** (per-asset deviation band $\theta$ + heartbeat), not a slow Chainlink feed: the stale gap is bounded to $\theta$, and any keeper lag past the grace is priced by the staleness surcharge ([Spread & Fees §3.4](/docs/1-1-4-spread-fees#34-staleness-surcharge)). No Chainlink feed sits anywhere in the quote path, so the heartbeat-lag arb surface does not exist here.

---

## 5. Oracle-side guarantees

Both breakers on this page assume the marks feeding them are themselves bounded. That chain is specified elsewhere and not repeated:

- **The mandatory per-feed push clamp** and its $10\,d_{max}$ ceiling: [Oracles §8.3](/docs/3-4-oracles#83-deviation-bounds).
- **The k-of-n signer quorum** and its ceremony: [Oracle Price-Push Security §4](/docs/3-6-oracle-price-push-security#4-the-whitelisting-ceremony).
- **The one property neither contract enforces**, signer disjointness between a primary and its reference: [3.6 §4.6](/docs/3-6-oracle-price-push-security#46-independent-reference-the-deploy-disjointness-preflight).

The cumulative-manipulation bound of [§2.4](#24-per-asset-price-band-depeg-guard-for-spokes) holds only on a deployment that satisfies that last one.

---

## 6. Related documentation

- [Oracles](/docs/3-4-oracles) - external-mark feed architecture + freshness/confidence gates
- [Capital Efficiency](/docs/foundations) - why we accept the binary halt tradeoff (curated venue economics)
- [Foundations §10 OrbSwap](/docs/foundations) - sphere invariant for geometric isolation comparison
- [AMM Landscape](/docs/foundations) - peer comparison including depeg-handling axes
- [Manifesto §12.2](/docs/manifesto) - hybrid-oracle structural differentiator
