Skip to main content

RD Market Oracle

The MarketOracle is a single, protocol-wide oracle that measures two things about RD's market: its price (an RD/USD TWAP) and the depth of its market (a TWAL — time-weighted average liquidity). It is the shared input for the protocol's two controllers:

  • The system rate controller (RateParControl) reads the RD price to steer rate and par back toward the peg.
  • The GlobalFeeRouter reads the pool liquidity to measure LP utilization and steer the LP share of protocol fees.

It is built as a Balancer hook on the protocol's RD pool, so it is refreshed atomically by trading rather than by a keeper poke. For collateral pricing — a separate oracle for a separate job — see Collateral oracles.

The RD Balancer pool​

The protocol designates one Balancer pool as its canonical RD market-price venue. This pool is the single source of truth for both signals, and it's also where LP staking earns rewards (see LP staking).

  • Assets. A fixed 4-asset stable pool holding RD, USDC, USDT, and USDS (the pool RD-USDC-USDT-USDS). Addresses on Deployed addresses.
  • Pool type. A Balancer stable pool: RD's target is $1 and the stable curve concentrates depth around parity, which is exactly where the controller needs an accurate price signal.
  • Hook integration. MarketOracle is attached as a Balancer hook, so every swap and liquidity event writes a fresh observation. No keeper poke is required during organic activity.
  • Price the protocol uses. A 24-hour TWAP, read only from finalized observations (windows ending at least 60 s in the past — see below), not the spot price at any single block.
  • Liquidity the protocol watches. A parallel 24-hour TWAL, used to size the price signal and to drive LP utilization.
Balancerstable poolamp curve · targets $1RDtargets $1USDC≈ $1USDT≈ $1USDS≈ $1
One Balancer stable pool holds RD alongside USDC, USDT, and USDS. The stable-swap curve concentrates depth around $1, exactly where the controller needs an accurate signal. This single pool is the protocol's canonical RD price venue and where LP staking earns.

The oracles, RD/USD TWAP and TWAL​

Single instance protocol-wide. Built as a Balancer hook (BaseHooks, IHooks) implementing a Uniswap-V3-style observations array with TWAP/TWAL reads.

Finalized observations​

The oracle reads only finalized intervals — windows whose end is no newer than:

min(now - _MIN_PRICE_AGE, latest_written_observation_time)

with _MIN_PRICE_AGE = 60 seconds. This blocks same-block manipulation: a flash-loaner moving the pool price in the same block as the read can't poison a controller, because only intervals that ended ≥ 60 s ago are consumed. The sliding window also never extrapolates a stale observation forward to "now".

Hooks​

MarketOracle implements onBeforeSwap, onBeforeAddLiquidity, and onBeforeRemoveLiquidity; each samples pool state and appends an observation. Organic flow keeps it fresh; for very quiet periods a keeper can call an explicit checkpoint.

trade activityswap · addremove liq.RD stable pool4-assetMarketOracleBalancer hook →append observationonBeforeSwapobservations ringnewestTWAP → priceRD/USD, 24hTWAL → depthliquidity, 24h
Every swap and liquidity event calls the hook, which samples pool state and appends one observation to a Uniswap-V3-style ring buffer. Organic trading keeps price (TWAP) and depth (TWAL) fresh — no keeper poke needed. Reads consume only finalized observations (windows ending ≥ 60 s ago), which blocks same-block manipulation.

The three measurement gates​

Before an observation is accepted, the oracle runs three gates and records the result as a flag bitmap (lastCheckpointFlags, exposed via lastCheckpointState() and emitted as MarketOracleCheckpoint(flags)). The price ring requires all three gates; the liquidity ring requires quorum only (so a thin or off-peg market is still recorded as low liquidity rather than silently dropped).

1. Quorum(required by both TWAP/TWAL)​

A balance-ratio health check across the USDC / USDT / USDS basket (are the three stablecoins still reasonably balanced, i.e. none has depegged). Required for any write: computing a price or liquidity from a degenerate basket would poison the rings.

quorum holdsUSDCUSDTUSDS✓ writes allowedquorum failsUSDCUSDTUSDS✕ all writes blocked · arms clock
Quorum checks that the USDC / USDT / USDS basket is still reasonably balanced — no stablecoin depegged or draining. It gates every write: pricing off a degenerate basket would poison both rings. Quorum failure is the only condition that arms the shutdown clock.

2. Spot price bounds(required by TWAP only)​

The synthetic RD/USD spot must lie within [MIN_SPOT_PRICE_WAD, MAX_SPOT_PRICE_WAD] = [0.75, 1.25].

price write accepted0.751.001.25targetMINMAXsynthetic spot ✓
The synthetic RD/USD spot must land inside [0.75, 1.25]. Outside that band the oracle skips the price write only — the liquidity ring still records. A briefly off-peg spot is an operational condition the controller rides out; it never triggers shutdown.

3. Liquidity threshold(required by TWAP only)​

Pool liquidity must be above a floor.

