Skip to content

Commit

Permalink
Merge pull request #65 from morpho-org/feat/wsteth-oracle
Browse files Browse the repository at this point in the history
wstETH/ETH oracle using exchange rate only
  • Loading branch information
MathisGD authored Mar 5, 2024
2 parents 3e7297c + ac54cd1 commit 756347b
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pragma solidity 0.8.21;

import {IChainlinkOracle} from "./interfaces/IChainlinkOracle.sol";
import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol";
import {IOracle} from "../../lib/morpho-blue/src/interfaces/IOracle.sol";

import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol";
import {IERC4626, VaultLib} from "./libraries/VaultLib.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {Math} from "../lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
import {Math} from "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol";

/// @title ChainlinkOracle
/// @author Morpho Labs
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity >=0.5.0;

import {IERC4626} from "./IERC4626.sol";
import {AggregatorV3Interface} from "./AggregatorV3Interface.sol";
import {IOracle} from "../../lib/morpho-blue/src/interfaces/IOracle.sol";
import {IOracle} from "../../../lib/morpho-blue/src/interfaces/IOracle.sol";

/// @title IChainlinkOracle
/// @author Morpho Labs
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

import {IStEth} from "./interfaces/IStEth.sol";
import {MinimalAggregatorV3Interface} from "./interfaces/MinimalAggregatorV3Interface.sol";

/// @title WstEthStEthExchangeRateChainlinkAdapter
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice wstETH/stETH exchange rate price feed.
/// @dev This contract should only be deployed on Ethereum and used as a price feed for Morpho oracles.
contract WstEthStEthExchangeRateChainlinkAdapter is MinimalAggregatorV3Interface {
/// @inheritdoc MinimalAggregatorV3Interface
// @dev The calculated price has 18 decimals precision, whatever the value of `decimals`.
uint8 public constant decimals = 18;

/// @notice The description of the price feed.
string public constant description = "wstETH/stETH exchange rate";

/// @notice The address of stETH on Ethereum.
IStEth public constant ST_ETH = IStEth(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84);

/// @inheritdoc MinimalAggregatorV3Interface
/// @dev Returns zero for roundId, startedAt, updatedAt and answeredInRound.
/// @dev Silently overflows if `getPooledEthByShares`'s return value is greater than `type(int256).max`.
function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {
// It is assumed that `getPooledEthByShares` returns a price with 18 decimals precision.
return (0, int256(ST_ETH.getPooledEthByShares(1 ether)), 0, 0, 0);
}
}
6 changes: 6 additions & 0 deletions src/wsteth-exchange-rate-adapter/interfaces/IStEth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

