|
| 1 | +// SPDX-License-Identifier: BSD-3-Clause |
| 2 | +// SPDX-FileCopyrightText: 2025 Venus |
| 3 | +pragma solidity 0.8.25; |
| 4 | + |
| 5 | +import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; |
| 6 | +import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol"; |
| 7 | +import { ResilientOracleInterface, OracleInterface } from "./interfaces/OracleInterface.sol"; |
| 8 | + |
| 9 | +/** |
| 10 | + * @title ReferenceOracle |
| 11 | + * @author Venus |
| 12 | + * @notice Reference oracle is the oracle that is not used for production but required for |
| 13 | + * price monitoring. This oracle contains some extra configurations for assets required to |
| 14 | + * compute reference prices of their derivative assets (OneJump, ERC4626, Pendle, etc.) |
| 15 | + */ |
| 16 | +contract ReferenceOracle is Ownable2StepUpgradeable, OracleInterface { |
| 17 | + struct ExternalPrice { |
| 18 | + /// @notice asset address |
| 19 | + address asset; |
| 20 | + /// @notice price of the asset from an external source |
| 21 | + uint256 price; |
| 22 | + } |
| 23 | + |
| 24 | + /// @notice Slot to temporarily store price information from external sources |
| 25 | + /// like CMC/Coingecko, useful to compute prices of derivative assets based on |
| 26 | + /// prices of the base assets with no on chain price information |
| 27 | + bytes32 public constant PRICES_SLOT = keccak256(abi.encode("venus-protocol/oracle/ReferenceOracle/prices")); |
| 28 | + |
| 29 | + /// @notice Resilient oracle address |
| 30 | + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable |
| 31 | + ResilientOracleInterface public immutable RESILIENT_ORACLE; |
| 32 | + |
| 33 | + /// @notice Oracle configuration for assets |
| 34 | + mapping(address => OracleInterface) public oracles; |
| 35 | + |
| 36 | + /// @notice Event emitted when an oracle is set |
| 37 | + event OracleConfigured(address indexed asset, address indexed oracle); |
| 38 | + |
| 39 | + /** |
| 40 | + * @notice Constructor for the implementation contract. Sets immutable variables. |
| 41 | + * @param resilientOracle Resilient oracle address |
| 42 | + * @custom:error ZeroAddressNotAllowed is thrown if resilient oracle address is null |
| 43 | + * @custom:oz-upgrades-unsafe-allow constructor |
| 44 | + */ |
| 45 | + constructor(ResilientOracleInterface resilientOracle) { |
| 46 | + ensureNonzeroAddress(address(resilientOracle)); |
| 47 | + RESILIENT_ORACLE = resilientOracle; |
| 48 | + _disableInitializers(); |
| 49 | + } |
| 50 | + |
| 51 | + /** |
| 52 | + * @notice Initializes the contract admin |
| 53 | + */ |
| 54 | + function initialize() external initializer { |
| 55 | + __Ownable2Step_init(); |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * @notice Sets an oracle to use for a specific asset |
| 60 | + * @dev The production resilientOracle will be used if zero address is passed |
| 61 | + * @param asset Asset address |
| 62 | + * @param oracle Oracle address |
| 63 | + * @custom:access Only owner |
| 64 | + * @custom:error ZeroAddressNotAllowed is thrown if asset address is null |
| 65 | + * @custom:event Emits OracleConfigured event |
| 66 | + */ |
| 67 | + function setOracle(address asset, OracleInterface oracle) external onlyOwner { |
| 68 | + ensureNonzeroAddress(asset); |
| 69 | + oracles[asset] = OracleInterface(oracle); |
| 70 | + emit OracleConfigured(asset, address(oracle)); |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * @notice Gets price of the asset assuming other assets have the defined price |
| 75 | + * @param asset asset address |
| 76 | + * @param externalPrices an array of prices for other assets |
| 77 | + * @return USD price in scaled decimal places |
| 78 | + */ |
| 79 | + function getPriceAssuming(address asset, ExternalPrice[] memory externalPrices) external returns (uint256) { |
| 80 | + uint256 externalPricesCount = externalPrices.length; |
| 81 | + for (uint256 i = 0; i < externalPricesCount; ++i) { |
| 82 | + _storeExternalPrice(externalPrices[i].asset, externalPrices[i].price); |
| 83 | + } |
| 84 | + return _getPrice(asset); |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * @notice Gets price of the asset |
| 89 | + * @param asset asset address |
| 90 | + * @return USD price in scaled decimal places |
| 91 | + */ |
| 92 | + function getPrice(address asset) external view override returns (uint256) { |
| 93 | + return _getPrice(asset); |
| 94 | + } |
| 95 | + |
| 96 | + function _storeExternalPrice(address asset, uint256 price) internal { |
| 97 | + bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset)); |
| 98 | + // solhint-disable-next-line no-inline-assembly |
| 99 | + assembly ("memory-safe") { |
| 100 | + tstore(slot, price) |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + function _getPrice(address asset) internal view returns (uint256) { |
| 105 | + uint256 externalPrice = _loadExternalPrice(asset); |
| 106 | + if (externalPrice != 0) { |
| 107 | + return externalPrice; |
| 108 | + } |
| 109 | + OracleInterface oracle = oracles[asset]; |
| 110 | + if (oracle != OracleInterface(address(0))) { |
| 111 | + return oracle.getPrice(asset); |
| 112 | + } |
| 113 | + return RESILIENT_ORACLE.getPrice(asset); |
| 114 | + } |
| 115 | + |
| 116 | + function _loadExternalPrice(address asset) internal view returns (uint256 value) { |
| 117 | + bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset)); |
| 118 | + // solhint-disable-next-line no-inline-assembly |
| 119 | + assembly ("memory-safe") { |
| 120 | + value := tload(slot) |
| 121 | + } |
| 122 | + } |
| 123 | +} |
0 commit comments