Skip to content

Feature/centralized pricefeed Introducing Modular, Secure Oracle Infrastructure - #28

Merged
olujimiAdebakin merged 2 commits into
devfrom
feature/CENTRALIZED_PRICEFEED
Dec 31, 2025
Merged

Feature/centralized pricefeed Introducing Modular, Secure Oracle Infrastructure#28
olujimiAdebakin merged 2 commits into
devfrom
feature/CENTRALIZED_PRICEFEED

Conversation

@olujimiAdebakin

Copy link
Copy Markdown
Owner

PR: Introduce Modular, Secure Oracle Infrastructure

Executive Summary

This PR establishes a production-grade oracle infrastructure for the Baobab Protocol built on a fundamental security principle:

Fetching prices ≠ Trusting prices

We implement a three-layer security model that separates data ingestion, routing, and validation into distinct, upgradeable components. This architecture prevents the common DeFi failure mode where oracle issues brick entire protocols.

⚠️ Important: This PR intentionally does NOT integrate oracles into PerpEngine. We're building the foundation correctly first, ensuring trust boundaries are properly established before wiring them into critical protocol logic.


Architecture Overview

┌────────────────────────────────────────────────────────────┐
│  Protocol Consumers (PerpEngine, Liquidations, Funding)    │
│                                                             │
│  ❌ NEVER call adapters or registry directly                │
│  ✅ ONLY call OracleSecurity.getValidatedPrice()            │
└────────────────────────────────────────────────────────────┘
                              ↓
┌────────────────────────────────────────────────────────────┐
│  Layer 3: OracleSecurity (Trust Boundary)                   │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│  • Circuit breakers (global + per-market)                   │
│  • Staleness enforcement                                    │
│  • Context-aware confidence thresholds                      │
│  • PriceUse-based validation (LIQUIDATION vs SPOT)          │
└────────────────────────────────────────────────────────────┘
                              ↓
┌────────────────────────────────────────────────────────────┐
│  Layer 2: OracleRegistry (Routing + Fallback)               │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│  • Asset → MarketID mapping                                 │
│  • Primary/fallback oracle selection                        │
│  • Market-level heartbeat configuration                     │
│  • Metadata enrichment (confidence, timestamps)             │
└────────────────────────────────────────────────────────────┘
                              ↓
┌────────────────────────────────────────────────────────────┐
│  Layer 1: Price Feed Adapters (Data Ingestion)              │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│  • ChainlinkAdapter, PythAdapter, TrustedOracle             │
│  • ComputedOracle (derived prices)                          │
│  • TWAPAdapter (time-weighted averaging)                    │
│  • ⚠️ NO validation logic — fetch only                      │
└────────────────────────────────────────────────────────────┘

What Was Implemented

1. Unified IPriceFeed Interface

All oracle sources conform to a single interface, enabling protocol-wide consistency:

interface IPriceFeed {
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 price,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound,
            uint256 confidence
        );
}

Why this matters:

  • Swap oracle providers without touching consumer contracts
  • Compose prices (TWAP, Computed) using the same interface
  • Uniform treatment of all feeds inside the registry

2. Oracle Adapters (Layer 1: Data Ingestion)

Adapters are intentionally "dumb" — they only fetch data, with zero validation logic.

Implemented adapters:

  • ChainlinkAdapter — Wraps Chainlink aggregators
  • PythAdapter — Wraps Pyth Network feeds
  • TrustedOracle — Admin-controlled price feed
  • ComputedOracle — Derives prices from two feeds (e.g., ETH/USD from ETH/BTC × BTC/USD)
  • TWAPAdapter — Time-weighted average pricing

Example: ChainlinkAdapter

function latestRoundData()
    external
    view
    override
    returns (
        uint80,
        int256 price,
        uint256,
        uint256 updatedAt,
        uint80,
        uint256 confidence
    )
{
    (, price, , updatedAt, ) = AGGREGATOR.latestRoundData();
    return (0, price, 0, updatedAt, 0, 0); // confidence = 0 (Chainlink doesn't provide this)
}

Critical Design Decision:

Adapters do NOT enforce:

  • ❌ Staleness rules
  • ❌ Confidence thresholds
  • ❌ Circuit breakers

Why? Because if we enforced confidence checks at the adapter level:

  • Chainlink would always fail (it returns confidence = 0)
  • The entire protocol would halt
  • We'd lose flexibility to tune validation rules per use case

Validation happens at Layer 3 (OracleSecurity), not here.


3. OracleRegistry (Layer 2: Routing + Fallback)