interface IStEth {
function getPooledEthByShares(uint256) external view returns (uint256);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

/// @dev Inspired by
/// https://github.com/smartcontractkit/chainlink/blob/master/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol
/// @dev This is the minimal feed interface required by `MorphoChainlinkOracleV2`.
interface MinimalAggregatorV3Interface {
/// @notice Returns the precision of the feed.
function decimals() external view returns (uint8);

/// @notice Returns Chainlink's `latestRoundData` return values.
/// @notice Only the `answer` field is used by `MorphoChainlinkOracleV2`.
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
25 changes: 2 additions & 23 deletions test/ChainlinkOracleTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,9 @@
pragma solidity ^0.8.0;

import "../lib/forge-std/src/Test.sol";
import "../src/ChainlinkOracle.sol";
import "../src/libraries/ErrorsLib.sol";
import "../src/morpho-chainlink-v1/ChainlinkOracle.sol";
import "./mocks/ChainlinkAggregatorMock.sol";

AggregatorV3Interface constant feedZero = AggregatorV3Interface(address(0));
// 8 decimals of precision
AggregatorV3Interface constant btcUsdFeed = AggregatorV3Interface(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c);
// 8 decimals of precision
AggregatorV3Interface constant usdcUsdFeed = AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6);
// 18 decimals of precision
AggregatorV3Interface constant btcEthFeed = AggregatorV3Interface(0xdeb288F737066589598e9214E782fa5A8eD689e8);
// 8 decimals of precision
AggregatorV3Interface constant wBtcBtcFeed = AggregatorV3Interface(0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23);
// 18 decimals of precision
AggregatorV3Interface constant stEthEthFeed = AggregatorV3Interface(0x86392dC19c0b719886221c78AB11eb8Cf5c52812);
// 18 decimals of precision
AggregatorV3Interface constant usdcEthFeed = AggregatorV3Interface(0x986b5E1e1755e3C2440e960477f25201B0a8bbD4);
// 8 decimals of precision
AggregatorV3Interface constant ethUsdFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
// 18 decimals of precision
AggregatorV3Interface constant daiEthFeed = AggregatorV3Interface(0x773616E4d11A78F511299002da57A0a94577F1f4);

IERC4626 constant vaultZero = IERC4626(address(0));
IERC4626 constant sDaiVault = IERC4626(0x83F20F44975D03b1b09e64809B757c47f942BEeA);
import "./helpers/Constants.sol";

contract ChainlinkOracleTest is Test {
function setUp() public {
Expand Down
51 changes: 51 additions & 0 deletions test/WstEthStEthExchangeRateChainlinkAdapterTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "./helpers/Constants.sol";
import "../lib/forge-std/src/Test.sol";
import {ChainlinkOracle} from "../src/morpho-chainlink-v1/ChainlinkOracle.sol";
import "../src/wsteth-exchange-rate-adapter/WstEthStEthExchangeRateChainlinkAdapter.sol";

contract WstEthStEthExchangeRateChainlinkAdapterTest is Test {
IStEth internal constant ST_ETH = IStEth(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84);

WstEthStEthExchangeRateChainlinkAdapter internal oracle;
ChainlinkOracle internal chainlinkOracle;

function setUp() public {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));
oracle = new WstEthStEthExchangeRateChainlinkAdapter();
chainlinkOracle = new ChainlinkOracle(
vaultZero, AggregatorV3Interface(address(oracle)), feedZero, feedZero, feedZero, 1, 18, 18
);
}

function testDecimals() public {
assertEq(oracle.decimals(), uint8(18));
}

function testDescription() public {
assertEq(oracle.description(), "wstETH/stETH exchange rate");
}

function testLatestRoundData() public {
(uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
oracle.latestRoundData();
assertEq(roundId, 0);
assertEq(uint256(answer), ST_ETH.getPooledEthByShares(1 ether));
assertEq(startedAt, 0);
assertEq(updatedAt, 0);
assertEq(answeredInRound, 0);
}

function testLatestRoundDataBounds() public {
(, int256 answer,,,) = oracle.latestRoundData();
assertGe(uint256(answer), 1154690031824824994); // Exchange rate queried at block 19070943
assertLe(uint256(answer), 1.5e18); // Max bounds of the exchange rate. Should work for a long enough time.
}

function testOracleWstEthStEthExchangeRate() public {
(, int256 expectedPrice,,,) = oracle.latestRoundData();
assertEq(chainlinkOracle.price(), uint256(expectedPrice) * 10 ** (36 + 18 - 18 - 18));
}
}
25 changes: 25 additions & 0 deletions test/helpers/Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {IERC4626, AggregatorV3Interface} from "../../src/morpho-chainlink-v1/ChainlinkOracle.sol";

AggregatorV3Interface constant feedZero = AggregatorV3Interface(address(0));
// 8 decimals of precision
AggregatorV3Interface constant btcUsdFeed = AggregatorV3Interface(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c);
// 8 decimals of precision
AggregatorV3Interface constant usdcUsdFeed = AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6);
// 18 decimals of precision
AggregatorV3Interface constant btcEthFeed = AggregatorV3Interface(0xdeb288F737066589598e9214E782fa5A8eD689e8);
// 8 decimals of precision
AggregatorV3Interface constant wBtcBtcFeed = AggregatorV3Interface(0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23);
// 18 decimals of precision
AggregatorV3Interface constant stEthEthFeed = AggregatorV3Interface(0x86392dC19c0b719886221c78AB11eb8Cf5c52812);
// 18 decimals of precision
AggregatorV3Interface constant usdcEthFeed = AggregatorV3Interface(0x986b5E1e1755e3C2440e960477f25201B0a8bbD4);
// 8 decimals of precision
AggregatorV3Interface constant ethUsdFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
// 18 decimals of precision
AggregatorV3Interface constant daiEthFeed = AggregatorV3Interface(0x773616E4d11A78F511299002da57A0a94577F1f4);

IERC4626 constant vaultZero = IERC4626(address(0));
IERC4626 constant sDaiVault = IERC4626(0x83F20F44975D03b1b09e64809B757c47f942BEeA);

0 comments on commit 756347b

Please sign in to comment.