Skip to content

Commit

Permalink
Merge branch 'feat/chainlink' of github.com:morpho-labs/morpho-blue-o…
Browse files Browse the repository at this point in the history
…racles into merlin-review
  • Loading branch information
MerlinEgalite committed Oct 4, 2023
2 parents 7abc4f2 + 0007653 commit 35d7992
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 259 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/chainlink"]
path = lib/chainlink
url = https://github.com/smartcontractkit/chainlink
[submodule "lib/morpho-blue"]
path = lib/morpho-blue
url = https://github.com/morpho-labs/morpho-blue
1 change: 0 additions & 1 deletion lib/chainlink
Submodule chainlink deleted from 6163d1
2 changes: 1 addition & 1 deletion lib/morpho-blue
58 changes: 58 additions & 0 deletions src/ChainlinkOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

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

import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol";

contract ChainlinkOracle is IOracle {
using ChainlinkDataFeedLib for AggregatorV3Interface;

/* CONSTANT */

/// @notice First base feed.
AggregatorV3Interface public immutable BASE_FEED_1;
/// @notice Second base feed.
AggregatorV3Interface public immutable BASE_FEED_2;
/// @notice First quote feed.
AggregatorV3Interface public immutable QUOTE_FEED_1;
/// @notice Second quote feed.
AggregatorV3Interface public immutable QUOTE_FEED_2;
/// @notice Price scale factor, computed at contract creation.
uint256 public immutable SCALE_FACTOR;

/* CONSTRUCTOR */

/// @param baseFeed1 First base feed. Pass address zero if the price = 1.
/// @param baseFeed2 Second base feed. Pass address zero if the price = 1.
/// @param quoteFeed1 First quote feed. Pass address zero if the price = 1.
/// @param quoteFeed2 Second quote feed. Pass address zero if the price = 1.
/// @param baseTokenDecimals Base token decimals.
/// @param quoteTokenDecimals Quote token decimals.
constructor(
AggregatorV3Interface baseFeed1,
AggregatorV3Interface baseFeed2,
AggregatorV3Interface quoteFeed1,
AggregatorV3Interface quoteFeed2,
uint256 baseTokenDecimals,
uint256 quoteTokenDecimals
) {
BASE_FEED_1 = baseFeed1;
BASE_FEED_2 = baseFeed2;
QUOTE_FEED_1 = quoteFeed1;
QUOTE_FEED_2 = quoteFeed2;
SCALE_FACTOR = 10
** (
36 + quoteTokenDecimals + quoteFeed1.getDecimals() + quoteFeed2.getDecimals() - baseFeed1.getDecimals()
- baseFeed2.getDecimals() - baseTokenDecimals
);
}

/* PRICE */

/// @inheritdoc IOracle
function price() external view returns (uint256) {
return (BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice() * SCALE_FACTOR)
/ (QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice());
}
}
62 changes: 0 additions & 62 deletions src/chainlink/OracleFourFeeds.sol

This file was deleted.

55 changes: 0 additions & 55 deletions src/chainlink/OracleTwoFeeds.sol

This file was deleted.

