Skip to content

RAAC pmUSD to USD Oracle

Overview

The PmUSDToUSDOracle is a robust, multi-tiered price oracle that provides the pmUSD/USD exchange rate for the RAAC protocol. It implements a sophisticated fallback mechanism to ensure price availability even during oracle failures, with built-in circuit breakers to protect against depeg events.

The oracle uses a three-tier fallback system:

  1. Primary: Direct Chainlink pmUSD/USD feed — not configured
  2. Secondary: ETH/USD + ETH/pmUSD (Curve TriCRV pool) — not configured
  3. Tertiary: Hard-coded fallback price

Current configuration: tertiary only

The primary and secondary sources are not set. crvUSDDataFeed, ethDataFeed and curveOracle are all left at the zero address, and the constructor does not populate them — they are owner-set through their respective setters.

Both branches of _crvUSDPriceInUSD() are gated on those addresses being non-zero, so with them unset the oracle serves the hard-coded fallback price (level 2) on every query: 1e18, or 1.00 USD. The multi-tier logic below describes the mechanism that becomes active once the feeds are configured; it is not what the oracle does today.

Non-Reverting Design

The oracle is designed to never revert on price queries. Instead, it gracefully falls back through multiple price sources to ensure continuous operation of the protocol.


Architecture

ENTRY — NEVER REVERTS LEVEL 0 — CHAINLINK pmUSD/USD LEVEL 1 — ETH/USD × CURVE TRICRV LEVEL 2 — HARD-CODED FALLBACK CLAMP & RETURN getLatestPrice() · getPrice() resolves a level — no revert path crvUSDDataFeed set ? and fresh — 1 day + 1 h yes Level 0 — direct feed address unset — branch skipped no ethDataFeed + curveOracle ? ETH fresh — 1 h + 10 min yes — both required Level 1 — ETH/USD ÷ ETH/pmUSD addresses unset — branch skipped no fallbackPriceInUSD 1e18 — 1.00 USD owner-set · the only live source today inactive while unset _clampCircuitBreaker() clamp into [ 0.90 , 1.10 ] USD enabled by default · owner-configurable price · timestamp · fallbackLevel always 18 decimals

Fallback Levels

Level Source Description Staleness Check Status
0 Chainlink pmUSD/USD Direct price feed from Chainlink 1 day + 1 hour Not configured
1 ETH/USD + ETH/pmUSD Chainlink ETH/USD × Curve TriCRV ETH/pmUSD 1 hour + 10 minutes Not configured
2 Hard-coded fallback Protocol-set fallback price (default: $1.00) N/A Active

Purpose

  • Price Conversion: Convert pmUSD prices to USD for house price calculations
  • Reliability: Ensure price availability through multiple fallback mechanisms
  • Depeg Protection: Circuit breaker prevents extreme price deviations
  • Protocol Integration: Seamless integration with RAACHousePrices for price conversion

Key Features

Multi-Tier Fallback System

The oracle implements a sophisticated three-tier fallback mechanism:

  1. Primary Source (not configured): a Chainlink pmUSD/USD feed
  2. Secondary Source (not configured): a Chainlink ETH/USD feed combined with a Curve TriCRV pool ETH/pmUSD price
  3. Tertiary Source (active): hard-coded fallback price, configurable by the owner

Circuit Breaker Protection

APPLIED TO EVERY RESOLVED PRICE resolved price from level 0 · 1 · 2 circuitBreakerEnabled ? no returned unchanged no clamp applied yes CLAMP WINDOW — 0.90 … 1.10 USD BY DEFAULT price < min clamped up min ≤ price ≤ max returned unchanged price > max clamped down 0.90 minPriceThreshold 1.00 peg · fallback value 1.10 maxPriceThreshold
  • Default thresholds: 0.9 USD (min) to 1.1 USD (max)
  • Configurable: Owner can adjust thresholds or disable circuit breaker
  • Depeg protection: Prevents extreme price deviations during market stress

Price Sources

crvUSDDataFeed is unset, so level 0 is skipped. Once a feed is set, the values that apply are:

  • Staleness: maxStableStaleTime, 1 day + 1 hour
  • Decimals: normalized to 18 decimals

Secondary: ETH/USD + Curve TriCRV — not configured

