-
Notifications
You must be signed in to change notification settings - Fork 7
ChainlinkBridgeAssetFeed #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| interface IChainlinkFeed { | ||
| function aggregator() external view returns (address aggregator); | ||
| function decimals() external view returns (uint8 decimals); | ||
| function latestRoundData() | ||
| external | ||
| view | ||
| returns ( | ||
| uint80 roundId, | ||
| int256 crvUsdPrice, | ||
| uint256 startedAt, | ||
| uint256 updatedAt, | ||
| uint80 answeredInRound | ||
| ); | ||
| function latestAnswer() external view returns (int256 price); | ||
| } | ||
| interface IAggregator { | ||
| function maxAnswer() external view returns (int192); | ||
| function minAnswer() external view returns (int192); | ||
| } | ||
| interface ICurvePool { | ||
08xmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| function price_oracle(uint k) external view returns (uint256); | ||
| } | ||
|
|
||
| contract ChainlinkBridgeAssetFeed { | ||
| IChainlinkFeed public immutable collateralToBridgeAsset; | ||
| IChainlinkFeed public immutable bridgeAssetToUsd; | ||
| bool public immutable bridgeAssetDenominator; | ||
| string public description; | ||
|
|
||
| /** | ||
| * @notice Oracle for the USD price of a collateral asset derived by combining a collateral-bridgeAsset oracle and bridgeAsset-USD oracle | ||
| * @param _collateralToBridgeAsset Chainlink oracle returning the collateral/bridgeAsset OR bridgeAsset/collateral price | ||
| * @param _bridgeAssetToUsd Chainlink oracle returning the bridgeAsset/USD price | ||
| * @param _bridgeAssetDenominator If true, the `_collateralToBridgeAsset` oracle will return collateral/bridgeAsset, if false, bridgeAsset/collateral. | ||
| * @dev We assume the underlying oracles have already been normalized using our standard chainlink feed. These feeds should also be used for fallback logic. | ||
| */ | ||
| constructor(address _collateralToBridgeAsset, address _bridgeAssetToUsd, bool _bridgeAssetDenominator){ | ||
| collateralToBridgeAsset = IChainlinkFeed(_collateralToBridgeAsset); | ||
| bridgeAssetToUsd = IChainlinkFeed(_bridgeAssetToUsd); | ||
| bridgeAssetDenominator = _bridgeAssetDenominator; | ||
| //TODO: Do string concat | ||
| description = "Combine oracle of 2 underlying oracles"; | ||
| require(collateralToBridgeAsset.decimals() == 18, "collateralToBridgeAsset feed not normalized"); | ||
| require(bridgeAssetToUsd.decimals() == 18, "bridgeAssetToUsd feed not normalize"); | ||
| } | ||
|
|
||
| function decimals() external view returns (uint8) { | ||
| return 18; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Retrieves the latest round data for the collateral token price feed | ||
| * @dev This function calculates the collateral price in USD by combining the bridgeAsset to USD price from a Chainlink oracle and the collateral to bridgeAsset ratio from the bridgeAsset Chainlink oracle | ||
| * @return roundId The round ID of the Chainlink price feed for the feed with the lowest updatedAt feed | ||
| * @return bridgeAssetToUsdPrice The latest collateral price in USD computed from the collateral/bridgeAsset and bridgeAsset/USD feeds | ||
| * @return startedAt The timestamp when the latest round of Chainlink price feed started of the lowest last updatedAt feed | ||
| * @return updatedAt The lowest timestamp when either of the latest round of Chainlink price feed was updated | ||
| * @return answeredInRound The round ID in which the answer was computed of the lowest updatedAt feed | ||
| */ | ||
| function latestRoundData() | ||
| public | ||
| view | ||
| returns (uint80, int256, uint256, uint256, uint80) | ||
| { | ||
| ( | ||
| uint80 collateralToBridgeAssetRoundId, | ||
| int256 collateralToBridgeAssetPrice, | ||
| uint collateralToBridgeAssetStartedAt, | ||
| uint collateralToBridgeAssetUpdatedAt, | ||
| uint80 collateralToBridgeAssetAnsweredInRound | ||
| ) = collateralToBridgeAsset.latestRoundData(); | ||
| ( | ||
| uint80 bridgeAssetToUsdRoundId, | ||
| int256 bridgeAssetToUsdPrice, | ||
| uint bridgeAssetToUsdStartedAt, | ||
| uint bridgeAssetToUsdUpdatedAt, | ||
| uint80 bridgeAssetToUsdAnsweredInRound | ||
| ) = bridgeAssetToUsd.latestRoundData(); | ||
| int price; | ||
| if(bridgeAssetDenominator){ | ||
| price = bridgeAssetToUsdPrice * collateralToBridgeAssetPrice / 10 ** 18; | ||
| } else { | ||
| price = bridgeAssetToUsdPrice * 10 ** 18 / collateralToBridgeAssetPrice; | ||
| } | ||
| if (collateralToBridgeAssetUpdatedAt < bridgeAssetToUsdUpdatedAt) { | ||
| return ( | ||
| collateralToBridgeAssetRoundId, | ||
| price, | ||
| collateralToBridgeAssetStartedAt, | ||
| collateralToBridgeAssetUpdatedAt, | ||
| collateralToBridgeAssetAnsweredInRound | ||
| ); | ||
| } else { | ||
| return ( | ||
| bridgeAssetToUsdRoundId, | ||
| price, | ||
| bridgeAssetToUsdStartedAt, | ||
| bridgeAssetToUsdUpdatedAt, | ||
| bridgeAssetToUsdAnsweredInRound | ||
| ); | ||
| } | ||
| } | ||
| /** | ||
| * @notice Returns the latest price only | ||
| * @dev Unlike chainlink oracles, the latestAnswer will always be the same as in the latestRoundData | ||
| * @return int256 Returns the last finalized price of the chainlink oracle | ||
| */ | ||
| function latestAnswer() external view returns (int256) { | ||
| (, int256 latestPrice, , , ) = latestRoundData(); | ||
| return latestPrice; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.19; | ||
|
|
||
| import "forge-std/Test.sol"; | ||
| import {console} from "forge-std/console.sol"; | ||
| import "src/feeds/ChainlinkBasePriceFeed.sol"; | ||
| import {ChainlinkBridgeAssetFeed} from "src/feeds/ChainlinkBridgeAssetFeed.sol"; | ||
|
|
||
| abstract contract ChainlinkBridgeAssetBase is Test { | ||
| ChainlinkBridgeAssetFeed feed; | ||
| ChainlinkBasePriceFeed collateralToBridgeAssetFeed; // main coin1 feed | ||
| ChainlinkBasePriceFeed bridgeAssetToUsdFeed; // main coin2 feed | ||
|
|
||
| uint256 public constant SCALE = 1e18; | ||
|
|
||
| function init( | ||
| address _collateralToBridgeAssetFeed, | ||
| address _bridgeAssetToUsdFeed, | ||
| bool _bridgeAssetDenominator | ||
| ) public { | ||
| collateralToBridgeAssetFeed = ChainlinkBasePriceFeed(_collateralToBridgeAssetFeed); | ||
| bridgeAssetToUsdFeed = ChainlinkBasePriceFeed(_bridgeAssetToUsdFeed); | ||
| feed = new ChainlinkBridgeAssetFeed(_collateralToBridgeAssetFeed, _bridgeAssetToUsdFeed, _bridgeAssetDenominator); | ||
| console.log("feed :", feed.description()); | ||
| } | ||
|
|
||
| function test_decimals() public { | ||
| assertEq(feed.decimals(), 18); | ||
| } | ||
|
|
||
| function test_latestAnswer() public { | ||
| (, int256 lpUsdPrice, , , ) = feed.latestRoundData(); | ||
|
|
||
| assertEq(feed.latestAnswer(), lpUsdPrice); | ||
| } | ||
|
|
||
| function test_collateralToBridgeAssetIncrease() public { | ||
| (,int256 priceBefore,,,) = feed.latestRoundData(); | ||
|
|
||
| (uint80 roundId, int256 price, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) | ||
| = collateralToBridgeAssetFeed.latestRoundData(); | ||
|
|
||
| _mockLatestRoundData( | ||
| address(collateralToBridgeAssetFeed), | ||
| roundId, | ||
| price * 110 / 100, | ||
| startedAt, | ||
| updatedAt, | ||
| answeredInRound | ||
| ); | ||
|
|
||
| (,int256 priceAfter,,,) = feed.latestRoundData(); | ||
|
|
||
| if(feed.bridgeAssetDenominator()){ | ||
| assertGt(priceAfter, priceBefore); | ||
| } else { | ||
| assertLt(priceAfter, priceBefore); | ||
| } | ||
| } | ||
|
|
||
| function test_collateralToBridgeAssetDecrease() public { | ||
| (,int256 priceBefore,,,) = feed.latestRoundData(); | ||
|
|
||
| (uint80 roundId, int256 price, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) | ||
| = collateralToBridgeAssetFeed.latestRoundData(); | ||
|
|
||
| _mockLatestRoundData( | ||
| address(collateralToBridgeAssetFeed), | ||
| roundId, | ||
| price * 90 / 100, | ||
| startedAt, | ||
| updatedAt, | ||
| answeredInRound | ||
| ); | ||
|
|
||
| (,int256 priceAfter,,,) = feed.latestRoundData(); | ||
|
|
||
| if(feed.bridgeAssetDenominator()){ | ||
| assertLt(priceAfter, priceBefore); | ||
| } else { | ||
| assertGt(priceAfter, priceBefore); | ||
| } | ||
| } | ||
|
|
||
| function test_bridgeAssetToUsdIncrease() public { | ||
| (,int256 priceBefore,,,) = feed.latestRoundData(); | ||
|
|
||
| (uint80 roundId, int256 price, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) | ||
| = collateralToBridgeAssetFeed.latestRoundData(); | ||
|
|
||
| _mockLatestRoundData( | ||
| address(bridgeAssetToUsdFeed), | ||
| roundId, | ||
| price * 110 / 100, | ||
| startedAt, | ||
| updatedAt, | ||
| answeredInRound | ||
| ); | ||
|
|
||
| (,int256 priceAfter,,,) = feed.latestRoundData(); | ||
|
|
||
| assertLt(priceAfter, priceBefore); | ||
| } | ||
|
|
||
| function test_bridgeAssetToUsdDecrease() public { | ||
| (,int256 priceBefore,,,) = feed.latestRoundData(); | ||
|
|
||
| (uint80 roundId, int256 price, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) | ||
| = collateralToBridgeAssetFeed.latestRoundData(); | ||
|
|
||
| _mockLatestRoundData( | ||
| address(bridgeAssetToUsdFeed), | ||
| roundId, | ||
| price * 90 / 100, | ||
| startedAt, | ||
| updatedAt, | ||
| answeredInRound | ||
| ); | ||
|
|
||
| (,int256 priceAfter,,,) = feed.latestRoundData(); | ||
|
|
||
| assertLt(priceAfter, priceBefore); | ||
| } | ||
|
|
||
| function _mockLatestRoundData( | ||
| address target, | ||
| uint80 roundId, | ||
| int256 price, | ||
| uint256 startedAt, | ||
| uint256 updatedAt, | ||
| uint80 answeredInRound | ||
| ) internal { | ||
| vm.mockCall( | ||
| target, | ||
| abi.encodeWithSelector(IChainlinkFeed.latestRoundData.selector), | ||
| abi.encode(roundId, price, startedAt, updatedAt, answeredInRound) | ||
| ); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.