From 0063cb04cd6588b97b32f32bc4720e20ba8bc734 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 4 Oct 2023 11:17:07 +0200 Subject: [PATCH 1/7] style: rename feeds --- src/chainlink/OracleFourFeeds.sol | 40 +++++++++++++------------- test/chainlink/OracleFourFeedsTest.sol | 2 +- test/chainlink/OracleTwoFeedsTest.sol | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/chainlink/OracleFourFeeds.sol b/src/chainlink/OracleFourFeeds.sol index 084f5cb..d7eb3e5 100644 --- a/src/chainlink/OracleFourFeeds.sol +++ b/src/chainlink/OracleFourFeeds.sol @@ -11,40 +11,40 @@ contract OracleFourFeeds is IOracle { /* CONSTANT */ /// @notice First base feed. - AggregatorV3Interface public immutable FIRST_BASE_FEED; + AggregatorV3Interface public immutable BASE_FEED_1; /// @notice Second base feed. - AggregatorV3Interface public immutable SECOND_BASE_FEED; + AggregatorV3Interface public immutable BASE_FEED_2; /// @notice First quote feed. - AggregatorV3Interface public immutable FIRST_QUOTE_FEED; + AggregatorV3Interface public immutable QUOTE_FEED_1; /// @notice Second quote feed. - AggregatorV3Interface public immutable SECOND_QUOTE_FEED; + AggregatorV3Interface public immutable QUOTE_FEED_2; /// @notice Price scale factor, computed at contract creation. uint256 public immutable SCALE_FACTOR; /* CONSTRUCTOR */ - /// @param firstBaseFeed First base feed. Pass address zero if the price = 1. - /// @param secondBaseFeed Second base feed. Pass address zero if the price = 1. - /// @param firstQuoteFeed Quote feed. Pass address zero if the price = 1. - /// @param secondQuoteFeed Quote feed. Pass address zero if the price = 1. + /// @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 firstBaseFeed, - AggregatorV3Interface secondBaseFeed, - AggregatorV3Interface firstQuoteFeed, - AggregatorV3Interface secondQuoteFeed, + AggregatorV3Interface baseFeed1, + AggregatorV3Interface baseFeed2, + AggregatorV3Interface quoteFeed1, + AggregatorV3Interface quoteFeed2, uint256 baseTokenDecimals, uint256 quoteTokenDecimals ) { - FIRST_BASE_FEED = firstBaseFeed; - SECOND_BASE_FEED = secondBaseFeed; - FIRST_QUOTE_FEED = firstQuoteFeed; - SECOND_QUOTE_FEED = secondQuoteFeed; + BASE_FEED_1 = baseFeed1; + BASE_FEED_2 = baseFeed2; + QUOTE_FEED_1 = quoteFeed1; + QUOTE_FEED_2 = quoteFeed2; SCALE_FACTOR = 10 ** ( - 36 + quoteTokenDecimals + firstQuoteFeed.getDecimals() + secondQuoteFeed.getDecimals() - - firstBaseFeed.getDecimals() - secondBaseFeed.getDecimals() - baseTokenDecimals + 36 + quoteTokenDecimals + quoteFeed1.getDecimals() + quoteFeed2.getDecimals() + - baseFeed1.getDecimals() - baseFeed2.getDecimals() - baseTokenDecimals ); } @@ -52,7 +52,7 @@ contract OracleFourFeeds is IOracle { /// @inheritdoc IOracle function price() external view returns (uint256) { - return (FIRST_BASE_FEED.getPrice() * SECOND_BASE_FEED.getPrice() * SCALE_FACTOR) - / (FIRST_QUOTE_FEED.getPrice() * SECOND_QUOTE_FEED.getPrice()); + return (BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice() * SCALE_FACTOR) + / (QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()); } } diff --git a/test/chainlink/OracleFourFeedsTest.sol b/test/chainlink/OracleFourFeedsTest.sol index 52981a5..4bb27f2 100644 --- a/test/chainlink/OracleFourFeedsTest.sol +++ b/test/chainlink/OracleFourFeedsTest.sol @@ -16,7 +16,7 @@ AggregatorV3Interface constant wBtcBtcFeed = AggregatorV3Interface(0xfdFD9C85aD2 contract OracleFourFeedsTest is Test { function setUp() public { - vm.selectFork(vm.createFork(vm.envString("ETH_RPC_URL"))); + vm.createSelectFork(vm.envString("ETH_RPC_URL")); } function testOracleWbtcUsdc() public { diff --git a/test/chainlink/OracleTwoFeedsTest.sol b/test/chainlink/OracleTwoFeedsTest.sol index 091f832..6444e6e 100644 --- a/test/chainlink/OracleTwoFeedsTest.sol +++ b/test/chainlink/OracleTwoFeedsTest.sol @@ -32,7 +32,7 @@ contract FakeAggregator { contract OracleTwoFeedsTest is Test { function setUp() public { - vm.selectFork(vm.createFork(vm.envString("ETH_RPC_URL"))); + vm.createSelectFork(vm.envString("ETH_RPC_URL")); } function testOracleStEthUsdc() public { From b1bd24d003c36c0ae9bcfb285b1746cd1dcfaeda Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 4 Oct 2023 11:17:51 +0200 Subject: [PATCH 2/7] chore: fmt --- src/chainlink/OracleFourFeeds.sol | 4 ++-- test/chainlink/OracleFourFeedsTest.sol | 12 ++++++++---- test/chainlink/OracleTwoFeedsTest.sol | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/chainlink/OracleFourFeeds.sol b/src/chainlink/OracleFourFeeds.sol index d7eb3e5..8beb8a8 100644 --- a/src/chainlink/OracleFourFeeds.sol +++ b/src/chainlink/OracleFourFeeds.sol @@ -43,8 +43,8 @@ contract OracleFourFeeds is IOracle { QUOTE_FEED_2 = quoteFeed2; SCALE_FACTOR = 10 ** ( - 36 + quoteTokenDecimals + quoteFeed1.getDecimals() + quoteFeed2.getDecimals() - - baseFeed1.getDecimals() - baseFeed2.getDecimals() - baseTokenDecimals + 36 + quoteTokenDecimals + quoteFeed1.getDecimals() + quoteFeed2.getDecimals() - baseFeed1.getDecimals() + - baseFeed2.getDecimals() - baseTokenDecimals ); } diff --git a/test/chainlink/OracleFourFeedsTest.sol b/test/chainlink/OracleFourFeedsTest.sol index 4bb27f2..a7446ae 100644 --- a/test/chainlink/OracleFourFeedsTest.sol +++ b/test/chainlink/OracleFourFeedsTest.sol @@ -20,24 +20,28 @@ contract OracleFourFeedsTest is Test { } function testOracleWbtcUsdc() public { - OracleFourFeeds oracle = new OracleFourFeeds(wBtcBtcFeed, btcUsdFeed, usdcUsdFeed, AggregatorV3Interface(address(0)), 8, 6); + OracleFourFeeds oracle = + new OracleFourFeeds(wBtcBtcFeed, btcUsdFeed, usdcUsdFeed, AggregatorV3Interface(address(0)), 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) + (uint256(firstBaseAnswer) * uint256(secondBaseAnswer) * 10 ** (36 + 8 + 6 - 8 - 8 - 8)) + / uint256(quoteAnswer) ); } function testOracleUsdcWbtc() public { - OracleFourFeeds oracle = new OracleFourFeeds(usdcUsdFeed, AggregatorV3Interface(address(0)), wBtcBtcFeed, btcUsdFeed, 6, 8); + OracleFourFeeds oracle = + new OracleFourFeeds(usdcUsdFeed, AggregatorV3Interface(address(0)), 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)) + (uint256(baseAnswer) * 10 ** (36 + 8 + 8 + 8 - 6 - 8)) + / (uint256(firstQuoteAnswer) * uint256(secondQuoteAnswer)) ); } diff --git a/test/chainlink/OracleTwoFeedsTest.sol b/test/chainlink/OracleTwoFeedsTest.sol index 6444e6e..93c5b63 100644 --- a/test/chainlink/OracleTwoFeedsTest.sol +++ b/test/chainlink/OracleTwoFeedsTest.sol @@ -67,7 +67,7 @@ contract OracleTwoFeedsTest is Test { assertApproxEqRel(oracle.price(), 1e36 / 1e6, 0.01 ether); } - function testNegativeAnswer(int price) public { + function testNegativeAnswer(int256 price) public { vm.assume(price < 0); FakeAggregator aggregator = new FakeAggregator(); OracleTwoFeeds oracle = From ca38c554383cc8b7307e838327779551c48f0b04 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 4 Oct 2023 11:26:14 +0200 Subject: [PATCH 3/7] chore: submodules --- .gitmodules | 3 --- lib/chainlink | 1 - lib/morpho-blue | 2 +- .../interfaces/AggregatorV3Interface.sol | 22 +++++++++++++++++++ src/chainlink/libraries/DataFeedLib.sol | 2 +- test/chainlink/OracleTwoFeedsTest.sol | 2 +- 6 files changed, 25 insertions(+), 7 deletions(-) delete mode 160000 lib/chainlink create mode 100644 src/chainlink/interfaces/AggregatorV3Interface.sol diff --git a/.gitmodules b/.gitmodules index 3f53fcf..1020b31 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/lib/chainlink b/lib/chainlink deleted file mode 160000 index 6163d14..0000000 --- a/lib/chainlink +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6163d1432ad6a6c64db25c82fca941762ad4a29c diff --git a/lib/morpho-blue b/lib/morpho-blue index 7d60ca0..fa7e34b 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit 7d60ca06e827780115d90508339de5560be9f643 +Subproject commit fa7e34bbc0d80c24143ceb416d831cc6311859ca diff --git a/src/chainlink/interfaces/AggregatorV3Interface.sol b/src/chainlink/interfaces/AggregatorV3Interface.sol new file mode 100644 index 0000000..e12ac3e --- /dev/null +++ b/src/chainlink/interfaces/AggregatorV3Interface.sol @@ -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); +} diff --git a/src/chainlink/libraries/DataFeedLib.sol b/src/chainlink/libraries/DataFeedLib.sol index c3b5086..01344c5 100644 --- a/src/chainlink/libraries/DataFeedLib.sol +++ b/src/chainlink/libraries/DataFeedLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {ErrorsLib} from "./ErrorsLib.sol"; -import {AggregatorV3Interface} from "chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import {AggregatorV3Interface} from "../interfaces/AggregatorV3Interface.sol"; library DataFeedLib { /// @dev Performing some security checks and returns the latest price of a feed. diff --git a/test/chainlink/OracleTwoFeedsTest.sol b/test/chainlink/OracleTwoFeedsTest.sol index 93c5b63..8124e4c 100644 --- a/test/chainlink/OracleTwoFeedsTest.sol +++ b/test/chainlink/OracleTwoFeedsTest.sol @@ -68,7 +68,7 @@ contract OracleTwoFeedsTest is Test { } function testNegativeAnswer(int256 price) public { - vm.assume(price < 0); + price = bound(price, type(int256).min, -1); FakeAggregator aggregator = new FakeAggregator(); OracleTwoFeeds oracle = new OracleTwoFeeds(AggregatorV3Interface(address(aggregator)), AggregatorV3Interface(address(0)), 18, 0); From 3d88fd5884fdb09af3ec1044192b023bbb8c2430 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 4 Oct 2023 14:52:10 +0200 Subject: [PATCH 4/7] docs: minor improvements --- src/chainlink/OracleTwoFeeds.sol | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/chainlink/OracleTwoFeeds.sol b/src/chainlink/OracleTwoFeeds.sol index a08e259..3c0a0a3 100644 --- a/src/chainlink/OracleTwoFeeds.sol +++ b/src/chainlink/OracleTwoFeeds.sol @@ -31,13 +31,14 @@ contract OracleTwoFeeds is IOracle { ) { BASE_FEED = baseFeed; QUOTE_FEED = quoteFeed; - // We note pB the base price, and pQ the quote price (price of 1e(decimals) asset). - // We want to return 1e36 * (pB/1e(bDecimals) / (pQ/1e(qDecimals)). - // Chainlink returns pB * bFeedPrecision and pQ * qFeedPrecision. - // So we have 1e36 * (pB/1e(bDecimals) / (pQ/1e(qDecimals) = pB * 1e(bFeedPrecision) * SCALE_FACTOR / (pQ * - // 1e(qFeedPrecision)) + // Let pB be the base price, and pQ the quote price (price of 1e(decimals) asset). + // Chainlink feeds return pB * bFeedPrecision and pQ * qFeedPrecision. + // `price()` should return 1e36 * (pB/1e(bDecimals) / (pQ/1e(qDecimals)). + // Yet `price()` returns pB * 1e(bFeedPrecision) * SCALE_FACTOR / (pQ * 1e(qFeedPrecision)) + // So 1e36 * (pB/1e(bDecimals) / (pQ/1e(qDecimals) = pB*1e(bFeedPrecision) * SCALE_FACTOR / + // (pQ*1e(qFeedPrecision)) // So SCALE_FACTOR = 1e36 / 1e(bDecimals) * 1e(qDecimals) * 1e(qFeedPrecision) / 1e(bFeedPrecision) - // So SCALE_FACTOR = 1e(36 + qDecimals + qFeedPrecision - bDecimals - bFeedPrecision) + // = 1e(36 + qDecimals + qFeedPrecision - bDecimals - bFeedPrecision) SCALE_FACTOR = 10 ** (36 + quoteTokenDecimals + quoteFeed.getDecimals() - baseFeed.getDecimals() - baseTokenDecimals); } From a6668131f6a7c742fe36e6fd07ecb0b69fb68eae Mon Sep 17 00:00:00 2001 From: MathisGD <74971347+MathisGD@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:55:05 +0200 Subject: [PATCH 5/7] docs: minor improvements Co-authored-by: Romain Milon Signed-off-by: MathisGD <74971347+MathisGD@users.noreply.github.com> --- src/chainlink/libraries/DataFeedLib.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/chainlink/libraries/DataFeedLib.sol b/src/chainlink/libraries/DataFeedLib.sol index 01344c5..54c85a3 100644 --- a/src/chainlink/libraries/DataFeedLib.sol +++ b/src/chainlink/libraries/DataFeedLib.sol @@ -5,7 +5,7 @@ import {ErrorsLib} from "./ErrorsLib.sol"; import {AggregatorV3Interface} from "../interfaces/AggregatorV3Interface.sol"; library DataFeedLib { - /// @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; @@ -14,7 +14,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(); From 2ce15dd31d27120e22849506d2911db302540873 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 4 Oct 2023 15:41:11 +0200 Subject: [PATCH 6/7] refactor: remove oracle 2 feeds --- ...racleFourFeeds.sol => ChainlinkOracle.sol} | 2 +- src/chainlink/OracleTwoFeeds.sol | 52 -------- test/chainlink/ChainlinkOracleTest.sol | 120 ++++++++++++++++++ test/chainlink/OracleFourFeedsTest.sol | 55 -------- test/chainlink/OracleTwoFeedsTest.sol | 79 ------------ 5 files changed, 121 insertions(+), 187 deletions(-) rename src/chainlink/{OracleFourFeeds.sol => ChainlinkOracle.sol} (98%) delete mode 100644 src/chainlink/OracleTwoFeeds.sol create mode 100644 test/chainlink/ChainlinkOracleTest.sol delete mode 100644 test/chainlink/OracleFourFeedsTest.sol delete mode 100644 test/chainlink/OracleTwoFeedsTest.sol diff --git a/src/chainlink/OracleFourFeeds.sol b/src/chainlink/ChainlinkOracle.sol similarity index 98% rename from src/chainlink/OracleFourFeeds.sol rename to src/chainlink/ChainlinkOracle.sol index 8beb8a8..196e2c6 100644 --- a/src/chainlink/OracleFourFeeds.sol +++ b/src/chainlink/ChainlinkOracle.sol @@ -5,7 +5,7 @@ import {IOracle} from "morpho-blue/interfaces/IOracle.sol"; import {AggregatorV3Interface, DataFeedLib} from "./libraries/DataFeedLib.sol"; -contract OracleFourFeeds is IOracle { +contract ChainlinkOracle is IOracle { using DataFeedLib for AggregatorV3Interface; /* CONSTANT */ diff --git a/src/chainlink/OracleTwoFeeds.sol b/src/chainlink/OracleTwoFeeds.sol deleted file mode 100644 index 3c0a0a3..0000000 --- a/src/chainlink/OracleTwoFeeds.sol +++ /dev/null @@ -1,52 +0,0 @@ -// 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 OracleTwoFeeds is IOracle { - using DataFeedLib for AggregatorV3Interface; - - /* CONSTANT */ - - /// @notice Base feed. - AggregatorV3Interface public immutable BASE_FEED; - /// @notice Quote feed. - AggregatorV3Interface public immutable QUOTE_FEED; - /// @notice Price scale factor, computed at contract creation. - uint256 public immutable SCALE_FACTOR; - - /* CONSTRUCTOR */ - - /// @param baseFeed Base feed. Pass address zero if the price = 1. - /// @param quoteFeed Quote feed. Pass address zero if the price = 1. - /// @param baseTokenDecimals Base token decimals. - /// @param quoteTokenDecimals Quote token decimals. - constructor( - AggregatorV3Interface baseFeed, - AggregatorV3Interface quoteFeed, - uint256 baseTokenDecimals, - uint256 quoteTokenDecimals - ) { - BASE_FEED = baseFeed; - QUOTE_FEED = quoteFeed; - // Let pB be the base price, and pQ the quote price (price of 1e(decimals) asset). - // Chainlink feeds return pB * bFeedPrecision and pQ * qFeedPrecision. - // `price()` should return 1e36 * (pB/1e(bDecimals) / (pQ/1e(qDecimals)). - // Yet `price()` returns pB * 1e(bFeedPrecision) * SCALE_FACTOR / (pQ * 1e(qFeedPrecision)) - // So 1e36 * (pB/1e(bDecimals) / (pQ/1e(qDecimals) = pB*1e(bFeedPrecision) * SCALE_FACTOR / - // (pQ*1e(qFeedPrecision)) - // So SCALE_FACTOR = 1e36 / 1e(bDecimals) * 1e(qDecimals) * 1e(qFeedPrecision) / 1e(bFeedPrecision) - // = 1e(36 + qDecimals + qFeedPrecision - bDecimals - bFeedPrecision) - SCALE_FACTOR = - 10 ** (36 + quoteTokenDecimals + quoteFeed.getDecimals() - baseFeed.getDecimals() - baseTokenDecimals); - } - - /* PRICE */ - - /// @inheritdoc IOracle - function price() external view returns (uint256) { - return (BASE_FEED.getPrice() * SCALE_FACTOR) / QUOTE_FEED.getPrice(); - } -} diff --git a/test/chainlink/ChainlinkOracleTest.sol b/test/chainlink/ChainlinkOracleTest.sol new file mode 100644 index 0000000..bcbe74f --- /dev/null +++ b/test/chainlink/ChainlinkOracleTest.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "src/chainlink/ChainlinkOracle.sol"; +import "src/chainlink/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(); + } +} diff --git a/test/chainlink/OracleFourFeedsTest.sol b/test/chainlink/OracleFourFeedsTest.sol deleted file mode 100644 index a7446ae..0000000 --- a/test/chainlink/OracleFourFeedsTest.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import "src/chainlink/OracleFourFeeds.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 OracleFourFeedsTest is Test { - function setUp() public { - vm.createSelectFork(vm.envString("ETH_RPC_URL")); - } - - function testOracleWbtcUsdc() public { - OracleFourFeeds oracle = - new OracleFourFeeds(wBtcBtcFeed, btcUsdFeed, usdcUsdFeed, AggregatorV3Interface(address(0)), 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 { - OracleFourFeeds oracle = - new OracleFourFeeds(usdcUsdFeed, AggregatorV3Interface(address(0)), 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 { - OracleFourFeeds oracle = - new OracleFourFeeds(wBtcBtcFeed, btcEthFeed, AggregatorV3Interface(address(0)), AggregatorV3Interface(address(0)), 8, 18); - (, int256 firstBaseAnswer,,,) = wBtcBtcFeed.latestRoundData(); - (, int256 secondBaseAnswer,,,) = btcEthFeed.latestRoundData(); - assertEq(oracle.price(), (uint256(firstBaseAnswer) * uint256(secondBaseAnswer) * 10 ** (36 + 18 - 8 - 8 - 18))); - } -} diff --git a/test/chainlink/OracleTwoFeedsTest.sol b/test/chainlink/OracleTwoFeedsTest.sol deleted file mode 100644 index 8124e4c..0000000 --- a/test/chainlink/OracleTwoFeedsTest.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import "src/chainlink/OracleTwoFeeds.sol"; -import "src/chainlink/libraries/ErrorsLib.sol"; - -// 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); -// 8 decimals of precision -AggregatorV3Interface constant usdcUsd = AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6); - -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 OracleTwoFeedsTest is Test { - function setUp() public { - vm.createSelectFork(vm.envString("ETH_RPC_URL")); - } - - function testOracleStEthUsdc() public { - OracleTwoFeeds oracle = new OracleTwoFeeds(stEthEthFeed, usdcEthFeed, 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 { - OracleTwoFeeds oracle = new OracleTwoFeeds(ethUsdFeed, AggregatorV3Interface(address(0)), 18, 0); - (, int256 expectedPrice,,,) = ethUsdFeed.latestRoundData(); - assertEq(oracle.price(), uint256(expectedPrice) * 10 ** (36 - 18 - 8)); - } - - function testOracleStEthEth() public { - OracleTwoFeeds oracle = new OracleTwoFeeds(stEthEthFeed, AggregatorV3Interface(address(0)), 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 { - OracleTwoFeeds oracle = new OracleTwoFeeds(AggregatorV3Interface(address(0)), stEthEthFeed, 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 { - OracleTwoFeeds oracle = new OracleTwoFeeds(usdcUsd, AggregatorV3Interface(address(0)), 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(); - OracleTwoFeeds oracle = - new OracleTwoFeeds(AggregatorV3Interface(address(aggregator)), AggregatorV3Interface(address(0)), 18, 0); - aggregator.setAnwser(price); - vm.expectRevert(bytes(ErrorsLib.NEGATIVE_ANSWER)); - oracle.price(); - } -} From 0007653923d96e2d817f79b4a0e0fec87857e486 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Wed, 4 Oct 2023 15:44:39 +0200 Subject: [PATCH 7/7] refactor: repo structure --- src/{chainlink => }/ChainlinkOracle.sol | 4 ++-- .../interfaces/AggregatorV3Interface.sol | 0 .../ChainlinkDataFeedLib.sol} | 2 +- src/{chainlink => }/libraries/ErrorsLib.sol | 0 test/chainlink/ChainlinkOracleTest.sol | 13 +++++-------- 5 files changed, 8 insertions(+), 11 deletions(-) rename src/{chainlink => }/ChainlinkOracle.sol (92%) rename src/{chainlink => }/interfaces/AggregatorV3Interface.sol (100%) rename src/{chainlink/libraries/DataFeedLib.sol => libraries/ChainlinkDataFeedLib.sol} (96%) rename src/{chainlink => }/libraries/ErrorsLib.sol (100%) diff --git a/src/chainlink/ChainlinkOracle.sol b/src/ChainlinkOracle.sol similarity index 92% rename from src/chainlink/ChainlinkOracle.sol rename to src/ChainlinkOracle.sol index 196e2c6..1ae9c81 100644 --- a/src/chainlink/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -3,10 +3,10 @@ pragma solidity 0.8.19; import {IOracle} from "morpho-blue/interfaces/IOracle.sol"; -import {AggregatorV3Interface, DataFeedLib} from "./libraries/DataFeedLib.sol"; +import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol"; contract ChainlinkOracle is IOracle { - using DataFeedLib for AggregatorV3Interface; + using ChainlinkDataFeedLib for AggregatorV3Interface; /* CONSTANT */ diff --git a/src/chainlink/interfaces/AggregatorV3Interface.sol b/src/interfaces/AggregatorV3Interface.sol similarity index 100% rename from src/chainlink/interfaces/AggregatorV3Interface.sol rename to src/interfaces/AggregatorV3Interface.sol diff --git a/src/chainlink/libraries/DataFeedLib.sol b/src/libraries/ChainlinkDataFeedLib.sol similarity index 96% rename from src/chainlink/libraries/DataFeedLib.sol rename to src/libraries/ChainlinkDataFeedLib.sol index 54c85a3..268d702 100644 --- a/src/chainlink/libraries/DataFeedLib.sol +++ b/src/libraries/ChainlinkDataFeedLib.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {ErrorsLib} from "./ErrorsLib.sol"; import {AggregatorV3Interface} from "../interfaces/AggregatorV3Interface.sol"; -library DataFeedLib { +library ChainlinkDataFeedLib { /// @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) { diff --git a/src/chainlink/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol similarity index 100% rename from src/chainlink/libraries/ErrorsLib.sol rename to src/libraries/ErrorsLib.sol diff --git a/test/chainlink/ChainlinkOracleTest.sol b/test/chainlink/ChainlinkOracleTest.sol index bcbe74f..576112c 100644 --- a/test/chainlink/ChainlinkOracleTest.sol +++ b/test/chainlink/ChainlinkOracleTest.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; -import "src/chainlink/ChainlinkOracle.sol"; -import "src/chainlink/libraries/ErrorsLib.sol"; +import "src/ChainlinkOracle.sol"; +import "src/libraries/ErrorsLib.sol"; AggregatorV3Interface constant feedZero = AggregatorV3Interface(address(0)); // 8 decimals of precision @@ -43,8 +43,7 @@ contract ChainlinkOracleTest is Test { } function testOracleWbtcUsdc() public { - ChainlinkOracle oracle = - new ChainlinkOracle(wBtcBtcFeed, btcUsdFeed, usdcUsdFeed, feedZero, 8, 6); + ChainlinkOracle oracle = new ChainlinkOracle(wBtcBtcFeed, btcUsdFeed, usdcUsdFeed, feedZero, 8, 6); (, int256 firstBaseAnswer,,,) = wBtcBtcFeed.latestRoundData(); (, int256 secondBaseAnswer,,,) = btcUsdFeed.latestRoundData(); (, int256 quoteAnswer,,,) = usdcUsdFeed.latestRoundData(); @@ -56,8 +55,7 @@ contract ChainlinkOracleTest is Test { } function testOracleUsdcWbtc() public { - ChainlinkOracle oracle = - new ChainlinkOracle(usdcUsdFeed, feedZero, wBtcBtcFeed, btcUsdFeed, 6, 8); + ChainlinkOracle oracle = new ChainlinkOracle(usdcUsdFeed, feedZero, wBtcBtcFeed, btcUsdFeed, 6, 8); (, int256 baseAnswer,,,) = usdcUsdFeed.latestRoundData(); (, int256 firstQuoteAnswer,,,) = wBtcBtcFeed.latestRoundData(); (, int256 secondQuoteAnswer,,,) = btcUsdFeed.latestRoundData(); @@ -69,8 +67,7 @@ contract ChainlinkOracleTest is Test { } function testOracleWbtcEth() public { - ChainlinkOracle oracle = - new ChainlinkOracle(wBtcBtcFeed, btcEthFeed, feedZero, feedZero, 8, 18); + 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)));