ethDataFeed and curveOracle are unset, and level 1 requires both, so the branch is skipped. Once they are set, the values that apply are:

  • Calculation: pmUSD/USD = ETH/USD ÷ ETH/pmUSD
  • Staleness: maxUnstableStaleTime, 1 hour + 10 minutes for ETH/USD
  • K Value: 0 (ETH index in the TriCRV pool)

Tertiary: Hard-coded fallback — active

  • Value: fallbackPriceInUSD, set to 1e18 (1.00 USD) in the constructor
  • Timestamp: lastFallbackUpdateTimestamp, set at deployment and on each setFallbackPriceInUSD call
  • Owner-configurable via setFallbackPriceInUSD(uint256)

Key Functions

Price Queries

Function Description Returns
getLatestPrice() Full price data with fallback level (price, timestamp, fallbackLevel)
getPrice() Simple price query uint256 price
decimals() Price decimals (always 18) uint8

Configuration (Owner Only)

Function Description Parameters
setCrvUSDDataFeed(address) Set Chainlink pmUSD/USD feed _crvUSDDataFeed
setEthDataFeed(address) Set Chainlink ETH/USD feed _ethDataFeed
setCurveOracle(address) Set Curve TriCRV oracle _curveOracle
setMaxStableStaleTime(uint256) Set pmUSD feed staleness limit _maxStableStaleTime
setMaxUnstableStaleTime(uint256) Set ETH feed staleness limit _maxUnstableStaleTime
setFallbackPriceInUSD(uint256) Set hard-coded fallback price _fallbackPriceInUSD
setCircuitBreakerConfig(bool, uint256, uint256) Configure circuit breaker enabled, minThreshold, maxThreshold
setK(uint256) Set Curve pool token index _K

Fallback Logic Details

// Check if pmUSD/USD feed is available and fresh
if (address(crvUSDDataFeed) != address(0)) {
    (, int256 answer, , uint256 updatedAt, ) = crvUSDDataFeed.latestRoundData();
    if (answer > 0 && block.timestamp - updatedAt <= maxStableStaleTime) {
        return normalizeTo18Decimals(uint256(answer), crvUSDDataFeed.decimals());
    }
}

Level 1: ETH/USD + Curve Calculation

// Calculate pmUSD/USD = ETH/USD ÷ ETH/pmUSD
uint256 ethPriceInUSD = normalizeTo18Decimals(ethAnswer, ethDataFeed.decimals());
uint256 ETHPriceInCrvUSD = curveOracle.price_oracle(K);
uint256 crvUSDPriceInUSD = (ethPriceInUSD * 10**18) / ETHPriceInCrvUSD;

Level 2: Hard-coded Fallback

// Return protocol-set fallback price
return fallbackPriceInUSD; // Default: 1e18 (1.00 USD)

Circuit Breaker Configuration

Default Settings

  • Enabled: true
  • Min Threshold: 0.9e18 (0.90 USD)
  • Max Threshold: 1.1e18 (1.10 USD)

Circuit Breaker Logic

function _clampCircuitBreaker(uint256 price) internal view returns (uint256) {
    if (!circuitBreakerEnabled) return price;

    if (price < minPriceThreshold) return minPriceThreshold;
    if (price > maxPriceThreshold) return maxPriceThreshold;
    return price;
}

Integration with RAACHousePrices

The oracle is integrated with RAACHousePrices for price conversion:

// In RAACHousePrices.getLatestPrice()
uint256 usdPrice = tokenToHousePrice[_tokenId];
uint256 crvUSDPrice = (usdPrice * 10**crvUSDToUSDOracle.decimals()) / crvUSDToUSDOracle.getPrice();

This ensures house prices stored in USD are properly converted to pmUSD for protocol calculations.


Operational Considerations

Monitoring

  • Monitor fallback level usage to detect oracle issues
  • Track circuit breaker activations during market stress
  • Ensure all price feeds remain healthy and updated

Emergency Procedures

  • Oracle Failure: System automatically falls back to the next configured source; with the primary and secondary unset, every query resolves to the tertiary fallback
  • Depeg Event: Circuit breaker clamps prices within safe thresholds
  • Feed Staleness: Automatic fallback to fresher sources

Owner Responsibilities

The owner must ensure all oracle addresses are correctly configured and monitor the system for proper operation. Incorrect configuration could lead to stale or incorrect prices.