The registry is the single source of price data for the protocol. It handles routing and fallback logic, but still doesn't make trust decisions.

Responsibilities:

function getPrice(address asset)
    external
    view
    returns (
        int256 price,
        bool success,
        uint256 confidence,
        uint256 lastUpdated
    )
{
    bytes32 marketId = _assetToMarketId[asset];
    if (marketId == bytes32(0)) revert AssetNotLinked(asset);
return _getMarketPriceWithMetadata(marketId);

}

Key features:

  • Asset → Market ID mapping: One asset can have multiple markets (spot, perp)
  • Primary + Fallback oracles: Per-market configuration
  • Heartbeat enforcement: Market-level staleness checks
  • Metadata enrichment: Returns confidence, timestamps with price

Fallback logic:

  1. Try primary oracle (with heartbeat check)
  2. If stale or failed → try fallback oracle
  3. If both fail → return success = false

This allows the protocol to gracefully degrade rather than halt on oracle failures.


4. OracleSecurity (Layer 3: Trust Boundary)

This is the only contract protocol logic should call.

It enforces global and context-specific safety guarantees on top of raw prices.

function getValidatedPrice(address asset, PriceUse use)
    external
    view
    returns (int256)
{
    _enforceCircuitBreakers(asset);
    _enforceGlobalGuards();
(
    int256 price,
    bool success,
    uint256 confidence,
    uint256 lastUpdated
) = ORACLE_REGISTRY.getPrice(asset);

if (!success || price <= 0) revert OracleSecurity__InvalidPrice(asset);

_enforceStaleness(lastUpdated);
_enforceConfidence(use, confidence);

return price;

}

Security guarantees enforced:

  1. Circuit breakers: Global halt + per-market emergency stops
  2. Staleness enforcement: Max age for price data
  3. Context-aware confidence: Different thresholds based on use case

Context-aware validation via PriceUse enum:

enum PriceUse {
    SPOT,          // Loose confidence, informational
    PERP_TRADE,    // Medium confidence, regular trading
    LIQUIDATION,   // Strict confidence, high-stakes
    FUNDING        // Medium confidence, periodic settlements
}

Why this matters:

Use Case Confidence Requirement Reasoning
SPOT Low (e.g., 50%) Informational only, no financial risk
PERP_TRADE Medium (e.g., 75%) User-initiated, position changes
LIQUIDATION High (e.g., 95%) Involuntary, high stakes, must be accurate
FUNDING Medium (e.g., 75%) Periodic, affects all positions

Example usage:

// In PerpEngine (future integration)
int256 price = ORACLE_SECURITY.getValidatedPrice(
    asset,
    PriceUse.LIQUIDATION  // Strict validation for liquidations
);

No accidental misuse. No silent assumptions. The risk profile is explicit in the code.


5. Computed Oracle Factory

Safely deploys and registers derived price feeds:

function deployAndRegisterOracle(
    address targetAsset,
    address feedA,
    address feedB,
    Operation op,          // MULTIPLY, DIVIDE, ADD, SUBTRACT
    uint256 heartbeat
) external returns (address)

Use case: Creating synthetic prices

Example: ETH/USD from ETH/BTC and BTC/USD:

factory.deployAndRegisterOracle(
    WETH,
    ethBtcFeed,
    btcUsdFeed,
    Operation.MULTIPLY,
    3600  // 1 hour heartbeat
);

Benefits:

  • ✅ Atomic deployment + registration
  • ✅ No dangling, unregistered feeds
  • ✅ Governance-controlled expansion

🧠 Why This Architecture?

1. Prevents Protocol-Level Bricking

The problem with naive approaches:

If confidence checks were enforced at the adapter level:

  • Chainlink always returns confidence = 0
  • All Chainlink-based markets would instantly fail
  • The entire protocol would halt

Our solution:

By separating layers:

  • Adapters fetch (no validation)
  • Registry routes (fallback logic)
  • Security validates (context-aware rules)

The protocol stays flexible and resilient.


2. Enables Future Upgrades Without Rewrites

Want to:

  • ✅ Add a new oracle provider? → Write a new adapter
  • ✅ Tighten liquidation confidence rules? → Update OracleSecurity
  • ✅ Introduce TWAP fallback logic? → Update OracleRegistry

You do all of this without touching PerpEngine or any consumer contracts.

This is critical for a production protocol where:

  • Oracle providers evolve (Pyth, Chainlink, UMA, etc.)
  • Risk parameters need tuning based on market conditions
  • Governance needs to respond quickly to threats

