Skip to content

Commit

Permalink
feat: implement requested data by round id
Browse files Browse the repository at this point in the history
Signed-off-by: Reinis Martinsons <[email protected]>
  • Loading branch information
Reinis-FRP committed May 10, 2024
1 parent c93c358 commit 3518ea2
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 5 deletions.
19 changes: 19 additions & 0 deletions src/DiamondRootOval.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ abstract contract DiamondRootOval is IBaseController, IOval, IBaseOracleAdapter
*/
function getLatestSourceData() public view virtual returns (int256, uint256);

/**
* @notice Returns the requested round data from the source.
* @dev If the source does not support rounds this would return uninitialized data.
* @param roundId The roundId to retrieve the round data for.
* @return answer Round answer in 18 decimals.
* @return updatedAt The timestamp of the answer.
*/
function getSourceDataAtRound(uint256 roundId) public view virtual returns (int256, uint256);

/**
* @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.
Expand All @@ -42,6 +51,16 @@ abstract contract DiamondRootOval is IBaseController, IOval, IBaseOracleAdapter
*/
function internalLatestData() public view virtual returns (int256, uint256, uint256);

/**
* @notice Returns the requested round data from the source. Depending on when Oval was last unlocked this might
* also return uninitialized value to protect the OEV from being stolen by a front runner.
* @dev If the source does not support rounds this would always return uninitialized data.
* @param roundId The roundId to retrieve the round data for.
* @return answer Round answer in 18 decimals.
* @return updatedAt The timestamp of the answer.
*/
function internalDataAtRound(uint256 roundId) public view virtual returns (int256, uint256);

/**
* @notice Snapshot the current source data. Is a no-op if the source does not require snapshotting.
*/
Expand Down
19 changes: 19 additions & 0 deletions src/Oval.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,23 @@ abstract contract Oval is DiamondRootOval {
//-> If unlockLatestValue has not been called in lockWindow, then return most recent value that is at least lockWindow old.
return tryLatestDataAt(Math.max(lastUnlockTime, block.timestamp - lockWindow()), maxTraversal());
}

/**
* @notice Returns the requested round data from the source. Depending on when Oval was last unlocked this might
* also return uninitialized values to protect the OEV from being stolen by a front runner.
* @dev If the source does not support rounds this would always return uninitialized data.
* @param roundId The roundId to retrieve the round data for.
* @return answer Round answer in 18 decimals.
* @return updatedAt The timestamp of the answer.
*/
function internalDataAtRound(uint256 roundId) public view override returns (int256, uint256) {
(int256 answer, uint256 timestamp) = getSourceDataAtRound(roundId);

// Return source data for the requested round only if it has been either explicitly or implicitly unlocked:
//-> explicit unlock when source time is not newer than the time when last unlockLatestValue has been called, or
//-> implicit unlock when source data is at least lockWindow old.
uint256 latestUnlockedTimestamp = Math.max(lastUnlockTime, block.timestamp - lockWindow());
if (timestamp <= latestUnlockedTimestamp) return (answer, timestamp);
return (0, 0); // Source data is too recent, return uninitialized values.
}
}
15 changes: 15 additions & 0 deletions src/adapters/destination-adapters/ChainlinkDestinationAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,19 @@ abstract contract ChainlinkDestinationAdapter is DiamondRootOval, IAggregatorV3
uint80 roundId = SafeCast.toUint80(_roundId);
return (roundId, DecimalLib.convertDecimals(answer, 18, decimals), updatedAt, updatedAt, roundId);
}

/**
* @notice Returns the requested round data if available or uninitialized values then it is too recent.
* @dev If the source does not support round data, always returns uninitialized answer and timestamp values.
* @param _roundId The roundId to retrieve the round data for.
* @return roundId The roundId of the latest answer (same as requested roundId).
* @return answer The latest answer in the configured number of decimals.
* @return startedAt The timestamp when the value was updated.
* @return updatedAt The timestamp when the value was updated.
* @return answeredInRound The roundId of the round in which the answer was computed (same as requested roundId).
*/
function getRoundData(uint80 _roundId) external view returns (uint80, int256, uint256, uint256, uint80) {
(int256 answer, uint256 updatedAt) = internalDataAtRound(_roundId);
return (_roundId, DecimalLib.convertDecimals(answer, 18, decimals), updatedAt, updatedAt, _roundId);
}
}
14 changes: 14 additions & 0 deletions src/adapters/source-adapters/ChainlinkSourceAdapter.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.17;

import {SafeCast} from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol";

