-
Notifications
You must be signed in to change notification settings - Fork 158
Market oracle #228
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
Market oracle #228
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
23d538d
adds Select lib + mock contracts with necessary packages
nms-7 e22955b
migrates Select tests for hardhat
nms-7 b3f002b
copies MedianOracle contract from market-oracle repo
nms-7 d3ee2b6
migrates MedianOracle testing to hardhat (adding a caller contract fo…
nms-7 0256189
migrates MedianOracle_gas_cost test to hardhat/ts from market-oracle …
nms-7 a2e5798
specifies imports in MedianOracle and Select contracts
nms-7 bcaf60b
removes explicit gas test for MedianOracle
nms-7 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
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,223 @@ | ||
| pragma solidity 0.4.24; | ||
|
|
||
| import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; | ||
| import {Ownable} from "openzeppelin-solidity/contracts/ownership/Ownable.sol"; | ||
|
|
||
| import "./lib/Select.sol"; | ||
|
|
||
| interface IOracle { | ||
| function getData() external returns (uint256, bool); | ||
| } | ||
|
|
||
| /** | ||
| * @title Median Oracle | ||
| * | ||
| * @notice Provides a value onchain that's aggregated from a whitelisted set of | ||
| * providers. | ||
| */ | ||
| contract MedianOracle is Ownable, IOracle { | ||
| using SafeMath for uint256; | ||
|
|
||
| struct Report { | ||
| uint256 timestamp; | ||
| uint256 payload; | ||
| } | ||
|
|
||
| // Addresses of providers authorized to push reports. | ||
| address[] public providers; | ||
|
|
||
| // Reports indexed by provider address. Report[0].timestamp > 0 | ||
| // indicates provider existence. | ||
| mapping(address => Report[2]) public providerReports; | ||
|
|
||
| event ProviderAdded(address provider); | ||
| event ProviderRemoved(address provider); | ||
| event ReportTimestampOutOfRange(address provider); | ||
| event ProviderReportPushed(address indexed provider, uint256 payload, uint256 timestamp); | ||
|
|
||
| // The number of seconds after which the report is deemed expired. | ||
| uint256 public reportExpirationTimeSec; | ||
|
|
||
| // The number of seconds since reporting that has to pass before a report | ||
| // is usable. | ||
| uint256 public reportDelaySec; | ||
|
|
||
| // The minimum number of providers with valid reports to consider the | ||
| // aggregate report valid. | ||
| uint256 public minimumProviders = 1; | ||
|
|
||
| // Timestamp of 1 is used to mark uninitialized and invalidated data. | ||
| // This is needed so that timestamp of 1 is always considered expired. | ||
| uint256 private constant MAX_REPORT_EXPIRATION_TIME = 520 weeks; | ||
|
|
||
| /** | ||
| * @param reportExpirationTimeSec_ The number of seconds after which the | ||
| * report is deemed expired. | ||
| * @param reportDelaySec_ The number of seconds since reporting that has to | ||
| * pass before a report is usable | ||
| * @param minimumProviders_ The minimum number of providers with valid | ||
| * reports to consider the aggregate report valid. | ||
| */ | ||
| constructor( | ||
| uint256 reportExpirationTimeSec_, | ||
| uint256 reportDelaySec_, | ||
| uint256 minimumProviders_ | ||
| ) public { | ||
| require(reportExpirationTimeSec_ <= MAX_REPORT_EXPIRATION_TIME); | ||
| require(minimumProviders_ > 0); | ||
| reportExpirationTimeSec = reportExpirationTimeSec_; | ||
| reportDelaySec = reportDelaySec_; | ||
| minimumProviders = minimumProviders_; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Sets the report expiration period. | ||
| * @param reportExpirationTimeSec_ The number of seconds after which the | ||
| * report is deemed expired. | ||
| */ | ||
| function setReportExpirationTimeSec(uint256 reportExpirationTimeSec_) external onlyOwner { | ||
| require(reportExpirationTimeSec_ <= MAX_REPORT_EXPIRATION_TIME); | ||
| reportExpirationTimeSec = reportExpirationTimeSec_; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Sets the time period since reporting that has to pass before a | ||
| * report is usable. | ||
| * @param reportDelaySec_ The new delay period in seconds. | ||
| */ | ||
| function setReportDelaySec(uint256 reportDelaySec_) external onlyOwner { | ||
| reportDelaySec = reportDelaySec_; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Sets the minimum number of providers with valid reports to | ||
| * consider the aggregate report valid. | ||
| * @param minimumProviders_ The new minimum number of providers. | ||
| */ | ||
| function setMinimumProviders(uint256 minimumProviders_) external onlyOwner { | ||
| require(minimumProviders_ > 0); | ||
| minimumProviders = minimumProviders_; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Pushes a report for the calling provider. | ||
| * @param payload is expected to be 18 decimal fixed point number. | ||
| */ | ||
| function pushReport(uint256 payload) external { | ||
| address providerAddress = msg.sender; | ||
| Report[2] storage reports = providerReports[providerAddress]; | ||
| uint256[2] memory timestamps = [reports[0].timestamp, reports[1].timestamp]; | ||
|
|
||
| require(timestamps[0] > 0); | ||
|
|
||
| uint8 index_recent = timestamps[0] >= timestamps[1] ? 0 : 1; | ||
| uint8 index_past = 1 - index_recent; | ||
|
|
||
| // Check that the push is not too soon after the last one. | ||
| require(timestamps[index_recent].add(reportDelaySec) <= now); | ||
|
|
||
| reports[index_past].timestamp = now; | ||
| reports[index_past].payload = payload; | ||
|
|
||
| emit ProviderReportPushed(providerAddress, payload, now); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Invalidates the reports of the calling provider. | ||
| */ | ||
| function purgeReports() external { | ||
| address providerAddress = msg.sender; | ||
| require(providerReports[providerAddress][0].timestamp > 0); | ||
| providerReports[providerAddress][0].timestamp = 1; | ||
| providerReports[providerAddress][1].timestamp = 1; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Computes median of provider reports whose timestamps are in the | ||
| * valid timestamp range. | ||
| * @return AggregatedValue: Median of providers reported values. | ||
| * valid: Boolean indicating an aggregated value was computed successfully. | ||
| */ | ||
| function getData() external returns (uint256, bool) { | ||
| uint256 reportsCount = providers.length; | ||
| uint256[] memory validReports = new uint256[](reportsCount); | ||
| uint256 size = 0; | ||
| uint256 minValidTimestamp = now.sub(reportExpirationTimeSec); | ||
| uint256 maxValidTimestamp = now.sub(reportDelaySec); | ||
|
|
||
| for (uint256 i = 0; i < reportsCount; i++) { | ||
| address providerAddress = providers[i]; | ||
| Report[2] memory reports = providerReports[providerAddress]; | ||
|
|
||
| uint8 index_recent = reports[0].timestamp >= reports[1].timestamp ? 0 : 1; | ||
| uint8 index_past = 1 - index_recent; | ||
| uint256 reportTimestampRecent = reports[index_recent].timestamp; | ||
| if (reportTimestampRecent > maxValidTimestamp) { | ||
| // Recent report is too recent. | ||
| uint256 reportTimestampPast = providerReports[providerAddress][index_past] | ||
| .timestamp; | ||
| if (reportTimestampPast < minValidTimestamp) { | ||
| // Past report is too old. | ||
| emit ReportTimestampOutOfRange(providerAddress); | ||
| } else if (reportTimestampPast > maxValidTimestamp) { | ||
| // Past report is too recent. | ||
| emit ReportTimestampOutOfRange(providerAddress); | ||
| } else { | ||
| // Using past report. | ||
| validReports[size++] = providerReports[providerAddress][index_past].payload; | ||
| } | ||
| } else { | ||
| // Recent report is not too recent. | ||
| if (reportTimestampRecent < minValidTimestamp) { | ||
| // Recent report is too old. | ||
| emit ReportTimestampOutOfRange(providerAddress); | ||
| } else { | ||
| // Using recent report. | ||
| validReports[size++] = providerReports[providerAddress][index_recent].payload; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (size < minimumProviders) { | ||
| return (0, false); | ||
| } | ||
|
|
||
| return (Select.computeMedian(validReports, size), true); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Authorizes a provider. | ||
| * @param provider Address of the provider. | ||
| */ | ||
| function addProvider(address provider) external onlyOwner { | ||
| require(providerReports[provider][0].timestamp == 0); | ||
| providers.push(provider); | ||
| providerReports[provider][0].timestamp = 1; | ||
| emit ProviderAdded(provider); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Revokes provider authorization. | ||
| * @param provider Address of the provider. | ||
| */ | ||
| function removeProvider(address provider) external onlyOwner { | ||
| delete providerReports[provider]; | ||
| for (uint256 i = 0; i < providers.length; i++) { | ||
| if (providers[i] == provider) { | ||
| if (i + 1 != providers.length) { | ||
| providers[i] = providers[providers.length - 1]; | ||
| } | ||
| providers.length--; | ||
| emit ProviderRemoved(provider); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @return The number of authorized providers. | ||
| */ | ||
| function providersSize() external view returns (uint256) { | ||
| return providers.length; | ||
| } | ||
| } |
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,32 @@ | ||
| pragma solidity ^0.4.24; | ||
| import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; | ||
|
|
||
| /** | ||
| * @title Select | ||
| * @dev Median Selection Library | ||
| */ | ||
| library Select { | ||
| using SafeMath for uint256; | ||
|
|
||
| /** | ||
| * @dev Sorts the input array up to the denoted size, and returns the median. | ||
| * @param array Input array to compute its median. | ||
| * @param size Number of elements in array to compute the median for. | ||
| * @return Median of array. | ||
| */ | ||
| function computeMedian(uint256[] array, uint256 size) internal pure returns (uint256) { | ||
| require(size > 0 && array.length >= size); | ||
| for (uint256 i = 1; i < size; i++) { | ||
| for (uint256 j = i; j > 0 && array[j - 1] > array[j]; j--) { | ||
| uint256 tmp = array[j]; | ||
| array[j] = array[j - 1]; | ||
| array[j - 1] = tmp; | ||
| } | ||
| } | ||
| if (size % 2 == 1) { | ||
| return array[size / 2]; | ||
| } else { | ||
| return array[size / 2].add(array[size / 2 - 1]) / 2; | ||
| } | ||
| } | ||
| } |
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,23 @@ | ||
| pragma solidity 0.4.24; | ||
|
|
||
| import "../MedianOracle.sol"; | ||
|
|
||
| contract GetMedianOracleDataCallerContract { | ||
| event ReturnValueUInt256Bool(uint256 value, bool valid); | ||
|
|
||
| IOracle public oracle; | ||
|
|
||
| constructor() public {} | ||
|
|
||
| function setOracle(IOracle _oracle) public { | ||
| oracle = _oracle; | ||
| } | ||
|
|
||
| function getData() public returns (uint256) { | ||
| uint256 _value; | ||
| bool _valid; | ||
| (_value, _valid) = oracle.getData(); | ||
| emit ReturnValueUInt256Bool(_value, _valid); | ||
| return _value; | ||
| } | ||
| } |
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,15 @@ | ||
| pragma solidity 0.4.24; | ||
|
|
||
| import "../lib/Select.sol"; | ||
|
|
||
| contract Mock { | ||
| event ReturnValueUInt256(uint256 val); | ||
| } | ||
|
|
||
| contract SelectMock is Mock { | ||
| function computeMedian(uint256[] data, uint256 size) external returns (uint256) { | ||
| uint256 result = Select.computeMedian(data, size); | ||
| emit ReturnValueUInt256(result); | ||
| return result; | ||
| } | ||
| } |
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
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.