ETH Price: $2,235.82 (-1.32%)

Transaction Decoder

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 Code
0x03eb7Ce2...573d3cECC 18.701585781518837547 Eth18.901585781518837547 Eth0.2
0x0799442C...5625B4bf9
2.398164991533417382 Eth
Nonce: 2336
2.188346702533417382 Eth
Nonce: 2337
0.209818289
(Spark Pool)
97.803212466264884405 Eth97.813030755264884405 Eth0.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
        // 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);
            }
        }