-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Pablo Maldonado <[email protected]>
- Loading branch information
Showing
5 changed files
with
148 additions
and
11 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
121 changes: 121 additions & 0 deletions
121
src/adapters/source-adapters/ChainlinkSourceAdapterTest.sol
This file contains 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,121 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity 0.8.17; | ||
|
||
import {DecimalLib} from "../lib/DecimalLib.sol"; | ||
import {IAggregatorV3Source} from "../../interfaces/chainlink/IAggregatorV3Source.sol"; | ||
import {DiamondRootOval} from "../../DiamondRootOval.sol"; | ||
|
||
/** | ||
* @title ChainlinkSourceAdapter contract to read data from Chainlink aggregator and standardize it for Oval. | ||
* @dev Can fetch information from Chainlink source at a desired timestamp for historic lookups. | ||
*/ | ||
|
||
abstract contract ChainlinkSourceAdapterT is DiamondRootOval { | ||
IAggregatorV3Source public immutable CHAINLINK_SOURCE; | ||
uint8 private immutable SOURCE_DECIMALS; | ||
|
||
// As per Chainlink documentation https://docs.chain.link/data-feeds/historical-data#roundid-in-proxy | ||
// roundId on the aggregator proxy is comprised of phaseId (higher 16 bits) and roundId from phase aggregator | ||
// (lower 64 bits). PHASE_MASK is used to calculate first roundId of current phase aggregator. | ||
uint80 private constant PHASE_MASK = uint80(0xFFFF) << 64; | ||
|
||
event SourceSet(address indexed sourceOracle, uint8 indexed sourceDecimals); | ||
|
||
constructor(IAggregatorV3Source source) { | ||
CHAINLINK_SOURCE = source; | ||
SOURCE_DECIMALS = source.DECIMALS(); | ||
|
||
emit SourceSet(address(source), SOURCE_DECIMALS); | ||
} | ||
|
||
/** | ||
* @notice Tries getting latest data as of requested timestamp. If this is not possible, returns the earliest data | ||
* available past the requested timestamp within provided traversal limitations. | ||
* @param timestamp The timestamp to try getting latest data at. | ||
* @param maxTraversal The maximum number of rounds to traverse when looking for historical data. | ||
* @return answer The answer as of requested timestamp, or earliest available data if not available, in 18 decimals. | ||
* @return updatedAt The timestamp of the answer. | ||
*/ | ||
function tryLatestDataAt( | ||
uint256 timestamp, | ||
uint256 maxTraversal | ||
) public view virtual override returns (int256, uint256) { | ||
(int256 answer, uint256 updatedAt) = _tryLatestRoundDataAt( | ||
timestamp, | ||
maxTraversal | ||
); | ||
return ( | ||
DecimalLib.convertDecimals(answer, SOURCE_DECIMALS, 18), | ||
updatedAt | ||
); | ||
} | ||
|
||
/** | ||
* @notice Initiate a snapshot of the source data. This is a no-op for Chainlink. | ||
*/ | ||
function snapshotData() public virtual override {} | ||
|
||
/** | ||
* @notice Returns the latest data from the source. | ||
* @return answer The latest answer in 18 decimals. | ||
* @return updatedAt The timestamp of the answer. | ||
*/ | ||
function getLatestSourceData() | ||
public | ||
view | ||
virtual | ||
override | ||
returns (int256, uint256) | ||
{ | ||
(, int256 sourceAnswer, , uint256 updatedAt, ) = CHAINLINK_SOURCE | ||
.latestRoundData(); | ||
return ( | ||
DecimalLib.convertDecimals(sourceAnswer, SOURCE_DECIMALS, 18), | ||
updatedAt | ||
); | ||
} | ||
|
||
// Tries getting latest data as of requested timestamp. If this is not possible, returns the earliest data available | ||
// past the requested timestamp considering the maxTraversal limitations. | ||
function _tryLatestRoundDataAt( | ||
uint256 timestamp, | ||
uint256 maxTraversal | ||
) internal view returns (int256, uint256) { | ||
return (CHAINLINK_SOURCE.latestAnswer(), 0); | ||
} | ||
|
||
// Tries finding latest historical data (ignoring current roundId) not newer than requested timestamp. Might return | ||
// newer data than requested if exceeds traversal or hold uninitialized data that should be handled by the caller. | ||
function _searchRoundDataAt( | ||
uint256 timestamp, | ||
uint80 targetRoundId, | ||
uint256 maxTraversal | ||
) internal view returns (int256, uint256) { | ||
uint80 roundId; | ||
int256 answer; | ||
uint256 updatedAt; | ||
uint80 traversedRounds = 0; | ||
uint80 startRoundId = (targetRoundId & PHASE_MASK) + 1; // Phase aggregators are starting at round 1. | ||
|
||
while ( | ||
traversedRounds < uint80(maxTraversal) && | ||
targetRoundId > startRoundId | ||
) { | ||
targetRoundId--; // We started from latest roundId that should be ignored. | ||
// The aggregator proxy does not keep track when its phase aggregators got switched. This means that we can | ||
// only traverse rounds of the current phase aggregator. When phase aggregators are switched there is | ||
// normally an overlap period when both new and old phase aggregators receive updates. Without knowing exact | ||
// time when the aggregator proxy switched them we might end up returning historical data from the new phase | ||
// aggregator that was not yet available on the aggregator proxy at the requested timestamp. | ||
|
||
(roundId, answer, , updatedAt, ) = CHAINLINK_SOURCE.getRoundData( | ||
targetRoundId | ||
); | ||
if (!(roundId == targetRoundId && updatedAt > 0)) return (0, 0); | ||
if (updatedAt <= timestamp) return (answer, updatedAt); | ||
traversedRounds++; | ||
} | ||
|
||
return (answer, updatedAt); // Did not find requested round. Return earliest round or uninitialized data. | ||
} | ||
} |
This file contains 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
This file contains 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