3. Makes Oracle Risk Explicit

Consumers must declare why they're asking for a price:

// ❌ BAD (implicit assumptions)
int256 price = oracle.getPrice(asset);

// ✅ GOOD (explicit risk context)
int256 price = ORACLE_SECURITY.getValidatedPrice(asset, PriceUse.LIQUIDATION);

This prevents:

  • Accidental use of stale prices for liquidations
  • Using low-confidence prices for high-stakes operations
  • Silent degradation of security guarantees

The risk profile is now part of the type system.


What This PR Does NOT Do (By Design)

This PR intentionally lays infrastructure only:

  • ❌ Does not modify PerpEngine
  • ❌ Does not wire protocol consumers yet
  • ❌ Does not enforce liquidation logic
  • ❌ Does not implement funding rate calculations

Why?

Because getting the trust boundaries right is more important than rushing integration. A poorly designed oracle system cannot be fixed retroactively without protocol-wide rewrites.

We're building the foundation correctly first.


🔜 Follow-Up Work (Next PRs)

  1. Wire PerpEngine to OracleSecurity

    • Replace all price reads with validated calls
    • Implement context-specific PriceUse for each operation
  2. Add TWAP fallback routing inside registry

    • Automatic fallback to TWAP during high volatility
    • Configurable threshold for switching
  3. Add funding-rate specific confidence logic

    • Separate validation rules for funding calculations
    • Support for time-weighted vs spot prices
  4. Introduce oracle slashing / monitoring hooks

    • Reward reporters of oracle failures
    • Automated circuit breaker triggers

✅ Testing & Validation

Unit tests cover:

  • ✅ Adapter correctness (fetch-only behavior)
  • ✅ Registry fallback logic (primary → fallback → failure)
  • ✅ Security layer enforcement (circuit breakers, staleness, confidence)
  • ✅ Computed oracle arithmetic (no overflow/underflow)
  • ✅ Factory deployment + registration atomicity

Integration tests validate:

  • ✅ End-to-end price flow (adapter → registry → security → consumer)
  • ✅ Context-specific confidence thresholds
  • ✅ Graceful degradation during oracle failures

🎓 Final Notes

This PR intentionally front-loads correctness and safety.

It prioritizes:

  • ✅ Clear trust boundaries
  • ✅ Upgradeability
  • ✅ Risk isolation

All of which are critical for perpetual protocols, where oracle failures can lead to:

  • Mass liquidations
  • Protocol insolvency
  • Loss of user funds

Once merged, the protocol gains a production-grade oracle foundation ready for live market integration.


📚 Additional Context

Why three layers instead of two?

Two-layer designs (adapter + validation) couple routing logic with security logic. This means:

  • ❌ Can't add fallback oracles without modifying security layer
  • ❌ Can't have different heartbeats per market
  • ❌ Hard to test routing independently of validation

Three layers provide separation of concerns:

  • Layer 1: "What is the data?" (adapters)
  • Layer 2: "Where do I get it from?" (registry)
  • Layer 3: "Should I trust it?" (security)

Each layer can evolve independently.

Why PriceUse enum instead of per-function validation?

Alternative design:

function getPriceForLiquidation(address asset) external view returns (int256);
function getPriceForTrading(address asset) external view returns (int256);

Problems:

  • ❌ Function explosion (4 use cases = 4 functions)
  • ❌ Hard to add new use cases
  • ❌ Doesn't compose (what if you need "liquidation OR funding"?)

Our design:

function getValidatedPrice(address asset, PriceUse use) external view returns (int256);

Benefits:

  • ✅ Single function, multiple contexts
  • ✅ Easy to extend (just add enum values)
  • ✅ Composable (can combine multiple PriceUse checks)
  • ✅ Explicit at call site (caller declares intent)

Review Checklist:

  • Architecture diagram is clear
  • Each layer's responsibility is well-defined
  • Security guarantees are explicit
  • Future upgrade path is documented
  • Non-goals are stated (no PerpEngine integration yet)
  • Tests cover all critical paths

This commit finalizes the core components of the Baobab Protocol's decentralized oracle infrastructure, migrating to a unified  interface across all data sources (Chainlink, Pyth, Computed Feeds, and TWAP).

The central goal is to enhance manipulation resistance, data consistency, and security through a multi-layered validation approach enforced by the new  contract.

### Key Features and Changes:

1.  **BaobabOracleSecurity Integration:**
    * Implements a central security gateway () enforcing sequential checks: Circuit Breakers, Global Pause, Global Staleness (), and contextual risk-based Confidence checks ().
    * **Refinement:** Streamlined price fetching to rely solely on the  output, eliminating redundant internal calls and improving gas efficiency.

