Skip to content

Commit

Permalink
Merge pull request #1 from UMAprotocol/reinis-frp/wip-2
Browse files Browse the repository at this point in the history
Reinis frp/wip 2
  • Loading branch information
Reinis-FRP authored Aug 25, 2023
2 parents 904aa79 + 8444334 commit 99256ea
Show file tree
Hide file tree
Showing 24 changed files with 504 additions and 196 deletions.
119 changes: 35 additions & 84 deletions src/OevOracle.sol
Original file line number Diff line number Diff line change
@@ -1,115 +1,66 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "./interfaces/AggregatorV3Interface.sol";
import "./interfaces/IBaseOracleAdapter.sol";
import "./interfaces/IOevOracle.sol";

import "openzeppelin-contracts/contracts/access/Ownable.sol";
//TODO: potentially make this abstract?
contract OevOracle is IOevOracle {
IBaseOracleAdapter public sourceAdapter;
//TODO: add method to UpdateController to change the sourceAdapter.

contract OevOracle is AggregatorV3Interface, Ownable {
int256 public override latestAnswer;
uint256 public override latestTimestamp;
uint256 public override latestRound;
//TODO: think about what we'd want the ideal internal and public interfaces to be that are exposed over all destination adapters.

struct RoundData {
uint80 roundId;
int256 answer;
uint256 startedAt;
uint256 updatedAt;
uint80 answeredInRound;
}

mapping(uint256 => RoundData) roundData;

AggregatorV3Interface public baseAggregator;

address public updater;
int256 public cachedLatestAnswer; // Always 18 decimals.

uint256 public permissionWindow = 10 minutes;
uint256 public cachedLatestTimestamp;

constructor(AggregatorV3Interface _baseAggregator, address _updater) {
baseAggregator = _baseAggregator;
updater = _updater;

updateAnswer();
constructor(IBaseOracleAdapter _sourceAdapter) {
sourceAdapter = _sourceAdapter;
}

function updateAnswer() public {
(uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
baseAggregator.latestRoundData();

require(latestRound != roundId, "No update needed");
if (msg.sender != updater && msg.sender != owner()) {
require(block.timestamp - updatedAt >= permissionWindow, "In permissioned window");
}

latestRound = roundId;
latestAnswer = answer;
latestTimestamp = updatedAt;
roundData[roundId] = RoundData(roundId, answer, startedAt, updatedAt, answeredInRound);
}
require(_canUpdate(cachedLatestAnswer));
int256 _latestAnswer = sourceAdapter.latestAnswer();
uint256 _latestTimestamp = sourceAdapter.latestTimestamp();

function canUpdateAnswer() public view returns (bool) {
(uint256 roundId,,,,) = baseAggregator.latestRoundData();
return latestRound != roundId;
}
require(_latestAnswer != cachedLatestAnswer || _latestTimestamp != cachedLatestTimestamp);

function setUpdater(address _updater) public onlyOwner {
updater = _updater;
}
require(_validateUpdate(_latestAnswer, _latestTimestamp));

function setPermissionWindow(uint256 _permissionWindow) public onlyOwner {
permissionWindow = _permissionWindow;
cachedLatestAnswer = _latestAnswer;
cachedLatestTimestamp = _latestTimestamp;
}

function getRoundData(uint80 _roundId)
external
view
override
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (
roundData[_roundId].roundId,
roundData[_roundId].answer,
roundData[_roundId].startedAt,
roundData[_roundId].updatedAt,
roundData[_roundId].answeredInRound
);
function rawLatestAnswer() public view override returns (int256) {
if (_canReturnCachedValue(cachedLatestTimestamp)) return cachedLatestAnswer;
return sourceAdapter.latestAnswer();
}

function latestRoundData()
external
view
override
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (
roundData[latestRound].roundId,
roundData[latestRound].answer,
roundData[latestRound].startedAt,
roundData[latestRound].updatedAt,
roundData[latestRound].answeredInRound
);
function rawLatestTimestamp() public view override returns (uint256) {
if (_canReturnCachedValue(cachedLatestTimestamp)) return cachedLatestTimestamp;
return sourceAdapter.latestTimestamp();
}

function getAnswer(uint256 roundId) public view override returns (int256) {
return roundData[roundId].answer;
function _canUpdate(int256 _latestAnswer) internal view returns (bool) {
return true;
}

function getTimestamp(uint256 roundId) public view override returns (uint256) {
return roundData[roundId].updatedAt;
function setSourceOracleAdapter(IBaseOracleAdapter _sourceAdapter) external {
require(_canSetSourceOracleAdapter());
sourceAdapter = _sourceAdapter;
}

function decimals() public view override returns (uint8) {
return baseAggregator.decimals();
function _validateUpdate(int256 _latestAnswer, uint256 _latestTimestamp) internal view returns (bool) {
return true;
}

function description() public view override returns (string memory) {
return baseAggregator.description();
function _canReturnCachedValue(uint256 _cachedValue) internal view returns (bool) {
return false;
}

function version() public view override returns (uint256) {
return baseAggregator.version();
function _canSetSourceOracleAdapter() internal view returns (bool) {
return false;
}

// TODO: we have no notion of phase or phase ID at this point. Should be pulled from Aggregator.
}
15 changes: 15 additions & 0 deletions src/adapters/BaseDestinationOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pragma solidity 0.8.17;

import "../controllers/BaseUpdateController.sol";
import "../interfaces/IBaseOracleAdapter.sol";
import "../OevOracle.sol";

contract BaseDestinationOracleAdapter is BaseUpdateController, OevOracle, IBaseOracleAdapter {

constructor(IBaseOracleAdapter _sourceAdapter) OevOracle(_sourceAdapter) {
}

function decimals() public view override returns (uint8) {
return 18;
}
}
36 changes: 36 additions & 0 deletions src/adapters/MeanSourceOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "../interfaces/IBaseOracleAdapter.sol";

contract MeanSourceOracleAdapter is IBaseOracleAdapter {
IBaseOracleAdapter[] public sources;

uint256 public immutable numberOfSources;

constructor(IBaseOracleAdapter[] memory _sources) {
sources = _sources;
numberOfSources = sources.length;
}

function latestAnswer() public view override returns (int256) {
int256 sum = 0;
for (uint256 i = 0; i < numberOfSources; i++) {
sum += sources[i].latestAnswer();
}
return sum / int256(numberOfSources);
}

function latestTimestamp() public view override returns (uint256) {
uint256 max = 0;
for (uint256 i = 0; i < numberOfSources; i++) {
uint256 timestamp = sources[i].latestTimestamp();
if (timestamp > max) max = timestamp;
}
return max;
}

function decimals() public view override returns (uint8) {
return 18;
}
}
45 changes: 45 additions & 0 deletions src/adapters/chainlink/ChainlinkDestinationOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "../../controllers/BaseUpdateController.sol";
import "../../interfaces/IBaseOracleAdapter.sol";
import "../../interfaces/chainlink/IAggregatorV3.sol";
import "../../OevOracle.sol";

contract ChainlinkDestinationOracleAdapter is
BaseUpdateController,
OevOracle,
IBaseOracleAdapter,
IAggregatorV3
{
uint8 public decimals;

constructor(uint8 _decimals, IBaseOracleAdapter _sourceAdapter)
OevOracle(_sourceAdapter)
{
decimals = _decimals;
}

function latestAnswer() public view override returns (int256) {
// TODO: if we never have more than 18 decimals on the output oracle then we can subtract decimals and divide.
return (rawLatestAnswer() * int256(10**uint256(decimals))) / (10**18);
}

function latestTimestamp() public view override returns (uint256) {
return rawLatestTimestamp();
}

function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (0, latestAnswer(), 0, 0, 0);
}
}
25 changes: 25 additions & 0 deletions src/adapters/chainlink/ChainlinkSourceOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "../../interfaces/IBaseOracleAdapter.sol";
import "../../interfaces/chainlink/IAggregatorV3.sol";

contract ChainlinkSourceOracleAdapter is IBaseOracleAdapter {
IAggregatorV3 public source;

constructor(address _source) {
source = IAggregatorV3(_source);
}

function latestAnswer() public view override returns (int256) {
return (source.latestAnswer() * 10 ** 18) / int256(10 ** decimals());
}

function latestTimestamp() public view override returns (uint256) {
return source.latestTimestamp();
}

function decimals() public view override returns (uint8) {
return source.decimals();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
pragma solidity 0.8.17;

import "openzeppelin-contracts/contracts/access/Ownable.sol";

import "../../interfaces/compound/IUniswapAnchoredView.sol";
import "../../interfaces/IOevOracle.sol";
import "../../interfaces/compound/ICToken.sol";

contract UniswapAnchoredViewDestinationOracleAdapter is Ownable, IUniswapAnchoredView {
mapping(address => address) public cTokenToOevOracles;
mapping(address => uint8) public cTokenToDecimals;

constructor () Ownable() {
}

function setOevOracle(address cToken, address oevOracle) public onlyOwner {
cTokenToOevOracles[cToken] = oevOracle;
cTokenToDecimals[cToken] = ICToken(cToken).decimals();
}

function getUnderlyingPrice(address cToken) external view returns (uint256) {
uint256 rawPrice = IOevOracle(cTokenToOevOracles[cToken]).rawLatestAnswer();
return rawPrice * (10 ** (36 - cTokenToDecimals[cToken])) / (10 ** 18);
}
}
39 changes: 39 additions & 0 deletions src/adapters/compound/UniswapAnchoredViewSourceOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "../../interfaces/IBaseOracleAdapter.sol";
import "../../interfaces/chainlink/IAggregatorV3.sol";
import "../../interfaces/compound/IUniswapAnchoredView.sol";
import "../../interfaces/compound/ICToken.sol";
import "../../interfaces/compound/IValidatorProxy.sol";

contract UniswapAnchoredViewSourceOracleAdapter is IBaseOracleAdapter, IUniswapAnchoredView {
IUniswapAnchoredView public source;

ICToken public cToken;

uint8 public decimals;

IAggregatorV3 public aggregator;

constructor(IUniswapAnchoredView _source, address _cToken) {
source = _source;
cToken = ICToken(_cToken);
decimals = cToken.decimals();

TokenConfig memory tokenConfig = source.getTokenConfigByCToken(address(cToken));
// TODO: make sure this does not change over time
(address current,,) = IValidatorProxy(tokenConfig.reporter).getAggregators();
aggregator = IAggregatorV3(current);
}

function latestAnswer() public view override returns (int256) {
// Price is scaled with (36 - decimals)
uint256 sourcePrice = source.getUnderlyingPrice(address(cToken));
return int256(sourcePrice * (10 ** 18) / (10 ** (36 - decimals))); // TODO: merge power math to avoid overflow
}

function latestTimestamp() public view override returns (uint256) {
return aggregator.latestTimestamp();
}
}
34 changes: 34 additions & 0 deletions src/adapters/makerdao/ChronicleDestinationOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "../../controllers/BaseUpdateController.sol";
import "../../interfaces/IBaseOracleAdapter.sol";
import "../../interfaces/chronicle/IMedian.sol";
import "../../OevOracle.sol";

contract ChronicleDestinationOracleAdapter is BaseUpdateController, OevOracle, IMedian {
uint8 public decimals;

constructor(uint8 _decimals, IBaseOracleAdapter _sourceAdapter) OevOracle(_sourceAdapter) {
decimals = _decimals;
}

function read() public view override returns (uint256) {
// TODO: if we never have more than 18 decimals on the output oracle then we can subtract decimals and divide.
return (uint256(rawLatestAnswer()) * (10 ** decimals)) / (10 ** 18);
}

// TODO: might need to add toll functionality to replicate consumer authorization as in MakerDao.
function peek() public view override returns (uint256, bool) {
uint256 val = read();
return (val, val > 0);
}

function age() public view override returns (uint32) {
return uint32(rawLatestTimestamp());
}

function bar() public view override returns (uint256) {
return uint256(decimals);
}
}
Loading

0 comments on commit 99256ea

Please sign in to comment.