import {DecimalLib} from "../lib/DecimalLib.sol";
import {IAggregatorV3Source} from "../../interfaces/chainlink/IAggregatorV3Source.sol";
import {DiamondRootOval} from "../../DiamondRootOval.sol";
Expand Down Expand Up @@ -62,6 +64,18 @@ abstract contract ChainlinkSourceAdapter is DiamondRootOval {
return (DecimalLib.convertDecimals(sourceAnswer, SOURCE_DECIMALS, 18), updatedAt);
}

/**
* @notice Returns the requested round data from the source.
* @dev If the source does not have the requested round it would return uninitialized data.
* @param roundId The roundId to retrieve the round data for.
* @return answer Round answer in 18 decimals.
* @return updatedAt The timestamp of the answer.
*/
function getSourceDataAtRound(uint256 roundId) public view virtual override returns (int256, uint256) {
(, int256 sourceAnswer,, uint256 updatedAt,) = CHAINLINK_SOURCE.getRoundData(SafeCast.toUint80(roundId));
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)
Expand Down
10 changes: 10 additions & 0 deletions src/adapters/source-adapters/UniswapAnchoredViewSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ abstract contract UniswapAnchoredViewSourceAdapter is SnapshotSource {
return (DecimalLib.convertDecimals(sourcePrice, SOURCE_DECIMALS, 18), latestTimestamp);
}

/**
* @notice Returns the requested round data from the source.
* @dev UniswapAnchoredView does not support this and returns uninitialized data.
* @return answer Round answer in 18 decimals.
* @return updatedAt The timestamp of the answer.
*/
function getSourceDataAtRound(uint256 /* roundId */ ) public view virtual override returns (int256, uint256) {
return (0, 0);
}

/**
* @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.
Expand Down
10 changes: 5 additions & 5 deletions src/interfaces/chainlink/IAggregatorV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ interface IAggregatorV3 {
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

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

// Other Chainlink functions we don't need.

// function latestRound() external view returns (uint256);
Expand All @@ -25,11 +30,6 @@ interface IAggregatorV3 {

// function version() external view returns (uint256);

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

// event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);

// event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
Expand Down
2 changes: 2 additions & 0 deletions test/fork/adapters/ChainlinkSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ contract TestedSourceAdapter is ChainlinkSourceAdapter {

function internalLatestData() public view override returns (int256, uint256, uint256) {}

function internalDataAtRound(uint256 roundId) public view override returns (int256, uint256) {}

function canUnlock(address caller, uint256 cachedLatestTimestamp) public view virtual override returns (bool) {}

function lockWindow() public view virtual override returns (uint256) {}
Expand Down
1 change: 1 addition & 0 deletions test/fork/adapters/UniswapAnchoredViewSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {IUniswapAnchoredView} from "../../../src/interfaces/compound/IUniswapAnc
contract TestedSourceAdapter is UniswapAnchoredViewSourceAdapter {
constructor(IUniswapAnchoredView source, address cToken) UniswapAnchoredViewSourceAdapter(source, cToken) {}
function internalLatestData() public view override returns (int256, uint256, uint256) {}
function internalDataAtRound(uint256 roundId) public view override returns (int256, uint256) {}
function canUnlock(address caller, uint256 cachedLatestTimestamp) public view virtual override returns (bool) {}
function lockWindow() public view virtual override returns (uint256) {}
function maxTraversal() public view virtual override returns (uint256) {}
Expand Down
4 changes: 4 additions & 0 deletions test/mocks/MockSnapshotSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ abstract contract MockSnapshotSourceAdapter is SnapshotSource {
return (latestData.answer, latestData.timestamp);
}

function getSourceDataAtRound(uint256 /* roundId */ ) public view virtual override returns (int256, uint256) {
return (0, 0);
}

function tryLatestDataAt(uint256 timestamp, uint256 maxTraversal)
public
view
Expand Down
6 changes: 6 additions & 0 deletions test/mocks/MockSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ abstract contract MockSourceAdapter is DiamondRootOval {
return (latestData.answer, latestData.timestamp);
}

function getSourceDataAtRound(uint256 roundId) public view virtual override returns (int256, uint256) {
if (roundId == 0 || rounds.length <= roundId) return (0, 0);
RoundData memory roundData = rounds[roundId - 1];
return (roundData.answer, roundData.timestamp);
}

function _latestRoundData() internal view returns (RoundData memory) {
if (rounds.length > 0) return rounds[rounds.length - 1];
return RoundData(0, 0, 0);
Expand Down

0 comments on commit 3518ea2

Please sign in to comment.