Front end and contract
This commit is contained in:
32
contracts/Constants.sol
Normal file
32
contracts/Constants.sol
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title Stores common interface names used throughout the DVM by registration in the Finder.
|
||||
*/
|
||||
library OracleInterfaces {
|
||||
bytes32 public constant Oracle = "Oracle";
|
||||
bytes32 public constant IdentifierWhitelist = "IdentifierWhitelist";
|
||||
bytes32 public constant Store = "Store";
|
||||
bytes32 public constant FinancialContractsAdmin = "FinancialContractsAdmin";
|
||||
bytes32 public constant Registry = "Registry";
|
||||
bytes32 public constant CollateralWhitelist = "CollateralWhitelist";
|
||||
bytes32 public constant OptimisticOracle = "OptimisticOracle";
|
||||
bytes32 public constant Bridge = "Bridge";
|
||||
bytes32 public constant GenericHandler = "GenericHandler";
|
||||
bytes32 public constant SkinnyOptimisticOracle = "SkinnyOptimisticOracle";
|
||||
bytes32 public constant ChildMessenger = "ChildMessenger";
|
||||
bytes32 public constant OracleHub = "OracleHub";
|
||||
bytes32 public constant OracleSpoke = "OracleSpoke";
|
||||
}
|
||||
|
||||
/**
|
||||
* @title Commonly re-used values for contracts associated with the OptimisticOracle.
|
||||
*/
|
||||
library OptimisticOracleConstraints {
|
||||
// Any price request submitted to the OptimisticOracle must contain ancillary data no larger than this value.
|
||||
// This value must be <= the Voting contract's `ancillaryBytesLimit` constant value otherwise it is possible
|
||||
// that a price can be requested to the OptimisticOracle successfully, but cannot be resolved by the DVM which
|
||||
// refuses to accept a price request made with ancillary data length over a certain size.
|
||||
uint256 public constant ancillaryBytesLimit = 8192;
|
||||
}
|
||||
730
contracts/Oracle.sol
Normal file
730
contracts/Oracle.sol
Normal file
@@ -0,0 +1,730 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
|
||||
import "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
import "./interfaces/StoreInterface.sol";
|
||||
import "./interfaces/OracleAncillaryInterface.sol";
|
||||
import "./interfaces/FinderInterface.sol";
|
||||
import "./interfaces/IdentifierWhitelistInterface.sol";
|
||||
import "./interfaces/OptimisticOracleV2Interface.sol";
|
||||
import "./Constants.sol";
|
||||
|
||||
import "./implementation/Testable.sol";
|
||||
import "./implementation/Lockable.sol";
|
||||
import "./implementation/FixedPoint.sol";
|
||||
import "./implementation/AncillaryData.sol";
|
||||
import "./implementation/AddressWhitelist.sol";
|
||||
|
||||
/**
|
||||
* @title Optimistic Requester.
|
||||
* @notice Optional interface that requesters can implement to receive callbacks.
|
||||
* @dev this contract does _not_ work with ERC777 collateral currencies or any others that call into the receiver on
|
||||
* transfer(). Using an ERC777 token would allow a user to maliciously grief other participants (while also losing
|
||||
* money themselves).
|
||||
*/
|
||||
interface OptimisticRequester {
|
||||
/**
|
||||
* @notice Callback for proposals.
|
||||
* @param identifier price identifier being requested.
|
||||
* @param timestamp timestamp of the price being requested.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
*/
|
||||
function priceProposed(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @notice Callback for disputes.
|
||||
* @param identifier price identifier being requested.
|
||||
* @param timestamp timestamp of the price being requested.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param refund refund received in the case that refundOnDispute was enabled.
|
||||
*/
|
||||
function priceDisputed(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
uint256 refund
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @notice Callback for settlement.
|
||||
* @param identifier price identifier being requested.
|
||||
* @param timestamp timestamp of the price being requested.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param price price that was resolved by the escalation process.
|
||||
*/
|
||||
function priceSettled(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
int256 price
|
||||
) external;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title Optimistic Oracle.
|
||||
* @notice Pre-DVM escalation contract that allows faster settlement.
|
||||
*/
|
||||
contract OptimisticOracleV2 is OptimisticOracleV2Interface, Testable, Lockable {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
using Address for address;
|
||||
|
||||
// Finder to provide addresses for DVM contracts.
|
||||
FinderInterface public override finder;
|
||||
|
||||
// Default liveness value for all price requests.
|
||||
uint256 public override defaultLiveness;
|
||||
|
||||
// This is effectively the extra ancillary data to add ",ooRequester:0000000000000000000000000000000000000000".
|
||||
uint256 private constant MAX_ADDED_ANCILLARY_DATA = 53;
|
||||
uint256 public constant OO_ANCILLARY_DATA_LIMIT = ancillaryBytesLimit - MAX_ADDED_ANCILLARY_DATA;
|
||||
int256 public constant TOO_EARLY_RESPONSE = type(int256).min;
|
||||
|
||||
/**
|
||||
* @notice Constructor.
|
||||
* @param _liveness default liveness applied to each price request.
|
||||
* @param _finderAddress finder to use to get addresses of DVM contracts.
|
||||
* @param _timerAddress address of the timer contract. Should be 0x0 in prod.
|
||||
*/
|
||||
constructor(
|
||||
uint256 _liveness,
|
||||
address _finderAddress,
|
||||
address _timerAddress
|
||||
) Testable(_timerAddress) {
|
||||
finder = FinderInterface(_finderAddress);
|
||||
_validateLiveness(_liveness);
|
||||
defaultLiveness = _liveness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Requests a new price.
|
||||
* @param identifier price identifier being requested.
|
||||
* @param timestamp timestamp of the price being requested.
|
||||
* @param ancillaryData ancillary data representing additional args being passed with the price request.
|
||||
* @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM.
|
||||
* @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0,
|
||||
* which could make sense if the contract requests and proposes the value in the same call or
|
||||
* provides its own reward system.
|
||||
* @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay.
|
||||
* This can be changed with a subsequent call to setBond().
|
||||
*/
|
||||
function requestPrice(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
IERC20 currency,
|
||||
uint256 reward
|
||||
) external override nonReentrant() returns (uint256 totalBond) {
|
||||
require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Invalid, "requestPrice: Invalid");
|
||||
require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier");
|
||||
require(_getCollateralWhitelist().isOnWhitelist(address(currency)), "Unsupported currency");
|
||||
require(timestamp <= getCurrentTime(), "Timestamp in future");
|
||||
|
||||
// This ensures that the ancillary data is <= the OO limit, which is lower than the DVM limit because the
|
||||
// OO adds some data before sending to the DVM.
|
||||
require(ancillaryData.length <= OO_ANCILLARY_DATA_LIMIT, "Ancillary Data too long");
|
||||
|
||||
uint256 finalFee = _getStore().computeFinalFee(address(currency)).rawValue;
|
||||
requests[_getId(msg.sender, identifier, timestamp, ancillaryData)] = Request({
|
||||
proposer: address(0),
|
||||
disputer: address(0),
|
||||
currency: currency,
|
||||
settled: false,
|
||||
requestSettings: RequestSettings({
|
||||
eventBased: false,
|
||||
refundOnDispute: false,
|
||||
callbackOnPriceProposed: false,
|
||||
callbackOnPriceDisputed: false,
|
||||
callbackOnPriceSettled: false,
|
||||
bond: finalFee,
|
||||
customLiveness: 0
|
||||
}),
|
||||
proposedPrice: 0,
|
||||
resolvedPrice: 0,
|
||||
expirationTime: 0,
|
||||
reward: reward,
|
||||
finalFee: finalFee
|
||||
});
|
||||
|
||||
if (reward > 0) {
|
||||
currency.safeTransferFrom(msg.sender, address(this), reward);
|
||||
}
|
||||
|
||||
emit RequestPrice(msg.sender, identifier, timestamp, ancillaryData, address(currency), reward, finalFee);
|
||||
|
||||
// This function returns the initial proposal bond for this request, which can be customized by calling
|
||||
// setBond() with the same identifier and timestamp.
|
||||
return finalFee.mul(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Set the proposal bond associated with a price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param bond custom bond amount to set.
|
||||
* @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be
|
||||
* changed again with a subsequent call to setBond().
|
||||
*/
|
||||
function setBond(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
uint256 bond
|
||||
) external override nonReentrant() returns (uint256 totalBond) {
|
||||
require(_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested, "setBond: Requested");
|
||||
Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData);
|
||||
request.requestSettings.bond = bond;
|
||||
|
||||
// Total bond is the final fee + the newly set bond.
|
||||
return bond.add(request.finalFee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller
|
||||
* in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's
|
||||
* bond, so there is still profit to be made even if the reward is refunded.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
*/
|
||||
function setRefundOnDispute(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external override nonReentrant() {
|
||||
require(
|
||||
_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested,
|
||||
"setRefundOnDispute: Requested"
|
||||
);
|
||||
_getRequest(msg.sender, identifier, timestamp, ancillaryData).requestSettings.refundOnDispute = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before
|
||||
* being auto-resolved.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param customLiveness new custom liveness.
|
||||
*/
|
||||
function setCustomLiveness(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
uint256 customLiveness
|
||||
) external override nonReentrant() {
|
||||
require(
|
||||
_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested,
|
||||
"setCustomLiveness: Requested"
|
||||
);
|
||||
_validateLiveness(customLiveness);
|
||||
_getRequest(msg.sender, identifier, timestamp, ancillaryData).requestSettings.customLiveness = customLiveness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the request to be an "event-based" request.
|
||||
* @dev Calling this method has a few impacts on the request:
|
||||
*
|
||||
* 1. The timestamp at which the request is evaluated is the time of the proposal, not the timestamp associated
|
||||
* with the request.
|
||||
*
|
||||
* 2. The proposer cannot propose the "too early" value (TOO_EARLY_RESPONSE). This is to ensure that a proposer who
|
||||
* prematurely proposes a response loses their bond.
|
||||
*
|
||||
* 3. RefundoOnDispute is automatically set, meaning disputes trigger the reward to be automatically refunded to
|
||||
* the requesting contract.
|
||||
*
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
*/
|
||||
function setEventBased(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external override nonReentrant() {
|
||||
require(
|
||||
_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested,
|
||||
"setEventBased: Requested"
|
||||
);
|
||||
Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData);
|
||||
request.requestSettings.eventBased = true;
|
||||
request.requestSettings.refundOnDispute = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets which callbacks should be enabled for the request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param callbackOnPriceProposed whether to enable the callback onPriceProposed.
|
||||
* @param callbackOnPriceDisputed whether to enable the callback onPriceDisputed.
|
||||
* @param callbackOnPriceSettled whether to enable the callback onPriceSettled.
|
||||
*/
|
||||
function setCallbacks(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
bool callbackOnPriceProposed,
|
||||
bool callbackOnPriceDisputed,
|
||||
bool callbackOnPriceSettled
|
||||
) external override nonReentrant() {
|
||||
require(
|
||||
_getState(msg.sender, identifier, timestamp, ancillaryData) == State.Requested,
|
||||
"setCallbacks: Requested"
|
||||
);
|
||||
Request storage request = _getRequest(msg.sender, identifier, timestamp, ancillaryData);
|
||||
request.requestSettings.callbackOnPriceProposed = callbackOnPriceProposed;
|
||||
request.requestSettings.callbackOnPriceDisputed = callbackOnPriceDisputed;
|
||||
request.requestSettings.callbackOnPriceSettled = callbackOnPriceSettled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come
|
||||
* from this proposal. However, any bonds are pulled from the caller.
|
||||
* @param proposer address to set as the proposer.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param proposedPrice price being proposed.
|
||||
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
|
||||
* the proposer once settled if the proposal is correct.
|
||||
*/
|
||||
function proposePriceFor(
|
||||
address proposer,
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
int256 proposedPrice
|
||||
) public override nonReentrant() returns (uint256 totalBond) {
|
||||
require(proposer != address(0), "proposer address must be non 0");
|
||||
require(
|
||||
_getState(requester, identifier, timestamp, ancillaryData) == State.Requested,
|
||||
"proposePriceFor: Requested"
|
||||
);
|
||||
Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData);
|
||||
if (request.requestSettings.eventBased)
|
||||
require(proposedPrice != TOO_EARLY_RESPONSE, "Cannot propose 'too early'");
|
||||
request.proposer = proposer;
|
||||
request.proposedPrice = proposedPrice;
|
||||
|
||||
// If a custom liveness has been set, use it instead of the default.
|
||||
request.expirationTime = getCurrentTime().add(
|
||||
request.requestSettings.customLiveness != 0 ? request.requestSettings.customLiveness : defaultLiveness
|
||||
);
|
||||
|
||||
totalBond = request.requestSettings.bond.add(request.finalFee);
|
||||
if (totalBond > 0) request.currency.safeTransferFrom(msg.sender, address(this), totalBond);
|
||||
|
||||
emit ProposePrice(
|
||||
requester,
|
||||
proposer,
|
||||
identifier,
|
||||
timestamp,
|
||||
ancillaryData,
|
||||
proposedPrice,
|
||||
request.expirationTime,
|
||||
address(request.currency)
|
||||
);
|
||||
|
||||
// End the re-entrancy guard early to allow the caller to potentially take OO-related actions inside this callback.
|
||||
_startReentrantGuardDisabled();
|
||||
// Callback.
|
||||
if (address(requester).isContract() && request.requestSettings.callbackOnPriceProposed)
|
||||
OptimisticRequester(requester).priceProposed(identifier, timestamp, ancillaryData);
|
||||
_endReentrantGuardDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Proposes a price value for an existing price request.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param proposedPrice price being proposed.
|
||||
* @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to
|
||||
* the proposer once settled if the proposal is correct.
|
||||
*/
|
||||
function proposePrice(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
int256 proposedPrice
|
||||
) external override returns (uint256 totalBond) {
|
||||
// Note: re-entrancy guard is done in the inner call.
|
||||
return proposePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData, proposedPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will
|
||||
* receive any rewards that come from this dispute. However, any bonds are pulled from the caller.
|
||||
* @param disputer address to set as the disputer.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
|
||||
* the disputer once settled if the dispute was valid (the proposal was incorrect).
|
||||
*/
|
||||
function disputePriceFor(
|
||||
address disputer,
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public override nonReentrant() returns (uint256 totalBond) {
|
||||
require(disputer != address(0), "disputer address must be non 0");
|
||||
require(
|
||||
_getState(requester, identifier, timestamp, ancillaryData) == State.Proposed,
|
||||
"disputePriceFor: Proposed"
|
||||
);
|
||||
Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData);
|
||||
request.disputer = disputer;
|
||||
|
||||
uint256 finalFee = request.finalFee;
|
||||
uint256 bond = request.requestSettings.bond;
|
||||
totalBond = bond.add(finalFee);
|
||||
if (totalBond > 0) {
|
||||
request.currency.safeTransferFrom(msg.sender, address(this), totalBond);
|
||||
}
|
||||
|
||||
StoreInterface store = _getStore();
|
||||
|
||||
// Along with the final fee, "burn" part of the loser's bond to ensure that a larger bond always makes it
|
||||
// proportionally more expensive to delay the resolution even if the proposer and disputer are the same
|
||||
// party.
|
||||
|
||||
// The total fee is the burned bond and the final fee added together.
|
||||
uint256 totalFee = finalFee.add(_computeBurnedBond(request));
|
||||
if (totalFee > 0) {
|
||||
request.currency.safeIncreaseAllowance(address(store), totalFee);
|
||||
_getStore().payOracleFeesErc20(address(request.currency), FixedPoint.Unsigned(totalFee));
|
||||
}
|
||||
|
||||
_getOracle().requestPrice(
|
||||
identifier,
|
||||
_getTimestampForDvmRequest(request, timestamp),
|
||||
_stampAncillaryData(ancillaryData, requester)
|
||||
);
|
||||
|
||||
// Compute refund.
|
||||
uint256 refund = 0;
|
||||
if (request.reward > 0 && request.requestSettings.refundOnDispute) {
|
||||
refund = request.reward;
|
||||
request.reward = 0;
|
||||
request.currency.safeTransfer(requester, refund);
|
||||
}
|
||||
|
||||
emit DisputePrice(
|
||||
requester,
|
||||
request.proposer,
|
||||
disputer,
|
||||
identifier,
|
||||
timestamp,
|
||||
ancillaryData,
|
||||
request.proposedPrice
|
||||
);
|
||||
|
||||
// End the re-entrancy guard early to allow the caller to potentially re-request inside this callback.
|
||||
_startReentrantGuardDisabled();
|
||||
// Callback.
|
||||
if (address(requester).isContract() && request.requestSettings.callbackOnPriceDisputed)
|
||||
OptimisticRequester(requester).priceDisputed(identifier, timestamp, ancillaryData, refund);
|
||||
_endReentrantGuardDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Disputes a price value for an existing price request with an active proposal.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to
|
||||
* the disputer once settled if the dispute was valid (the proposal was incorrect).
|
||||
*/
|
||||
function disputePrice(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external override returns (uint256 totalBond) {
|
||||
// Note: re-entrancy guard is done in the inner call.
|
||||
return disputePriceFor(msg.sender, requester, identifier, timestamp, ancillaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled
|
||||
* or settleable. Note: this method is not view so that this call may actually settle the price request if it
|
||||
* hasn't been settled.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return resolved price.
|
||||
*/
|
||||
function settleAndGetPrice(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external override nonReentrant() returns (int256) {
|
||||
if (_getState(msg.sender, identifier, timestamp, ancillaryData) != State.Settled) {
|
||||
_settle(msg.sender, identifier, timestamp, ancillaryData);
|
||||
}
|
||||
|
||||
return _getRequest(msg.sender, identifier, timestamp, ancillaryData).resolvedPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes
|
||||
* the returned bonds as well as additional rewards.
|
||||
*/
|
||||
function settle(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external override nonReentrant() returns (uint256 payout) {
|
||||
return _settle(requester, identifier, timestamp, ancillaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Gets the current data structure containing all information about a price request.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return the Request data structure.
|
||||
*/
|
||||
function getRequest(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public view override nonReentrantView() returns (Request memory) {
|
||||
return _getRequest(requester, identifier, timestamp, ancillaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Computes the current state of a price request. See the State enum for more details.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return the State.
|
||||
*/
|
||||
function getState(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public view override nonReentrantView() returns (State) {
|
||||
return _getState(requester, identifier, timestamp, ancillaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Checks if a given request has resolved, expired or been settled (i.e the optimistic oracle has a price).
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return boolean indicating true if price exists and false if not.
|
||||
*/
|
||||
function hasPrice(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public view override nonReentrantView() returns (bool) {
|
||||
State state = _getState(requester, identifier, timestamp, ancillaryData);
|
||||
return state == State.Settled || state == State.Resolved || state == State.Expired;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Generates stamped ancillary data in the format that it would be used in the case of a price dispute.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param requester sender of the initial price request.
|
||||
* @return the stamped ancillary bytes.
|
||||
*/
|
||||
function stampAncillaryData(bytes memory ancillaryData, address requester)
|
||||
public
|
||||
pure
|
||||
override
|
||||
returns (bytes memory)
|
||||
{
|
||||
return _stampAncillaryData(ancillaryData, requester);
|
||||
}
|
||||
|
||||
function _getId(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) private pure returns (bytes32) {
|
||||
return keccak256(abi.encodePacked(requester, identifier, timestamp, ancillaryData));
|
||||
}
|
||||
|
||||
function _settle(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) private returns (uint256 payout) {
|
||||
State state = _getState(requester, identifier, timestamp, ancillaryData);
|
||||
|
||||
// Set it to settled so this function can never be entered again.
|
||||
Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData);
|
||||
request.settled = true;
|
||||
|
||||
if (state == State.Expired) {
|
||||
// In the expiry case, just pay back the proposer's bond and final fee along with the reward.
|
||||
request.resolvedPrice = request.proposedPrice;
|
||||
payout = request.requestSettings.bond.add(request.finalFee).add(request.reward);
|
||||
request.currency.safeTransfer(request.proposer, payout);
|
||||
} else if (state == State.Resolved) {
|
||||
// In the Resolved case, pay either the disputer or the proposer the entire payout (+ bond and reward).
|
||||
request.resolvedPrice = _getOracle().getPrice(
|
||||
identifier,
|
||||
_getTimestampForDvmRequest(request, timestamp),
|
||||
_stampAncillaryData(ancillaryData, requester)
|
||||
);
|
||||
bool disputeSuccess = request.resolvedPrice != request.proposedPrice;
|
||||
uint256 bond = request.requestSettings.bond;
|
||||
|
||||
// Unburned portion of the loser's bond = 1 - burned bond.
|
||||
uint256 unburnedBond = bond.sub(_computeBurnedBond(request));
|
||||
|
||||
// Winner gets:
|
||||
// - Their bond back.
|
||||
// - The unburned portion of the loser's bond.
|
||||
// - Their final fee back.
|
||||
// - The request reward (if not already refunded -- if refunded, it will be set to 0).
|
||||
payout = bond.add(unburnedBond).add(request.finalFee).add(request.reward);
|
||||
request.currency.safeTransfer(disputeSuccess ? request.disputer : request.proposer, payout);
|
||||
} else revert("_settle: not settleable");
|
||||
|
||||
emit Settle(
|
||||
requester,
|
||||
request.proposer,
|
||||
request.disputer,
|
||||
identifier,
|
||||
timestamp,
|
||||
ancillaryData,
|
||||
request.resolvedPrice,
|
||||
payout
|
||||
);
|
||||
|
||||
// Temporarily disable the re-entrancy guard early to allow the caller to take an OO-related action inside this callback.
|
||||
_startReentrantGuardDisabled();
|
||||
// Callback.
|
||||
if (address(requester).isContract() && request.requestSettings.callbackOnPriceSettled)
|
||||
OptimisticRequester(requester).priceSettled(identifier, timestamp, ancillaryData, request.resolvedPrice);
|
||||
_endReentrantGuardDisabled();
|
||||
}
|
||||
|
||||
function _getRequest(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) private view returns (Request storage) {
|
||||
return requests[_getId(requester, identifier, timestamp, ancillaryData)];
|
||||
}
|
||||
|
||||
function _computeBurnedBond(Request storage request) private view returns (uint256) {
|
||||
// burnedBond = floor(bond / 2)
|
||||
return request.requestSettings.bond.div(2);
|
||||
}
|
||||
|
||||
function _validateLiveness(uint256 _liveness) private pure {
|
||||
require(_liveness < 5200 weeks, "Liveness too large");
|
||||
require(_liveness > 0, "Liveness cannot be 0");
|
||||
}
|
||||
|
||||
function _getState(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) internal view returns (State) {
|
||||
Request storage request = _getRequest(requester, identifier, timestamp, ancillaryData);
|
||||
|
||||
if (address(request.currency) == address(0)) return State.Invalid;
|
||||
|
||||
if (request.proposer == address(0)) return State.Requested;
|
||||
|
||||
if (request.settled) return State.Settled;
|
||||
|
||||
if (request.disputer == address(0))
|
||||
return request.expirationTime <= getCurrentTime() ? State.Expired : State.Proposed;
|
||||
|
||||
return
|
||||
_getOracle().hasPrice(
|
||||
identifier,
|
||||
_getTimestampForDvmRequest(request, timestamp),
|
||||
_stampAncillaryData(ancillaryData, requester)
|
||||
)
|
||||
? State.Resolved
|
||||
: State.Disputed;
|
||||
}
|
||||
|
||||
function _getOracle() internal view returns (OracleAncillaryInterface) {
|
||||
return OracleAncillaryInterface(finder.getImplementationAddress(OracleInterfaces.Oracle));
|
||||
}
|
||||
|
||||
function _getCollateralWhitelist() internal view returns (AddressWhitelist) {
|
||||
return AddressWhitelist(finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist));
|
||||
}
|
||||
|
||||
function _getStore() internal view returns (StoreInterface) {
|
||||
return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store));
|
||||
}
|
||||
|
||||
function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) {
|
||||
return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist));
|
||||
}
|
||||
|
||||
function _getTimestampForDvmRequest(Request storage request, uint256 requestTimestamp)
|
||||
internal
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
if (request.requestSettings.eventBased) {
|
||||
uint256 liveness =
|
||||
request.requestSettings.customLiveness != 0 ? request.requestSettings.customLiveness : defaultLiveness;
|
||||
return request.expirationTime.sub(liveness);
|
||||
} else {
|
||||
return requestTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev We don't handle specifically the case where `ancillaryData` is not already readily translateable in utf8.
|
||||
* For those cases, we assume that the client will be able to strip out the utf8-translateable part of the
|
||||
* ancillary data that this contract stamps.
|
||||
*/
|
||||
function _stampAncillaryData(bytes memory ancillaryData, address requester) internal pure returns (bytes memory) {
|
||||
// Since this contract will be the one to formally submit DVM price requests, its useful for voters to know who
|
||||
// the original requester was.
|
||||
return AncillaryData.appendKeyValueAddress(ancillaryData, "ooRequester", requester);
|
||||
}
|
||||
|
||||
function getCurrentTime() public view override(Testable, OptimisticOracleV2Interface) returns (uint256) {
|
||||
return Testable.getCurrentTime();
|
||||
}
|
||||
}
|
||||
51
contracts/WaybackETH.sol
Normal file
51
contracts/WaybackETH.sol
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.14;
|
||||
|
||||
import "./Oracle.sol";
|
||||
|
||||
contract WaybackETH {
|
||||
|
||||
OptimisticOracleV2Interface oo = OptimisticOracleV2Interface(0xA5B9d8a0B0Fa04Ba71BDD68069661ED5C0848884);
|
||||
|
||||
bytes32 identifier = bytes32("YES_OR_NO_QUERY");
|
||||
|
||||
mapping(uint => bytes) public indexToAncillaryData;
|
||||
mapping(uint => uint) public indexToHasSettled;
|
||||
|
||||
// Hash confirming for is the key
|
||||
mapping(string => DataByRequest) private hashToData;
|
||||
struct DataByRequest {
|
||||
bytes ancillaryData;
|
||||
uint256 requestTime;
|
||||
bool hasSettled;
|
||||
}
|
||||
|
||||
function requestHashMatch(string calldata dockerURL, string calldata hashURL, string calldata hash) public {
|
||||
bytes memory ancillaryData =
|
||||
bytes(string.concat("Q: Using this Docker container ", dockerURL, " and this URI as input ", hashURL, " --- Does the output match this hash: ", hash, "? A: 1 for yes, 2 for no"));
|
||||
uint256 requestTime = block.timestamp;
|
||||
IERC20 bondCurrency = IERC20(0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6);
|
||||
uint256 reward = 0;
|
||||
hashToData[hash] = DataByRequest(
|
||||
ancillaryData,
|
||||
requestTime,
|
||||
false
|
||||
);
|
||||
|
||||
oo.requestPrice(identifier, requestTime, ancillaryData, bondCurrency, reward);
|
||||
oo.setCustomLiveness(identifier, requestTime, ancillaryData, 300);
|
||||
}
|
||||
|
||||
function settleHashMatchRequest(string calldata hash) public {
|
||||
if (hashToData[hash].hasSettled == false) {
|
||||
uint256 payout = oo.settle(address(this), identifier, hashToData[hash].requestTime, hashToData[hash].ancillaryData);
|
||||
if (payout > 0) {
|
||||
hashToData[hash].hasSettled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSettledData(string calldata hash) public view returns (int256) {
|
||||
return oo.getRequest(address(this), identifier, hashToData[hash].requestTime, hashToData[hash].ancillaryData).resolvedPrice;
|
||||
}
|
||||
}
|
||||
88
contracts/implementation/AddressWhitelist.sol
Normal file
88
contracts/implementation/AddressWhitelist.sol
Normal file
@@ -0,0 +1,88 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../interfaces/AddressWhitelistInterface.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "./Lockable.sol";
|
||||
|
||||
/**
|
||||
* @title A contract to track a whitelist of addresses.
|
||||
*/
|
||||
contract AddressWhitelist is AddressWhitelistInterface, Ownable, Lockable {
|
||||
enum Status { None, In, Out }
|
||||
mapping(address => Status) public whitelist;
|
||||
|
||||
address[] public whitelistIndices;
|
||||
|
||||
event AddedToWhitelist(address indexed addedAddress);
|
||||
event RemovedFromWhitelist(address indexed removedAddress);
|
||||
|
||||
/**
|
||||
* @notice Adds an address to the whitelist.
|
||||
* @param newElement the new address to add.
|
||||
*/
|
||||
function addToWhitelist(address newElement) external override nonReentrant() onlyOwner {
|
||||
// Ignore if address is already included
|
||||
if (whitelist[newElement] == Status.In) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only append new addresses to the array, never a duplicate
|
||||
if (whitelist[newElement] == Status.None) {
|
||||
whitelistIndices.push(newElement);
|
||||
}
|
||||
|
||||
whitelist[newElement] = Status.In;
|
||||
|
||||
emit AddedToWhitelist(newElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Removes an address from the whitelist.
|
||||
* @param elementToRemove the existing address to remove.
|
||||
*/
|
||||
function removeFromWhitelist(address elementToRemove) external override nonReentrant() onlyOwner {
|
||||
if (whitelist[elementToRemove] != Status.Out) {
|
||||
whitelist[elementToRemove] = Status.Out;
|
||||
emit RemovedFromWhitelist(elementToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Checks whether an address is on the whitelist.
|
||||
* @param elementToCheck the address to check.
|
||||
* @return True if `elementToCheck` is on the whitelist, or False.
|
||||
*/
|
||||
function isOnWhitelist(address elementToCheck) external view override nonReentrantView() returns (bool) {
|
||||
return whitelist[elementToCheck] == Status.In;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Gets all addresses that are currently included in the whitelist.
|
||||
* @dev Note: This method skips over, but still iterates through addresses. It is possible for this call to run out
|
||||
* of gas if a large number of addresses have been removed. To reduce the likelihood of this unlikely scenario, we
|
||||
* can modify the implementation so that when addresses are removed, the last addresses in the array is moved to
|
||||
* the empty index.
|
||||
* @return activeWhitelist the list of addresses on the whitelist.
|
||||
*/
|
||||
function getWhitelist() external view override nonReentrantView() returns (address[] memory activeWhitelist) {
|
||||
// Determine size of whitelist first
|
||||
uint256 activeCount = 0;
|
||||
for (uint256 i = 0; i < whitelistIndices.length; i++) {
|
||||
if (whitelist[whitelistIndices[i]] == Status.In) {
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate whitelist
|
||||
activeWhitelist = new address[](activeCount);
|
||||
activeCount = 0;
|
||||
for (uint256 i = 0; i < whitelistIndices.length; i++) {
|
||||
address addr = whitelistIndices[i];
|
||||
if (whitelist[addr] == Status.In) {
|
||||
activeWhitelist[activeCount] = addr;
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
144
contracts/implementation/AncillaryData.sol
Normal file
144
contracts/implementation/AncillaryData.sol
Normal file
@@ -0,0 +1,144 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title Library for encoding and decoding ancillary data for DVM price requests.
|
||||
* @notice We assume that on-chain ancillary data can be formatted directly from bytes to utf8 encoding via
|
||||
* web3.utils.hexToUtf8, and that clients will parse the utf8-encoded ancillary data as a comma-delimitted key-value
|
||||
* dictionary. Therefore, this library provides internal methods that aid appending to ancillary data from Solidity
|
||||
* smart contracts. More details on UMA's ancillary data guidelines below:
|
||||
* https://docs.google.com/document/d/1zhKKjgY1BupBGPPrY_WOJvui0B6DMcd-xDR8-9-SPDw/edit
|
||||
*/
|
||||
library AncillaryData {
|
||||
// This converts the bottom half of a bytes32 input to hex in a highly gas-optimized way.
|
||||
// Source: the brilliant implementation at https://gitter.im/ethereum/solidity?at=5840d23416207f7b0ed08c9b.
|
||||
function toUtf8Bytes32Bottom(bytes32 bytesIn) private pure returns (bytes32) {
|
||||
unchecked {
|
||||
uint256 x = uint256(bytesIn);
|
||||
|
||||
// Nibble interleave
|
||||
x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
|
||||
x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
|
||||
x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
|
||||
x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
|
||||
x = (x | (x * 2**8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
|
||||
x = (x | (x * 2**4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
|
||||
|
||||
// Hex encode
|
||||
uint256 h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
|
||||
uint256 i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
|
||||
uint256 j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
|
||||
x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;
|
||||
|
||||
// Return the result.
|
||||
return bytes32(x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns utf8-encoded bytes32 string that can be read via web3.utils.hexToUtf8.
|
||||
* @dev Will return bytes32 in all lower case hex characters and without the leading 0x.
|
||||
* This has minor changes from the toUtf8BytesAddress to control for the size of the input.
|
||||
* @param bytesIn bytes32 to encode.
|
||||
* @return utf8 encoded bytes32.
|
||||
*/
|
||||
function toUtf8Bytes(bytes32 bytesIn) internal pure returns (bytes memory) {
|
||||
return abi.encodePacked(toUtf8Bytes32Bottom(bytesIn >> 128), toUtf8Bytes32Bottom(bytesIn));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns utf8-encoded address that can be read via web3.utils.hexToUtf8.
|
||||
* Source: https://ethereum.stackexchange.com/questions/8346/convert-address-to-string/8447#8447
|
||||
* @dev Will return address in all lower case characters and without the leading 0x.
|
||||
* @param x address to encode.
|
||||
* @return utf8 encoded address bytes.
|
||||
*/
|
||||
function toUtf8BytesAddress(address x) internal pure returns (bytes memory) {
|
||||
return
|
||||
abi.encodePacked(toUtf8Bytes32Bottom(bytes32(bytes20(x)) >> 128), bytes8(toUtf8Bytes32Bottom(bytes20(x))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type.
|
||||
* @dev This method is based off of this code: https://stackoverflow.com/a/65707309.
|
||||
*/
|
||||
function toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) {
|
||||
if (x == 0) {
|
||||
return "0";
|
||||
}
|
||||
uint256 j = x;
|
||||
uint256 len;
|
||||
while (j != 0) {
|
||||
len++;
|
||||
j /= 10;
|
||||
}
|
||||
bytes memory bstr = new bytes(len);
|
||||
uint256 k = len;
|
||||
while (x != 0) {
|
||||
k = k - 1;
|
||||
uint8 temp = (48 + uint8(x - (x / 10) * 10));
|
||||
bytes1 b1 = bytes1(temp);
|
||||
bstr[k] = b1;
|
||||
x /= 10;
|
||||
}
|
||||
return bstr;
|
||||
}
|
||||
|
||||
function appendKeyValueBytes32(
|
||||
bytes memory currentAncillaryData,
|
||||
bytes memory key,
|
||||
bytes32 value
|
||||
) internal pure returns (bytes memory) {
|
||||
bytes memory prefix = constructPrefix(currentAncillaryData, key);
|
||||
return abi.encodePacked(currentAncillaryData, prefix, toUtf8Bytes(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds "key:value" to `currentAncillaryData` where `value` is an address that first needs to be converted
|
||||
* to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return
|
||||
* `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`.
|
||||
* @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not.
|
||||
* @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not.
|
||||
* @param value An address to set as the value in the key:value pair to append to `currentAncillaryData`.
|
||||
* @return Newly appended ancillary data.
|
||||
*/
|
||||
function appendKeyValueAddress(
|
||||
bytes memory currentAncillaryData,
|
||||
bytes memory key,
|
||||
address value
|
||||
) internal pure returns (bytes memory) {
|
||||
bytes memory prefix = constructPrefix(currentAncillaryData, key);
|
||||
return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesAddress(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds "key:value" to `currentAncillaryData` where `value` is a uint that first needs to be converted
|
||||
* to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return
|
||||
* `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`.
|
||||
* @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not.
|
||||
* @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not.
|
||||
* @param value A uint to set as the value in the key:value pair to append to `currentAncillaryData`.
|
||||
* @return Newly appended ancillary data.
|
||||
*/
|
||||
function appendKeyValueUint(
|
||||
bytes memory currentAncillaryData,
|
||||
bytes memory key,
|
||||
uint256 value
|
||||
) internal pure returns (bytes memory) {
|
||||
bytes memory prefix = constructPrefix(currentAncillaryData, key);
|
||||
return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesUint(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Helper method that returns the left hand side of a "key:value" pair plus the colon ":" and a leading
|
||||
* comma "," if the `currentAncillaryData` is not empty. The return value is intended to be prepended as a prefix to
|
||||
* some utf8 value that is ultimately added to a comma-delimited, key-value dictionary.
|
||||
*/
|
||||
function constructPrefix(bytes memory currentAncillaryData, bytes memory key) internal pure returns (bytes memory) {
|
||||
if (currentAncillaryData.length > 0) {
|
||||
return abi.encodePacked(",", key, ":");
|
||||
} else {
|
||||
return abi.encodePacked(key, ":");
|
||||
}
|
||||
}
|
||||
}
|
||||
763
contracts/implementation/FixedPoint.sol
Normal file
763
contracts/implementation/FixedPoint.sol
Normal file
@@ -0,0 +1,763 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
|
||||
import "@openzeppelin/contracts/utils/math/SignedSafeMath.sol";
|
||||
|
||||
/**
|
||||
* @title Library for fixed point arithmetic on uints
|
||||
*/
|
||||
library FixedPoint {
|
||||
using SafeMath for uint256;
|
||||
using SignedSafeMath for int256;
|
||||
|
||||
// Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5".
|
||||
// For unsigned values:
|
||||
// This can represent a value up to (2^256 - 1)/10^18 = ~10^59. 10^59 will be stored internally as uint256 10^77.
|
||||
uint256 private constant FP_SCALING_FACTOR = 10**18;
|
||||
|
||||
// --------------------------------------- UNSIGNED -----------------------------------------------------------------------------
|
||||
struct Unsigned {
|
||||
uint256 rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Constructs an `Unsigned` from an unscaled uint, e.g., `b=5` gets stored internally as `5*(10**18)`.
|
||||
* @param a uint to convert into a FixedPoint.
|
||||
* @return the converted FixedPoint.
|
||||
*/
|
||||
function fromUnscaledUint(uint256 a) internal pure returns (Unsigned memory) {
|
||||
return Unsigned(a.mul(FP_SCALING_FACTOR));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is equal to `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return True if equal, or False.
|
||||
*/
|
||||
function isEqual(Unsigned memory a, uint256 b) internal pure returns (bool) {
|
||||
return a.rawValue == fromUnscaledUint(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is equal to `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if equal, or False.
|
||||
*/
|
||||
function isEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
|
||||
return a.rawValue == b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a > b`, or False.
|
||||
*/
|
||||
function isGreaterThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
|
||||
return a.rawValue > b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return True if `a > b`, or False.
|
||||
*/
|
||||
function isGreaterThan(Unsigned memory a, uint256 b) internal pure returns (bool) {
|
||||
return a.rawValue > fromUnscaledUint(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than `b`.
|
||||
* @param a a uint256.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a > b`, or False.
|
||||
*/
|
||||
function isGreaterThan(uint256 a, Unsigned memory b) internal pure returns (bool) {
|
||||
return fromUnscaledUint(a).rawValue > b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than or equal to `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a >= b`, or False.
|
||||
*/
|
||||
function isGreaterThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
|
||||
return a.rawValue >= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than or equal to `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return True if `a >= b`, or False.
|
||||
*/
|
||||
function isGreaterThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) {
|
||||
return a.rawValue >= fromUnscaledUint(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than or equal to `b`.
|
||||
* @param a a uint256.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a >= b`, or False.
|
||||
*/
|
||||
function isGreaterThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) {
|
||||
return fromUnscaledUint(a).rawValue >= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a < b`, or False.
|
||||
*/
|
||||
function isLessThan(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
|
||||
return a.rawValue < b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return True if `a < b`, or False.
|
||||
*/
|
||||
function isLessThan(Unsigned memory a, uint256 b) internal pure returns (bool) {
|
||||
return a.rawValue < fromUnscaledUint(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than `b`.
|
||||
* @param a a uint256.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a < b`, or False.
|
||||
*/
|
||||
function isLessThan(uint256 a, Unsigned memory b) internal pure returns (bool) {
|
||||
return fromUnscaledUint(a).rawValue < b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than or equal to `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a <= b`, or False.
|
||||
*/
|
||||
function isLessThanOrEqual(Unsigned memory a, Unsigned memory b) internal pure returns (bool) {
|
||||
return a.rawValue <= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than or equal to `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return True if `a <= b`, or False.
|
||||
*/
|
||||
function isLessThanOrEqual(Unsigned memory a, uint256 b) internal pure returns (bool) {
|
||||
return a.rawValue <= fromUnscaledUint(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than or equal to `b`.
|
||||
* @param a a uint256.
|
||||
* @param b a FixedPoint.
|
||||
* @return True if `a <= b`, or False.
|
||||
*/
|
||||
function isLessThanOrEqual(uint256 a, Unsigned memory b) internal pure returns (bool) {
|
||||
return fromUnscaledUint(a).rawValue <= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice The minimum of `a` and `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return the minimum of `a` and `b`.
|
||||
*/
|
||||
function min(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
return a.rawValue < b.rawValue ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice The maximum of `a` and `b`.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return the maximum of `a` and `b`.
|
||||
*/
|
||||
function max(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
return a.rawValue > b.rawValue ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds two `Unsigned`s, reverting on overflow.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return the sum of `a` and `b`.
|
||||
*/
|
||||
function add(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
return Unsigned(a.rawValue.add(b.rawValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds an `Unsigned` to an unscaled uint, reverting on overflow.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return the sum of `a` and `b`.
|
||||
*/
|
||||
function add(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
|
||||
return add(a, fromUnscaledUint(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Subtracts two `Unsigned`s, reverting on overflow.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return the difference of `a` and `b`.
|
||||
*/
|
||||
function sub(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
return Unsigned(a.rawValue.sub(b.rawValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Subtracts an unscaled uint256 from an `Unsigned`, reverting on overflow.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return the difference of `a` and `b`.
|
||||
*/
|
||||
function sub(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
|
||||
return sub(a, fromUnscaledUint(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Subtracts an `Unsigned` from an unscaled uint256, reverting on overflow.
|
||||
* @param a a uint256.
|
||||
* @param b a FixedPoint.
|
||||
* @return the difference of `a` and `b`.
|
||||
*/
|
||||
function sub(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
return sub(fromUnscaledUint(a), b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies two `Unsigned`s, reverting on overflow.
|
||||
* @dev This will "floor" the product.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mul(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
// There are two caveats with this computation:
|
||||
// 1. Max output for the represented number is ~10^41, otherwise an intermediate value overflows. 10^41 is
|
||||
// stored internally as a uint256 ~10^59.
|
||||
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 1.4 * 2e-18 = 2.8e-18, which
|
||||
// would round to 3, but this computation produces the result 2.
|
||||
// No need to use SafeMath because FP_SCALING_FACTOR != 0.
|
||||
return Unsigned(a.rawValue.mul(b.rawValue) / FP_SCALING_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies an `Unsigned` and an unscaled uint256, reverting on overflow.
|
||||
* @dev This will "floor" the product.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a uint256.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mul(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
|
||||
return Unsigned(a.rawValue.mul(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies two `Unsigned`s and "ceil's" the product, reverting on overflow.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mulCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
uint256 mulRaw = a.rawValue.mul(b.rawValue);
|
||||
uint256 mulFloor = mulRaw / FP_SCALING_FACTOR;
|
||||
uint256 mod = mulRaw.mod(FP_SCALING_FACTOR);
|
||||
if (mod != 0) {
|
||||
return Unsigned(mulFloor.add(1));
|
||||
} else {
|
||||
return Unsigned(mulFloor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies an `Unsigned` and an unscaled uint256 and "ceil's" the product, reverting on overflow.
|
||||
* @param a a FixedPoint.
|
||||
* @param b a FixedPoint.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mulCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
|
||||
// Since b is an uint, there is no risk of truncation and we can just mul it normally
|
||||
return Unsigned(a.rawValue.mul(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Unsigned` by an `Unsigned`, reverting on overflow or division by 0.
|
||||
* @dev This will "floor" the quotient.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b a FixedPoint denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function div(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
// There are two caveats with this computation:
|
||||
// 1. Max value for the number dividend `a` represents is ~10^41, otherwise an intermediate value overflows.
|
||||
// 10^41 is stored internally as a uint256 10^59.
|
||||
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 2 / 3 = 0.6 repeating, which
|
||||
// would round to 0.666666666666666667, but this computation produces the result 0.666666666666666666.
|
||||
return Unsigned(a.rawValue.mul(FP_SCALING_FACTOR).div(b.rawValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Unsigned` by an unscaled uint256, reverting on overflow or division by 0.
|
||||
* @dev This will "floor" the quotient.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b a uint256 denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function div(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
|
||||
return Unsigned(a.rawValue.div(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one unscaled uint256 by an `Unsigned`, reverting on overflow or division by 0.
|
||||
* @dev This will "floor" the quotient.
|
||||
* @param a a uint256 numerator.
|
||||
* @param b a FixedPoint denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function div(uint256 a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
return div(fromUnscaledUint(a), b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Unsigned` by an `Unsigned` and "ceil's" the quotient, reverting on overflow or division by 0.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b a FixedPoint denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function divCeil(Unsigned memory a, Unsigned memory b) internal pure returns (Unsigned memory) {
|
||||
uint256 aScaled = a.rawValue.mul(FP_SCALING_FACTOR);
|
||||
uint256 divFloor = aScaled.div(b.rawValue);
|
||||
uint256 mod = aScaled.mod(b.rawValue);
|
||||
if (mod != 0) {
|
||||
return Unsigned(divFloor.add(1));
|
||||
} else {
|
||||
return Unsigned(divFloor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Unsigned` by an unscaled uint256 and "ceil's" the quotient, reverting on overflow or division by 0.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b a uint256 denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function divCeil(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory) {
|
||||
// Because it is possible that a quotient gets truncated, we can't just call "Unsigned(a.rawValue.div(b))"
|
||||
// similarly to mulCeil with a uint256 as the second parameter. Therefore we need to convert b into an Unsigned.
|
||||
// This creates the possibility of overflow if b is very large.
|
||||
return divCeil(a, fromUnscaledUint(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Raises an `Unsigned` to the power of an unscaled uint256, reverting on overflow. E.g., `b=2` squares `a`.
|
||||
* @dev This will "floor" the result.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b a uint256 denominator.
|
||||
* @return output is `a` to the power of `b`.
|
||||
*/
|
||||
function pow(Unsigned memory a, uint256 b) internal pure returns (Unsigned memory output) {
|
||||
output = fromUnscaledUint(1);
|
||||
for (uint256 i = 0; i < b; i = i.add(1)) {
|
||||
output = mul(output, a);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------- SIGNED -------------------------------------------------------------
|
||||
// Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5".
|
||||
// For signed values:
|
||||
// This can represent a value up (or down) to +-(2^255 - 1)/10^18 = ~10^58. 10^58 will be stored internally as int256 10^76.
|
||||
int256 private constant SFP_SCALING_FACTOR = 10**18;
|
||||
|
||||
struct Signed {
|
||||
int256 rawValue;
|
||||
}
|
||||
|
||||
function fromSigned(Signed memory a) internal pure returns (Unsigned memory) {
|
||||
require(a.rawValue >= 0, "Negative value provided");
|
||||
return Unsigned(uint256(a.rawValue));
|
||||
}
|
||||
|
||||
function fromUnsigned(Unsigned memory a) internal pure returns (Signed memory) {
|
||||
require(a.rawValue <= uint256(type(int256).max), "Unsigned too large");
|
||||
return Signed(int256(a.rawValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Constructs a `Signed` from an unscaled int, e.g., `b=5` gets stored internally as `5*(10**18)`.
|
||||
* @param a int to convert into a FixedPoint.Signed.
|
||||
* @return the converted FixedPoint.Signed.
|
||||
*/
|
||||
function fromUnscaledInt(int256 a) internal pure returns (Signed memory) {
|
||||
return Signed(a.mul(SFP_SCALING_FACTOR));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is equal to `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a int256.
|
||||
* @return True if equal, or False.
|
||||
*/
|
||||
function isEqual(Signed memory a, int256 b) internal pure returns (bool) {
|
||||
return a.rawValue == fromUnscaledInt(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is equal to `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if equal, or False.
|
||||
*/
|
||||
function isEqual(Signed memory a, Signed memory b) internal pure returns (bool) {
|
||||
return a.rawValue == b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a > b`, or False.
|
||||
*/
|
||||
function isGreaterThan(Signed memory a, Signed memory b) internal pure returns (bool) {
|
||||
return a.rawValue > b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b an int256.
|
||||
* @return True if `a > b`, or False.
|
||||
*/
|
||||
function isGreaterThan(Signed memory a, int256 b) internal pure returns (bool) {
|
||||
return a.rawValue > fromUnscaledInt(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than `b`.
|
||||
* @param a an int256.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a > b`, or False.
|
||||
*/
|
||||
function isGreaterThan(int256 a, Signed memory b) internal pure returns (bool) {
|
||||
return fromUnscaledInt(a).rawValue > b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than or equal to `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a >= b`, or False.
|
||||
*/
|
||||
function isGreaterThanOrEqual(Signed memory a, Signed memory b) internal pure returns (bool) {
|
||||
return a.rawValue >= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than or equal to `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b an int256.
|
||||
* @return True if `a >= b`, or False.
|
||||
*/
|
||||
function isGreaterThanOrEqual(Signed memory a, int256 b) internal pure returns (bool) {
|
||||
return a.rawValue >= fromUnscaledInt(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is greater than or equal to `b`.
|
||||
* @param a an int256.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a >= b`, or False.
|
||||
*/
|
||||
function isGreaterThanOrEqual(int256 a, Signed memory b) internal pure returns (bool) {
|
||||
return fromUnscaledInt(a).rawValue >= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a < b`, or False.
|
||||
*/
|
||||
function isLessThan(Signed memory a, Signed memory b) internal pure returns (bool) {
|
||||
return a.rawValue < b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b an int256.
|
||||
* @return True if `a < b`, or False.
|
||||
*/
|
||||
function isLessThan(Signed memory a, int256 b) internal pure returns (bool) {
|
||||
return a.rawValue < fromUnscaledInt(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than `b`.
|
||||
* @param a an int256.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a < b`, or False.
|
||||
*/
|
||||
function isLessThan(int256 a, Signed memory b) internal pure returns (bool) {
|
||||
return fromUnscaledInt(a).rawValue < b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than or equal to `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a <= b`, or False.
|
||||
*/
|
||||
function isLessThanOrEqual(Signed memory a, Signed memory b) internal pure returns (bool) {
|
||||
return a.rawValue <= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than or equal to `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b an int256.
|
||||
* @return True if `a <= b`, or False.
|
||||
*/
|
||||
function isLessThanOrEqual(Signed memory a, int256 b) internal pure returns (bool) {
|
||||
return a.rawValue <= fromUnscaledInt(b).rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whether `a` is less than or equal to `b`.
|
||||
* @param a an int256.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return True if `a <= b`, or False.
|
||||
*/
|
||||
function isLessThanOrEqual(int256 a, Signed memory b) internal pure returns (bool) {
|
||||
return fromUnscaledInt(a).rawValue <= b.rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice The minimum of `a` and `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the minimum of `a` and `b`.
|
||||
*/
|
||||
function min(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
return a.rawValue < b.rawValue ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice The maximum of `a` and `b`.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the maximum of `a` and `b`.
|
||||
*/
|
||||
function max(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
return a.rawValue > b.rawValue ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds two `Signed`s, reverting on overflow.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the sum of `a` and `b`.
|
||||
*/
|
||||
function add(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
return Signed(a.rawValue.add(b.rawValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds an `Signed` to an unscaled int, reverting on overflow.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b an int256.
|
||||
* @return the sum of `a` and `b`.
|
||||
*/
|
||||
function add(Signed memory a, int256 b) internal pure returns (Signed memory) {
|
||||
return add(a, fromUnscaledInt(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Subtracts two `Signed`s, reverting on overflow.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the difference of `a` and `b`.
|
||||
*/
|
||||
function sub(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
return Signed(a.rawValue.sub(b.rawValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Subtracts an unscaled int256 from an `Signed`, reverting on overflow.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b an int256.
|
||||
* @return the difference of `a` and `b`.
|
||||
*/
|
||||
function sub(Signed memory a, int256 b) internal pure returns (Signed memory) {
|
||||
return sub(a, fromUnscaledInt(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Subtracts an `Signed` from an unscaled int256, reverting on overflow.
|
||||
* @param a an int256.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the difference of `a` and `b`.
|
||||
*/
|
||||
function sub(int256 a, Signed memory b) internal pure returns (Signed memory) {
|
||||
return sub(fromUnscaledInt(a), b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies two `Signed`s, reverting on overflow.
|
||||
* @dev This will "floor" the product.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mul(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
// There are two caveats with this computation:
|
||||
// 1. Max output for the represented number is ~10^41, otherwise an intermediate value overflows. 10^41 is
|
||||
// stored internally as an int256 ~10^59.
|
||||
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 1.4 * 2e-18 = 2.8e-18, which
|
||||
// would round to 3, but this computation produces the result 2.
|
||||
// No need to use SafeMath because SFP_SCALING_FACTOR != 0.
|
||||
return Signed(a.rawValue.mul(b.rawValue) / SFP_SCALING_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies an `Signed` and an unscaled int256, reverting on overflow.
|
||||
* @dev This will "floor" the product.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b an int256.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mul(Signed memory a, int256 b) internal pure returns (Signed memory) {
|
||||
return Signed(a.rawValue.mul(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies two `Signed`s and "ceil's" the product, reverting on overflow.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mulAwayFromZero(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
int256 mulRaw = a.rawValue.mul(b.rawValue);
|
||||
int256 mulTowardsZero = mulRaw / SFP_SCALING_FACTOR;
|
||||
// Manual mod because SignedSafeMath doesn't support it.
|
||||
int256 mod = mulRaw % SFP_SCALING_FACTOR;
|
||||
if (mod != 0) {
|
||||
bool isResultPositive = isLessThan(a, 0) == isLessThan(b, 0);
|
||||
int256 valueToAdd = isResultPositive ? int256(1) : int256(-1);
|
||||
return Signed(mulTowardsZero.add(valueToAdd));
|
||||
} else {
|
||||
return Signed(mulTowardsZero);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Multiplies an `Signed` and an unscaled int256 and "ceil's" the product, reverting on overflow.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a FixedPoint.Signed.
|
||||
* @return the product of `a` and `b`.
|
||||
*/
|
||||
function mulAwayFromZero(Signed memory a, int256 b) internal pure returns (Signed memory) {
|
||||
// Since b is an int, there is no risk of truncation and we can just mul it normally
|
||||
return Signed(a.rawValue.mul(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Signed` by an `Signed`, reverting on overflow or division by 0.
|
||||
* @dev This will "floor" the quotient.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b a FixedPoint denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function div(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
// There are two caveats with this computation:
|
||||
// 1. Max value for the number dividend `a` represents is ~10^41, otherwise an intermediate value overflows.
|
||||
// 10^41 is stored internally as an int256 10^59.
|
||||
// 2. Results that can't be represented exactly are truncated not rounded. E.g., 2 / 3 = 0.6 repeating, which
|
||||
// would round to 0.666666666666666667, but this computation produces the result 0.666666666666666666.
|
||||
return Signed(a.rawValue.mul(SFP_SCALING_FACTOR).div(b.rawValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Signed` by an unscaled int256, reverting on overflow or division by 0.
|
||||
* @dev This will "floor" the quotient.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b an int256 denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function div(Signed memory a, int256 b) internal pure returns (Signed memory) {
|
||||
return Signed(a.rawValue.div(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one unscaled int256 by an `Signed`, reverting on overflow or division by 0.
|
||||
* @dev This will "floor" the quotient.
|
||||
* @param a an int256 numerator.
|
||||
* @param b a FixedPoint denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function div(int256 a, Signed memory b) internal pure returns (Signed memory) {
|
||||
return div(fromUnscaledInt(a), b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Signed` by an `Signed` and "ceil's" the quotient, reverting on overflow or division by 0.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b a FixedPoint denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function divAwayFromZero(Signed memory a, Signed memory b) internal pure returns (Signed memory) {
|
||||
int256 aScaled = a.rawValue.mul(SFP_SCALING_FACTOR);
|
||||
int256 divTowardsZero = aScaled.div(b.rawValue);
|
||||
// Manual mod because SignedSafeMath doesn't support it.
|
||||
int256 mod = aScaled % b.rawValue;
|
||||
if (mod != 0) {
|
||||
bool isResultPositive = isLessThan(a, 0) == isLessThan(b, 0);
|
||||
int256 valueToAdd = isResultPositive ? int256(1) : int256(-1);
|
||||
return Signed(divTowardsZero.add(valueToAdd));
|
||||
} else {
|
||||
return Signed(divTowardsZero);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divides one `Signed` by an unscaled int256 and "ceil's" the quotient, reverting on overflow or division by 0.
|
||||
* @param a a FixedPoint numerator.
|
||||
* @param b an int256 denominator.
|
||||
* @return the quotient of `a` divided by `b`.
|
||||
*/
|
||||
function divAwayFromZero(Signed memory a, int256 b) internal pure returns (Signed memory) {
|
||||
// Because it is possible that a quotient gets truncated, we can't just call "Signed(a.rawValue.div(b))"
|
||||
// similarly to mulCeil with an int256 as the second parameter. Therefore we need to convert b into an Signed.
|
||||
// This creates the possibility of overflow if b is very large.
|
||||
return divAwayFromZero(a, fromUnscaledInt(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Raises an `Signed` to the power of an unscaled uint256, reverting on overflow. E.g., `b=2` squares `a`.
|
||||
* @dev This will "floor" the result.
|
||||
* @param a a FixedPoint.Signed.
|
||||
* @param b a uint256 (negative exponents are not allowed).
|
||||
* @return output is `a` to the power of `b`.
|
||||
*/
|
||||
function pow(Signed memory a, uint256 b) internal pure returns (Signed memory output) {
|
||||
output = fromUnscaledInt(1);
|
||||
for (uint256 i = 0; i < b; i = i.add(1)) {
|
||||
output = mul(output, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
contracts/implementation/Lockable.sol
Normal file
77
contracts/implementation/Lockable.sol
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract
|
||||
* is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol
|
||||
* and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol.
|
||||
*/
|
||||
contract Lockable {
|
||||
bool private _notEntered;
|
||||
|
||||
constructor() {
|
||||
// Storing an initial non-zero value makes deployment a bit more expensive, but in exchange the refund on every
|
||||
// call to nonReentrant will be lower in amount. Since refunds are capped to a percentage of the total
|
||||
// transaction's gas, it is best to keep them low in cases like this one, to increase the likelihood of the full
|
||||
// refund coming into effect.
|
||||
_notEntered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Prevents a contract from calling itself, directly or indirectly.
|
||||
* Calling a `nonReentrant` function from another `nonReentrant` function is not supported. It is possible to
|
||||
* prevent this from happening by making the `nonReentrant` function external, and making it call a `private`
|
||||
* function that does the actual state modification.
|
||||
*/
|
||||
modifier nonReentrant() {
|
||||
_preEntranceCheck();
|
||||
_preEntranceSet();
|
||||
_;
|
||||
_postEntranceReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Designed to prevent a view-only method from being re-entered during a call to a `nonReentrant()` state-changing method.
|
||||
*/
|
||||
modifier nonReentrantView() {
|
||||
_preEntranceCheck();
|
||||
_;
|
||||
}
|
||||
|
||||
// Internal methods are used to avoid copying the require statement's bytecode to every `nonReentrant()` method.
|
||||
// On entry into a function, `_preEntranceCheck()` should always be called to check if the function is being
|
||||
// re-entered. Then, if the function modifies state, it should call `_postEntranceSet()`, perform its logic, and
|
||||
// then call `_postEntranceReset()`.
|
||||
// View-only methods can simply call `_preEntranceCheck()` to make sure that it is not being re-entered.
|
||||
function _preEntranceCheck() internal view {
|
||||
// On the first call to nonReentrant, _notEntered will be true
|
||||
require(_notEntered, "ReentrancyGuard: reentrant call");
|
||||
}
|
||||
|
||||
function _preEntranceSet() internal {
|
||||
// Any calls to nonReentrant after this point will fail
|
||||
_notEntered = false;
|
||||
}
|
||||
|
||||
function _postEntranceReset() internal {
|
||||
// By storing the original value once again, a refund is triggered (see
|
||||
// https://eips.ethereum.org/EIPS/eip-2200)
|
||||
_notEntered = true;
|
||||
}
|
||||
|
||||
// These functions are intended to be used by child contracts to temporarily disable and re-enable the guard.
|
||||
// Intended use:
|
||||
// _startReentrantGuardDisabled();
|
||||
// ...
|
||||
// _endReentrantGuardDisabled();
|
||||
//
|
||||
// IMPORTANT: these should NEVER be used in a method that isn't inside a nonReentrant block. Otherwise, it's
|
||||
// possible to permanently lock your contract.
|
||||
function _startReentrantGuardDisabled() internal {
|
||||
_notEntered = true;
|
||||
}
|
||||
|
||||
function _endReentrantGuardDisabled() internal {
|
||||
_notEntered = false;
|
||||
}
|
||||
}
|
||||
52
contracts/implementation/Testable.sol
Normal file
52
contracts/implementation/Testable.sol
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./Timer.sol";
|
||||
|
||||
/**
|
||||
* @title Base class that provides time overrides, but only if being run in test mode.
|
||||
*/
|
||||
abstract contract Testable {
|
||||
// If the contract is being run in production, then `timerAddress` will be the 0x0 address.
|
||||
// Note: this variable should be set on construction and never modified.
|
||||
address public timerAddress;
|
||||
|
||||
/**
|
||||
* @notice Constructs the Testable contract. Called by child contracts.
|
||||
* @param _timerAddress Contract that stores the current time in a testing environment.
|
||||
* Must be set to 0x0 for production environments that use live time.
|
||||
*/
|
||||
constructor(address _timerAddress) {
|
||||
timerAddress = _timerAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Reverts if not running in test mode.
|
||||
*/
|
||||
modifier onlyIfTest {
|
||||
require(timerAddress != address(0x0));
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the current time.
|
||||
* @dev Will revert if not running in test mode.
|
||||
* @param time timestamp to set current Testable time to.
|
||||
*/
|
||||
function setCurrentTime(uint256 time) external onlyIfTest {
|
||||
Timer(timerAddress).setCurrentTime(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Gets the current time. Will return the last time set in `setCurrentTime` if running in test mode.
|
||||
* Otherwise, it will return the block timestamp.
|
||||
* @return uint for the current Testable timestamp.
|
||||
*/
|
||||
function getCurrentTime() public view virtual returns (uint256) {
|
||||
if (timerAddress != address(0x0)) {
|
||||
return Timer(timerAddress).getCurrentTime();
|
||||
} else {
|
||||
return block.timestamp; // solhint-disable-line not-rely-on-time
|
||||
}
|
||||
}
|
||||
}
|
||||
30
contracts/implementation/Timer.sol
Normal file
30
contracts/implementation/Timer.sol
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title Universal store of current contract time for testing environments.
|
||||
*/
|
||||
contract Timer {
|
||||
uint256 private currentTime;
|
||||
|
||||
constructor() {
|
||||
currentTime = block.timestamp; // solhint-disable-line not-rely-on-time
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the current time.
|
||||
* @dev Will revert if not running in test mode.
|
||||
* @param time timestamp to set `currentTime` to.
|
||||
*/
|
||||
function setCurrentTime(uint256 time) external {
|
||||
currentTime = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Gets the currentTime variable set in the Timer.
|
||||
* @return uint256 for the current Testable timestamp.
|
||||
*/
|
||||
function getCurrentTime() public view returns (uint256) {
|
||||
return currentTime;
|
||||
}
|
||||
}
|
||||
12
contracts/interfaces/AddressWhitelistInterface.sol
Normal file
12
contracts/interfaces/AddressWhitelistInterface.sol
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface AddressWhitelistInterface {
|
||||
function addToWhitelist(address newElement) external;
|
||||
|
||||
function removeFromWhitelist(address newElement) external;
|
||||
|
||||
function isOnWhitelist(address newElement) external view returns (bool);
|
||||
|
||||
function getWhitelist() external view returns (address[] memory);
|
||||
}
|
||||
22
contracts/interfaces/FinderInterface.sol
Normal file
22
contracts/interfaces/FinderInterface.sol
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title Provides addresses of the live contracts implementing certain interfaces.
|
||||
* @dev Examples are the Oracle or Store interfaces.
|
||||
*/
|
||||
interface FinderInterface {
|
||||
/**
|
||||
* @notice Updates the address of the contract that implements `interfaceName`.
|
||||
* @param interfaceName bytes32 encoding of the interface name that is either changed or registered.
|
||||
* @param implementationAddress address of the deployed contract that implements the interface.
|
||||
*/
|
||||
function changeImplementationAddress(bytes32 interfaceName, address implementationAddress) external;
|
||||
|
||||
/**
|
||||
* @notice Gets the address of the contract that implements the given `interfaceName`.
|
||||
* @param interfaceName queried interface.
|
||||
* @return implementationAddress address of the deployed contract that implements the interface.
|
||||
*/
|
||||
function getImplementationAddress(bytes32 interfaceName) external view returns (address);
|
||||
}
|
||||
28
contracts/interfaces/IdentifierWhitelistInterface.sol
Normal file
28
contracts/interfaces/IdentifierWhitelistInterface.sol
Normal file
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title Interface for whitelists of supported identifiers that the oracle can provide prices for.
|
||||
*/
|
||||
interface IdentifierWhitelistInterface {
|
||||
/**
|
||||
* @notice Adds the provided identifier as a supported identifier.
|
||||
* @dev Price requests using this identifier will succeed after this call.
|
||||
* @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD.
|
||||
*/
|
||||
function addSupportedIdentifier(bytes32 identifier) external;
|
||||
|
||||
/**
|
||||
* @notice Removes the identifier from the whitelist.
|
||||
* @dev Price requests using this identifier will no longer succeed after this call.
|
||||
* @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD.
|
||||
*/
|
||||
function removeSupportedIdentifier(bytes32 identifier) external;
|
||||
|
||||
/**
|
||||
* @notice Checks whether an identifier is on the whitelist.
|
||||
* @param identifier bytes32 encoding of the string identifier. Eg: BTC/USD.
|
||||
* @return bool if the identifier is supported (or not).
|
||||
*/
|
||||
function isIdentifierSupported(bytes32 identifier) external view returns (bool);
|
||||
}
|
||||
360
contracts/interfaces/OptimisticOracleV2Interface.sol
Normal file
360
contracts/interfaces/OptimisticOracleV2Interface.sol
Normal file
@@ -0,0 +1,360 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "./FinderInterface.sol";
|
||||
|
||||
/**
|
||||
* @title Financial contract facing Oracle interface.
|
||||
* @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface.
|
||||
*/
|
||||
abstract contract OptimisticOracleV2Interface {
|
||||
event RequestPrice(
|
||||
address indexed requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes ancillaryData,
|
||||
address currency,
|
||||
uint256 reward,
|
||||
uint256 finalFee
|
||||
);
|
||||
event ProposePrice(
|
||||
address indexed requester,
|
||||
address indexed proposer,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes ancillaryData,
|
||||
int256 proposedPrice,
|
||||
uint256 expirationTimestamp,
|
||||
address currency
|
||||
);
|
||||
event DisputePrice(
|
||||
address indexed requester,
|
||||
address indexed proposer,
|
||||
address indexed disputer,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes ancillaryData,
|
||||
int256 proposedPrice
|
||||
);
|
||||
event Settle(
|
||||
address indexed requester,
|
||||
address indexed proposer,
|
||||
address indexed disputer,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes ancillaryData,
|
||||
int256 price,
|
||||
uint256 payout
|
||||
);
|
||||
// Struct representing the state of a price request.
|
||||
enum State {
|
||||
Invalid, // Never requested.
|
||||
Requested, // Requested, no other actions taken.
|
||||
Proposed, // Proposed, but not expired or disputed yet.
|
||||
Expired, // Proposed, not disputed, past liveness.
|
||||
Disputed, // Disputed, but no DVM price returned yet.
|
||||
Resolved, // Disputed and DVM price is available.
|
||||
Settled // Final price has been set in the contract (can get here from Expired or Resolved).
|
||||
}
|
||||
|
||||
struct RequestSettings {
|
||||
bool eventBased; // True if the request is set to be event-based.
|
||||
bool refundOnDispute; // True if the requester should be refunded their reward on dispute.
|
||||
bool callbackOnPriceProposed; // True if callbackOnPriceProposed callback is required.
|
||||
bool callbackOnPriceDisputed; // True if callbackOnPriceDisputed callback is required.
|
||||
bool callbackOnPriceSettled; // True if callbackOnPriceSettled callback is required.
|
||||
uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee.
|
||||
uint256 customLiveness; // Custom liveness value set by the requester.
|
||||
}
|
||||
|
||||
// Struct representing a price request.
|
||||
struct Request {
|
||||
address proposer; // Address of the proposer.
|
||||
address disputer; // Address of the disputer.
|
||||
IERC20 currency; // ERC20 token used to pay rewards and fees.
|
||||
bool settled; // True if the request is settled.
|
||||
RequestSettings requestSettings; // Custom settings associated with a request.
|
||||
int256 proposedPrice; // Price that the proposer submitted.
|
||||
int256 resolvedPrice; // Price resolved once the request is settled.
|
||||
uint256 expirationTime; // Time at which the request auto-settles without a dispute.
|
||||
uint256 reward; // Amount of the currency to pay to the proposer on settlement.
|
||||
uint256 finalFee; // Final fee to pay to the Store upon request to the DVM.
|
||||
}
|
||||
|
||||
// This value must be <= the Voting contract's `ancillaryBytesLimit` value otherwise it is possible
|
||||
// that a price can be requested to this contract successfully, but cannot be disputed because the DVM refuses
|
||||
// to accept a price request made with ancillary data length over a certain size.
|
||||
uint256 public constant ancillaryBytesLimit = 8192;
|
||||
|
||||
function defaultLiveness() external view virtual returns (uint256);
|
||||
|
||||
function finder() external view virtual returns (FinderInterface);
|
||||
|
||||
function getCurrentTime() external view virtual returns (uint256);
|
||||
|
||||
// Note: this is required so that typechain generates a return value with named fields.
|
||||
mapping(bytes32 => Request) public requests;
|
||||
|
||||
/**
|
||||
* @notice Requests a new price.
|
||||
* @param identifier price identifier being requested.
|
||||
* @param timestamp timestamp of the price being requested.
|
||||
* @param ancillaryData ancillary data representing additional args being passed with the price request.
|
||||
* @param currency ERC20 token used for payment of rewards and fees. Must be approved for use with the DVM.
|
||||
* @param reward reward offered to a successful proposer. Will be pulled from the caller. Note: this can be 0,
|
||||
* which could make sense if the contract requests and proposes the value in the same call or
|
||||
* provides its own reward system.
|
||||
* @return totalBond default bond (final fee) + final fee that the proposer and disputer will be required to pay.
|
||||
* This can be changed with a subsequent call to setBond().
|
||||
*/
|
||||
function requestPrice(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
IERC20 currency,
|
||||
uint256 reward
|
||||
) external virtual returns (uint256 totalBond);
|
||||
|
||||
/**
|
||||
* @notice Set the proposal bond associated with a price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param bond custom bond amount to set.
|
||||
* @return totalBond new bond + final fee that the proposer and disputer will be required to pay. This can be
|
||||
* changed again with a subsequent call to setBond().
|
||||
*/
|
||||
function setBond(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
uint256 bond
|
||||
) external virtual returns (uint256 totalBond);
|
||||
|
||||
/**
|
||||
* @notice Sets the request to refund the reward if the proposal is disputed. This can help to "hedge" the caller
|
||||
* in the event of a dispute-caused delay. Note: in the event of a dispute, the winner still receives the other's
|
||||
* bond, so there is still profit to be made even if the reward is refunded.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
*/
|
||||
function setRefundOnDispute(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external virtual;
|
||||
|
||||
/**
|
||||
* @notice Sets a custom liveness value for the request. Liveness is the amount of time a proposal must wait before
|
||||
* being auto-resolved.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param customLiveness new custom liveness.
|
||||
*/
|
||||
function setCustomLiveness(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
uint256 customLiveness
|
||||
) external virtual;
|
||||
|
||||
/**
|
||||
* @notice Sets the request to be an "event-based" request.
|
||||
* @dev Calling this method has a few impacts on the request:
|
||||
*
|
||||
* 1. The timestamp at which the request is evaluated is the time of the proposal, not the timestamp associated
|
||||
* with the request.
|
||||
*
|
||||
* 2. The proposer cannot propose the "too early" value (TOO_EARLY_RESPONSE). This is to ensure that a proposer who
|
||||
* prematurely proposes a response loses their bond.
|
||||
*
|
||||
* 3. RefundoOnDispute is automatically set, meaning disputes trigger the reward to be automatically refunded to
|
||||
* the requesting contract.
|
||||
*
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
*/
|
||||
function setEventBased(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external virtual;
|
||||
|
||||
/**
|
||||
* @notice Sets which callbacks should be enabled for the request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param callbackOnPriceProposed whether to enable the callback onPriceProposed.
|
||||
* @param callbackOnPriceDisputed whether to enable the callback onPriceDisputed.
|
||||
* @param callbackOnPriceSettled whether to enable the callback onPriceSettled.
|
||||
*/
|
||||
function setCallbacks(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
bool callbackOnPriceProposed,
|
||||
bool callbackOnPriceDisputed,
|
||||
bool callbackOnPriceSettled
|
||||
) external virtual;
|
||||
|
||||
/**
|
||||
* @notice Proposes a price value on another address' behalf. Note: this address will receive any rewards that come
|
||||
* from this proposal. However, any bonds are pulled from the caller.
|
||||
* @param proposer address to set as the proposer.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param proposedPrice price being proposed.
|
||||
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
|
||||
* the proposer once settled if the proposal is correct.
|
||||
*/
|
||||
function proposePriceFor(
|
||||
address proposer,
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
int256 proposedPrice
|
||||
) public virtual returns (uint256 totalBond);
|
||||
|
||||
/**
|
||||
* @notice Proposes a price value for an existing price request.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @param proposedPrice price being proposed.
|
||||
* @return totalBond the amount that's pulled from the proposer's wallet as a bond. The bond will be returned to
|
||||
* the proposer once settled if the proposal is correct.
|
||||
*/
|
||||
function proposePrice(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData,
|
||||
int256 proposedPrice
|
||||
) external virtual returns (uint256 totalBond);
|
||||
|
||||
/**
|
||||
* @notice Disputes a price request with an active proposal on another address' behalf. Note: this address will
|
||||
* receive any rewards that come from this dispute. However, any bonds are pulled from the caller.
|
||||
* @param disputer address to set as the disputer.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return totalBond the amount that's pulled from the caller's wallet as a bond. The bond will be returned to
|
||||
* the disputer once settled if the dispute was value (the proposal was incorrect).
|
||||
*/
|
||||
function disputePriceFor(
|
||||
address disputer,
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public virtual returns (uint256 totalBond);
|
||||
|
||||
/**
|
||||
* @notice Disputes a price value for an existing price request with an active proposal.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return totalBond the amount that's pulled from the disputer's wallet as a bond. The bond will be returned to
|
||||
* the disputer once settled if the dispute was valid (the proposal was incorrect).
|
||||
*/
|
||||
function disputePrice(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external virtual returns (uint256 totalBond);
|
||||
|
||||
/**
|
||||
* @notice Retrieves a price that was previously requested by a caller. Reverts if the request is not settled
|
||||
* or settleable. Note: this method is not view so that this call may actually settle the price request if it
|
||||
* hasn't been settled.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return resolved price.
|
||||
*/
|
||||
function settleAndGetPrice(
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external virtual returns (int256);
|
||||
|
||||
/**
|
||||
* @notice Attempts to settle an outstanding price request. Will revert if it isn't settleable.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return payout the amount that the "winner" (proposer or disputer) receives on settlement. This amount includes
|
||||
* the returned bonds as well as additional rewards.
|
||||
*/
|
||||
function settle(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) external virtual returns (uint256 payout);
|
||||
|
||||
/**
|
||||
* @notice Gets the current data structure containing all information about a price request.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return the Request data structure.
|
||||
*/
|
||||
function getRequest(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public view virtual returns (Request memory);
|
||||
|
||||
/**
|
||||
* @notice Returns the state of a price request.
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return the State enum value.
|
||||
*/
|
||||
function getState(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public view virtual returns (State);
|
||||
|
||||
/**
|
||||
* @notice Checks if a given request has resolved or been settled (i.e the optimistic oracle has a price).
|
||||
* @param requester sender of the initial price request.
|
||||
* @param identifier price identifier to identify the existing request.
|
||||
* @param timestamp timestamp to identify the existing request.
|
||||
* @param ancillaryData ancillary data of the price being requested.
|
||||
* @return true if price has resolved or settled, false otherwise.
|
||||
*/
|
||||
function hasPrice(
|
||||
address requester,
|
||||
bytes32 identifier,
|
||||
uint256 timestamp,
|
||||
bytes memory ancillaryData
|
||||
) public view virtual returns (bool);
|
||||
|
||||
function stampAncillaryData(bytes memory ancillaryData, address requester)
|
||||
public
|
||||
view
|
||||
virtual
|
||||
returns (bytes memory);
|
||||
}
|
||||
51
contracts/interfaces/OracleAncillaryInterface.sol
Normal file
51
contracts/interfaces/OracleAncillaryInterface.sol
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title Financial contract facing Oracle interface.
|
||||
* @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface.
|
||||
*/
|
||||
abstract contract OracleAncillaryInterface {
|
||||
/**
|
||||
* @notice Enqueues a request (if a request isn't already present) for the given `identifier`, `time` pair.
|
||||
* @dev Time must be in the past and the identifier must be supported.
|
||||
* @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested.
|
||||
* @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller.
|
||||
* @param time unix timestamp for the price request.
|
||||
*/
|
||||
|
||||
function requestPrice(
|
||||
bytes32 identifier,
|
||||
uint256 time,
|
||||
bytes memory ancillaryData
|
||||
) public virtual;
|
||||
|
||||
/**
|
||||
* @notice Whether the price for `identifier` and `time` is available.
|
||||
* @dev Time must be in the past and the identifier must be supported.
|
||||
* @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested.
|
||||
* @param time unix timestamp for the price request.
|
||||
* @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller.
|
||||
* @return bool if the DVM has resolved to a price for the given identifier and timestamp.
|
||||
*/
|
||||
function hasPrice(
|
||||
bytes32 identifier,
|
||||
uint256 time,
|
||||
bytes memory ancillaryData
|
||||
) public view virtual returns (bool);
|
||||
|
||||
/**
|
||||
* @notice Gets the price for `identifier` and `time` if it has already been requested and resolved.
|
||||
* @dev If the price is not available, the method reverts.
|
||||
* @param identifier uniquely identifies the price requested. eg BTC/USD (encoded as bytes32) could be requested.
|
||||
* @param time unix timestamp for the price request.
|
||||
* @param ancillaryData arbitrary data appended to a price request to give the voters more info from the caller.
|
||||
* @return int256 representing the resolved price for the given identifier and timestamp.
|
||||
*/
|
||||
|
||||
function getPrice(
|
||||
bytes32 identifier,
|
||||
uint256 time,
|
||||
bytes memory ancillaryData
|
||||
) public view virtual returns (int256);
|
||||
}
|
||||
46
contracts/interfaces/StoreInterface.sol
Normal file
46
contracts/interfaces/StoreInterface.sol
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "../implementation/FixedPoint.sol";
|
||||
|
||||
/**
|
||||
* @title Interface that allows financial contracts to pay oracle fees for their use of the system.
|
||||
*/
|
||||
interface StoreInterface {
|
||||
/**
|
||||
* @notice Pays Oracle fees in ETH to the store.
|
||||
* @dev To be used by contracts whose margin currency is ETH.
|
||||
*/
|
||||
function payOracleFees() external payable;
|
||||
|
||||
/**
|
||||
* @notice Pays oracle fees in the margin currency, erc20Address, to the store.
|
||||
* @dev To be used if the margin currency is an ERC20 token rather than ETH.
|
||||
* @param erc20Address address of the ERC20 token used to pay the fee.
|
||||
* @param amount number of tokens to transfer. An approval for at least this amount must exist.
|
||||
*/
|
||||
function payOracleFeesErc20(address erc20Address, FixedPoint.Unsigned calldata amount) external;
|
||||
|
||||
/**
|
||||
* @notice Computes the regular oracle fees that a contract should pay for a period.
|
||||
* @param startTime defines the beginning time from which the fee is paid.
|
||||
* @param endTime end time until which the fee is paid.
|
||||
* @param pfc "profit from corruption", or the maximum amount of margin currency that a
|
||||
* token sponsor could extract from the contract through corrupting the price feed in their favor.
|
||||
* @return regularFee amount owed for the duration from start to end time for the given pfc.
|
||||
* @return latePenalty for paying the fee after the deadline.
|
||||
*/
|
||||
function computeRegularFee(
|
||||
uint256 startTime,
|
||||
uint256 endTime,
|
||||
FixedPoint.Unsigned calldata pfc
|
||||
) external view returns (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty);
|
||||
|
||||
/**
|
||||
* @notice Computes the final oracle fees that a contract should pay at settlement.
|
||||
* @param currency token used to pay the final fee.
|
||||
* @return finalFee amount due.
|
||||
*/
|
||||
function computeFinalFee(address currency) external view returns (FixedPoint.Unsigned memory);
|
||||
}
|
||||
Reference in New Issue
Block a user