// File: @openzeppelin/contracts/math/SafeMath.sol
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;
}
}
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
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);
}
// File: @openzeppelin/contracts/utils/Address.sol
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");
}
}
// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
pragma solidity ^0.6.0;
/**
* @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");
}
}
}
// File: contracts/common/implementation/FixedPoint.sol
pragma solidity ^0.6.0;
/**
* @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);
}
}
}
// File: contracts/common/interfaces/ExpandedIERC20.sol
pragma solidity ^0.6.0;
/**
* @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);
}
// File: contracts/oracle/interfaces/OracleInterface.sol
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);
}
// File: contracts/oracle/interfaces/IdentifierWhitelistInterface.sol
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);
}
// File: contracts/oracle/interfaces/AdministrateeInterface.sol
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;
}
// File: contracts/oracle/implementation/Constants.sol
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";
}
// File: @openzeppelin/contracts/GSN/Context.sol
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;
}
}
// File: @openzeppelin/contracts/token/ERC20/ERC20.sol
pragma solidity ^0.6.0;
/**
* @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 { }
}
// File: contracts/common/implementation/MultiRole.sol
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"
);
}
}
// File: contracts/common/implementation/ExpandedERC20.sol
pragma solidity ^0.6.0;
/**
* @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);
}
}
// File: contracts/common/implementation/Lockable.sol
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;
}
}
// File: contracts/financial-templates/common/SyntheticToken.sol
pragma solidity ^0.6.0;
/**
* @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);
}
}
// File: contracts/financial-templates/common/TokenFactory.sol
pragma solidity ^0.6.0;
/**
* @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));
}
}
// File: contracts/common/implementation/Timer.sol
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;
}
}
// File: contracts/common/implementation/Testable.sol
pragma solidity ^0.6.0;
/**
* @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
}
}
}
// File: contracts/oracle/interfaces/StoreInterface.sol
pragma solidity ^0.6.0;
/**
* @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);
}
// File: contracts/oracle/interfaces/FinderInterface.sol
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);
}
// File: contracts/financial-templates/common/FeePayer.sol
pragma solidity ^0.6.0;
/**
* @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 corrution 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));
}
}
// File: contracts/financial-templates/expiring-multiparty/PricelessPositionManager.sol
pragma solidity ^0.6.0;
/**
* @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.
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;
/****************************************
* 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.
* 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
) 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;
}
/****************************************
* 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 onlyPreExpiration() 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()
{
require(_checkCollateralization(collateralAmount, numTokens), "CR below GCR");
PositionData storage positionData = positions[msg.sender];
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
onlyPreExpiration()
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 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);
}
}
}
// File: contracts/financial-templates/expiring-multiparty/Liquidatable.sol
pragma solidity ^0.6.0;
/**
* @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;
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.
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
)
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.
* @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
})
);
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 tokenRedemptionValue = liquidation
.tokensOutstanding
.mul(liquidation.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,
liquidation.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"
);
}
}
// File: contracts/financial-templates/expiring-multiparty/ExpiringMultiParty.sol
pragma solidity ^0.6.0;
/**
* @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.
{
}
}
{
"compilationTarget": {
"ExpiringMultiParty.sol": "ExpiringMultiParty"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"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":"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":"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":[{"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"}]