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 steerrateandparback 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.
MarketOracleis 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.
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.
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.
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].
3. Liquidity threshold(required by TWAP only)
Pool liquidity must be above a floor.
| Checkpoint flag | Trigger | Blocks | → System rate controller | → GlobalFeeRouter | Shutdown trigger? |
|---|---|---|---|---|---|
ACCEPTED (0) | all gates pass | — | price + liquidity fresh | LP-utilization sample taken | — |
CHECKPOINT_QUORUM_FAILED | basket balance-ratio fails | all writes | price invalid → controller holds; arms the shutdown clock | liquidity invalid → time elapses, lpAllocFrac unchanged | Yes — the only trigger |
CHECKPOINT_SPOT_FAILED | synthetic spot outside [0.75, 1.25] | price ring only | TWAP ages → validity eventually drops (benign) | liquidity ring still writes | No |
CHECKPOINT_LIQUIDITY_FAILED | liquidity below floor | price ring only | signal is down-weighted via the liquidity authority (benign) | below-floor → treated as a valid zero-utilization sample | No |
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.
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_FAILEDsetsfirstQuorumFailTimeand emitsQuorumClockArmed(see Controller shutdown). - Recover.
SHUTDOWN_QUORUM_RECOVERY_DWELL = 24 hof continuously accepted checkpoints clears the streak and emitsQuorumClockReset— a single transient accepted checkpoint is not enough to reset a near-expired clock. - Latch.
SHUTDOWN_QUORUM_FAIL_THRESHOLD = 7 daysof unrecovered quorum failure latchescontrollerShutdownand emitsControllerShutdown; from there RD winds down through the redemption discount schedule.
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.
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
lpAllocFracis 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.