Skip to content

Commit

Permalink
Merge pull request #4 from morpho-labs/feat/chainlink-threeway
Browse files Browse the repository at this point in the history
Chainlink threeway oracles
  • Loading branch information
MathisGD authored Oct 2, 2023
2 parents 6a80566 + e7da492 commit 539b6b4
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 5 deletions.
8 changes: 4 additions & 4 deletions src/chainlink/Oracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ contract Oracle is IOracle {

/* CONSTRUCTOR */

/// @param baseFeed Base feed. Pass address zero for price = 1.
/// @param baseTokenDecimals Base token decimals. Pass 0 if the price is one.
/// @param quoteFeed Quote feed. Pass address zero for price = 1.
/// @param quoteTokenDecimals Quote token decimals. Pass 0 if the price is one.
/// @param baseFeed Base feed. Pass address zero if the price = 1.
/// @param baseTokenDecimals Base token decimals. Pass 0 if the price = 1.
/// @param quoteFeed Quote feed. Pass address zero if the price = 1.
/// @param quoteTokenDecimals Quote token decimals. Pass 0 if the price = 1.
constructor(
AggregatorV3Interface baseFeed,
uint256 baseTokenDecimals,
Expand Down
54 changes: 54 additions & 0 deletions src/chainlink/OracleThreeWay.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

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

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

contract OracleThreeWay is IOracle {
using DataFeedLib for AggregatorV3Interface;

/* CONSTANT */

/// @notice First base feed.
AggregatorV3Interface public immutable FIRST_BASE_FEED;
/// @notice Second base feed.
AggregatorV3Interface public immutable SECOND_BASE_FEED;
/// @notice Quote feed.
AggregatorV3Interface public immutable QUOTE_FEED;
/// @notice Price scale factor. Automatically computed at contract creation.
uint256 public immutable SCALE_FACTOR;

/* CONSTRUCTOR */

/// @param firstBaseFeed First base feed.
/// @param secondBaseFeed Second base feed.
/// @param quoteFeed Quote feed. Pass address zero if the price = 1.
/// @param baseTokenDecimals Base token decimals.
/// @param quoteTokenDecimals Quote token decimals. Pass 0 if the price = 1.
constructor(
AggregatorV3Interface firstBaseFeed,
AggregatorV3Interface secondBaseFeed,
AggregatorV3Interface quoteFeed,
uint256 baseTokenDecimals,
uint256 quoteTokenDecimals
) {
FIRST_BASE_FEED = firstBaseFeed;
SECOND_BASE_FEED = secondBaseFeed;
QUOTE_FEED = quoteFeed;
// SCALE_FACTOR = 10 ** (36 + (quoteFeedDecimals - quoteTokenDecimals) - (firstBaseFeedDecimals +
// secondBaseFeedDecimals - baseTokenDecimals))
SCALE_FACTOR = 10
** (
36 + baseTokenDecimals + quoteFeed.getDecimals() - firstBaseFeed.getDecimals()
- secondBaseFeed.getDecimals() - quoteTokenDecimals
);
}

/* PRICE */

/// @inheritdoc IOracle
function price() external view returns (uint256) {
return (FIRST_BASE_FEED.getPrice() * SECOND_BASE_FEED.getPrice() * SCALE_FACTOR) / QUOTE_FEED.getPrice();
}
}
2 changes: 1 addition & 1 deletion src/chainlink/libraries/DataFeedLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {AggregatorV3Interface} from "chainlink/contracts/src/v0.8/interfaces/Agg

library DataFeedLib {
/// @dev Performing some security checks and returns the lateste price of a feed.
/// @dev When feed = address(0), returns 1.
/// @dev When feed is the address zero, returns 1.
function getPrice(AggregatorV3Interface feed) internal view returns (uint256) {
if (address(feed) == address(0)) return 1;
(, int256 answer,,,) = feed.latestRoundData();
Expand Down
40 changes: 40 additions & 0 deletions test/chainlink/OracleThreeWayTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "src/chainlink/OracleThreeWay.sol";
import "src/chainlink/libraries/ErrorsLib.sol";

// 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);

contract OracleTest is Test {
function setUp() public {
vm.selectFork(vm.createFork(vm.envString("ETH_RPC_URL")));
}

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

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

0 comments on commit 539b6b4

Please sign in to comment.