Contract Name:
MedianOracle
Contract Source Code:
File 1 of 1 : MedianOracle
pragma solidity ^0.4.24;
/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two numbers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
pragma solidity >=0.4.24 <0.6.0;
/**
* @title Initializable
*
* @dev Helper contract to support initializer functions. To use it, replace
* the constructor with a function that has the `initializer` modifier.
* WARNING: Unlike constructors, initializer functions must be manually
* invoked. This applies both to deploying an Initializable contract, as well
* as extending an Initializable contract via inheritance.
* WARNING: When used with inheritance, manual care must be taken to not invoke
* a parent initializer twice, or ensure that all initializers are idempotent,
* because this is not dealt with automatically as with constructors.
*/
contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private initializing;
/**
* @dev Modifier to use in the initializer function of a contract.
*/
modifier initializer() {
require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
bool wasInitializing = initializing;
initializing = true;
initialized = true;
_;
initializing = wasInitializing;
}
/// @dev Returns true if and only if the function is running in the constructor
function isConstructor() private view returns (bool) {
// extcodesize checks the size of the code stored in an address, and
// address returns the current address. Since the code is still not
// deployed when running a constructor, any checks on its code size will
// yield zero, making it an effective way to detect if a contract is
// under construction or not.
uint256 cs;
assembly { cs := extcodesize(address) }
return cs == 0;
}
// Reserved storage space to allow for layout changes in the future.
uint256[50] private ______gap;
}
pragma solidity ^0.4.24;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable is Initializable {
address private _owner;
event OwnershipRenounced(address indexed previousOwner);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function initialize(address sender) public initializer {
_owner = sender;
}
/**
* @return the address of the owner.
*/
function owner() public view returns(address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns(bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipRenounced(_owner);
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
uint256[50] private ______gap;
}
// File: contracts/lib/Select.sol
pragma solidity ^0.4.24;
/**
* @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;
}
}
}
// File: contracts/MedianOracle.sol
pragma solidity ^0.4.24;
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.
// 24hours
uint256 public reportExpirationTimeSec;
// The number of seconds since reporting that has to pass before a report
// is usable.
// 3600/1hour
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);
Ownable.initialize(msg.sender);
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;
}
}