Transaction Hash:
Block:
11552229 at Dec-30-2020 12:58:38 AM +UTC
Transaction Fee:
0.009818289 ETH
$21.95
Gas Used:
105,573 Gas / 93 Gwei
Emitted Events:
222 |
USM.BuySellAdjustmentChanged( previous=1021736622016677206, latest=1021502928187678042 )
|
223 |
FUM.Transfer( from=0x0000000000000000000000000000000000000000, to=[Sender] 0x0799442ce0f90f8837fdeece82cd2735625b4bf9, value=13892974292077047017 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x03eb7Ce2...573d3cECC | 18.701585781518837547 Eth | 18.901585781518837547 Eth | 0.2 | ||
0x0799442C...5625B4bf9 |
2.398164991533417382 Eth
Nonce: 2336
|
2.188346702533417382 Eth
Nonce: 2337
| 0.209818289 | ||
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 97.803212466264884405 Eth | 97.813030755264884405 Eth | 0.009818289 | |
0xf04a5D82...defF1a394 |
Execution Trace
ETH 0.2
FUM.CALL( )

ETH 0.2
USM.fund( to=0x0799442CE0f90F8837fdeEce82Cd2735625B4bf9, minFumOut=0 ) => ( fumOut=13892974292077047017 )
EACAggregatorProxy.STATICCALL( )
-
AccessControlledAggregator.STATICCALL( )
-
-
UniswapAnchoredView.price( symbol=ETH ) => ( 739640000 )
-
UniswapV2Pair.STATICCALL( )
-
UniswapV2Pair.STATICCALL( )
-
FUM.STATICCALL( )
-
FUM.mint( _recipient=0x0799442CE0f90F8837fdeEce82Cd2735625B4bf9, _amount=13892974292077047017 )
File 1 of 6: FUM
File 2 of 6: USM
File 3 of 6: EACAggregatorProxy
File 4 of 6: AccessControlledAggregator
File 5 of 6: UniswapAnchoredView
File 6 of 6: UniswapV2Pair
// SPDX-License-Identifier: GPL-3.0-or-later // 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. */ abstract contract Context { 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/access/Ownable.sol pragma solidity ^0.6.0; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () internal { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(_owner == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } // 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/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) { 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/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"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain`call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return _functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); return _functionCallWithValue(target, data, value, errorMessage); } function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // 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 {ERC20PresetMinterPauser}. * * 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: erc20permit/contracts/IERC2612.sol // Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/ pragma solidity ^0.6.0; /** * @dev Interface of the ERC2612 standard as defined in the EIP. * * Adds the {permit} method, which can be used to change one's * {IERC20-allowance} without having to send a transaction, by signing a * message. This allows users to spend tokens without having to hold Ether. * * See https://eips.ethereum.org/EIPS/eip-2612. */ interface IERC2612 { /** * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens, * given `owner`'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; /** * @dev Returns the current ERC2612 nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); } // File: erc20permit/contracts/ERC20Permit.sol // Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol pragma solidity ^0.6.0; /** * @author Georgios Konstantopoulos * @dev Extension of {ERC20} that allows token holders to use their tokens * without sending any transactions by setting {IERC20-allowance} with a * signature using the {permit} method, and then spend them via * {IERC20-transferFrom}. * * The {permit} signature mechanism conforms to the {IERC2612} interface. */ abstract contract ERC20Permit is ERC20, IERC2612 { mapping (address => uint256) public override nonces; bytes32 public immutable PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public immutable DOMAIN_SEPARATOR; constructor(string memory name_, string memory symbol_) internal ERC20(name_, symbol_) { uint256 chainId; assembly { chainId := chainid() } DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name_)), keccak256(bytes("1")), chainId, address(this) ) ); } /** * @dev See {IERC2612-permit}. * * In cases where the free option is not a concern, deadline can simply be * set to uint(-1), so it should be seen as an optional parameter */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { require(deadline >= block.timestamp, "ERC20Permit: expired deadline"); bytes32 hashStruct = keccak256( abi.encode( PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline ) ); bytes32 hash = keccak256( abi.encodePacked( '\x19\x01', DOMAIN_SEPARATOR, hashStruct ) ); address signer = ecrecover(hash, v, r, s); require( signer != address(0) && signer == owner, "ERC20Permit: invalid signature" ); _approve(owner, spender, amount); } } // File: contracts/IUSM.sol pragma solidity ^0.6.6; interface IUSM { function mint(address to, uint minUsmOut) external payable returns (uint); function burn(address from, address payable to, uint usmToBurn, uint minEthOut) external returns (uint); function fund(address to, uint minFumOut) external payable returns (uint); function defund(address from, address payable to, uint fumToBurn, uint minEthOut) external returns (uint); function defundFromFUM(address from, address payable to, uint fumToBurn, uint minEthOut) external returns (uint); } // File: contracts/MinOut.sol pragma solidity ^0.6.6; library MinOut { function parseMinTokenOut(uint ethIn) internal pure returns (uint minTokenOut) { uint minPrice = ethIn % 100000000000; if (minPrice != 0 && minPrice < 10000000) { minTokenOut = ethIn * minPrice / 100; } } function parseMinEthOut(uint tokenIn) internal pure returns (uint minEthOut) { uint maxPrice = tokenIn % 100000000000; if (maxPrice != 0 && maxPrice < 10000000) { minEthOut = tokenIn * 100 / maxPrice; } } } // File: contracts/FUM.sol pragma solidity ^0.6.6; /** * @title FUM Token * @author Alberto Cuesta Cañada, Jacob Eliosoff, Alex Roan * * @notice This should be owned by the stablecoin. */ contract FUM is ERC20Permit, Ownable { IUSM public immutable usm; constructor(IUSM usm_) public ERC20Permit("Minimal Funding", "FUM") { usm = usm_; } /** * @notice If anyone sends ETH here, assume they intend it as a `fund`. * If decimals 8 to 11 (included) of the amount of Ether received are `0000` then the next 7 will * be parsed as the minimum Ether price accepted, with 2 digits before and 5 digits after the comma. */ receive() external payable { usm.fund{ value: msg.value }(msg.sender, MinOut.parseMinTokenOut(msg.value)); } /** * @notice If a user sends FUM tokens directly to this contract (or to the USM contract), assume they intend it as a `defund`. * If using `transfer`/`transferFrom` as `defund`, and if decimals 8 to 11 (included) of the amount transferred received * are `0000` then the next 7 will be parsed as the maximum FUM price accepted, with 5 digits before and 2 digits after the comma. */ function _transfer(address sender, address recipient, uint256 amount) internal override { if (recipient == address(this) || recipient == address(usm) || recipient == address(0)) { usm.defundFromFUM(sender, payable(sender), amount, MinOut.parseMinEthOut(amount)); } else { super._transfer(sender, recipient, amount); } } /** * @notice Mint new FUM to the _recipient * * @param _recipient address to mint to * @param _amount amount to mint */ function mint(address _recipient, uint _amount) external onlyOwner { _mint(_recipient, _amount); } /** * @notice Burn FUM from _holder * * @param _holder address to burn from * @param _amount amount to burn */ function burn(address _holder, uint _amount) external onlyOwner { _burn(_holder, _amount); } }
File 2 of 6: USM
// SPDX-License-Identifier: GPL-3.0-or-later // 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"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain`call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return _functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); return _functionCallWithValue(target, data, value, errorMessage); } function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // 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) { 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/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. */ abstract contract Context { 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/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/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 {ERC20PresetMinterPauser}. * * 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: erc20permit/contracts/IERC2612.sol // Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/ pragma solidity ^0.6.0; /** * @dev Interface of the ERC2612 standard as defined in the EIP. * * Adds the {permit} method, which can be used to change one's * {IERC20-allowance} without having to send a transaction, by signing a * message. This allows users to spend tokens without having to hold Ether. * * See https://eips.ethereum.org/EIPS/eip-2612. */ interface IERC2612 { /** * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens, * given `owner`'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; /** * @dev Returns the current ERC2612 nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); } // File: erc20permit/contracts/ERC20Permit.sol // Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol pragma solidity ^0.6.0; /** * @author Georgios Konstantopoulos * @dev Extension of {ERC20} that allows token holders to use their tokens * without sending any transactions by setting {IERC20-allowance} with a * signature using the {permit} method, and then spend them via * {IERC20-transferFrom}. * * The {permit} signature mechanism conforms to the {IERC2612} interface. */ abstract contract ERC20Permit is ERC20, IERC2612 { mapping (address => uint256) public override nonces; bytes32 public immutable PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public immutable DOMAIN_SEPARATOR; constructor(string memory name_, string memory symbol_) internal ERC20(name_, symbol_) { uint256 chainId; assembly { chainId := chainid() } DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name_)), keccak256(bytes("1")), chainId, address(this) ) ); } /** * @dev See {IERC2612-permit}. * * In cases where the free option is not a concern, deadline can simply be * set to uint(-1), so it should be seen as an optional parameter */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { require(deadline >= block.timestamp, "ERC20Permit: expired deadline"); bytes32 hashStruct = keccak256( abi.encode( PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline ) ); bytes32 hash = keccak256( abi.encodePacked( '\x19\x01', DOMAIN_SEPARATOR, hashStruct ) ); address signer = ecrecover(hash, v, r, s); require( signer != address(0) && signer == owner, "ERC20Permit: invalid signature" ); _approve(owner, spender, amount); } } // File: contracts/IUSM.sol pragma solidity ^0.6.6; interface IUSM { function mint(address to, uint minUsmOut) external payable returns (uint); function burn(address from, address payable to, uint usmToBurn, uint minEthOut) external returns (uint); function fund(address to, uint minFumOut) external payable returns (uint); function defund(address from, address payable to, uint fumToBurn, uint minEthOut) external returns (uint); function defundFromFUM(address from, address payable to, uint fumToBurn, uint minEthOut) external returns (uint); } // File: contracts/Delegable.sol pragma solidity ^0.6.6; /// @dev Delegable enables users to delegate their account management to other users. /// Delegable implements addDelegateBySignature, to add delegates using a signature instead of a separate transaction. contract Delegable { event Delegate(address indexed user, address indexed delegate, bool enabled); // keccak256("Signature(address user,address delegate,uint256 nonce,uint256 deadline)"); bytes32 public immutable SIGNATURE_TYPEHASH = 0x0d077601844dd17f704bafff948229d27f33b57445915754dfe3d095fda2beb7; bytes32 public immutable DELEGABLE_DOMAIN; mapping(address => uint) public signatureCount; mapping(address => mapping(address => bool)) public delegated; constructor () public { uint256 chainId; assembly { chainId := chainid() } DELEGABLE_DOMAIN = keccak256( abi.encode( keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), keccak256(bytes('USMFUM')), keccak256(bytes('1')), chainId, address(this) ) ); } /// @dev Require that msg.sender is the account holder or a delegate modifier onlyHolderOrDelegate(address holder, string memory errorMessage) { require( msg.sender == holder || delegated[holder][msg.sender], errorMessage ); _; } /// @dev Enable a delegate to act on the behalf of caller function addDelegate(address delegate) public { _addDelegate(msg.sender, delegate); } /// @dev Stop a delegate from acting on the behalf of caller function revokeDelegate(address delegate) public { _revokeDelegate(msg.sender, delegate); } /// @dev Add a delegate through an encoded signature function addDelegateBySignature(address user, address delegate, uint deadline, uint8 v, bytes32 r, bytes32 s) public { require(deadline >= block.timestamp, 'Delegable: Signature expired'); bytes32 hashStruct = keccak256( abi.encode( SIGNATURE_TYPEHASH, user, delegate, signatureCount[user]++, deadline ) ); bytes32 digest = keccak256( abi.encodePacked( '\x19\x01', DELEGABLE_DOMAIN, hashStruct ) ); address signer = ecrecover(digest, v, r, s); require( signer != address(0) && signer == user, 'Delegable: Invalid signature' ); _addDelegate(user, delegate); } /// @dev Enable a delegate to act on the behalf of an user function _addDelegate(address user, address delegate) internal { require(!delegated[user][delegate], "Delegable: Already delegated"); delegated[user][delegate] = true; emit Delegate(user, delegate, true); } /// @dev Stop a delegate from acting on the behalf of an user function _revokeDelegate(address user, address delegate) internal { require(delegated[user][delegate], "Delegable: Already undelegated"); delegated[user][delegate] = false; emit Delegate(user, delegate, false); } } // File: contracts/WadMath.sol pragma solidity ^0.6.6; /** * @title Fixed point arithmetic library * @author Alberto Cuesta Cañada, Jacob Eliosoff, Alex Roan */ library WadMath { using SafeMath for uint; enum Round {Down, Up} uint private constant WAD = 10 ** 18; uint private constant WAD_MINUS_1 = WAD - 1; uint private constant WAD_SQUARED = WAD * WAD; uint private constant WAD_SQUARED_MINUS_1 = WAD_SQUARED - 1; uint private constant WAD_OVER_10 = WAD / 10; uint private constant WAD_OVER_20 = WAD / 20; uint private constant HALF_TO_THE_ONE_TENTH = 933032991536807416; uint private constant TWO_WAD = 2 * WAD; function wadMul(uint x, uint y, Round upOrDown) internal pure returns (uint) { return upOrDown == Round.Down ? wadMulDown(x, y) : wadMulUp(x, y); } function wadMulDown(uint x, uint y) internal pure returns (uint) { return x.mul(y) / WAD; } function wadMulUp(uint x, uint y) internal pure returns (uint) { return (x.mul(y)).add(WAD_MINUS_1) / WAD; } function wadSquaredDown(uint x) internal pure returns (uint) { return (x.mul(x)) / WAD; } function wadSquaredUp(uint x) internal pure returns (uint) { return (x.mul(x)).add(WAD_MINUS_1) / WAD; } function wadCubedDown(uint x) internal pure returns (uint) { return (x.mul(x)).mul(x) / WAD_SQUARED; } function wadCubedUp(uint x) internal pure returns (uint) { return ((x.mul(x)).mul(x)).add(WAD_SQUARED_MINUS_1) / WAD_SQUARED; } function wadDiv(uint x, uint y, Round upOrDown) internal pure returns (uint) { return upOrDown == Round.Down ? wadDivDown(x, y) : wadDivUp(x, y); } function wadDivDown(uint x, uint y) internal pure returns (uint) { return (x.mul(WAD)).div(y); } function wadDivUp(uint x, uint y) internal pure returns (uint) { return ((x.mul(WAD)).add(y - 1)).div(y); // Can use "-" instead of sub() since div(y) will catch y = 0 case anyway } function wadHalfExp(uint power) internal pure returns (uint) { return wadHalfExp(power, uint(-1)); } // Returns a loose but "gas-efficient" approximation of 0.5**power, where power is rounded to the nearest 0.1, and is // capped at maxPower. Note power is WAD-scaled (eg, 2.7364 * WAD), but maxPower is just a plain unscaled uint (eg, 10). // Negative powers are not handled (as implied by power being a uint). function wadHalfExp(uint power, uint maxPower) internal pure returns (uint) { uint powerInTenthsUnscaled = power.add(WAD_OVER_20) / WAD_OVER_10; if (powerInTenthsUnscaled / 10 > maxPower) { return 0; } return wadPow(HALF_TO_THE_ONE_TENTH, powerInTenthsUnscaled); } // Adapted from rpow() in https://github.com/dapphub/ds-math/blob/master/src/math.sol - thank you! // // This famous algorithm is called "exponentiation by squaring" // and calculates x^n with x as fixed-point and n as regular unsigned. // // It's O(log n), instead of O(n) for naive repeated multiplication. // // These facts are why it works: // // If n is even, then x^n = (x^2)^(n/2). // If n is odd, then x^n = x * x^(n-1), // and applying the equation for even x gives // x^n = x * (x^2)^((n-1) / 2). // // Also, EVM division is flooring and // floor[(n-1) / 2] = floor[n / 2]. // function wadPow(uint x, uint n) internal pure returns (uint z) { z = n % 2 != 0 ? x : WAD; for (n /= 2; n != 0; n /= 2) { x = wadSquaredDown(x); if (n % 2 != 0) { z = wadMulDown(z, x); } } } // Using Newton's method (see eg https://stackoverflow.com/a/8827111/3996653), but with WAD fixed-point math. function wadCbrtDown(uint y) internal pure returns (uint root) { if (y > 0 ) { uint newRoot = y.add(TWO_WAD) / 3; uint yTimesWadSquared = y.mul(WAD_SQUARED); do { root = newRoot; newRoot = (root + root + (yTimesWadSquared / (root * root))) / 3; } while (newRoot < root); } //require(root**3 <= y.mul(WAD_SQUARED) && y.mul(WAD_SQUARED) < (root + 1)**3); } function wadCbrtUp(uint y) internal pure returns (uint root) { root = wadCbrtDown(y); // The only case where wadCbrtUp(y) *isn't* equal to wadCbrtDown(y) + 1 is when y is a perfect cube; so check for that. // These "*"s are safe because: 1. root**3 <= y.mul(WAD_SQUARED), and 2. y.mul(WAD_SQUARED) is calculated (safely) above. if (root * root * root != y * WAD_SQUARED ) { ++root; } //require((root - 1)**3 < y.mul(WAD_SQUARED) && y.mul(WAD_SQUARED) <= root**3); } } // File: @openzeppelin/contracts/access/Ownable.sol pragma solidity ^0.6.0; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () internal { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(_owner == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } // File: contracts/MinOut.sol pragma solidity ^0.6.6; library MinOut { function parseMinTokenOut(uint ethIn) internal pure returns (uint minTokenOut) { uint minPrice = ethIn % 100000000000; if (minPrice != 0 && minPrice < 10000000) { minTokenOut = ethIn * minPrice / 100; } } function parseMinEthOut(uint tokenIn) internal pure returns (uint minEthOut) { uint maxPrice = tokenIn % 100000000000; if (maxPrice != 0 && maxPrice < 10000000) { minEthOut = tokenIn * 100 / maxPrice; } } } // File: contracts/FUM.sol pragma solidity ^0.6.6; /** * @title FUM Token * @author Alberto Cuesta Cañada, Jacob Eliosoff, Alex Roan * * @notice This should be owned by the stablecoin. */ contract FUM is ERC20Permit, Ownable { IUSM public immutable usm; constructor(IUSM usm_) public ERC20Permit("Minimal Funding", "FUM") { usm = usm_; } /** * @notice If anyone sends ETH here, assume they intend it as a `fund`. * If decimals 8 to 11 (included) of the amount of Ether received are `0000` then the next 7 will * be parsed as the minimum Ether price accepted, with 2 digits before and 5 digits after the comma. */ receive() external payable { usm.fund{ value: msg.value }(msg.sender, MinOut.parseMinTokenOut(msg.value)); } /** * @notice If a user sends FUM tokens directly to this contract (or to the USM contract), assume they intend it as a `defund`. * If using `transfer`/`transferFrom` as `defund`, and if decimals 8 to 11 (included) of the amount transferred received * are `0000` then the next 7 will be parsed as the maximum FUM price accepted, with 5 digits before and 2 digits after the comma. */ function _transfer(address sender, address recipient, uint256 amount) internal override { if (recipient == address(this) || recipient == address(usm) || recipient == address(0)) { usm.defundFromFUM(sender, payable(sender), amount, MinOut.parseMinEthOut(amount)); } else { super._transfer(sender, recipient, amount); } } /** * @notice Mint new FUM to the _recipient * * @param _recipient address to mint to * @param _amount amount to mint */ function mint(address _recipient, uint _amount) external onlyOwner { _mint(_recipient, _amount); } /** * @notice Burn FUM from _holder * * @param _holder address to burn from * @param _amount amount to burn */ function burn(address _holder, uint _amount) external onlyOwner { _burn(_holder, _amount); } } // File: contracts/oracles/Oracle.sol pragma solidity ^0.6.6; abstract contract Oracle { function latestPrice() public virtual view returns (uint price); // Prices must be WAD-scaled - 18 decimal places /** * @dev This pure virtual implementation, which is intended to be (optionally) overridden by stateful implementations, * confuses solhint into giving a "Function state mutability can be restricted to view" warning. Unfortunately there seems * to be no elegant/gas-free workaround as yet: see https://github.com/ethereum/solidity/issues/3529. */ function cacheLatestPrice() public virtual returns (uint price) { price = latestPrice(); // Default implementation doesn't do any cacheing, just returns price. But override as needed } } // File: contracts/USMTemplate.sol pragma solidity ^0.6.6; // import "@nomiclabs/buidler/console.sol"; /** * @title USMTemplate * @author Alberto Cuesta Cañada, Jacob Eliosoff, Alex Roan * @notice Concept by Jacob Eliosoff (@jacob-eliosoff). * * This abstract USM contract must be inherited by a concrete implementation, that also adds an Oracle implementation - eg, by * also inheriting a concrete Oracle implementation. See USM (and MockUSM) for an example. * * We use this inheritance-based design (rather than the more natural, and frankly normally more correct, composition-based design * of storing the Oracle here as a variable), because inheriting the Oracle makes all the latestPrice() calls *internal* rather * than calls to a separate oracle contract (or multiple contracts) - which leads to a significant saving in gas. */ abstract contract USMTemplate is IUSM, Oracle, ERC20Permit, Delegable { using Address for address payable; using SafeMath for uint; using WadMath for uint; enum Side {Buy, Sell} event MinFumBuyPriceChanged(uint previous, uint latest); event BuySellAdjustmentChanged(uint previous, uint latest); uint public constant WAD = 10 ** 18; uint public constant MAX_DEBT_RATIO = WAD * 8 / 10; // 80% uint public constant MIN_FUM_BUY_PRICE_HALF_LIFE = 1 days; // Solidity for 1 * 24 * 60 * 60 uint public constant BUY_SELL_ADJUSTMENT_HALF_LIFE = 1 minutes; // Solidity for 1 * 60 FUM public immutable fum; uint256 deadline; // Second at which the trial expires and `mint` and `fund` get disabled. struct TimedValue { uint32 timestamp; uint224 value; } TimedValue public minFumBuyPriceStored; TimedValue public buySellAdjustmentStored = TimedValue({ timestamp: 0, value: uint224(WAD) }); constructor() public ERC20Permit("Minimal USD", "USM") { fum = new FUM(this); deadline = now + (60 * 60 * 24 * 28); // Four weeks into the future } /** EXTERNAL TRANSACTIONAL FUNCTIONS **/ /** * @notice Mint new USM, sending it to the given address, and only if the amount minted >= minUsmOut. The amount of ETH is * passed in as msg.value. * @param to address to send the USM to. * @param minUsmOut Minimum accepted USM for a successful mint. */ function mint(address to, uint minUsmOut) external payable override returns (uint usmOut) { usmOut = _mintUsm(to, minUsmOut); } /** * @dev Burn USM in exchange for ETH. * @param from address to deduct the USM from. * @param to address to send the ETH to. * @param usmToBurn Amount of USM to burn. * @param minEthOut Minimum accepted ETH for a successful burn. */ function burn(address from, address payable to, uint usmToBurn, uint minEthOut) external override onlyHolderOrDelegate(from, "Only holder or delegate") returns (uint ethOut) { ethOut = _burnUsm(from, to, usmToBurn, minEthOut); } /** * @notice Funds the pool with ETH, minting new FUM and sending it to the given address, but only if the amount minted >= * minFumOut. The amount of ETH is passed in as msg.value. * @param to address to send the FUM to. * @param minFumOut Minimum accepted FUM for a successful fund. */ function fund(address to, uint minFumOut) external payable override returns (uint fumOut) { fumOut = _fundFum(to, minFumOut); } /** * @notice Defunds the pool by redeeming FUM in exchange for equivalent ETH from the pool. * @param from address to deduct the FUM from. * @param to address to send the ETH to. * @param fumToBurn Amount of FUM to burn. * @param minEthOut Minimum accepted ETH for a successful defund. */ function defund(address from, address payable to, uint fumToBurn, uint minEthOut) external override onlyHolderOrDelegate(from, "Only holder or delegate") returns (uint ethOut) { ethOut = _defundFum(from, to, fumToBurn, minEthOut); } /** * @notice Defunds the pool by redeeming FUM from an arbitrary address in exchange for equivalent ETH from the pool. * Called only by the FUM contract, when FUM is sent to it. * @param from address to deduct the FUM from. * @param to address to send the ETH to. * @param fumToBurn Amount of FUM to burn. * @param minEthOut Minimum accepted ETH for a successful defund. */ function defundFromFUM(address from, address payable to, uint fumToBurn, uint minEthOut) external override returns (uint ethOut) { require(msg.sender == address(fum), "Restricted to FUM"); ethOut = _defundFum(from, to, fumToBurn, minEthOut); } /** * @notice If anyone sends ETH here, assume they intend it as a `mint`. * If decimals 8 to 11 (included) of the amount of Ether received are `0000` then the next 7 will * be parsed as the minimum Ether price accepted, with 2 digits before and 5 digits after the comma. */ receive() external payable { _mintUsm(msg.sender, MinOut.parseMinTokenOut(msg.value)); } /** * @notice If a user sends USM tokens directly to this contract (or to the FUM contract), assume they intend it as a `burn`. * If using `transfer`/`transferFrom` as `burn`, and if decimals 8 to 11 (included) of the amount transferred received * are `0000` then the next 7 will be parsed as the maximum USM price accepted, with 5 digits before and 2 digits after the comma. */ function _transfer(address sender, address recipient, uint256 amount) internal override { if (recipient == address(this) || recipient == address(fum) || recipient == address(0)) { _burnUsm(sender, payable(sender), amount, MinOut.parseMinEthOut(amount)); } else { super._transfer(sender, recipient, amount); } } /** INTERNAL TRANSACTIONAL FUNCTIONS */ function _mintUsm(address to, uint minUsmOut) internal returns (uint usmOut) { // 1. Check that fund() has been called first - no minting before funding: uint rawEthInPool = ethPool(); uint ethInPool = rawEthInPool.sub(msg.value); // Backing out the ETH just received, which our calculations should ignore require(ethInPool > 0, "Fund before minting"); // 2. Calculate usmOut: uint ethUsmPrice = cacheLatestPrice(); uint usmTotalSupply = totalSupply(); uint oldDebtRatio = debtRatio(ethUsmPrice, ethInPool, usmTotalSupply); usmOut = usmFromMint(ethUsmPrice, msg.value, ethInPool, usmTotalSupply, oldDebtRatio); require(usmOut >= minUsmOut, "Limit not reached"); // 3. Update buySellAdjustmentStored and mint the user's new USM: uint newDebtRatio = debtRatio(ethUsmPrice, rawEthInPool, usmTotalSupply.add(usmOut)); _updateBuySellAdjustment(oldDebtRatio, newDebtRatio, buySellAdjustment()); _mint(to, usmOut); require(now <= deadline, "Trial expired, remove assets"); require(msg.value <= 1e18, "Capped at 1 ETH per tx"); require(ethPool() <= 1e20, "Capped at 100 pooled ETH"); } function _burnUsm(address from, address payable to, uint usmToBurn, uint minEthOut) internal returns (uint ethOut) { // 1. Calculate ethOut: uint ethUsmPrice = cacheLatestPrice(); uint ethInPool = ethPool(); uint usmTotalSupply = totalSupply(); uint oldDebtRatio = debtRatio(ethUsmPrice, ethInPool, usmTotalSupply); ethOut = ethFromBurn(ethUsmPrice, usmToBurn, ethInPool, usmTotalSupply, oldDebtRatio); require(ethOut >= minEthOut, "Limit not reached"); // 2. Burn the input USM, update buySellAdjustmentStored, and return the user's ETH: uint newDebtRatio = debtRatio(ethUsmPrice, ethInPool.sub(ethOut), usmTotalSupply.sub(usmToBurn)); require(newDebtRatio <= WAD, "Debt ratio > 100%"); _burn(from, usmToBurn); _updateBuySellAdjustment(oldDebtRatio, newDebtRatio, buySellAdjustment()); to.sendValue(ethOut); } function _fundFum(address to, uint minFumOut) internal returns (uint fumOut) { // 1. Refresh mfbp: uint ethUsmPrice = cacheLatestPrice(); uint rawEthInPool = ethPool(); uint ethInPool = rawEthInPool.sub(msg.value); // Backing out the ETH just received, which our calculations should ignore uint usmTotalSupply = totalSupply(); uint oldDebtRatio = debtRatio(ethUsmPrice, ethInPool, usmTotalSupply); uint fumTotalSupply = fum.totalSupply(); _updateMinFumBuyPrice(oldDebtRatio, ethInPool, fumTotalSupply); // 2. Calculate fumOut: uint adjustment = buySellAdjustment(); fumOut = fumFromFund(ethUsmPrice, msg.value, ethInPool, usmTotalSupply, fumTotalSupply, adjustment); require(fumOut >= minFumOut, "Limit not reached"); // 3. Update buySellAdjustmentStored and mint the user's new FUM: uint newDebtRatio = debtRatio(ethUsmPrice, rawEthInPool, usmTotalSupply); _updateBuySellAdjustment(oldDebtRatio, newDebtRatio, adjustment); fum.mint(to, fumOut); require(now <= deadline, "Trial expired, remove assets"); require(msg.value <= 1e18, "Capped at 1 ETH per tx"); require(ethPool() <= 1e20, "Capped at 100 pooled ETH"); } function _defundFum(address from, address payable to, uint fumToBurn, uint minEthOut) internal returns (uint ethOut) { // 1. Calculate ethOut: uint ethUsmPrice = cacheLatestPrice(); uint ethInPool = ethPool(); uint usmTotalSupply = totalSupply(); uint oldDebtRatio = debtRatio(ethUsmPrice, ethInPool, usmTotalSupply); ethOut = ethFromDefund(ethUsmPrice, fumToBurn, ethInPool, usmTotalSupply); require(ethOut >= minEthOut, "Limit not reached"); // 2. Burn the input FUM, update buySellAdjustmentStored, and return the user's ETH: uint newDebtRatio = debtRatio(ethUsmPrice, ethInPool.sub(ethOut), usmTotalSupply); require(newDebtRatio <= MAX_DEBT_RATIO, "Debt ratio > max"); fum.burn(from, fumToBurn); _updateBuySellAdjustment(oldDebtRatio, newDebtRatio, buySellAdjustment()); to.sendValue(ethOut); } /** * @notice Set the min FUM price, based on the current oracle price and debt ratio. Emits a MinFumBuyPriceChanged event. * @dev The logic for calculating a new minFumBuyPrice is as follows. We want to set it to the FUM price, in ETH terms, at * which debt ratio was exactly MAX_DEBT_RATIO. So we can assume: * * usmToEth(totalSupply()) / ethPool() = MAX_DEBT_RATIO, or in other words: * usmToEth(totalSupply()) = MAX_DEBT_RATIO * ethPool() * * And with this assumption, we calculate the FUM price (buffer / FUM qty) like so: * * minFumBuyPrice = ethBuffer() / fum.totalSupply() * = (ethPool() - usmToEth(totalSupply())) / fum.totalSupply() * = (ethPool() - (MAX_DEBT_RATIO * ethPool())) / fum.totalSupply() * = (1 - MAX_DEBT_RATIO) * ethPool() / fum.totalSupply() */ function _updateMinFumBuyPrice(uint debtRatio_, uint ethInPool, uint fumTotalSupply) internal { uint previous = minFumBuyPriceStored.value; if (debtRatio_ <= MAX_DEBT_RATIO) { // We've dropped below (or were already below, whatev) max debt ratio if (previous != 0) { minFumBuyPriceStored.timestamp = 0; // Clear mfbp minFumBuyPriceStored.value = 0; emit MinFumBuyPriceChanged(previous, 0); } } else if (previous == 0) { // We were < max debt ratio, but have now crossed above - so set mfbp // See reasoning in @dev comment above minFumBuyPriceStored.timestamp = uint32(block.timestamp); minFumBuyPriceStored.value = uint224((WAD - MAX_DEBT_RATIO).wadMulUp(ethInPool).wadDivUp(fumTotalSupply)); emit MinFumBuyPriceChanged(previous, minFumBuyPriceStored.value); } } /** * @notice Update the buy/sell adjustment factor, as of the current block time, after a price-moving operation. * @param oldDebtRatio The debt ratio before the operation (eg, mint()) was done * @param newDebtRatio The current, post-op debt ratio */ function _updateBuySellAdjustment(uint oldDebtRatio, uint newDebtRatio, uint oldAdjustment) internal { // Skip this if either the old or new debt ratio == 0. Normally this will only happen on the system's first couple of // calls, but in principle it could happen later if every single USM holder burns all their USM. This seems // vanishingly unlikely though if the system gets any uptake at all, and anyway if it does happen it will just mean a // couple of skipped updates to the buySellAdjustment - no big deal. if (oldDebtRatio != 0 && newDebtRatio != 0) { uint previous = buySellAdjustmentStored.value; // Not nec the same as oldAdjustment, because of the time decay! // Eg: if a user operation reduced debt ratio from 70% to 50%, it was either a fund() or a burn(). These are both // "long-ETH" operations. So we can take (old / new)**2 = (70% / 50%)**2 = 1.4**2 = 1.96 as the ratio by which to // increase buySellAdjustment, which is intended as a measure of "how long-ETH recent user activity has been": uint newAdjustment = (oldAdjustment.mul(oldDebtRatio).mul(oldDebtRatio) / newDebtRatio) / newDebtRatio; buySellAdjustmentStored.timestamp = uint32(block.timestamp); buySellAdjustmentStored.value = uint224(newAdjustment); emit BuySellAdjustmentChanged(previous, newAdjustment); } } /** PUBLIC AND INTERNAL VIEW FUNCTIONS **/ /** * @notice Total amount of ETH in the pool (ie, in the contract). * @return pool ETH pool */ function ethPool() public view returns (uint pool) { pool = address(this).balance; } /** * @notice Calculate the amount of ETH in the buffer. * @return buffer ETH buffer */ function ethBuffer(uint ethUsmPrice, uint ethInPool, uint usmTotalSupply, WadMath.Round upOrDown) internal pure returns (int buffer) { // Reverse the input upOrDown, since we're using it for usmToEth(), which will be *subtracted* from ethInPool below: WadMath.Round downOrUp = (upOrDown == WadMath.Round.Down ? WadMath.Round.Up : WadMath.Round.Down); buffer = int(ethInPool) - int(usmToEth(ethUsmPrice, usmTotalSupply, downOrUp)); require(buffer <= int(ethInPool), "Underflow error"); } /** * @notice Calculate debt ratio for a given eth to USM price: ratio of the outstanding USM (amount of USM in total supply), to * the current ETH pool amount. * @return ratio Debt ratio */ function debtRatio(uint ethUsmPrice, uint ethInPool, uint usmTotalSupply) internal pure returns (uint ratio) { uint ethPoolValueInUsd = ethInPool.wadMulDown(ethUsmPrice); ratio = (ethInPool == 0 ? 0 : usmTotalSupply.wadDivUp(ethPoolValueInUsd)); } /** * @notice Convert ETH amount to USM using a ETH/USD price. * @param ethAmount The amount of ETH to convert * @return usmOut The amount of USM */ function ethToUsm(uint ethUsmPrice, uint ethAmount, WadMath.Round upOrDown) internal pure returns (uint usmOut) { usmOut = ethAmount.wadMul(ethUsmPrice, upOrDown); } /** * @notice Convert USM amount to ETH using a ETH/USD price. * @param usmAmount The amount of USM to convert * @return ethOut The amount of ETH */ function usmToEth(uint ethUsmPrice, uint usmAmount, WadMath.Round upOrDown) internal pure returns (uint ethOut) { ethOut = usmAmount.wadDiv(ethUsmPrice, upOrDown); } /** * @notice Calculate the *marginal* price of USM (in ETH terms) - that is, of the next unit, before the price start sliding. * @return price USM price in ETH terms */ function usmPrice(Side side, uint ethUsmPrice, uint debtRatio_) internal view returns (uint price) { WadMath.Round upOrDown = (side == Side.Buy ? WadMath.Round.Up : WadMath.Round.Down); price = usmToEth(ethUsmPrice, WAD, upOrDown); uint adjustment = buySellAdjustment(); if (debtRatio_ <= WAD) { if ((side == Side.Buy && adjustment < WAD) || (side == Side.Sell && adjustment > WAD)) { price = price.wadDiv(adjustment, upOrDown); } } else { // See comment at the bottom of usmFromMint() explaining this special case where debtRatio > 100% if ((side == Side.Buy && adjustment > WAD) || (side == Side.Sell && adjustment < WAD)) { price = price.wadMul(adjustment, upOrDown); } } } /** * @notice Calculate the *marginal* price of FUM (in ETH terms) - that is, of the next unit, before the price start sliding. * @return price FUM price in ETH terms */ function fumPrice(Side side, uint ethUsmPrice, uint ethInPool, uint usmTotalSupply, uint fumTotalSupply, uint adjustment) internal view returns (uint price) { WadMath.Round upOrDown = (side == Side.Buy ? WadMath.Round.Up : WadMath.Round.Down); if (fumTotalSupply == 0) { return usmToEth(ethUsmPrice, WAD, upOrDown); // if no FUM issued yet, default fumPrice to 1 USD (in ETH terms) } int buffer = ethBuffer(ethUsmPrice, ethInPool, usmTotalSupply, upOrDown); price = (buffer <= 0 ? 0 : uint(buffer).wadDiv(fumTotalSupply, upOrDown)); if (side == Side.Buy) { if (adjustment > WAD) { price = price.wadMulUp(adjustment); } // Floor the buy price at minFumBuyPrice: uint mfbp = minFumBuyPrice(); if (price < mfbp) { price = mfbp; } } else { if (adjustment < WAD) { price = price.wadMulDown(adjustment); } } } /** * @notice How much USM a minter currently gets back for ethIn ETH, accounting for adjustment and sliding prices. * @param ethIn The amount of ETH passed to mint() * @return usmOut The amount of USM to receive in exchange */ function usmFromMint(uint ethUsmPrice, uint ethIn, uint ethQty0, uint usmQty0, uint debtRatio0) internal view returns (uint usmOut) { uint usmPrice0 = usmPrice(Side.Buy, ethUsmPrice, debtRatio0); uint ethQty1 = ethQty0.add(ethIn); if (usmQty0 == 0) { // No USM in the system, so debtRatio() == 0 which breaks the integral below - skip sliding-prices this time: usmOut = ethIn.wadDivDown(usmPrice0); } else if (debtRatio0 <= WAD) { // Mint USM at a sliding-up USM price (ie, at a sliding-down ETH price). **BASIC RULE:** anytime debtRatio() // changes by factor k (here > 1), ETH price changes by factor 1/k**2 (ie, USM price, in ETH terms, changes by // factor k**2). (Earlier versions of this logic scaled ETH price based on change in ethPool(), or change in // ethPool()**2: the latter gives simpler math - no cbrt() - but doesn't let mint/burn offset fund/defund, which // debtRatio()**2 nicely does.) // Math: this is an integral - sum of all USM minted at a sliding-down ETH price: // u - u_0 = ((((e / e_0)**3 - 1) * e_0 / ubp_0 + u_0) * u_0**2)**(1/3) - u_0 uint integralFirstPart = (ethQty1.wadDivDown(ethQty0).wadCubedDown().sub(WAD)).mul(ethQty0).div(usmPrice0); usmOut = (integralFirstPart.add(usmQty0)).wadMulDown(usmQty0.wadSquaredDown()).wadCbrtDown().sub(usmQty0); } else { // Here we have the special, unusual case where we're minting while debt ratio > 100%. In this case (only), // minting will actually *reduce* debt ratio, whereas normally it increases it. (In short: minting always pushes // debt ratio closer to 100%.) Because debt ratio is decreasing as we buy, and USM buy price must *increase* as we // buy, we need to make USM price grow proportionally to (1 / change in debt ratio)**2, rather than the usual // (1 / change in debt ratio)**2 above. This gives the following different integral: // x = e0 * (e - e0) / (u0 * pu0) // u - u_0 = u0 * x / (e - x) uint integralFirstPart = ethQty0.mul(ethIn).div(usmQty0.wadMulUp(usmPrice0)); usmOut = usmQty0.mul(integralFirstPart).div(ethQty1.sub(integralFirstPart)); } } /** * @notice How much ETH a burner currently gets from burning usmIn USM, accounting for adjustment and sliding prices. * @param usmIn The amount of USM passed to burn() * @return ethOut The amount of ETH to receive in exchange */ function ethFromBurn(uint ethUsmPrice, uint usmIn, uint ethQty0, uint usmQty0, uint debtRatio0) internal view returns (uint ethOut) { // Burn USM at a sliding-down USM price (ie, a sliding-up ETH price): uint usmPrice0 = usmPrice(Side.Sell, ethUsmPrice, debtRatio0); uint usmQty1 = usmQty0.sub(usmIn); // Math: this is an integral - sum of all USM burned at a sliding price. Follows the same mathematical invariant as // above: if debtRatio() *= k (here, k < 1), ETH price *= 1/k**2, ie, USM price in ETH terms *= k**2. // e_0 - e = e_0 - (e_0**2 * (e_0 - usp_0 * u_0 * (1 - (u / u_0)**3)))**(1/3) uint integralFirstPart = usmPrice0.wadMulDown(usmQty0).wadMulDown(WAD.sub(usmQty1.wadDivUp(usmQty0).wadCubedUp())); ethOut = ethQty0.sub(ethQty0.wadSquaredUp().wadMulUp(ethQty0.sub(integralFirstPart)).wadCbrtUp()); } /** * @notice How much FUM a funder currently gets back for ethIn ETH, accounting for adjustment and sliding prices. * @param ethIn The amount of ETH passed to fund() * @return fumOut The amount of FUM to receive in exchange */ function fumFromFund(uint ethUsmPrice, uint ethIn, uint ethQty0, uint usmQty0, uint fumQty0, uint adjustment) internal view returns (uint fumOut) { // Create FUM at a sliding-up FUM price: uint fumPrice0 = fumPrice(Side.Buy, ethUsmPrice, ethQty0, usmQty0, fumQty0, adjustment); if (usmQty0 == 0) { // No USM in the system - skip sliding-prices: fumOut = ethIn.wadDivDown(fumPrice0); } else { // Math: f - f_0 = e_0 * (e - e_0) / (e * fbp_0) uint ethQty1 = ethQty0.add(ethIn); fumOut = ethQty0.mul(ethIn).div(ethQty1.wadMulUp(fumPrice0)); } } /** * @notice How much ETH a defunder currently gets back for fumIn FUM, accounting for adjustment and sliding prices. * @param fumIn The amount of FUM passed to defund() * @return ethOut The amount of ETH to receive in exchange */ function ethFromDefund(uint ethUsmPrice, uint fumIn, uint ethQty0, uint usmQty0) internal view returns (uint ethOut) { // Burn FUM at a sliding-down FUM price: uint fumQty0 = fum.totalSupply(); uint fumPrice0 = fumPrice(Side.Sell, ethUsmPrice, ethQty0, usmQty0, fumQty0, buySellAdjustment()); if (usmQty0 == 0) { // No USM in the system - skip sliding-prices: ethOut = fumIn.wadMulDown(fumPrice0); } else { // Math: e_0 - e = e_0 * (f_0 - f) * fsp_0 / (e_0 + (f_0 - f) * fsp_0) ethOut = ethQty0.mul(fumIn.wadMulDown(fumPrice0)).div(ethQty0.add(fumIn.wadMulUp(fumPrice0))); } } /** * @notice The current min FUM buy price, equal to the stored value decayed by time since minFumBuyPriceTimestamp. * @return mfbp The minFumBuyPrice, in ETH terms */ function minFumBuyPrice() public view returns (uint mfbp) { if (minFumBuyPriceStored.value != 0) { uint numHalvings = block.timestamp.sub(minFumBuyPriceStored.timestamp).wadDivDown(MIN_FUM_BUY_PRICE_HALF_LIFE); uint decayFactor = numHalvings.wadHalfExp(); mfbp = uint256(minFumBuyPriceStored.value).wadMulUp(decayFactor); } // Otherwise just returns 0 } /** * @notice The current buy/sell adjustment, equal to the stored value decayed by time since buySellAdjustmentTimestamp. This * adjustment is intended as a measure of "how long-ETH recent user activity has been", so that we can slide price * accordingly: if recent activity was mostly long-ETH (fund() and burn()), raise FUM buy price/reduce USM sell price; if * recent activity was short-ETH (defund() and mint()), reduce FUM sell price/raise USM buy price. We use "it reduced debt * ratio" as a rough proxy for "the operation was long-ETH". * * (There is one odd case: when debt ratio > 100%, a *short*-ETH mint() will actually reduce debt ratio. This does no real * harm except to make fast-succession mint()s and fund()s in such > 100% cases a little more expensive than they would be.) * * @return adjustment The sliding-price buy/sell adjustment */ function buySellAdjustment() public view returns (uint adjustment) { uint numHalvings = block.timestamp.sub(buySellAdjustmentStored.timestamp).wadDivDown(BUY_SELL_ADJUSTMENT_HALF_LIFE); uint decayFactor = numHalvings.wadHalfExp(10); // Here we use the idea that for any b and 0 <= p <= 1, we can crudely approximate b**p by 1 + (b-1)p = 1 + bp - p. // Eg: 0.6**0.5 pulls 0.6 "about halfway" to 1 (0.8); 0.6**0.25 pulls 0.6 "about 3/4 of the way" to 1 (0.9). // So b**p =~ b + (1-p)(1-b) = b + 1 - b - p + bp = 1 + bp - p. // (Don't calculate it as 1 + (b-1)p because we're using uints, b-1 can be negative!) adjustment = WAD.add(uint256(buySellAdjustmentStored.value).wadMulDown(decayFactor)).sub(decayFactor); } /** EXTERNAL VIEW FUNCTIONS */ /** * @notice Calculate the amount of ETH in the buffer. * @return buffer ETH buffer */ function ethBuffer(WadMath.Round upOrDown) external view returns (int buffer) { buffer = ethBuffer(latestPrice(), ethPool(), totalSupply(), upOrDown); } /** * @notice Convert ETH amount to USM using the latest oracle ETH/USD price. * @param ethAmount The amount of ETH to convert * @return usmOut The amount of USM */ function ethToUsm(uint ethAmount, WadMath.Round upOrDown) external view returns (uint usmOut) { usmOut = ethToUsm(latestPrice(), ethAmount, upOrDown); } /** * @notice Convert USM amount to ETH using the latest oracle ETH/USD price. * @param usmAmount The amount of USM to convert * @return ethOut The amount of ETH */ function usmToEth(uint usmAmount, WadMath.Round upOrDown) external view returns (uint ethOut) { ethOut = usmToEth(latestPrice(), usmAmount, upOrDown); } /** * @notice Calculate debt ratio. * @return ratio Debt ratio */ function debtRatio() external view returns (uint ratio) { ratio = debtRatio(latestPrice(), ethPool(), totalSupply()); } /** * @notice Calculate the *marginal* price of USM (in ETH terms) - that is, of the next unit, before the price start sliding. * @return price USM price in ETH terms */ function usmPrice(Side side) external view returns (uint price) { uint ethUsdPrice = latestPrice(); price = usmPrice(side, ethUsdPrice, debtRatio(ethUsdPrice, ethPool(), totalSupply())); } /** * @notice Calculate the *marginal* price of FUM (in ETH terms) - that is, of the next unit, before the price start sliding. * @return price FUM price in ETH terms */ function fumPrice(Side side) external view returns (uint price) { price = fumPrice(side, latestPrice(), ethPool(), totalSupply(), fum.totalSupply(), buySellAdjustment()); } } // File: @chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol pragma solidity >=0.6.0; interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external view returns (uint256); // getRoundData and latestRoundData should both raise "No data present" // if they do not have data to report, instead of returning unset values // which could be misinterpreted as actual reported values. function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); } // File: contracts/oracles/ChainlinkOracle.sol pragma solidity ^0.6.6; /** * @title ChainlinkOracle */ contract ChainlinkOracle is Oracle { using SafeMath for uint; uint private constant SCALE_FACTOR = 10 ** 10; // Since Chainlink has 8 dec places, and latestPrice() needs 18 AggregatorV3Interface private aggregator; constructor(AggregatorV3Interface aggregator_) public { aggregator = aggregator_; } /** * @notice Retrieve the latest price of the price oracle. * @return price */ function latestPrice() public virtual override view returns (uint price) { price = latestChainlinkPrice(); } function latestChainlinkPrice() public view returns (uint price) { (, int rawPrice,,,) = aggregator.latestRoundData(); price = uint(rawPrice).mul(SCALE_FACTOR); // TODO: Cast safely } } // File: contracts/oracles/CompoundOpenOracle.sol pragma solidity ^0.6.6; interface UniswapAnchoredView { function price(string calldata symbol) external view returns (uint); } /** * @title CompoundOpenOracle */ contract CompoundOpenOracle is Oracle { using SafeMath for uint; uint private constant SCALE_FACTOR = 10 ** 12; // Since Compound has 6 dec places, and latestPrice() needs 18 UniswapAnchoredView private anchoredView; constructor(UniswapAnchoredView anchoredView_) public { anchoredView = anchoredView_; } /** * @notice Retrieve the latest price of the price oracle. * @return price */ function latestPrice() public virtual override view returns (uint price) { price = latestCompoundPrice(); } function latestCompoundPrice() public view returns (uint price) { price = anchoredView.price("ETH").mul(SCALE_FACTOR); } } // File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol pragma solidity >=0.5.0; interface IUniswapV2Pair { event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value); function name() external pure returns (string memory); function symbol() external pure returns (string memory); function decimals() external pure returns (uint8); function totalSupply() external view returns (uint); function balanceOf(address owner) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); function approve(address spender, uint value) external returns (bool); function transfer(address to, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); function DOMAIN_SEPARATOR() external view returns (bytes32); function PERMIT_TYPEHASH() external pure returns (bytes32); function nonces(address owner) external view returns (uint); function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; event Mint(address indexed sender, uint amount0, uint amount1); event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); event Swap( address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to ); event Sync(uint112 reserve0, uint112 reserve1); function MINIMUM_LIQUIDITY() external pure returns (uint); function factory() external view returns (address); function token0() external view returns (address); function token1() external view returns (address); function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function price0CumulativeLast() external view returns (uint); function price1CumulativeLast() external view returns (uint); function kLast() external view returns (uint); function mint(address to) external returns (uint liquidity); function burn(address to) external returns (uint amount0, uint amount1); function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; function skim(address to) external; function sync() external; function initialize(address, address) external; } // File: contracts/oracles/OurUniswapV2TWAPOracle.sol pragma solidity ^0.6.6; contract OurUniswapV2TWAPOracle is Oracle { using SafeMath for uint; /** * MIN_TWAP_PERIOD plays two roles: * * 1. Minimum age of the stored CumulativePrice we calculate our current TWAP vs. Eg, if one of our stored prices is from * 5 secs ago, and the other from 10 min ago, we should calculate TWAP vs the 10-min-old one, since a 5-second TWAP is too * short - relatively easy to manipulate. * * 2. Minimum time gap between stored CumulativePrices. Eg, if we stored one 5 seconds ago, we don't need to store another * one now - and shouldn't, since then if someone else made a TWAP call a few seconds later, both stored prices would be * too recent to calculate a robust TWAP. * * These roles could in principle be separated, eg: "Require the stored price we calculate TWAP from to be >= 2 minutes * old, but leave >= 10 minutes before storing a new price." But for simplicity we keep them the same. */ uint public constant MIN_TWAP_PERIOD = 2 minutes; // Uniswap stores its cumulative prices in "FixedPoint.uq112x112" format - 112-bit fixed point: uint public constant UNISWAP_CUM_PRICE_SCALE_FACTOR = 2 ** 112; uint private constant UINT32_MAX = 2 ** 32 - 1; // Should really be type(uint32).max, but that needs Solidity 0.6.8... uint private constant UINT224_MAX = 2 ** 224 - 1; // Ditto, type(uint224).max IUniswapV2Pair immutable uniswapPair; uint immutable token0Decimals; uint immutable token1Decimals; bool immutable tokensInReverseOrder; uint immutable scaleFactor; struct CumulativePrice { uint32 timestamp; uint224 priceSeconds; // See cumulativePrice() below for an explanation of "priceSeconds" } /** * We store two CumulativePrices, A and B, without specifying which is more recent. This is so that we only need to do one * SSTORE each time we save a new one: we can inspect them later to figure out which is newer - see orderedStoredPrices(). */ CumulativePrice private storedPriceA; CumulativePrice private storedPriceB; /** * Example pairs to pass in: * ETH/USDT: 0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852, false, 18, 6 (WETH reserve is stored w/ 18 dec places, USDT w/ 18) * USDC/ETH: 0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc, true, 6, 18 (USDC reserve is stored w/ 6 dec places, WETH w/ 18) * DAI/ETH: 0xa478c2975ab1ea89e8196811f51a7b7ade33eb11, true, 18, 18 (DAI reserve is stored w/ 18 dec places, WETH w/ 18) */ constructor(IUniswapV2Pair uniswapPair_, uint token0Decimals_, uint token1Decimals_, bool tokensInReverseOrder_) public { uniswapPair = uniswapPair_; token0Decimals = token0Decimals_; token1Decimals = token1Decimals_; tokensInReverseOrder = tokensInReverseOrder_; (uint aDecimals, uint bDecimals) = tokensInReverseOrder_ ? (token1Decimals_, token0Decimals_) : (token0Decimals_, token1Decimals_); scaleFactor = 10 ** aDecimals.add(18).sub(bDecimals); } function cacheLatestPrice() public virtual override returns (uint price) { (CumulativePrice storage olderStoredPrice, CumulativePrice storage newerStoredPrice) = orderedStoredPrices(); uint timestamp; uint priceSeconds; (price, timestamp, priceSeconds) = _latestPrice(newerStoredPrice); // Store the latest cumulative price, if it's been long enough since the latest stored price: if (areNewAndStoredPriceFarEnoughApart(timestamp, newerStoredPrice)) { storeCumulativePrice(timestamp, priceSeconds, olderStoredPrice); } } function latestPrice() public virtual override view returns (uint price) { price = latestUniswapTWAPPrice(); } function latestUniswapTWAPPrice() public view returns (uint price) { (, CumulativePrice storage newerStoredPrice) = orderedStoredPrices(); (price, , ) = _latestPrice(newerStoredPrice); } function _latestPrice(CumulativePrice storage newerStoredPrice) internal view returns (uint price, uint timestamp, uint priceSeconds) { (timestamp, priceSeconds) = cumulativePrice(); // Now that we have the current cum price, subtract-&-divide the stored one, to get the TWAP price: CumulativePrice storage refPrice = storedPriceToCompareVs(timestamp, newerStoredPrice); price = calculateTWAP(timestamp, priceSeconds, uint(refPrice.timestamp), uint(refPrice.priceSeconds)); } function storeCumulativePrice(uint timestamp, uint priceSeconds, CumulativePrice storage olderStoredPrice) internal { require(timestamp <= UINT32_MAX, "timestamp overflow"); require(priceSeconds <= UINT224_MAX, "priceSeconds overflow"); // (Note: this assignment only stores because olderStoredPrice has modifier "storage" - ie, store by reference!) (olderStoredPrice.timestamp, olderStoredPrice.priceSeconds) = (uint32(timestamp), uint224(priceSeconds)); } function storedPriceToCompareVs(uint newTimestamp, CumulativePrice storage newerStoredPrice) internal view returns (CumulativePrice storage refPrice) { bool aAcceptable = areNewAndStoredPriceFarEnoughApart(newTimestamp, storedPriceA); bool bAcceptable = areNewAndStoredPriceFarEnoughApart(newTimestamp, storedPriceB); if (aAcceptable) { if (bAcceptable) { refPrice = newerStoredPrice; // Neither is *too* recent, so return the fresher of the two } else { refPrice = storedPriceA; // Only A is acceptable } } else if (bAcceptable) { refPrice = storedPriceB; // Only B is acceptable } else { revert("Both stored prices too recent"); } } function orderedStoredPrices() internal view returns (CumulativePrice storage olderStoredPrice, CumulativePrice storage newerStoredPrice) { (olderStoredPrice, newerStoredPrice) = storedPriceB.timestamp > storedPriceA.timestamp ? (storedPriceA, storedPriceB) : (storedPriceB, storedPriceA); } function areNewAndStoredPriceFarEnoughApart(uint newTimestamp, CumulativePrice storage storedPrice) internal view returns (bool farEnough) { farEnough = newTimestamp >= storedPrice.timestamp + MIN_TWAP_PERIOD; // No risk of overflow on a uint32 } /** * @return timestamp Timestamp at which Uniswap stored the priceSeconds. * @return priceSeconds Our pair's cumulative "price-seconds", using Uniswap's TWAP logic. Eg, if at time t0 * priceSeconds = 10,000,000 (returned here as 10,000,000 * 10**18, ie, in WAD fixed-point format), and during the 30 * seconds between t0 and t1 = t0 + 30, the price is $45.67, then at time t1, priceSeconds = 10,000,000 + 30 * 45.67 = * 10,001,370.1 (stored as 10,001,370.1 * 10**18). */ function cumulativePrice() private view returns (uint timestamp, uint priceSeconds) { (, , timestamp) = uniswapPair.getReserves(); // Retrieve the current Uniswap cumulative price. Modeled off of Uniswap's own example: // https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/examples/ExampleOracleSimple.sol uint uniswapCumPrice = tokensInReverseOrder ? uniswapPair.price1CumulativeLast() : uniswapPair.price0CumulativeLast(); priceSeconds = uniswapCumPrice.mul(scaleFactor) / UNISWAP_CUM_PRICE_SCALE_FACTOR; } /** * @param newTimestamp in seconds (eg, 1606764888) - not WAD-scaled! * @param newPriceSeconds WAD-scaled. * @param oldTimestamp in raw seconds again. * @param oldPriceSeconds WAD-scaled. * @return price WAD-scaled. */ function calculateTWAP(uint newTimestamp, uint newPriceSeconds, uint oldTimestamp, uint oldPriceSeconds) private pure returns (uint price) { price = (newPriceSeconds.sub(oldPriceSeconds)).div(newTimestamp.sub(oldTimestamp)); } } // File: contracts/oracles/MedianOracle.sol pragma solidity ^0.6.6; contract MedianOracle is ChainlinkOracle, CompoundOpenOracle, OurUniswapV2TWAPOracle { using SafeMath for uint; constructor( AggregatorV3Interface chainlinkAggregator, UniswapAnchoredView compoundView, IUniswapV2Pair uniswapPair, uint uniswapToken0Decimals, uint uniswapToken1Decimals, bool uniswapTokensInReverseOrder ) public ChainlinkOracle(chainlinkAggregator) CompoundOpenOracle(compoundView) OurUniswapV2TWAPOracle(uniswapPair, uniswapToken0Decimals, uniswapToken1Decimals, uniswapTokensInReverseOrder) {} function latestPrice() public override(ChainlinkOracle, CompoundOpenOracle, OurUniswapV2TWAPOracle) view returns (uint price) { price = median(ChainlinkOracle.latestPrice(), CompoundOpenOracle.latestPrice(), OurUniswapV2TWAPOracle.latestPrice()); } function cacheLatestPrice() public virtual override(Oracle, OurUniswapV2TWAPOracle) returns (uint price) { price = median(ChainlinkOracle.latestPrice(), // Not ideal to call latestPrice() on two of these CompoundOpenOracle.latestPrice(), // and cacheLatestPrice() on one... But works, and OurUniswapV2TWAPOracle.cacheLatestPrice()); // inheriting them like this saves significant gas } /** * @notice Currently only supports three inputs * @return median value */ function median(uint a, uint b, uint c) private pure returns (uint) { bool ab = a > b; bool bc = b > c; bool ca = c > a; return (ca == ab ? a : (ab == bc ? b : c)); } } // File: contracts/USM.sol pragma solidity ^0.6.6; contract USM is USMTemplate, MedianOracle { constructor( AggregatorV3Interface chainlinkAggregator, UniswapAnchoredView compoundView, IUniswapV2Pair uniswapPair, uint uniswapToken0Decimals, uint uniswapToken1Decimals, bool uniswapTokensInReverseOrder ) public USMTemplate() MedianOracle(chainlinkAggregator, compoundView, uniswapPair, uniswapToken0Decimals, uniswapToken1Decimals, uniswapTokensInReverseOrder) {} function cacheLatestPrice() public virtual override(Oracle, MedianOracle) returns (uint price) { price = super.cacheLatestPrice(); } }
File 3 of 6: EACAggregatorProxy
pragma solidity 0.6.6; /** * @title The Owned contract * @notice A contract with helpers for basic contract ownership. */ contract Owned { address payable public owner; address private pendingOwner; event OwnershipTransferRequested( address indexed from, address indexed to ); event OwnershipTransferred( address indexed from, address indexed to ); constructor() public { owner = msg.sender; } /** * @dev Allows an owner to begin transferring ownership to a new address, * pending. */ function transferOwnership(address _to) external onlyOwner() { pendingOwner = _to; emit OwnershipTransferRequested(owner, _to); } /** * @dev Allows an ownership transfer to be completed by the recipient. */ function acceptOwnership() external { require(msg.sender == pendingOwner, "Must be proposed owner"); address oldOwner = owner; owner = msg.sender; pendingOwner = address(0); emit OwnershipTransferred(oldOwner, msg.sender); } /** * @dev Reverts if called by anyone other than the contract owner. */ modifier onlyOwner() { require(msg.sender == owner, "Only callable by owner"); _; } } interface AggregatorInterface { function latestAnswer() external view returns (int256); function latestTimestamp() external view returns (uint256); function latestRound() external view returns (uint256); function getAnswer(uint256 roundId) external view returns (int256); function getTimestamp(uint256 roundId) external view returns (uint256); event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); } interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external view returns (uint256); // getRoundData and latestRoundData should both raise "No data present" // if they do not have data to report, instead of returning unset values // which could be misinterpreted as actual reported values. function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); } interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface { } /** * @title A trusted proxy for updating where current answers are read from * @notice This contract provides a consistent address for the * CurrentAnwerInterface but delegates where it reads from to the owner, who is * trusted to update it. */ contract AggregatorProxy is AggregatorV2V3Interface, Owned { struct Phase { uint16 id; AggregatorV2V3Interface aggregator; } Phase private currentPhase; AggregatorV2V3Interface public proposedAggregator; mapping(uint16 => AggregatorV2V3Interface) public phaseAggregators; uint256 constant private PHASE_OFFSET = 64; uint256 constant private PHASE_SIZE = 16; uint256 constant private MAX_ID = 2**(PHASE_OFFSET+PHASE_SIZE) - 1; constructor(address _aggregator) public Owned() { setAggregator(_aggregator); } /** * @notice Reads the current answer from aggregator delegated to. * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestAnswer() public view virtual override returns (int256 answer) { return currentPhase.aggregator.latestAnswer(); } /** * @notice Reads the last updated height from aggregator delegated to. * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestTimestamp() public view virtual override returns (uint256 updatedAt) { return currentPhase.aggregator.latestTimestamp(); } /** * @notice get past rounds answers * @param _roundId the answer number to retrieve the answer for * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getAnswer(uint256 _roundId) public view virtual override returns (int256 answer) { if (_roundId > MAX_ID) return 0; (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(_roundId); AggregatorV2V3Interface aggregator = phaseAggregators[phaseId]; if (address(aggregator) == address(0)) return 0; return aggregator.getAnswer(aggregatorRoundId); } /** * @notice get block timestamp when an answer was last updated * @param _roundId the answer number to retrieve the updated timestamp for * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getTimestamp(uint256 _roundId) public view virtual override returns (uint256 updatedAt) { if (_roundId > MAX_ID) return 0; (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(_roundId); AggregatorV2V3Interface aggregator = phaseAggregators[phaseId]; if (address(aggregator) == address(0)) return 0; return aggregator.getTimestamp(aggregatorRoundId); } /** * @notice get the latest completed round where the answer was updated. This * ID includes the proxy's phase, to make sure round IDs increase even when * switching to a newly deployed aggregator. * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestRound() public view virtual override returns (uint256 roundId) { Phase memory phase = currentPhase; // cache storage reads return addPhase(phase.id, uint64(phase.aggregator.latestRound())); } /** * @notice get data about a round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. * Note that different underlying implementations of AggregatorV3Interface * have slightly different semantics for some of the return values. Consumers * should determine what implementations they expect to receive * data from and validate that they can properly handle return data from all * of them. * @param _roundId the requested round ID as presented through the proxy, this * is made up of the aggregator's round ID with the phase ID encoded in the * two highest order bytes * @return roundId is the round ID from the aggregator for which the data was * retrieved combined with an phase to ensure that round IDs get larger as * time moves forward. * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. * (Only some AggregatorV3Interface implementations return meaningful values) * @dev Note that answer and updatedAt may change between queries. */ function getRoundData(uint80 _roundId) public view virtual override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(_roundId); ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 ansIn ) = phaseAggregators[phaseId].getRoundData(aggregatorRoundId); return addPhaseIds(roundId, answer, startedAt, updatedAt, ansIn, phaseId); } /** * @notice get data about the latest round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. * Note that different underlying implementations of AggregatorV3Interface * have slightly different semantics for some of the return values. Consumers * should determine what implementations they expect to receive * data from and validate that they can properly handle return data from all * of them. * @return roundId is the round ID from the aggregator for which the data was * retrieved combined with an phase to ensure that round IDs get larger as * time moves forward. * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. * (Only some AggregatorV3Interface implementations return meaningful values) * @dev Note that answer and updatedAt may change between queries. */ function latestRoundData() public view virtual override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { Phase memory current = currentPhase; // cache storage reads ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 ansIn ) = current.aggregator.latestRoundData(); return addPhaseIds(roundId, answer, startedAt, updatedAt, ansIn, current.id); } /** * @notice Used if an aggregator contract has been proposed. * @param _roundId the round ID to retrieve the round data for * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. */ function proposedGetRoundData(uint80 _roundId) public view virtual hasProposal() returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return proposedAggregator.getRoundData(_roundId); } /** * @notice Used if an aggregator contract has been proposed. * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. */ function proposedLatestRoundData() public view virtual hasProposal() returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return proposedAggregator.latestRoundData(); } /** * @notice returns the current phase's aggregator address. */ function aggregator() external view returns (address) { return address(currentPhase.aggregator); } /** * @notice returns the current phase's ID. */ function phaseId() external view returns (uint16) { return currentPhase.id; } /** * @notice represents the number of decimals the aggregator responses represent. */ function decimals() external view override returns (uint8) { return currentPhase.aggregator.decimals(); } /** * @notice the version number representing the type of aggregator the proxy * points to. */ function version() external view override returns (uint256) { return currentPhase.aggregator.version(); } /** * @notice returns the description of the aggregator the proxy points to. */ function description() external view override returns (string memory) { return currentPhase.aggregator.description(); } /** * @notice Allows the owner to propose a new address for the aggregator * @param _aggregator The new address for the aggregator contract */ function proposeAggregator(address _aggregator) external onlyOwner() { proposedAggregator = AggregatorV2V3Interface(_aggregator); } /** * @notice Allows the owner to confirm and change the address * to the proposed aggregator * @dev Reverts if the given address doesn't match what was previously * proposed * @param _aggregator The new address for the aggregator contract */ function confirmAggregator(address _aggregator) external onlyOwner() { require(_aggregator == address(proposedAggregator), "Invalid proposed aggregator"); delete proposedAggregator; setAggregator(_aggregator); } /* * Internal */ function setAggregator(address _aggregator) internal { uint16 id = currentPhase.id + 1; currentPhase = Phase(id, AggregatorV2V3Interface(_aggregator)); phaseAggregators[id] = AggregatorV2V3Interface(_aggregator); } function addPhase( uint16 _phase, uint64 _originalId ) internal view returns (uint80) { return uint80(uint256(_phase) << PHASE_OFFSET | _originalId); } function parseIds( uint256 _roundId ) internal view returns (uint16, uint64) { uint16 phaseId = uint16(_roundId >> PHASE_OFFSET); uint64 aggregatorRoundId = uint64(_roundId); return (phaseId, aggregatorRoundId); } function addPhaseIds( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound, uint16 phaseId ) internal view returns (uint80, int256, uint256, uint256, uint80) { return ( addPhase(phaseId, uint64(roundId)), answer, startedAt, updatedAt, addPhase(phaseId, uint64(answeredInRound)) ); } /* * Modifiers */ modifier hasProposal() { require(address(proposedAggregator) != address(0), "No proposed aggregator present"); _; } } interface AccessControllerInterface { function hasAccess(address user, bytes calldata data) external view returns (bool); } /** * @title External Access Controlled Aggregator Proxy * @notice A trusted proxy for updating where current answers are read from * @notice This contract provides a consistent address for the * Aggregator and AggregatorV3Interface but delegates where it reads from to the owner, who is * trusted to update it. * @notice Only access enabled addresses are allowed to access getters for * aggregated answers and round information. */ contract EACAggregatorProxy is AggregatorProxy { AccessControllerInterface public accessController; constructor( address _aggregator, address _accessController ) public AggregatorProxy(_aggregator) { setController(_accessController); } /** * @notice Allows the owner to update the accessController contract address. * @param _accessController The new address for the accessController contract */ function setController(address _accessController) public onlyOwner() { accessController = AccessControllerInterface(_accessController); } /** * @notice Reads the current answer from aggregator delegated to. * @dev overridden function to add the checkAccess() modifier * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestAnswer() public view override checkAccess() returns (int256) { return super.latestAnswer(); } /** * @notice get the latest completed round where the answer was updated. This * ID includes the proxy's phase, to make sure round IDs increase even when * switching to a newly deployed aggregator. * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestTimestamp() public view override checkAccess() returns (uint256) { return super.latestTimestamp(); } /** * @notice get past rounds answers * @param _roundId the answer number to retrieve the answer for * @dev overridden function to add the checkAccess() modifier * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getAnswer(uint256 _roundId) public view override checkAccess() returns (int256) { return super.getAnswer(_roundId); } /** * @notice get block timestamp when an answer was last updated * @param _roundId the answer number to retrieve the updated timestamp for * @dev overridden function to add the checkAccess() modifier * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getTimestamp(uint256 _roundId) public view override checkAccess() returns (uint256) { return super.getTimestamp(_roundId); } /** * @notice get the latest completed round where the answer was updated * @dev overridden function to add the checkAccess() modifier * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestRound() public view override checkAccess() returns (uint256) { return super.latestRound(); } /** * @notice get data about a round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. * Note that different underlying implementations of AggregatorV3Interface * have slightly different semantics for some of the return values. Consumers * should determine what implementations they expect to receive * data from and validate that they can properly handle return data from all * of them. * @param _roundId the round ID to retrieve the round data for * @return roundId is the round ID from the aggregator for which the data was * retrieved combined with a phase to ensure that round IDs get larger as * time moves forward. * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. * (Only some AggregatorV3Interface implementations return meaningful values) * @dev Note that answer and updatedAt may change between queries. */ function getRoundData(uint80 _roundId) public view checkAccess() override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return super.getRoundData(_roundId); } /** * @notice get data about the latest round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. * Note that different underlying implementations of AggregatorV3Interface * have slightly different semantics for some of the return values. Consumers * should determine what implementations they expect to receive * data from and validate that they can properly handle return data from all * of them. * @return roundId is the round ID from the aggregator for which the data was * retrieved combined with a phase to ensure that round IDs get larger as * time moves forward. * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. * (Only some AggregatorV3Interface implementations return meaningful values) * @dev Note that answer and updatedAt may change between queries. */ function latestRoundData() public view checkAccess() override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return super.latestRoundData(); } /** * @notice Used if an aggregator contract has been proposed. * @param _roundId the round ID to retrieve the round data for * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. */ function proposedGetRoundData(uint80 _roundId) public view checkAccess() hasProposal() override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return super.proposedGetRoundData(_roundId); } /** * @notice Used if an aggregator contract has been proposed. * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. * (Only some AggregatorV3Interface implementations return meaningful values) * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. */ function proposedLatestRoundData() public view checkAccess() hasProposal() override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return super.proposedLatestRoundData(); } /** * @dev reverts if the caller does not have access by the accessController * contract or is the contract itself. */ modifier checkAccess() { AccessControllerInterface ac = accessController; require(address(ac) == address(0) || ac.hasAccess(msg.sender, msg.data), "No access"); _; } }
File 4 of 6: AccessControlledAggregator
pragma solidity 0.6.6; /** * @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) { require(b <= a, "SafeMath: subtraction overflow"); 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-solidity/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) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); 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) { require(b != 0, "SafeMath: modulo by zero"); return a % b; } } library SignedSafeMath { int256 constant private _INT256_MIN = -2**255; /** * @dev Multiplies two signed integers, reverts on overflow. */ function mul(int256 a, int256 b) internal pure returns (int256) { // 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; } require(!(a == -1 && b == _INT256_MIN), "SignedSafeMath: multiplication overflow"); int256 c = a * b; require(c / a == b, "SignedSafeMath: multiplication overflow"); return c; } /** * @dev Integer division of two signed integers truncating the quotient, reverts on division by zero. */ function div(int256 a, int256 b) internal pure returns (int256) { require(b != 0, "SignedSafeMath: division by zero"); require(!(b == -1 && a == _INT256_MIN), "SignedSafeMath: division overflow"); int256 c = a / b; return c; } /** * @dev Subtracts two signed integers, reverts on overflow. */ function sub(int256 a, int256 b) internal pure returns (int256) { int256 c = a - b; require((b >= 0 && c <= a) || (b < 0 && c > a), "SignedSafeMath: subtraction overflow"); return c; } /** * @dev Adds two signed integers, reverts on overflow. */ function add(int256 a, int256 b) internal pure returns (int256) { int256 c = a + b; require((b >= 0 && c >= a) || (b < 0 && c < a), "SignedSafeMath: addition overflow"); return c; } /** * @notice Computes average of two signed integers, ensuring that the computation * doesn't overflow. * @dev If the result is not an integer, it is rounded towards zero. For example, * avg(-3, -4) = -3 */ function avg(int256 _a, int256 _b) internal pure returns (int256) { if ((_a < 0 && _b > 0) || (_a > 0 && _b < 0)) { return add(_a, _b) / 2; } int256 remainder = (_a % 2 + _b % 2) / 2; return add(add(_a / 2, _b / 2), remainder); } } library Median { using SignedSafeMath for int256; int256 constant INT_MAX = 2**255-1; /** * @notice Returns the sorted middle, or the average of the two middle indexed items if the * array has an even number of elements. * @dev The list passed as an argument isn't modified. * @dev This algorithm has expected runtime O(n), but for adversarially chosen inputs * the runtime is O(n^2). * @param list The list of elements to compare */ function calculate(int256[] memory list) internal pure returns (int256) { return calculateInplace(copy(list)); } /** * @notice See documentation for function calculate. * @dev The list passed as an argument may be permuted. */ function calculateInplace(int256[] memory list) internal pure returns (int256) { require(0 < list.length, "list must not be empty"); uint256 len = list.length; uint256 middleIndex = len / 2; if (len % 2 == 0) { int256 median1; int256 median2; (median1, median2) = quickselectTwo(list, 0, len - 1, middleIndex - 1, middleIndex); return SignedSafeMath.avg(median1, median2); } else { return quickselect(list, 0, len - 1, middleIndex); } } /** * @notice Maximum length of list that shortSelectTwo can handle */ uint256 constant SHORTSELECTTWO_MAX_LENGTH = 7; /** * @notice Select the k1-th and k2-th element from list of length at most 7 * @dev Uses an optimal sorting network */ function shortSelectTwo( int256[] memory list, uint256 lo, uint256 hi, uint256 k1, uint256 k2 ) private pure returns (int256 k1th, int256 k2th) { // Uses an optimal sorting network (https://en.wikipedia.org/wiki/Sorting_network) // for lists of length 7. Network layout is taken from // http://jgamble.ripco.net/cgi-bin/nw.cgi?inputs=7&algorithm=hibbard&output=svg uint256 len = hi + 1 - lo; int256 x0 = list[lo + 0]; int256 x1 = 1 < len ? list[lo + 1] : INT_MAX; int256 x2 = 2 < len ? list[lo + 2] : INT_MAX; int256 x3 = 3 < len ? list[lo + 3] : INT_MAX; int256 x4 = 4 < len ? list[lo + 4] : INT_MAX; int256 x5 = 5 < len ? list[lo + 5] : INT_MAX; int256 x6 = 6 < len ? list[lo + 6] : INT_MAX; if (x0 > x1) {(x0, x1) = (x1, x0);} if (x2 > x3) {(x2, x3) = (x3, x2);} if (x4 > x5) {(x4, x5) = (x5, x4);} if (x0 > x2) {(x0, x2) = (x2, x0);} if (x1 > x3) {(x1, x3) = (x3, x1);} if (x4 > x6) {(x4, x6) = (x6, x4);} if (x1 > x2) {(x1, x2) = (x2, x1);} if (x5 > x6) {(x5, x6) = (x6, x5);} if (x0 > x4) {(x0, x4) = (x4, x0);} if (x1 > x5) {(x1, x5) = (x5, x1);} if (x2 > x6) {(x2, x6) = (x6, x2);} if (x1 > x4) {(x1, x4) = (x4, x1);} if (x3 > x6) {(x3, x6) = (x6, x3);} if (x2 > x4) {(x2, x4) = (x4, x2);} if (x3 > x5) {(x3, x5) = (x5, x3);} if (x3 > x4) {(x3, x4) = (x4, x3);} uint256 index1 = k1 - lo; if (index1 == 0) {k1th = x0;} else if (index1 == 1) {k1th = x1;} else if (index1 == 2) {k1th = x2;} else if (index1 == 3) {k1th = x3;} else if (index1 == 4) {k1th = x4;} else if (index1 == 5) {k1th = x5;} else if (index1 == 6) {k1th = x6;} else {revert("k1 out of bounds");} uint256 index2 = k2 - lo; if (k1 == k2) {return (k1th, k1th);} else if (index2 == 0) {return (k1th, x0);} else if (index2 == 1) {return (k1th, x1);} else if (index2 == 2) {return (k1th, x2);} else if (index2 == 3) {return (k1th, x3);} else if (index2 == 4) {return (k1th, x4);} else if (index2 == 5) {return (k1th, x5);} else if (index2 == 6) {return (k1th, x6);} else {revert("k2 out of bounds");} } /** * @notice Selects the k-th ranked element from list, looking only at indices between lo and hi * (inclusive). Modifies list in-place. */ function quickselect(int256[] memory list, uint256 lo, uint256 hi, uint256 k) private pure returns (int256 kth) { require(lo <= k); require(k <= hi); while (lo < hi) { if (hi - lo < SHORTSELECTTWO_MAX_LENGTH) { int256 ignore; (kth, ignore) = shortSelectTwo(list, lo, hi, k, k); return kth; } uint256 pivotIndex = partition(list, lo, hi); if (k <= pivotIndex) { // since pivotIndex < (original hi passed to partition), // termination is guaranteed in this case hi = pivotIndex; } else { // since (original lo passed to partition) <= pivotIndex, // termination is guaranteed in this case lo = pivotIndex + 1; } } return list[lo]; } /** * @notice Selects the k1-th and k2-th ranked elements from list, looking only at indices between * lo and hi (inclusive). Modifies list in-place. */ function quickselectTwo( int256[] memory list, uint256 lo, uint256 hi, uint256 k1, uint256 k2 ) internal // for testing pure returns (int256 k1th, int256 k2th) { require(k1 < k2); require(lo <= k1 && k1 <= hi); require(lo <= k2 && k2 <= hi); while (true) { if (hi - lo < SHORTSELECTTWO_MAX_LENGTH) { return shortSelectTwo(list, lo, hi, k1, k2); } uint256 pivotIdx = partition(list, lo, hi); if (k2 <= pivotIdx) { hi = pivotIdx; } else if (pivotIdx < k1) { lo = pivotIdx + 1; } else { assert(k1 <= pivotIdx && pivotIdx < k2); k1th = quickselect(list, lo, pivotIdx, k1); k2th = quickselect(list, pivotIdx + 1, hi, k2); return (k1th, k2th); } } } /** * @notice Partitions list in-place using Hoare's partitioning scheme. * Only elements of list between indices lo and hi (inclusive) will be modified. * Returns an index i, such that: * - lo <= i < hi * - forall j in [lo, i]. list[j] <= list[i] * - forall j in [i, hi]. list[i] <= list[j] */ function partition(int256[] memory list, uint256 lo, uint256 hi) private pure returns (uint256) { // We don't care about overflow of the addition, because it would require a list // larger than any feasible computer's memory. int256 pivot = list[(lo + hi) / 2]; lo -= 1; // this can underflow. that's intentional. hi += 1; while (true) { do { lo += 1; } while (list[lo] < pivot); do { hi -= 1; } while (list[hi] > pivot); if (lo < hi) { (list[lo], list[hi]) = (list[hi], list[lo]); } else { // Let orig_lo and orig_hi be the original values of lo and hi passed to partition. // Then, hi < orig_hi, because hi decreases *strictly* monotonically // in each loop iteration and // - either list[orig_hi] > pivot, in which case the first loop iteration // will achieve hi < orig_hi; // - or list[orig_hi] <= pivot, in which case at least two loop iterations are // needed: // - lo will have to stop at least once in the interval // [orig_lo, (orig_lo + orig_hi)/2] // - (orig_lo + orig_hi)/2 < orig_hi return hi; } } } /** * @notice Makes an in-memory copy of the array passed in * @param list Reference to the array to be copied */ function copy(int256[] memory list) private pure returns(int256[] memory) { int256[] memory list2 = new int256[](list.length); for (uint256 i = 0; i < list.length; i++) { list2[i] = list[i]; } return list2; } } /** * @title The Owned contract * @notice A contract with helpers for basic contract ownership. */ contract Owned { address payable public owner; address private pendingOwner; event OwnershipTransferRequested( address indexed from, address indexed to ); event OwnershipTransferred( address indexed from, address indexed to ); constructor() public { owner = msg.sender; } /** * @dev Allows an owner to begin transferring ownership to a new address, * pending. */ function transferOwnership(address _to) external onlyOwner() { pendingOwner = _to; emit OwnershipTransferRequested(owner, _to); } /** * @dev Allows an ownership transfer to be completed by the recipient. */ function acceptOwnership() external { require(msg.sender == pendingOwner, "Must be proposed owner"); address oldOwner = owner; owner = msg.sender; pendingOwner = address(0); emit OwnershipTransferred(oldOwner, msg.sender); } /** * @dev Reverts if called by anyone other than the contract owner. */ modifier onlyOwner() { require(msg.sender == owner, "Only callable by owner"); _; } } /** * @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. * * This library is a version of Open Zeppelin's SafeMath, modified to support * unsigned 128 bit integers. */ library SafeMath128 { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint128 a, uint128 b) internal pure returns (uint128) { uint128 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(uint128 a, uint128 b) internal pure returns (uint128) { require(b <= a, "SafeMath: subtraction overflow"); uint128 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(uint128 a, uint128 b) internal pure returns (uint128) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint128 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(uint128 a, uint128 b) internal pure returns (uint128) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); uint128 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(uint128 a, uint128 b) internal pure returns (uint128) { require(b != 0, "SafeMath: modulo by zero"); return a % b; } } /** * @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. * * This library is a version of Open Zeppelin's SafeMath, modified to support * unsigned 32 bit integers. */ library SafeMath32 { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint32 a, uint32 b) internal pure returns (uint32) { uint32 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(uint32 a, uint32 b) internal pure returns (uint32) { require(b <= a, "SafeMath: subtraction overflow"); uint32 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(uint32 a, uint32 b) internal pure returns (uint32) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint32 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(uint32 a, uint32 b) internal pure returns (uint32) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); uint32 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(uint32 a, uint32 b) internal pure returns (uint32) { require(b != 0, "SafeMath: modulo by zero"); return a % b; } } /** * @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. * * This library is a version of Open Zeppelin's SafeMath, modified to support * unsigned 64 bit integers. */ library SafeMath64 { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint64 a, uint64 b) internal pure returns (uint64) { uint64 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(uint64 a, uint64 b) internal pure returns (uint64) { require(b <= a, "SafeMath: subtraction overflow"); uint64 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(uint64 a, uint64 b) internal pure returns (uint64) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint64 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(uint64 a, uint64 b) internal pure returns (uint64) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); uint64 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(uint64 a, uint64 b) internal pure returns (uint64) { require(b != 0, "SafeMath: modulo by zero"); return a % b; } } interface AggregatorInterface { function latestAnswer() external view returns (int256); function latestTimestamp() external view returns (uint256); function latestRound() external view returns (uint256); function getAnswer(uint256 roundId) external view returns (int256); function getTimestamp(uint256 roundId) external view returns (uint256); event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); } interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external view returns (uint256); // getRoundData and latestRoundData should both raise "No data present" // if they do not have data to report, instead of returning unset values // which could be misinterpreted as actual reported values. function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); } interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface { } interface AggregatorValidatorInterface { function validate( uint256 previousRoundId, int256 previousAnswer, uint256 currentRoundId, int256 currentAnswer ) external returns (bool); } interface LinkTokenInterface { function allowance(address owner, address spender) external view returns (uint256 remaining); function approve(address spender, uint256 value) external returns (bool success); function balanceOf(address owner) external view returns (uint256 balance); function decimals() external view returns (uint8 decimalPlaces); function decreaseApproval(address spender, uint256 addedValue) external returns (bool success); function increaseApproval(address spender, uint256 subtractedValue) external; function name() external view returns (string memory tokenName); function symbol() external view returns (string memory tokenSymbol); function totalSupply() external view returns (uint256 totalTokensIssued); function transfer(address to, uint256 value) external returns (bool success); function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success); function transferFrom(address from, address to, uint256 value) external returns (bool success); } /** * @title The Prepaid Aggregator contract * @notice Handles aggregating data pushed in from off-chain, and unlocks * payment for oracles as they report. Oracles' submissions are gathered in * rounds, with each round aggregating the submissions for each oracle into a * single answer. The latest aggregated answer is exposed as well as historical * answers and their updated at timestamp. */ contract FluxAggregator is AggregatorV2V3Interface, Owned { using SafeMath for uint256; using SafeMath128 for uint128; using SafeMath64 for uint64; using SafeMath32 for uint32; struct Round { int256 answer; uint64 startedAt; uint64 updatedAt; uint32 answeredInRound; } struct RoundDetails { int256[] submissions; uint32 maxSubmissions; uint32 minSubmissions; uint32 timeout; uint128 paymentAmount; } struct OracleStatus { uint128 withdrawable; uint32 startingRound; uint32 endingRound; uint32 lastReportedRound; uint32 lastStartedRound; int256 latestSubmission; uint16 index; address admin; address pendingAdmin; } struct Requester { bool authorized; uint32 delay; uint32 lastStartedRound; } struct Funds { uint128 available; uint128 allocated; } LinkTokenInterface public linkToken; AggregatorValidatorInterface public validator; // Round related params uint128 public paymentAmount; uint32 public maxSubmissionCount; uint32 public minSubmissionCount; uint32 public restartDelay; uint32 public timeout; uint8 public override decimals; string public override description; int256 immutable public minSubmissionValue; int256 immutable public maxSubmissionValue; uint256 constant public override version = 3; /** * @notice To ensure owner isn't withdrawing required funds as oracles are * submitting updates, we enforce that the contract maintains a minimum * reserve of RESERVE_ROUNDS * oracleCount() LINK earmarked for payment to * oracles. (Of course, this doesn't prevent the contract from running out of * funds without the owner's intervention.) */ uint256 constant private RESERVE_ROUNDS = 2; uint256 constant private MAX_ORACLE_COUNT = 77; uint32 constant private ROUND_MAX = 2**32-1; uint256 private constant VALIDATOR_GAS_LIMIT = 100000; // An error specific to the Aggregator V3 Interface, to prevent possible // confusion around accidentally reading unset values as reported values. string constant private V3_NO_DATA_ERROR = "No data present"; uint32 private reportingRoundId; uint32 internal latestRoundId; mapping(address => OracleStatus) private oracles; mapping(uint32 => Round) internal rounds; mapping(uint32 => RoundDetails) internal details; mapping(address => Requester) internal requesters; address[] private oracleAddresses; Funds private recordedFunds; event AvailableFundsUpdated( uint256 indexed amount ); event RoundDetailsUpdated( uint128 indexed paymentAmount, uint32 indexed minSubmissionCount, uint32 indexed maxSubmissionCount, uint32 restartDelay, uint32 timeout // measured in seconds ); event OraclePermissionsUpdated( address indexed oracle, bool indexed whitelisted ); event OracleAdminUpdated( address indexed oracle, address indexed newAdmin ); event OracleAdminUpdateRequested( address indexed oracle, address admin, address newAdmin ); event SubmissionReceived( int256 indexed submission, uint32 indexed round, address indexed oracle ); event RequesterPermissionsSet( address indexed requester, bool authorized, uint32 delay ); event ValidatorUpdated( address indexed previous, address indexed current ); /** * @notice set up the aggregator with initial configuration * @param _link The address of the LINK token * @param _paymentAmount The amount paid of LINK paid to each oracle per submission, in wei (units of 10⁻¹⁸ LINK) * @param _timeout is the number of seconds after the previous round that are * allowed to lapse before allowing an oracle to skip an unfinished round * @param _validator is an optional contract address for validating * external validation of answers * @param _minSubmissionValue is an immutable check for a lower bound of what * submission values are accepted from an oracle * @param _maxSubmissionValue is an immutable check for an upper bound of what * submission values are accepted from an oracle * @param _decimals represents the number of decimals to offset the answer by * @param _description a short description of what is being reported */ constructor( address _link, uint128 _paymentAmount, uint32 _timeout, address _validator, int256 _minSubmissionValue, int256 _maxSubmissionValue, uint8 _decimals, string memory _description ) public { linkToken = LinkTokenInterface(_link); updateFutureRounds(_paymentAmount, 0, 0, 0, _timeout); setValidator(_validator); minSubmissionValue = _minSubmissionValue; maxSubmissionValue = _maxSubmissionValue; decimals = _decimals; description = _description; rounds[0].updatedAt = uint64(block.timestamp.sub(uint256(_timeout))); } /** * @notice called by oracles when they have witnessed a need to update * @param _roundId is the ID of the round this submission pertains to * @param _submission is the updated data that the oracle is submitting */ function submit(uint256 _roundId, int256 _submission) external { bytes memory error = validateOracleRound(msg.sender, uint32(_roundId)); require(_submission >= minSubmissionValue, "value below minSubmissionValue"); require(_submission <= maxSubmissionValue, "value above maxSubmissionValue"); require(error.length == 0, string(error)); oracleInitializeNewRound(uint32(_roundId)); recordSubmission(_submission, uint32(_roundId)); (bool updated, int256 newAnswer) = updateRoundAnswer(uint32(_roundId)); payOracle(uint32(_roundId)); deleteRoundDetails(uint32(_roundId)); if (updated) { validateAnswer(uint32(_roundId), newAnswer); } } /** * @notice called by the owner to remove and add new oracles as well as * update the round related parameters that pertain to total oracle count * @param _removed is the list of addresses for the new Oracles being removed * @param _added is the list of addresses for the new Oracles being added * @param _addedAdmins is the admin addresses for the new respective _added * list. Only this address is allowed to access the respective oracle's funds * @param _minSubmissions is the new minimum submission count for each round * @param _maxSubmissions is the new maximum submission count for each round * @param _restartDelay is the number of rounds an Oracle has to wait before * they can initiate a round */ function changeOracles( address[] calldata _removed, address[] calldata _added, address[] calldata _addedAdmins, uint32 _minSubmissions, uint32 _maxSubmissions, uint32 _restartDelay ) external onlyOwner() { for (uint256 i = 0; i < _removed.length; i++) { removeOracle(_removed[i]); } require(_added.length == _addedAdmins.length, "need same oracle and admin count"); require(uint256(oracleCount()).add(_added.length) <= MAX_ORACLE_COUNT, "max oracles allowed"); for (uint256 i = 0; i < _added.length; i++) { addOracle(_added[i], _addedAdmins[i]); } updateFutureRounds(paymentAmount, _minSubmissions, _maxSubmissions, _restartDelay, timeout); } /** * @notice update the round and payment related parameters for subsequent * rounds * @param _paymentAmount is the payment amount for subsequent rounds * @param _minSubmissions is the new minimum submission count for each round * @param _maxSubmissions is the new maximum submission count for each round * @param _restartDelay is the number of rounds an Oracle has to wait before * they can initiate a round */ function updateFutureRounds( uint128 _paymentAmount, uint32 _minSubmissions, uint32 _maxSubmissions, uint32 _restartDelay, uint32 _timeout ) public onlyOwner() { uint32 oracleNum = oracleCount(); // Save on storage reads require(_maxSubmissions >= _minSubmissions, "max must equal/exceed min"); require(oracleNum >= _maxSubmissions, "max cannot exceed total"); require(oracleNum == 0 || oracleNum > _restartDelay, "delay cannot exceed total"); require(recordedFunds.available >= requiredReserve(_paymentAmount), "insufficient funds for payment"); if (oracleCount() > 0) { require(_minSubmissions > 0, "min must be greater than 0"); } paymentAmount = _paymentAmount; minSubmissionCount = _minSubmissions; maxSubmissionCount = _maxSubmissions; restartDelay = _restartDelay; timeout = _timeout; emit RoundDetailsUpdated( paymentAmount, _minSubmissions, _maxSubmissions, _restartDelay, _timeout ); } /** * @notice the amount of payment yet to be withdrawn by oracles */ function allocatedFunds() external view returns (uint128) { return recordedFunds.allocated; } /** * @notice the amount of future funding available to oracles */ function availableFunds() external view returns (uint128) { return recordedFunds.available; } /** * @notice recalculate the amount of LINK available for payouts */ function updateAvailableFunds() public { Funds memory funds = recordedFunds; uint256 nowAvailable = linkToken.balanceOf(address(this)).sub(funds.allocated); if (funds.available != nowAvailable) { recordedFunds.available = uint128(nowAvailable); emit AvailableFundsUpdated(nowAvailable); } } /** * @notice returns the number of oracles */ function oracleCount() public view returns (uint8) { return uint8(oracleAddresses.length); } /** * @notice returns an array of addresses containing the oracles on contract */ function getOracles() external view returns (address[] memory) { return oracleAddresses; } /** * @notice get the most recently reported answer * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestAnswer() public view virtual override returns (int256) { return rounds[latestRoundId].answer; } /** * @notice get the most recent updated at timestamp * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestTimestamp() public view virtual override returns (uint256) { return rounds[latestRoundId].updatedAt; } /** * @notice get the ID of the last updated round * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestRound() public view virtual override returns (uint256) { return latestRoundId; } /** * @notice get past rounds answers * @param _roundId the round number to retrieve the answer for * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getAnswer(uint256 _roundId) public view virtual override returns (int256) { if (validRoundId(_roundId)) { return rounds[uint32(_roundId)].answer; } return 0; } /** * @notice get timestamp when an answer was last updated * @param _roundId the round number to retrieve the updated timestamp for * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getTimestamp(uint256 _roundId) public view virtual override returns (uint256) { if (validRoundId(_roundId)) { return rounds[uint32(_roundId)].updatedAt; } return 0; } /** * @notice get data about a round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. * @param _roundId the round ID to retrieve the round data for * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. This is 0 * if the round hasn't been started yet. * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. answeredInRound may be smaller than roundId when the round * timed out. answeredInRound is equal to roundId when the round didn't time out * and was completed regularly. * @dev Note that for in-progress rounds (i.e. rounds that haven't yet received * maxSubmissions) answer and updatedAt may change between queries. */ function getRoundData(uint80 _roundId) public view virtual override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { Round memory r = rounds[uint32(_roundId)]; require(r.answeredInRound > 0 && validRoundId(_roundId), V3_NO_DATA_ERROR); return ( _roundId, r.answer, r.startedAt, r.updatedAt, r.answeredInRound ); } /** * @notice get data about the latest round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. Consumers are encouraged to * use this more fully featured method over the "legacy" latestRound/ * latestAnswer/latestTimestamp functions. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. This is 0 * if the round hasn't been started yet. * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. answeredInRound may be smaller than roundId when the round * timed out. answeredInRound is equal to roundId when the round didn't time * out and was completed regularly. * @dev Note that for in-progress rounds (i.e. rounds that haven't yet * received maxSubmissions) answer and updatedAt may change between queries. */ function latestRoundData() public view virtual override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return getRoundData(latestRoundId); } /** * @notice query the available amount of LINK for an oracle to withdraw */ function withdrawablePayment(address _oracle) external view returns (uint256) { return oracles[_oracle].withdrawable; } /** * @notice transfers the oracle's LINK to another address. Can only be called * by the oracle's admin. * @param _oracle is the oracle whose LINK is transferred * @param _recipient is the address to send the LINK to * @param _amount is the amount of LINK to send */ function withdrawPayment(address _oracle, address _recipient, uint256 _amount) external { require(oracles[_oracle].admin == msg.sender, "only callable by admin"); // Safe to downcast _amount because the total amount of LINK is less than 2^128. uint128 amount = uint128(_amount); uint128 available = oracles[_oracle].withdrawable; require(available >= amount, "insufficient withdrawable funds"); oracles[_oracle].withdrawable = available.sub(amount); recordedFunds.allocated = recordedFunds.allocated.sub(amount); assert(linkToken.transfer(_recipient, uint256(amount))); } /** * @notice transfers the owner's LINK to another address * @param _recipient is the address to send the LINK to * @param _amount is the amount of LINK to send */ function withdrawFunds(address _recipient, uint256 _amount) external onlyOwner() { uint256 available = uint256(recordedFunds.available); require(available.sub(requiredReserve(paymentAmount)) >= _amount, "insufficient reserve funds"); require(linkToken.transfer(_recipient, _amount), "token transfer failed"); updateAvailableFunds(); } /** * @notice get the admin address of an oracle * @param _oracle is the address of the oracle whose admin is being queried */ function getAdmin(address _oracle) external view returns (address) { return oracles[_oracle].admin; } /** * @notice transfer the admin address for an oracle * @param _oracle is the address of the oracle whose admin is being transferred * @param _newAdmin is the new admin address */ function transferAdmin(address _oracle, address _newAdmin) external { require(oracles[_oracle].admin == msg.sender, "only callable by admin"); oracles[_oracle].pendingAdmin = _newAdmin; emit OracleAdminUpdateRequested(_oracle, msg.sender, _newAdmin); } /** * @notice accept the admin address transfer for an oracle * @param _oracle is the address of the oracle whose admin is being transferred */ function acceptAdmin(address _oracle) external { require(oracles[_oracle].pendingAdmin == msg.sender, "only callable by pending admin"); oracles[_oracle].pendingAdmin = address(0); oracles[_oracle].admin = msg.sender; emit OracleAdminUpdated(_oracle, msg.sender); } /** * @notice allows non-oracles to request a new round */ function requestNewRound() external returns (uint80) { require(requesters[msg.sender].authorized, "not authorized requester"); uint32 current = reportingRoundId; require(rounds[current].updatedAt > 0 || timedOut(current), "prev round must be supersedable"); uint32 newRoundId = current.add(1); requesterInitializeNewRound(newRoundId); return newRoundId; } /** * @notice allows the owner to specify new non-oracles to start new rounds * @param _requester is the address to set permissions for * @param _authorized is a boolean specifying whether they can start new rounds or not * @param _delay is the number of rounds the requester must wait before starting another round */ function setRequesterPermissions(address _requester, bool _authorized, uint32 _delay) external onlyOwner() { if (requesters[_requester].authorized == _authorized) return; if (_authorized) { requesters[_requester].authorized = _authorized; requesters[_requester].delay = _delay; } else { delete requesters[_requester]; } emit RequesterPermissionsSet(_requester, _authorized, _delay); } /** * @notice called through LINK's transferAndCall to update available funds * in the same transaction as the funds were transferred to the aggregator * @param _data is mostly ignored. It is checked for length, to be sure * nothing strange is passed in. */ function onTokenTransfer(address, uint256, bytes calldata _data) external { require(_data.length == 0, "transfer doesn't accept calldata"); updateAvailableFunds(); } /** * @notice a method to provide all current info oracles need. Intended only * only to be callable by oracles. Not for use by contracts to read state. * @param _oracle the address to look up information for. */ function oracleRoundState(address _oracle, uint32 _queriedRoundId) external view returns ( bool _eligibleToSubmit, uint32 _roundId, int256 _latestSubmission, uint64 _startedAt, uint64 _timeout, uint128 _availableFunds, uint8 _oracleCount, uint128 _paymentAmount ) { require(msg.sender == tx.origin, "off-chain reading only"); if (_queriedRoundId > 0) { Round storage round = rounds[_queriedRoundId]; RoundDetails storage details = details[_queriedRoundId]; return ( eligibleForSpecificRound(_oracle, _queriedRoundId), _queriedRoundId, oracles[_oracle].latestSubmission, round.startedAt, details.timeout, recordedFunds.available, oracleCount(), (round.startedAt > 0 ? details.paymentAmount : paymentAmount) ); } else { return oracleRoundStateSuggestRound(_oracle); } } /** * @notice method to update the address which does external data validation. * @param _newValidator designates the address of the new validation contract. */ function setValidator(address _newValidator) public onlyOwner() { address previous = address(validator); if (previous != _newValidator) { validator = AggregatorValidatorInterface(_newValidator); emit ValidatorUpdated(previous, _newValidator); } } /** * Private */ function initializeNewRound(uint32 _roundId) private { updateTimedOutRoundInfo(_roundId.sub(1)); reportingRoundId = _roundId; RoundDetails memory nextDetails = RoundDetails( new int256[](0), maxSubmissionCount, minSubmissionCount, timeout, paymentAmount ); details[_roundId] = nextDetails; rounds[_roundId].startedAt = uint64(block.timestamp); emit NewRound(_roundId, msg.sender, rounds[_roundId].startedAt); } function oracleInitializeNewRound(uint32 _roundId) private { if (!newRound(_roundId)) return; uint256 lastStarted = oracles[msg.sender].lastStartedRound; // cache storage reads if (_roundId <= lastStarted + restartDelay && lastStarted != 0) return; initializeNewRound(_roundId); oracles[msg.sender].lastStartedRound = _roundId; } function requesterInitializeNewRound(uint32 _roundId) private { if (!newRound(_roundId)) return; uint256 lastStarted = requesters[msg.sender].lastStartedRound; // cache storage reads require(_roundId > lastStarted + requesters[msg.sender].delay || lastStarted == 0, "must delay requests"); initializeNewRound(_roundId); requesters[msg.sender].lastStartedRound = _roundId; } function updateTimedOutRoundInfo(uint32 _roundId) private { if (!timedOut(_roundId)) return; uint32 prevId = _roundId.sub(1); rounds[_roundId].answer = rounds[prevId].answer; rounds[_roundId].answeredInRound = rounds[prevId].answeredInRound; rounds[_roundId].updatedAt = uint64(block.timestamp); delete details[_roundId]; } function eligibleForSpecificRound(address _oracle, uint32 _queriedRoundId) private view returns (bool _eligible) { if (rounds[_queriedRoundId].startedAt > 0) { return acceptingSubmissions(_queriedRoundId) && validateOracleRound(_oracle, _queriedRoundId).length == 0; } else { return delayed(_oracle, _queriedRoundId) && validateOracleRound(_oracle, _queriedRoundId).length == 0; } } function oracleRoundStateSuggestRound(address _oracle) private view returns ( bool _eligibleToSubmit, uint32 _roundId, int256 _latestSubmission, uint64 _startedAt, uint64 _timeout, uint128 _availableFunds, uint8 _oracleCount, uint128 _paymentAmount ) { Round storage round = rounds[0]; OracleStatus storage oracle = oracles[_oracle]; bool shouldSupersede = oracle.lastReportedRound == reportingRoundId || !acceptingSubmissions(reportingRoundId); // Instead of nudging oracles to submit to the next round, the inclusion of // the shouldSupersede bool in the if condition pushes them towards // submitting in a currently open round. if (supersedable(reportingRoundId) && shouldSupersede) { _roundId = reportingRoundId.add(1); round = rounds[_roundId]; _paymentAmount = paymentAmount; _eligibleToSubmit = delayed(_oracle, _roundId); } else { _roundId = reportingRoundId; round = rounds[_roundId]; _paymentAmount = details[_roundId].paymentAmount; _eligibleToSubmit = acceptingSubmissions(_roundId); } if (validateOracleRound(_oracle, _roundId).length != 0) { _eligibleToSubmit = false; } return ( _eligibleToSubmit, _roundId, oracle.latestSubmission, round.startedAt, details[_roundId].timeout, recordedFunds.available, oracleCount(), _paymentAmount ); } function updateRoundAnswer(uint32 _roundId) internal returns (bool, int256) { if (details[_roundId].submissions.length < details[_roundId].minSubmissions) { return (false, 0); } int256 newAnswer = Median.calculateInplace(details[_roundId].submissions); rounds[_roundId].answer = newAnswer; rounds[_roundId].updatedAt = uint64(block.timestamp); rounds[_roundId].answeredInRound = _roundId; latestRoundId = _roundId; emit AnswerUpdated(newAnswer, _roundId, now); return (true, newAnswer); } function validateAnswer( uint32 _roundId, int256 _newAnswer ) private { AggregatorValidatorInterface av = validator; // cache storage reads if (address(av) == address(0)) return; uint32 prevRound = _roundId.sub(1); uint32 prevAnswerRoundId = rounds[prevRound].answeredInRound; int256 prevRoundAnswer = rounds[prevRound].answer; // We do not want the validator to ever prevent reporting, so we limit its // gas usage and catch any errors that may arise. try av.validate{gas: VALIDATOR_GAS_LIMIT}( prevAnswerRoundId, prevRoundAnswer, _roundId, _newAnswer ) {} catch {} } function payOracle(uint32 _roundId) private { uint128 payment = details[_roundId].paymentAmount; Funds memory funds = recordedFunds; funds.available = funds.available.sub(payment); funds.allocated = funds.allocated.add(payment); recordedFunds = funds; oracles[msg.sender].withdrawable = oracles[msg.sender].withdrawable.add(payment); emit AvailableFundsUpdated(funds.available); } function recordSubmission(int256 _submission, uint32 _roundId) private { require(acceptingSubmissions(_roundId), "round not accepting submissions"); details[_roundId].submissions.push(_submission); oracles[msg.sender].lastReportedRound = _roundId; oracles[msg.sender].latestSubmission = _submission; emit SubmissionReceived(_submission, _roundId, msg.sender); } function deleteRoundDetails(uint32 _roundId) private { if (details[_roundId].submissions.length < details[_roundId].maxSubmissions) return; delete details[_roundId]; } function timedOut(uint32 _roundId) private view returns (bool) { uint64 startedAt = rounds[_roundId].startedAt; uint32 roundTimeout = details[_roundId].timeout; return startedAt > 0 && roundTimeout > 0 && startedAt.add(roundTimeout) < block.timestamp; } function getStartingRound(address _oracle) private view returns (uint32) { uint32 currentRound = reportingRoundId; if (currentRound != 0 && currentRound == oracles[_oracle].endingRound) { return currentRound; } return currentRound.add(1); } function previousAndCurrentUnanswered(uint32 _roundId, uint32 _rrId) private view returns (bool) { return _roundId.add(1) == _rrId && rounds[_rrId].updatedAt == 0; } function requiredReserve(uint256 payment) private view returns (uint256) { return payment.mul(oracleCount()).mul(RESERVE_ROUNDS); } function addOracle( address _oracle, address _admin ) private { require(!oracleEnabled(_oracle), "oracle already enabled"); require(_admin != address(0), "cannot set admin to 0"); require(oracles[_oracle].admin == address(0) || oracles[_oracle].admin == _admin, "owner cannot overwrite admin"); oracles[_oracle].startingRound = getStartingRound(_oracle); oracles[_oracle].endingRound = ROUND_MAX; oracles[_oracle].index = uint16(oracleAddresses.length); oracleAddresses.push(_oracle); oracles[_oracle].admin = _admin; emit OraclePermissionsUpdated(_oracle, true); emit OracleAdminUpdated(_oracle, _admin); } function removeOracle( address _oracle ) private { require(oracleEnabled(_oracle), "oracle not enabled"); oracles[_oracle].endingRound = reportingRoundId.add(1); address tail = oracleAddresses[uint256(oracleCount()).sub(1)]; uint16 index = oracles[_oracle].index; oracles[tail].index = index; delete oracles[_oracle].index; oracleAddresses[index] = tail; oracleAddresses.pop(); emit OraclePermissionsUpdated(_oracle, false); } function validateOracleRound(address _oracle, uint32 _roundId) private view returns (bytes memory) { // cache storage reads uint32 startingRound = oracles[_oracle].startingRound; uint32 rrId = reportingRoundId; if (startingRound == 0) return "not enabled oracle"; if (startingRound > _roundId) return "not yet enabled oracle"; if (oracles[_oracle].endingRound < _roundId) return "no longer allowed oracle"; if (oracles[_oracle].lastReportedRound >= _roundId) return "cannot report on previous rounds"; if (_roundId != rrId && _roundId != rrId.add(1) && !previousAndCurrentUnanswered(_roundId, rrId)) return "invalid round to report"; if (_roundId != 1 && !supersedable(_roundId.sub(1))) return "previous round not supersedable"; } function supersedable(uint32 _roundId) private view returns (bool) { return rounds[_roundId].updatedAt > 0 || timedOut(_roundId); } function oracleEnabled(address _oracle) private view returns (bool) { return oracles[_oracle].endingRound == ROUND_MAX; } function acceptingSubmissions(uint32 _roundId) private view returns (bool) { return details[_roundId].maxSubmissions != 0; } function delayed(address _oracle, uint32 _roundId) private view returns (bool) { uint256 lastStarted = oracles[_oracle].lastStartedRound; return _roundId > lastStarted + restartDelay || lastStarted == 0; } function newRound(uint32 _roundId) private view returns (bool) { return _roundId == reportingRoundId.add(1); } function validRoundId(uint256 _roundId) private view returns (bool) { return _roundId <= ROUND_MAX; } } interface AccessControllerInterface { function hasAccess(address user, bytes calldata data) external view returns (bool); } /** * @title SimpleWriteAccessController * @notice Gives access to accounts explicitly added to an access list by the * controller's owner. * @dev does not make any special permissions for externally, see * SimpleReadAccessController for that. */ contract SimpleWriteAccessController is AccessControllerInterface, Owned { bool public checkEnabled; mapping(address => bool) internal accessList; event AddedAccess(address user); event RemovedAccess(address user); event CheckAccessEnabled(); event CheckAccessDisabled(); constructor() public { checkEnabled = true; } /** * @notice Returns the access of an address * @param _user The address to query */ function hasAccess( address _user, bytes memory ) public view virtual override returns (bool) { return accessList[_user] || !checkEnabled; } /** * @notice Adds an address to the access list * @param _user The address to add */ function addAccess(address _user) external onlyOwner() { if (!accessList[_user]) { accessList[_user] = true; emit AddedAccess(_user); } } /** * @notice Removes an address from the access list * @param _user The address to remove */ function removeAccess(address _user) external onlyOwner() { if (accessList[_user]) { accessList[_user] = false; emit RemovedAccess(_user); } } /** * @notice makes the access check enforced */ function enableAccessCheck() external onlyOwner() { if (!checkEnabled) { checkEnabled = true; emit CheckAccessEnabled(); } } /** * @notice makes the access check unenforced */ function disableAccessCheck() external onlyOwner() { if (checkEnabled) { checkEnabled = false; emit CheckAccessDisabled(); } } /** * @dev reverts if the caller does not have access */ modifier checkAccess() { require(hasAccess(msg.sender, msg.data), "No access"); _; } } /** * @title SimpleReadAccessController * @notice Gives access to: * - any externally owned account (note that offchain actors can always read * any contract storage regardless of onchain access control measures, so this * does not weaken the access control while improving usability) * - accounts explicitly added to an access list * @dev SimpleReadAccessController is not suitable for access controlling writes * since it grants any externally owned account access! See * SimpleWriteAccessController for that. */ contract SimpleReadAccessController is SimpleWriteAccessController { /** * @notice Returns the access of an address * @param _user The address to query */ function hasAccess( address _user, bytes memory _calldata ) public view virtual override returns (bool) { return super.hasAccess(_user, _calldata) || _user == tx.origin; } } /** * @title AccessControlled FluxAggregator contract * @notice This contract requires addresses to be added to a controller * in order to read the answers stored in the FluxAggregator contract */ contract AccessControlledAggregator is FluxAggregator, SimpleReadAccessController { /** * @notice set up the aggregator with initial configuration * @param _link The address of the LINK token * @param _paymentAmount The amount paid of LINK paid to each oracle per submission, in wei (units of 10⁻¹⁸ LINK) * @param _timeout is the number of seconds after the previous round that are * allowed to lapse before allowing an oracle to skip an unfinished round * @param _validator is an optional contract address for validating * external validation of answers * @param _minSubmissionValue is an immutable check for a lower bound of what * submission values are accepted from an oracle * @param _maxSubmissionValue is an immutable check for an upper bound of what * submission values are accepted from an oracle * @param _decimals represents the number of decimals to offset the answer by * @param _description a short description of what is being reported */ constructor( address _link, uint128 _paymentAmount, uint32 _timeout, address _validator, int256 _minSubmissionValue, int256 _maxSubmissionValue, uint8 _decimals, string memory _description ) public FluxAggregator( _link, _paymentAmount, _timeout, _validator, _minSubmissionValue, _maxSubmissionValue, _decimals, _description ){} /** * @notice get data about a round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. * @param _roundId the round ID to retrieve the round data for * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. This is 0 * if the round hasn't been started yet. * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. answeredInRound may be smaller than roundId when the round * timed out. answerInRound is equal to roundId when the round didn't time out * and was completed regularly. * @dev overridden funcion to add the checkAccess() modifier * @dev Note that for in-progress rounds (i.e. rounds that haven't yet * received maxSubmissions) answer and updatedAt may change between queries. */ function getRoundData(uint80 _roundId) public view override checkAccess() returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return super.getRoundData(_roundId); } /** * @notice get data about the latest round. Consumers are encouraged to check * that they're receiving fresh data by inspecting the updatedAt and * answeredInRound return values. Consumers are encouraged to * use this more fully featured method over the "legacy" latestAnswer * functions. Consumers are encouraged to check that they're receiving fresh * data by inspecting the updatedAt and answeredInRound return values. * @return roundId is the round ID for which data was retrieved * @return answer is the answer for the given round * @return startedAt is the timestamp when the round was started. This is 0 * if the round hasn't been started yet. * @return updatedAt is the timestamp when the round last was updated (i.e. * answer was last computed) * @return answeredInRound is the round ID of the round in which the answer * was computed. answeredInRound may be smaller than roundId when the round * timed out. answerInRound is equal to roundId when the round didn't time out * and was completed regularly. * @dev overridden funcion to add the checkAccess() modifier * @dev Note that for in-progress rounds (i.e. rounds that haven't yet * received maxSubmissions) answer and updatedAt may change between queries. */ function latestRoundData() public view override checkAccess() returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return super.latestRoundData(); } /** * @notice get the most recently reported answer * @dev overridden funcion to add the checkAccess() modifier * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestAnswer() public view override checkAccess() returns (int256) { return super.latestAnswer(); } /** * @notice get the most recently reported round ID * @dev overridden funcion to add the checkAccess() modifier * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestRound() public view override checkAccess() returns (uint256) { return super.latestRound(); } /** * @notice get the most recent updated at timestamp * @dev overridden funcion to add the checkAccess() modifier * * @dev #[deprecated] Use latestRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended latestRoundData * instead which includes better verification information. */ function latestTimestamp() public view override checkAccess() returns (uint256) { return super.latestTimestamp(); } /** * @notice get past rounds answers * @dev overridden funcion to add the checkAccess() modifier * @param _roundId the round number to retrieve the answer for * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getAnswer(uint256 _roundId) public view override checkAccess() returns (int256) { return super.getAnswer(_roundId); } /** * @notice get timestamp when an answer was last updated * @dev overridden funcion to add the checkAccess() modifier * @param _roundId the round number to retrieve the updated timestamp for * * @dev #[deprecated] Use getRoundData instead. This does not error if no * answer has been reached, it will simply return 0. Either wait to point to * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ function getTimestamp(uint256 _roundId) public view override checkAccess() returns (uint256) { return super.getTimestamp(_roundId); } }
File 5 of 6: UniswapAnchoredView
/** *Submitted for verification at Etherscan.io on 2020-08-03 */ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.6.10; pragma experimental ABIEncoderV2; /** * @title The Open Oracle Data Base Contract * @author Compound Labs, Inc. */ contract OpenOracleData { /** * @notice The event emitted when a source writes to its storage */ //event Write(address indexed source, <Key> indexed key, string kind, uint64 timestamp, <Value> value); /** * @notice Write a bunch of signed datum to the authenticated storage mapping * @param message The payload containing the timestamp, and (key, value) pairs * @param signature The cryptographic signature of the message payload, authorizing the source to write * @return The keys that were written */ //function put(bytes calldata message, bytes calldata signature) external returns (<Key> memory); /** * @notice Read a single key with a pre-defined type signature from an authenticated source * @param source The verifiable author of the data * @param key The selector for the value to return * @return The claimed Unix timestamp for the data and the encoded value (defaults to (0, 0x)) */ //function get(address source, <Key> key) external view returns (uint, <Value>); /** * @notice Recovers the source address which signed a message * @dev Comparing to a claimed address would add nothing, * as the caller could simply perform the recover and claim that address. * @param message The data that was presumably signed * @param signature The fingerprint of the data + private key * @return The source address which signed the message, presumably */ function source(bytes memory message, bytes memory signature) public pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = abi.decode(signature, (bytes32, bytes32, uint8)); bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(message))); return ecrecover(hash, v, r, s); } } /** * @title The Open Oracle Price Data Contract * @notice Values stored in this contract should represent a USD price with 6 decimals precision * @author Compound Labs, Inc. */ contract OpenOraclePriceData is OpenOracleData { ///@notice The event emitted when a source writes to its storage event Write(address indexed source, string key, uint64 timestamp, uint64 value); ///@notice The event emitted when the timestamp on a price is invalid and it is not written to storage event NotWritten(uint64 priorTimestamp, uint256 messageTimestamp, uint256 blockTimestamp); ///@notice The fundamental unit of storage for a reporter source struct Datum { uint64 timestamp; uint64 value; } /** * @dev The most recent authenticated data from all sources. * This is private because dynamic mapping keys preclude auto-generated getters. */ mapping(address => mapping(string => Datum)) private data; /** * @notice Write a bunch of signed datum to the authenticated storage mapping * @param message The payload containing the timestamp, and (key, value) pairs * @param signature The cryptographic signature of the message payload, authorizing the source to write * @return The keys that were written */ function put(bytes calldata message, bytes calldata signature) external returns (string memory) { (address source, uint64 timestamp, string memory key, uint64 value) = decodeMessage(message, signature); return putInternal(source, timestamp, key, value); } function putInternal(address source, uint64 timestamp, string memory key, uint64 value) internal returns (string memory) { // Only update if newer than stored, according to source Datum storage prior = data[source][key]; if (timestamp > prior.timestamp && timestamp < block.timestamp + 60 minutes && source != address(0)) { data[source][key] = Datum(timestamp, value); emit Write(source, key, timestamp, value); } else { emit NotWritten(prior.timestamp, timestamp, block.timestamp); } return key; } function decodeMessage(bytes calldata message, bytes calldata signature) internal pure returns (address, uint64, string memory, uint64) { // Recover the source address address source = source(message, signature); // Decode the message and check the kind (string memory kind, uint64 timestamp, string memory key, uint64 value) = abi.decode(message, (string, uint64, string, uint64)); require(keccak256(abi.encodePacked(kind)) == keccak256(abi.encodePacked("prices")), "Kind of data must be 'prices'"); return (source, timestamp, key, value); } /** * @notice Read a single key from an authenticated source * @param source The verifiable author of the data * @param key The selector for the value to return * @return The claimed Unix timestamp for the data and the price value (defaults to (0, 0)) */ function get(address source, string calldata key) external view returns (uint64, uint64) { Datum storage datum = data[source][key]; return (datum.timestamp, datum.value); } /** * @notice Read only the value for a single key from an authenticated source * @param source The verifiable author of the data * @param key The selector for the value to return * @return The price value (defaults to 0) */ function getPrice(address source, string calldata key) external view returns (uint64) { return data[source][key].value; } } interface CErc20 { function underlying() external view returns (address); } contract UniswapConfig { /// @dev Describe how to interpret the fixedPrice in the TokenConfig. enum PriceSource { FIXED_ETH, /// implies the fixedPrice is a constant multiple of the ETH price (which varies) FIXED_USD, /// implies the fixedPrice is a constant multiple of the USD price (which is 1) REPORTER /// implies the price is set by the reporter } /// @dev Describe how the USD price should be determined for an asset. /// There should be 1 TokenConfig object for each supported asset, passed in the constructor. struct TokenConfig { address cToken; address underlying; bytes32 symbolHash; uint256 baseUnit; PriceSource priceSource; uint256 fixedPrice; address uniswapMarket; bool isUniswapReversed; } /// @notice The max number of tokens this contract is hardcoded to support /// @dev Do not change this variable without updating all the fields throughout the contract. uint public constant maxTokens = 30; /// @notice The number of tokens this contract actually supports uint public immutable numTokens; address internal immutable cToken00; address internal immutable cToken01; address internal immutable cToken02; address internal immutable cToken03; address internal immutable cToken04; address internal immutable cToken05; address internal immutable cToken06; address internal immutable cToken07; address internal immutable cToken08; address internal immutable cToken09; address internal immutable cToken10; address internal immutable cToken11; address internal immutable cToken12; address internal immutable cToken13; address internal immutable cToken14; address internal immutable cToken15; address internal immutable cToken16; address internal immutable cToken17; address internal immutable cToken18; address internal immutable cToken19; address internal immutable cToken20; address internal immutable cToken21; address internal immutable cToken22; address internal immutable cToken23; address internal immutable cToken24; address internal immutable cToken25; address internal immutable cToken26; address internal immutable cToken27; address internal immutable cToken28; address internal immutable cToken29; address internal immutable underlying00; address internal immutable underlying01; address internal immutable underlying02; address internal immutable underlying03; address internal immutable underlying04; address internal immutable underlying05; address internal immutable underlying06; address internal immutable underlying07; address internal immutable underlying08; address internal immutable underlying09; address internal immutable underlying10; address internal immutable underlying11; address internal immutable underlying12; address internal immutable underlying13; address internal immutable underlying14; address internal immutable underlying15; address internal immutable underlying16; address internal immutable underlying17; address internal immutable underlying18; address internal immutable underlying19; address internal immutable underlying20; address internal immutable underlying21; address internal immutable underlying22; address internal immutable underlying23; address internal immutable underlying24; address internal immutable underlying25; address internal immutable underlying26; address internal immutable underlying27; address internal immutable underlying28; address internal immutable underlying29; bytes32 internal immutable symbolHash00; bytes32 internal immutable symbolHash01; bytes32 internal immutable symbolHash02; bytes32 internal immutable symbolHash03; bytes32 internal immutable symbolHash04; bytes32 internal immutable symbolHash05; bytes32 internal immutable symbolHash06; bytes32 internal immutable symbolHash07; bytes32 internal immutable symbolHash08; bytes32 internal immutable symbolHash09; bytes32 internal immutable symbolHash10; bytes32 internal immutable symbolHash11; bytes32 internal immutable symbolHash12; bytes32 internal immutable symbolHash13; bytes32 internal immutable symbolHash14; bytes32 internal immutable symbolHash15; bytes32 internal immutable symbolHash16; bytes32 internal immutable symbolHash17; bytes32 internal immutable symbolHash18; bytes32 internal immutable symbolHash19; bytes32 internal immutable symbolHash20; bytes32 internal immutable symbolHash21; bytes32 internal immutable symbolHash22; bytes32 internal immutable symbolHash23; bytes32 internal immutable symbolHash24; bytes32 internal immutable symbolHash25; bytes32 internal immutable symbolHash26; bytes32 internal immutable symbolHash27; bytes32 internal immutable symbolHash28; bytes32 internal immutable symbolHash29; uint256 internal immutable baseUnit00; uint256 internal immutable baseUnit01; uint256 internal immutable baseUnit02; uint256 internal immutable baseUnit03; uint256 internal immutable baseUnit04; uint256 internal immutable baseUnit05; uint256 internal immutable baseUnit06; uint256 internal immutable baseUnit07; uint256 internal immutable baseUnit08; uint256 internal immutable baseUnit09; uint256 internal immutable baseUnit10; uint256 internal immutable baseUnit11; uint256 internal immutable baseUnit12; uint256 internal immutable baseUnit13; uint256 internal immutable baseUnit14; uint256 internal immutable baseUnit15; uint256 internal immutable baseUnit16; uint256 internal immutable baseUnit17; uint256 internal immutable baseUnit18; uint256 internal immutable baseUnit19; uint256 internal immutable baseUnit20; uint256 internal immutable baseUnit21; uint256 internal immutable baseUnit22; uint256 internal immutable baseUnit23; uint256 internal immutable baseUnit24; uint256 internal immutable baseUnit25; uint256 internal immutable baseUnit26; uint256 internal immutable baseUnit27; uint256 internal immutable baseUnit28; uint256 internal immutable baseUnit29; PriceSource internal immutable priceSource00; PriceSource internal immutable priceSource01; PriceSource internal immutable priceSource02; PriceSource internal immutable priceSource03; PriceSource internal immutable priceSource04; PriceSource internal immutable priceSource05; PriceSource internal immutable priceSource06; PriceSource internal immutable priceSource07; PriceSource internal immutable priceSource08; PriceSource internal immutable priceSource09; PriceSource internal immutable priceSource10; PriceSource internal immutable priceSource11; PriceSource internal immutable priceSource12; PriceSource internal immutable priceSource13; PriceSource internal immutable priceSource14; PriceSource internal immutable priceSource15; PriceSource internal immutable priceSource16; PriceSource internal immutable priceSource17; PriceSource internal immutable priceSource18; PriceSource internal immutable priceSource19; PriceSource internal immutable priceSource20; PriceSource internal immutable priceSource21; PriceSource internal immutable priceSource22; PriceSource internal immutable priceSource23; PriceSource internal immutable priceSource24; PriceSource internal immutable priceSource25; PriceSource internal immutable priceSource26; PriceSource internal immutable priceSource27; PriceSource internal immutable priceSource28; PriceSource internal immutable priceSource29; uint256 internal immutable fixedPrice00; uint256 internal immutable fixedPrice01; uint256 internal immutable fixedPrice02; uint256 internal immutable fixedPrice03; uint256 internal immutable fixedPrice04; uint256 internal immutable fixedPrice05; uint256 internal immutable fixedPrice06; uint256 internal immutable fixedPrice07; uint256 internal immutable fixedPrice08; uint256 internal immutable fixedPrice09; uint256 internal immutable fixedPrice10; uint256 internal immutable fixedPrice11; uint256 internal immutable fixedPrice12; uint256 internal immutable fixedPrice13; uint256 internal immutable fixedPrice14; uint256 internal immutable fixedPrice15; uint256 internal immutable fixedPrice16; uint256 internal immutable fixedPrice17; uint256 internal immutable fixedPrice18; uint256 internal immutable fixedPrice19; uint256 internal immutable fixedPrice20; uint256 internal immutable fixedPrice21; uint256 internal immutable fixedPrice22; uint256 internal immutable fixedPrice23; uint256 internal immutable fixedPrice24; uint256 internal immutable fixedPrice25; uint256 internal immutable fixedPrice26; uint256 internal immutable fixedPrice27; uint256 internal immutable fixedPrice28; uint256 internal immutable fixedPrice29; address internal immutable uniswapMarket00; address internal immutable uniswapMarket01; address internal immutable uniswapMarket02; address internal immutable uniswapMarket03; address internal immutable uniswapMarket04; address internal immutable uniswapMarket05; address internal immutable uniswapMarket06; address internal immutable uniswapMarket07; address internal immutable uniswapMarket08; address internal immutable uniswapMarket09; address internal immutable uniswapMarket10; address internal immutable uniswapMarket11; address internal immutable uniswapMarket12; address internal immutable uniswapMarket13; address internal immutable uniswapMarket14; address internal immutable uniswapMarket15; address internal immutable uniswapMarket16; address internal immutable uniswapMarket17; address internal immutable uniswapMarket18; address internal immutable uniswapMarket19; address internal immutable uniswapMarket20; address internal immutable uniswapMarket21; address internal immutable uniswapMarket22; address internal immutable uniswapMarket23; address internal immutable uniswapMarket24; address internal immutable uniswapMarket25; address internal immutable uniswapMarket26; address internal immutable uniswapMarket27; address internal immutable uniswapMarket28; address internal immutable uniswapMarket29; bool internal immutable isUniswapReversed00; bool internal immutable isUniswapReversed01; bool internal immutable isUniswapReversed02; bool internal immutable isUniswapReversed03; bool internal immutable isUniswapReversed04; bool internal immutable isUniswapReversed05; bool internal immutable isUniswapReversed06; bool internal immutable isUniswapReversed07; bool internal immutable isUniswapReversed08; bool internal immutable isUniswapReversed09; bool internal immutable isUniswapReversed10; bool internal immutable isUniswapReversed11; bool internal immutable isUniswapReversed12; bool internal immutable isUniswapReversed13; bool internal immutable isUniswapReversed14; bool internal immutable isUniswapReversed15; bool internal immutable isUniswapReversed16; bool internal immutable isUniswapReversed17; bool internal immutable isUniswapReversed18; bool internal immutable isUniswapReversed19; bool internal immutable isUniswapReversed20; bool internal immutable isUniswapReversed21; bool internal immutable isUniswapReversed22; bool internal immutable isUniswapReversed23; bool internal immutable isUniswapReversed24; bool internal immutable isUniswapReversed25; bool internal immutable isUniswapReversed26; bool internal immutable isUniswapReversed27; bool internal immutable isUniswapReversed28; bool internal immutable isUniswapReversed29; /** * @notice Construct an immutable store of configs into the contract data * @param configs The configs for the supported assets */ constructor(TokenConfig[] memory configs) public { require(configs.length <= maxTokens, "too many configs"); numTokens = configs.length; cToken00 = get(configs, 0).cToken; cToken01 = get(configs, 1).cToken; cToken02 = get(configs, 2).cToken; cToken03 = get(configs, 3).cToken; cToken04 = get(configs, 4).cToken; cToken05 = get(configs, 5).cToken; cToken06 = get(configs, 6).cToken; cToken07 = get(configs, 7).cToken; cToken08 = get(configs, 8).cToken; cToken09 = get(configs, 9).cToken; cToken10 = get(configs, 10).cToken; cToken11 = get(configs, 11).cToken; cToken12 = get(configs, 12).cToken; cToken13 = get(configs, 13).cToken; cToken14 = get(configs, 14).cToken; cToken15 = get(configs, 15).cToken; cToken16 = get(configs, 16).cToken; cToken17 = get(configs, 17).cToken; cToken18 = get(configs, 18).cToken; cToken19 = get(configs, 19).cToken; cToken20 = get(configs, 20).cToken; cToken21 = get(configs, 21).cToken; cToken22 = get(configs, 22).cToken; cToken23 = get(configs, 23).cToken; cToken24 = get(configs, 24).cToken; cToken25 = get(configs, 25).cToken; cToken26 = get(configs, 26).cToken; cToken27 = get(configs, 27).cToken; cToken28 = get(configs, 28).cToken; cToken29 = get(configs, 29).cToken; underlying00 = get(configs, 0).underlying; underlying01 = get(configs, 1).underlying; underlying02 = get(configs, 2).underlying; underlying03 = get(configs, 3).underlying; underlying04 = get(configs, 4).underlying; underlying05 = get(configs, 5).underlying; underlying06 = get(configs, 6).underlying; underlying07 = get(configs, 7).underlying; underlying08 = get(configs, 8).underlying; underlying09 = get(configs, 9).underlying; underlying10 = get(configs, 10).underlying; underlying11 = get(configs, 11).underlying; underlying12 = get(configs, 12).underlying; underlying13 = get(configs, 13).underlying; underlying14 = get(configs, 14).underlying; underlying15 = get(configs, 15).underlying; underlying16 = get(configs, 16).underlying; underlying17 = get(configs, 17).underlying; underlying18 = get(configs, 18).underlying; underlying19 = get(configs, 19).underlying; underlying20 = get(configs, 20).underlying; underlying21 = get(configs, 21).underlying; underlying22 = get(configs, 22).underlying; underlying23 = get(configs, 23).underlying; underlying24 = get(configs, 24).underlying; underlying25 = get(configs, 25).underlying; underlying26 = get(configs, 26).underlying; underlying27 = get(configs, 27).underlying; underlying28 = get(configs, 28).underlying; underlying29 = get(configs, 29).underlying; symbolHash00 = get(configs, 0).symbolHash; symbolHash01 = get(configs, 1).symbolHash; symbolHash02 = get(configs, 2).symbolHash; symbolHash03 = get(configs, 3).symbolHash; symbolHash04 = get(configs, 4).symbolHash; symbolHash05 = get(configs, 5).symbolHash; symbolHash06 = get(configs, 6).symbolHash; symbolHash07 = get(configs, 7).symbolHash; symbolHash08 = get(configs, 8).symbolHash; symbolHash09 = get(configs, 9).symbolHash; symbolHash10 = get(configs, 10).symbolHash; symbolHash11 = get(configs, 11).symbolHash; symbolHash12 = get(configs, 12).symbolHash; symbolHash13 = get(configs, 13).symbolHash; symbolHash14 = get(configs, 14).symbolHash; symbolHash15 = get(configs, 15).symbolHash; symbolHash16 = get(configs, 16).symbolHash; symbolHash17 = get(configs, 17).symbolHash; symbolHash18 = get(configs, 18).symbolHash; symbolHash19 = get(configs, 19).symbolHash; symbolHash20 = get(configs, 20).symbolHash; symbolHash21 = get(configs, 21).symbolHash; symbolHash22 = get(configs, 22).symbolHash; symbolHash23 = get(configs, 23).symbolHash; symbolHash24 = get(configs, 24).symbolHash; symbolHash25 = get(configs, 25).symbolHash; symbolHash26 = get(configs, 26).symbolHash; symbolHash27 = get(configs, 27).symbolHash; symbolHash28 = get(configs, 28).symbolHash; symbolHash29 = get(configs, 29).symbolHash; baseUnit00 = get(configs, 0).baseUnit; baseUnit01 = get(configs, 1).baseUnit; baseUnit02 = get(configs, 2).baseUnit; baseUnit03 = get(configs, 3).baseUnit; baseUnit04 = get(configs, 4).baseUnit; baseUnit05 = get(configs, 5).baseUnit; baseUnit06 = get(configs, 6).baseUnit; baseUnit07 = get(configs, 7).baseUnit; baseUnit08 = get(configs, 8).baseUnit; baseUnit09 = get(configs, 9).baseUnit; baseUnit10 = get(configs, 10).baseUnit; baseUnit11 = get(configs, 11).baseUnit; baseUnit12 = get(configs, 12).baseUnit; baseUnit13 = get(configs, 13).baseUnit; baseUnit14 = get(configs, 14).baseUnit; baseUnit15 = get(configs, 15).baseUnit; baseUnit16 = get(configs, 16).baseUnit; baseUnit17 = get(configs, 17).baseUnit; baseUnit18 = get(configs, 18).baseUnit; baseUnit19 = get(configs, 19).baseUnit; baseUnit20 = get(configs, 20).baseUnit; baseUnit21 = get(configs, 21).baseUnit; baseUnit22 = get(configs, 22).baseUnit; baseUnit23 = get(configs, 23).baseUnit; baseUnit24 = get(configs, 24).baseUnit; baseUnit25 = get(configs, 25).baseUnit; baseUnit26 = get(configs, 26).baseUnit; baseUnit27 = get(configs, 27).baseUnit; baseUnit28 = get(configs, 28).baseUnit; baseUnit29 = get(configs, 29).baseUnit; priceSource00 = get(configs, 0).priceSource; priceSource01 = get(configs, 1).priceSource; priceSource02 = get(configs, 2).priceSource; priceSource03 = get(configs, 3).priceSource; priceSource04 = get(configs, 4).priceSource; priceSource05 = get(configs, 5).priceSource; priceSource06 = get(configs, 6).priceSource; priceSource07 = get(configs, 7).priceSource; priceSource08 = get(configs, 8).priceSource; priceSource09 = get(configs, 9).priceSource; priceSource10 = get(configs, 10).priceSource; priceSource11 = get(configs, 11).priceSource; priceSource12 = get(configs, 12).priceSource; priceSource13 = get(configs, 13).priceSource; priceSource14 = get(configs, 14).priceSource; priceSource15 = get(configs, 15).priceSource; priceSource16 = get(configs, 16).priceSource; priceSource17 = get(configs, 17).priceSource; priceSource18 = get(configs, 18).priceSource; priceSource19 = get(configs, 19).priceSource; priceSource20 = get(configs, 20).priceSource; priceSource21 = get(configs, 21).priceSource; priceSource22 = get(configs, 22).priceSource; priceSource23 = get(configs, 23).priceSource; priceSource24 = get(configs, 24).priceSource; priceSource25 = get(configs, 25).priceSource; priceSource26 = get(configs, 26).priceSource; priceSource27 = get(configs, 27).priceSource; priceSource28 = get(configs, 28).priceSource; priceSource29 = get(configs, 29).priceSource; fixedPrice00 = get(configs, 0).fixedPrice; fixedPrice01 = get(configs, 1).fixedPrice; fixedPrice02 = get(configs, 2).fixedPrice; fixedPrice03 = get(configs, 3).fixedPrice; fixedPrice04 = get(configs, 4).fixedPrice; fixedPrice05 = get(configs, 5).fixedPrice; fixedPrice06 = get(configs, 6).fixedPrice; fixedPrice07 = get(configs, 7).fixedPrice; fixedPrice08 = get(configs, 8).fixedPrice; fixedPrice09 = get(configs, 9).fixedPrice; fixedPrice10 = get(configs, 10).fixedPrice; fixedPrice11 = get(configs, 11).fixedPrice; fixedPrice12 = get(configs, 12).fixedPrice; fixedPrice13 = get(configs, 13).fixedPrice; fixedPrice14 = get(configs, 14).fixedPrice; fixedPrice15 = get(configs, 15).fixedPrice; fixedPrice16 = get(configs, 16).fixedPrice; fixedPrice17 = get(configs, 17).fixedPrice; fixedPrice18 = get(configs, 18).fixedPrice; fixedPrice19 = get(configs, 19).fixedPrice; fixedPrice20 = get(configs, 20).fixedPrice; fixedPrice21 = get(configs, 21).fixedPrice; fixedPrice22 = get(configs, 22).fixedPrice; fixedPrice23 = get(configs, 23).fixedPrice; fixedPrice24 = get(configs, 24).fixedPrice; fixedPrice25 = get(configs, 25).fixedPrice; fixedPrice26 = get(configs, 26).fixedPrice; fixedPrice27 = get(configs, 27).fixedPrice; fixedPrice28 = get(configs, 28).fixedPrice; fixedPrice29 = get(configs, 29).fixedPrice; uniswapMarket00 = get(configs, 0).uniswapMarket; uniswapMarket01 = get(configs, 1).uniswapMarket; uniswapMarket02 = get(configs, 2).uniswapMarket; uniswapMarket03 = get(configs, 3).uniswapMarket; uniswapMarket04 = get(configs, 4).uniswapMarket; uniswapMarket05 = get(configs, 5).uniswapMarket; uniswapMarket06 = get(configs, 6).uniswapMarket; uniswapMarket07 = get(configs, 7).uniswapMarket; uniswapMarket08 = get(configs, 8).uniswapMarket; uniswapMarket09 = get(configs, 9).uniswapMarket; uniswapMarket10 = get(configs, 10).uniswapMarket; uniswapMarket11 = get(configs, 11).uniswapMarket; uniswapMarket12 = get(configs, 12).uniswapMarket; uniswapMarket13 = get(configs, 13).uniswapMarket; uniswapMarket14 = get(configs, 14).uniswapMarket; uniswapMarket15 = get(configs, 15).uniswapMarket; uniswapMarket16 = get(configs, 16).uniswapMarket; uniswapMarket17 = get(configs, 17).uniswapMarket; uniswapMarket18 = get(configs, 18).uniswapMarket; uniswapMarket19 = get(configs, 19).uniswapMarket; uniswapMarket20 = get(configs, 20).uniswapMarket; uniswapMarket21 = get(configs, 21).uniswapMarket; uniswapMarket22 = get(configs, 22).uniswapMarket; uniswapMarket23 = get(configs, 23).uniswapMarket; uniswapMarket24 = get(configs, 24).uniswapMarket; uniswapMarket25 = get(configs, 25).uniswapMarket; uniswapMarket26 = get(configs, 26).uniswapMarket; uniswapMarket27 = get(configs, 27).uniswapMarket; uniswapMarket28 = get(configs, 28).uniswapMarket; uniswapMarket29 = get(configs, 29).uniswapMarket; isUniswapReversed00 = get(configs, 0).isUniswapReversed; isUniswapReversed01 = get(configs, 1).isUniswapReversed; isUniswapReversed02 = get(configs, 2).isUniswapReversed; isUniswapReversed03 = get(configs, 3).isUniswapReversed; isUniswapReversed04 = get(configs, 4).isUniswapReversed; isUniswapReversed05 = get(configs, 5).isUniswapReversed; isUniswapReversed06 = get(configs, 6).isUniswapReversed; isUniswapReversed07 = get(configs, 7).isUniswapReversed; isUniswapReversed08 = get(configs, 8).isUniswapReversed; isUniswapReversed09 = get(configs, 9).isUniswapReversed; isUniswapReversed10 = get(configs, 10).isUniswapReversed; isUniswapReversed11 = get(configs, 11).isUniswapReversed; isUniswapReversed12 = get(configs, 12).isUniswapReversed; isUniswapReversed13 = get(configs, 13).isUniswapReversed; isUniswapReversed14 = get(configs, 14).isUniswapReversed; isUniswapReversed15 = get(configs, 15).isUniswapReversed; isUniswapReversed16 = get(configs, 16).isUniswapReversed; isUniswapReversed17 = get(configs, 17).isUniswapReversed; isUniswapReversed18 = get(configs, 18).isUniswapReversed; isUniswapReversed19 = get(configs, 19).isUniswapReversed; isUniswapReversed20 = get(configs, 20).isUniswapReversed; isUniswapReversed21 = get(configs, 21).isUniswapReversed; isUniswapReversed22 = get(configs, 22).isUniswapReversed; isUniswapReversed23 = get(configs, 23).isUniswapReversed; isUniswapReversed24 = get(configs, 24).isUniswapReversed; isUniswapReversed25 = get(configs, 25).isUniswapReversed; isUniswapReversed26 = get(configs, 26).isUniswapReversed; isUniswapReversed27 = get(configs, 27).isUniswapReversed; isUniswapReversed28 = get(configs, 28).isUniswapReversed; isUniswapReversed29 = get(configs, 29).isUniswapReversed; } function get(TokenConfig[] memory configs, uint i) internal pure returns (TokenConfig memory) { if (i < configs.length) return configs[i]; return TokenConfig({ cToken: address(0), underlying: address(0), symbolHash: bytes32(0), baseUnit: uint256(0), priceSource: PriceSource(0), fixedPrice: uint256(0), uniswapMarket: address(0), isUniswapReversed: false }); } function getCTokenIndex(address cToken) internal view returns (uint) { if (cToken == cToken00) return 0; if (cToken == cToken01) return 1; if (cToken == cToken02) return 2; if (cToken == cToken03) return 3; if (cToken == cToken04) return 4; if (cToken == cToken05) return 5; if (cToken == cToken06) return 6; if (cToken == cToken07) return 7; if (cToken == cToken08) return 8; if (cToken == cToken09) return 9; if (cToken == cToken10) return 10; if (cToken == cToken11) return 11; if (cToken == cToken12) return 12; if (cToken == cToken13) return 13; if (cToken == cToken14) return 14; if (cToken == cToken15) return 15; if (cToken == cToken16) return 16; if (cToken == cToken17) return 17; if (cToken == cToken18) return 18; if (cToken == cToken19) return 19; if (cToken == cToken20) return 20; if (cToken == cToken21) return 21; if (cToken == cToken22) return 22; if (cToken == cToken23) return 23; if (cToken == cToken24) return 24; if (cToken == cToken25) return 25; if (cToken == cToken26) return 26; if (cToken == cToken27) return 27; if (cToken == cToken28) return 28; if (cToken == cToken29) return 29; return uint(-1); } function getUnderlyingIndex(address underlying) internal view returns (uint) { if (underlying == underlying00) return 0; if (underlying == underlying01) return 1; if (underlying == underlying02) return 2; if (underlying == underlying03) return 3; if (underlying == underlying04) return 4; if (underlying == underlying05) return 5; if (underlying == underlying06) return 6; if (underlying == underlying07) return 7; if (underlying == underlying08) return 8; if (underlying == underlying09) return 9; if (underlying == underlying10) return 10; if (underlying == underlying11) return 11; if (underlying == underlying12) return 12; if (underlying == underlying13) return 13; if (underlying == underlying14) return 14; if (underlying == underlying15) return 15; if (underlying == underlying16) return 16; if (underlying == underlying17) return 17; if (underlying == underlying18) return 18; if (underlying == underlying19) return 19; if (underlying == underlying20) return 20; if (underlying == underlying21) return 21; if (underlying == underlying22) return 22; if (underlying == underlying23) return 23; if (underlying == underlying24) return 24; if (underlying == underlying25) return 25; if (underlying == underlying26) return 26; if (underlying == underlying27) return 27; if (underlying == underlying28) return 28; if (underlying == underlying29) return 29; return uint(-1); } function getSymbolHashIndex(bytes32 symbolHash) internal view returns (uint) { if (symbolHash == symbolHash00) return 0; if (symbolHash == symbolHash01) return 1; if (symbolHash == symbolHash02) return 2; if (symbolHash == symbolHash03) return 3; if (symbolHash == symbolHash04) return 4; if (symbolHash == symbolHash05) return 5; if (symbolHash == symbolHash06) return 6; if (symbolHash == symbolHash07) return 7; if (symbolHash == symbolHash08) return 8; if (symbolHash == symbolHash09) return 9; if (symbolHash == symbolHash10) return 10; if (symbolHash == symbolHash11) return 11; if (symbolHash == symbolHash12) return 12; if (symbolHash == symbolHash13) return 13; if (symbolHash == symbolHash14) return 14; if (symbolHash == symbolHash15) return 15; if (symbolHash == symbolHash16) return 16; if (symbolHash == symbolHash17) return 17; if (symbolHash == symbolHash18) return 18; if (symbolHash == symbolHash19) return 19; if (symbolHash == symbolHash20) return 20; if (symbolHash == symbolHash21) return 21; if (symbolHash == symbolHash22) return 22; if (symbolHash == symbolHash23) return 23; if (symbolHash == symbolHash24) return 24; if (symbolHash == symbolHash25) return 25; if (symbolHash == symbolHash26) return 26; if (symbolHash == symbolHash27) return 27; if (symbolHash == symbolHash28) return 28; if (symbolHash == symbolHash29) return 29; return uint(-1); } /** * @notice Get the i-th config, according to the order they were passed in originally * @param i The index of the config to get * @return The config object */ function getTokenConfig(uint i) public view returns (TokenConfig memory) { require(i < numTokens, "token config not found"); if (i == 0) return TokenConfig({cToken: cToken00, underlying: underlying00, symbolHash: symbolHash00, baseUnit: baseUnit00, priceSource: priceSource00, fixedPrice: fixedPrice00, uniswapMarket: uniswapMarket00, isUniswapReversed: isUniswapReversed00}); if (i == 1) return TokenConfig({cToken: cToken01, underlying: underlying01, symbolHash: symbolHash01, baseUnit: baseUnit01, priceSource: priceSource01, fixedPrice: fixedPrice01, uniswapMarket: uniswapMarket01, isUniswapReversed: isUniswapReversed01}); if (i == 2) return TokenConfig({cToken: cToken02, underlying: underlying02, symbolHash: symbolHash02, baseUnit: baseUnit02, priceSource: priceSource02, fixedPrice: fixedPrice02, uniswapMarket: uniswapMarket02, isUniswapReversed: isUniswapReversed02}); if (i == 3) return TokenConfig({cToken: cToken03, underlying: underlying03, symbolHash: symbolHash03, baseUnit: baseUnit03, priceSource: priceSource03, fixedPrice: fixedPrice03, uniswapMarket: uniswapMarket03, isUniswapReversed: isUniswapReversed03}); if (i == 4) return TokenConfig({cToken: cToken04, underlying: underlying04, symbolHash: symbolHash04, baseUnit: baseUnit04, priceSource: priceSource04, fixedPrice: fixedPrice04, uniswapMarket: uniswapMarket04, isUniswapReversed: isUniswapReversed04}); if (i == 5) return TokenConfig({cToken: cToken05, underlying: underlying05, symbolHash: symbolHash05, baseUnit: baseUnit05, priceSource: priceSource05, fixedPrice: fixedPrice05, uniswapMarket: uniswapMarket05, isUniswapReversed: isUniswapReversed05}); if (i == 6) return TokenConfig({cToken: cToken06, underlying: underlying06, symbolHash: symbolHash06, baseUnit: baseUnit06, priceSource: priceSource06, fixedPrice: fixedPrice06, uniswapMarket: uniswapMarket06, isUniswapReversed: isUniswapReversed06}); if (i == 7) return TokenConfig({cToken: cToken07, underlying: underlying07, symbolHash: symbolHash07, baseUnit: baseUnit07, priceSource: priceSource07, fixedPrice: fixedPrice07, uniswapMarket: uniswapMarket07, isUniswapReversed: isUniswapReversed07}); if (i == 8) return TokenConfig({cToken: cToken08, underlying: underlying08, symbolHash: symbolHash08, baseUnit: baseUnit08, priceSource: priceSource08, fixedPrice: fixedPrice08, uniswapMarket: uniswapMarket08, isUniswapReversed: isUniswapReversed08}); if (i == 9) return TokenConfig({cToken: cToken09, underlying: underlying09, symbolHash: symbolHash09, baseUnit: baseUnit09, priceSource: priceSource09, fixedPrice: fixedPrice09, uniswapMarket: uniswapMarket09, isUniswapReversed: isUniswapReversed09}); if (i == 10) return TokenConfig({cToken: cToken10, underlying: underlying10, symbolHash: symbolHash10, baseUnit: baseUnit10, priceSource: priceSource10, fixedPrice: fixedPrice10, uniswapMarket: uniswapMarket10, isUniswapReversed: isUniswapReversed10}); if (i == 11) return TokenConfig({cToken: cToken11, underlying: underlying11, symbolHash: symbolHash11, baseUnit: baseUnit11, priceSource: priceSource11, fixedPrice: fixedPrice11, uniswapMarket: uniswapMarket11, isUniswapReversed: isUniswapReversed11}); if (i == 12) return TokenConfig({cToken: cToken12, underlying: underlying12, symbolHash: symbolHash12, baseUnit: baseUnit12, priceSource: priceSource12, fixedPrice: fixedPrice12, uniswapMarket: uniswapMarket12, isUniswapReversed: isUniswapReversed12}); if (i == 13) return TokenConfig({cToken: cToken13, underlying: underlying13, symbolHash: symbolHash13, baseUnit: baseUnit13, priceSource: priceSource13, fixedPrice: fixedPrice13, uniswapMarket: uniswapMarket13, isUniswapReversed: isUniswapReversed13}); if (i == 14) return TokenConfig({cToken: cToken14, underlying: underlying14, symbolHash: symbolHash14, baseUnit: baseUnit14, priceSource: priceSource14, fixedPrice: fixedPrice14, uniswapMarket: uniswapMarket14, isUniswapReversed: isUniswapReversed14}); if (i == 15) return TokenConfig({cToken: cToken15, underlying: underlying15, symbolHash: symbolHash15, baseUnit: baseUnit15, priceSource: priceSource15, fixedPrice: fixedPrice15, uniswapMarket: uniswapMarket15, isUniswapReversed: isUniswapReversed15}); if (i == 16) return TokenConfig({cToken: cToken16, underlying: underlying16, symbolHash: symbolHash16, baseUnit: baseUnit16, priceSource: priceSource16, fixedPrice: fixedPrice16, uniswapMarket: uniswapMarket16, isUniswapReversed: isUniswapReversed16}); if (i == 17) return TokenConfig({cToken: cToken17, underlying: underlying17, symbolHash: symbolHash17, baseUnit: baseUnit17, priceSource: priceSource17, fixedPrice: fixedPrice17, uniswapMarket: uniswapMarket17, isUniswapReversed: isUniswapReversed17}); if (i == 18) return TokenConfig({cToken: cToken18, underlying: underlying18, symbolHash: symbolHash18, baseUnit: baseUnit18, priceSource: priceSource18, fixedPrice: fixedPrice18, uniswapMarket: uniswapMarket18, isUniswapReversed: isUniswapReversed18}); if (i == 19) return TokenConfig({cToken: cToken19, underlying: underlying19, symbolHash: symbolHash19, baseUnit: baseUnit19, priceSource: priceSource19, fixedPrice: fixedPrice19, uniswapMarket: uniswapMarket19, isUniswapReversed: isUniswapReversed19}); if (i == 20) return TokenConfig({cToken: cToken20, underlying: underlying20, symbolHash: symbolHash20, baseUnit: baseUnit20, priceSource: priceSource20, fixedPrice: fixedPrice20, uniswapMarket: uniswapMarket20, isUniswapReversed: isUniswapReversed20}); if (i == 21) return TokenConfig({cToken: cToken21, underlying: underlying21, symbolHash: symbolHash21, baseUnit: baseUnit21, priceSource: priceSource21, fixedPrice: fixedPrice21, uniswapMarket: uniswapMarket21, isUniswapReversed: isUniswapReversed21}); if (i == 22) return TokenConfig({cToken: cToken22, underlying: underlying22, symbolHash: symbolHash22, baseUnit: baseUnit22, priceSource: priceSource22, fixedPrice: fixedPrice22, uniswapMarket: uniswapMarket22, isUniswapReversed: isUniswapReversed22}); if (i == 23) return TokenConfig({cToken: cToken23, underlying: underlying23, symbolHash: symbolHash23, baseUnit: baseUnit23, priceSource: priceSource23, fixedPrice: fixedPrice23, uniswapMarket: uniswapMarket23, isUniswapReversed: isUniswapReversed23}); if (i == 24) return TokenConfig({cToken: cToken24, underlying: underlying24, symbolHash: symbolHash24, baseUnit: baseUnit24, priceSource: priceSource24, fixedPrice: fixedPrice24, uniswapMarket: uniswapMarket24, isUniswapReversed: isUniswapReversed24}); if (i == 25) return TokenConfig({cToken: cToken25, underlying: underlying25, symbolHash: symbolHash25, baseUnit: baseUnit25, priceSource: priceSource25, fixedPrice: fixedPrice25, uniswapMarket: uniswapMarket25, isUniswapReversed: isUniswapReversed25}); if (i == 26) return TokenConfig({cToken: cToken26, underlying: underlying26, symbolHash: symbolHash26, baseUnit: baseUnit26, priceSource: priceSource26, fixedPrice: fixedPrice26, uniswapMarket: uniswapMarket26, isUniswapReversed: isUniswapReversed26}); if (i == 27) return TokenConfig({cToken: cToken27, underlying: underlying27, symbolHash: symbolHash27, baseUnit: baseUnit27, priceSource: priceSource27, fixedPrice: fixedPrice27, uniswapMarket: uniswapMarket27, isUniswapReversed: isUniswapReversed27}); if (i == 28) return TokenConfig({cToken: cToken28, underlying: underlying28, symbolHash: symbolHash28, baseUnit: baseUnit28, priceSource: priceSource28, fixedPrice: fixedPrice28, uniswapMarket: uniswapMarket28, isUniswapReversed: isUniswapReversed28}); if (i == 29) return TokenConfig({cToken: cToken29, underlying: underlying29, symbolHash: symbolHash29, baseUnit: baseUnit29, priceSource: priceSource29, fixedPrice: fixedPrice29, uniswapMarket: uniswapMarket29, isUniswapReversed: isUniswapReversed29}); } /** * @notice Get the config for symbol * @param symbol The symbol of the config to get * @return The config object */ function getTokenConfigBySymbol(string memory symbol) public view returns (TokenConfig memory) { return getTokenConfigBySymbolHash(keccak256(abi.encodePacked(symbol))); } /** * @notice Get the config for the symbolHash * @param symbolHash The keccack256 of the symbol of the config to get * @return The config object */ function getTokenConfigBySymbolHash(bytes32 symbolHash) public view returns (TokenConfig memory) { uint index = getSymbolHashIndex(symbolHash); if (index != uint(-1)) { return getTokenConfig(index); } revert("token config not found"); } /** * @notice Get the config for the cToken * @dev If a config for the cToken is not found, falls back to searching for the underlying. * @param cToken The address of the cToken of the config to get * @return The config object */ function getTokenConfigByCToken(address cToken) public view returns (TokenConfig memory) { uint index = getCTokenIndex(cToken); if (index != uint(-1)) { return getTokenConfig(index); } return getTokenConfigByUnderlying(CErc20(cToken).underlying()); } /** * @notice Get the config for an underlying asset * @param underlying The address of the underlying asset of the config to get * @return The config object */ function getTokenConfigByUnderlying(address underlying) public view returns (TokenConfig memory) { uint index = getUnderlyingIndex(underlying); if (index != uint(-1)) { return getTokenConfig(index); } revert("token config not found"); } } // Based on code from https://github.com/Uniswap/uniswap-v2-periphery // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) library FixedPoint { // range: [0, 2**112 - 1] // resolution: 1 / 2**112 struct uq112x112 { uint224 _x; } // returns a uq112x112 which represents the ratio of the numerator to the denominator // equivalent to encode(numerator).div(denominator) function fraction(uint112 numerator, uint112 denominator) internal pure returns (uq112x112 memory) { require(denominator > 0, "FixedPoint: DIV_BY_ZERO"); return uq112x112((uint224(numerator) << 112) / denominator); } // decode a uq112x112 into a uint with 18 decimals of precision function decode112with18(uq112x112 memory self) internal pure returns (uint) { // we only have 256 - 224 = 32 bits to spare, so scaling up by ~60 bits is dangerous // instead, get close to: // (x * 1e18) >> 112 // without risk of overflowing, e.g.: // (x) / 2 ** (112 - lg(1e18)) return uint(self._x) / 5192296858534827; } } // library with helper methods for oracles that are concerned with computing average prices library UniswapV2OracleLibrary { using FixedPoint for *; // helper function that returns the current block timestamp within the range of uint32, i.e. [0, 2**32 - 1] function currentBlockTimestamp() internal view returns (uint32) { return uint32(block.timestamp % 2 ** 32); } // produces the cumulative price using counterfactuals to save gas and avoid a call to sync. function currentCumulativePrices( address pair ) internal view returns (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) { blockTimestamp = currentBlockTimestamp(); price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast(); price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast(); // if time has elapsed since the last update on the pair, mock the accumulated price values (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(pair).getReserves(); if (blockTimestampLast != blockTimestamp) { // subtraction overflow is desired uint32 timeElapsed = blockTimestamp - blockTimestampLast; // addition overflow is desired // counterfactual price0Cumulative += uint(FixedPoint.fraction(reserve1, reserve0)._x) * timeElapsed; // counterfactual price1Cumulative += uint(FixedPoint.fraction(reserve0, reserve1)._x) * timeElapsed; } } } interface IUniswapV2Pair { function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function price0CumulativeLast() external view returns (uint); function price1CumulativeLast() external view returns (uint); } struct Observation { uint timestamp; uint acc; } contract UniswapAnchoredView is UniswapConfig { using FixedPoint for *; /// @notice The Open Oracle Price Data contract OpenOraclePriceData public immutable priceData; /// @notice The number of wei in 1 ETH uint public constant ethBaseUnit = 1e18; /// @notice A common scaling factor to maintain precision uint public constant expScale = 1e18; /// @notice The Open Oracle Reporter address public immutable reporter; /// @notice The highest ratio of the new price to the anchor price that will still trigger the price to be updated uint public immutable upperBoundAnchorRatio; /// @notice The lowest ratio of the new price to the anchor price that will still trigger the price to be updated uint public immutable lowerBoundAnchorRatio; /// @notice The minimum amount of time in seconds required for the old uniswap price accumulator to be replaced uint public immutable anchorPeriod; /// @notice Official prices by symbol hash mapping(bytes32 => uint) public prices; /// @notice Circuit breaker for using anchor price oracle directly, ignoring reporter bool public reporterInvalidated; /// @notice The old observation for each symbolHash mapping(bytes32 => Observation) public oldObservations; /// @notice The new observation for each symbolHash mapping(bytes32 => Observation) public newObservations; /// @notice The event emitted when new prices are posted but the stored price is not updated due to the anchor event PriceGuarded(string symbol, uint reporter, uint anchor); /// @notice The event emitted when the stored price is updated event PriceUpdated(string symbol, uint price); /// @notice The event emitted when anchor price is updated event AnchorPriceUpdated(string symbol, uint anchorPrice, uint oldTimestamp, uint newTimestamp); /// @notice The event emitted when the uniswap window changes event UniswapWindowUpdated(bytes32 indexed symbolHash, uint oldTimestamp, uint newTimestamp, uint oldPrice, uint newPrice); /// @notice The event emitted when reporter invalidates itself event ReporterInvalidated(address reporter); bytes32 constant ethHash = keccak256(abi.encodePacked("ETH")); bytes32 constant rotateHash = keccak256(abi.encodePacked("rotate")); /** * @notice Construct a uniswap anchored view for a set of token configurations * @dev Note that to avoid immature TWAPs, the system must run for at least a single anchorPeriod before using. * @param reporter_ The reporter whose prices are to be used * @param anchorToleranceMantissa_ The percentage tolerance that the reporter may deviate from the uniswap anchor * @param anchorPeriod_ The minimum amount of time required for the old uniswap price accumulator to be replaced * @param configs The static token configurations which define what prices are supported and how */ constructor(OpenOraclePriceData priceData_, address reporter_, uint anchorToleranceMantissa_, uint anchorPeriod_, TokenConfig[] memory configs) UniswapConfig(configs) public { priceData = priceData_; reporter = reporter_; anchorPeriod = anchorPeriod_; // Allow the tolerance to be whatever the deployer chooses, but prevent under/overflow (and prices from being 0) upperBoundAnchorRatio = anchorToleranceMantissa_ > uint(-1) - 100e16 ? uint(-1) : 100e16 + anchorToleranceMantissa_; lowerBoundAnchorRatio = anchorToleranceMantissa_ < 100e16 ? 100e16 - anchorToleranceMantissa_ : 1; for (uint i = 0; i < configs.length; i++) { TokenConfig memory config = configs[i]; require(config.baseUnit > 0, "baseUnit must be greater than zero"); address uniswapMarket = config.uniswapMarket; if (config.priceSource == PriceSource.REPORTER) { require(uniswapMarket != address(0), "reported prices must have an anchor"); bytes32 symbolHash = config.symbolHash; uint cumulativePrice = currentCumulativePrice(config); oldObservations[symbolHash].timestamp = block.timestamp; newObservations[symbolHash].timestamp = block.timestamp; oldObservations[symbolHash].acc = cumulativePrice; newObservations[symbolHash].acc = cumulativePrice; emit UniswapWindowUpdated(symbolHash, block.timestamp, block.timestamp, cumulativePrice, cumulativePrice); } else { require(uniswapMarket == address(0), "only reported prices utilize an anchor"); } } } /** * @notice Get the official price for a symbol * @param symbol The symbol to fetch the price of * @return Price denominated in USD, with 6 decimals */ function price(string memory symbol) external view returns (uint) { TokenConfig memory config = getTokenConfigBySymbol(symbol); return priceInternal(config); } function priceInternal(TokenConfig memory config) internal view returns (uint) { if (config.priceSource == PriceSource.REPORTER) return prices[config.symbolHash]; if (config.priceSource == PriceSource.FIXED_USD) return config.fixedPrice; if (config.priceSource == PriceSource.FIXED_ETH) { uint usdPerEth = prices[ethHash]; require(usdPerEth > 0, "ETH price not set, cannot convert to dollars"); return mul(usdPerEth, config.fixedPrice) / ethBaseUnit; } } /** * @notice Get the underlying price of a cToken * @dev Implements the PriceOracle interface for Compound v2. * @param cToken The cToken address for price retrieval * @return Price denominated in USD, with 18 decimals, for the given cToken address */ function getUnderlyingPrice(address cToken) external view returns (uint) { TokenConfig memory config = getTokenConfigByCToken(cToken); // Comptroller needs prices in the format: ${raw price} * 1e(36 - baseUnit) // Since the prices in this view have 6 decimals, we must scale them by 1e(36 - 6 - baseUnit) return mul(1e30, priceInternal(config)) / config.baseUnit; } /** * @notice Post open oracle reporter prices, and recalculate stored price by comparing to anchor * @dev We let anyone pay to post anything, but only prices from configured reporter will be stored in the view. * @param messages The messages to post to the oracle * @param signatures The signatures for the corresponding messages * @param symbols The symbols to compare to anchor for authoritative reading */ function postPrices(bytes[] calldata messages, bytes[] calldata signatures, string[] calldata symbols) external { require(messages.length == signatures.length, "messages and signatures must be 1:1"); // Save the prices for (uint i = 0; i < messages.length; i++) { priceData.put(messages[i], signatures[i]); } uint ethPrice = fetchEthPrice(); // Try to update the view storage for (uint i = 0; i < symbols.length; i++) { postPriceInternal(symbols[i], ethPrice); } } function postPriceInternal(string memory symbol, uint ethPrice) internal { TokenConfig memory config = getTokenConfigBySymbol(symbol); require(config.priceSource == PriceSource.REPORTER, "only reporter prices get posted"); bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); uint reporterPrice = priceData.getPrice(reporter, symbol); uint anchorPrice; if (symbolHash == ethHash) { anchorPrice = ethPrice; } else { anchorPrice = fetchAnchorPrice(symbol, config, ethPrice); } if (reporterInvalidated) { prices[symbolHash] = anchorPrice; emit PriceUpdated(symbol, anchorPrice); } else if (isWithinAnchor(reporterPrice, anchorPrice)) { prices[symbolHash] = reporterPrice; emit PriceUpdated(symbol, reporterPrice); } else { emit PriceGuarded(symbol, reporterPrice, anchorPrice); } } function isWithinAnchor(uint reporterPrice, uint anchorPrice) internal view returns (bool) { if (reporterPrice > 0) { uint anchorRatio = mul(anchorPrice, 100e16) / reporterPrice; return anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio; } return false; } /** * @dev Fetches the current token/eth price accumulator from uniswap. */ function currentCumulativePrice(TokenConfig memory config) internal view returns (uint) { (uint cumulativePrice0, uint cumulativePrice1,) = UniswapV2OracleLibrary.currentCumulativePrices(config.uniswapMarket); if (config.isUniswapReversed) { return cumulativePrice1; } else { return cumulativePrice0; } } /** * @dev Fetches the current eth/usd price from uniswap, with 6 decimals of precision. * Conversion factor is 1e18 for eth/usdc market, since we decode uniswap price statically with 18 decimals. */ function fetchEthPrice() internal returns (uint) { return fetchAnchorPrice("ETH", getTokenConfigBySymbolHash(ethHash), ethBaseUnit); } /** * @dev Fetches the current token/usd price from uniswap, with 6 decimals of precision. * @param conversionFactor 1e18 if seeking the ETH price, and a 6 decimal ETH-USDC price in the case of other assets */ function fetchAnchorPrice(string memory symbol, TokenConfig memory config, uint conversionFactor) internal virtual returns (uint) { (uint nowCumulativePrice, uint oldCumulativePrice, uint oldTimestamp) = pokeWindowValues(config); // This should be impossible, but better safe than sorry require(block.timestamp > oldTimestamp, "now must come after before"); uint timeElapsed = block.timestamp - oldTimestamp; // Calculate uniswap time-weighted average price // Underflow is a property of the accumulators: https://uniswap.org/audit.html#orgc9b3190 FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(uint224((nowCumulativePrice - oldCumulativePrice) / timeElapsed)); uint rawUniswapPriceMantissa = priceAverage.decode112with18(); uint unscaledPriceMantissa = mul(rawUniswapPriceMantissa, conversionFactor); uint anchorPrice; // Adjust rawUniswapPrice according to the units of the non-ETH asset // In the case of ETH, we would have to scale by 1e6 / USDC_UNITS, but since baseUnit2 is 1e6 (USDC), it cancels if (config.isUniswapReversed) { // unscaledPriceMantissa * ethBaseUnit / config.baseUnit / expScale, but we simplify bc ethBaseUnit == expScale anchorPrice = unscaledPriceMantissa / config.baseUnit; } else { anchorPrice = mul(unscaledPriceMantissa, config.baseUnit) / ethBaseUnit / expScale; } emit AnchorPriceUpdated(symbol, anchorPrice, oldTimestamp, block.timestamp); return anchorPrice; } /** * @dev Get time-weighted average prices for a token at the current timestamp. * Update new and old observations of lagging window if period elapsed. */ function pokeWindowValues(TokenConfig memory config) internal returns (uint, uint, uint) { bytes32 symbolHash = config.symbolHash; uint cumulativePrice = currentCumulativePrice(config); Observation memory newObservation = newObservations[symbolHash]; // Update new and old observations if elapsed time is greater than or equal to anchor period uint timeElapsed = block.timestamp - newObservation.timestamp; if (timeElapsed >= anchorPeriod) { oldObservations[symbolHash].timestamp = newObservation.timestamp; oldObservations[symbolHash].acc = newObservation.acc; newObservations[symbolHash].timestamp = block.timestamp; newObservations[symbolHash].acc = cumulativePrice; emit UniswapWindowUpdated(config.symbolHash, newObservation.timestamp, block.timestamp, newObservation.acc, cumulativePrice); } return (cumulativePrice, oldObservations[symbolHash].acc, oldObservations[symbolHash].timestamp); } /** * @notice Invalidate the reporter, and fall back to using anchor directly in all cases * @dev Only the reporter may sign a message which allows it to invalidate itself. * To be used in cases of emergency, if the reporter thinks their key may be compromised. * @param message The data that was presumably signed * @param signature The fingerprint of the data + private key */ function invalidateReporter(bytes memory message, bytes memory signature) external { (string memory decodedMessage, ) = abi.decode(message, (string, address)); require(keccak256(abi.encodePacked(decodedMessage)) == rotateHash, "invalid message must be 'rotate'"); require(source(message, signature) == reporter, "invalidation message must come from the reporter"); reporterInvalidated = true; emit ReporterInvalidated(reporter); } /** * @notice Recovers the source address which signed a message * @dev Comparing to a claimed address would add nothing, * as the caller could simply perform the recover and claim that address. * @param message The data that was presumably signed * @param signature The fingerprint of the data + private key * @return The source address which signed the message, presumably */ function source(bytes memory message, bytes memory signature) public pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = abi.decode(signature, (bytes32, bytes32, uint8)); bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(message))); return ecrecover(hash, v, r, s); } /// @dev Overflow proof multiplication function mul(uint a, uint b) internal pure returns (uint) { if (a == 0) return 0; uint c = a * b; require(c / a == b, "multiplication overflow"); return c; } }
File 6 of 6: UniswapV2Pair
// File: contracts/interfaces/IUniswapV2Pair.sol pragma solidity >=0.5.0; interface IUniswapV2Pair { event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value); function name() external pure returns (string memory); function symbol() external pure returns (string memory); function decimals() external pure returns (uint8); function totalSupply() external view returns (uint); function balanceOf(address owner) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); function approve(address spender, uint value) external returns (bool); function transfer(address to, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); function DOMAIN_SEPARATOR() external view returns (bytes32); function PERMIT_TYPEHASH() external pure returns (bytes32); function nonces(address owner) external view returns (uint); function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; event Mint(address indexed sender, uint amount0, uint amount1); event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); event Swap( address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to ); event Sync(uint112 reserve0, uint112 reserve1); function MINIMUM_LIQUIDITY() external pure returns (uint); function factory() external view returns (address); function token0() external view returns (address); function token1() external view returns (address); function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function price0CumulativeLast() external view returns (uint); function price1CumulativeLast() external view returns (uint); function kLast() external view returns (uint); function mint(address to) external returns (uint liquidity); function burn(address to) external returns (uint amount0, uint amount1); function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; function skim(address to) external; function sync() external; function initialize(address, address) external; } // File: contracts/interfaces/IUniswapV2ERC20.sol pragma solidity >=0.5.0; interface IUniswapV2ERC20 { event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value); function name() external pure returns (string memory); function symbol() external pure returns (string memory); function decimals() external pure returns (uint8); function totalSupply() external view returns (uint); function balanceOf(address owner) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); function approve(address spender, uint value) external returns (bool); function transfer(address to, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); function DOMAIN_SEPARATOR() external view returns (bytes32); function PERMIT_TYPEHASH() external pure returns (bytes32); function nonces(address owner) external view returns (uint); function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; } // File: contracts/libraries/SafeMath.sol pragma solidity =0.5.16; // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) library SafeMath { function add(uint x, uint y) internal pure returns (uint z) { require((z = x + y) >= x, 'ds-math-add-overflow'); } function sub(uint x, uint y) internal pure returns (uint z) { require((z = x - y) <= x, 'ds-math-sub-underflow'); } function mul(uint x, uint y) internal pure returns (uint z) { require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); } } // File: contracts/UniswapV2ERC20.sol pragma solidity =0.5.16; contract UniswapV2ERC20 is IUniswapV2ERC20 { using SafeMath for uint; string public constant name = 'Uniswap V2'; string public constant symbol = 'UNI-V2'; uint8 public constant decimals = 18; uint public totalSupply; mapping(address => uint) public balanceOf; mapping(address => mapping(address => uint)) public allowance; bytes32 public DOMAIN_SEPARATOR; // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; mapping(address => uint) public nonces; event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value); constructor() public { uint chainId; assembly { chainId := chainid } DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), keccak256(bytes(name)), keccak256(bytes('1')), chainId, address(this) ) ); } function _mint(address to, uint value) internal { totalSupply = totalSupply.add(value); balanceOf[to] = balanceOf[to].add(value); emit Transfer(address(0), to, value); } function _burn(address from, uint value) internal { balanceOf[from] = balanceOf[from].sub(value); totalSupply = totalSupply.sub(value); emit Transfer(from, address(0), value); } function _approve(address owner, address spender, uint value) private { allowance[owner][spender] = value; emit Approval(owner, spender, value); } function _transfer(address from, address to, uint value) private { balanceOf[from] = balanceOf[from].sub(value); balanceOf[to] = balanceOf[to].add(value); emit Transfer(from, to, value); } function approve(address spender, uint value) external returns (bool) { _approve(msg.sender, spender, value); return true; } function transfer(address to, uint value) external returns (bool) { _transfer(msg.sender, to, value); return true; } function transferFrom(address from, address to, uint value) external returns (bool) { if (allowance[from][msg.sender] != uint(-1)) { allowance[from][msg.sender] = allowance[from][msg.sender].sub(value); } _transfer(from, to, value); return true; } function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external { require(deadline >= block.timestamp, 'UniswapV2: EXPIRED'); bytes32 digest = keccak256( abi.encodePacked( '\x19\x01', DOMAIN_SEPARATOR, keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) ) ); address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE'); _approve(owner, spender, value); } } // File: contracts/libraries/Math.sol pragma solidity =0.5.16; // a library for performing various math operations library Math { function min(uint x, uint y) internal pure returns (uint z) { z = x < y ? x : y; } // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) function sqrt(uint y) internal pure returns (uint z) { if (y > 3) { z = y; uint x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } } // File: contracts/libraries/UQ112x112.sol pragma solidity =0.5.16; // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) // range: [0, 2**112 - 1] // resolution: 1 / 2**112 library UQ112x112 { uint224 constant Q112 = 2**112; // encode a uint112 as a UQ112x112 function encode(uint112 y) internal pure returns (uint224 z) { z = uint224(y) * Q112; // never overflows } // divide a UQ112x112 by a uint112, returning a UQ112x112 function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { z = x / uint224(y); } } // File: contracts/interfaces/IERC20.sol pragma solidity >=0.5.0; interface IERC20 { event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value); function name() external view returns (string memory); function symbol() external view returns (string memory); function decimals() external view returns (uint8); function totalSupply() external view returns (uint); function balanceOf(address owner) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); function approve(address spender, uint value) external returns (bool); function transfer(address to, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); } // File: contracts/interfaces/IUniswapV2Factory.sol pragma solidity >=0.5.0; interface IUniswapV2Factory { event PairCreated(address indexed token0, address indexed token1, address pair, uint); function feeTo() external view returns (address); function feeToSetter() external view returns (address); function getPair(address tokenA, address tokenB) external view returns (address pair); function allPairs(uint) external view returns (address pair); function allPairsLength() external view returns (uint); function createPair(address tokenA, address tokenB) external returns (address pair); function setFeeTo(address) external; function setFeeToSetter(address) external; } // File: contracts/interfaces/IUniswapV2Callee.sol pragma solidity >=0.5.0; interface IUniswapV2Callee { function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; } // File: contracts/UniswapV2Pair.sol pragma solidity =0.5.16; contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { using SafeMath for uint; using UQ112x112 for uint224; uint public constant MINIMUM_LIQUIDITY = 10**3; bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)'))); address public factory; address public token0; address public token1; uint112 private reserve0; // uses single storage slot, accessible via getReserves uint112 private reserve1; // uses single storage slot, accessible via getReserves uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves uint public price0CumulativeLast; uint public price1CumulativeLast; uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event uint private unlocked = 1; modifier lock() { require(unlocked == 1, 'UniswapV2: LOCKED'); unlocked = 0; _; unlocked = 1; } function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) { _reserve0 = reserve0; _reserve1 = reserve1; _blockTimestampLast = blockTimestampLast; } function _safeTransfer(address token, address to, uint value) private { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED'); } event Mint(address indexed sender, uint amount0, uint amount1); event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); event Swap( address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to ); event Sync(uint112 reserve0, uint112 reserve1); constructor() public { factory = msg.sender; } // called once by the factory at time of deployment function initialize(address _token0, address _token1) external { require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check token0 = _token0; token1 = _token1; } // update reserves and, on the first call per block, price accumulators function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { // * never overflows, and + overflow is desired price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; } reserve0 = uint112(balance0); reserve1 = uint112(balance1); blockTimestampLast = blockTimestamp; emit Sync(reserve0, reserve1); } // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) { address feeTo = IUniswapV2Factory(factory).feeTo(); feeOn = feeTo != address(0); uint _kLast = kLast; // gas savings if (feeOn) { if (_kLast != 0) { uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1)); uint rootKLast = Math.sqrt(_kLast); if (rootK > rootKLast) { uint numerator = totalSupply.mul(rootK.sub(rootKLast)); uint denominator = rootK.mul(5).add(rootKLast); uint liquidity = numerator / denominator; if (liquidity > 0) _mint(feeTo, liquidity); } } } else if (_kLast != 0) { kLast = 0; } } // this low-level function should be called from a contract which performs important safety checks function mint(address to) external lock returns (uint liquidity) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); uint amount0 = balance0.sub(_reserve0); uint amount1 = balance1.sub(_reserve1); bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee if (_totalSupply == 0) { liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); } require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); _mint(to, liquidity); _update(balance0, balance1, _reserve0, _reserve1); if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date emit Mint(msg.sender, amount0, amount1); } // this low-level function should be called from a contract which performs important safety checks function burn(address to) external lock returns (uint amount0, uint amount1) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings address _token0 = token0; // gas savings address _token1 = token1; // gas savings uint balance0 = IERC20(_token0).balanceOf(address(this)); uint balance1 = IERC20(_token1).balanceOf(address(this)); uint liquidity = balanceOf[address(this)]; bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); _burn(address(this), liquidity); _safeTransfer(_token0, to, amount0); _safeTransfer(_token1, to, amount1); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); _update(balance0, balance1, _reserve0, _reserve1); if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date emit Burn(msg.sender, amount0, amount1, to); } // this low-level function should be called from a contract which performs important safety checks function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY'); uint balance0; uint balance1; { // scope for _token{0,1}, avoids stack too deep errors address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); } _update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } // force balances to match reserves function skim(address to) external lock { address _token0 = token0; // gas savings address _token1 = token1; // gas savings _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0)); _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1)); } // force reserves to match balances function sync() external lock { _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1); } }