Contract Source Code:
File 1 of 1 : Oracle
pragma solidity ^0.5.11;
contract Oracle {
/** Contract Constructor
* @param ethPrice the starting price of ETH in USD, represented as 150000000 = 150.00 USD
* @dev The message sender is assigned as the contract administrator
*/
constructor (uint ethPrice) public {
admins[msg.sender] = true;
addAsset("ETHUSD", ethPrice);
}
Asset[] public assets;
uint[8][] private prices;
mapping(address => bool) public admins;
mapping(address => bool) public readers;
// This prevents the Oracle from sneaking in an unexpected settlement price, especially a rapid succession of them
// it is 20 hours to accomodate a situation where there is an exogenous circumstance preventing the update, eg, network problems
uint public constant UPDATE_TIME_MIN = 0 hours;
// this gives time for players to cure their margins, burn
uint public constant SETTLE_TIME_MIN1 = 0 days; // 1 day
// this prevents addition of new prices before settlements are completed
uint public constant SETTLE_TIME_MIN2 = 46 hours; // 46
// this allows the oracle to rectify honest errors that might accidentally be posted
uint public constant EDIT_TIME_MAX = 30 minutes; // 90 min
struct Asset {
bytes32 name;
uint8 currentDay;
uint lastUpdateTime;
uint lastSettleTime;
bool isFinalDay;
}
event PriceUpdated(
uint indexed id,
bytes32 indexed name,
uint price,
uint timestamp,
uint8 dayNumber,
bool isCorrection
);
modifier onlyAdmin()
{
require(admins[msg.sender]);
_;
}
/** Grant administrator priviledges to a user,
* mainly intended for when the admin wants to switch accounts, ie, paired with a removal
* @param newAdmin the address to promote
*/
function addAdmin(address newAdmin)
public
onlyAdmin
{
admins[newAdmin] = true;
}
/** Add a new asset tracked by the Oracle
* @param _name the hexadecimal version of the simple name
* plaintext name of the asset, eg, SPXUSD = 0x5350585553440000000000000000000000000000000000000000000000000000
* @param _startPrice the starting price of the asset in USD * 10^2, eg 1200 = $12.00
* @dev this should usually be called on a Settlement Day
* @return id the newly assigned ID of the asset
*/
function addAsset(bytes32 _name, uint _startPrice)
public
returns (uint _assetID)
{
require (admins[msg.sender] || msg.sender == address(this));
// Fill the asset struct
Asset memory asset;
asset.name = _name;
asset.currentDay = 0;
asset.lastUpdateTime = now;
asset.lastSettleTime = now - 5 days;
assets.push(asset);
uint[8] memory _prices;
_prices[0] = _startPrice;
prices.push(_prices);
return assets.length - 1;
}
/** Quickly fix an erroneous price
* @param _assetID the id of the asset to change
* @param _newPrice the new price to change to
* @dev this must be called within 30 minutes of the lates price update occurence
*/
function editPrice(uint _assetID, uint _newPrice)
public
onlyAdmin
{
Asset storage asset = assets[_assetID];
require(now < asset.lastUpdateTime + EDIT_TIME_MAX);
prices[_assetID][asset.currentDay] = _newPrice;
emit PriceUpdated(_assetID, asset.name, _newPrice, now, asset.currentDay, true);
}
/** Grant an address permision to access private information about the assets
* @param newReader the address of the account to grant reading priviledges,
* any new contract the Oracle services would thus need the Oracle's permission
* @dev this allows the reader to use the getCurrentPricesFunction
*/
function addReader(address newReader)
public
onlyAdmin
{
readers[newReader] = true;
}
/** Return the entire current price array for a given asset
* @param _assetID the asset id of the desired asset
* @return _priceHist the price array for the asset
* @dev only the admin and addresses granted readership may call this function
*/
function getPrices(uint _assetID)
public
view
returns (uint[8] memory _priceHist)
{
require (admins[msg.sender] || readers[msg.sender]);
_priceHist = prices[_assetID];
}
/** Return the current prices array for a given asset
* excepting the last one. This is useful for users trying to calculate their PNL, as holidays can make
* inferences about the settlement or start date ambiguous. Anyone trying to use this contract as an oracle, however,
* would have a day lag
* @param _assetID the asset id of the desired asset
* @return _priceHist the price array for the asset excluding the most recent observation
* @dev only the admin and addresses granted readership may call this function
*/
function getStalePrices(uint _assetID)
public
view
returns (uint[8] memory _priceHist)
{
_priceHist = prices[_assetID];
_priceHist[assets[_assetID].currentDay]=0;
}
/** Return only the latest prices
* @param _assetID the asset id of the desired asset
* @return _price the latest price of the given asset
* @dev only the admin or a designated reader may call this function
*/
function getCurrentPrice(uint _assetID)
public
view
returns (uint _price)
{
require (admins[msg.sender] || readers[msg.sender]);
_price = prices[_assetID][assets[_assetID].currentDay];
}
/** Get the timestamp of the last price update time
* @param _assetID the asset id of the desired asset
* @return timestamp the price update timestamp
*/
function getLastUpdateTime(uint _assetID)
public
view
returns (uint timestamp)
{
timestamp = assets[_assetID].lastUpdateTime;
}
/** Get the timestamp of the last settle update time
* @param _assetID the asset id of the desired asset
* @return timestamp the settle timestamp
* this is useful for knowing when to run the WeeklyReturns function, and that settlement is soon
*/
function getLastSettleTime(uint _assetID)
public
view
returns (uint timestamp)
{
timestamp = assets[_assetID].lastSettleTime;
}
/**
* @param _assetID the asset id of the desired asset
* pulls the day relevant for new AssetSwap takes
*/
function getStartDay(uint _assetID)
public
view
returns (uint8 _startDay)
{
if (assets[_assetID].isFinalDay) _startDay = 7;
else if (assets[_assetID].currentDay == 7) _startDay = 1;
else _startDay = assets[_assetID].currentDay + 1;
}
/** Show if the current day is the final price update before settle
* @param _assetID the asset id of the desired asset
* @return true if it is the final day, false otherwise
* This makes sure the oracle cannot sneak it a settlement unaware, as when flagged false a user knows that a
* settlement cannot occur for at least 2 days. When set to false it lets a user know the next price update will be a
* settlement price and they need to potentially cure or cancel
*/
function isFinalDay(uint _assetID)
public
view
returns (bool)
{
return assets[_assetID].isFinalDay;
}
/** Show if the last price update was a settle price update
* @param _assetID the asset id of the desired asset
* @return true if the last update was a settle, false otherwise
* This tells LPs they need to settle their books, and that all parties must cure their margin if needed
*/
function isSettleDay(uint _assetID)
public
view
returns (bool)
{
return (assets[_assetID].currentDay == 7);
}
/** Remove administrator priviledges from a user
* @param toRemove the address to demote
* @notice you may not remove yourself. This allows the oracle to deprecate old addresses
*/
function removeAdmin(address toRemove)
public
onlyAdmin
{
require(toRemove != msg.sender);
admins[toRemove] = false;
}
/** Publishes an asset price. Does not initiate a settlement.
* @param _assetID the ID of the asset to update
* @param _price the current price of the asset * 10^2
* @param finalDayStatus true if this is the last intraweek price update (the next will be a settle)
* @dev this can only be called after the required time has elapsed since the most recent price update
* @dev if finalDayStatus is true this function cannot be called again until after settle
*/
function setIntraWeekPrice(uint _assetID, uint _price, bool finalDayStatus)
public
onlyAdmin
{
Asset storage asset = assets[_assetID];
// Prevent a quick succession of price updates
require(now > asset.lastUpdateTime + UPDATE_TIME_MIN);
// the price update follawing the isFinalDay=true must be a settlement price
require(!asset.isFinalDay);
if (asset.currentDay == 7) {
require(now > asset.lastSettleTime + SETTLE_TIME_MIN2,
"Sufficient time must pass after settlement update.");
asset.currentDay = 1;
uint[8] memory newPrices;
// the start price for each week is the settlement price of the prior week
newPrices[0] = prices[_assetID][7];
newPrices[1] = _price;
prices[_assetID] = newPrices;
} else {
asset.currentDay = asset.currentDay + 1;
prices[_assetID][asset.currentDay] = _price;
asset.isFinalDay = finalDayStatus;
}
asset.lastUpdateTime = now;
emit PriceUpdated(_assetID, asset.name, _price, now, asset.currentDay, false);
}
/** Publishes an asset price. Does not initiate a settlement.
* @param _assetID the ID of the asset to update
* @param _price the current price of the asset * 10^2
* @dev this can only be called after the required time has elapsed since the most recent price update
* @dev if finalDayStatus is true this function cannot be called again until after settle
*/
function setSettlePrice(uint _assetID, uint _price)
public
onlyAdmin
{
Asset storage asset = assets[_assetID];
// Prevent price update too early
require(now > asset.lastUpdateTime + UPDATE_TIME_MIN);
// can only be set when the last update signalled as such
require(asset.isFinalDay);
// need at least 5 days between settlements
require(now > asset.lastSettleTime + SETTLE_TIME_MIN1,
"Sufficient time must pass between weekly price updates.");
// settlement prices are set to slot 7 in the prices array
asset.currentDay = 7;
prices[_assetID][7] = _price;
asset.lastSettleTime = now;
asset.isFinalDay = false;
asset.lastUpdateTime = now;
emit PriceUpdated(_assetID, asset.name, _price, now, 7, false);
}
}