Section 8 of 18

Build
+10 Lynx

Price Oracle

What You Are Building

You are building the price oracle interface and a simple implementation. The oracle tells the Comptroller how much each asset is worth, which is essential for every risk calculation in the protocol. Without accurate prices, the entire collateral and borrowing system breaks down.

Oracle manipulation is the single most exploited attack vector in lending protocols. Mango Markets lost $117M to oracle manipulation. Cream Finance lost $130M. When you audit Compound forks, this is where you look first.

Concept: Why Oracles Matter for Lending

Uniswap V2 does not need external price data. Each pool is self-contained. The ratio of reserves IS the price. Compound is fundamentally different. A user deposits ETH and borrows USDC. The protocol must know how much the ETH collateral is worth in the same units as the USDC debt. This requires an oracle.

The oracle answers one question: "What is the price of the underlying asset for this cToken?" Every time the Comptroller calculates whether a user is solvent, it calls getUnderlyingPrice() for every market the user has entered. The price is returned as a mantissa (scaled by 1e18), denominated in a common unit (typically USD or ETH).

Concept: The PriceOracle Interface

Compound V2 defines a minimal abstract contract:

abstract contract PriceOracle {
    bool public constant isPriceOracle = true;
    function getUnderlyingPrice(address cToken) virtual external view returns (uint256);
}

The isPriceOracle constant is a type check. When the Comptroller admin sets a new oracle, the Comptroller verifies oracle.isPriceOracle() returns true. This prevents accidentally setting the oracle to a random contract address. It is a pattern you will see throughout Compound: sentinel constants that act as interface markers.

The getUnderlyingPrice() function takes a cToken address (not the underlying token address). This is a design choice. The oracle implementation maps cToken to underlying internally. It keeps the Comptroller's code simpler since it already works with cToken addresses everywhere.

Concept: SimplePriceOracle vs Production Oracles

The SimplePriceOracle you are building is for testing and development. An admin manually sets prices. This is obviously not suitable for production because a single admin controls all prices.

In production, Compound uses Chainlink price feeds through an adapter contract. Chainlink aggregates prices from multiple independent data providers, making manipulation extremely expensive. The adapter contract reads Chainlink's latestRoundData() and converts it to the format Compound expects.

Oracle manipulation is one of the most exploited attack vectors in DeFi. Mango Markets lost $117M in October 2022 when an attacker manipulated the price of MNGO on low-liquidity markets, then used the inflated value as collateral to drain the protocol. The oracle reported the manipulated price as real, and the Comptroller approved massive borrows against worthless collateral.

This is why the Comptroller rejects operations when the oracle returns 0. A zero price is treated as "price unavailable," and no borrowing or liquidation can proceed.

Your Task

Build two contracts:

  1. PriceOracle (abstract contract):

    • Declare a public constant isPriceOracle set to true.
    • Declare an abstract function getUnderlyingPrice(address cToken) that is external, view, and returns uint256.
  2. SimplePriceOracle (inherits PriceOracle):

    • State: mapping(address => uint256) public prices and address public admin.
    • Event: PricePosted(address cToken, uint256 previousPrice, uint256 newPrice).
    • Constructor: set admin = msg.sender.
    • setUnderlyingPrice(address cToken, uint256 underlyingPriceMantissa): require caller is admin, store the previous price, update prices[cToken], emit PricePosted.
    • getUnderlyingPrice(address cToken): override the abstract function, return prices[cToken].

Your Code

Solution.sol
Solidity
Loading editor...

Requirements

Write your implementation, then click Run Tests. Tests execute on the server.