floorabove floorliquidity sample takenbelow floorcounts as 0-utilization → LP alloc ↑TWAL
Pool depth (TWAL) must clear a liquidity floor. Above it, a normal liquidity sample is taken. Below it, the reading is treated as a valid zero-utilization sample — a genuinely empty pool pushes the LP fee allocation up rather than freezing it. Either way, no shutdown.
Checkpoint flagTriggerBlocks→ System rate controller→ GlobalFeeRouterShutdown trigger?
ACCEPTED (0)all gates pass—price + liquidity freshLP-utilization sample taken—
CHECKPOINT_QUORUM_FAILEDbasket balance-ratio failsall writesprice invalid → controller holds; arms the shutdown clockliquidity invalid → time elapses, lpAllocFrac unchangedYes — the only trigger
CHECKPOINT_SPOT_FAILEDsynthetic spot outside [0.75, 1.25]price ring onlyTWAP ages → validity eventually drops (benign)liquidity ring still writesNo
CHECKPOINT_LIQUIDITY_FAILEDliquidity below floorprice ring onlysignal is down-weighted via the liquidity authority (benign)below-floor → treated as a valid zero-utilization sampleNo

The design principle: only a persistently broken market structure (quorum) is allowed to shutdown the controller. A briefly off-peg spot or a thin pool are operational conditions the controller rides out — they must never trigger shutdown.

Input to the system rate controller​

RateParControl reads getResultWithValidity() (RD/USD TWAP + a validity flag) and uses it to drive rate and par. If validity is false, the controller refuses to act on the price and holds its last state. Liquidity feeds a liquidity-authority scalar that scales how aggressively the controller acts: a thinner pool produces a noisier TWAP, so the controller trusts it less. Once the input gates pass, combined authority is max(20%, min(q_fast, q_slow)): low liquidity can weaken an admitted signal to 20%, but cannot continuously dial active control to zero. A separate seven-day sustained-error dwell guarantees the baton can still pass to PAR even when suppressed authority keeps rate short of its bound. The rate/par math itself is on Peg control.

TWAPRD/USD + validityRateParControlpeg PI controllerrateparliquidity authority (from TWAL)scales gain
RateParControl reads the RD/USD TWAP with a validity flag and drives rate and par back toward the peg. If validity is false the controller holds its last state. A liquidity-authority scalar (from the TWAL) scales how aggressively it acts — a thinner pool yields a noisier TWAP, so it's trusted less.

The quorum-failure shutdown clock​

Terminal controller shutdown keys only on the quorum gate — never on spot deviation, low liquidity, or low RD supply, which are benign/operational. It reads the settled, prior-block marketOracle.lastCheckpointState(), not a manipulable spot value, and only after the bootstrap period:

  • Arm. The first unrecovered CHECKPOINT_QUORUM_FAILED sets firstQuorumFailTime and emits QuorumClockArmed (see Controller shutdown).
  • Recover. SHUTDOWN_QUORUM_RECOVERY_DWELL = 24 h of continuously accepted checkpoints clears the streak and emits QuorumClockReset — a single transient accepted checkpoint is not enough to reset a near-expired clock.
  • Latch. SHUTDOWN_QUORUM_FAIL_THRESHOLD = 7 days of unrecovered quorum failure latches controllerShutdown and emits ControllerShutdown; from there RD winds down through the redemption discount schedule.
day 0 · armday 7 · thresholdrecovers24 h acceptedclock reset → controller runslatchesquorum keeps failing…ControllerShutdown → wind-down
The clock keys only on quorum. The first unrecovered failure arms it (QuorumClockArmed). 24 h of continuously accepted checkpoints resets it (QuorumClockReset) — a single transient accept isn't enough. 7 days unrecovered latches ControllerShutdown and RD winds down.

Input to the GlobalFeeRouter​

The GlobalFeeRouter reads getLiquidityWithValidity() (the pool TWAL) to compute LP utilization = pool liquidity ÷ RD total supply (a WAD fraction). It runs that through an EMA (its process variable) and a PI controller that sets lpAllocFrac — the share of protocol fees routed to LP stakers — steering utilization toward targetLpUtil.

TWALpool liquidityLP utilizationTWAL ÷ RD supplyEMAPI controller→ targetLpUtillpAllocFracLP fee share
The router reads the pool TWAL, forms LP utilization = TWAL ÷ RD supply, smooths it through an EMA, and runs a PI controller that steers utilization toward targetLpUtil by setting lpAllocFrac — the LP share of protocol fees. Invalid liquidity leaves lpAllocFrac unchanged (no drift).

Contingencies mirror the gates:

  • Invalid liquidity / failed liquidity quorum — elapsed time is consumed but lpAllocFrac is left unchanged (no sample, no drift).
  • Liquidity below the floor — treated as a valid zero-utilization sample, so a genuinely empty pool pushes the LP allocation up rather than freezing it.

See Fees for how lpAllocFrac combines with the Stability Pool split.

Deep dive​

  • Peg control — how the controller consumes the RD price.
  • Fees — the LP / SP fee split driven by LP utilization.
  • Controller shutdown — the terminal quorum-failure path.
  • Risks — oracle-specific risks and mitigations.