2.  **OracleRegistry (Assumed Role):**
    * Acknowledges the reliance on the core  to manage asset configuration (Primary/Fallback feeds) and enforce internal heartbeat/staleness logic before data is passed to the Security layer. The Registry acts as the sole source of data for the  layer.

3.  **ComputedOracle (Derived Prices):**
    * Introduced a system for deriving secondary asset prices (e.g., ETH/BTC) via multiplication or division of two base feeds.
    * **Security Fix:** Updated  logic to return the **oldest** component timestamp, preventing the computed price from falsely appearing fresh when an underlying feed is stale.
    * **Refinement:** Optimized fixed-point division math for gas efficiency while preserving 8-decimal output precision.

4.  **Oracle Adapters (Chainlink & Pyth):**
    * Created  and  to strictly conform to the  interface.
    * **Consistency:** Standardized output to 8 decimals for Pyth by implementing a robust  helper that handles variable exponents.
    * **Efficiency:** Refactored all adapters to ensure only a single external call is made per price query, drastically reducing transaction gas costs.
    * **Fail Safe:** Ensured adapters return the sentinel value () on any failure (call revert, stale data, non-positive price).

5.  **TWAPAdapter (Manipulation Resistance):**
    * Implemented a Time-Weighted Average Price (TWAP) adapter used for sensitive actions, sourcing only validated prices from .
    * **Security Principle:** The price pushing mechanism is permissionless to decentralize maintenance cost and increase data freshness.
    * **Note on Pruning:** Identified and noted the gas-inefficiency of the current  array pruning method, which is marked for a future refactor to a Ring/Circular Buffer structure for superior scalability.
This commit finalizes the core components of the Baobab Protocol's decentralized oracle infrastructure, migrating to a unified `IPriceFeed` interface across all data sources (Chainlink, Pyth, Computed Feeds, and TWAP).

The central goal is to enhance manipulation resistance, data consistency, and security through a multi-layered validation approach enforced by the new `BaobabOracleSecurity` contract.

### Key Features and Changes:
1. **BaobabOracleSecurity Integration:**
   - Implements a central security gateway (`getValidatedPrice`) enforcing sequential checks: Circuit Breakers, Global Pause, Global Staleness (`maxStalenessPeriod`), and contextual risk-based Confidence checks (`PriceUse`).
   - **Refinement:** Streamlined price fetching to rely solely on the `OracleRegistry` output, eliminating redundant internal calls and improving gas efficiency.

2. **OracleRegistry Role:**
   - Relies on the core `OracleRegistry` to manage asset configuration (Primary/Fallback feeds) and enforce internal heartbeat/staleness logic before data is passed to the Security layer.
   - The Registry acts as the sole source of data for the `BaobabOracleSecurity` layer.

3. **ComputedOracle (Derived Prices):**
   - Introduced a system for deriving secondary asset prices (e.g., ETH/BTC) via multiplication or division of two base feeds.
   - **Security Fix:** Updated `latestTimestamp()` logic to return the **oldest** component timestamp, preventing the computed price from falsely appearing "fresh" when an underlying feed is stale.
   - **Refinement:** Optimized fixed-point division math for gas efficiency while preserving 8-decimal output precision.

4. **Oracle Adapters (Chainlink & Pyth):**
   - Created `ChainlinkAdapter` and `PythAdapter` to strictly conform to the `IPriceFeed` interface.
   - **Consistency:** Standardized output to 8 decimals for Pyth by implementing a robust `_scalePrice` helper that handles variable exponents.
   - **Efficiency:** Refactored all adapters to ensure only a single external call is made per price query, drastically reducing transaction gas costs.
   - **Fail Safe:** Ensured adapters return the sentinel value (`type(int256).min`) on any failure (call revert, stale data, non-positive price).

5. **TWAPAdapter (Manipulation Resistance):**
   - Implemented a Time-Weighted Average Price (TWAP) adapter used for sensitive actions, sourcing only validated prices from `BaobabOracleSecurity`.
   - **Security Principle:** The price pushing mechanism is permissionless to decentralize maintenance cost and increase data freshness.
   - **Note on Pruning:** Identified gas-inefficiency in the current `_shiftLeft` array pruning method; marked for future refactor to a Ring/Circular Buffer structure for superior scalability.
@olujimiAdebakin
olujimiAdebakin merged commit 33aa70a into dev Dec 31, 2025
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant