pragma solidity ^0.6.2;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./Lockable.sol";
/**
* @title A contract to track a whitelist of addresses.
*/
contract AddressWhitelist is 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 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 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 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 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++;
}
}
}
}
pragma solidity ^0.6.0;
/**
* @title Interface that all financial contracts expose to the admin.
*/
interface AdministrateeInterface {
/**
* @notice Initiates the shutdown process, in case of an emergency.
*/
function emergencyShutdown() external;
/**
* @notice A core contract method called independently or as a part of other financial contract transactions.
* @dev It pays fees and moves money between margin accounts to make sure they reflect the NAV of the contract.
*/
function remargin() external;
}
pragma solidity ^0.6.0;
import "../math/Math.sol";
/**
* @dev Collection of functions related to array types.
*/
library Arrays {
/**
* @dev Searches a sorted `array` and returns the first index that contains
* a value greater or equal to `element`. If no such index exists (i.e. all
* values in the array are strictly less than `element`), the array length is
* returned. Time complexity O(log n).
*
* `array` is expected to be sorted in ascending order, and to contain no
* repeated elements.
*/
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
if (array.length == 0) {
return 0;
}
uint256 low = 0;
uint256 high = array.length;
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds down (it does integer division with truncation).
if (array[mid] > element) {
high = mid;
} else {
low = mid + 1;
}
}
// At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && array[low - 1] == element) {
return low - 1;
} else {
return low;
}
}
}
pragma solidity ^0.6.0;
/**
* @title Interface for Balancer.
* @dev This only contains the methods/events that we use in our contracts or offchain infrastructure.
*/
abstract contract Balancer {
function getSpotPriceSansFee(address tokenIn, address tokenOut) external virtual view returns (uint256 spotPrice);
}
pragma solidity ^0.6.0;
import "../interfaces/Balancer.sol";
/**
* @title Balancer Mock
*/
contract BalancerMock is Balancer {
uint256 price = 0;
// these params arent used in the mock, but this is to maintain compatibility with balancer API
function getSpotPriceSansFee(address tokenIn, address tokenOut)
external
virtual
override
view
returns (uint256 spotPrice)
{
return price;
}
// this is not a balancer call, but for testing for changing price.
function setPrice(uint256 newPrice) external {
price = newPrice;
}
}
pragma solidity ^0.6.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";
}
pragma solidity ^0.6.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
contract Context {
// Empty internal constructor, to prevent people from mistakenly deploying
// an instance of this contract, which should be used via inheritance.
constructor () internal { }
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
pragma solidity ^0.6.0;
import "../interfaces/FinderInterface.sol";
import "../../common/implementation/AddressWhitelist.sol";
import "./Registry.sol";
import "./Constants.sol";
/**
* @title Base contract for all financial contract creators
*/
abstract contract ContractCreator {
address internal finderAddress;
constructor(address _finderAddress) public {
finderAddress = _finderAddress;
}
function _requireWhitelistedCollateral(address collateralAddress) internal view {
FinderInterface finder = FinderInterface(finderAddress);
AddressWhitelist collateralWhitelist = AddressWhitelist(
finder.getImplementationAddress(OracleInterfaces.CollateralWhitelist)
);
require(collateralWhitelist.isOnWhitelist(collateralAddress), "Collateral not whitelisted");
}
function _registerContract(address[] memory parties, address contractToRegister) internal {
FinderInterface finder = FinderInterface(finderAddress);
Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry));
registry.registerContract(parties, contractToRegister);
}
}
pragma solidity ^0.6.0;
import "../math/SafeMath.sol";
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
* Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the {SafeMath}
* overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
* directly accessed.
*/
library Counters {
using SafeMath for uint256;
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
// The {SafeMath} overflow check can be skipped here, see the comment at the top
counter._value += 1;
}
function decrement(Counter storage counter) internal {
counter._value = counter._value.sub(1);
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "../common/FeePayer.sol";
import "../../common/implementation/FixedPoint.sol";
import "../../oracle/interfaces/IdentifierWhitelistInterface.sol";
import "../../oracle/interfaces/OracleInterface.sol";
import "../../oracle/interfaces/AdministrateeInterface.sol";
import "../../oracle/implementation/ContractCreator.sol";
/**
* @title Token Deposit Box
* @notice This is a minimal example of a financial template that depends on price requests from the DVM.
* This contract should be thought of as a "Deposit Box" into which the user deposits some ERC20 collateral.
* The main feature of this box is that the user can withdraw their ERC20 corresponding to a desired USD amount.
* When the user wants to make a withdrawal, a price request is enqueued with the UMA DVM.
* For simplicty, the user is constrained to have one outstanding withdrawal request at any given time.
* Regular fees are charged on the collateral in the deposit box throughout the lifetime of the deposit box,
* and final fees are charged on each price request.
*
* This example is intended to accompany a technical tutorial for how to integrate the DVM into a project.
* The main feature this demo serves to showcase is how to build a financial product on-chain that "pulls" price
* requests from the DVM on-demand, which is an implementation of the "priceless" oracle framework.
*
* The typical user flow would be:
* - User sets up a deposit box for the (wETH - USD) price-identifier. The "collateral currency" in this deposit
* box is therefore wETH.
* The user can subsequently make withdrawal requests for USD-denominated amounts of wETH.
* - User deposits 10 wETH into their deposit box.
* - User later requests to withdraw $100 USD of wETH.
* - DepositBox asks DVM for latest wETH/USD exchange rate.
* - DVM resolves the exchange rate at: 1 wETH is worth 200 USD.
* - DepositBox transfers 0.5 wETH to user.
*/
contract DepositBox is FeePayer, AdministrateeInterface, ContractCreator {
using SafeMath for uint256;
using FixedPoint for FixedPoint.Unsigned;
using SafeERC20 for IERC20;
// Represents a single caller's deposit box. All collateral is held by this contract.
struct DepositBoxData {
// Requested amount of collateral, denominated in quote asset of the price identifier.
// Example: If the price identifier is wETH-USD, and the `withdrawalRequestAmount = 100`, then
// this represents a withdrawal request for 100 USD worth of wETH.
FixedPoint.Unsigned withdrawalRequestAmount;
// Timestamp of the latest withdrawal request. A withdrawal request is pending if `requestPassTimestamp != 0`.
uint256 requestPassTimestamp;
// Raw collateral value. This value should never be accessed directly -- always use _getFeeAdjustedCollateral().
// To add or remove collateral, use _addCollateral() and _removeCollateral().
FixedPoint.Unsigned rawCollateral;
}
// Maps addresses to their deposit boxes. Each address can have only one position.
mapping(address => DepositBoxData) private depositBoxes;
// Unique identifier for DVM price feed ticker.
bytes32 private priceIdentifier;
// Similar to the rawCollateral in DepositBoxData, this value should not be used directly.
// _getFeeAdjustedCollateral(), _addCollateral() and _removeCollateral() must be used to access and adjust.
FixedPoint.Unsigned private rawTotalDepositBoxCollateral;
// This blocks every public state-modifying method until it flips to true, via the `initialize()` method.
bool private initialized;
/****************************************
* EVENTS *
****************************************/
event NewDepositBox(address indexed user);
event EndedDepositBox(address indexed user);
event Deposit(address indexed user, uint256 indexed collateralAmount);
event RequestWithdrawal(address indexed user, uint256 indexed collateralAmount, uint256 requestPassTimestamp);
event RequestWithdrawalExecuted(
address indexed user,
uint256 indexed collateralAmount,
uint256 exchangeRate,
uint256 requestPassTimestamp
);
event RequestWithdrawalCanceled(
address indexed user,
uint256 indexed collateralAmount,
uint256 requestPassTimestamp
);
/****************************************
* MODIFIERS *
****************************************/
modifier noPendingWithdrawal(address user) {
_depositBoxHasNoPendingWithdrawal(user);
_;
}
modifier isInitialized() {
_isInitialized();
_;
}
/****************************************
* PUBLIC FUNCTIONS *
****************************************/
/**
* @notice Construct the DepositBox.
* @param _collateralAddress ERC20 token to be deposited.
* @param _finderAddress UMA protocol Finder used to discover other protocol contracts.
* @param _priceIdentifier registered in the DVM, used to price the ERC20 deposited.
* The price identifier consists of a "base" asset and a "quote" asset. The "base" asset corresponds to the collateral ERC20
* currency deposited into this account, and it is denominated in the "quote" asset on withdrawals.
* An example price identifier would be "ETH-USD" which will resolve and return the USD price of ETH.
* @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 _collateralAddress,
address _finderAddress,
bytes32 _priceIdentifier,
address _timerAddress
)
public
ContractCreator(_finderAddress)
FeePayer(_collateralAddress, _finderAddress, _timerAddress)
nonReentrant()
{
require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier), "Unsupported price identifier");
priceIdentifier = _priceIdentifier;
}
/**
* @notice This should be called after construction of the DepositBox and handles registration with the Registry, which is required
* to make price requests in production environments.
* @dev This contract must hold the `ContractCreator` role with the Registry in order to register itself as a financial-template with the DVM.
* Note that `_registerContract` cannot be called from the constructor because this contract first needs to be given the `ContractCreator` role
* in order to register with the `Registry`. But, its address is not known until after deployment.
*/
function initialize() public nonReentrant() {
initialized = true;
_registerContract(new address[](0), address(this));
}
/**
* @notice Transfers `collateralAmount` of `collateralCurrency` into caller's deposit box.
* @dev This contract must be approved to spend at least `collateralAmount` of `collateralCurrency`.
* @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position.
*/
function deposit(FixedPoint.Unsigned memory collateralAmount) public isInitialized() fees() nonReentrant() {
require(collateralAmount.isGreaterThan(0), "Invalid collateral amount");
DepositBoxData storage depositBoxData = depositBoxes[msg.sender];
if (_getFeeAdjustedCollateral(depositBoxData.rawCollateral).isEqual(0)) {
emit NewDepositBox(msg.sender);
}
// Increase the individual deposit box and global collateral balance by collateral amount.
_incrementCollateralBalances(depositBoxData, collateralAmount);
emit Deposit(msg.sender, collateralAmount.rawValue);
// Move collateral currency from sender to contract.
collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue);
}
/**
* @notice Starts a withdrawal request that allows the sponsor to withdraw `denominatedCollateralAmount`
* from their position denominated in the quote asset of the price identifier, following a DVM price resolution.
* @dev The request will be pending for the duration of the DVM vote and can be cancelled at any time.
* Only one withdrawal request can exist for the user.
* @param denominatedCollateralAmount the quote-asset denominated amount of collateral requested to withdraw.
*/
function requestWithdrawal(FixedPoint.Unsigned memory denominatedCollateralAmount)
public
isInitialized()
noPendingWithdrawal(msg.sender)
nonReentrant()
{
DepositBoxData storage depositBoxData = depositBoxes[msg.sender];
require(denominatedCollateralAmount.isGreaterThan(0), "Invalid collateral amount");
// Update the position object for the user.
depositBoxData.withdrawalRequestAmount = denominatedCollateralAmount;
depositBoxData.requestPassTimestamp = getCurrentTime();
emit RequestWithdrawal(msg.sender, denominatedCollateralAmount.rawValue, depositBoxData.requestPassTimestamp);
// Every price request costs a fixed fee. Check that this user has enough deposited to cover the final fee.
FixedPoint.Unsigned memory finalFee = _computeFinalFees();
require(
_getFeeAdjustedCollateral(depositBoxData.rawCollateral).isGreaterThanOrEqual(finalFee),
"Cannot pay final fee"
);
_payFinalFees(address(this), finalFee);
// A price request is sent for the current timestamp.
_requestOraclePrice(depositBoxData.requestPassTimestamp);
}
/**
* @notice After a passed withdrawal request (i.e., by a call to `requestWithdrawal` and subsequent DVM price resolution),
* withdraws `depositBoxData.withdrawalRequestAmount` of collateral currency denominated in the quote asset.
* @dev Might not withdraw the full requested amount in order to account for precision loss or if the full requested
* amount exceeds the collateral in the position (due to paying fees).
* @return amountWithdrawn The actual amount of collateral withdrawn.
*/
function executeWithdrawal()
external
isInitialized()
fees()
nonReentrant()
returns (FixedPoint.Unsigned memory amountWithdrawn)
{
DepositBoxData storage depositBoxData = depositBoxes[msg.sender];
require(
depositBoxData.requestPassTimestamp != 0 && depositBoxData.requestPassTimestamp <= getCurrentTime(),
"Invalid withdraw request"
);
// Get the resolved price or revert.
FixedPoint.Unsigned memory exchangeRate = _getOraclePrice(depositBoxData.requestPassTimestamp);
// Calculate denomated amount of collateral based on resolved exchange rate.
// Example 1: User wants to withdraw $100 of ETH, exchange rate is $200/ETH, therefore user to receive 0.5 ETH.
// Example 2: User wants to withdraw $250 of ETH, exchange rate is $200/ETH, therefore user to receive 1.25 ETH.
FixedPoint.Unsigned memory denominatedAmountToWithdraw = depositBoxData.withdrawalRequestAmount.div(
exchangeRate
);
// If withdrawal request amount is > collateral, then withdraw the full collateral amount and delete the deposit box data.
if (denominatedAmountToWithdraw.isGreaterThan(_getFeeAdjustedCollateral(depositBoxData.rawCollateral))) {
denominatedAmountToWithdraw = _getFeeAdjustedCollateral(depositBoxData.rawCollateral);
// Reset the position state as all the value has been removed after settlement.
emit EndedDepositBox(msg.sender);
}
// Decrease the individual deposit box and global collateral balance.
amountWithdrawn = _decrementCollateralBalances(depositBoxData, denominatedAmountToWithdraw);
emit RequestWithdrawalExecuted(
msg.sender,
amountWithdrawn.rawValue,
exchangeRate.rawValue,
depositBoxData.requestPassTimestamp
);
// Reset withdrawal request by setting withdrawal request timestamp to 0.
_resetWithdrawalRequest(depositBoxData);
// Transfer approved withdrawal amount from the contract to the caller.
collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue);
}
/**
* @notice Cancels a pending withdrawal request.
*/
function cancelWithdrawal() external isInitialized() nonReentrant() {
DepositBoxData storage depositBoxData = depositBoxes[msg.sender];
require(depositBoxData.requestPassTimestamp != 0, "No pending withdrawal");
emit RequestWithdrawalCanceled(
msg.sender,
depositBoxData.withdrawalRequestAmount.rawValue,
depositBoxData.requestPassTimestamp
);
// Reset withdrawal request by setting withdrawal request timestamp to 0.
_resetWithdrawalRequest(depositBoxData);
}
/**
* @notice `emergencyShutdown` and `remargin` are required to be implemented by all financial contracts and exposed to the DVM, but
* because this is a minimal demo they will simply exit silently.
*/
function emergencyShutdown() external override isInitialized() nonReentrant() {
return;
}
/**
* @notice Same comment as `emergencyShutdown`. For the sake of simplicity, this will simply exit silently.
*/
function remargin() external override isInitialized() nonReentrant() {
return;
}
/**
* @notice Accessor method for a user's collateral.
* @dev This is necessary because the struct returned by the depositBoxes() method shows
* rawCollateral, which isn't a user-readable value.
* @param user address whose collateral amount is retrieved.
* @return the fee-adjusted collateral amount in the deposit box (i.e. available for withdrawal).
*/
function getCollateral(address user) external view nonReentrantView() returns (FixedPoint.Unsigned memory) {
return _getFeeAdjustedCollateral(depositBoxes[user].rawCollateral);
}
/**
* @notice Accessor method for the total collateral stored within the entire contract.
* @return the total fee-adjusted collateral amount in the contract (i.e. across all users).
*/
function totalDepositBoxCollateral() external view nonReentrantView() returns (FixedPoint.Unsigned memory) {
return _getFeeAdjustedCollateral(rawTotalDepositBoxCollateral);
}
/****************************************
* INTERNAL FUNCTIONS *
****************************************/
// Requests a price for `priceIdentifier` at `requestedTime` from the Oracle.
function _requestOraclePrice(uint256 requestedTime) internal {
OracleInterface oracle = _getOracle();
oracle.requestPrice(priceIdentifier, requestedTime);
}
// Ensure individual and global consistency when increasing collateral balances. Returns the change to the position.
function _incrementCollateralBalances(
DepositBoxData storage depositBoxData,
FixedPoint.Unsigned memory collateralAmount
) internal returns (FixedPoint.Unsigned memory) {
_addCollateral(depositBoxData.rawCollateral, collateralAmount);
return _addCollateral(rawTotalDepositBoxCollateral, collateralAmount);
}
// Ensure individual and global consistency when decrementing collateral balances. Returns the change to the
// position. We elect to return the amount that the global collateral is decreased by, rather than the individual
// position's collateral, because we need to maintain the invariant that the global collateral is always
// <= the collateral owned by the contract to avoid reverts on withdrawals. The amount returned = amount withdrawn.
function _decrementCollateralBalances(
DepositBoxData storage depositBoxData,
FixedPoint.Unsigned memory collateralAmount
) internal returns (FixedPoint.Unsigned memory) {
_removeCollateral(depositBoxData.rawCollateral, collateralAmount);
return _removeCollateral(rawTotalDepositBoxCollateral, collateralAmount);
}
function _resetWithdrawalRequest(DepositBoxData storage depositBoxData) internal {
depositBoxData.withdrawalRequestAmount = FixedPoint.fromUnscaledUint(0);
depositBoxData.requestPassTimestamp = 0;
}
function _depositBoxHasNoPendingWithdrawal(address user) internal view {
require(depositBoxes[user].requestPassTimestamp == 0, "Pending withdrawal");
}
function _isInitialized() internal view {
require(initialized, "Uninitialized contract");
}
function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) {
return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist));
}
function _getOracle() internal view returns (OracleInterface) {
return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle));
}
// Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request.
function _getOraclePrice(uint256 requestedTime) internal view returns (FixedPoint.Unsigned memory) {
OracleInterface oracle = _getOracle();
require(oracle.hasPrice(priceIdentifier, requestedTime), "Unresolved oracle price");
int256 oraclePrice = oracle.getPrice(priceIdentifier, requestedTime);
// For simplicity we don't want to deal with negative prices.
if (oraclePrice < 0) {
oraclePrice = 0;
}
return FixedPoint.Unsigned(uint256(oraclePrice));
}
// `_pfc()` is inherited from FeePayer and must be implemented to return the available pool of collateral from
// which fees can be charged. For this contract, the available fee pool is simply all of the collateral locked up in the
// contract.
function _pfc() internal virtual override view returns (FixedPoint.Unsigned memory) {
return _getFeeAdjustedCollateral(rawTotalDepositBoxCollateral);
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/MultiRole.sol";
import "../../common/implementation/Withdrawable.sol";
import "../interfaces/VotingInterface.sol";
import "../interfaces/FinderInterface.sol";
import "./Constants.sol";
/**
* @title Proxy to allow voting from another address.
* @dev Allows a UMA token holder to designate another address to vote on their behalf.
* Each voter must deploy their own instance of this contract.
*/
contract DesignatedVoting is Withdrawable {
/****************************************
* INTERNAL VARIABLES AND STORAGE *
****************************************/
enum Roles {
Owner, // Can set the Voter role. Is also permanently permissioned as the minter role.
Voter // Can vote through this contract.
}
// Reference to the UMA Finder contract, allowing Voting upgrades to be performed
// without requiring any calls to this contract.
FinderInterface private finder;
/**
* @notice Construct the DesignatedVoting contract.
* @param finderAddress keeps track of all contracts within the system based on their interfaceName.
* @param ownerAddress address of the owner of the DesignatedVoting contract.
* @param voterAddress address to which the owner has delegated their voting power.
*/
constructor(
address finderAddress,
address ownerAddress,
address voterAddress
) public {
_createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), ownerAddress);
_createExclusiveRole(uint256(Roles.Voter), uint256(Roles.Owner), voterAddress);
_setWithdrawRole(uint256(Roles.Owner));
finder = FinderInterface(finderAddress);
}
/****************************************
* VOTING AND REWARD FUNCTIONALITY *
****************************************/
/**
* @notice Forwards a commit to Voting.
* @param identifier uniquely identifies the feed for this vote. EG BTC/USD price pair.
* @param time specifies the unix timestamp of the price being voted on.
* @param hash the keccak256 hash of the price you want to vote for and a random integer salt value.
*/
function commitVote(
bytes32 identifier,
uint256 time,
bytes32 hash
) external onlyRoleHolder(uint256(Roles.Voter)) {
_getVotingAddress().commitVote(identifier, time, hash);
}
/**
* @notice Forwards a batch commit to Voting.
* @param commits struct to encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`.
*/
function batchCommit(VotingInterface.Commitment[] calldata commits) external onlyRoleHolder(uint256(Roles.Voter)) {
_getVotingAddress().batchCommit(commits);
}
/**
* @notice Forwards a reveal to Voting.
* @param identifier voted on in the commit phase. EG BTC/USD price pair.
* @param time specifies the unix timestamp of the price being voted on.
* @param price used along with the `salt` to produce the `hash` during the commit phase.
* @param salt used along with the `price` to produce the `hash` during the commit phase.
*/
function revealVote(
bytes32 identifier,
uint256 time,
int256 price,
int256 salt
) external onlyRoleHolder(uint256(Roles.Voter)) {
_getVotingAddress().revealVote(identifier, time, price, salt);
}
/**
* @notice Forwards a batch reveal to Voting.
* @param reveals is an array of the Reveal struct which contains an identifier, time, price and salt.
*/
function batchReveal(VotingInterface.Reveal[] calldata reveals) external onlyRoleHolder(uint256(Roles.Voter)) {
_getVotingAddress().batchReveal(reveals);
}
/**
* @notice Forwards a reward retrieval to Voting.
* @dev Rewards are added to the tokens already held by this contract.
* @param roundId defines the round from which voting rewards will be retrieved from.
* @param toRetrieve an array of PendingRequests which rewards are retrieved from.
* @return amount of rewards that the user should receive.
*/
function retrieveRewards(uint256 roundId, VotingInterface.PendingRequest[] memory toRetrieve)
public
onlyRoleHolder(uint256(Roles.Voter))
returns (FixedPoint.Unsigned memory)
{
return _getVotingAddress().retrieveRewards(address(this), roundId, toRetrieve);
}
function _getVotingAddress() private view returns (VotingInterface) {
return VotingInterface(finder.getImplementationAddress(OracleInterfaces.Oracle));
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/Withdrawable.sol";
import "./DesignatedVoting.sol";
/**
* @title Factory to deploy new instances of DesignatedVoting and look up previously deployed instances.
* @dev Allows off-chain infrastructure to look up a hot wallet's deployed DesignatedVoting contract.
*/
contract DesignatedVotingFactory is Withdrawable {
/****************************************
* INTERNAL VARIABLES AND STORAGE *
****************************************/
enum Roles {
Withdrawer // Can withdraw any ETH or ERC20 sent accidentally to this contract.
}
address private finder;
mapping(address => DesignatedVoting) public designatedVotingContracts;
/**
* @notice Construct the DesignatedVotingFactory contract.
* @param finderAddress keeps track of all contracts within the system based on their interfaceName.
*/
constructor(address finderAddress) public {
finder = finderAddress;
_createWithdrawRole(uint256(Roles.Withdrawer), uint256(Roles.Withdrawer), msg.sender);
}
/**
* @notice Deploys a new `DesignatedVoting` contract.
* @param ownerAddress defines who will own the deployed instance of the designatedVoting contract.
* @return designatedVoting a new DesignatedVoting contract.
*/
function newDesignatedVoting(address ownerAddress) external returns (DesignatedVoting) {
require(address(designatedVotingContracts[msg.sender]) == address(0), "Duplicate hot key not permitted");
DesignatedVoting designatedVoting = new DesignatedVoting(finder, ownerAddress, msg.sender);
designatedVotingContracts[msg.sender] = designatedVoting;
return designatedVoting;
}
/**
* @notice Associates a `DesignatedVoting` instance with `msg.sender`.
* @param designatedVotingAddress address to designate voting to.
* @dev This is generally only used if the owner of a `DesignatedVoting` contract changes their `voter`
* address and wants that reflected here.
*/
function setDesignatedVoting(address designatedVotingAddress) external {
require(address(designatedVotingContracts[msg.sender]) == address(0), "Duplicate hot key not permitted");
designatedVotingContracts[msg.sender] = DesignatedVoting(designatedVotingAddress);
}
}
pragma solidity ^0.6.0;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// Check the signature length
if (signature.length != 65) {
revert("ECDSA: invalid signature length");
}
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
revert("ECDSA: invalid signature 's' value");
}
if (v != 27 && v != 28) {
revert("ECDSA: invalid signature 'v' value");
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* replicates the behavior of the
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
* JSON-RPC method.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
pragma solidity ^0.6.0;
import "../../GSN/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../utils/Address.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20MinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
using Address for address;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with
* a default value of 18.
*
* To select a different value for {decimals}, use {_setupDecimals}.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name, string memory symbol) public {
_name = name;
_symbol = symbol;
_decimals = 18;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20};
*
* Requirements:
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
*
* This is internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* WARNING: This function should only be called from the constructor. Most
* applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does.
*/
function _setupDecimals(uint8 decimals_) internal {
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}
pragma solidity ^0.6.0;
import "../../math/SafeMath.sol";
import "../../utils/Arrays.sol";
import "../../utils/Counters.sol";
import "./ERC20.sol";
/**
* @dev This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and
* total supply at the time are recorded for later access.
*
* This can be used to safely create mechanisms based on token balances such as trustless dividends or weighted voting.
* In naive implementations it's possible to perform a "double spend" attack by reusing the same balance from different
* accounts. By using snapshots to calculate dividends or voting power, those attacks no longer apply. It can also be
* used to create an efficient ERC20 forking mechanism.
*
* Snapshots are created by the internal {_snapshot} function, which will emit the {Snapshot} event and return a
* snapshot id. To get the total supply at the time of a snapshot, call the function {totalSupplyAt} with the snapshot
* id. To get the balance of an account at the time of a snapshot, call the {balanceOfAt} function with the snapshot id
* and the account address.
*
* ==== Gas Costs
*
* Snapshots are efficient. Snapshot creation is _O(1)_. Retrieval of balances or total supply from a snapshot is _O(log
* n)_ in the number of snapshots that have been created, although _n_ for a specific account will generally be much
* smaller since identical balances in subsequent snapshots are stored as a single entry.
*
* There is a constant overhead for normal ERC20 transfers due to the additional snapshot bookkeeping. This overhead is
* only significant for the first transfer that immediately follows a snapshot for a particular account. Subsequent
* transfers will have normal cost until the next snapshot, and so on.
*/
abstract contract ERC20Snapshot is ERC20 {
// Inspired by Jordi Baylina's MiniMeToken to record historical balances:
// https://github.com/Giveth/minimd/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol
using SafeMath for uint256;
using Arrays for uint256[];
using Counters for Counters.Counter;
// Snapshotted values have arrays of ids and the value corresponding to that id. These could be an array of a
// Snapshot struct, but that would impede usage of functions that work on an array.
struct Snapshots {
uint256[] ids;
uint256[] values;
}
mapping (address => Snapshots) private _accountBalanceSnapshots;
Snapshots private _totalSupplySnapshots;
// Snapshot ids increase monotonically, with the first value being 1. An id of 0 is invalid.
Counters.Counter private _currentSnapshotId;
/**
* @dev Emitted by {_snapshot} when a snapshot identified by `id` is created.
*/
event Snapshot(uint256 id);
/**
* @dev Creates a new snapshot and returns its snapshot id.
*
* Emits a {Snapshot} event that contains the same id.
*
* {_snapshot} is `internal` and you have to decide how to expose it externally. Its usage may be restricted to a
* set of accounts, for example using {AccessControl}, or it may be open to the public.
*
* [WARNING]
* ====
* While an open way of calling {_snapshot} is required for certain trust minimization mechanisms such as forking,
* you must consider that it can potentially be used by attackers in two ways.
*
* First, it can be used to increase the cost of retrieval of values from snapshots, although it will grow
* logarithmically thus rendering this attack ineffective in the long term. Second, it can be used to target
* specific accounts and increase the cost of ERC20 transfers for them, in the ways specified in the Gas Costs
* section above.
*
* We haven't measured the actual numbers; if this is something you're interested in please reach out to us.
* ====
*/
function _snapshot() internal virtual returns (uint256) {
_currentSnapshotId.increment();
uint256 currentId = _currentSnapshotId.current();
emit Snapshot(currentId);
return currentId;
}
/**
* @dev Retrieves the balance of `account` at the time `snapshotId` was created.
*/
function balanceOfAt(address account, uint256 snapshotId) public view returns (uint256) {
(bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]);
return snapshotted ? value : balanceOf(account);
}
/**
* @dev Retrieves the total supply at the time `snapshotId` was created.
*/
function totalSupplyAt(uint256 snapshotId) public view returns(uint256) {
(bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots);
return snapshotted ? value : totalSupply();
}
// _transfer, _mint and _burn are the only functions where the balances are modified, so it is there that the
// snapshots are updated. Note that the update happens _before_ the balance change, with the pre-modified value.
// The same is true for the total supply and _mint and _burn.
function _transfer(address from, address to, uint256 value) internal virtual override {
_updateAccountSnapshot(from);
_updateAccountSnapshot(to);
super._transfer(from, to, value);
}
function _mint(address account, uint256 value) internal virtual override {
_updateAccountSnapshot(account);
_updateTotalSupplySnapshot();
super._mint(account, value);
}
function _burn(address account, uint256 value) internal virtual override {
_updateAccountSnapshot(account);
_updateTotalSupplySnapshot();
super._burn(account, value);
}
function _valueAt(uint256 snapshotId, Snapshots storage snapshots)
private view returns (bool, uint256)
{
require(snapshotId > 0, "ERC20Snapshot: id is 0");
// solhint-disable-next-line max-line-length
require(snapshotId <= _currentSnapshotId.current(), "ERC20Snapshot: nonexistent id");
// When a valid snapshot is queried, there are three possibilities:
// a) The queried value was not modified after the snapshot was taken. Therefore, a snapshot entry was never
// created for this id, and all stored snapshot ids are smaller than the requested one. The value that corresponds
// to this id is the current one.
// b) The queried value was modified after the snapshot was taken. Therefore, there will be an entry with the
// requested id, and its value is the one to return.
// c) More snapshots were created after the requested one, and the queried value was later modified. There will be
// no entry for the requested id: the value that corresponds to it is that of the smallest snapshot id that is
// larger than the requested one.
//
// In summary, we need to find an element in an array, returning the index of the smallest value that is larger if
// it is not found, unless said value doesn't exist (e.g. when all values are smaller). Arrays.findUpperBound does
// exactly this.
uint256 index = snapshots.ids.findUpperBound(snapshotId);
if (index == snapshots.ids.length) {
return (false, 0);
} else {
return (true, snapshots.values[index]);
}
}
function _updateAccountSnapshot(address account) private {
_updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account));
}
function _updateTotalSupplySnapshot() private {
_updateSnapshot(_totalSupplySnapshots, totalSupply());
}
function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) private {
uint256 currentId = _currentSnapshotId.current();
if (_lastSnapshotId(snapshots.ids) < currentId) {
snapshots.ids.push(currentId);
snapshots.values.push(currentValue);
}
}
function _lastSnapshotId(uint256[] storage ids) private view returns (uint256) {
if (ids.length == 0) {
return 0;
} else {
return ids[ids.length - 1];
}
}
}
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./MultiRole.sol";
import "../interfaces/ExpandedIERC20.sol";
/**
* @title An ERC20 with permissioned burning and minting. The contract deployer will initially
* be the owner who is capable of adding new roles.
*/
contract ExpandedERC20 is ExpandedIERC20, ERC20, MultiRole {
enum Roles {
// Can set the minter and burner.
Owner,
// Addresses that can mint new tokens.
Minter,
// Addresses that can burn tokens that address owns.
Burner
}
/**
* @notice Constructs the ExpandedERC20.
* @param _tokenName The name which describes the new token.
* @param _tokenSymbol The ticker abbreviation of the name. Ideally < 5 chars.
* @param _tokenDecimals The number of decimals to define token precision.
*/
constructor(
string memory _tokenName,
string memory _tokenSymbol,
uint8 _tokenDecimals
) public ERC20(_tokenName, _tokenSymbol) {
_setupDecimals(_tokenDecimals);
_createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender);
_createSharedRole(uint256(Roles.Minter), uint256(Roles.Owner), new address[](0));
_createSharedRole(uint256(Roles.Burner), uint256(Roles.Owner), new address[](0));
}
/**
* @dev Mints `value` tokens to `recipient`, returning true on success.
* @param recipient address to mint to.
* @param value amount of tokens to mint.
* @return True if the mint succeeded, or False.
*/
function mint(address recipient, uint256 value)
external
override
onlyRoleHolder(uint256(Roles.Minter))
returns (bool)
{
_mint(recipient, value);
return true;
}
/**
* @dev Burns `value` tokens owned by `msg.sender`.
* @param value amount of tokens to burn.
*/
function burn(uint256 value) external override onlyRoleHolder(uint256(Roles.Burner)) {
_burn(msg.sender, value);
}
}
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title ERC20 interface that includes burn and mint methods.
*/
abstract contract ExpandedIERC20 is IERC20 {
/**
* @notice Burns a specific amount of the caller's tokens.
* @dev Only burns the caller's tokens, so it is safe to leave this method permissionless.
*/
function burn(uint256 value) external virtual;
/**
* @notice Mints tokens and adds them to the balance of the `to` address.
* @dev This method should be permissioned to only allow designated parties to mint tokens.
*/
function mint(address to, uint256 value) external virtual returns (bool);
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "./Liquidatable.sol";
/**
* @title Expiring Multi Party.
* @notice Convenient wrapper for Liquidatable.
*/
contract ExpiringMultiParty is Liquidatable {
/**
* @notice Constructs the ExpiringMultiParty contract.
* @param params struct to define input parameters for construction of Liquidatable. Some params
* are fed directly into the PricelessPositionManager's constructor within the inheritance tree.
*/
constructor(ConstructorParams memory params)
public
Liquidatable(params)
// Note: since there is no logic here, there is no need to add a re-entrancy guard.
{
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../oracle/implementation/ContractCreator.sol";
import "../../common/implementation/Testable.sol";
import "../../common/implementation/AddressWhitelist.sol";
import "../../common/implementation/Lockable.sol";
import "./ExpiringMultiPartyLib.sol";
/**
* @title Expiring Multi Party Contract creator.
* @notice Factory contract to create and register new instances of expiring multiparty contracts.
* Responsible for constraining the parameters used to construct a new EMP. This creator contains a number of constraints
* that are applied to newly created expiring multi party contract. These constraints can evolve over time and are
* initially constrained to conservative values in this first iteration. Technically there is nothing in the
* ExpiringMultiParty contract requiring these constraints. However, because `createExpiringMultiParty()` is intended
* to be the only way to create valid financial contracts that are registered with the DVM (via _registerContract),
we can enforce deployment configurations here.
*/
contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable {
using FixedPoint for FixedPoint.Unsigned;
/****************************************
* EMP CREATOR DATA STRUCTURES *
****************************************/
struct Params {
uint256 expirationTimestamp;
address collateralAddress;
bytes32 priceFeedIdentifier;
string syntheticName;
string syntheticSymbol;
FixedPoint.Unsigned collateralRequirement;
FixedPoint.Unsigned disputeBondPct;
FixedPoint.Unsigned sponsorDisputeRewardPct;
FixedPoint.Unsigned disputerDisputeRewardPct;
FixedPoint.Unsigned minSponsorTokens;
uint256 withdrawalLiveness;
uint256 liquidationLiveness;
address excessTokenBeneficiary;
}
// - Address of TokenFactory to pass into newly constructed ExpiringMultiParty contracts
address public tokenFactoryAddress;
event CreatedExpiringMultiParty(address indexed expiringMultiPartyAddress, address indexed deployerAddress);
/**
* @notice Constructs the ExpiringMultiPartyCreator contract.
* @param _finderAddress UMA protocol Finder used to discover other protocol contracts.
* @param _tokenFactoryAddress ERC20 token factory used to deploy synthetic token instances.
* @param _timerAddress Contract that stores the current time in a testing environment.
*/
constructor(
address _finderAddress,
address _tokenFactoryAddress,
address _timerAddress
) public ContractCreator(_finderAddress) Testable(_timerAddress) nonReentrant() {
tokenFactoryAddress = _tokenFactoryAddress;
}
/**
* @notice Creates an instance of expiring multi party and registers it within the registry.
* @param params is a `ConstructorParams` object from ExpiringMultiParty.
* @return address of the deployed ExpiringMultiParty contract.
*/
function createExpiringMultiParty(Params memory params) public nonReentrant() returns (address) {
address derivative = ExpiringMultiPartyLib.deploy(_convertParams(params));
_registerContract(new address[](0), address(derivative));
emit CreatedExpiringMultiParty(address(derivative), msg.sender);
return address(derivative);
}
/****************************************
* PRIVATE FUNCTIONS *
****************************************/
// Converts createExpiringMultiParty params to ExpiringMultiParty constructor params.
function _convertParams(Params memory params)
private
view
returns (ExpiringMultiParty.ConstructorParams memory constructorParams)
{
// Known from creator deployment.
constructorParams.finderAddress = finderAddress;
constructorParams.tokenFactoryAddress = tokenFactoryAddress;
constructorParams.timerAddress = timerAddress;
// Enforce configuration constraints.
require(bytes(params.syntheticName).length != 0, "Missing synthetic name");
require(bytes(params.syntheticSymbol).length != 0, "Missing synthetic symbol");
require(params.withdrawalLiveness != 0, "Withdrawal liveness cannot be 0");
require(params.liquidationLiveness != 0, "Liquidation liveness cannot be 0");
require(params.excessTokenBeneficiary != address(0), "Token Beneficiary cannot be 0x0");
require(params.expirationTimestamp > now, "Invalid expiration time");
_requireWhitelistedCollateral(params.collateralAddress);
// We don't want EMP deployers to be able to intentionally or unintentionally set
// liveness periods that could induce arithmetic overflow, but we also don't want
// to be opinionated about what livenesses are "correct", so we will somewhat
// arbitrarily set the liveness upper bound to 100 years (5200 weeks). In practice, liveness
// periods even greater than a few days would make the EMP unusable for most users.
require(params.withdrawalLiveness < 5200 weeks, "Withdrawal liveness too large");
require(params.liquidationLiveness < 5200 weeks, "Liquidation liveness too large");
// Input from function call.
constructorParams.expirationTimestamp = params.expirationTimestamp;
constructorParams.collateralAddress = params.collateralAddress;
constructorParams.priceFeedIdentifier = params.priceFeedIdentifier;
constructorParams.syntheticName = params.syntheticName;
constructorParams.syntheticSymbol = params.syntheticSymbol;
constructorParams.collateralRequirement = params.collateralRequirement;
constructorParams.disputeBondPct = params.disputeBondPct;
constructorParams.sponsorDisputeRewardPct = params.sponsorDisputeRewardPct;
constructorParams.disputerDisputeRewardPct = params.disputerDisputeRewardPct;
constructorParams.minSponsorTokens = params.minSponsorTokens;
constructorParams.withdrawalLiveness = params.withdrawalLiveness;
constructorParams.liquidationLiveness = params.liquidationLiveness;
constructorParams.excessTokenBeneficiary = params.excessTokenBeneficiary;
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "./ExpiringMultiParty.sol";
/**
* @title Provides convenient Expiring Multi Party contract utilities.
* @dev Using this library to deploy EMP's allows calling contracts to avoid importing the full EMP bytecode.
*/
library ExpiringMultiPartyLib {
/**
* @notice Returns address of new EMP deployed with given `params` configuration.
* @dev Caller will need to register new EMP with the Registry to begin requesting prices. Caller is also
* responsible for enforcing constraints on `params`.
* @param params is a `ConstructorParams` object from ExpiringMultiParty.
* @return address of the deployed ExpiringMultiParty contract
*/
function deploy(ExpiringMultiParty.ConstructorParams memory params) public returns (address) {
ExpiringMultiParty derivative = new ExpiringMultiParty(params);
return address(derivative);
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "../../common/implementation/Lockable.sol";
import "../../common/implementation/FixedPoint.sol";
import "../../common/implementation/Testable.sol";
import "../../oracle/interfaces/StoreInterface.sol";
import "../../oracle/interfaces/FinderInterface.sol";
import "../../oracle/implementation/Constants.sol";
/**
* @title FeePayer contract.
* @notice Provides fee payment functionality for the ExpiringMultiParty contract.
* contract is abstract as each derived contract that inherits `FeePayer` must implement `pfc()`.
*/
abstract contract FeePayer is Testable, Lockable {
using SafeMath for uint256;
using FixedPoint for FixedPoint.Unsigned;
using SafeERC20 for IERC20;
/****************************************
* FEE PAYER DATA STRUCTURES *
****************************************/
// The collateral currency used to back the positions in this contract.
IERC20 public collateralCurrency;
// Finder contract used to look up addresses for UMA system contracts.
FinderInterface public finder;
// Tracks the last block time when the fees were paid.
uint256 private lastPaymentTime;
// Tracks the cumulative fees that have been paid by the contract for use by derived contracts.
// The multiplier starts at 1, and is updated by computing cumulativeFeeMultiplier * (1 - effectiveFee).
// Put another way, the cumulativeFeeMultiplier is (1 - effectiveFee1) * (1 - effectiveFee2) ...
// For example:
// The cumulativeFeeMultiplier should start at 1.
// If a 1% fee is charged, the multiplier should update to .99.
// If another 1% fee is charged, the multiplier should be 0.99^2 (0.9801).
FixedPoint.Unsigned public cumulativeFeeMultiplier;
/****************************************
* EVENTS *
****************************************/
event RegularFeesPaid(uint256 indexed regularFee, uint256 indexed lateFee);
event FinalFeesPaid(uint256 indexed amount);
/****************************************
* MODIFIERS *
****************************************/
// modifier that calls payRegularFees().
modifier fees {
payRegularFees();
_;
}
/**
* @notice Constructs the FeePayer contract. Called by child contracts.
* @param _collateralAddress ERC20 token that is used as the underlying collateral for the synthetic.
* @param _finderAddress UMA protocol Finder used to discover other protocol 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 _collateralAddress,
address _finderAddress,
address _timerAddress
) public Testable(_timerAddress) nonReentrant() {
collateralCurrency = IERC20(_collateralAddress);
finder = FinderInterface(_finderAddress);
lastPaymentTime = getCurrentTime();
cumulativeFeeMultiplier = FixedPoint.fromUnscaledUint(1);
}
/****************************************
* FEE PAYMENT FUNCTIONS *
****************************************/
/**
* @notice Pays UMA DVM regular fees (as a % of the collateral pool) to the Store contract.
* @dev These must be paid periodically for the life of the contract. If the contract has not paid its regular fee
* in a week or more then a late penalty is applied which is sent to the caller. If the amount of
* fees owed are greater than the pfc, then this will pay as much as possible from the available collateral.
* An event is only fired if the fees charged are greater than 0.
* @return totalPaid Amount of collateral that the contract paid (sum of the amount paid to the Store and caller).
* This returns 0 and exit early if there is no pfc, fees were already paid during the current block, or the fee rate is 0.
*/
function payRegularFees() public nonReentrant() returns (FixedPoint.Unsigned memory totalPaid) {
StoreInterface store = _getStore();
uint256 time = getCurrentTime();
FixedPoint.Unsigned memory collateralPool = _pfc();
// Exit early if there is no collateral from which to pay fees.
if (collateralPool.isEqual(0)) {
return totalPaid;
}
// Exit early if fees were already paid during this block.
if (lastPaymentTime == time) {
return totalPaid;
}
(FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty) = store.computeRegularFee(
lastPaymentTime,
time,
collateralPool
);
lastPaymentTime = time;
totalPaid = regularFee.add(latePenalty);
if (totalPaid.isEqual(0)) {
return totalPaid;
}
// If the effective fees paid as a % of the pfc is > 100%, then we need to reduce it and make the contract pay
// as much of the fee that it can (up to 100% of its pfc). We'll reduce the late penalty first and then the
// regular fee, which has the effect of paying the store first, followed by the caller if there is any fee remaining.
if (totalPaid.isGreaterThan(collateralPool)) {
FixedPoint.Unsigned memory deficit = totalPaid.sub(collateralPool);
FixedPoint.Unsigned memory latePenaltyReduction = FixedPoint.min(latePenalty, deficit);
latePenalty = latePenalty.sub(latePenaltyReduction);
deficit = deficit.sub(latePenaltyReduction);
regularFee = regularFee.sub(FixedPoint.min(regularFee, deficit));
totalPaid = collateralPool;
}
emit RegularFeesPaid(regularFee.rawValue, latePenalty.rawValue);
_adjustCumulativeFeeMultiplier(totalPaid, collateralPool);
if (regularFee.isGreaterThan(0)) {
collateralCurrency.safeIncreaseAllowance(address(store), regularFee.rawValue);
store.payOracleFeesErc20(address(collateralCurrency), regularFee);
}
if (latePenalty.isGreaterThan(0)) {
collateralCurrency.safeTransfer(msg.sender, latePenalty.rawValue);
}
return totalPaid;
}
/**
* @notice Gets the current profit from corruption for this contract in terms of the collateral currency.
* @dev This is equivalent to the collateral pool available from which to pay fees. Therefore, derived contracts are
* expected to implement this so that pay-fee methods can correctly compute the owed fees as a % of PfC.
* @return pfc value for equal to the current profit from corruption denominated in collateral currency.
*/
function pfc() public view nonReentrantView() returns (FixedPoint.Unsigned memory) {
return _pfc();
}
/****************************************
* INTERNAL FUNCTIONS *
****************************************/
// Pays UMA Oracle final fees of `amount` in `collateralCurrency` to the Store contract. Final fee is a flat fee
// charged for each price request. If payer is the contract, adjusts internal bookkeeping variables. If payer is not
// the contract, pulls in `amount` of collateral currency.
function _payFinalFees(address payer, FixedPoint.Unsigned memory amount) internal {
if (amount.isEqual(0)) {
return;
}
if (payer != address(this)) {
// If the payer is not the contract pull the collateral from the payer.
collateralCurrency.safeTransferFrom(payer, address(this), amount.rawValue);
} else {
// If the payer is the contract, adjust the cumulativeFeeMultiplier to compensate.
FixedPoint.Unsigned memory collateralPool = _pfc();
// The final fee must be < available collateral or the fee will be larger than 100%.
require(collateralPool.isGreaterThan(amount), "Final fee is more than PfC");
_adjustCumulativeFeeMultiplier(amount, collateralPool);
}
emit FinalFeesPaid(amount.rawValue);
StoreInterface store = _getStore();
collateralCurrency.safeIncreaseAllowance(address(store), amount.rawValue);
store.payOracleFeesErc20(address(collateralCurrency), amount);
}
function _pfc() internal virtual view returns (FixedPoint.Unsigned memory);
function _getStore() internal view returns (StoreInterface) {
return StoreInterface(finder.getImplementationAddress(OracleInterfaces.Store));
}
function _computeFinalFees() internal view returns (FixedPoint.Unsigned memory finalFees) {
StoreInterface store = _getStore();
return store.computeFinalFee(address(collateralCurrency));
}
// Returns the user's collateral minus any fees that have been subtracted since it was originally
// deposited into the contract. Note: if the contract has paid fees since it was deployed, the raw
// value should be larger than the returned value.
function _getFeeAdjustedCollateral(FixedPoint.Unsigned memory rawCollateral)
internal
view
returns (FixedPoint.Unsigned memory collateral)
{
return rawCollateral.mul(cumulativeFeeMultiplier);
}
// Converts a user-readable collateral value into a raw value that accounts for already-assessed fees. If any fees
// have been taken from this contract in the past, then the raw value will be larger than the user-readable value.
function _convertToRawCollateral(FixedPoint.Unsigned memory collateral)
internal
view
returns (FixedPoint.Unsigned memory rawCollateral)
{
return collateral.div(cumulativeFeeMultiplier);
}
// Decrease rawCollateral by a fee-adjusted collateralToRemove amount. Fee adjustment scales up collateralToRemove
// by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore
// rawCollateral is decreased by less than expected. Because this method is usually called in conjunction with an
// actual removal of collateral from this contract, return the fee-adjusted amount that the rawCollateral is
// decreased by so that the caller can minimize error between collateral removed and rawCollateral debited.
function _removeCollateral(FixedPoint.Unsigned storage rawCollateral, FixedPoint.Unsigned memory collateralToRemove)
internal
returns (FixedPoint.Unsigned memory removedCollateral)
{
FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral);
FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToRemove);
rawCollateral.rawValue = rawCollateral.sub(adjustedCollateral).rawValue;
removedCollateral = initialBalance.sub(_getFeeAdjustedCollateral(rawCollateral));
}
// Increase rawCollateral by a fee-adjusted collateralToAdd amount. Fee adjustment scales up collateralToAdd
// by dividing it by cumulativeFeeMultiplier. There is potential for this quotient to be floored, therefore
// rawCollateral is increased by less than expected. Because this method is usually called in conjunction with an
// actual addition of collateral to this contract, return the fee-adjusted amount that the rawCollateral is
// increased by so that the caller can minimize error between collateral added and rawCollateral credited.
// NOTE: This return value exists only for the sake of symmetry with _removeCollateral. We don't actually use it
// because we are OK if more collateral is stored in the contract than is represented by rawTotalPositionCollateral.
function _addCollateral(FixedPoint.Unsigned storage rawCollateral, FixedPoint.Unsigned memory collateralToAdd)
internal
returns (FixedPoint.Unsigned memory addedCollateral)
{
FixedPoint.Unsigned memory initialBalance = _getFeeAdjustedCollateral(rawCollateral);
FixedPoint.Unsigned memory adjustedCollateral = _convertToRawCollateral(collateralToAdd);
rawCollateral.rawValue = rawCollateral.add(adjustedCollateral).rawValue;
addedCollateral = _getFeeAdjustedCollateral(rawCollateral).sub(initialBalance);
}
// Scale the cumulativeFeeMultiplier by the ratio of fees paid to the current available collateral.
function _adjustCumulativeFeeMultiplier(FixedPoint.Unsigned memory amount, FixedPoint.Unsigned memory currentPfc)
internal
{
FixedPoint.Unsigned memory effectiveFee = amount.divCeil(currentPfc);
cumulativeFeeMultiplier = cumulativeFeeMultiplier.mul(FixedPoint.fromUnscaledUint(1).sub(effectiveFee));
}
}
pragma solidity ^0.6.0;
import "../interfaces/AdministrateeInterface.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Admin for financial contracts in the UMA system.
* @dev Allows appropriately permissioned admin roles to interact with financial contracts.
*/
contract FinancialContractsAdmin is Ownable {
/**
* @notice Calls emergency shutdown on the provided financial contract.
* @param financialContract address of the FinancialContract to be shut down.
*/
function callEmergencyShutdown(address financialContract) external onlyOwner {
AdministrateeInterface administratee = AdministrateeInterface(financialContract);
administratee.emergencyShutdown();
}
/**
* @notice Calls remargin on the provided financial contract.
* @param financialContract address of the FinancialContract to be remargined.
*/
function callRemargin(address financialContract) external onlyOwner {
AdministrateeInterface administratee = AdministrateeInterface(financialContract);
administratee.remargin();
}
}
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/FinderInterface.sol";
/**
* @title Provides addresses of the live contracts implementing certain interfaces.
* @dev Examples of interfaces with implementations that Finder locates are the Oracle and Store interfaces.
*/
contract Finder is FinderInterface, Ownable {
mapping(bytes32 => address) public interfacesImplemented;
event InterfaceImplementationChanged(bytes32 indexed interfaceName, address indexed newImplementationAddress);
/**
* @notice Updates the address of the contract that implements `interfaceName`.
* @param interfaceName bytes32 of the interface name that is either changed or registered.
* @param implementationAddress address of the implementation contract.
*/
function changeImplementationAddress(bytes32 interfaceName, address implementationAddress)
external
override
onlyOwner
{
interfacesImplemented[interfaceName] = implementationAddress;
emit InterfaceImplementationChanged(interfaceName, implementationAddress);
}
/**
* @notice Gets the address of the contract that implements the given `interfaceName`.
* @param interfaceName queried interface.
* @return implementationAddress address of the defined interface.
*/
function getImplementationAddress(bytes32 interfaceName) external override view returns (address) {
address implementationAddress = interfacesImplemented[interfaceName];
require(implementationAddress != address(0x0), "Implementation not found");
return implementationAddress;
}
}
pragma solidity ^0.6.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);
}
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/math/SafeMath.sol";
/**
* @title Library for fixed point arithmetic on uints
*/
library FixedPoint {
using SafeMath for uint256;
// Supports 18 decimals. E.g., 1e18 represents "1", 5e17 represents "0.5".
// 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;
struct Unsigned {
uint256 rawValue;
}
/**
* @notice Constructs an `Unsigned` from an unscaled uint, e.g., `b=5` gets stored internally as `5**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 int, 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);
}
}
}
pragma solidity ^0.6.0;
import "../implementation/FixedPoint.sol";
// Wraps the FixedPoint library for testing purposes.
contract FixedPointTest {
using FixedPoint for FixedPoint.Unsigned;
using FixedPoint for uint256;
using SafeMath for uint256;
function wrapFromUnscaledUint(uint256 a) external pure returns (uint256) {
return FixedPoint.fromUnscaledUint(a).rawValue;
}
function wrapIsEqual(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isEqual(FixedPoint.Unsigned(b));
}
function wrapMixedIsEqual(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isEqual(b);
}
function wrapIsGreaterThan(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isGreaterThan(FixedPoint.Unsigned(b));
}
function wrapIsGreaterThanOrEqual(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isGreaterThanOrEqual(FixedPoint.Unsigned(b));
}
function wrapMixedIsGreaterThan(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isGreaterThan(b);
}
function wrapMixedIsGreaterThanOrEqual(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isGreaterThanOrEqual(b);
}
function wrapMixedIsGreaterThanOpposite(uint256 a, uint256 b) external pure returns (bool) {
return a.isGreaterThan(FixedPoint.Unsigned(b));
}
function wrapMixedIsGreaterThanOrEqualOpposite(uint256 a, uint256 b) external pure returns (bool) {
return a.isGreaterThanOrEqual(FixedPoint.Unsigned(b));
}
function wrapIsLessThan(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isLessThan(FixedPoint.Unsigned(b));
}
function wrapIsLessThanOrEqual(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isLessThanOrEqual(FixedPoint.Unsigned(b));
}
function wrapMixedIsLessThan(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isLessThan(b);
}
function wrapMixedIsLessThanOrEqual(uint256 a, uint256 b) external pure returns (bool) {
return FixedPoint.Unsigned(a).isLessThanOrEqual(b);
}
function wrapMixedIsLessThanOpposite(uint256 a, uint256 b) external pure returns (bool) {
return a.isLessThan(FixedPoint.Unsigned(b));
}
function wrapMixedIsLessThanOrEqualOpposite(uint256 a, uint256 b) external pure returns (bool) {
return a.isLessThanOrEqual(FixedPoint.Unsigned(b));
}
function wrapMin(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).min(FixedPoint.Unsigned(b)).rawValue;
}
function wrapMax(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).max(FixedPoint.Unsigned(b)).rawValue;
}
function wrapAdd(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).add(FixedPoint.Unsigned(b)).rawValue;
}
// The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly.
function wrapMixedAdd(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).add(b).rawValue;
}
function wrapSub(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).sub(FixedPoint.Unsigned(b)).rawValue;
}
// The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly.
function wrapMixedSub(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).sub(b).rawValue;
}
// The second uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly.
function wrapMixedSubOpposite(uint256 a, uint256 b) external pure returns (uint256) {
return a.sub(FixedPoint.Unsigned(b)).rawValue;
}
function wrapMul(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).mul(FixedPoint.Unsigned(b)).rawValue;
}
function wrapMulCeil(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).mulCeil(FixedPoint.Unsigned(b)).rawValue;
}
// The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly.
function wrapMixedMul(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).mul(b).rawValue;
}
function wrapMixedMulCeil(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).mulCeil(b).rawValue;
}
function wrapDiv(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).div(FixedPoint.Unsigned(b)).rawValue;
}
function wrapDivCeil(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).divCeil(FixedPoint.Unsigned(b)).rawValue;
}
// The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly.
function wrapMixedDiv(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).div(b).rawValue;
}
function wrapMixedDivCeil(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).divCeil(b).rawValue;
}
// The second uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly.
function wrapMixedDivOpposite(uint256 a, uint256 b) external pure returns (uint256) {
return a.div(FixedPoint.Unsigned(b)).rawValue;
}
// The first uint256 is interpreted with a scaling factor and is converted to an `Unsigned` directly.
function wrapPow(uint256 a, uint256 b) external pure returns (uint256) {
return FixedPoint.Unsigned(a).pow(b).rawValue;
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/MultiRole.sol";
import "../../common/implementation/FixedPoint.sol";
import "../../common/implementation/Testable.sol";
import "../interfaces/FinderInterface.sol";
import "../interfaces/IdentifierWhitelistInterface.sol";
import "../interfaces/OracleInterface.sol";
import "./Constants.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Address.sol";
/**
* @title Takes proposals for certain governance actions and allows UMA token holders to vote on them.
*/
contract Governor is MultiRole, Testable {
using SafeMath for uint256;
using Address for address;
/****************************************
* INTERNAL VARIABLES AND STORAGE *
****************************************/
enum Roles {
Owner, // Can set the proposer.
Proposer // Address that can make proposals.
}
struct Transaction {
address to;
uint256 value;
bytes data;
}
struct Proposal {
Transaction[] transactions;
uint256 requestTime;
}
FinderInterface private finder;
Proposal[] public proposals;
/****************************************
* EVENTS *
****************************************/
// Emitted when a new proposal is created.
event NewProposal(uint256 indexed id, Transaction[] transactions);
// Emitted when an existing proposal is executed.
event ProposalExecuted(uint256 indexed id, uint256 transactionIndex);
/**
* @notice Construct the Governor contract.
* @param _finderAddress keeps track of all contracts within the system based on their interfaceName.
* @param _startingId the initial proposal id that the contract will begin incrementing from.
* @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 _finderAddress,
uint256 _startingId,
address _timerAddress
) public Testable(_timerAddress) {
finder = FinderInterface(_finderAddress);
_createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender);
_createExclusiveRole(uint256(Roles.Proposer), uint256(Roles.Owner), msg.sender);
// Ensure the startingId is not set unreasonably high to avoid it being set such that new proposals overwrite
// other storage slots in the contract.
uint256 maxStartingId = 10**18;
require(_startingId <= maxStartingId, "Cannot set startingId larger than 10^18");
// This just sets the initial length of the array to the startingId since modifying length directly has been
// disallowed in solidity 0.6.
assembly {
sstore(proposals_slot, _startingId)
}
}
/****************************************
* PROPOSAL ACTIONS *
****************************************/
/**
* @notice Proposes a new governance action. Can only be called by the holder of the Proposer role.
* @param transactions list of transactions that are being proposed.
* @dev You can create the data portion of each transaction by doing the following:
* ```
* const truffleContractInstance = await TruffleContract.deployed()
* const data = truffleContractInstance.methods.methodToCall(arg1, arg2).encodeABI()
* ```
* Note: this method must be public because of a solidity limitation that
* disallows structs arrays to be passed to external functions.
*/
function propose(Transaction[] memory transactions) public onlyRoleHolder(uint256(Roles.Proposer)) {
uint256 id = proposals.length;
uint256 time = getCurrentTime();
// Note: doing all of this array manipulation manually is necessary because directly setting an array of
// structs in storage to an an array of structs in memory is currently not implemented in solidity :/.
// Add a zero-initialized element to the proposals array.
proposals.push();
// Initialize the new proposal.
Proposal storage proposal = proposals[id];
proposal.requestTime = time;
// Initialize the transaction array.
for (uint256 i = 0; i < transactions.length; i++) {
require(transactions[i].to != address(0), "The `to` address cannot be 0x0");
// If the transaction has any data with it the recipient must be a contract, not an EOA.
if (transactions[i].data.length > 0) {
require(transactions[i].to.isContract(), "EOA can't accept tx with data");
}
proposal.transactions.push(transactions[i]);
}
bytes32 identifier = _constructIdentifier(id);
// Request a vote on this proposal in the DVM.
OracleInterface oracle = _getOracle();
IdentifierWhitelistInterface supportedIdentifiers = _getIdentifierWhitelist();
supportedIdentifiers.addSupportedIdentifier(identifier);
oracle.requestPrice(identifier, time);
supportedIdentifiers.removeSupportedIdentifier(identifier);
emit NewProposal(id, transactions);
}
/**
* @notice Executes a proposed governance action that has been approved by voters.
* @dev This can be called by any address. Caller is expected to send enough ETH to execute payable transactions.
* @param id unique id for the executed proposal.
* @param transactionIndex unique transaction index for the executed proposal.
*/
function executeProposal(uint256 id, uint256 transactionIndex) external payable {
Proposal storage proposal = proposals[id];
int256 price = _getOracle().getPrice(_constructIdentifier(id), proposal.requestTime);
Transaction memory transaction = proposal.transactions[transactionIndex];
require(
transactionIndex == 0 || proposal.transactions[transactionIndex.sub(1)].to == address(0),
"Previous tx not yet executed"
);
require(transaction.to != address(0), "Tx already executed");
require(price != 0, "Proposal was rejected");
require(msg.value == transaction.value, "Must send exact amount of ETH");
// Delete the transaction before execution to avoid any potential re-entrancy issues.
delete proposal.transactions[transactionIndex];
require(_executeCall(transaction.to, transaction.value, transaction.data), "Tx execution failed");
emit ProposalExecuted(id, transactionIndex);
}
/****************************************
* GOVERNOR STATE GETTERS *
****************************************/
/**
* @notice Gets the total number of proposals (includes executed and non-executed).
* @return uint256 representing the current number of proposals.
*/
function numProposals() external view returns (uint256) {
return proposals.length;
}
/**
* @notice Gets the proposal data for a particular id.
* @dev after a proposal is executed, its data will be zeroed out, except for the request time.
* @param id uniquely identify the identity of the proposal.
* @return proposal struct containing transactions[] and requestTime.
*/
function getProposal(uint256 id) external view returns (Proposal memory) {
return proposals[id];
}
/****************************************
* PRIVATE GETTERS AND FUNCTIONS *
****************************************/
function _executeCall(
address to,
uint256 value,
bytes memory data
) private returns (bool) {
// Mostly copied from:
// solhint-disable-next-line max-line-length
// https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31
// solhint-disable-next-line no-inline-assembly
bool success;
assembly {
let inputData := add(data, 0x20)
let inputDataSize := mload(data)
success := call(gas(), to, value, inputData, inputDataSize, 0, 0)
}
return success;
}
function _getOracle() private view returns (OracleInterface) {
return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle));
}
function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) {
return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist));
}
// Returns a UTF-8 identifier representing a particular admin proposal.
// The identifier is of the form "Admin n", where n is the proposal id provided.
function _constructIdentifier(uint256 id) internal pure returns (bytes32) {
bytes32 bytesId = _uintToUtf8(id);
return _addPrefix(bytesId, "Admin ", 6);
}
// This method converts the integer `v` into a base-10, UTF-8 representation stored in a `bytes32` type.
// If the input cannot be represented by 32 base-10 digits, it returns only the highest 32 digits.
// This method is based off of this code: https://ethereum.stackexchange.com/a/6613/47801.
function _uintToUtf8(uint256 v) internal pure returns (bytes32) {
bytes32 ret;
if (v == 0) {
// Handle 0 case explicitly.
ret = "0";
} else {
// Constants.
uint256 bitsPerByte = 8;
uint256 base = 10; // Note: the output should be base-10. The below implementation will not work for bases > 10.
uint256 utf8NumberOffset = 48;
while (v > 0) {
// Downshift the entire bytes32 to allow the new digit to be added at the "front" of the bytes32, which
// translates to the beginning of the UTF-8 representation.
ret = ret >> bitsPerByte;
// Separate the last digit that remains in v by modding by the base of desired output representation.
uint256 leastSignificantDigit = v % base;
// Digits 0-9 are represented by 48-57 in UTF-8, so an offset must be added to create the character.
bytes32 utf8Digit = bytes32(leastSignificantDigit + utf8NumberOffset);
// The top byte of ret has already been cleared to make room for the new digit.
// Upshift by 31 bytes to put it in position, and OR it with ret to leave the other characters untouched.
ret |= utf8Digit << (31 * bitsPerByte);
// Divide v by the base to remove the digit that was just added.
v /= base;
}
}
return ret;
}
// This method takes two UTF-8 strings represented as bytes32 and outputs one as a prefixed by the other.
// `input` is the UTF-8 that should have the prefix prepended.
// `prefix` is the UTF-8 that should be prepended onto input.
// `prefixLength` is number of UTF-8 characters represented by `prefix`.
// Notes:
// 1. If the resulting UTF-8 is larger than 32 characters, then only the first 32 characters will be represented
// by the bytes32 output.
// 2. If `prefix` has more characters than `prefixLength`, the function will produce an invalid result.
function _addPrefix(
bytes32 input,
bytes32 prefix,
uint256 prefixLength
) internal pure returns (bytes32) {
// Downshift `input` to open space at the "front" of the bytes32
bytes32 shiftedInput = input >> (prefixLength * 8);
return shiftedInput | prefix;
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../Governor.sol";
// GovernorTest exposes internal methods in the Governor for testing.
contract GovernorTest is Governor {
constructor(address _timerAddress) public Governor(address(0), 0, _timerAddress) {}
function addPrefix(
bytes32 input,
bytes32 prefix,
uint256 prefixLength
) external pure returns (bytes32) {
return _addPrefix(input, prefix, prefixLength);
}
function uintToUtf8(uint256 v) external pure returns (bytes32 ret) {
return _uintToUtf8(v);
}
function constructIdentifier(uint256 id) external pure returns (bytes32 identifier) {
return _constructIdentifier(id);
}
}
pragma solidity ^0.6.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
pragma solidity ^0.6.0;
import "../interfaces/IdentifierWhitelistInterface.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Stores a whitelist of supported identifiers that the oracle can provide prices for.
*/
contract IdentifierWhitelist is IdentifierWhitelistInterface, Ownable {
/****************************************
* INTERNAL VARIABLES AND STORAGE *
****************************************/
mapping(bytes32 => bool) private supportedIdentifiers;
/****************************************
* EVENTS *
****************************************/
event SupportedIdentifierAdded(bytes32 indexed identifier);
event SupportedIdentifierRemoved(bytes32 indexed identifier);
/****************************************
* ADMIN STATE MODIFYING FUNCTIONS *
****************************************/
/**
* @notice Adds the provided identifier as a supported identifier.
* @dev Price requests using this identifier will succeed after this call.
* @param identifier unique UTF-8 representation for the feed being added. Eg: BTC/USD.
*/
function addSupportedIdentifier(bytes32 identifier) external override onlyOwner {
if (!supportedIdentifiers[identifier]) {
supportedIdentifiers[identifier] = true;
emit SupportedIdentifierAdded(identifier);
}
}
/**
* @notice Removes the identifier from the whitelist.
* @dev Price requests using this identifier will no longer succeed after this call.
* @param identifier unique UTF-8 representation for the feed being removed. Eg: BTC/USD.
*/
function removeSupportedIdentifier(bytes32 identifier) external override onlyOwner {
if (supportedIdentifiers[identifier]) {
supportedIdentifiers[identifier] = false;
emit SupportedIdentifierRemoved(identifier);
}
}
/****************************************
* WHITELIST GETTERS FUNCTIONS *
****************************************/
/**
* @notice Checks whether an identifier is on the whitelist.
* @param identifier unique UTF-8 representation for the feed being queried. Eg: BTC/USD.
* @return bool if the identifier is supported (or not).
*/
function isIdentifierSupported(bytes32 identifier) external override view returns (bool) {
return supportedIdentifiers[identifier];
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
/**
* @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);
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "./PricelessPositionManager.sol";
import "../../common/implementation/FixedPoint.sol";
/**
* @title Liquidatable
* @notice Adds logic to a position-managing contract that enables callers to liquidate an undercollateralized position.
* @dev The liquidation has a liveness period before expiring successfully, during which someone can "dispute" the
* liquidation, which sends a price request to the relevant Oracle to settle the final collateralization ratio based on
* a DVM price. The contract enforces dispute rewards in order to incentivize disputers to correctly dispute false
* liquidations and compensate position sponsors who had their position incorrectly liquidated. Importantly, a
* prospective disputer must deposit a dispute bond that they can lose in the case of an unsuccessful dispute.
*/
contract Liquidatable is PricelessPositionManager {
using FixedPoint for FixedPoint.Unsigned;
using SafeMath for uint256;
using SafeERC20 for IERC20;
/****************************************
* LIQUIDATION DATA STRUCTURES *
****************************************/
// Because of the check in withdrawable(), the order of these enum values should not change.
enum Status { Uninitialized, PreDispute, PendingDispute, DisputeSucceeded, DisputeFailed }
struct LiquidationData {
// Following variables set upon creation of liquidation:
address sponsor; // Address of the liquidated position's sponsor
address liquidator; // Address who created this liquidation
Status state; // Liquidated (and expired or not), Pending a Dispute, or Dispute has resolved
uint256 liquidationTime; // Time when liquidation is initiated, needed to get price from Oracle
// Following variables determined by the position that is being liquidated:
FixedPoint.Unsigned tokensOutstanding; // Synthetic tokens required to be burned by liquidator to initiate dispute
FixedPoint.Unsigned lockedCollateral; // Collateral locked by contract and released upon expiry or post-dispute
// Amount of collateral being liquidated, which could be different from
// lockedCollateral if there were pending withdrawals at the time of liquidation
FixedPoint.Unsigned liquidatedCollateral;
// Unit value (starts at 1) that is used to track the fees per unit of collateral over the course of the liquidation.
FixedPoint.Unsigned rawUnitCollateral;
// Following variable set upon initiation of a dispute:
address disputer; // Person who is disputing a liquidation
// Following variable set upon a resolution of a dispute:
FixedPoint.Unsigned settlementPrice; // Final price as determined by an Oracle following a dispute
FixedPoint.Unsigned finalFee;
}
// Define the contract's constructor parameters as a struct to enable more variables to be specified.
// This is required to enable more params, over and above Solidity's limits.
struct ConstructorParams {
// Params for PricelessPositionManager only.
uint256 expirationTimestamp;
uint256 withdrawalLiveness;
address collateralAddress;
address finderAddress;
address tokenFactoryAddress;
address timerAddress;
address excessTokenBeneficiary;
bytes32 priceFeedIdentifier;
string syntheticName;
string syntheticSymbol;
FixedPoint.Unsigned minSponsorTokens;
// Params specifically for Liquidatable.
uint256 liquidationLiveness;
FixedPoint.Unsigned collateralRequirement;
FixedPoint.Unsigned disputeBondPct;
FixedPoint.Unsigned sponsorDisputeRewardPct;
FixedPoint.Unsigned disputerDisputeRewardPct;
}
// Liquidations are unique by ID per sponsor
mapping(address => LiquidationData[]) public liquidations;
// Total collateral in liquidation.
FixedPoint.Unsigned public rawLiquidationCollateral;
// Immutable contract parameters:
// Amount of time for pending liquidation before expiry.
// !!Note: The lower the liquidation liveness value, the more risk incurred by sponsors.
// Extremely low liveness values increase the chance that opportunistic invalid liquidations
// expire without dispute, thereby decreasing the usability for sponsors and increasing the risk
// for the contract as a whole. An insolvent contract is extremely risky for any sponsor or synthetic
// token holder for the contract.
uint256 public liquidationLiveness;
// Required collateral:TRV ratio for a position to be considered sufficiently collateralized.
FixedPoint.Unsigned public collateralRequirement;
// Percent of a Liquidation/Position's lockedCollateral to be deposited by a potential disputer
// Represented as a multiplier, for example 1.5e18 = "150%" and 0.05e18 = "5%"
FixedPoint.Unsigned public disputeBondPct;
// Percent of oraclePrice paid to sponsor in the Disputed state (i.e. following a successful dispute)
// Represented as a multiplier, see above.
FixedPoint.Unsigned public sponsorDisputeRewardPct;
// Percent of oraclePrice paid to disputer in the Disputed state (i.e. following a successful dispute)
// Represented as a multiplier, see above.
FixedPoint.Unsigned public disputerDisputeRewardPct;
/****************************************
* EVENTS *
****************************************/
event LiquidationCreated(
address indexed sponsor,
address indexed liquidator,
uint256 indexed liquidationId,
uint256 tokensOutstanding,
uint256 lockedCollateral,
uint256 liquidatedCollateral,
uint256 liquidationTime
);
event LiquidationDisputed(
address indexed sponsor,
address indexed liquidator,
address indexed disputer,
uint256 liquidationId,
uint256 disputeBondAmount
);
event DisputeSettled(
address indexed caller,
address indexed sponsor,
address indexed liquidator,
address disputer,
uint256 liquidationId,
bool disputeSucceeded
);
event LiquidationWithdrawn(
address indexed caller,
uint256 withdrawalAmount,
Status indexed liquidationStatus,
uint256 settlementPrice
);
/****************************************
* MODIFIERS *
****************************************/
modifier disputable(uint256 liquidationId, address sponsor) {
_disputable(liquidationId, sponsor);
_;
}
modifier withdrawable(uint256 liquidationId, address sponsor) {
_withdrawable(liquidationId, sponsor);
_;
}
/**
* @notice Constructs the liquidatable contract.
* @param params struct to define input parameters for construction of Liquidatable. Some params
* are fed directly into the PricelessPositionManager's constructor within the inheritance tree.
*/
constructor(ConstructorParams memory params)
public
PricelessPositionManager(
params.expirationTimestamp,
params.withdrawalLiveness,
params.collateralAddress,
params.finderAddress,
params.priceFeedIdentifier,
params.syntheticName,
params.syntheticSymbol,
params.tokenFactoryAddress,
params.minSponsorTokens,
params.timerAddress,
params.excessTokenBeneficiary
)
nonReentrant()
{
require(params.collateralRequirement.isGreaterThan(1), "CR is more than 100%");
require(
params.sponsorDisputeRewardPct.add(params.disputerDisputeRewardPct).isLessThan(1),
"Rewards are more than 100%"
);
// Set liquidatable specific variables.
liquidationLiveness = params.liquidationLiveness;
collateralRequirement = params.collateralRequirement;
disputeBondPct = params.disputeBondPct;
sponsorDisputeRewardPct = params.sponsorDisputeRewardPct;
disputerDisputeRewardPct = params.disputerDisputeRewardPct;
}
/****************************************
* LIQUIDATION FUNCTIONS *
****************************************/
/**
* @notice Liquidates the sponsor's position if the caller has enough
* synthetic tokens to retire the position's outstanding tokens. Liquidations above
* a minimum size also reset an ongoing "slow withdrawal"'s liveness.
* @dev This method generates an ID that will uniquely identify liquidation for the sponsor. This contract must be
* approved to spend at least `tokensLiquidated` of `tokenCurrency` and at least `finalFeeBond` of `collateralCurrency`.
* @param sponsor address of the sponsor to liquidate.
* @param minCollateralPerToken abort the liquidation if the position's collateral per token is below this value.
* @param maxCollateralPerToken abort the liquidation if the position's collateral per token exceeds this value.
* @param maxTokensToLiquidate max number of tokens to liquidate.
* @param deadline abort the liquidation if the transaction is mined after this timestamp.
* @return liquidationId ID of the newly created liquidation.
* @return tokensLiquidated amount of synthetic tokens removed and liquidated from the `sponsor`'s position.
* @return finalFeeBond amount of collateral to be posted by liquidator and returned if not disputed successfully.
*/
function createLiquidation(
address sponsor,
FixedPoint.Unsigned calldata minCollateralPerToken,
FixedPoint.Unsigned calldata maxCollateralPerToken,
FixedPoint.Unsigned calldata maxTokensToLiquidate,
uint256 deadline
)
external
fees()
onlyPreExpiration()
nonReentrant()
returns (
uint256 liquidationId,
FixedPoint.Unsigned memory tokensLiquidated,
FixedPoint.Unsigned memory finalFeeBond
)
{
// Check that this transaction was mined pre-deadline.
require(getCurrentTime() <= deadline, "Mined after deadline");
// Retrieve Position data for sponsor
PositionData storage positionToLiquidate = _getPositionData(sponsor);
tokensLiquidated = FixedPoint.min(maxTokensToLiquidate, positionToLiquidate.tokensOutstanding);
// Starting values for the Position being liquidated. If withdrawal request amount is > position's collateral,
// then set this to 0, otherwise set it to (startCollateral - withdrawal request amount).
FixedPoint.Unsigned memory startCollateral = _getFeeAdjustedCollateral(positionToLiquidate.rawCollateral);
FixedPoint.Unsigned memory startCollateralNetOfWithdrawal = FixedPoint.fromUnscaledUint(0);
if (positionToLiquidate.withdrawalRequestAmount.isLessThanOrEqual(startCollateral)) {
startCollateralNetOfWithdrawal = startCollateral.sub(positionToLiquidate.withdrawalRequestAmount);
}
// Scoping to get rid of a stack too deep error.
{
FixedPoint.Unsigned memory startTokens = positionToLiquidate.tokensOutstanding;
// The Position's collateralization ratio must be between [minCollateralPerToken, maxCollateralPerToken].
// maxCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens.
require(
maxCollateralPerToken.mul(startTokens).isGreaterThanOrEqual(startCollateralNetOfWithdrawal),
"CR is more than max liq. price"
);
// minCollateralPerToken >= startCollateralNetOfWithdrawal / startTokens.
require(
minCollateralPerToken.mul(startTokens).isLessThanOrEqual(startCollateralNetOfWithdrawal),
"CR is less than min liq. price"
);
}
// Compute final fee at time of liquidation.
finalFeeBond = _computeFinalFees();
// These will be populated within the scope below.
FixedPoint.Unsigned memory lockedCollateral;
FixedPoint.Unsigned memory liquidatedCollateral;
// Scoping to get rid of a stack too deep error.
{
FixedPoint.Unsigned memory ratio = tokensLiquidated.div(positionToLiquidate.tokensOutstanding);
// The actual amount of collateral that gets moved to the liquidation.
lockedCollateral = startCollateral.mul(ratio);
// For purposes of disputes, it's actually this liquidatedCollateral value that's used. This value is net of
// withdrawal requests.
liquidatedCollateral = startCollateralNetOfWithdrawal.mul(ratio);
// Part of the withdrawal request is also removed. Ideally:
// liquidatedCollateral + withdrawalAmountToRemove = lockedCollateral.
FixedPoint.Unsigned memory withdrawalAmountToRemove = positionToLiquidate.withdrawalRequestAmount.mul(
ratio
);
_reduceSponsorPosition(sponsor, tokensLiquidated, lockedCollateral, withdrawalAmountToRemove);
}
// Add to the global liquidation collateral count.
_addCollateral(rawLiquidationCollateral, lockedCollateral.add(finalFeeBond));
// Construct liquidation object.
// Note: All dispute-related values are zeroed out until a dispute occurs. liquidationId is the index of the new
// LiquidationData that is pushed into the array, which is equal to the current length of the array pre-push.
liquidationId = liquidations[sponsor].length;
liquidations[sponsor].push(
LiquidationData({
sponsor: sponsor,
liquidator: msg.sender,
state: Status.PreDispute,
liquidationTime: getCurrentTime(),
tokensOutstanding: tokensLiquidated,
lockedCollateral: lockedCollateral,
liquidatedCollateral: liquidatedCollateral,
rawUnitCollateral: _convertToRawCollateral(FixedPoint.fromUnscaledUint(1)),
disputer: address(0),
settlementPrice: FixedPoint.fromUnscaledUint(0),
finalFee: finalFeeBond
})
);
// If this liquidation is a subsequent liquidation on the position, and the liquidation size is larger than
// some "griefing threshold", then re-set the liveness. This enables a liquidation against a withdraw request to be
// "dragged out" if the position is very large and liquidators need time to gather funds. The griefing threshold
// is enforced so that liquidations for trivially small # of tokens cannot drag out an honest sponsor's slow withdrawal.
// We arbitrarily set the "griefing threshold" to `minSponsorTokens` because it is the only parameter
// denominated in token currency units and we can avoid adding another parameter.
FixedPoint.Unsigned memory griefingThreshold = minSponsorTokens;
if (
positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // The position is undergoing a slow withdrawal.
positionToLiquidate.withdrawalRequestPassTimestamp > getCurrentTime() && // The slow withdrawal has not yet expired.
tokensLiquidated.isGreaterThanOrEqual(griefingThreshold) // The liquidated token count is above a "griefing threshold".
) {
positionToLiquidate.withdrawalRequestPassTimestamp = getCurrentTime().add(withdrawalLiveness);
}
emit LiquidationCreated(
sponsor,
msg.sender,
liquidationId,
tokensLiquidated.rawValue,
lockedCollateral.rawValue,
liquidatedCollateral.rawValue,
getCurrentTime()
);
// Destroy tokens
tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensLiquidated.rawValue);
tokenCurrency.burn(tokensLiquidated.rawValue);
// Pull final fee from liquidator.
collateralCurrency.safeTransferFrom(msg.sender, address(this), finalFeeBond.rawValue);
}
/**
* @notice Disputes a liquidation, if the caller has enough collateral to post a dispute bond
* and pay a fixed final fee charged on each price request.
* @dev Can only dispute a liquidation before the liquidation expires and if there are no other pending disputes.
* This contract must be approved to spend at least the dispute bond amount of `collateralCurrency`. This dispute
* bond amount is calculated from `disputeBondPct` times the collateral in the liquidation.
* @param liquidationId of the disputed liquidation.
* @param sponsor the address of the sponsor whose liquidation is being disputed.
* @return totalPaid amount of collateral charged to disputer (i.e. final fee bond + dispute bond).
*/
function dispute(uint256 liquidationId, address sponsor)
external
disputable(liquidationId, sponsor)
fees()
nonReentrant()
returns (FixedPoint.Unsigned memory totalPaid)
{
LiquidationData storage disputedLiquidation = _getLiquidationData(sponsor, liquidationId);
// Multiply by the unit collateral so the dispute bond is a percentage of the locked collateral after fees.
FixedPoint.Unsigned memory disputeBondAmount = disputedLiquidation.lockedCollateral.mul(disputeBondPct).mul(
_getFeeAdjustedCollateral(disputedLiquidation.rawUnitCollateral)
);
_addCollateral(rawLiquidationCollateral, disputeBondAmount);
// Request a price from DVM. Liquidation is pending dispute until DVM returns a price.
disputedLiquidation.state = Status.PendingDispute;
disputedLiquidation.disputer = msg.sender;
// Enqueue a request with the DVM.
_requestOraclePrice(disputedLiquidation.liquidationTime);
emit LiquidationDisputed(
sponsor,
disputedLiquidation.liquidator,
msg.sender,
liquidationId,
disputeBondAmount.rawValue
);
totalPaid = disputeBondAmount.add(disputedLiquidation.finalFee);
// Pay the final fee for requesting price from the DVM.
_payFinalFees(msg.sender, disputedLiquidation.finalFee);
// Transfer the dispute bond amount from the caller to this contract.
collateralCurrency.safeTransferFrom(msg.sender, address(this), disputeBondAmount.rawValue);
}
/**
* @notice After a dispute has settled or after a non-disputed liquidation has expired,
* the sponsor, liquidator, and/or disputer can call this method to receive payments.
* @dev If the dispute SUCCEEDED: the sponsor, liquidator, and disputer are eligible for payment.
* If the dispute FAILED: only the liquidator can receive payment.
* Once all collateral is withdrawn, delete the liquidation data.
* @param liquidationId uniquely identifies the sponsor's liquidation.
* @param sponsor address of the sponsor associated with the liquidation.
* @return amountWithdrawn the total amount of underlying returned from the liquidation.
*/
function withdrawLiquidation(uint256 liquidationId, address sponsor)
public
withdrawable(liquidationId, sponsor)
fees()
nonReentrant()
returns (FixedPoint.Unsigned memory amountWithdrawn)
{
LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId);
require(
(msg.sender == liquidation.disputer) ||
(msg.sender == liquidation.liquidator) ||
(msg.sender == liquidation.sponsor),
"Caller cannot withdraw rewards"
);
// Settles the liquidation if necessary. This call will revert if the price has not resolved yet.
_settle(liquidationId, sponsor);
// Calculate rewards as a function of the TRV.
// Note: all payouts are scaled by the unit collateral value so all payouts are charged the fees pro rata.
FixedPoint.Unsigned memory feeAttenuation = _getFeeAdjustedCollateral(liquidation.rawUnitCollateral);
FixedPoint.Unsigned memory settlementPrice = liquidation.settlementPrice;
FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul(settlementPrice).mul(
feeAttenuation
);
FixedPoint.Unsigned memory collateral = liquidation.lockedCollateral.mul(feeAttenuation);
FixedPoint.Unsigned memory disputerDisputeReward = disputerDisputeRewardPct.mul(tokenRedemptionValue);
FixedPoint.Unsigned memory sponsorDisputeReward = sponsorDisputeRewardPct.mul(tokenRedemptionValue);
FixedPoint.Unsigned memory disputeBondAmount = collateral.mul(disputeBondPct);
FixedPoint.Unsigned memory finalFee = liquidation.finalFee.mul(feeAttenuation);
// There are three main outcome states: either the dispute succeeded, failed or was not updated.
// Based on the state, different parties of a liquidation can withdraw different amounts.
// Once a caller has been paid their address deleted from the struct.
// This prevents them from being paid multiple from times the same liquidation.
FixedPoint.Unsigned memory withdrawalAmount = FixedPoint.fromUnscaledUint(0);
if (liquidation.state == Status.DisputeSucceeded) {
// If the dispute is successful then all three users can withdraw from the contract.
if (msg.sender == liquidation.disputer) {
// Pay DISPUTER: disputer reward + dispute bond + returned final fee
FixedPoint.Unsigned memory payToDisputer = disputerDisputeReward.add(disputeBondAmount).add(finalFee);
withdrawalAmount = withdrawalAmount.add(payToDisputer);
delete liquidation.disputer;
}
if (msg.sender == liquidation.sponsor) {
// Pay SPONSOR: remaining collateral (collateral - TRV) + sponsor reward
FixedPoint.Unsigned memory remainingCollateral = collateral.sub(tokenRedemptionValue);
FixedPoint.Unsigned memory payToSponsor = sponsorDisputeReward.add(remainingCollateral);
withdrawalAmount = withdrawalAmount.add(payToSponsor);
delete liquidation.sponsor;
}
if (msg.sender == liquidation.liquidator) {
// Pay LIQUIDATOR: TRV - dispute reward - sponsor reward
// If TRV > Collateral, then subtract rewards from collateral
// NOTE: This should never be below zero since we prevent (sponsorDisputePct+disputerDisputePct) >= 0 in
// the constructor when these params are set.
FixedPoint.Unsigned memory payToLiquidator = tokenRedemptionValue.sub(sponsorDisputeReward).sub(
disputerDisputeReward
);
withdrawalAmount = withdrawalAmount.add(payToLiquidator);
delete liquidation.liquidator;
}
// Free up space once all collateral is withdrawn by removing the liquidation object from the array.
if (
liquidation.disputer == address(0) &&
liquidation.sponsor == address(0) &&
liquidation.liquidator == address(0)
) {
delete liquidations[sponsor][liquidationId];
}
// In the case of a failed dispute only the liquidator can withdraw.
} else if (liquidation.state == Status.DisputeFailed && msg.sender == liquidation.liquidator) {
// Pay LIQUIDATOR: collateral + dispute bond + returned final fee
withdrawalAmount = collateral.add(disputeBondAmount).add(finalFee);
delete liquidations[sponsor][liquidationId];
// If the state is pre-dispute but time has passed liveness then there was no dispute. We represent this
// state as a dispute failed and the liquidator can withdraw.
} else if (liquidation.state == Status.PreDispute && msg.sender == liquidation.liquidator) {
// Pay LIQUIDATOR: collateral + returned final fee
withdrawalAmount = collateral.add(finalFee);
delete liquidations[sponsor][liquidationId];
}
require(withdrawalAmount.isGreaterThan(0), "Invalid withdrawal amount");
// Decrease the total collateral held in liquidatable by the amount withdrawn.
amountWithdrawn = _removeCollateral(rawLiquidationCollateral, withdrawalAmount);
emit LiquidationWithdrawn(msg.sender, amountWithdrawn.rawValue, liquidation.state, settlementPrice.rawValue);
// Transfer amount withdrawn from this contract to the caller.
collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue);
return amountWithdrawn;
}
/**
* @notice Gets all liquidation information for a given sponsor address.
* @param sponsor address of the position sponsor.
* @return liquidationData array of all liquidation information for the given sponsor address.
*/
function getLiquidations(address sponsor)
external
view
nonReentrantView()
returns (LiquidationData[] memory liquidationData)
{
return liquidations[sponsor];
}
/****************************************
* INTERNAL FUNCTIONS *
****************************************/
// This settles a liquidation if it is in the PendingDispute state. If not, it will immediately return.
// If the liquidation is in the PendingDispute state, but a price is not available, this will revert.
function _settle(uint256 liquidationId, address sponsor) internal {
LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId);
// Settlement only happens when state == PendingDispute and will only happen once per liquidation.
// If this liquidation is not ready to be settled, this method should return immediately.
if (liquidation.state != Status.PendingDispute) {
return;
}
// Get the returned price from the oracle. If this has not yet resolved will revert.
liquidation.settlementPrice = _getOraclePrice(liquidation.liquidationTime);
// Find the value of the tokens in the underlying collateral.
FixedPoint.Unsigned memory tokenRedemptionValue = liquidation.tokensOutstanding.mul(
liquidation.settlementPrice
);
// The required collateral is the value of the tokens in underlying * required collateral ratio.
FixedPoint.Unsigned memory requiredCollateral = tokenRedemptionValue.mul(collateralRequirement);
// If the position has more than the required collateral it is solvent and the dispute is valid(liquidation is invalid)
// Note that this check uses the liquidatedCollateral not the lockedCollateral as this considers withdrawals.
bool disputeSucceeded = liquidation.liquidatedCollateral.isGreaterThanOrEqual(requiredCollateral);
liquidation.state = disputeSucceeded ? Status.DisputeSucceeded : Status.DisputeFailed;
emit DisputeSettled(
msg.sender,
sponsor,
liquidation.liquidator,
liquidation.disputer,
liquidationId,
disputeSucceeded
);
}
function _pfc() internal override view returns (FixedPoint.Unsigned memory) {
return super._pfc().add(_getFeeAdjustedCollateral(rawLiquidationCollateral));
}
function _getLiquidationData(address sponsor, uint256 liquidationId)
internal
view
returns (LiquidationData storage liquidation)
{
LiquidationData[] storage liquidationArray = liquidations[sponsor];
// Revert if the caller is attempting to access an invalid liquidation
// (one that has never been created or one has never been initialized).
require(
liquidationId < liquidationArray.length && liquidationArray[liquidationId].state != Status.Uninitialized,
"Invalid liquidation ID"
);
return liquidationArray[liquidationId];
}
function _getLiquidationExpiry(LiquidationData storage liquidation) internal view returns (uint256) {
return liquidation.liquidationTime.add(liquidationLiveness);
}
// These internal functions are supposed to act identically to modifiers, but re-used modifiers
// unnecessarily increase contract bytecode size.
// source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6
function _disputable(uint256 liquidationId, address sponsor) internal view {
LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId);
require(
(getCurrentTime() < _getLiquidationExpiry(liquidation)) && (liquidation.state == Status.PreDispute),
"Liquidation not disputable"
);
}
function _withdrawable(uint256 liquidationId, address sponsor) internal view {
LiquidationData storage liquidation = _getLiquidationData(sponsor, liquidationId);
Status state = liquidation.state;
// Must be disputed or the liquidation has passed expiry.
require(
(state > Status.PreDispute) ||
((_getLiquidationExpiry(liquidation) <= getCurrentTime()) && (state == Status.PreDispute)),
"Liquidation not withdrawable"
);
}
}
pragma solidity ^0.6.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() internal {
// 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 percetange 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 make it call a
* `private` function that does the actual work.
*/
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;
}
}
pragma solidity ^0.6.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow, so we distribute
return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
}
}
pragma solidity ^0.6.0;
/**
* @title Used internally by Truffle migrations.
* @dev See https://www.trufflesuite.com/docs/truffle/getting-started/running-migrations#initial-migration for details.
*/
contract Migrations {
address public owner;
uint256 public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint256 completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
pragma solidity ^0.6.0;
import "../../interfaces/AdministrateeInterface.sol";
// A mock implementation of AdministrateeInterface, taking the place of a financial contract.
contract MockAdministratee is AdministrateeInterface {
uint256 public timesRemargined;
uint256 public timesEmergencyShutdown;
function remargin() external override {
timesRemargined++;
}
function emergencyShutdown() external override {
timesEmergencyShutdown++;
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/Testable.sol";
import "../interfaces/OracleInterface.sol";
import "../interfaces/IdentifierWhitelistInterface.sol";
import "../interfaces/FinderInterface.sol";
import "../implementation/Constants.sol";
// A mock oracle used for testing.
contract MockOracle is OracleInterface, Testable {
// Represents an available price. Have to keep a separate bool to allow for price=0.
struct Price {
bool isAvailable;
int256 price;
// Time the verified price became available.
uint256 verifiedTime;
}
// The two structs below are used in an array and mapping to keep track of prices that have been requested but are
// not yet available.
struct QueryIndex {
bool isValid;
uint256 index;
}
// Represents a (identifier, time) point that has been queried.
struct QueryPoint {
bytes32 identifier;
uint256 time;
}
// Reference to the Finder.
FinderInterface private finder;
// Conceptually we want a (time, identifier) -> price map.
mapping(bytes32 => mapping(uint256 => Price)) private verifiedPrices;
// The mapping and array allow retrieving all the elements in a mapping and finding/deleting elements.
// Can we generalize this data structure?
mapping(bytes32 => mapping(uint256 => QueryIndex)) private queryIndices;
QueryPoint[] private requestedPrices;
constructor(address _finderAddress, address _timerAddress) public Testable(_timerAddress) {
finder = FinderInterface(_finderAddress);
}
// Enqueues a request (if a request isn't already present) for the given (identifier, time) pair.
function requestPrice(bytes32 identifier, uint256 time) external override {
require(_getIdentifierWhitelist().isIdentifierSupported(identifier));
Price storage lookup = verifiedPrices[identifier][time];
if (!lookup.isAvailable && !queryIndices[identifier][time].isValid) {
// New query, enqueue it for review.
queryIndices[identifier][time] = QueryIndex(true, requestedPrices.length);
requestedPrices.push(QueryPoint(identifier, time));
}
}
// Pushes the verified price for a requested query.
function pushPrice(
bytes32 identifier,
uint256 time,
int256 price
) external {
verifiedPrices[identifier][time] = Price(true, price, getCurrentTime());
QueryIndex storage queryIndex = queryIndices[identifier][time];
require(queryIndex.isValid, "Can't push prices that haven't been requested");
// Delete from the array. Instead of shifting the queries over, replace the contents of `indexToReplace` with
// the contents of the last index (unless it is the last index).
uint256 indexToReplace = queryIndex.index;
delete queryIndices[identifier][time];
uint256 lastIndex = requestedPrices.length - 1;
if (lastIndex != indexToReplace) {
QueryPoint storage queryToCopy = requestedPrices[lastIndex];
queryIndices[queryToCopy.identifier][queryToCopy.time].index = indexToReplace;
requestedPrices[indexToReplace] = queryToCopy;
}
}
// Checks whether a price has been resolved.
function hasPrice(bytes32 identifier, uint256 time) external override view returns (bool) {
require(_getIdentifierWhitelist().isIdentifierSupported(identifier));
Price storage lookup = verifiedPrices[identifier][time];
return lookup.isAvailable;
}
// Gets a price that has already been resolved.
function getPrice(bytes32 identifier, uint256 time) external override view returns (int256) {
require(_getIdentifierWhitelist().isIdentifierSupported(identifier));
Price storage lookup = verifiedPrices[identifier][time];
require(lookup.isAvailable);
return lookup.price;
}
// Gets the queries that still need verified prices.
function getPendingQueries() external view returns (QueryPoint[] memory) {
return requestedPrices;
}
function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) {
return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist));
}
}
pragma solidity ^0.6.0;
library Exclusive {
struct RoleMembership {
address member;
}
function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) {
return roleMembership.member == memberToCheck;
}
function resetMember(RoleMembership storage roleMembership, address newMember) internal {
require(newMember != address(0x0), "Cannot set an exclusive role to 0x0");
roleMembership.member = newMember;
}
function getMember(RoleMembership storage roleMembership) internal view returns (address) {
return roleMembership.member;
}
function init(RoleMembership storage roleMembership, address initialMember) internal {
resetMember(roleMembership, initialMember);
}
}
library Shared {
struct RoleMembership {
mapping(address => bool) members;
}
function isMember(RoleMembership storage roleMembership, address memberToCheck) internal view returns (bool) {
return roleMembership.members[memberToCheck];
}
function addMember(RoleMembership storage roleMembership, address memberToAdd) internal {
require(memberToAdd != address(0x0), "Cannot add 0x0 to a shared role");
roleMembership.members[memberToAdd] = true;
}
function removeMember(RoleMembership storage roleMembership, address memberToRemove) internal {
roleMembership.members[memberToRemove] = false;
}
function init(RoleMembership storage roleMembership, address[] memory initialMembers) internal {
for (uint256 i = 0; i < initialMembers.length; i++) {
addMember(roleMembership, initialMembers[i]);
}
}
}
/**
* @title Base class to manage permissions for the derived class.
*/
abstract contract MultiRole {
using Exclusive for Exclusive.RoleMembership;
using Shared for Shared.RoleMembership;
enum RoleType { Invalid, Exclusive, Shared }
struct Role {
uint256 managingRole;
RoleType roleType;
Exclusive.RoleMembership exclusiveRoleMembership;
Shared.RoleMembership sharedRoleMembership;
}
mapping(uint256 => Role) private roles;
event ResetExclusiveMember(uint256 indexed roleId, address indexed newMember, address indexed manager);
event AddedSharedMember(uint256 indexed roleId, address indexed newMember, address indexed manager);
event RemovedSharedMember(uint256 indexed roleId, address indexed oldMember, address indexed manager);
/**
* @notice Reverts unless the caller is a member of the specified roleId.
*/
modifier onlyRoleHolder(uint256 roleId) {
require(holdsRole(roleId, msg.sender), "Sender does not hold required role");
_;
}
/**
* @notice Reverts unless the caller is a member of the manager role for the specified roleId.
*/
modifier onlyRoleManager(uint256 roleId) {
require(holdsRole(roles[roleId].managingRole, msg.sender), "Can only be called by a role manager");
_;
}
/**
* @notice Reverts unless the roleId represents an initialized, exclusive roleId.
*/
modifier onlyExclusive(uint256 roleId) {
require(roles[roleId].roleType == RoleType.Exclusive, "Must be called on an initialized Exclusive role");
_;
}
/**
* @notice Reverts unless the roleId represents an initialized, shared roleId.
*/
modifier onlyShared(uint256 roleId) {
require(roles[roleId].roleType == RoleType.Shared, "Must be called on an initialized Shared role");
_;
}
/**
* @notice Whether `memberToCheck` is a member of roleId.
* @dev Reverts if roleId does not correspond to an initialized role.
* @param roleId the Role to check.
* @param memberToCheck the address to check.
* @return True if `memberToCheck` is a member of `roleId`.
*/
function holdsRole(uint256 roleId, address memberToCheck) public view returns (bool) {
Role storage role = roles[roleId];
if (role.roleType == RoleType.Exclusive) {
return role.exclusiveRoleMembership.isMember(memberToCheck);
} else if (role.roleType == RoleType.Shared) {
return role.sharedRoleMembership.isMember(memberToCheck);
}
revert("Invalid roleId");
}
/**
* @notice Changes the exclusive role holder of `roleId` to `newMember`.
* @dev Reverts if the caller is not a member of the managing role for `roleId` or if `roleId` is not an
* initialized, ExclusiveRole.
* @param roleId the ExclusiveRole membership to modify.
* @param newMember the new ExclusiveRole member.
*/
function resetMember(uint256 roleId, address newMember) public onlyExclusive(roleId) onlyRoleManager(roleId) {
roles[roleId].exclusiveRoleMembership.resetMember(newMember);
emit ResetExclusiveMember(roleId, newMember, msg.sender);
}
/**
* @notice Gets the current holder of the exclusive role, `roleId`.
* @dev Reverts if `roleId` does not represent an initialized, exclusive role.
* @param roleId the ExclusiveRole membership to check.
* @return the address of the current ExclusiveRole member.
*/
function getMember(uint256 roleId) public view onlyExclusive(roleId) returns (address) {
return roles[roleId].exclusiveRoleMembership.getMember();
}
/**
* @notice Adds `newMember` to the shared role, `roleId`.
* @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the
* managing role for `roleId`.
* @param roleId the SharedRole membership to modify.
* @param newMember the new SharedRole member.
*/
function addMember(uint256 roleId, address newMember) public onlyShared(roleId) onlyRoleManager(roleId) {
roles[roleId].sharedRoleMembership.addMember(newMember);
emit AddedSharedMember(roleId, newMember, msg.sender);
}
/**
* @notice Removes `memberToRemove` from the shared role, `roleId`.
* @dev Reverts if `roleId` does not represent an initialized, SharedRole or if the caller is not a member of the
* managing role for `roleId`.
* @param roleId the SharedRole membership to modify.
* @param memberToRemove the current SharedRole member to remove.
*/
function removeMember(uint256 roleId, address memberToRemove) public onlyShared(roleId) onlyRoleManager(roleId) {
roles[roleId].sharedRoleMembership.removeMember(memberToRemove);
emit RemovedSharedMember(roleId, memberToRemove, msg.sender);
}
/**
* @notice Removes caller from the role, `roleId`.
* @dev Reverts if the caller is not a member of the role for `roleId` or if `roleId` is not an
* initialized, SharedRole.
* @param roleId the SharedRole membership to modify.
*/
function renounceMembership(uint256 roleId) public onlyShared(roleId) onlyRoleHolder(roleId) {
roles[roleId].sharedRoleMembership.removeMember(msg.sender);
emit RemovedSharedMember(roleId, msg.sender, msg.sender);
}
/**
* @notice Reverts if `roleId` is not initialized.
*/
modifier onlyValidRole(uint256 roleId) {
require(roles[roleId].roleType != RoleType.Invalid, "Attempted to use an invalid roleId");
_;
}
/**
* @notice Reverts if `roleId` is initialized.
*/
modifier onlyInvalidRole(uint256 roleId) {
require(roles[roleId].roleType == RoleType.Invalid, "Cannot use a pre-existing role");
_;
}
/**
* @notice Internal method to initialize a shared role, `roleId`, which will be managed by `managingRoleId`.
* `initialMembers` will be immediately added to the role.
* @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already
* initialized.
*/
function _createSharedRole(
uint256 roleId,
uint256 managingRoleId,
address[] memory initialMembers
) internal onlyInvalidRole(roleId) {
Role storage role = roles[roleId];
role.roleType = RoleType.Shared;
role.managingRole = managingRoleId;
role.sharedRoleMembership.init(initialMembers);
require(
roles[managingRoleId].roleType != RoleType.Invalid,
"Attempted to use an invalid role to manage a shared role"
);
}
/**
* @notice Internal method to initialize an exclusive role, `roleId`, which will be managed by `managingRoleId`.
* `initialMember` will be immediately added to the role.
* @dev Should be called by derived contracts, usually at construction time. Will revert if the role is already
* initialized.
*/
function _createExclusiveRole(
uint256 roleId,
uint256 managingRoleId,
address initialMember
) internal onlyInvalidRole(roleId) {
Role storage role = roles[roleId];
role.roleType = RoleType.Exclusive;
role.managingRole = managingRoleId;
role.exclusiveRoleMembership.init(initialMember);
require(
roles[managingRoleId].roleType != RoleType.Invalid,
"Attempted to use an invalid role to manage an exclusive role"
);
}
}
/*
MultiRoleTest contract.
*/
pragma solidity ^0.6.0;
import "../implementation/MultiRole.sol";
// The purpose of this contract is to make the MultiRole creation methods externally callable for testing purposes.
contract MultiRoleTest is MultiRole {
function createSharedRole(
uint256 roleId,
uint256 managingRoleId,
address[] calldata initialMembers
) external {
_createSharedRole(roleId, managingRoleId, initialMembers);
}
function createExclusiveRole(
uint256 roleId,
uint256 managingRoleId,
address initialMember
) external {
_createExclusiveRole(roleId, managingRoleId, initialMember);
}
// solhint-disable-next-line no-empty-blocks
function revertIfNotHoldingRole(uint256 roleId) external view onlyRoleHolder(roleId) {}
}
pragma solidity ^0.6.0;
abstract contract OneSplit {
function getExpectedReturn(
address fromToken,
address destToken,
uint256 amount,
uint256 parts,
uint256 flags // See constants in IOneSplit.sol
) public virtual view returns (uint256 returnAmount, uint256[] memory distribution);
function swap(
address fromToken,
address destToken,
uint256 amount,
uint256 minReturn,
uint256[] memory distribution,
uint256 flags
) public virtual payable returns (uint256 returnAmount);
}
pragma solidity ^0.6.0;
import "../interfaces/OneSplit.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title OneSplit Mock that allows manual price injection.
*/
contract OneSplitMock is OneSplit {
address constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
mapping(bytes32 => uint256) prices;
receive() external payable {}
// Sets price of 1 FROM = <PRICE> TO
function setPrice(
address from,
address to,
uint256 price
) external {
prices[keccak256(abi.encodePacked(from, to))] = price;
}
function getExpectedReturn(
address fromToken,
address destToken,
uint256 amount,
uint256 parts,
uint256 flags // See constants in IOneSplit.sol
) public override view returns (uint256 returnAmount, uint256[] memory distribution) {
returnAmount = prices[keccak256(abi.encodePacked(fromToken, destToken))] * amount;
return (returnAmount, distribution);
}
function swap(
address fromToken,
address destToken,
uint256 amount,
uint256 minReturn,
uint256[] memory distribution,
uint256 flags
) public override payable returns (uint256 returnAmount) {
uint256 amountReturn = prices[keccak256(abi.encodePacked(fromToken, destToken))] * amount;
require(amountReturn >= minReturn, "Min Amount not reached");
if (destToken == ETH_ADDRESS) {
msg.sender.transfer(amountReturn);
} else {
require(IERC20(destToken).transfer(msg.sender, amountReturn), "erc20-send-failed");
}
}
}
pragma solidity ^0.6.0;
/**
* @title Financial contract facing Oracle interface.
* @dev Interface used by financial contracts to interact with the Oracle. Voters will use a different interface.
*/
interface OracleInterface {
/**
* @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 time unix timestamp for the price request.
*/
function requestPrice(bytes32 identifier, uint256 time) external;
/**
* @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.
* @return bool if the DVM has resolved to a price for the given identifier and timestamp.
*/
function hasPrice(bytes32 identifier, uint256 time) external view 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.
* @return int256 representing the resolved price for the given identifier and timestamp.
*/
function getPrice(bytes32 identifier, uint256 time) external view returns (int256);
}
pragma solidity ^0.6.0;
import "../GSN/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(_owner == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "../../common/implementation/FixedPoint.sol";
import "../../common/interfaces/ExpandedIERC20.sol";
import "../../oracle/interfaces/OracleInterface.sol";
import "../../oracle/interfaces/IdentifierWhitelistInterface.sol";
import "../../oracle/interfaces/AdministrateeInterface.sol";
import "../../oracle/implementation/Constants.sol";
import "../common/TokenFactory.sol";
import "../common/FeePayer.sol";
/**
* @title Financial contract with priceless position management.
* @notice Handles positions for multiple sponsors in an optimistic (i.e., priceless) way without relying
* on a price feed. On construction, deploys a new ERC20, managed by this contract, that is the synthetic token.
*/
contract PricelessPositionManager is FeePayer, AdministrateeInterface {
using SafeMath for uint256;
using FixedPoint for FixedPoint.Unsigned;
using SafeERC20 for IERC20;
using SafeERC20 for ExpandedIERC20;
/****************************************
* PRICELESS POSITION DATA STRUCTURES *
****************************************/
// Stores the state of the PricelessPositionManager. Set on expiration, emergency shutdown, or settlement.
enum ContractState { Open, ExpiredPriceRequested, ExpiredPriceReceived }
ContractState public contractState;
// Represents a single sponsor's position. All collateral is held by this contract.
// This struct acts as bookkeeping for how much of that collateral is allocated to each sponsor.
struct PositionData {
FixedPoint.Unsigned tokensOutstanding;
// Tracks pending withdrawal requests. A withdrawal request is pending if `withdrawalRequestPassTimestamp != 0`.
uint256 withdrawalRequestPassTimestamp;
FixedPoint.Unsigned withdrawalRequestAmount;
// Raw collateral value. This value should never be accessed directly -- always use _getFeeAdjustedCollateral().
// To add or remove collateral, use _addCollateral() and _removeCollateral().
FixedPoint.Unsigned rawCollateral;
// Tracks pending transfer position requests. A transfer position request is pending if `transferPositionRequestPassTimestamp != 0`.
uint256 transferPositionRequestPassTimestamp;
}
// Maps sponsor addresses to their positions. Each sponsor can have only one position.
mapping(address => PositionData) public positions;
// Keep track of the total collateral and tokens across all positions to enable calculating the
// global collateralization ratio without iterating over all positions.
FixedPoint.Unsigned public totalTokensOutstanding;
// Similar to the rawCollateral in PositionData, this value should not be used directly.
// _getFeeAdjustedCollateral(), _addCollateral() and _removeCollateral() must be used to access and adjust.
FixedPoint.Unsigned public rawTotalPositionCollateral;
// Synthetic token created by this contract.
ExpandedIERC20 public tokenCurrency;
// Unique identifier for DVM price feed ticker.
bytes32 public priceIdentifier;
// Time that this contract expires. Should not change post-construction unless an emergency shutdown occurs.
uint256 public expirationTimestamp;
// Time that has to elapse for a withdrawal request to be considered passed, if no liquidations occur.
// !!Note: The lower the withdrawal liveness value, the more risk incurred by the contract.
// Extremely low liveness values increase the chance that opportunistic invalid withdrawal requests
// expire without liquidation, thereby increasing the insolvency risk for the contract as a whole. An insolvent
// contract is extremely risky for any sponsor or synthetic token holder for the contract.
uint256 public withdrawalLiveness;
// Minimum number of tokens in a sponsor's position.
FixedPoint.Unsigned public minSponsorTokens;
// The expiry price pulled from the DVM.
FixedPoint.Unsigned public expiryPrice;
// The excessTokenBeneficiary of any excess tokens added to the contract.
address public excessTokenBeneficiary;
/****************************************
* EVENTS *
****************************************/
event RequestTransferPosition(address indexed oldSponsor);
event RequestTransferPositionExecuted(address indexed oldSponsor, address indexed newSponsor);
event RequestTransferPositionCanceled(address indexed oldSponsor);
event Deposit(address indexed sponsor, uint256 indexed collateralAmount);
event Withdrawal(address indexed sponsor, uint256 indexed collateralAmount);
event RequestWithdrawal(address indexed sponsor, uint256 indexed collateralAmount);
event RequestWithdrawalExecuted(address indexed sponsor, uint256 indexed collateralAmount);
event RequestWithdrawalCanceled(address indexed sponsor, uint256 indexed collateralAmount);
event PositionCreated(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount);
event NewSponsor(address indexed sponsor);
event EndedSponsorPosition(address indexed sponsor);
event Redeem(address indexed sponsor, uint256 indexed collateralAmount, uint256 indexed tokenAmount);
event ContractExpired(address indexed caller);
event SettleExpiredPosition(
address indexed caller,
uint256 indexed collateralReturned,
uint256 indexed tokensBurned
);
event EmergencyShutdown(address indexed caller, uint256 originalExpirationTimestamp, uint256 shutdownTimestamp);
/****************************************
* MODIFIERS *
****************************************/
modifier onlyPreExpiration() {
_onlyPreExpiration();
_;
}
modifier onlyPostExpiration() {
_onlyPostExpiration();
_;
}
modifier onlyCollateralizedPosition(address sponsor) {
_onlyCollateralizedPosition(sponsor);
_;
}
// Check that the current state of the pricelessPositionManager is Open.
// This prevents multiple calls to `expire` and `EmergencyShutdown` post expiration.
modifier onlyOpenState() {
_onlyOpenState();
_;
}
modifier noPendingWithdrawal(address sponsor) {
_positionHasNoPendingWithdrawal(sponsor);
_;
}
/**
* @notice Construct the PricelessPositionManager
* @param _expirationTimestamp unix timestamp of when the contract will expire.
* @param _withdrawalLiveness liveness delay, in seconds, for pending withdrawals.
* @param _collateralAddress ERC20 token used as collateral for all positions.
* @param _finderAddress UMA protocol Finder used to discover other protocol contracts.
* @param _priceIdentifier registered in the DVM for the synthetic.
* @param _syntheticName name for the token contract that will be deployed.
* @param _syntheticSymbol symbol for the token contract that will be deployed.
* @param _tokenFactoryAddress deployed UMA token factory to create the synthetic token.
* @param _minSponsorTokens minimum amount of collateral that must exist at any time in a position.
* @param _timerAddress Contract that stores the current time in a testing environment.
* @param _excessTokenBeneficiary Beneficiary to which all excess token balances that accrue in the contract can be
* sent.
* Must be set to 0x0 for production environments that use live time.
*/
constructor(
uint256 _expirationTimestamp,
uint256 _withdrawalLiveness,
address _collateralAddress,
address _finderAddress,
bytes32 _priceIdentifier,
string memory _syntheticName,
string memory _syntheticSymbol,
address _tokenFactoryAddress,
FixedPoint.Unsigned memory _minSponsorTokens,
address _timerAddress,
address _excessTokenBeneficiary
) public FeePayer(_collateralAddress, _finderAddress, _timerAddress) nonReentrant() {
require(_expirationTimestamp > getCurrentTime(), "Invalid expiration in future");
require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier), "Unsupported price identifier");
expirationTimestamp = _expirationTimestamp;
withdrawalLiveness = _withdrawalLiveness;
TokenFactory tf = TokenFactory(_tokenFactoryAddress);
tokenCurrency = tf.createToken(_syntheticName, _syntheticSymbol, 18);
minSponsorTokens = _minSponsorTokens;
priceIdentifier = _priceIdentifier;
excessTokenBeneficiary = _excessTokenBeneficiary;
}
/****************************************
* POSITION FUNCTIONS *
****************************************/
/**
* @notice Requests to transfer ownership of the caller's current position to a new sponsor address.
* Once the request liveness is passed, the sponsor can execute the transfer and specify the new sponsor.
* @dev The liveness length is the same as the withdrawal liveness.
*/
function requestTransferPosition() public onlyPreExpiration() nonReentrant() {
PositionData storage positionData = _getPositionData(msg.sender);
require(positionData.transferPositionRequestPassTimestamp == 0, "Pending transfer");
// Make sure the proposed expiration of this request is not post-expiry.
uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness);
require(requestPassTime < expirationTimestamp, "Request expires post-expiry");
// Update the position object for the user.
positionData.transferPositionRequestPassTimestamp = requestPassTime;
emit RequestTransferPosition(msg.sender);
}
/**
* @notice After a passed transfer position request (i.e., by a call to `requestTransferPosition` and waiting
* `withdrawalLiveness`), transfers ownership of the caller's current position to `newSponsorAddress`.
* @dev Transferring positions can only occur if the recipient does not already have a position.
* @param newSponsorAddress is the address to which the position will be transferred.
*/
function transferPositionPassedRequest(address newSponsorAddress)
public
onlyPreExpiration()
noPendingWithdrawal(msg.sender)
nonReentrant()
{
require(
_getFeeAdjustedCollateral(positions[newSponsorAddress].rawCollateral).isEqual(
FixedPoint.fromUnscaledUint(0)
),
"Sponsor already has position"
);
PositionData storage positionData = _getPositionData(msg.sender);
require(
positionData.transferPositionRequestPassTimestamp != 0 &&
positionData.transferPositionRequestPassTimestamp <= getCurrentTime(),
"Invalid transfer request"
);
// Reset transfer request.
positionData.transferPositionRequestPassTimestamp = 0;
positions[newSponsorAddress] = positionData;
delete positions[msg.sender];
emit RequestTransferPositionExecuted(msg.sender, newSponsorAddress);
emit NewSponsor(newSponsorAddress);
emit EndedSponsorPosition(msg.sender);
}
/**
* @notice Cancels a pending transfer position request.
*/
function cancelTransferPosition() external onlyPreExpiration() nonReentrant() {
PositionData storage positionData = _getPositionData(msg.sender);
require(positionData.transferPositionRequestPassTimestamp != 0, "No pending transfer");
emit RequestTransferPositionCanceled(msg.sender);
// Reset withdrawal request.
positionData.transferPositionRequestPassTimestamp = 0;
}
/**
* @notice Transfers `collateralAmount` of `collateralCurrency` into the specified sponsor's position.
* @dev Increases the collateralization level of a position after creation. This contract must be approved to spend
* at least `collateralAmount` of `collateralCurrency`.
* @param sponsor the sponsor to credit the deposit to.
* @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position.
*/
function depositTo(address sponsor, FixedPoint.Unsigned memory collateralAmount)
public
onlyPreExpiration()
noPendingWithdrawal(sponsor)
fees()
nonReentrant()
{
require(collateralAmount.isGreaterThan(0), "Invalid collateral amount");
PositionData storage positionData = _getPositionData(sponsor);
// Increase the position and global collateral balance by collateral amount.
_incrementCollateralBalances(positionData, collateralAmount);
emit Deposit(sponsor, collateralAmount.rawValue);
// Move collateral currency from sender to contract.
collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue);
}
/**
* @notice Transfers `collateralAmount` of `collateralCurrency` into the caller's position.
* @dev Increases the collateralization level of a position after creation. This contract must be approved to spend
* at least `collateralAmount` of `collateralCurrency`.
* @param collateralAmount total amount of collateral tokens to be sent to the sponsor's position.
*/
function deposit(FixedPoint.Unsigned memory collateralAmount) public {
// This is just a thin wrapper over depositTo that specified the sender as the sponsor.
depositTo(msg.sender, collateralAmount);
}
/**
* @notice Transfers `collateralAmount` of `collateralCurrency` from the sponsor's position to the sponsor.
* @dev Reverts if the withdrawal puts this position's collateralization ratio below the global collateralization
* ratio. In that case, use `requestWithdrawal`. Might not withdraw the full requested amount to account for precision loss.
* @param collateralAmount is the amount of collateral to withdraw.
* @return amountWithdrawn The actual amount of collateral withdrawn.
*/
function withdraw(FixedPoint.Unsigned memory collateralAmount)
public
onlyPreExpiration()
noPendingWithdrawal(msg.sender)
fees()
nonReentrant()
returns (FixedPoint.Unsigned memory amountWithdrawn)
{
PositionData storage positionData = _getPositionData(msg.sender);
require(collateralAmount.isGreaterThan(0), "Invalid collateral amount");
// Decrement the sponsor's collateral and global collateral amounts. Check the GCR between decrement to ensure
// position remains above the GCR within the witdrawl. If this is not the case the caller must submit a request.
amountWithdrawn = _decrementCollateralBalancesCheckGCR(positionData, collateralAmount);
emit Withdrawal(msg.sender, amountWithdrawn.rawValue);
// Move collateral currency from contract to sender.
// Note: that we move the amount of collateral that is decreased from rawCollateral (inclusive of fees)
// instead of the user requested amount. This eliminates precision loss that could occur
// where the user withdraws more collateral than rawCollateral is decremented by.
collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue);
}
/**
* @notice Starts a withdrawal request that, if passed, allows the sponsor to withdraw` from their position.
* @dev The request will be pending for `withdrawalLiveness`, during which the position can be liquidated.
* @param collateralAmount the amount of collateral requested to withdraw
*/
function requestWithdrawal(FixedPoint.Unsigned memory collateralAmount)
public
onlyPreExpiration()
noPendingWithdrawal(msg.sender)
nonReentrant()
{
PositionData storage positionData = _getPositionData(msg.sender);
require(
collateralAmount.isGreaterThan(0) &&
collateralAmount.isLessThanOrEqual(_getFeeAdjustedCollateral(positionData.rawCollateral)),
"Invalid collateral amount"
);
// Make sure the proposed expiration of this request is not post-expiry.
uint256 requestPassTime = getCurrentTime().add(withdrawalLiveness);
require(requestPassTime < expirationTimestamp, "Request expires post-expiry");
// Update the position object for the user.
positionData.withdrawalRequestPassTimestamp = requestPassTime;
positionData.withdrawalRequestAmount = collateralAmount;
emit RequestWithdrawal(msg.sender, collateralAmount.rawValue);
}
/**
* @notice After a passed withdrawal request (i.e., by a call to `requestWithdrawal` and waiting
* `withdrawalLiveness`), withdraws `positionData.withdrawalRequestAmount` of collateral currency.
* @dev Might not withdraw the full requested amount in order to account for precision loss or if the full requested
* amount exceeds the collateral in the position (due to paying fees).
* @return amountWithdrawn The actual amount of collateral withdrawn.
*/
function withdrawPassedRequest()
external
onlyPreExpiration()
fees()
nonReentrant()
returns (FixedPoint.Unsigned memory amountWithdrawn)
{
PositionData storage positionData = _getPositionData(msg.sender);
require(
positionData.withdrawalRequestPassTimestamp != 0 &&
positionData.withdrawalRequestPassTimestamp <= getCurrentTime(),
"Invalid withdraw request"
);
// If withdrawal request amount is > position collateral, then withdraw the full collateral amount.
// This situation is possible due to fees charged since the withdrawal was originally requested.
FixedPoint.Unsigned memory amountToWithdraw = positionData.withdrawalRequestAmount;
if (positionData.withdrawalRequestAmount.isGreaterThan(_getFeeAdjustedCollateral(positionData.rawCollateral))) {
amountToWithdraw = _getFeeAdjustedCollateral(positionData.rawCollateral);
}
// Decrement the sponsor's collateral and global collateral amounts.
amountWithdrawn = _decrementCollateralBalances(positionData, amountToWithdraw);
// Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0.
_resetWithdrawalRequest(positionData);
// Transfer approved withdrawal amount from the contract to the caller.
collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue);
emit RequestWithdrawalExecuted(msg.sender, amountWithdrawn.rawValue);
}
/**
* @notice Cancels a pending withdrawal request.
*/
function cancelWithdrawal() external nonReentrant() {
PositionData storage positionData = _getPositionData(msg.sender);
require(positionData.withdrawalRequestPassTimestamp != 0, "No pending withdrawal");
emit RequestWithdrawalCanceled(msg.sender, positionData.withdrawalRequestAmount.rawValue);
// Reset withdrawal request by setting withdrawal amount and withdrawal timestamp to 0.
_resetWithdrawalRequest(positionData);
}
/**
* @notice Creates tokens by creating a new position or by augmenting an existing position. Pulls `collateralAmount` into the sponsor's position and mints `numTokens` of `tokenCurrency`.
* @dev Reverts if minting these tokens would put the position's collateralization ratio below the
* global collateralization ratio. This contract must be approved to spend at least `collateralAmount` of
* `collateralCurrency`.
* @param collateralAmount is the number of collateral tokens to collateralize the position with
* @param numTokens is the number of tokens to mint from the position.
*/
function create(FixedPoint.Unsigned memory collateralAmount, FixedPoint.Unsigned memory numTokens)
public
onlyPreExpiration()
fees()
nonReentrant()
{
PositionData storage positionData = positions[msg.sender];
// Either the new create ratio or the resultant position CR must be above the current GCR.
require(
(_checkCollateralization(
_getFeeAdjustedCollateral(positionData.rawCollateral).add(collateralAmount),
positionData.tokensOutstanding.add(numTokens)
) || _checkCollateralization(collateralAmount, numTokens)),
"Insufficient collateral"
);
require(positionData.withdrawalRequestPassTimestamp == 0, "Pending withdrawal");
if (positionData.tokensOutstanding.isEqual(0)) {
require(numTokens.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position");
emit NewSponsor(msg.sender);
}
// Increase the position and global collateral balance by collateral amount.
_incrementCollateralBalances(positionData, collateralAmount);
// Add the number of tokens created to the position's outstanding tokens.
positionData.tokensOutstanding = positionData.tokensOutstanding.add(numTokens);
totalTokensOutstanding = totalTokensOutstanding.add(numTokens);
emit PositionCreated(msg.sender, collateralAmount.rawValue, numTokens.rawValue);
// Transfer tokens into the contract from caller and mint corresponding synthetic tokens to the caller's address.
collateralCurrency.safeTransferFrom(msg.sender, address(this), collateralAmount.rawValue);
require(tokenCurrency.mint(msg.sender, numTokens.rawValue), "Minting synthetic tokens failed");
}
/**
* @notice Burns `numTokens` of `tokenCurrency` and sends back the proportional amount of `collateralCurrency`.
* @dev Can only be called by a token sponsor. Might not redeem the full proportional amount of collateral
* in order to account for precision loss. This contract must be approved to spend at least `numTokens` of
* `tokenCurrency`.
* @param numTokens is the number of tokens to be burnt for a commensurate amount of collateral.
* @return amountWithdrawn The actual amount of collateral withdrawn.
*/
function redeem(FixedPoint.Unsigned memory numTokens)
public
noPendingWithdrawal(msg.sender)
fees()
nonReentrant()
returns (FixedPoint.Unsigned memory amountWithdrawn)
{
PositionData storage positionData = _getPositionData(msg.sender);
require(!numTokens.isGreaterThan(positionData.tokensOutstanding), "Invalid token amount");
FixedPoint.Unsigned memory fractionRedeemed = numTokens.div(positionData.tokensOutstanding);
FixedPoint.Unsigned memory collateralRedeemed = fractionRedeemed.mul(
_getFeeAdjustedCollateral(positionData.rawCollateral)
);
// If redemption returns all tokens the sponsor has then we can delete their position. Else, downsize.
if (positionData.tokensOutstanding.isEqual(numTokens)) {
amountWithdrawn = _deleteSponsorPosition(msg.sender);
} else {
// Decrement the sponsor's collateral and global collateral amounts.
amountWithdrawn = _decrementCollateralBalances(positionData, collateralRedeemed);
// Decrease the sponsors position tokens size. Ensure it is above the min sponsor size.
FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(numTokens);
require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position");
positionData.tokensOutstanding = newTokenCount;
// Update the totalTokensOutstanding after redemption.
totalTokensOutstanding = totalTokensOutstanding.sub(numTokens);
}
emit Redeem(msg.sender, amountWithdrawn.rawValue, numTokens.rawValue);
// Transfer collateral from contract to caller and burn callers synthetic tokens.
collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue);
tokenCurrency.safeTransferFrom(msg.sender, address(this), numTokens.rawValue);
tokenCurrency.burn(numTokens.rawValue);
}
/**
* @notice After a contract has passed expiry all token holders can redeem their tokens for underlying at the
* prevailing price defined by the DVM from the `expire` function.
* @dev This burns all tokens from the caller of `tokenCurrency` and sends back the proportional amount of
* `collateralCurrency`. Might not redeem the full proportional amount of collateral in order to account for
* precision loss. This contract must be approved to spend `tokenCurrency` at least up to the caller's full balance.
* @return amountWithdrawn The actual amount of collateral withdrawn.
*/
function settleExpired()
external
onlyPostExpiration()
fees()
nonReentrant()
returns (FixedPoint.Unsigned memory amountWithdrawn)
{
// If the contract state is open and onlyPostExpiration passed then `expire()` has not yet been called.
require(contractState != ContractState.Open, "Unexpired position");
// Get the current settlement price and store it. If it is not resolved will revert.
if (contractState != ContractState.ExpiredPriceReceived) {
expiryPrice = _getOraclePrice(expirationTimestamp);
contractState = ContractState.ExpiredPriceReceived;
}
// Get caller's tokens balance and calculate amount of underlying entitled to them.
FixedPoint.Unsigned memory tokensToRedeem = FixedPoint.Unsigned(tokenCurrency.balanceOf(msg.sender));
FixedPoint.Unsigned memory totalRedeemableCollateral = tokensToRedeem.mul(expiryPrice);
// If the caller is a sponsor with outstanding collateral they are also entitled to their excess collateral after their debt.
PositionData storage positionData = positions[msg.sender];
if (_getFeeAdjustedCollateral(positionData.rawCollateral).isGreaterThan(0)) {
// Calculate the underlying entitled to a token sponsor. This is collateral - debt in underlying.
FixedPoint.Unsigned memory tokenDebtValueInCollateral = positionData.tokensOutstanding.mul(expiryPrice);
FixedPoint.Unsigned memory positionCollateral = _getFeeAdjustedCollateral(positionData.rawCollateral);
// If the debt is greater than the remaining collateral, they cannot redeem anything.
FixedPoint.Unsigned memory positionRedeemableCollateral = tokenDebtValueInCollateral.isLessThan(
positionCollateral
)
? positionCollateral.sub(tokenDebtValueInCollateral)
: FixedPoint.Unsigned(0);
// Add the number of redeemable tokens for the sponsor to their total redeemable collateral.
totalRedeemableCollateral = totalRedeemableCollateral.add(positionRedeemableCollateral);
// Reset the position state as all the value has been removed after settlement.
delete positions[msg.sender];
emit EndedSponsorPosition(msg.sender);
}
// Take the min of the remaining collateral and the collateral "owed". If the contract is undercapitalized,
// the caller will get as much collateral as the contract can pay out.
FixedPoint.Unsigned memory payout = FixedPoint.min(
_getFeeAdjustedCollateral(rawTotalPositionCollateral),
totalRedeemableCollateral
);
// Decrement total contract collateral and outstanding debt.
amountWithdrawn = _removeCollateral(rawTotalPositionCollateral, payout);
totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRedeem);
emit SettleExpiredPosition(msg.sender, amountWithdrawn.rawValue, tokensToRedeem.rawValue);
// Transfer tokens & collateral and burn the redeemed tokens.
collateralCurrency.safeTransfer(msg.sender, amountWithdrawn.rawValue);
tokenCurrency.safeTransferFrom(msg.sender, address(this), tokensToRedeem.rawValue);
tokenCurrency.burn(tokensToRedeem.rawValue);
}
/****************************************
* GLOBAL STATE FUNCTIONS *
****************************************/
/**
* @notice Locks contract state in expired and requests oracle price.
* @dev this function can only be called once the contract is expired and can't be re-called.
*/
function expire() external onlyPostExpiration() onlyOpenState() fees() nonReentrant() {
contractState = ContractState.ExpiredPriceRequested;
// The final fee for this request is paid out of the contract rather than by the caller.
_payFinalFees(address(this), _computeFinalFees());
_requestOraclePrice(expirationTimestamp);
emit ContractExpired(msg.sender);
}
/**
* @notice Premature contract settlement under emergency circumstances.
* @dev Only the governor can call this function as they are permissioned within the `FinancialContractAdmin`.
* Upon emergency shutdown, the contract settlement time is set to the shutdown time. This enables withdrawal
* to occur via the standard `settleExpired` function. Contract state is set to `ExpiredPriceRequested`
* which prevents re-entry into this function or the `expire` function. No fees are paid when calling
* `emergencyShutdown` as the governor who would call the function would also receive the fees.
*/
function emergencyShutdown() external override onlyPreExpiration() onlyOpenState() nonReentrant() {
require(msg.sender == _getFinancialContractsAdminAddress(), "Caller not Governor");
contractState = ContractState.ExpiredPriceRequested;
// Expiratory time now becomes the current time (emergency shutdown time).
// Price requested at this time stamp. `settleExpired` can now withdraw at this timestamp.
uint256 oldExpirationTimestamp = expirationTimestamp;
expirationTimestamp = getCurrentTime();
_requestOraclePrice(expirationTimestamp);
emit EmergencyShutdown(msg.sender, oldExpirationTimestamp, expirationTimestamp);
}
/**
* @notice Theoretically supposed to pay fees and move money between margin accounts to make sure they
* reflect the NAV of the contract. However, this functionality doesn't apply to this contract.
* @dev This is supposed to be implemented by any contract that inherits `AdministrateeInterface` and callable
* only by the Governor contract. This method is therefore minimally implemented in this contract and does nothing.
*/
function remargin() external override onlyPreExpiration() nonReentrant() {
return;
}
/**
* @notice Drains any excess balance of the provided ERC20 token to a pre-selected beneficiary.
* @dev This will drain down to the amount of tracked collateral and drain the full balance of any other token.
* @param token address of the ERC20 token whose excess balance should be drained.
*/
function trimExcess(IERC20 token) external fees() nonReentrant() returns (FixedPoint.Unsigned memory amount) {
FixedPoint.Unsigned memory balance = FixedPoint.Unsigned(token.balanceOf(address(this)));
if (address(token) == address(collateralCurrency)) {
// If it is the collateral currency, send only the amount that the contract is not tracking.
// Note: this could be due to rounding error or balance-changing tokens, like aTokens.
amount = balance.sub(_pfc());
} else {
// If it's not the collateral currency, send the entire balance.
amount = balance;
}
token.safeTransfer(excessTokenBeneficiary, amount.rawValue);
}
/**
* @notice Accessor method for a sponsor's collateral.
* @dev This is necessary because the struct returned by the positions() method shows
* rawCollateral, which isn't a user-readable value.
* @param sponsor address whose collateral amount is retrieved.
* @return collateralAmount amount of collateral within a sponsors position.
*/
function getCollateral(address sponsor)
external
view
nonReentrantView()
returns (FixedPoint.Unsigned memory collateralAmount)
{
// Note: do a direct access to avoid the validity check.
return _getFeeAdjustedCollateral(positions[sponsor].rawCollateral);
}
/**
* @notice Accessor method for the total collateral stored within the PricelessPositionManager.
* @return totalCollateral amount of all collateral within the Expiring Multi Party Contract.
*/
function totalPositionCollateral()
external
view
nonReentrantView()
returns (FixedPoint.Unsigned memory totalCollateral)
{
return _getFeeAdjustedCollateral(rawTotalPositionCollateral);
}
/****************************************
* INTERNAL FUNCTIONS *
****************************************/
// Reduces a sponsor's position and global counters by the specified parameters. Handles deleting the entire
// position if the entire position is being removed. Does not make any external transfers.
function _reduceSponsorPosition(
address sponsor,
FixedPoint.Unsigned memory tokensToRemove,
FixedPoint.Unsigned memory collateralToRemove,
FixedPoint.Unsigned memory withdrawalAmountToRemove
) internal {
PositionData storage positionData = _getPositionData(sponsor);
// If the entire position is being removed, delete it instead.
if (
tokensToRemove.isEqual(positionData.tokensOutstanding) &&
_getFeeAdjustedCollateral(positionData.rawCollateral).isEqual(collateralToRemove)
) {
_deleteSponsorPosition(sponsor);
return;
}
// Decrement the sponsor's collateral and global collateral amounts.
_decrementCollateralBalances(positionData, collateralToRemove);
// Ensure that the sponsor will meet the min position size after the reduction.
FixedPoint.Unsigned memory newTokenCount = positionData.tokensOutstanding.sub(tokensToRemove);
require(newTokenCount.isGreaterThanOrEqual(minSponsorTokens), "Below minimum sponsor position");
positionData.tokensOutstanding = newTokenCount;
// Decrement the position's withdrawal amount.
positionData.withdrawalRequestAmount = positionData.withdrawalRequestAmount.sub(withdrawalAmountToRemove);
// Decrement the total outstanding tokens in the overall contract.
totalTokensOutstanding = totalTokensOutstanding.sub(tokensToRemove);
}
// Deletes a sponsor's position and updates global counters. Does not make any external transfers.
function _deleteSponsorPosition(address sponsor) internal returns (FixedPoint.Unsigned memory) {
PositionData storage positionToLiquidate = _getPositionData(sponsor);
FixedPoint.Unsigned memory startingGlobalCollateral = _getFeeAdjustedCollateral(rawTotalPositionCollateral);
// Remove the collateral and outstanding from the overall total position.
FixedPoint.Unsigned memory remainingRawCollateral = positionToLiquidate.rawCollateral;
rawTotalPositionCollateral = rawTotalPositionCollateral.sub(remainingRawCollateral);
totalTokensOutstanding = totalTokensOutstanding.sub(positionToLiquidate.tokensOutstanding);
// Reset the sponsors position to have zero outstanding and collateral.
delete positions[sponsor];
emit EndedSponsorPosition(sponsor);
// Return fee-adjusted amount of collateral deleted from position.
return startingGlobalCollateral.sub(_getFeeAdjustedCollateral(rawTotalPositionCollateral));
}
function _pfc() internal virtual override view returns (FixedPoint.Unsigned memory) {
return _getFeeAdjustedCollateral(rawTotalPositionCollateral);
}
function _getPositionData(address sponsor)
internal
view
onlyCollateralizedPosition(sponsor)
returns (PositionData storage)
{
return positions[sponsor];
}
function _getIdentifierWhitelist() internal view returns (IdentifierWhitelistInterface) {
return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist));
}
function _getOracle() internal view returns (OracleInterface) {
return OracleInterface(finder.getImplementationAddress(OracleInterfaces.Oracle));
}
function _getFinancialContractsAdminAddress() internal view returns (address) {
return finder.getImplementationAddress(OracleInterfaces.FinancialContractsAdmin);
}
// Requests a price for `priceIdentifier` at `requestedTime` from the Oracle.
function _requestOraclePrice(uint256 requestedTime) internal {
OracleInterface oracle = _getOracle();
oracle.requestPrice(priceIdentifier, requestedTime);
}
// Fetches a resolved Oracle price from the Oracle. Reverts if the Oracle hasn't resolved for this request.
function _getOraclePrice(uint256 requestedTime) internal view returns (FixedPoint.Unsigned memory) {
// Create an instance of the oracle and get the price. If the price is not resolved revert.
OracleInterface oracle = _getOracle();
require(oracle.hasPrice(priceIdentifier, requestedTime), "Unresolved oracle price");
int256 oraclePrice = oracle.getPrice(priceIdentifier, requestedTime);
// For now we don't want to deal with negative prices in positions.
if (oraclePrice < 0) {
oraclePrice = 0;
}
return FixedPoint.Unsigned(uint256(oraclePrice));
}
// Reset withdrawal request by setting the withdrawal request and withdrawal timestamp to 0.
function _resetWithdrawalRequest(PositionData storage positionData) internal {
positionData.withdrawalRequestAmount = FixedPoint.fromUnscaledUint(0);
positionData.withdrawalRequestPassTimestamp = 0;
}
// Ensure individual and global consistency when increasing collateral balances. Returns the change to the position.
function _incrementCollateralBalances(
PositionData storage positionData,
FixedPoint.Unsigned memory collateralAmount
) internal returns (FixedPoint.Unsigned memory) {
_addCollateral(positionData.rawCollateral, collateralAmount);
return _addCollateral(rawTotalPositionCollateral, collateralAmount);
}
// Ensure individual and global consistency when decrementing collateral balances. Returns the change to the
// position. We elect to return the amount that the global collateral is decreased by, rather than the individual
// position's collateral, because we need to maintain the invariant that the global collateral is always
// <= the collateral owned by the contract to avoid reverts on withdrawals. The amount returned = amount withdrawn.
function _decrementCollateralBalances(
PositionData storage positionData,
FixedPoint.Unsigned memory collateralAmount
) internal returns (FixedPoint.Unsigned memory) {
_removeCollateral(positionData.rawCollateral, collateralAmount);
return _removeCollateral(rawTotalPositionCollateral, collateralAmount);
}
// Ensure individual and global consistency when decrementing collateral balances. Returns the change to the position.
// This function is similar to the _decrementCollateralBalances function except this function checks position GCR
// between the decrements. This ensures that collateral removal will not leave the position undercollateralized.
function _decrementCollateralBalancesCheckGCR(
PositionData storage positionData,
FixedPoint.Unsigned memory collateralAmount
) internal returns (FixedPoint.Unsigned memory) {
_removeCollateral(positionData.rawCollateral, collateralAmount);
require(_checkPositionCollateralization(positionData), "CR below GCR");
return _removeCollateral(rawTotalPositionCollateral, collateralAmount);
}
// These internal functions are supposed to act identically to modifiers, but re-used modifiers
// unnecessarily increase contract bytecode size.
// source: https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6
function _onlyOpenState() internal view {
require(contractState == ContractState.Open, "Contract state is not OPEN");
}
function _onlyPreExpiration() internal view {
require(getCurrentTime() < expirationTimestamp, "Only callable pre-expiry");
}
function _onlyPostExpiration() internal view {
require(getCurrentTime() >= expirationTimestamp, "Only callable post-expiry");
}
function _onlyCollateralizedPosition(address sponsor) internal view {
require(
_getFeeAdjustedCollateral(positions[sponsor].rawCollateral).isGreaterThan(0),
"Position has no collateral"
);
}
// Note: This checks whether an already existing position has a pending withdrawal. This cannot be used on the
// `create` method because it is possible that `create` is called on a new position (i.e. one without any collateral
// or tokens outstanding) which would fail the `onlyCollateralizedPosition` modifier on `_getPositionData`.
function _positionHasNoPendingWithdrawal(address sponsor) internal view {
require(_getPositionData(sponsor).withdrawalRequestPassTimestamp == 0, "Pending withdrawal");
}
/****************************************
* PRIVATE FUNCTIONS *
****************************************/
function _checkPositionCollateralization(PositionData storage positionData) private view returns (bool) {
return
_checkCollateralization(
_getFeeAdjustedCollateral(positionData.rawCollateral),
positionData.tokensOutstanding
);
}
// Checks whether the provided `collateral` and `numTokens` have a collateralization ratio above the global
// collateralization ratio.
function _checkCollateralization(FixedPoint.Unsigned memory collateral, FixedPoint.Unsigned memory numTokens)
private
view
returns (bool)
{
FixedPoint.Unsigned memory global = _getCollateralizationRatio(
_getFeeAdjustedCollateral(rawTotalPositionCollateral),
totalTokensOutstanding
);
FixedPoint.Unsigned memory thisChange = _getCollateralizationRatio(collateral, numTokens);
return !global.isGreaterThan(thisChange);
}
function _getCollateralizationRatio(FixedPoint.Unsigned memory collateral, FixedPoint.Unsigned memory numTokens)
private
pure
returns (FixedPoint.Unsigned memory ratio)
{
if (!numTokens.isGreaterThan(0)) {
return FixedPoint.fromUnscaledUint(0);
} else {
return collateral.div(numTokens);
}
}
}
pragma solidity ^0.6.0;
// Tests reentrancy guards defined in Lockable.sol.
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyAttack.sol.
contract ReentrancyAttack {
function callSender(bytes4 data) public {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = msg.sender.call(abi.encodeWithSelector(data));
require(success, "ReentrancyAttack: failed call");
}
}
pragma solidity ^0.6.0;
// The Reentrancy Checker causes failures if it is successfully able to re-enter a contract.
// How to use:
// 1. Call setTransactionData with the transaction data you want the Reentrancy Checker to reenter the calling
// contract with.
// 2. Get the calling contract to call into the reentrancy checker with any call. The fallback function will receive
// this call and reenter the contract with the transaction data provided in 1. If that reentrancy call does not
// revert, then the reentrancy checker reverts the initial call, likely causeing the entire transaction to revert.
//
// Note: the reentrancy checker has a guard to prevent an infinite cycle of reentrancy. Inifinite cycles will run out
// of gas in all cases, potentially causing a revert when the contract is adequately protected from reentrancy.
contract ReentrancyChecker {
bytes public txnData;
bool hasBeenCalled;
// Used to prevent infinite cycles where the reentrancy is cycled forever.
modifier skipIfReentered {
if (hasBeenCalled) {
return;
}
hasBeenCalled = true;
_;
hasBeenCalled = false;
}
function setTransactionData(bytes memory _txnData) public {
txnData = _txnData;
}
function _executeCall(
address to,
uint256 value,
bytes memory data
) private returns (bool success) {
// Mostly copied from:
// solhint-disable-next-line max-line-length
// https://github.com/gnosis/safe-contracts/blob/59cfdaebcd8b87a0a32f87b50fead092c10d3a05/contracts/base/Executor.sol#L23-L31
// solhint-disable-next-line no-inline-assembly
assembly {
let inputData := add(data, 0x20)
let inputDataSize := mload(data)
success := call(gas(), to, value, inputData, inputDataSize, 0, 0)
}
}
fallback() external skipIfReentered {
// Attampt to re-enter with the set txnData.
bool success = _executeCall(msg.sender, 0, txnData);
// Fail if the call succeeds because that means the re-entrancy was successful.
require(!success, "Re-entrancy was successful");
}
}
pragma solidity ^0.6.0;
import "../implementation/Lockable.sol";
import "./ReentrancyAttack.sol";
// Tests reentrancy guards defined in Lockable.sol.
// Extends https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyMock.sol.
contract ReentrancyMock is Lockable {
uint256 public counter;
constructor() public {
counter = 0;
}
function callback() external nonReentrant {
_count();
}
function countAndSend(ReentrancyAttack attacker) external nonReentrant {
_count();
bytes4 func = bytes4(keccak256("callback()"));
attacker.callSender(func);
}
function countAndCall(ReentrancyAttack attacker) external nonReentrant {
_count();
bytes4 func = bytes4(keccak256("getCount()"));
attacker.callSender(func);
}
function countLocalRecursive(uint256 n) public nonReentrant {
if (n > 0) {
_count();
countLocalRecursive(n - 1);
}
}
function countThisRecursive(uint256 n) public nonReentrant {
if (n > 0) {
_count();
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1));
require(success, "ReentrancyMock: failed call");
}
}
function countLocalCall() public nonReentrant {
getCount();
}
function countThisCall() public nonReentrant {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(this).call(abi.encodeWithSignature("getCount()"));
require(success, "ReentrancyMock: failed call");
}
function getCount() public view nonReentrantView returns (uint256) {
return counter;
}
function _count() private {
counter += 1;
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/MultiRole.sol";
import "../interfaces/RegistryInterface.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
/**
* @title Registry for financial contracts and approved financial contract creators.
* @dev Maintains a whitelist of financial contract creators that are allowed
* to register new financial contracts and stores party members of a financial contract.
*/
contract Registry is RegistryInterface, MultiRole {
using SafeMath for uint256;
/****************************************
* INTERNAL VARIABLES AND STORAGE *
****************************************/
enum Roles {
Owner, // The owner manages the set of ContractCreators.
ContractCreator // Can register financial contracts.
}
// This enum is required because a `WasValid` state is required
// to ensure that financial contracts cannot be re-registered.
enum Validity { Invalid, Valid }
// Local information about a contract.
struct FinancialContract {
Validity valid;
uint128 index;
}
struct Party {
address[] contracts; // Each financial contract address is stored in this array.
// The address of each financial contract is mapped to its index for constant time look up and deletion.
mapping(address => uint256) contractIndex;
}
// Array of all contracts that are approved to use the UMA Oracle.
address[] public registeredContracts;
// Map of financial contract contracts to the associated FinancialContract struct.
mapping(address => FinancialContract) public contractMap;
// Map each party member to their their associated Party struct.
mapping(address => Party) private partyMap;
/****************************************
* EVENTS *
****************************************/
event NewContractRegistered(address indexed contractAddress, address indexed creator, address[] parties);
event PartyAdded(address indexed contractAddress, address indexed party);
event PartyRemoved(address indexed contractAddress, address indexed party);
/**
* @notice Construct the Registry contract.
*/
constructor() public {
_createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender);
// Start with no contract creators registered.
_createSharedRole(uint256(Roles.ContractCreator), uint256(Roles.Owner), new address[](0));
}
/****************************************
* REGISTRATION FUNCTIONS *
****************************************/
/**
* @notice Registers a new financial contract.
* @dev Only authorized contract creators can call this method.
* @param parties array of addresses who become parties in the contract.
* @param contractAddress address of the contract against which the parties are registered.
*/
function registerContract(address[] calldata parties, address contractAddress)
external
override
onlyRoleHolder(uint256(Roles.ContractCreator))
{
FinancialContract storage financialContract = contractMap[contractAddress];
require(contractMap[contractAddress].valid == Validity.Invalid, "Can only register once");
// Store contract address as a registered contract.
registeredContracts.push(contractAddress);
// No length check necessary because we should never hit (2^127 - 1) contracts.
financialContract.index = uint128(registeredContracts.length.sub(1));
// For all parties in the array add them to the contract's parties.
financialContract.valid = Validity.Valid;
for (uint256 i = 0; i < parties.length; i = i.add(1)) {
_addPartyToContract(parties[i], contractAddress);
}
emit NewContractRegistered(contractAddress, msg.sender, parties);
}
/**
* @notice Adds a party member to the calling contract.
* @dev msg.sender will be used to determine the contract that this party is added to.
* @param party new party for the calling contract.
*/
function addPartyToContract(address party) external override {
address contractAddress = msg.sender;
require(contractMap[contractAddress].valid == Validity.Valid, "Can only add to valid contract");
_addPartyToContract(party, contractAddress);
}
/**
* @notice Removes a party member from the calling contract.
* @dev msg.sender will be used to determine the contract that this party is removed from.
* @param partyAddress address to be removed from the calling contract.
*/
function removePartyFromContract(address partyAddress) external override {
address contractAddress = msg.sender;
Party storage party = partyMap[partyAddress];
uint256 numberOfContracts = party.contracts.length;
require(numberOfContracts != 0, "Party has no contracts");
require(contractMap[contractAddress].valid == Validity.Valid, "Remove only from valid contract");
require(isPartyMemberOfContract(partyAddress, contractAddress), "Can only remove existing party");
// Index of the current location of the contract to remove.
uint256 deleteIndex = party.contractIndex[contractAddress];
// Store the last contract's address to update the lookup map.
address lastContractAddress = party.contracts[numberOfContracts - 1];
// Swap the contract to be removed with the last contract.
party.contracts[deleteIndex] = lastContractAddress;
// Update the lookup index with the new location.
party.contractIndex[lastContractAddress] = deleteIndex;
// Pop the last contract from the array and update the lookup map.
party.contracts.pop();
delete party.contractIndex[contractAddress];
emit PartyRemoved(contractAddress, partyAddress);
}
/****************************************
* REGISTRY STATE GETTERS *
****************************************/
/**
* @notice Returns whether the contract has been registered with the registry.
* @dev If it is registered, it is an authorized participant in the UMA system.
* @param contractAddress address of the financial contract.
* @return bool indicates whether the contract is registered.
*/
function isContractRegistered(address contractAddress) external override view returns (bool) {
return contractMap[contractAddress].valid == Validity.Valid;
}
/**
* @notice Returns a list of all contracts that are associated with a particular party.
* @param party address of the party.
* @return an array of the contracts the party is registered to.
*/
function getRegisteredContracts(address party) external override view returns (address[] memory) {
return partyMap[party].contracts;
}
/**
* @notice Returns all registered contracts.
* @return all registered contract addresses within the system.
*/
function getAllRegisteredContracts() external override view returns (address[] memory) {
return registeredContracts;
}
/**
* @notice checks if an address is a party of a contract.
* @param party party to check.
* @param contractAddress address to check against the party.
* @return bool indicating if the address is a party of the contract.
*/
function isPartyMemberOfContract(address party, address contractAddress) public override view returns (bool) {
uint256 index = partyMap[party].contractIndex[contractAddress];
return partyMap[party].contracts.length > index && partyMap[party].contracts[index] == contractAddress;
}
/****************************************
* INTERNAL FUNCTIONS *
****************************************/
function _addPartyToContract(address party, address contractAddress) internal {
require(!isPartyMemberOfContract(party, contractAddress), "Can only register a party once");
uint256 contractIndex = partyMap[party].contracts.length;
partyMap[party].contracts.push(contractAddress);
partyMap[party].contractIndex[contractAddress] = contractIndex;
emit PartyAdded(contractAddress, party);
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
/**
* @title Interface for a registry of contracts and contract creators.
*/
interface RegistryInterface {
/**
* @notice Registers a new contract.
* @dev Only authorized contract creators can call this method.
* @param parties an array of addresses who become parties in the contract.
* @param contractAddress defines the address of the deployed contract.
*/
function registerContract(address[] calldata parties, address contractAddress) external;
/**
* @notice Returns whether the contract has been registered with the registry.
* @dev If it is registered, it is an authorized participant in the UMA system.
* @param contractAddress address of the contract.
* @return bool indicates whether the contract is registered.
*/
function isContractRegistered(address contractAddress) external view returns (bool);
/**
* @notice Returns a list of all contracts that are associated with a particular party.
* @param party address of the party.
* @return an array of the contracts the party is registered to.
*/
function getRegisteredContracts(address party) external view returns (address[] memory);
/**
* @notice Returns all registered contracts.
* @return all registered contract addresses within the system.
*/
function getAllRegisteredContracts() external view returns (address[] memory);
/**
* @notice Adds a party to the calling contract.
* @dev msg.sender must be the contract to which the party member is added.
* @param party address to be added to the contract.
*/
function addPartyToContract(address party) external;
/**
* @notice Removes a party member to the calling contract.
* @dev msg.sender must be the contract to which the party member is added.
* @param party address to be removed from the contract.
*/
function removePartyFromContract(address party) external;
/**
* @notice checks if an address is a party in a contract.
* @param party party to check.
* @param contractAddress address to check against the party.
* @return bool indicating if the address is a party of the contract.
*/
function isPartyMemberOfContract(address party, address contractAddress) external view returns (bool);
}
pragma solidity ^0.6.0;
import "../../common/implementation/FixedPoint.sol";
/**
* @title Computes vote results.
* @dev The result is the mode of the added votes. Otherwise, the vote is unresolved.
*/
library ResultComputation {
using FixedPoint for FixedPoint.Unsigned;
/****************************************
* INTERNAL LIBRARY DATA STRUCTURE *
****************************************/
struct Data {
// Maps price to number of tokens that voted for that price.
mapping(int256 => FixedPoint.Unsigned) voteFrequency;
// The total votes that have been added.
FixedPoint.Unsigned totalVotes;
// The price that is the current mode, i.e., the price with the highest frequency in `voteFrequency`.
int256 currentMode;
}
/****************************************
* VOTING FUNCTIONS *
****************************************/
/**
* @notice Adds a new vote to be used when computing the result.
* @param data contains information to which the vote is applied.
* @param votePrice value specified in the vote for the given `numberTokens`.
* @param numberTokens number of tokens that voted on the `votePrice`.
*/
function addVote(
Data storage data,
int256 votePrice,
FixedPoint.Unsigned memory numberTokens
) internal {
data.totalVotes = data.totalVotes.add(numberTokens);
data.voteFrequency[votePrice] = data.voteFrequency[votePrice].add(numberTokens);
if (
votePrice != data.currentMode &&
data.voteFrequency[votePrice].isGreaterThan(data.voteFrequency[data.currentMode])
) {
data.currentMode = votePrice;
}
}
/****************************************
* VOTING STATE GETTERS *
****************************************/
/**
* @notice Returns whether the result is resolved, and if so, what value it resolved to.
* @dev `price` should be ignored if `isResolved` is false.
* @param data contains information against which the `minVoteThreshold` is applied.
* @param minVoteThreshold min (exclusive) number of tokens that must have voted for the result to be valid. Can be
* used to enforce a minimum voter participation rate, regardless of how the votes are distributed.
* @return isResolved indicates if the price has been resolved correctly.
* @return price the price that the dvm resolved to.
*/
function getResolvedPrice(Data storage data, FixedPoint.Unsigned memory minVoteThreshold)
internal
view
returns (bool isResolved, int256 price)
{
FixedPoint.Unsigned memory modeThreshold = FixedPoint.fromUnscaledUint(50).div(100);
if (
data.totalVotes.isGreaterThan(minVoteThreshold) &&
data.voteFrequency[data.currentMode].div(data.totalVotes).isGreaterThan(modeThreshold)
) {
// `modeThreshold` and `minVoteThreshold` are exceeded, so the current mode is the resolved price.
isResolved = true;
price = data.currentMode;
} else {
isResolved = false;
}
}
/**
* @notice Checks whether a `voteHash` is considered correct.
* @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`.
* @param data contains information against which the `voteHash` is checked.
* @param voteHash committed hash submitted by the voter.
* @return bool true if the vote was correct.
*/
function wasVoteCorrect(Data storage data, bytes32 voteHash) internal view returns (bool) {
return voteHash == keccak256(abi.encode(data.currentMode));
}
/**
* @notice Gets the total number of tokens whose votes are considered correct.
* @dev Should only be called after a vote is resolved, i.e., via `getResolvedPrice`.
* @param data contains all votes against which the correctly voted tokens are counted.
* @return FixedPoint.Unsigned which indicates the frequency of the correctly voted tokens.
*/
function getTotalCorrectlyVotedTokens(Data storage data) internal view returns (FixedPoint.Unsigned memory) {
return data.voteFrequency[data.currentMode];
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../ResultComputation.sol";
import "../../../common/implementation/FixedPoint.sol";
// Wraps the library ResultComputation for testing purposes.
contract ResultComputationTest {
using ResultComputation for ResultComputation.Data;
ResultComputation.Data public data;
function wrapAddVote(int256 votePrice, uint256 numberTokens) external {
data.addVote(votePrice, FixedPoint.Unsigned(numberTokens));
}
function wrapGetResolvedPrice(uint256 minVoteThreshold) external view returns (bool isResolved, int256 price) {
return data.getResolvedPrice(FixedPoint.Unsigned(minVoteThreshold));
}
function wrapWasVoteCorrect(bytes32 revealHash) external view returns (bool) {
return data.wasVoteCorrect(revealHash);
}
function wrapGetTotalCorrectlyVotedTokens() external view returns (uint256) {
return data.getTotalCorrectlyVotedTokens().rawValue;
}
}
pragma solidity ^0.6.0;
import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
pragma solidity ^0.6.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "../../common/implementation/FixedPoint.sol";
import "../../common/implementation/MultiRole.sol";
import "../../common/implementation/Withdrawable.sol";
import "../../common/implementation/Testable.sol";
import "../interfaces/StoreInterface.sol";
/**
* @title An implementation of Store that can accept Oracle fees in ETH or any arbitrary ERC20 token.
*/
contract Store is StoreInterface, Withdrawable, Testable {
using SafeMath for uint256;
using FixedPoint for FixedPoint.Unsigned;
using FixedPoint for uint256;
using SafeERC20 for IERC20;
/****************************************
* INTERNAL VARIABLES AND STORAGE *
****************************************/
enum Roles { Owner, Withdrawer }
FixedPoint.Unsigned public fixedOracleFeePerSecondPerPfc; // Percentage of 1 E.g., .1 is 10% Oracle fee.
FixedPoint.Unsigned public weeklyDelayFeePerSecondPerPfc; // Percentage of 1 E.g., .1 is 10% weekly delay fee.
mapping(address => FixedPoint.Unsigned) public finalFees;
uint256 public constant SECONDS_PER_WEEK = 604800;
/****************************************
* EVENTS *
****************************************/
event NewFixedOracleFeePerSecondPerPfc(FixedPoint.Unsigned newOracleFee);
event NewWeeklyDelayFeePerSecondPerPfc(FixedPoint.Unsigned newWeeklyDelayFeePerSecondPerPfc);
event NewFinalFee(FixedPoint.Unsigned newFinalFee);
/**
* @notice Construct the Store contract.
*/
constructor(
FixedPoint.Unsigned memory _fixedOracleFeePerSecondPerPfc,
FixedPoint.Unsigned memory _weeklyDelayFeePerSecondPerPfc,
address _timerAddress
) public Testable(_timerAddress) {
_createExclusiveRole(uint256(Roles.Owner), uint256(Roles.Owner), msg.sender);
_createWithdrawRole(uint256(Roles.Withdrawer), uint256(Roles.Owner), msg.sender);
setFixedOracleFeePerSecondPerPfc(_fixedOracleFeePerSecondPerPfc);
setWeeklyDelayFeePerSecondPerPfc(_weeklyDelayFeePerSecondPerPfc);
}
/****************************************
* ORACLE FEE CALCULATION AND PAYMENT *
****************************************/
/**
* @notice Pays Oracle fees in ETH to the store.
* @dev To be used by contracts whose margin currency is ETH.
*/
function payOracleFees() external override payable {
require(msg.value > 0, "Value sent can't be zero");
}
/**
* @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 override {
IERC20 erc20 = IERC20(erc20Address);
require(amount.isGreaterThan(0), "Amount sent can't be zero");
erc20.safeTransferFrom(msg.sender, address(this), amount.rawValue);
}
/**
* @notice Computes the regular oracle fees that a contract should pay for a period.
* @dev The late penalty is similar to the regular fee in that is is charged per second over the period between
* startTime and endTime.
*
* The late penalty percentage increases over time as follows:
*
* - 0-1 week since startTime: no late penalty
*
* - 1-2 weeks since startTime: 1x late penalty percentage is applied
*
* - 2-3 weeks since startTime: 2x late penalty percentage is applied
*
* - ...
*
* @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 penalty percentage, if any, for paying the fee after the deadline.
*/
function computeRegularFee(
uint256 startTime,
uint256 endTime,
FixedPoint.Unsigned calldata pfc
) external override view returns (FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty) {
uint256 timeDiff = endTime.sub(startTime);
// Multiply by the unscaled `timeDiff` first, to get more accurate results.
regularFee = pfc.mul(timeDiff).mul(fixedOracleFeePerSecondPerPfc);
// Compute how long ago the start time was to compute the delay penalty.
uint256 paymentDelay = getCurrentTime().sub(startTime);
// Compute the additional percentage (per second) that will be charged because of the penalty.
// Note: if less than a week has gone by since the startTime, paymentDelay / SECONDS_PER_WEEK will truncate to
// 0, causing no penalty to be charged.
FixedPoint.Unsigned memory penaltyPercentagePerSecond = weeklyDelayFeePerSecondPerPfc.mul(
paymentDelay.div(SECONDS_PER_WEEK)
);
// Apply the penaltyPercentagePerSecond to the payment period.
latePenalty = pfc.mul(timeDiff).mul(penaltyPercentagePerSecond);
}
/**
* @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 denominated in units of `currency`.
*/
function computeFinalFee(address currency) external override view returns (FixedPoint.Unsigned memory) {
return finalFees[currency];
}
/****************************************
* ADMIN STATE MODIFYING FUNCTIONS *
****************************************/
/**
* @notice Sets a new oracle fee per second.
* @param newFixedOracleFeePerSecondPerPfc new fee per second charged to use the oracle.
*/
function setFixedOracleFeePerSecondPerPfc(FixedPoint.Unsigned memory newFixedOracleFeePerSecondPerPfc)
public
onlyRoleHolder(uint256(Roles.Owner))
{
// Oracle fees at or over 100% don't make sense.
require(newFixedOracleFeePerSecondPerPfc.isLessThan(1), "Fee must be < 100% per second.");
fixedOracleFeePerSecondPerPfc = newFixedOracleFeePerSecondPerPfc;
emit NewFixedOracleFeePerSecondPerPfc(newFixedOracleFeePerSecondPerPfc);
}
/**
* @notice Sets a new weekly delay fee.
* @param newWeeklyDelayFeePerSecondPerPfc fee escalation per week of late fee payment.
*/
function setWeeklyDelayFeePerSecondPerPfc(FixedPoint.Unsigned memory newWeeklyDelayFeePerSecondPerPfc)
public
onlyRoleHolder(uint256(Roles.Owner))
{
require(newWeeklyDelayFeePerSecondPerPfc.isLessThan(1), "weekly delay fee must be < 100%");
weeklyDelayFeePerSecondPerPfc = newWeeklyDelayFeePerSecondPerPfc;
emit NewWeeklyDelayFeePerSecondPerPfc(newWeeklyDelayFeePerSecondPerPfc);
}
/**
* @notice Sets a new final fee for a particular currency.
* @param currency defines the token currency used to pay the final fee.
* @param newFinalFee final fee amount.
*/
function setFinalFee(address currency, FixedPoint.Unsigned memory newFinalFee)
public
onlyRoleHolder(uint256(Roles.Owner))
{
finalFees[currency] = newFinalFee;
emit NewFinalFee(newFinalFee);
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../common/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);
}
pragma solidity ^0.6.0;
import "../../common/implementation/ExpandedERC20.sol";
import "../../common/implementation/Lockable.sol";
/**
* @title Burnable and mintable ERC20.
* @dev The contract deployer will initially be the only minter, burner and owner capable of adding new roles.
*/
contract SyntheticToken is ExpandedERC20, Lockable {
/**
* @notice Constructs the SyntheticToken.
* @param tokenName The name which describes the new token.
* @param tokenSymbol The ticker abbreviation of the name. Ideally < 5 chars.
* @param tokenDecimals The number of decimals to define token precision.
*/
constructor(
string memory tokenName,
string memory tokenSymbol,
uint8 tokenDecimals
) public ExpandedERC20(tokenName, tokenSymbol, tokenDecimals) nonReentrant() {}
/**
* @notice Add Minter role to account.
* @dev The caller must have the Owner role.
* @param account The address to which the Minter role is added.
*/
function addMinter(address account) external nonReentrant() {
addMember(uint256(Roles.Minter), account);
}
/**
* @notice Remove Minter role from account.
* @dev The caller must have the Owner role.
* @param account The address from which the Minter role is removed.
*/
function removeMinter(address account) external nonReentrant() {
removeMember(uint256(Roles.Minter), account);
}
/**
* @notice Add Burner role to account.
* @dev The caller must have the Owner role.
* @param account The address to which the Burner role is added.
*/
function addBurner(address account) external nonReentrant() {
addMember(uint256(Roles.Burner), account);
}
/**
* @notice Removes Burner role from account.
* @dev The caller must have the Owner role.
* @param account The address from which the Burner role is removed.
*/
function removeBurner(address account) external nonReentrant() {
removeMember(uint256(Roles.Burner), account);
}
/**
* @notice Reset Owner role to account.
* @dev The caller must have the Owner role.
* @param account The new holder of the Owner role.
*/
function resetOwner(address account) external nonReentrant() {
resetMember(uint256(Roles.Owner), account);
}
/**
* @notice Checks if a given account holds the Minter role.
* @param account The address which is checked for the Minter role.
* @return bool True if the provided account is a Minter.
*/
function isMinter(address account) public view nonReentrantView() returns (bool) {
return holdsRole(uint256(Roles.Minter), account);
}
/**
* @notice Checks if a given account holds the Burner role.
* @param account The address which is checked for the Burner role.
* @return bool True if the provided account is a Burner.
*/
function isBurner(address account) public view nonReentrantView() returns (bool) {
return holdsRole(uint256(Roles.Burner), account);
}
}
pragma solidity ^0.6.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 on the test network, 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) internal {
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 returns (uint256) {
if (timerAddress != address(0x0)) {
return Timer(timerAddress).getCurrentTime();
} else {
return now; // solhint-disable-line not-rely-on-time
}
}
}
pragma solidity ^0.6.0;
import "../implementation/Testable.sol";
// TestableTest is derived from the abstract contract Testable for testing purposes.
contract TestableTest is Testable {
// solhint-disable-next-line no-empty-blocks
constructor(address _timerAddress) public Testable(_timerAddress) {}
function getTestableTimeAndBlockTime() external view returns (uint256 testableTime, uint256 blockTime) {
// solhint-disable-next-line not-rely-on-time
return (getCurrentTime(), now);
}
}
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @title An implementation of ERC20 with the same interface as the Compound project's testnet tokens (mainly DAI)
* @dev This contract can be deployed or the interface can be used to communicate with Compound's ERC20 tokens. Note:
* this token should never be used to store real value since it allows permissionless minting.
*/
contract TestnetERC20 is ERC20 {
/**
* @notice Constructs the TestnetERC20.
* @param _name The name which describes the new token.
* @param _symbol The ticker abbreviation of the name. Ideally < 5 chars.
* @param _decimals The number of decimals to define token precision.
*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) public ERC20(_name, _symbol) {
_setupDecimals(_decimals);
}
// Sample token information.
/**
* @notice Mints value tokens to the owner address.
* @param ownerAddress the address to mint to.
* @param value the amount of tokens to mint.
*/
function allocateTo(address ownerAddress, uint256 value) external {
_mint(ownerAddress, value);
}
}
pragma solidity ^0.6.0;
/**
* @title Universal store of current contract time for testing environments.
*/
contract Timer {
uint256 private currentTime;
constructor() public {
currentTime = now; // 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 current time. Will return the last time set in `setCurrentTime` if running in test mode.
* Otherwise, it will return the block timestamp.
* @return uint256 for the current Testable timestamp.
*/
function getCurrentTime() public view returns (uint256) {
return currentTime;
}
}
pragma solidity ^0.6.0;
import "./SyntheticToken.sol";
import "../../common/interfaces/ExpandedIERC20.sol";
import "../../common/implementation/Lockable.sol";
/**
* @title Factory for creating new mintable and burnable tokens.
*/
contract TokenFactory is Lockable {
/**
* @notice Create a new token and return it to the caller.
* @dev The caller will become the only minter and burner and the new owner capable of assigning the roles.
* @param tokenName used to describe the new token.
* @param tokenSymbol short ticker abbreviation of the name. Ideally < 5 chars.
* @param tokenDecimals used to define the precision used in the token's numerical representation.
* @return newToken an instance of the newly created token interface.
*/
function createToken(
string calldata tokenName,
string calldata tokenSymbol,
uint8 tokenDecimals
) external nonReentrant() returns (ExpandedIERC20 newToken) {
SyntheticToken mintableToken = new SyntheticToken(tokenName, tokenSymbol, tokenDecimals);
mintableToken.addMinter(msg.sender);
mintableToken.addBurner(msg.sender);
mintableToken.resetOwner(msg.sender);
newToken = ExpandedIERC20(address(mintableToken));
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/FixedPoint.sol";
import "../../common/interfaces/ExpandedIERC20.sol";
import "./VotingToken.sol";
/**
* @title Migration contract for VotingTokens.
* @dev Handles migrating token holders from one token to the next.
*/
contract TokenMigrator {
using FixedPoint for FixedPoint.Unsigned;
/****************************************
* INTERNAL VARIABLES AND STORAGE *
****************************************/
VotingToken public oldToken;
ExpandedIERC20 public newToken;
uint256 public snapshotId;
FixedPoint.Unsigned public rate;
mapping(address => bool) public hasMigrated;
/**
* @notice Construct the TokenMigrator contract.
* @dev This function triggers the snapshot upon which all migrations will be based.
* @param _rate the number of old tokens it takes to generate one new token.
* @param _oldToken address of the token being migrated from.
* @param _newToken address of the token being migrated to.
*/
constructor(
FixedPoint.Unsigned memory _rate,
address _oldToken,
address _newToken
) public {
// Prevents division by 0 in migrateTokens().
// Also it doesn’t make sense to have “0 old tokens equate to 1 new token”.
require(_rate.isGreaterThan(0), "Rate can't be 0");
rate = _rate;
newToken = ExpandedIERC20(_newToken);
oldToken = VotingToken(_oldToken);
snapshotId = oldToken.snapshot();
}
/**
* @notice Migrates the tokenHolder's old tokens to new tokens.
* @dev This function can only be called once per `tokenHolder`. Anyone can call this method
* on behalf of any other token holder since there is no disadvantage to receiving the tokens earlier.
* @param tokenHolder address of the token holder to migrate.
*/
function migrateTokens(address tokenHolder) external {
require(!hasMigrated[tokenHolder], "Already migrated tokens");
hasMigrated[tokenHolder] = true;
FixedPoint.Unsigned memory oldBalance = FixedPoint.Unsigned(oldToken.balanceOfAt(tokenHolder, snapshotId));
if (!oldBalance.isGreaterThan(0)) {
return;
}
FixedPoint.Unsigned memory newBalance = oldBalance.div(rate);
require(newToken.mint(tokenHolder, newBalance.rawValue), "Mint failed");
}
}
pragma solidity ^0.6.0;
import "../oracle/implementation/Finder.sol";
import "../oracle/implementation/Constants.sol";
import "../oracle/implementation/Voting.sol";
/**
* @title A contract to track a whitelist of addresses.
*/
contract Umip3Upgrader {
// Existing governor is the only one who can initiate the upgrade.
address public existingGovernor;
// Existing Voting contract needs to be informed of the address of the new Voting contract.
Voting public existingVoting;
// New governor will be the new owner of the finder.
address public newGovernor;
// Finder contract to push upgrades to.
Finder public finder;
// Addresses to upgrade.
address public voting;
address public identifierWhitelist;
address public store;
address public financialContractsAdmin;
address public registry;
constructor(
address _existingGovernor,
address _existingVoting,
address _finder,
address _voting,
address _identifierWhitelist,
address _store,
address _financialContractsAdmin,
address _registry,
address _newGovernor
) public {
existingGovernor = _existingGovernor;
existingVoting = Voting(_existingVoting);
finder = Finder(_finder);
voting = _voting;
identifierWhitelist = _identifierWhitelist;
store = _store;
financialContractsAdmin = _financialContractsAdmin;
registry = _registry;
newGovernor = _newGovernor;
}
function upgrade() external {
require(msg.sender == existingGovernor, "Upgrade can only be initiated by the existing governor.");
// Change the addresses in the Finder.
finder.changeImplementationAddress(OracleInterfaces.Oracle, voting);
finder.changeImplementationAddress(OracleInterfaces.IdentifierWhitelist, identifierWhitelist);
finder.changeImplementationAddress(OracleInterfaces.Store, store);
finder.changeImplementationAddress(OracleInterfaces.FinancialContractsAdmin, financialContractsAdmin);
finder.changeImplementationAddress(OracleInterfaces.Registry, registry);
// Transfer the ownership of the Finder to the new Governor now that all the addresses have been updated.
finder.transferOwnership(newGovernor);
// Inform the existing Voting contract of the address of the new Voting contract and transfer its
// ownership to the new governor to allow for any future changes to the migrated contract.
existingVoting.setMigrated(voting);
existingVoting.transferOwnership(newGovernor);
}
}
pragma solidity ^0.6.0;
/**
* @title Interface for Uniswap v2.
* @dev This only contains the methods/events that we use in our contracts or offchain infrastructure.
*/
abstract contract Uniswap {
// Called after every swap showing the new uniswap "price" for this token pair.
event Sync(uint112 reserve0, uint112 reserve1);
}
pragma solidity ^0.6.0;
import "../interfaces/Uniswap.sol";
/**
* @title Uniswap v2 Mock that allows manual price injection.
*/
contract UniswapMock is Uniswap {
function setPrice(uint112 reserve0, uint112 reserve1) external {
emit Sync(reserve0, reserve1);
}
}
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../interfaces/VotingInterface.sol";
/**
* @title Library to compute rounds and phases for an equal length commit-reveal voting cycle.
*/
library VoteTiming {
using SafeMath for uint256;
struct Data {
uint256 phaseLength;
}
/**
* @notice Initializes the data object. Sets the phase length based on the input.
*/
function init(Data storage data, uint256 phaseLength) internal {
// This should have a require message but this results in an internal Solidity error.
require(phaseLength > 0);
data.phaseLength = phaseLength;
}
/**
* @notice Computes the roundID based off the current time as floor(timestamp/roundLength).
* @dev The round ID depends on the global timestamp but not on the lifetime of the system.
* The consequence is that the initial round ID starts at an arbitrary number (that increments, as expected, for subsequent rounds) instead of zero or one.
* @param data input data object.
* @param currentTime input unix timestamp used to compute the current roundId.
* @return roundId defined as a function of the currentTime and `phaseLength` from `data`.
*/
function computeCurrentRoundId(Data storage data, uint256 currentTime) internal view returns (uint256) {
uint256 roundLength = data.phaseLength.mul(uint256(VotingInterface.Phase.NUM_PHASES_PLACEHOLDER));
return currentTime.div(roundLength);
}
/**
* @notice compute the round end time as a function of the round Id.
* @param data input data object.
* @param roundId uniquely identifies the current round.
* @return timestamp unix time of when the current round will end.
*/
function computeRoundEndTime(Data storage data, uint256 roundId) internal view returns (uint256) {
uint256 roundLength = data.phaseLength.mul(uint256(VotingInterface.Phase.NUM_PHASES_PLACEHOLDER));
return roundLength.mul(roundId.add(1));
}
/**
* @notice Computes the current phase based only on the current time.
* @param data input data object.
* @param currentTime input unix timestamp used to compute the current roundId.
* @return current voting phase based on current time and vote phases configuration.
*/
function computeCurrentPhase(Data storage data, uint256 currentTime) internal view returns (VotingInterface.Phase) {
// This employs some hacky casting. We could make this an if-statement if we're worried about type safety.
return
VotingInterface.Phase(
currentTime.div(data.phaseLength).mod(uint256(VotingInterface.Phase.NUM_PHASES_PLACEHOLDER))
);
}
}
pragma solidity ^0.6.0;
import "../../interfaces/VotingInterface.sol";
import "../VoteTiming.sol";
// Wraps the library VoteTiming for testing purposes.
contract VoteTimingTest {
using VoteTiming for VoteTiming.Data;
VoteTiming.Data public voteTiming;
constructor(uint256 phaseLength) public {
wrapInit(phaseLength);
}
function wrapComputeCurrentRoundId(uint256 currentTime) external view returns (uint256) {
return voteTiming.computeCurrentRoundId(currentTime);
}
function wrapComputeCurrentPhase(uint256 currentTime) external view returns (VotingInterface.Phase) {
return voteTiming.computeCurrentPhase(currentTime);
}
function wrapInit(uint256 phaseLength) public {
voteTiming.init(phaseLength);
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/FixedPoint.sol";
import "../../common/implementation/Testable.sol";
import "../interfaces/FinderInterface.sol";
import "../interfaces/OracleInterface.sol";
import "../interfaces/VotingInterface.sol";
import "../interfaces/IdentifierWhitelistInterface.sol";
import "./Registry.sol";
import "./ResultComputation.sol";
import "./VoteTiming.sol";
import "./VotingToken.sol";
import "./Constants.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/cryptography/ECDSA.sol";
/**
* @title Voting system for Oracle.
* @dev Handles receiving and resolving price requests via a commit-reveal voting scheme.
*/
contract Voting is Testable, Ownable, OracleInterface, VotingInterface {
using FixedPoint for FixedPoint.Unsigned;
using SafeMath for uint256;
using VoteTiming for VoteTiming.Data;
using ResultComputation for ResultComputation.Data;
/****************************************
* VOTING DATA STRUCTURES *
****************************************/
// Identifies a unique price request for which the Oracle will always return the same value.
// Tracks ongoing votes as well as the result of the vote.
struct PriceRequest {
bytes32 identifier;
uint256 time;
// A map containing all votes for this price in various rounds.
mapping(uint256 => VoteInstance) voteInstances;
// If in the past, this was the voting round where this price was resolved. If current or the upcoming round,
// this is the voting round where this price will be voted on, but not necessarily resolved.
uint256 lastVotingRound;
// The index in the `pendingPriceRequests` that references this PriceRequest. A value of UINT_MAX means that
// this PriceRequest is resolved and has been cleaned up from `pendingPriceRequests`.
uint256 index;
}
struct VoteInstance {
// Maps (voterAddress) to their submission.
mapping(address => VoteSubmission) voteSubmissions;
// The data structure containing the computed voting results.
ResultComputation.Data resultComputation;
}
struct VoteSubmission {
// A bytes32 of `0` indicates no commit or a commit that was already revealed.
bytes32 commit;
// The hash of the value that was revealed.
// Note: this is only used for computation of rewards.
bytes32 revealHash;
}
struct Round {
uint256 snapshotId; // Voting token snapshot ID for this round. 0 if no snapshot has been taken.
FixedPoint.Unsigned inflationRate; // Inflation rate set for this round.
FixedPoint.Unsigned gatPercentage; // Gat rate set for this round.
uint256 rewardsExpirationTime; // Time that rewards for this round can be claimed until.
}
// Represents the status a price request has.
enum RequestStatus {
NotRequested, // Was never requested.
Active, // Is being voted on in the current round.
Resolved, // Was resolved in a previous round.
Future // Is scheduled to be voted on in a future round.
}
// Only used as a return value in view methods -- never stored in the contract.
struct RequestState {
RequestStatus status;
uint256 lastVotingRound;
}
/****************************************
* INTERNAL TRACKING *
****************************************/
// Maps round numbers to the rounds.
mapping(uint256 => Round) public rounds;
// Maps price request IDs to the PriceRequest struct.
mapping(bytes32 => PriceRequest) private priceRequests;
// Price request ids for price requests that haven't yet been marked as resolved.
// These requests may be for future rounds.
bytes32[] internal pendingPriceRequests;
VoteTiming.Data public voteTiming;
// Percentage of the total token supply that must be used in a vote to
// create a valid price resolution. 1 == 100%.
FixedPoint.Unsigned public gatPercentage;
// Global setting for the rate of inflation per vote. This is the percentage of the snapshotted total supply that
// should be split among the correct voters.
// Note: this value is used to set per-round inflation at the beginning of each round. 1 = 100%.
FixedPoint.Unsigned public inflationRate;
// Time in seconds from the end of the round in which a price request is
// resolved that voters can still claim their rewards.
uint256 public rewardsExpirationTimeout;
// Reference to the voting token.
VotingToken public votingToken;
// Reference to the Finder.
FinderInterface private finder;
// If non-zero, this contract has been migrated to this address. All voters and
// financial contracts should query the new address only.
address public migratedAddress;
// Max value of an unsigned integer.
uint256 private constant UINT_MAX = ~uint256(0);
bytes32 public snapshotMessageHash = ECDSA.toEthSignedMessageHash(keccak256(bytes("Sign For Snapshot")));
/***************************************
* EVENTS *
****************************************/
event VoteCommitted(address indexed voter, uint256 indexed roundId, bytes32 indexed identifier, uint256 time);
event EncryptedVote(
address indexed voter,
uint256 indexed roundId,
bytes32 indexed identifier,
uint256 time,
bytes encryptedVote
);
event VoteRevealed(
address indexed voter,
uint256 indexed roundId,
bytes32 indexed identifier,
uint256 time,
int256 price,
uint256 numTokens
);
event RewardsRetrieved(
address indexed voter,
uint256 indexed roundId,
bytes32 indexed identifier,
uint256 time,
uint256 numTokens
);
event PriceRequestAdded(uint256 indexed roundId, bytes32 indexed identifier, uint256 time);
event PriceResolved(uint256 indexed roundId, bytes32 indexed identifier, uint256 time, int256 price);
/**
* @notice Construct the Voting contract.
* @param _phaseLength length of the commit and reveal phases in seconds.
* @param _gatPercentage of the total token supply that must be used in a vote to create a valid price resolution.
* @param _inflationRate percentage inflation per round used to increase token supply of correct voters.
* @param _rewardsExpirationTimeout timeout, in seconds, within which rewards must be claimed.
* @param _votingToken address of the UMA token contract used to commit votes.
* @param _finder keeps track of all contracts within the system based on their interfaceName.
* @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(
uint256 _phaseLength,
FixedPoint.Unsigned memory _gatPercentage,
FixedPoint.Unsigned memory _inflationRate,
uint256 _rewardsExpirationTimeout,
address _votingToken,
address _finder,
address _timerAddress
) public Testable(_timerAddress) {
voteTiming.init(_phaseLength);
require(_gatPercentage.isLessThanOrEqual(1), "GAT percentage must be <= 100%");
gatPercentage = _gatPercentage;
inflationRate = _inflationRate;
votingToken = VotingToken(_votingToken);
finder = FinderInterface(_finder);
rewardsExpirationTimeout = _rewardsExpirationTimeout;
}
/***************************************
MODIFIERS
****************************************/
modifier onlyRegisteredContract() {
if (migratedAddress != address(0)) {
require(msg.sender == migratedAddress, "Caller must be migrated address");
} else {
Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry));
require(registry.isContractRegistered(msg.sender), "Called must be registered");
}
_;
}
modifier onlyIfNotMigrated() {
require(migratedAddress == address(0), "Only call this if not migrated");
_;
}
/****************************************
* PRICE REQUEST AND ACCESS FUNCTIONS *
****************************************/
/**
* @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 time unix timestamp for the price request.
*/
function requestPrice(bytes32 identifier, uint256 time) external override onlyRegisteredContract() {
uint256 blockTime = getCurrentTime();
require(time <= blockTime, "Can only request in past");
require(_getIdentifierWhitelist().isIdentifierSupported(identifier), "Unsupported identifier request");
bytes32 priceRequestId = _encodePriceRequest(identifier, time);
PriceRequest storage priceRequest = priceRequests[priceRequestId];
uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime);
RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId);
if (requestStatus == RequestStatus.NotRequested) {
// Price has never been requested.
// Price requests always go in the next round, so add 1 to the computed current round.
uint256 nextRoundId = currentRoundId.add(1);
priceRequests[priceRequestId] = PriceRequest({
identifier: identifier,
time: time,
lastVotingRound: nextRoundId,
index: pendingPriceRequests.length
});
pendingPriceRequests.push(priceRequestId);
emit PriceRequestAdded(nextRoundId, identifier, time);
}
}
/**
* @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 of for the price request.
* @return _hasPrice bool if the DVM has resolved to a price for the given identifier and timestamp.
*/
function hasPrice(bytes32 identifier, uint256 time) external override view onlyRegisteredContract() returns (bool) {
(bool _hasPrice, , ) = _getPriceOrError(identifier, time);
return _hasPrice;
}
/**
* @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 of for the price request.
* @return int256 representing the resolved price for the given identifier and timestamp.
*/
function getPrice(bytes32 identifier, uint256 time)
external
override
view
onlyRegisteredContract()
returns (int256)
{
(bool _hasPrice, int256 price, string memory message) = _getPriceOrError(identifier, time);
// If the price wasn't available, revert with the provided message.
require(_hasPrice, message);
return price;
}
/**
* @notice Gets the status of a list of price requests, identified by their identifier and time.
* @dev If the status for a particular request is NotRequested, the lastVotingRound will always be 0.
* @param requests array of type PendingRequest which includes an identifier and timestamp for each request.
* @return requestStates a list, in the same order as the input list, giving the status of each of the specified price requests.
*/
function getPriceRequestStatuses(PendingRequest[] memory requests) public view returns (RequestState[] memory) {
RequestState[] memory requestStates = new RequestState[](requests.length);
uint256 currentRoundId = voteTiming.computeCurrentRoundId(getCurrentTime());
for (uint256 i = 0; i < requests.length; i++) {
PriceRequest storage priceRequest = _getPriceRequest(requests[i].identifier, requests[i].time);
RequestStatus status = _getRequestStatus(priceRequest, currentRoundId);
// If it's an active request, its true lastVotingRound is the current one, even if it hasn't been updated.
if (status == RequestStatus.Active) {
requestStates[i].lastVotingRound = currentRoundId;
} else {
requestStates[i].lastVotingRound = priceRequest.lastVotingRound;
}
requestStates[i].status = status;
}
return requestStates;
}
/****************************************
* VOTING FUNCTIONS *
****************************************/
/**
* @notice Commit a vote for a price request for `identifier` at `time`.
* @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase.
* Commits can be changed.
* @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior,
* voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then
* they can determine the vote pre-reveal.
* @param identifier uniquely identifies the committed vote. EG BTC/USD price pair.
* @param time unix timestamp of the price being voted on.
* @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`.
*/
function commitVote(
bytes32 identifier,
uint256 time,
bytes32 hash
) public override onlyIfNotMigrated() {
require(hash != bytes32(0), "Invalid provided hash");
// Current time is required for all vote timing queries.
uint256 blockTime = getCurrentTime();
require(voteTiming.computeCurrentPhase(blockTime) == Phase.Commit, "Cannot commit in reveal phase");
// At this point, the computed and last updated round ID should be equal.
uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime);
PriceRequest storage priceRequest = _getPriceRequest(identifier, time);
require(
_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active,
"Cannot commit inactive request"
);
priceRequest.lastVotingRound = currentRoundId;
VoteInstance storage voteInstance = priceRequest.voteInstances[currentRoundId];
voteInstance.voteSubmissions[msg.sender].commit = hash;
emit VoteCommitted(msg.sender, currentRoundId, identifier, time);
}
/**
* @notice Snapshot the current round's token balances and lock in the inflation rate and GAT.
* @dev This function can be called multiple times, but only the first call per round into this function or `revealVote`
* will create the round snapshot. Any later calls will be a no-op. Will revert unless called during reveal period.
* @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the
* snapshot.
*/
function snapshotCurrentRound(bytes calldata signature) external override onlyIfNotMigrated() {
uint256 blockTime = getCurrentTime();
require(voteTiming.computeCurrentPhase(blockTime) == Phase.Reveal, "Only snapshot in reveal phase");
// Require public snapshot require signature to ensure caller is an EOA.
require(ECDSA.recover(snapshotMessageHash, signature) == msg.sender, "Signature must match sender");
uint256 roundId = voteTiming.computeCurrentRoundId(blockTime);
_freezeRoundVariables(roundId);
}
/**
* @notice Reveal a previously committed vote for `identifier` at `time`.
* @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash`
* that `commitVote()` was called with. Only the committer can reveal their vote.
* @param identifier voted on in the commit phase. EG BTC/USD price pair.
* @param time specifies the unix timestamp of the price being voted on.
* @param price voted on during the commit phase.
* @param salt value used to hide the commitment price during the commit phase.
*/
function revealVote(
bytes32 identifier,
uint256 time,
int256 price,
int256 salt
) public override onlyIfNotMigrated() {
uint256 blockTime = getCurrentTime();
require(voteTiming.computeCurrentPhase(blockTime) == Phase.Reveal, "Cannot reveal in commit phase");
// Note: computing the current round is required to disallow people from
// revealing an old commit after the round is over.
uint256 roundId = voteTiming.computeCurrentRoundId(blockTime);
PriceRequest storage priceRequest = _getPriceRequest(identifier, time);
VoteInstance storage voteInstance = priceRequest.voteInstances[roundId];
VoteSubmission storage voteSubmission = voteInstance.voteSubmissions[msg.sender];
// 0 hashes are disallowed in the commit phase, so they indicate a different error.
// Cannot reveal an uncommitted or previously revealed hash
require(voteSubmission.commit != bytes32(0), "Invalid hash reveal");
require(
keccak256(abi.encodePacked(price, salt, msg.sender, time, roundId, identifier)) == voteSubmission.commit,
"Revealed data != commit hash"
);
// To protect against flash loans, we require snapshot be validated as EOA.
require(rounds[roundId].snapshotId != 0, "Round has no snapshot");
// Get the frozen snapshotId
uint256 snapshotId = rounds[roundId].snapshotId;
delete voteSubmission.commit;
// Get the voter's snapshotted balance. Since balances are returned pre-scaled by 10**18, we can directly
// initialize the Unsigned value with the returned uint.
FixedPoint.Unsigned memory balance = FixedPoint.Unsigned(votingToken.balanceOfAt(msg.sender, snapshotId));
// Set the voter's submission.
voteSubmission.revealHash = keccak256(abi.encode(price));
// Add vote to the results.
voteInstance.resultComputation.addVote(price, balance);
emit VoteRevealed(msg.sender, roundId, identifier, time, price, balance.rawValue);
}
/**
* @notice commits a vote and logs an event with a data blob, typically an encrypted version of the vote
* @dev An encrypted version of the vote is emitted in an event `EncryptedVote` to allow off-chain infrastructure to
* retrieve the commit. The contents of `encryptedVote` are never used on chain: it is purely for convenience.
* @param identifier unique price pair identifier. Eg: BTC/USD price pair.
* @param time unix timestamp of for the price request.
* @param hash keccak256 hash of the price you want to vote for and a `int256 salt`.
* @param encryptedVote offchain encrypted blob containing the voters amount, time and salt.
*/
function commitAndEmitEncryptedVote(
bytes32 identifier,
uint256 time,
bytes32 hash,
bytes memory encryptedVote
) public {
commitVote(identifier, time, hash);
uint256 roundId = voteTiming.computeCurrentRoundId(getCurrentTime());
emit EncryptedVote(msg.sender, roundId, identifier, time, encryptedVote);
}
/**
* @notice Submit a batch of commits in a single transaction.
* @dev Using `encryptedVote` is optional. If included then commitment is emitted in an event.
* Look at `project-root/common/Constants.js` for the tested maximum number of
* commitments that can fit in one transaction.
* @param commits struct to encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`.
*/
function batchCommit(Commitment[] calldata commits) external override {
for (uint256 i = 0; i < commits.length; i++) {
if (commits[i].encryptedVote.length == 0) {
commitVote(commits[i].identifier, commits[i].time, commits[i].hash);
} else {
commitAndEmitEncryptedVote(
commits[i].identifier,
commits[i].time,
commits[i].hash,
commits[i].encryptedVote
);
}
}
}
/**
* @notice Reveal multiple votes in a single transaction.
* Look at `project-root/common/Constants.js` for the tested maximum number of reveals.
* that can fit in one transaction.
* @dev For more information on reveals, review the comment for `revealVote`.
* @param reveals array of the Reveal struct which contains an identifier, time, price and salt.
*/
function batchReveal(Reveal[] calldata reveals) external override {
for (uint256 i = 0; i < reveals.length; i++) {
revealVote(reveals[i].identifier, reveals[i].time, reveals[i].price, reveals[i].salt);
}
}
/**
* @notice Retrieves rewards owed for a set of resolved price requests.
* @dev Can only retrieve rewards if calling for a valid round and if the
* call is done within the timeout threshold (not expired).
* @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller.
* @param roundId the round from which voting rewards will be retrieved from.
* @param toRetrieve array of PendingRequests which rewards are retrieved from.
* @return totalRewardToIssue total amount of rewards returned to the voter.
*/
function retrieveRewards(
address voterAddress,
uint256 roundId,
PendingRequest[] memory toRetrieve
) public override returns (FixedPoint.Unsigned memory totalRewardToIssue) {
if (migratedAddress != address(0)) {
require(msg.sender == migratedAddress, "Can only call from migrated");
}
uint256 blockTime = getCurrentTime();
require(roundId < voteTiming.computeCurrentRoundId(blockTime), "Invalid roundId");
Round storage round = rounds[roundId];
bool isExpired = blockTime > round.rewardsExpirationTime;
FixedPoint.Unsigned memory snapshotBalance = FixedPoint.Unsigned(
votingToken.balanceOfAt(voterAddress, round.snapshotId)
);
// Compute the total amount of reward that will be issued for each of the votes in the round.
FixedPoint.Unsigned memory snapshotTotalSupply = FixedPoint.Unsigned(
votingToken.totalSupplyAt(round.snapshotId)
);
FixedPoint.Unsigned memory totalRewardPerVote = round.inflationRate.mul(snapshotTotalSupply);
// Keep track of the voter's accumulated token reward.
totalRewardToIssue = FixedPoint.Unsigned(0);
for (uint256 i = 0; i < toRetrieve.length; i++) {
PriceRequest storage priceRequest = _getPriceRequest(toRetrieve[i].identifier, toRetrieve[i].time);
VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound];
// Only retrieve rewards for votes resolved in same round
require(priceRequest.lastVotingRound == roundId, "Retrieve for votes same round");
_resolvePriceRequest(priceRequest, voteInstance);
if (voteInstance.voteSubmissions[voterAddress].revealHash == 0) {
continue;
} else if (isExpired) {
// Emit a 0 token retrieval on expired rewards.
emit RewardsRetrieved(voterAddress, roundId, toRetrieve[i].identifier, toRetrieve[i].time, 0);
} else if (
voteInstance.resultComputation.wasVoteCorrect(voteInstance.voteSubmissions[voterAddress].revealHash)
) {
// The price was successfully resolved during the voter's last voting round, the voter revealed
// and was correct, so they are eligible for a reward.
// Compute the reward and add to the cumulative reward.
FixedPoint.Unsigned memory reward = snapshotBalance.mul(totalRewardPerVote).div(
voteInstance.resultComputation.getTotalCorrectlyVotedTokens()
);
totalRewardToIssue = totalRewardToIssue.add(reward);
// Emit reward retrieval for this vote.
emit RewardsRetrieved(
voterAddress,
roundId,
toRetrieve[i].identifier,
toRetrieve[i].time,
reward.rawValue
);
} else {
// Emit a 0 token retrieval on incorrect votes.
emit RewardsRetrieved(voterAddress, roundId, toRetrieve[i].identifier, toRetrieve[i].time, 0);
}
// Delete the submission to capture any refund and clean up storage.
delete voteInstance.voteSubmissions[voterAddress].revealHash;
}
// Issue any accumulated rewards.
if (totalRewardToIssue.isGreaterThan(0)) {
require(votingToken.mint(voterAddress, totalRewardToIssue.rawValue), "Voting token issuance failed");
}
}
/****************************************
* VOTING GETTER FUNCTIONS *
****************************************/
/**
* @notice Gets the queries that are being voted on this round.
* @return pendingRequests array containing identifiers of type `PendingRequest`.
* and timestamps for all pending requests.
*/
function getPendingRequests() external override view returns (PendingRequest[] memory) {
uint256 blockTime = getCurrentTime();
uint256 currentRoundId = voteTiming.computeCurrentRoundId(blockTime);
// Solidity memory arrays aren't resizable (and reading storage is expensive). Hence this hackery to filter
// `pendingPriceRequests` only to those requests that have an Active RequestStatus.
PendingRequest[] memory unresolved = new PendingRequest[](pendingPriceRequests.length);
uint256 numUnresolved = 0;
for (uint256 i = 0; i < pendingPriceRequests.length; i++) {
PriceRequest storage priceRequest = priceRequests[pendingPriceRequests[i]];
if (_getRequestStatus(priceRequest, currentRoundId) == RequestStatus.Active) {
unresolved[numUnresolved] = PendingRequest({
identifier: priceRequest.identifier,
time: priceRequest.time
});
numUnresolved++;
}
}
PendingRequest[] memory pendingRequests = new PendingRequest[](numUnresolved);
for (uint256 i = 0; i < numUnresolved; i++) {
pendingRequests[i] = unresolved[i];
}
return pendingRequests;
}
/**
* @notice Returns the current voting phase, as a function of the current time.
* @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES_PLACEHOLDER }.
*/
function getVotePhase() external override view returns (Phase) {
return voteTiming.computeCurrentPhase(getCurrentTime());
}
/**
* @notice Returns the current round ID, as a function of the current time.
* @return uint256 representing the unique round ID.
*/
function getCurrentRoundId() external override view returns (uint256) {
return voteTiming.computeCurrentRoundId(getCurrentTime());
}
/****************************************
* OWNER ADMIN FUNCTIONS *
****************************************/
/**
* @notice Disables this Voting contract in favor of the migrated one.
* @dev Can only be called by the contract owner.
* @param newVotingAddress the newly migrated contract address.
*/
function setMigrated(address newVotingAddress) external onlyOwner {
migratedAddress = newVotingAddress;
}
/**
* @notice Resets the inflation rate. Note: this change only applies to rounds that have not yet begun.
* @dev This method is public because calldata structs are not currently supported by solidity.
* @param newInflationRate sets the next round's inflation rate.
*/
function setInflationRate(FixedPoint.Unsigned memory newInflationRate) public onlyOwner {
inflationRate = newInflationRate;
}
/**
* @notice Resets the Gat percentage. Note: this change only applies to rounds that have not yet begun.
* @dev This method is public because calldata structs are not currently supported by solidity.
* @param newGatPercentage sets the next round's Gat percentage.
*/
function setGatPercentage(FixedPoint.Unsigned memory newGatPercentage) public onlyOwner {
require(newGatPercentage.isLessThan(1), "GAT percentage must be < 100%");
gatPercentage = newGatPercentage;
}
/**
* @notice Resets the rewards expiration timeout.
* @dev This change only applies to rounds that have not yet begun.
* @param NewRewardsExpirationTimeout how long a caller can wait before choosing to withdraw their rewards.
*/
function setRewardsExpirationTimeout(uint256 NewRewardsExpirationTimeout) public onlyOwner {
rewardsExpirationTimeout = NewRewardsExpirationTimeout;
}
/****************************************
* PRIVATE AND INTERNAL FUNCTIONS *
****************************************/
// Returns the price for a given identifer. Three params are returns: bool if there was an error, int to represent
// the resolved price and a string which is filled with an error message, if there was an error or "".
function _getPriceOrError(bytes32 identifier, uint256 time)
private
view
returns (
bool,
int256,
string memory
)
{
PriceRequest storage priceRequest = _getPriceRequest(identifier, time);
uint256 currentRoundId = voteTiming.computeCurrentRoundId(getCurrentTime());
RequestStatus requestStatus = _getRequestStatus(priceRequest, currentRoundId);
if (requestStatus == RequestStatus.Active) {
return (false, 0, "Current voting round not ended");
} else if (requestStatus == RequestStatus.Resolved) {
VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound];
(, int256 resolvedPrice) = voteInstance.resultComputation.getResolvedPrice(
_computeGat(priceRequest.lastVotingRound)
);
return (true, resolvedPrice, "");
} else if (requestStatus == RequestStatus.Future) {
return (false, 0, "Price is still to be voted on");
} else {
return (false, 0, "Price was never requested");
}
}
function _getPriceRequest(bytes32 identifier, uint256 time) private view returns (PriceRequest storage) {
return priceRequests[_encodePriceRequest(identifier, time)];
}
function _encodePriceRequest(bytes32 identifier, uint256 time) private pure returns (bytes32) {
return keccak256(abi.encode(identifier, time));
}
function _freezeRoundVariables(uint256 roundId) private {
Round storage round = rounds[roundId];
// Only on the first reveal should the snapshot be captured for that round.
if (round.snapshotId == 0) {
// There is no snapshot ID set, so create one.
round.snapshotId = votingToken.snapshot();
// Set the round inflation rate to the current global inflation rate.
rounds[roundId].inflationRate = inflationRate;
// Set the round gat percentage to the current global gat rate.
rounds[roundId].gatPercentage = gatPercentage;
// Set the rewards expiration time based on end of time of this round and the current global timeout.
rounds[roundId].rewardsExpirationTime = voteTiming.computeRoundEndTime(roundId).add(
rewardsExpirationTimeout
);
}
}
function _resolvePriceRequest(PriceRequest storage priceRequest, VoteInstance storage voteInstance) private {
if (priceRequest.index == UINT_MAX) {
return;
}
(bool isResolved, int256 resolvedPrice) = voteInstance.resultComputation.getResolvedPrice(
_computeGat(priceRequest.lastVotingRound)
);
require(isResolved, "Can't resolve unresolved request");
// Delete the resolved price request from pendingPriceRequests.
uint256 lastIndex = pendingPriceRequests.length - 1;
PriceRequest storage lastPriceRequest = priceRequests[pendingPriceRequests[lastIndex]];
lastPriceRequest.index = priceRequest.index;
pendingPriceRequests[priceRequest.index] = pendingPriceRequests[lastIndex];
pendingPriceRequests.pop();
priceRequest.index = UINT_MAX;
emit PriceResolved(priceRequest.lastVotingRound, priceRequest.identifier, priceRequest.time, resolvedPrice);
}
function _computeGat(uint256 roundId) private view returns (FixedPoint.Unsigned memory) {
uint256 snapshotId = rounds[roundId].snapshotId;
if (snapshotId == 0) {
// No snapshot - return max value to err on the side of caution.
return FixedPoint.Unsigned(UINT_MAX);
}
// Grab the snapshotted supply from the voting token. It's already scaled by 10**18, so we can directly
// initialize the Unsigned value with the returned uint.
FixedPoint.Unsigned memory snapshottedSupply = FixedPoint.Unsigned(votingToken.totalSupplyAt(snapshotId));
// Multiply the total supply at the snapshot by the gatPercentage to get the GAT in number of tokens.
return snapshottedSupply.mul(rounds[roundId].gatPercentage);
}
function _getRequestStatus(PriceRequest storage priceRequest, uint256 currentRoundId)
private
view
returns (RequestStatus)
{
if (priceRequest.lastVotingRound == 0) {
return RequestStatus.NotRequested;
} else if (priceRequest.lastVotingRound < currentRoundId) {
VoteInstance storage voteInstance = priceRequest.voteInstances[priceRequest.lastVotingRound];
(bool isResolved, ) = voteInstance.resultComputation.getResolvedPrice(
_computeGat(priceRequest.lastVotingRound)
);
return isResolved ? RequestStatus.Resolved : RequestStatus.Active;
} else if (priceRequest.lastVotingRound == currentRoundId) {
return RequestStatus.Active;
} else {
// Means than priceRequest.lastVotingRound > currentRoundId
return RequestStatus.Future;
}
}
function _getIdentifierWhitelist() private view returns (IdentifierWhitelistInterface supportedIdentifiers) {
return IdentifierWhitelistInterface(finder.getImplementationAddress(OracleInterfaces.IdentifierWhitelist));
}
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../../common/implementation/FixedPoint.sol";
/**
* @title Interface that voters must use to Vote on price request resolutions.
*/
abstract contract VotingInterface {
struct PendingRequest {
bytes32 identifier;
uint256 time;
}
// Captures the necessary data for making a commitment.
// Used as a parameter when making batch commitments.
// Not used as a data structure for storage.
struct Commitment {
bytes32 identifier;
uint256 time;
bytes32 hash;
bytes encryptedVote;
}
// Captures the necessary data for revealing a vote.
// Used as a parameter when making batch reveals.
// Not used as a data structure for storage.
struct Reveal {
bytes32 identifier;
uint256 time;
int256 price;
int256 salt;
}
// Note: the phases must be in order. Meaning the first enum value must be the first phase, etc.
// `NUM_PHASES_PLACEHOLDER` is to get the number of phases. It isn't an actual phase, and it should always be last.
enum Phase { Commit, Reveal, NUM_PHASES_PLACEHOLDER }
/**
* @notice Commit a vote for a price request for `identifier` at `time`.
* @dev `identifier`, `time` must correspond to a price request that's currently in the commit phase.
* Commits can be changed.
* @dev Since transaction data is public, the salt will be revealed with the vote. While this is the system’s expected behavior,
* voters should never reuse salts. If someone else is able to guess the voted price and knows that a salt will be reused, then
* they can determine the vote pre-reveal.
* @param identifier uniquely identifies the committed vote. EG BTC/USD price pair.
* @param time unix timestamp of the price being voted on.
* @param hash keccak256 hash of the `price`, `salt`, voter `address`, `time`, current `roundId`, and `identifier`.
*/
function commitVote(
bytes32 identifier,
uint256 time,
bytes32 hash
) external virtual;
/**
* @notice Submit a batch of commits in a single transaction.
* @dev Using `encryptedVote` is optional. If included then commitment is stored on chain.
* Look at `project-root/common/Constants.js` for the tested maximum number of
* commitments that can fit in one transaction.
* @param commits array of structs that encapsulate an `identifier`, `time`, `hash` and optional `encryptedVote`.
*/
function batchCommit(Commitment[] calldata commits) external virtual;
/**
* @notice snapshot the current round's token balances and lock in the inflation rate and GAT.
* @dev This function can be called multiple times but each round will only every have one snapshot at the
* time of calling `_freezeRoundVariables`.
* @param signature signature required to prove caller is an EOA to prevent flash loans from being included in the
* snapshot.
*/
function snapshotCurrentRound(bytes calldata signature) external virtual;
/**
* @notice Reveal a previously committed vote for `identifier` at `time`.
* @dev The revealed `price`, `salt`, `address`, `time`, `roundId`, and `identifier`, must hash to the latest `hash`
* that `commitVote()` was called with. Only the committer can reveal their vote.
* @param identifier voted on in the commit phase. EG BTC/USD price pair.
* @param time specifies the unix timestamp of the price is being voted on.
* @param price voted on during the commit phase.
* @param salt value used to hide the commitment price during the commit phase.
*/
function revealVote(
bytes32 identifier,
uint256 time,
int256 price,
int256 salt
) external virtual;
/**
* @notice Reveal multiple votes in a single transaction.
* Look at `project-root/common/Constants.js` for the tested maximum number of reveals.
* that can fit in one transaction.
* @dev For more information on reveals, review the comment for `revealVote`.
* @param reveals array of the Reveal struct which contains an identifier, time, price and salt.
*/
function batchReveal(Reveal[] calldata reveals) external virtual;
/**
* @notice Gets the queries that are being voted on this round.
* @return pendingRequests `PendingRequest` array containing identifiers
* and timestamps for all pending requests.
*/
function getPendingRequests() external virtual view returns (PendingRequest[] memory);
/**
* @notice Returns the current voting phase, as a function of the current time.
* @return Phase to indicate the current phase. Either { Commit, Reveal, NUM_PHASES_PLACEHOLDER }.
*/
function getVotePhase() external virtual view returns (Phase);
/**
* @notice Returns the current round ID, as a function of the current time.
* @return uint256 representing the unique round ID.
*/
function getCurrentRoundId() external virtual view returns (uint256);
/**
* @notice Retrieves rewards owed for a set of resolved price requests.
* @dev Can only retrieve rewards if calling for a valid round and if the
* call is done within the timeout threshold (not expired).
* @param voterAddress voter for which rewards will be retrieved. Does not have to be the caller.
* @param roundId the round from which voting rewards will be retrieved from.
* @param toRetrieve array of PendingRequests which rewards are retrieved from.
* @return total amount of rewards returned to the voter.
*/
function retrieveRewards(
address voterAddress,
uint256 roundId,
PendingRequest[] memory toRetrieve
) public virtual returns (FixedPoint.Unsigned memory);
}
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../Voting.sol";
import "../../../common/implementation/FixedPoint.sol";
// Test contract used to access internal variables in the Voting contract.
contract VotingTest is Voting {
constructor(
uint256 _phaseLength,
FixedPoint.Unsigned memory _gatPercentage,
FixedPoint.Unsigned memory _inflationRate,
uint256 _rewardsExpirationTimeout,
address _votingToken,
address _finder,
address _timerAddress
)
public
Voting(
_phaseLength,
_gatPercentage,
_inflationRate,
_rewardsExpirationTimeout,
_votingToken,
_finder,
_timerAddress
)
{}
function getPendingPriceRequestsArray() external view returns (bytes32[] memory) {
return pendingPriceRequests;
}
}
pragma solidity ^0.6.0;
import "../../common/implementation/ExpandedERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Snapshot.sol";
/**
* @title Ownership of this token allows a voter to respond to price requests.
* @dev Supports snapshotting and allows the Oracle to mint new tokens as rewards.
*/
contract VotingToken is ExpandedERC20, ERC20Snapshot {
/**
* @notice Constructs the VotingToken.
*/
constructor() public ExpandedERC20("UMA Voting Token v1", "UMA", 18) {}
/**
* @notice Creates a new snapshot ID.
* @return uint256 Thew new snapshot ID.
*/
function snapshot() external returns (uint256) {
return _snapshot();
}
// _transfer, _mint and _burn are ERC20 internal methods that are overridden by ERC20Snapshot,
// therefore the compiler will complain that VotingToken must override these methods
// because the two base classes (ERC20 and ERC20Snapshot) both define the same functions
function _transfer(
address from,
address to,
uint256 value
) internal override(ERC20, ERC20Snapshot) {
super._transfer(from, to, value);
}
function _mint(address account, uint256 value) internal override(ERC20, ERC20Snapshot) {
super._mint(account, value);
}
function _burn(address account, uint256 value) internal override(ERC20, ERC20Snapshot) {
super._burn(account, value);
}
}
/**
*Submitted for verification at Etherscan.io on 2017-12-12
*/
// Copyright (C) 2015, 2016, 2017 Dapphub
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.6.0;
// Copied from the verified code from Etherscan:
// https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code
// And then updated for Solidity version 0.6. Specific changes:
// * Change `function() public` into `receive() external` and `fallback() external`
// * Change `this.balance` to `address(this).balance`
// * Ran prettier
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint256 wad);
event Transfer(address indexed src, address indexed dst, uint256 wad);
event Deposit(address indexed dst, uint256 wad);
event Withdrawal(address indexed src, uint256 wad);
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
receive() external payable {
deposit();
}
fallback() external payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
emit Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint256) {
return address(this).balance;
}
function approve(address guy, uint256 wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint256 wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(
address src,
address dst,
uint256 wad
) public returns (bool) {
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
emit Transfer(src, dst, wad);
return true;
}
}
/*
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
*/
/**
* Withdrawable contract.
*/
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "./MultiRole.sol";
/**
* @title Base contract that allows a specific role to withdraw any ETH and/or ERC20 tokens that the contract holds.
*/
abstract contract Withdrawable is MultiRole {
using SafeERC20 for IERC20;
uint256 private roleId;
/**
* @notice Withdraws ETH from the contract.
*/
function withdraw(uint256 amount) external onlyRoleHolder(roleId) {
Address.sendValue(msg.sender, amount);
}
/**
* @notice Withdraws ERC20 tokens from the contract.
* @param erc20Address ERC20 token to withdraw.
* @param amount amount of tokens to withdraw.
*/
function withdrawErc20(address erc20Address, uint256 amount) external onlyRoleHolder(roleId) {
IERC20 erc20 = IERC20(erc20Address);
erc20.safeTransfer(msg.sender, amount);
}
/**
* @notice Internal method that allows derived contracts to create a role for withdrawal.
* @dev Either this method or `_setWithdrawRole` must be called by the derived class for this contract to function
* properly.
* @param newRoleId ID corresponding to role whose members can withdraw.
* @param managingRoleId ID corresponding to managing role who can modify the withdrawable role's membership.
* @param withdrawerAddress new manager of withdrawable role.
*/
function _createWithdrawRole(
uint256 newRoleId,
uint256 managingRoleId,
address withdrawerAddress
) internal {
roleId = newRoleId;
_createExclusiveRole(newRoleId, managingRoleId, withdrawerAddress);
}
/**
* @notice Internal method that allows derived contracts to choose the role for withdrawal.
* @dev The role `setRoleId` must exist. Either this method or `_createWithdrawRole` must be
* called by the derived class for this contract to function properly.
* @param setRoleId ID corresponding to role whose members can withdraw.
*/
function _setWithdrawRole(uint256 setRoleId) internal onlyValidRole(setRoleId) {
roleId = setRoleId;
}
}
pragma solidity ^0.6.0;
import "../implementation/Withdrawable.sol";
// WithdrawableTest is derived from the abstract contract Withdrawable for testing purposes.
contract WithdrawableTest is Withdrawable {
enum Roles { Governance, Withdraw }
// solhint-disable-next-line no-empty-blocks
constructor() public {
_createExclusiveRole(uint256(Roles.Governance), uint256(Roles.Governance), msg.sender);
_createWithdrawRole(uint256(Roles.Withdraw), uint256(Roles.Governance), msg.sender);
}
function pay() external payable {
require(msg.value > 0);
}
function setInternalWithdrawRole(uint256 setRoleId) public {
_setWithdrawRole(setRoleId);
}
}
{
"compilationTarget": {
"contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol": "ExpiringMultiParty"
},
"evmVersion": "istanbul",
"libraries": {
"ExpiringMultiPartyLib": "0x0246fbf444cae32867b410464664f8f02e1822c7",
"PerpetualLib": "0x0246fbf444cae32867b410464664f8f02e1822c7"
},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 199
},
"remappings": []
}
[{"inputs":[{"components":[{"internalType":"uint256","name":"expirationTimestamp","type":"uint256"},{"internalType":"uint256","name":"withdrawalLiveness","type":"uint256"},{"internalType":"address","name":"collateralAddress","type":"address"},{"internalType":"address","name":"finderAddress","type":"address"},{"internalType":"address","name":"tokenFactoryAddress","type":"address"},{"internalType":"address","name":"timerAddress","type":"address"},{"internalType":"address","name":"excessTokenBeneficiary","type":"address"},{"internalType":"bytes32","name":"priceFeedIdentifier","type":"bytes32"},{"internalType":"string","name":"syntheticName","type":"string"},{"internalType":"string","name":"syntheticSymbol","type":"string"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"minSponsorTokens","type":"tuple"},{"internalType":"uint256","name":"liquidationLiveness","type":"uint256"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralRequirement","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"disputeBondPct","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"sponsorDisputeRewardPct","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"disputerDisputeRewardPct","type":"tuple"}],"internalType":"struct Liquidatable.ConstructorParams","name":"params","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"}],"name":"ContractExpired","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"address","name":"liquidator","type":"address"},{"indexed":false,"internalType":"address","name":"disputer","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidationId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"disputeSucceeded","type":"bool"}],"name":"DisputeSettled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"originalExpirationTimestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shutdownTimestamp","type":"uint256"}],"name":"EmergencyShutdown","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"}],"name":"EndedSponsorPosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FinalFeesPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"address","name":"liquidator","type":"address"},{"indexed":true,"internalType":"uint256","name":"liquidationId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokensOutstanding","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lockedCollateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidatedCollateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidationTime","type":"uint256"}],"name":"LiquidationCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"address","name":"liquidator","type":"address"},{"indexed":true,"internalType":"address","name":"disputer","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidationId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"disputeBondAmount","type":"uint256"}],"name":"LiquidationDisputed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"withdrawalAmount","type":"uint256"},{"indexed":true,"internalType":"enum Liquidatable.Status","name":"liquidationStatus","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"settlementPrice","type":"uint256"}],"name":"LiquidationWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"}],"name":"NewSponsor","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"PositionCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"regularFee","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"lateFee","type":"uint256"}],"name":"RegularFeesPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldSponsor","type":"address"}],"name":"RequestTransferPosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldSponsor","type":"address"}],"name":"RequestTransferPositionCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldSponsor","type":"address"},{"indexed":true,"internalType":"address","name":"newSponsor","type":"address"}],"name":"RequestTransferPositionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"RequestWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"RequestWithdrawalCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"RequestWithdrawalExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralReturned","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tokensBurned","type":"uint256"}],"name":"SettleExpiredPosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sponsor","type":"address"},{"indexed":true,"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"inputs":[],"name":"cancelTransferPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collateralCurrency","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralRequirement","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contractState","outputs":[{"internalType":"enum PricelessPositionManager.ContractState","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralAmount","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"numTokens","type":"tuple"}],"name":"create","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sponsor","type":"address"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"minCollateralPerToken","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"maxCollateralPerToken","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"maxTokensToLiquidate","type":"tuple"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"createLiquidation","outputs":[{"internalType":"uint256","name":"liquidationId","type":"uint256"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"tokensLiquidated","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"finalFeeBond","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cumulativeFeeMultiplier","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralAmount","type":"tuple"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sponsor","type":"address"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralAmount","type":"tuple"}],"name":"depositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"liquidationId","type":"uint256"},{"internalType":"address","name":"sponsor","type":"address"}],"name":"dispute","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"totalPaid","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"disputeBondPct","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disputerDisputeRewardPct","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyShutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"excessTokenBeneficiary","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"expirationTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"expire","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"expiryPrice","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"finder","outputs":[{"internalType":"contract FinderInterface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sponsor","type":"address"}],"name":"getCollateral","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralAmount","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sponsor","type":"address"}],"name":"getLiquidations","outputs":[{"components":[{"internalType":"address","name":"sponsor","type":"address"},{"internalType":"address","name":"liquidator","type":"address"},{"internalType":"enum Liquidatable.Status","name":"state","type":"uint8"},{"internalType":"uint256","name":"liquidationTime","type":"uint256"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"tokensOutstanding","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"lockedCollateral","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"liquidatedCollateral","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"rawUnitCollateral","type":"tuple"},{"internalType":"address","name":"disputer","type":"address"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"settlementPrice","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"finalFee","type":"tuple"}],"internalType":"struct Liquidatable.LiquidationData[]","name":"liquidationData","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidationLiveness","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"liquidations","outputs":[{"internalType":"address","name":"sponsor","type":"address"},{"internalType":"address","name":"liquidator","type":"address"},{"internalType":"enum Liquidatable.Status","name":"state","type":"uint8"},{"internalType":"uint256","name":"liquidationTime","type":"uint256"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"tokensOutstanding","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"lockedCollateral","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"liquidatedCollateral","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"rawUnitCollateral","type":"tuple"},{"internalType":"address","name":"disputer","type":"address"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"settlementPrice","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"finalFee","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minSponsorTokens","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"payRegularFees","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"totalPaid","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pfc","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"positions","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"tokensOutstanding","type":"tuple"},{"internalType":"uint256","name":"withdrawalRequestPassTimestamp","type":"uint256"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"withdrawalRequestAmount","type":"tuple"},{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"rawCollateral","type":"tuple"},{"internalType":"uint256","name":"transferPositionRequestPassTimestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceIdentifier","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rawLiquidationCollateral","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rawTotalPositionCollateral","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"numTokens","type":"tuple"}],"name":"redeem","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"amountWithdrawn","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"remargin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requestTransferPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralAmount","type":"tuple"}],"name":"requestWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"time","type":"uint256"}],"name":"setCurrentTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"settleExpired","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"amountWithdrawn","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sponsorDisputeRewardPct","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"timerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenCurrency","outputs":[{"internalType":"contract ExpandedIERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalPositionCollateral","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"totalCollateral","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTokensOutstanding","outputs":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newSponsorAddress","type":"address"}],"name":"transferPositionPassedRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"}],"name":"trimExcess","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"amount","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"collateralAmount","type":"tuple"}],"name":"withdraw","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"amountWithdrawn","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"liquidationId","type":"uint256"},{"internalType":"address","name":"sponsor","type":"address"}],"name":"withdrawLiquidation","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"amountWithdrawn","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawPassedRequest","outputs":[{"components":[{"internalType":"uint256","name":"rawValue","type":"uint256"}],"internalType":"struct FixedPoint.Unsigned","name":"amountWithdrawn","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawalLiveness","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]