# Custom oracle control Case ID: morpho1 Protocol: Morpho Component: Oracle ## Summary Morpho markets allow custom oracles to be used. There is nothing stopping a market creator from deploying a completely custom oracle with strange, unexpected, or outright malicious behaviour. Below, we show how a market using legitimate tokens can have its collateral price manipulated through a custom oracle. ## Context > Skip this if you already know how Morpho works. There are three useful pieces to keep in mind when thinking about Morpho: **Assets**, **Markets** and **Vaults**. **Assets** can be used permissionlessly. Anyone creating a market can choose its loan asset and collateral asset. **Markets** are also permissionless. A market creator chooses the assets, LLTV, interest-rate model and, importantly here, the oracle used to value the collateral. **Vaults** sit one level above markets. Allocators choose which markets vault capital can enter and therefore which assets and market configurations depositors are exposed to. ![A depositor supplies a vault that allocates to a Morpho market configured with USDC as the loan asset, AZND as the collateral asset, an LLTV, and an oracle.](/content/cases/morpho/1/vault-market-asset.png) ## Example For this POC, we deployed a [vault](https://app.morpho.org/base/vault/0xBdBAF6d413c174B2e5Aed83Dd9224294306A1fE6/just-vibin) and a [Morpho market](https://app.morpho.org/base/variable/0x3df6f44a6c5ff603f00de0c8106252fd5aadd79918ab7662717b851082d2f90c/usdc-zro) on Base. The market uses two legitimate tokens: [USDC](https://basescan.org/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) as the loan asset and [LayerZero's ZRO](https://basescan.org/token/0x6985884C4392D348587B19cb9eAAf157F13271cd) as collateral. We deployed a [custom oracle](https://basescan.org/address/0x22a5f13244D36151c32313840f79A0c78f577DE9#code) for the market instead of using an independent price feed. ### Contracts - [Vault](https://app.morpho.org/base/vault/0xBdBAF6d413c174B2e5Aed83Dd9224294306A1fE6/just-vibin) `app.morpho.org` The vault deployed for the POC. - [Morpho market](https://app.morpho.org/base/variable/0x3df6f44a6c5ff603f00de0c8106252fd5aadd79918ab7662717b851082d2f90c/usdc-zro) `app.morpho.org` The market with USDC as its loan asset and ZRO as its collateral asset. - [ZRO token](https://basescan.org/token/0x6985884C4392D348587B19cb9eAAf157F13271cd) `basescan.org` The LayerZero token used as collateral. - [Custom oracle](https://basescan.org/address/0x22a5f13244D36151c32313840f79A0c78f577DE9#code) `basescan.org` The verified oracle source used by the market. The key point here is that this is a valid price oracle from Morpho's perspective because it implements [`IOracle`](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IOracle.sol). Morpho's interface documentation says that users are responsible for selecting markets with safe oracles. Here, the owner is the deployer, so the deployer can update the price. ```solidity // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.13; import {IOracle} from "./IOracle.sol"; /// @title SettableOracle /// @notice A Morpho-compatible oracle whose price can be updated by its deployer. contract SettableOracle is IOracle { /// @notice Thrown when an address other than the owner attempts to set the price. error NotOwner(); /// @notice Emitted whenever the oracle price is updated. event PriceSet(uint256 oldPrice, uint256 newPrice); /// @notice The only address permitted to update the price. address public immutable owner; /// @inheritdoc IOracle uint256 public override price; constructor() { owner = msg.sender; } /// @notice Sets the oracle price. /// @param newPrice The new price in Morpho's oracle scale. function setPrice(uint256 newPrice) external { if (msg.sender != owner) revert NotOwner(); emit PriceSet(price, newPrice); price = newPrice; } } ``` ### Example - ZRO trades at `$1` and the oracle initially reports `$1`. - The vault allocates `$1,000,000` USDC to the market. - A borrower supplies `500,000` ZRO as collateral, worth `$500,000` at its real market price. - At the market's `98%` LLTV, that collateral should support up to `$490,000` of USDC borrowing. - The deployer changes the oracle price to `$2`. - Morpho now values the same collateral at `$1,000,000`, allowing the borrower to take up to `$980,000` USDC. - Liquidators can only sell the ZRO for its real value of `$500,000`, leaving insufficient value to repay the USDC. ## How to address Go to the market and check its linked oracle. If a curator is allocating to a new market, or a vault has caps that allow it to allocate to that market, confirm that the oracle has been checked. Work backwards from `price()`. Check: - Is the oracle contract verified? - Is it a proxy or otherwise upgradeable? - What ultimately determines the value returned by `price()`? - Does it depend on recognisable external feeds such as Chainlink or RedStone? - Are those feeds themselves the expected contracts? - Does the oracle contain privileged setters or mutable configuration? - Can an owner, admin or other address materially influence the returned price? - Is the price effectively hard-coded? - If a fixed-price assumption is being made, what economically enforces that price?