22 changes: 22 additions & 0 deletions src/interfaces/AggregatorV3Interface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @dev From
/// https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol.
interface AggregatorV3Interface {
function decimals() external view returns (uint8);

function description() external view returns (string memory);

function version() external view returns (uint256);

function getRoundData(uint80 _roundId)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {AggregatorV3Interface} from "chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {AggregatorV3Interface} from "../interfaces/AggregatorV3Interface.sol";

import {ErrorsLib} from "./ErrorsLib.sol";

/// @title DataFeedLib
/// @title ChainlinkDataFeedLib
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Library exposing functions to interact with a Chainlink-compliant feed.
library DataFeedLib {
library ChainlinkDataFeedLib {
/// @dev Returns the latest price of a `feed`.
/// @dev Performing some security checks and returns the latest price of a feed.
/// @dev Performs some safety checks and returns the latest price of a feed.
/// @dev When `feed` is the address zero, returns 1.
function getPrice(AggregatorV3Interface feed) internal view returns (uint256) {
if (address(feed) == address(0)) return 1;
Expand All @@ -20,7 +20,8 @@ library DataFeedLib {
return uint256(answer);
}

/// @dev Returns `feed.decimals()` when `feed` is not the address zero, else returns 0.
/// @dev Returns the number of decimals of a feed.
/// @dev When `feed` is the address zero, returns 0.
function getDecimals(AggregatorV3Interface feed) internal view returns (uint256) {
if (address(feed) == address(0)) return 0;
return feed.decimals();
Expand Down
File renamed without changes.
117 changes: 117 additions & 0 deletions test/chainlink/ChainlinkOracleTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "src/ChainlinkOracle.sol";
import "src/libraries/ErrorsLib.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);

contract FakeAggregator {
int256 public answer;

function setAnwser(int256 newAnswer) external {
answer = newAnswer;
}

function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {
return (0, answer, 0, 0, 0);
}

function decimals() external pure returns (uint256) {
return 8;
}
}

contract ChainlinkOracleTest is Test {
function setUp() public {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));
}

function testOracleWbtcUsdc() public {
ChainlinkOracle oracle = new ChainlinkOracle(wBtcBtcFeed, btcUsdFeed, usdcUsdFeed, feedZero, 8, 6);
(, int256 firstBaseAnswer,,,) = wBtcBtcFeed.latestRoundData();
(, int256 secondBaseAnswer,,,) = btcUsdFeed.latestRoundData();
(, int256 quoteAnswer,,,) = usdcUsdFeed.latestRoundData();
assertEq(
oracle.price(),
(uint256(firstBaseAnswer) * uint256(secondBaseAnswer) * 10 ** (36 + 8 + 6 - 8 - 8 - 8))
/ uint256(quoteAnswer)
);
}

function testOracleUsdcWbtc() public {
ChainlinkOracle oracle = new ChainlinkOracle(usdcUsdFeed, feedZero, wBtcBtcFeed, btcUsdFeed, 6, 8);
(, int256 baseAnswer,,,) = usdcUsdFeed.latestRoundData();
(, int256 firstQuoteAnswer,,,) = wBtcBtcFeed.latestRoundData();
(, int256 secondQuoteAnswer,,,) = btcUsdFeed.latestRoundData();
assertEq(
oracle.price(),
(uint256(baseAnswer) * 10 ** (36 + 8 + 8 + 8 - 6 - 8))
/ (uint256(firstQuoteAnswer) * uint256(secondQuoteAnswer))
);
}

function testOracleWbtcEth() public {
ChainlinkOracle oracle = new ChainlinkOracle(wBtcBtcFeed, btcEthFeed, feedZero, feedZero, 8, 18);
(, int256 firstBaseAnswer,,,) = wBtcBtcFeed.latestRoundData();
(, int256 secondBaseAnswer,,,) = btcEthFeed.latestRoundData();
assertEq(oracle.price(), (uint256(firstBaseAnswer) * uint256(secondBaseAnswer) * 10 ** (36 + 18 - 8 - 8 - 18)));
}

function testOracleStEthUsdc() public {
ChainlinkOracle oracle = new ChainlinkOracle(stEthEthFeed, feedZero, usdcEthFeed, feedZero, 18, 6);
(, int256 baseAnswer,,,) = stEthEthFeed.latestRoundData();
(, int256 quoteAnswer,,,) = usdcEthFeed.latestRoundData();
assertEq(oracle.price(), uint256(baseAnswer) * 10 ** (36 + 18 + 6 - 18 - 18) / uint256(quoteAnswer));
}

function testOracleEthUsd() public {
ChainlinkOracle oracle = new ChainlinkOracle(ethUsdFeed, feedZero, feedZero, feedZero, 18, 0);
(, int256 expectedPrice,,,) = ethUsdFeed.latestRoundData();
assertEq(oracle.price(), uint256(expectedPrice) * 10 ** (36 - 18 - 8));
}

function testOracleStEthEth() public {
ChainlinkOracle oracle = new ChainlinkOracle(stEthEthFeed, feedZero, feedZero, feedZero, 18, 18);
(, int256 expectedPrice,,,) = stEthEthFeed.latestRoundData();
assertEq(oracle.price(), uint256(expectedPrice) * 10 ** (36 + 18 - 18 - 18));
assertApproxEqRel(oracle.price(), 1e36, 0.01 ether);
}

function testOracleEthStEth() public {
ChainlinkOracle oracle = new ChainlinkOracle(feedZero, feedZero, stEthEthFeed, feedZero, 18, 18);
(, int256 expectedPrice,,,) = stEthEthFeed.latestRoundData();
assertEq(oracle.price(), 10 ** (36 + 18 + 18 - 18) / uint256(expectedPrice));
assertApproxEqRel(oracle.price(), 1e36, 0.01 ether);
}

function testOracleUsdcUsd() public {
ChainlinkOracle oracle = new ChainlinkOracle(usdcUsdFeed, feedZero, feedZero, feedZero, 6, 0);
assertApproxEqRel(oracle.price(), 1e36 / 1e6, 0.01 ether);
}

function testNegativeAnswer(int256 price) public {
price = bound(price, type(int256).min, -1);
FakeAggregator aggregator = new FakeAggregator();
ChainlinkOracle oracle =
new ChainlinkOracle(AggregatorV3Interface(address(aggregator)), feedZero, feedZero, feedZero, 18, 0);
aggregator.setAnwser(price);
vm.expectRevert(bytes(ErrorsLib.NEGATIVE_ANSWER));
oracle.price();
}
}
Loading

0 comments on commit 35d7992

Please sign in to comment.