ETH Price: $2,787.80 (+4.35%)

Transaction Decoder

Block:
21102757 at Nov-02-2024 09:52:23 PM +UTC
Transaction Fee:
0.000240389281081956 ETH $0.67
Gas Used:
58,796 Gas / 4.088531211 Gwei

Emitted Events:

317 EIP173Proxy.0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925( 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x000000000000000000000000501b06e19675eb0107ad13e7d3379c3389222f20, 0x0000000000000000000000004e5d9b093986d864331d88e0a13a616e1d508838, 0x00000000000000000000000000000000000000000000000000000000000001b3 )

Account State Difference:

  Address   Before After State Difference Code
0x501B06e1...389222f20
0.00086714041657206 Eth
Nonce: 211
0.000626751135490104 Eth
Nonce: 212
0.000240389281081956
0x57686612...24BBd01df
(beaverbuild)
14.553024342236213464 Eth14.553081374356213464 Eth0.00005703212

Execution Trace

EIP173Proxy.095ea7b3( )
  • PoolTokens.approve( to=0x4E5d9B093986D864331d88e0a13a616e1D508838, tokenId=435 )
    File 1 of 2: EIP173Proxy
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.7.0;
    import "./Proxy.sol";
    interface ERC165 {
        function supportsInterface(bytes4 id) external view returns (bool);
    }
    ///@notice Proxy implementing EIP173 for ownership management
    contract EIP173Proxy is Proxy {
        // ////////////////////////// EVENTS ///////////////////////////////////////////////////////////////////////
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        // /////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////////////
        constructor(
            address implementationAddress,
            address ownerAddress,
            bytes memory data
        ) payable {
            _setImplementation(implementationAddress, data);
            _setOwner(ownerAddress);
        }
        // ///////////////////// EXTERNAL ///////////////////////////////////////////////////////////////////////////
        function owner() external view returns (address) {
            return _owner();
        }
        function supportsInterface(bytes4 id) external view returns (bool) {
            if (id == 0x01ffc9a7 || id == 0x7f5828d0) {
                return true;
            }
            if (id == 0xFFFFFFFF) {
                return false;
            }
            ERC165 implementation;
            // solhint-disable-next-line security/no-inline-assembly
            assembly {
                implementation := sload(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)
            }
            // Technically this is not standard compliant as ERC-165 require 30,000 gas which that call cannot ensure
            // because it is itself inside `supportsInterface` that might only get 30,000 gas.
            // In practise this is unlikely to be an issue.
            try implementation.supportsInterface(id) returns (bool support) {
                return support;
            } catch {
                return false;
            }
        }
        function transferOwnership(address newOwner) external onlyOwner {
            _setOwner(newOwner);
        }
        function upgradeTo(address newImplementation) external onlyOwner {
            _setImplementation(newImplementation, "");
        }
        function upgradeToAndCall(address newImplementation, bytes calldata data) external payable onlyOwner {
            _setImplementation(newImplementation, data);
        }
        // /////////////////////// MODIFIERS ////////////////////////////////////////////////////////////////////////
        modifier onlyOwner() {
            require(msg.sender == _owner(), "NOT_AUTHORIZED");
            _;
        }
        // ///////////////////////// INTERNAL //////////////////////////////////////////////////////////////////////
        function _owner() internal view returns (address adminAddress) {
            // solhint-disable-next-line security/no-inline-assembly
            assembly {
                adminAddress := sload(0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103)
            }
        }
        function _setOwner(address newOwner) internal {
            address previousOwner = _owner();
            // solhint-disable-next-line security/no-inline-assembly
            assembly {
                sstore(0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103, newOwner)
            }
            emit OwnershipTransferred(previousOwner, newOwner);
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.7.0;
    // EIP-1967
    abstract contract Proxy {
        // /////////////////////// EVENTS ///////////////////////////////////////////////////////////////////////////
        event ProxyImplementationUpdated(address indexed previousImplementation, address indexed newImplementation);
        // ///////////////////// EXTERNAL ///////////////////////////////////////////////////////////////////////////
        receive() external payable virtual {
            revert("ETHER_REJECTED"); // explicit reject by default
        }
        fallback() external payable {
            _fallback();
        }
        // ///////////////////////// INTERNAL //////////////////////////////////////////////////////////////////////
        function _fallback() internal {
            // solhint-disable-next-line security/no-inline-assembly
            assembly {
                let implementationAddress := sload(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)
                calldatacopy(0x0, 0x0, calldatasize())
                let success := delegatecall(gas(), implementationAddress, 0x0, calldatasize(), 0, 0)
                let retSz := returndatasize()
                returndatacopy(0, 0, retSz)
                switch success
                    case 0 {
                        revert(0, retSz)
                    }
                    default {
                        return(0, retSz)
                    }
            }
        }
        function _setImplementation(address newImplementation, bytes memory data) internal {
            address previousImplementation;
            // solhint-disable-next-line security/no-inline-assembly
            assembly {
                previousImplementation := sload(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)
            }
            // solhint-disable-next-line security/no-inline-assembly
            assembly {
                sstore(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, newImplementation)
            }
            emit ProxyImplementationUpdated(previousImplementation, newImplementation);
            if (data.length > 0) {
                (bool success, ) = newImplementation.delegatecall(data);
                if (!success) {
                    assembly {
                        // This assembly ensure the revert contains the exact string data
                        let returnDataSize := returndatasize()
                        returndatacopy(0, 0, returnDataSize)
                        revert(0, returnDataSize)
                    }
                }
            }
        }
    }
    

    File 2 of 2: PoolTokens
    pragma solidity ^0.6.0;
    import "../Initializable.sol";
    /*
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with GSN meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    contract ContextUpgradeSafe is Initializable {
        // Empty internal constructor, to prevent people from mistakenly deploying
        // an instance of this contract, which should be used via inheritance.
        function __Context_init() internal initializer {
            __Context_init_unchained();
        }
        function __Context_init_unchained() internal initializer {
        }
        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;
        }
        uint256[50] private __gap;
    }
    pragma solidity >=0.4.24 <0.7.0;
    /**
     * @title Initializable
     *
     * @dev Helper contract to support initializer functions. To use it, replace
     * the constructor with a function that has the `initializer` modifier.
     * WARNING: Unlike constructors, initializer functions must be manually
     * invoked. This applies both to deploying an Initializable contract, as well
     * as extending an Initializable contract via inheritance.
     * WARNING: When used with inheritance, manual care must be taken to not invoke
     * a parent initializer twice, or ensure that all initializers are idempotent,
     * because this is not dealt with automatically as with constructors.
     */
    contract Initializable {
      /**
       * @dev Indicates that the contract has been initialized.
       */
      bool private initialized;
      /**
       * @dev Indicates that the contract is in the process of being initialized.
       */
      bool private initializing;
      /**
       * @dev Modifier to use in the initializer function of a contract.
       */
      modifier initializer() {
        require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
        bool isTopLevelCall = !initializing;
        if (isTopLevelCall) {
          initializing = true;
          initialized = true;
        }
        _;
        if (isTopLevelCall) {
          initializing = false;
        }
      }
      /// @dev Returns true if and only if the function is running in the constructor
      function isConstructor() private view returns (bool) {
        // extcodesize checks the size of the code stored in an address, and
        // address returns the current address. Since the code is still not
        // deployed when running a constructor, any checks on its code size will
        // yield zero, making it an effective way to detect if a contract is
        // under construction or not.
        address self = address(this);
        uint256 cs;
        assembly { cs := extcodesize(self) }
        return cs == 0;
      }
      // Reserved storage space to allow for layout changes in the future.
      uint256[50] private ______gap;
    }
    pragma solidity ^0.6.0;
    import "../utils/EnumerableSet.sol";
    import "../utils/Address.sol";
    import "../GSN/Context.sol";
    import "../Initializable.sol";
    /**
     * @dev Contract module that allows children to implement role-based access
     * control mechanisms.
     *
     * Roles are referred to by their `bytes32` identifier. These should be exposed
     * in the external API and be unique. The best way to achieve this is by
     * using `public constant` hash digests:
     *
     * ```
     * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
     * ```
     *
     * Roles can be used to represent a set of permissions. To restrict access to a
     * function call, use {hasRole}:
     *
     * ```
     * function foo() public {
     *     require(hasRole(MY_ROLE, _msgSender()));
     *     ...
     * }
     * ```
     *
     * Roles can be granted and revoked dynamically via the {grantRole} and
     * {revokeRole} functions. Each role has an associated admin role, and only
     * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
     *
     * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
     * that only accounts with this role will be able to grant or revoke other
     * roles. More complex role relationships can be created by using
     * {_setRoleAdmin}.
     */
    abstract contract AccessControlUpgradeSafe is Initializable, ContextUpgradeSafe {
        function __AccessControl_init() internal initializer {
            __Context_init_unchained();
            __AccessControl_init_unchained();
        }
        function __AccessControl_init_unchained() internal initializer {
        }
        using EnumerableSet for EnumerableSet.AddressSet;
        using Address for address;
        struct RoleData {
            EnumerableSet.AddressSet members;
            bytes32 adminRole;
        }
        mapping (bytes32 => RoleData) private _roles;
        bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
        /**
         * @dev Emitted when `account` is granted `role`.
         *
         * `sender` is the account that originated the contract call, an admin role
         * bearer except when using {_setupRole}.
         */
        event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
        /**
         * @dev Emitted when `account` is revoked `role`.
         *
         * `sender` is the account that originated the contract call:
         *   - if using `revokeRole`, it is the admin role bearer
         *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
         */
        event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) public view returns (bool) {
            return _roles[role].members.contains(account);
        }
        /**
         * @dev Returns the number of accounts that have `role`. Can be used
         * together with {getRoleMember} to enumerate all bearers of a role.
         */
        function getRoleMemberCount(bytes32 role) public view returns (uint256) {
            return _roles[role].members.length();
        }
        /**
         * @dev Returns one of the accounts that have `role`. `index` must be a
         * value between 0 and {getRoleMemberCount}, non-inclusive.
         *
         * Role bearers are not sorted in any particular way, and their ordering may
         * change at any point.
         *
         * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
         * you perform all queries on the same block. See the following
         * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
         * for more information.
         */
        function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
            return _roles[role].members.at(index);
        }
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) public view returns (bytes32) {
            return _roles[role].adminRole;
        }
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function grantRole(bytes32 role, address account) public virtual {
            require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
            _grantRole(role, account);
        }
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function revokeRole(bytes32 role, address account) public virtual {
            require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");
            _revokeRole(role, account);
        }
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been granted `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         */
        function renounceRole(bytes32 role, address account) public virtual {
            require(account == _msgSender(), "AccessControl: can only renounce roles for self");
            _revokeRole(role, account);
        }
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event. Note that unlike {grantRole}, this function doesn't perform any
         * checks on the calling account.
         *
         * [WARNING]
         * ====
         * This function should only be called from the constructor when setting
         * up the initial roles for the system.
         *
         * Using this function in any other way is effectively circumventing the admin
         * system imposed by {AccessControl}.
         * ====
         */
        function _setupRole(bytes32 role, address account) internal virtual {
            _grantRole(role, account);
        }
        /**
         * @dev Sets `adminRole` as ``role``'s admin role.
         */
        function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
            _roles[role].adminRole = adminRole;
        }
        function _grantRole(bytes32 role, address account) private {
            if (_roles[role].members.add(account)) {
                emit RoleGranted(role, account, _msgSender());
            }
        }
        function _revokeRole(bytes32 role, address account) private {
            if (_roles[role].members.remove(account)) {
                emit RoleRevoked(role, account, _msgSender());
            }
        }
        uint256[49] private __gap;
    }
    pragma solidity ^0.6.0;
    /**
     * @dev Wrappers over Solidity's arithmetic operations with added overflow
     * checks.
     *
     * Arithmetic operations in Solidity wrap on overflow. This can easily result
     * in bugs, because programmers usually assume that an overflow raises an
     * error, which is the standard behavior in high level programming languages.
     * `SafeMath` restores this intuition by reverting the transaction when an
     * operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     */
    library SafeMath {
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         * - Addition cannot overflow.
         */
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            uint256 c = a + b;
            require(c >= a, "SafeMath: addition overflow");
            return c;
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            return sub(a, b, "SafeMath: subtraction overflow");
        }
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b <= a, errorMessage);
            uint256 c = a - b;
            return c;
        }
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         * - Multiplication cannot overflow.
         */
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) {
                return 0;
            }
            uint256 c = a * b;
            require(c / a == b, "SafeMath: multiplication overflow");
            return c;
        }
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            return div(a, b, "SafeMath: division by zero");
        }
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            // Solidity only automatically asserts when dividing by 0
            require(b > 0, errorMessage);
            uint256 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            return c;
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
            return mod(a, b, "SafeMath: modulo by zero");
        }
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts with custom message when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b != 0, errorMessage);
            return a % b;
        }
    }
    pragma solidity ^0.6.0;
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves `amount` tokens from the caller's account to `recipient`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address recipient, uint256 amount) external returns (bool);
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
        /**
         * @dev Moves `amount` tokens from `sender` to `recipient` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    pragma solidity ^0.6.2;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
            // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
            // for accounts without code, i.e. `keccak256('')`
            bytes32 codehash;
            bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
            // solhint-disable-next-line no-inline-assembly
            assembly { codehash := extcodehash(account) }
            return (codehash != accountHash && codehash != 0x0);
        }
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
            // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
            (bool success, ) = recipient.call{ value: amount }("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
    }
    pragma solidity ^0.6.0;
    import "../math/SafeMath.sol";
    /**
     * @title Counters
     * @author Matt Condon (@shrugs)
     * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
     * of elements in a mapping, issuing ERC721 ids, or counting request ids.
     *
     * Include with `using Counters for Counters.Counter;`
     * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the {SafeMath}
     * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
     * directly accessed.
     */
    library Counters {
        using SafeMath for uint256;
        struct Counter {
            // This variable should never be directly accessed by users of the library: interactions must be restricted to
            // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
            // this feature: see https://github.com/ethereum/solidity/issues/4637
            uint256 _value; // default: 0
        }
        function current(Counter storage counter) internal view returns (uint256) {
            return counter._value;
        }
        function increment(Counter storage counter) internal {
            // The {SafeMath} overflow check can be skipped here, see the comment at the top
            counter._value += 1;
        }
        function decrement(Counter storage counter) internal {
            counter._value = counter._value.sub(1);
        }
    }
    pragma solidity ^0.6.0;
    /**
     * @dev Library for managing an enumerable variant of Solidity's
     * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
     * type.
     *
     * Maps have the following properties:
     *
     * - Entries are added, removed, and checked for existence in constant time
     * (O(1)).
     * - Entries are enumerated in O(n). No guarantees are made on the ordering.
     *
     * ```
     * contract Example {
     *     // Add the library methods
     *     using EnumerableMap for EnumerableMap.UintToAddressMap;
     *
     *     // Declare a set state variable
     *     EnumerableMap.UintToAddressMap private myMap;
     * }
     * ```
     *
     * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are
     * supported.
     */
    library EnumerableMap {
        // To implement this library for multiple types with as little code
        // repetition as possible, we write it in terms of a generic Map type with
        // bytes32 keys and values.
        // The Map implementation uses private functions, and user-facing
        // implementations (such as Uint256ToAddressMap) are just wrappers around
        // the underlying Map.
        // This means that we can only create new EnumerableMaps for types that fit
        // in bytes32.
        struct MapEntry {
            bytes32 _key;
            bytes32 _value;
        }
        struct Map {
            // Storage of map keys and values
            MapEntry[] _entries;
            // Position of the entry defined by a key in the `entries` array, plus 1
            // because index 0 means a key is not in the map.
            mapping (bytes32 => uint256) _indexes;
        }
        /**
         * @dev Adds a key-value pair to a map, or updates the value for an existing
         * key. O(1).
         *
         * Returns true if the key was added to the map, that is if it was not
         * already present.
         */
        function _set(Map storage map, bytes32 key, bytes32 value) private returns (bool) {
            // We read and store the key's index to prevent multiple reads from the same storage slot
            uint256 keyIndex = map._indexes[key];
            if (keyIndex == 0) { // Equivalent to !contains(map, key)
                map._entries.push(MapEntry({ _key: key, _value: value }));
                // The entry is stored at length-1, but we add 1 to all indexes
                // and use 0 as a sentinel value
                map._indexes[key] = map._entries.length;
                return true;
            } else {
                map._entries[keyIndex - 1]._value = value;
                return false;
            }
        }
        /**
         * @dev Removes a key-value pair from a map. O(1).
         *
         * Returns true if the key was removed from the map, that is if it was present.
         */
        function _remove(Map storage map, bytes32 key) private returns (bool) {
            // We read and store the key's index to prevent multiple reads from the same storage slot
            uint256 keyIndex = map._indexes[key];
            if (keyIndex != 0) { // Equivalent to contains(map, key)
                // To delete a key-value pair from the _entries array in O(1), we swap the entry to delete with the last one
                // in the array, and then remove the last entry (sometimes called as 'swap and pop').
                // This modifies the order of the array, as noted in {at}.
                uint256 toDeleteIndex = keyIndex - 1;
                uint256 lastIndex = map._entries.length - 1;
                // When the entry to delete is the last one, the swap operation is unnecessary. However, since this occurs
                // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
                MapEntry storage lastEntry = map._entries[lastIndex];
                // Move the last entry to the index where the entry to delete is
                map._entries[toDeleteIndex] = lastEntry;
                // Update the index for the moved entry
                map._indexes[lastEntry._key] = toDeleteIndex + 1; // All indexes are 1-based
                // Delete the slot where the moved entry was stored
                map._entries.pop();
                // Delete the index for the deleted slot
                delete map._indexes[key];
                return true;
            } else {
                return false;
            }
        }
        /**
         * @dev Returns true if the key is in the map. O(1).
         */
        function _contains(Map storage map, bytes32 key) private view returns (bool) {
            return map._indexes[key] != 0;
        }
        /**
         * @dev Returns the number of key-value pairs in the map. O(1).
         */
        function _length(Map storage map) private view returns (uint256) {
            return map._entries.length;
        }
       /**
        * @dev Returns the key-value pair stored at position `index` in the map. O(1).
        *
        * Note that there are no guarantees on the ordering of entries inside the
        * array, and it may change when more entries are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) {
            require(map._entries.length > index, "EnumerableMap: index out of bounds");
            MapEntry storage entry = map._entries[index];
            return (entry._key, entry._value);
        }
        /**
         * @dev Returns the value associated with `key`.  O(1).
         *
         * Requirements:
         *
         * - `key` must be in the map.
         */
        function _get(Map storage map, bytes32 key) private view returns (bytes32) {
            return _get(map, key, "EnumerableMap: nonexistent key");
        }
        /**
         * @dev Same as {_get}, with a custom error message when `key` is not in the map.
         */
        function _get(Map storage map, bytes32 key, string memory errorMessage) private view returns (bytes32) {
            uint256 keyIndex = map._indexes[key];
            require(keyIndex != 0, errorMessage); // Equivalent to contains(map, key)
            return map._entries[keyIndex - 1]._value; // All indexes are 1-based
        }
        // UintToAddressMap
        struct UintToAddressMap {
            Map _inner;
        }
        /**
         * @dev Adds a key-value pair to a map, or updates the value for an existing
         * key. O(1).
         *
         * Returns true if the key was added to the map, that is if it was not
         * already present.
         */
        function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
            return _set(map._inner, bytes32(key), bytes32(uint256(value)));
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the key was removed from the map, that is if it was present.
         */
        function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
            return _remove(map._inner, bytes32(key));
        }
        /**
         * @dev Returns true if the key is in the map. O(1).
         */
        function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
            return _contains(map._inner, bytes32(key));
        }
        /**
         * @dev Returns the number of elements in the map. O(1).
         */
        function length(UintToAddressMap storage map) internal view returns (uint256) {
            return _length(map._inner);
        }
       /**
        * @dev Returns the element stored at position `index` in the set. O(1).
        * Note that there are no guarantees on the ordering of values inside the
        * array, and it may change when more values are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
            (bytes32 key, bytes32 value) = _at(map._inner, index);
            return (uint256(key), address(uint256(value)));
        }
        /**
         * @dev Returns the value associated with `key`.  O(1).
         *
         * Requirements:
         *
         * - `key` must be in the map.
         */
        function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
            return address(uint256(_get(map._inner, bytes32(key))));
        }
        /**
         * @dev Same as {get}, with a custom error message when `key` is not in the map.
         */
        function get(UintToAddressMap storage map, uint256 key, string memory errorMessage) internal view returns (address) {
            return address(uint256(_get(map._inner, bytes32(key), errorMessage)));
        }
    }
    pragma solidity ^0.6.0;
    /**
     * @dev Library for managing
     * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
     * types.
     *
     * Sets have the following properties:
     *
     * - Elements are added, removed, and checked for existence in constant time
     * (O(1)).
     * - Elements are enumerated in O(n). No guarantees are made on the ordering.
     *
     * ```
     * contract Example {
     *     // Add the library methods
     *     using EnumerableSet for EnumerableSet.AddressSet;
     *
     *     // Declare a set state variable
     *     EnumerableSet.AddressSet private mySet;
     * }
     * ```
     *
     * As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256`
     * (`UintSet`) are supported.
     */
    library EnumerableSet {
        // To implement this library for multiple types with as little code
        // repetition as possible, we write it in terms of a generic Set type with
        // bytes32 values.
        // The Set implementation uses private functions, and user-facing
        // implementations (such as AddressSet) are just wrappers around the
        // underlying Set.
        // This means that we can only create new EnumerableSets for types that fit
        // in bytes32.
        struct Set {
            // Storage of set values
            bytes32[] _values;
            // Position of the value in the `values` array, plus 1 because index 0
            // means a value is not in the set.
            mapping (bytes32 => uint256) _indexes;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function _add(Set storage set, bytes32 value) private returns (bool) {
            if (!_contains(set, value)) {
                set._values.push(value);
                // The value is stored at length-1, but we add 1 to all indexes
                // and use 0 as a sentinel value
                set._indexes[value] = set._values.length;
                return true;
            } else {
                return false;
            }
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function _remove(Set storage set, bytes32 value) private returns (bool) {
            // We read and store the value's index to prevent multiple reads from the same storage slot
            uint256 valueIndex = set._indexes[value];
            if (valueIndex != 0) { // Equivalent to contains(set, value)
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                // the array, and then remove the last element (sometimes called as 'swap and pop').
                // This modifies the order of the array, as noted in {at}.
                uint256 toDeleteIndex = valueIndex - 1;
                uint256 lastIndex = set._values.length - 1;
                // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs
                // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
                bytes32 lastvalue = set._values[lastIndex];
                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastvalue;
                // Update the index for the moved value
                set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based
                // Delete the slot where the moved value was stored
                set._values.pop();
                // Delete the index for the deleted slot
                delete set._indexes[value];
                return true;
            } else {
                return false;
            }
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function _contains(Set storage set, bytes32 value) private view returns (bool) {
            return set._indexes[value] != 0;
        }
        /**
         * @dev Returns the number of values on the set. O(1).
         */
        function _length(Set storage set) private view returns (uint256) {
            return set._values.length;
        }
       /**
        * @dev Returns the value stored at position `index` in the set. O(1).
        *
        * Note that there are no guarantees on the ordering of values inside the
        * array, and it may change when more values are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
            require(set._values.length > index, "EnumerableSet: index out of bounds");
            return set._values[index];
        }
        // AddressSet
        struct AddressSet {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(AddressSet storage set, address value) internal returns (bool) {
            return _add(set._inner, bytes32(uint256(value)));
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(AddressSet storage set, address value) internal returns (bool) {
            return _remove(set._inner, bytes32(uint256(value)));
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(AddressSet storage set, address value) internal view returns (bool) {
            return _contains(set._inner, bytes32(uint256(value)));
        }
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(AddressSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
       /**
        * @dev Returns the value stored at position `index` in the set. O(1).
        *
        * Note that there are no guarantees on the ordering of values inside the
        * array, and it may change when more values are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function at(AddressSet storage set, uint256 index) internal view returns (address) {
            return address(uint256(_at(set._inner, index)));
        }
        // UintSet
        struct UintSet {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(UintSet storage set, uint256 value) internal returns (bool) {
            return _add(set._inner, bytes32(value));
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(UintSet storage set, uint256 value) internal returns (bool) {
            return _remove(set._inner, bytes32(value));
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(UintSet storage set, uint256 value) internal view returns (bool) {
            return _contains(set._inner, bytes32(value));
        }
        /**
         * @dev Returns the number of values on the set. O(1).
         */
        function length(UintSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
       /**
        * @dev Returns the value stored at position `index` in the set. O(1).
        *
        * Note that there are no guarantees on the ordering of values inside the
        * array, and it may change when more values are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function at(UintSet storage set, uint256 index) internal view returns (uint256) {
            return uint256(_at(set._inner, index));
        }
    }
    pragma solidity ^0.6.0;
    import "../GSN/Context.sol";
    import "../Initializable.sol";
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    contract PausableUpgradeSafe is Initializable, ContextUpgradeSafe {
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
        bool private _paused;
        /**
         * @dev Initializes the contract in unpaused state.
         */
        function __Pausable_init() internal initializer {
            __Context_init_unchained();
            __Pausable_init_unchained();
        }
        function __Pausable_init_unchained() internal initializer {
            _paused = false;
        }
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view returns (bool) {
            return _paused;
        }
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         */
        modifier whenNotPaused() {
            require(!_paused, "Pausable: paused");
            _;
        }
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         */
        modifier whenPaused() {
            require(_paused, "Pausable: not paused");
            _;
        }
        /**
         * @dev Triggers stopped state.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
        /**
         * @dev Returns to normal state.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
        uint256[49] private __gap;
    }
    pragma solidity ^0.6.0;
    import "../Initializable.sol";
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     */
    contract ReentrancyGuardUpgradeSafe is Initializable {
        bool private _notEntered;
        function __ReentrancyGuard_init() internal initializer {
            __ReentrancyGuard_init_unchained();
        }
        function __ReentrancyGuard_init_unchained() internal initializer {
            // Storing an initial non-zero value makes deployment a bit more
            // expensive, but in exchange the refund on every call to nonReentrant
            // will be lower in amount. Since refunds are capped to a percetange of
            // the total transaction's gas, it is best to keep them low in cases
            // like this one, to increase the likelihood of the full refund coming
            // into effect.
            _notEntered = true;
        }
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and make it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            // On the first call to nonReentrant, _notEntered will be true
            require(_notEntered, "ReentrancyGuard: reentrant call");
            // Any calls to nonReentrant after this point will fail
            _notEntered = false;
            _;
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            _notEntered = true;
        }
        uint256[49] private __gap;
    }
    pragma solidity ^0.6.0;
    /**
     * @dev String operations.
     */
    library Strings {
        /**
         * @dev Converts a `uint256` to its ASCII `string` representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            // Inspired by OraclizeAPI's implementation - MIT licence
            // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
            if (value == 0) {
                return "0";
            }
            uint256 temp = value;
            uint256 digits;
            while (temp != 0) {
                digits++;
                temp /= 10;
            }
            bytes memory buffer = new bytes(digits);
            uint256 index = digits - 1;
            temp = value;
            while (temp != 0) {
                buffer[index--] = byte(uint8(48 + temp % 10));
                temp /= 10;
            }
            return string(buffer);
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.2 <0.8.0;
    /**
     * @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) {
            // This method relies on extcodesize, which returns 0 for contracts in
            // construction, since the code is only stored at the end of the
            // constructor execution.
            uint256 size;
            // solhint-disable-next-line no-inline-assembly
            assembly { size := extcodesize(account) }
            return size > 0;
        }
        /**
         * @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");
            require(isContract(target), "Address: call to non-contract");
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory returndata) = target.call{ value: value }(data);
            return _verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
            require(isContract(target), "Address: static call to non-contract");
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory returndata) = target.staticcall(data);
            return _verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
            require(isContract(target), "Address: delegate call to non-contract");
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return _verifyCallResult(success, returndata, errorMessage);
        }
        function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
            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);
                }
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // solhint-disable
    /*
      Vendored from @openzeppelin/[email protected]
      Alterations:
       * Make supportsInterface virtual so it can be overriden by inheriting contracts
    */
    pragma solidity ^0.6.0;
    import "../interfaces/openzeppelin/IERC165.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";
    /**
     * @dev Implementation of the {IERC165} interface.
     *
     * Contracts may inherit from this and call {_registerInterface} to declare
     * their support of an interface.
     */
    contract ERC165UpgradeSafe is Initializable, IERC165 {
      /*
       * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
       */
      bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
      /**
       * @dev Mapping of interface ids to whether or not it's supported.
       */
      mapping(bytes4 => bool) private _supportedInterfaces;
      function __ERC165_init() internal initializer {
        __ERC165_init_unchained();
      }
      function __ERC165_init_unchained() internal initializer {
        // Derived contracts need only register support for their own interfaces,
        // we register support for ERC165 itself here
        _registerInterface(_INTERFACE_ID_ERC165);
      }
      /**
       * @dev See {IERC165-supportsInterface}.
       *
       * Time complexity O(1), guaranteed to always use less than 30 000 gas.
       */
      function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return _supportedInterfaces[interfaceId];
      }
      /**
       * @dev Registers the contract as an implementer of the interface defined by
       * `interfaceId`. Support of the actual ERC165 interface is automatic and
       * registering its interface id is not required.
       *
       * See {IERC165-supportsInterface}.
       *
       * Requirements:
       *
       * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
       */
      function _registerInterface(bytes4 interfaceId) internal virtual {
        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
        _supportedInterfaces[interfaceId] = true;
      }
      uint256[49] private __gap;
    }
    // SPDX-License-Identifier: MIT
    // solhint-disable
    /*
      Vendored from @openzeppelin/[email protected]
      Alterations:
       * Use vendored ERC165 with virtual supportsInterface
    */
    pragma solidity ^0.6.0;
    import "@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol";
    import "../interfaces/openzeppelin/IERC721.sol";
    import "../interfaces/openzeppelin/IERC721Metadata.sol";
    import "../interfaces/openzeppelin/IERC721Enumerable.sol";
    import "../interfaces/openzeppelin/IERC721Receiver.sol";
    import "./ERC165.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/utils/EnumerableSet.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/utils/EnumerableMap.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/utils/Strings.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";
    /**
     * @title ERC721 Non-Fungible Token Standard basic implementation
     * @dev see https://eips.ethereum.org/EIPS/eip-721
     */
    contract ERC721UpgradeSafe is
      Initializable,
      ContextUpgradeSafe,
      ERC165UpgradeSafe,
      IERC721,
      IERC721Metadata,
      IERC721Enumerable
    {
      using SafeMath for uint256;
      using Address for address;
      using EnumerableSet for EnumerableSet.UintSet;
      using EnumerableMap for EnumerableMap.UintToAddressMap;
      using Strings for uint256;
      // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
      // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
      bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
      // Mapping from holder address to their (enumerable) set of owned tokens
      mapping(address => EnumerableSet.UintSet) private _holderTokens;
      // Enumerable mapping from token ids to their owners
      EnumerableMap.UintToAddressMap private _tokenOwners;
      // Mapping from token ID to approved address
      mapping(uint256 => address) private _tokenApprovals;
      // Mapping from owner to operator approvals
      mapping(address => mapping(address => bool)) private _operatorApprovals;
      // Token name
      string private _name;
      // Token symbol
      string private _symbol;
      // Optional mapping for token URIs
      mapping(uint256 => string) private _tokenURIs;
      // Base URI
      string private _baseURI;
      /*
       *     bytes4(keccak256('balanceOf(address)')) == 0x70a08231
       *     bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
       *     bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3
       *     bytes4(keccak256('getApproved(uint256)')) == 0x081812fc
       *     bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465
       *     bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5
       *     bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
       *     bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
       *     bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde
       *
       *     => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
       *        0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
       */
      bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
      /*
       *     bytes4(keccak256('name()')) == 0x06fdde03
       *     bytes4(keccak256('symbol()')) == 0x95d89b41
       *     bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
       *
       *     => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
       */
      bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
      /*
       *     bytes4(keccak256('totalSupply()')) == 0x18160ddd
       *     bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
       *     bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
       *
       *     => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
       */
      bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
      function __ERC721_init(string memory name, string memory symbol) internal initializer {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __ERC721_init_unchained(name, symbol);
      }
      function __ERC721_init_unchained(string memory name, string memory symbol) internal initializer {
        _name = name;
        _symbol = symbol;
        // register the supported interfaces to conform to ERC721 via ERC165
        _registerInterface(_INTERFACE_ID_ERC721);
        _registerInterface(_INTERFACE_ID_ERC721_METADATA);
        _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
      }
      /**
       * @dev Gets the balance of the specified address.
       * @param owner address to query the balance of
       * @return uint256 representing the amount owned by the passed address
       */
      function balanceOf(address owner) public view override returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        return _holderTokens[owner].length();
      }
      /**
       * @dev Gets the owner of the specified token ID.
       * @param tokenId uint256 ID of the token to query the owner of
       * @return address currently marked as the owner of the given token ID
       */
      function ownerOf(uint256 tokenId) public view override returns (address) {
        return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token");
      }
      /**
       * @dev Gets the token name.
       * @return string representing the token name
       */
      function name() public view override returns (string memory) {
        return _name;
      }
      /**
       * @dev Gets the token symbol.
       * @return string representing the token symbol
       */
      function symbol() public view override returns (string memory) {
        return _symbol;
      }
      /**
       * @dev Returns the URI for a given token ID. May return an empty string.
       *
       * If a base URI is set (via {_setBaseURI}), it is added as a prefix to the
       * token's own URI (via {_setTokenURI}).
       *
       * If there is a base URI but no token URI, the token's ID will be used as
       * its URI when appending it to the base URI. This pattern for autogenerated
       * token URIs can lead to large gas savings.
       *
       * .Examples
       * |===
       * |`_setBaseURI()` |`_setTokenURI()` |`tokenURI()`
       * | ""
       * | ""
       * | ""
       * | ""
       * | "token.uri/123"
       * | "token.uri/123"
       * | "token.uri/"
       * | "123"
       * | "token.uri/123"
       * | "token.uri/"
       * | ""
       * | "token.uri/<tokenId>"
       * |===
       *
       * Requirements:
       *
       * - `tokenId` must exist.
       */
      function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory _tokenURI = _tokenURIs[tokenId];
        // If there is no base URI, return the token URI.
        if (bytes(_baseURI).length == 0) {
          return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
          return string(abi.encodePacked(_baseURI, _tokenURI));
        }
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(_baseURI, tokenId.toString()));
      }
      /**
       * @dev Returns the base URI set via {_setBaseURI}. This will be
       * automatically added as a prefix in {tokenURI} to each token's URI, or
       * to the token ID if no specific URI is set for that token ID.
       */
      function baseURI() public view returns (string memory) {
        return _baseURI;
      }
      /**
       * @dev Gets the token ID at a given index of the tokens list of the requested owner.
       * @param owner address owning the tokens list to be accessed
       * @param index uint256 representing the index to be accessed of the requested tokens list
       * @return uint256 token ID at the given index of the tokens list owned by the requested address
       */
      function tokenOfOwnerByIndex(
        address owner,
        uint256 index
      ) public view override returns (uint256) {
        return _holderTokens[owner].at(index);
      }
      /**
       * @dev Gets the total amount of tokens stored by the contract.
       * @return uint256 representing the total amount of tokens
       */
      function totalSupply() public view override returns (uint256) {
        // _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds
        return _tokenOwners.length();
      }
      /**
       * @dev Gets the token ID at a given index of all the tokens in this contract
       * Reverts if the index is greater or equal to the total number of tokens.
       * @param index uint256 representing the index to be accessed of the tokens list
       * @return uint256 token ID at the given index of the tokens list
       */
      function tokenByIndex(uint256 index) public view override returns (uint256) {
        (uint256 tokenId, ) = _tokenOwners.at(index);
        return tokenId;
      }
      /**
       * @dev Approves another address to transfer the given token ID
       * The zero address indicates there is no approved address.
       * There can only be one approved address per token at a given time.
       * Can only be called by the token owner or an approved operator.
       * @param to address to be approved for the given token ID
       * @param tokenId uint256 ID of the token to be approved
       */
      function approve(address to, uint256 tokenId) public virtual override {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");
        require(
          _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
          "ERC721: approve caller is not owner nor approved for all"
        );
        _approve(to, tokenId);
      }
      /**
       * @dev Gets the approved address for a token ID, or zero if no address set
       * Reverts if the token ID does not exist.
       * @param tokenId uint256 ID of the token to query the approval of
       * @return address currently approved for the given token ID
       */
      function getApproved(uint256 tokenId) public view override returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");
        return _tokenApprovals[tokenId];
      }
      /**
       * @dev Sets or unsets the approval of a given operator
       * An operator is allowed to transfer all tokens of the sender on their behalf.
       * @param operator operator address to set the approval
       * @param approved representing the status of the approval to be set
       */
      function setApprovalForAll(address operator, bool approved) public virtual override {
        require(operator != _msgSender(), "ERC721: approve to caller");
        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
      }
      /**
       * @dev Tells whether an operator is approved by a given owner.
       * @param owner owner address which you want to query the approval of
       * @param operator operator address which you want to query the approval of
       * @return bool whether the given operator is approved by the given owner
       */
      function isApprovedForAll(address owner, address operator) public view override returns (bool) {
        return _operatorApprovals[owner][operator];
      }
      /**
       * @dev Transfers the ownership of a given token ID to another address.
       * Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
       * Requires the msg.sender to be the owner, approved, or operator.
       * @param from current owner of the token
       * @param to address to receive the ownership of the given token ID
       * @param tokenId uint256 ID of the token to be transferred
       */
      function transferFrom(address from, address to, uint256 tokenId) public virtual override {
        //solhint-disable-next-line max-line-length
        require(
          _isApprovedOrOwner(_msgSender(), tokenId),
          "ERC721: transfer caller is not owner nor approved"
        );
        _transfer(from, to, tokenId);
      }
      /**
       * @dev Safely transfers the ownership of a given token ID to another address
       * If the target address is a contract, it must implement {IERC721Receiver-onERC721Received},
       * which is called upon a safe transfer, and return the magic value
       * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
       * the transfer is reverted.
       * Requires the msg.sender to be the owner, approved, or operator
       * @param from current owner of the token
       * @param to address to receive the ownership of the given token ID
       * @param tokenId uint256 ID of the token to be transferred
       */
      function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
      }
      /**
       * @dev Safely transfers the ownership of a given token ID to another address
       * If the target address is a contract, it must implement {IERC721Receiver-onERC721Received},
       * which is called upon a safe transfer, and return the magic value
       * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
       * the transfer is reverted.
       * Requires the _msgSender() to be the owner, approved, or operator
       * @param from current owner of the token
       * @param to address to receive the ownership of the given token ID
       * @param tokenId uint256 ID of the token to be transferred
       * @param _data bytes data to send along with a safe transfer check
       */
      function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
      ) public virtual override {
        require(
          _isApprovedOrOwner(_msgSender(), tokenId),
          "ERC721: transfer caller is not owner nor approved"
        );
        _safeTransfer(from, to, tokenId, _data);
      }
      /**
       * @dev Safely transfers the ownership of a given token ID to another address
       * If the target address is a contract, it must implement `onERC721Received`,
       * which is called upon a safe transfer, and return the magic value
       * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
       * the transfer is reverted.
       * Requires the msg.sender to be the owner, approved, or operator
       * @param from current owner of the token
       * @param to address to receive the ownership of the given token ID
       * @param tokenId uint256 ID of the token to be transferred
       * @param _data bytes data to send along with a safe transfer check
       */
      function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
      ) internal virtual {
        _transfer(from, to, tokenId);
        require(
          _checkOnERC721Received(from, to, tokenId, _data),
          "ERC721: transfer to non ERC721Receiver implementer"
        );
      }
      /**
       * @dev Returns whether the specified token exists.
       * @param tokenId uint256 ID of the token to query the existence of
       * @return bool whether the token exists
       */
      function _exists(uint256 tokenId) internal view returns (bool) {
        return _tokenOwners.contains(tokenId);
      }
      /**
       * @dev Returns whether the given spender can transfer a given token ID.
       * @param spender address of the spender to query
       * @param tokenId uint256 ID of the token to be transferred
       * @return bool whether the msg.sender is approved for the given token ID,
       * is an operator of the owner, or is the owner of the token
       */
      function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner ||
          getApproved(tokenId) == spender ||
          isApprovedForAll(owner, spender));
      }
      /**
       * @dev Internal function to safely mint a new token.
       * Reverts if the given token ID already exists.
       * If the target address is a contract, it must implement `onERC721Received`,
       * which is called upon a safe transfer, and return the magic value
       * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
       * the transfer is reverted.
       * @param to The address that will own the minted token
       * @param tokenId uint256 ID of the token to be minted
       */
      function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
      }
      /**
       * @dev Internal function to safely mint a new token.
       * Reverts if the given token ID already exists.
       * If the target address is a contract, it must implement `onERC721Received`,
       * which is called upon a safe transfer, and return the magic value
       * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
       * the transfer is reverted.
       * @param to The address that will own the minted token
       * @param tokenId uint256 ID of the token to be minted
       * @param _data bytes data to send along with a safe transfer check
       */
      function _safeMint(address to, uint256 tokenId, bytes memory _data) internal virtual {
        _mint(to, tokenId);
        require(
          _checkOnERC721Received(address(0), to, tokenId, _data),
          "ERC721: transfer to non ERC721Receiver implementer"
        );
      }
      /**
       * @dev Internal function to mint a new token.
       * Reverts if the given token ID already exists.
       * @param to The address that will own the minted token
       * @param tokenId uint256 ID of the token to be minted
       */
      function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");
        _beforeTokenTransfer(address(0), to, tokenId);
        _holderTokens[to].add(tokenId);
        _tokenOwners.set(tokenId, to);
        emit Transfer(address(0), to, tokenId);
      }
      /**
       * @dev Internal function to burn a specific token.
       * Reverts if the token does not exist.
       * @param tokenId uint256 ID of the token being burned
       */
      function _burn(uint256 tokenId) internal virtual {
        address owner = ownerOf(tokenId);
        _beforeTokenTransfer(owner, address(0), tokenId);
        // Clear approvals
        _approve(address(0), tokenId);
        // Clear metadata (if any)
        if (bytes(_tokenURIs[tokenId]).length != 0) {
          delete _tokenURIs[tokenId];
        }
        _holderTokens[owner].remove(tokenId);
        _tokenOwners.remove(tokenId);
        emit Transfer(owner, address(0), tokenId);
      }
      /**
       * @dev Internal function to transfer ownership of a given token ID to another address.
       * As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
       * @param from current owner of the token
       * @param to address to receive the ownership of the given token ID
       * @param tokenId uint256 ID of the token to be transferred
       */
      function _transfer(address from, address to, uint256 tokenId) internal virtual {
        require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");
        _beforeTokenTransfer(from, to, tokenId);
        // Clear approvals from the previous owner
        _approve(address(0), tokenId);
        _holderTokens[from].remove(tokenId);
        _holderTokens[to].add(tokenId);
        _tokenOwners.set(tokenId, to);
        emit Transfer(from, to, tokenId);
      }
      /**
       * @dev Internal function to set the token URI for a given token.
       *
       * Reverts if the token ID does not exist.
       *
       * TIP: If all token IDs share a prefix (for example, if your URIs look like
       * `https://api.myproject.com/token/<id>`), use {_setBaseURI} to store
       * it and save gas.
       */
      function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
      }
      /**
       * @dev Internal function to set the base URI for all token IDs. It is
       * automatically added as a prefix to the value returned in {tokenURI},
       * or to the token ID if {tokenURI} is empty.
       */
      function _setBaseURI(string memory baseURI_) internal virtual {
        _baseURI = baseURI_;
      }
      /**
       * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
       * The call is not executed if the target address is not a contract.
       *
       * @param from address representing the previous owner of the given token ID
       * @param to target address that will receive the tokens
       * @param tokenId uint256 ID of the token to be transferred
       * @param _data bytes optional data to send along with the call
       * @return bool whether the call correctly returned the expected magic value
       */
      function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
      ) private returns (bool) {
        if (!to.isContract()) {
          return true;
        }
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = to.call(
          abi.encodeWithSelector(
            IERC721Receiver(to).onERC721Received.selector,
            _msgSender(),
            from,
            tokenId,
            _data
          )
        );
        if (!success) {
          if (returndata.length > 0) {
            // solhint-disable-next-line no-inline-assembly
            assembly {
              let returndata_size := mload(returndata)
              revert(add(32, returndata), returndata_size)
            }
          } else {
            revert("ERC721: transfer to non ERC721Receiver implementer");
          }
        } else {
          bytes4 retval = abi.decode(returndata, (bytes4));
          return (retval == _ERC721_RECEIVED);
        }
      }
      function _approve(address to, uint256 tokenId) private {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
      }
      /**
       * @dev Hook that is called before any token transfer. This includes minting
       * and burning.
       *
       * Calling conditions:
       *
       * - when `from` and `to` are both non-zero, ``from``'s `tokenId` will be
       * transferred to `to`.
       * - when `from` is zero, `tokenId` will be minted for `to`.
       * - when `to` is zero, ``from``'s `tokenId` 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 tokenId) internal virtual {}
      uint256[41] private __gap;
    }
    // SPDX-License-Identifier: MIT
    // solhint-disable
    /*
      Vendored from @openzeppelin/[email protected]
      Alterations:
       * Use vendored ERC721, which inherits from vendored ERC165 with virtual supportsInterface
    */
    pragma solidity ^0.6.0;
    import "./ERC721.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/utils/Pausable.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";
    /**
     * @dev ERC721 token with pausable token transfers, minting and burning.
     *
     * Useful for scenarios such as preventing trades until the end of an evaluation
     * period, or having an emergency switch for freezing all token transfers in the
     * event of a large bug.
     */
    abstract contract ERC721PausableUpgradeSafe is
      Initializable,
      ERC721UpgradeSafe,
      PausableUpgradeSafe
    {
      function __ERC721Pausable_init() internal initializer {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __Pausable_init_unchained();
        __ERC721Pausable_init_unchained();
      }
      function __ERC721Pausable_init_unchained() internal initializer {}
      /**
       * @dev See {ERC721-_beforeTokenTransfer}.
       *
       * Requirements:
       *
       * - the contract must not be paused.
       */
      function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
      ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);
        require(!paused(), "ERC721Pausable: token transfer while paused");
      }
      uint256[50] private __gap;
    }
    // SPDX-License-Identifier: MIT
    // solhint-disable
    /*
      This is copied from OZ preset: https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/release-v3.0.0/contracts/presets/ERC721PresetMinterPauserAutoId.sol
      Alterations:
       * Make the counter public, so that we can use it in our custom mint function
       * Removed ERC721Burnable parent contract, but added our own custom burn function.
       * Removed original "mint" function, because we have a custom one.
       * Removed default initialization functions, because they set msg.sender as the owner, which
         we do not want, because we use a deployer account, which is separate from the protocol owner.
    */
    pragma solidity 0.6.12;
    import "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/utils/Counters.sol";
    import "./ERC721Pausable.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";
    /**
     * @dev {ERC721} token, including:
     *
     *  - ability for holders to burn (destroy) their tokens
     *  - a minter role that allows for token minting (creation)
     *  - a pauser role that allows to stop all token transfers
     *  - token ID and URI autogeneration
     *
     * This contract uses {AccessControl} to lock permissioned functions using the
     * different roles - head to its documentation for details.
     *
     * The account that deploys the contract will be granted the minter and pauser
     * roles, as well as the default admin role, which will let it grant both minter
     * and pauser roles to aother accounts
     */
    contract ERC721PresetMinterPauserAutoIdUpgradeSafe is
      Initializable,
      ContextUpgradeSafe,
      AccessControlUpgradeSafe,
      ERC721PausableUpgradeSafe
    {
      using Counters for Counters.Counter;
      bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
      bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
      Counters.Counter public _tokenIdTracker;
      /**
       * @dev Pauses all token transfers.
       *
       * See {ERC721Pausable} and {Pausable-_pause}.
       *
       * Requirements:
       *
       * - the caller must have the `PAUSER_ROLE`.
       */
      function pause() public {
        require(
          hasRole(PAUSER_ROLE, _msgSender()),
          "ERC721PresetMinterPauserAutoId: must have pauser role to pause"
        );
        _pause();
      }
      /**
       * @dev Unpauses all token transfers.
       *
       * See {ERC721Pausable} and {Pausable-_unpause}.
       *
       * Requirements:
       *
       * - the caller must have the `PAUSER_ROLE`.
       */
      function unpause() public {
        require(
          hasRole(PAUSER_ROLE, _msgSender()),
          "ERC721PresetMinterPauserAutoId: must have pauser role to unpause"
        );
        _unpause();
      }
      function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
      ) internal virtual override(ERC721PausableUpgradeSafe) {
        super._beforeTokenTransfer(from, to, tokenId);
      }
      uint256[49] private __gap;
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import {ITranchedPool} from "./ITranchedPool.sol";
    interface IBackerRewards {
      struct BackerRewardsTokenInfo {
        uint256 rewardsClaimed; // gfi claimed
        uint256 accRewardsPerPrincipalDollarAtMint; // Pool's accRewardsPerPrincipalDollar at PoolToken mint()
      }
      struct BackerRewardsInfo {
        uint256 accRewardsPerPrincipalDollar; // accumulator gfi per interest dollar
      }
      /// @notice Staking rewards parameters relevant to a TranchedPool
      struct StakingRewardsPoolInfo {
        // @notice the value `StakingRewards.accumulatedRewardsPerToken()` at the last checkpoint
        uint256 accumulatedRewardsPerTokenAtLastCheckpoint;
        // @notice last time the rewards info was updated
        //
        // we need this in order to know how much to pro rate rewards after the term is over.
        uint256 lastUpdateTime;
        // @notice staking rewards parameters for each slice of the tranched pool
        StakingRewardsSliceInfo[] slicesInfo;
      }
      /// @notice Staking rewards paramters relevant to a TranchedPool slice
      struct StakingRewardsSliceInfo {
        // @notice fidu share price when the slice is first drawn down
        //
        // we need to save this to calculate what an equivalent position in
        // the senior pool would be at the time the slice is downdown
        uint256 fiduSharePriceAtDrawdown;
        // @notice the amount of principal deployed at the last checkpoint
        //
        // we use this to calculate the amount of principal that should
        // acctually accrue rewards during between the last checkpoint and
        // and subsequent updates
        uint256 principalDeployedAtLastCheckpoint;
        // @notice the value of StakingRewards.accumulatedRewardsPerToken() at time of drawdown
        //
        // we need to keep track of this to use this as a base value to accumulate rewards
        // for tokens. If the token has never claimed staking rewards, we use this value
        // and the current staking rewards accumulator
        uint256 accumulatedRewardsPerTokenAtDrawdown;
        // @notice amount of rewards per token accumulated over the lifetime of the slice that a backer
        //          can claim
        uint256 accumulatedRewardsPerTokenAtLastCheckpoint;
        // @notice the amount of rewards per token accumulated over the lifetime of the slice
        //
        // this value is "unrealized" because backers will be unable to claim against this value.
        // we keep this value so that we can always accumulate rewards for the amount of capital
        // deployed at any point in time, but not allow backers to withdraw them until a payment
        // is made. For example: we want to accumulate rewards when a backer does a drawdown. but
        // a backer shouldn't be allowed to claim rewards until a payment is made.
        //
        // this value is scaled depending on the current proportion of capital currently deployed
        // in the slice. For example, if the staking rewards contract accrued 10 rewards per token
        // between the current checkpoint and a new update, and only 20% of the capital was deployed
        // during that period, we would accumulate 2 (10 * 20%) rewards.
        uint256 unrealizedAccumulatedRewardsPerTokenAtLastCheckpoint;
      }
      /// @notice Staking rewards parameters relevant to a PoolToken
      struct StakingRewardsTokenInfo {
        // @notice the amount of rewards accumulated the last time a token's rewards were withdrawn
        uint256 accumulatedRewardsPerTokenAtLastWithdraw;
      }
      /// @notice total amount of GFI rewards available, times 1e18
      function totalRewards() external view returns (uint256);
      /// @notice interest $ eligible for gfi rewards, times 1e18
      function maxInterestDollarsEligible() external view returns (uint256);
      /// @notice counter of total interest repayments, times 1e6
      function totalInterestReceived() external view returns (uint256);
      /// @notice totalRewards/totalGFISupply * 100, times 1e18
      function totalRewardPercentOfTotalGFI() external view returns (uint256);
      /// @notice Get backer rewards metadata for a pool token
      function getTokenInfo(uint256 poolTokenId) external view returns (BackerRewardsTokenInfo memory);
      /// @notice Get backer staking rewards metadata for a pool token
      function getStakingRewardsTokenInfo(
        uint256 poolTokenId
      ) external view returns (StakingRewardsTokenInfo memory);
      /// @notice Get backer staking rewards for a pool
      function getBackerStakingRewardsPoolInfo(
        ITranchedPool pool
      ) external view returns (StakingRewardsPoolInfo memory);
      /// @notice Calculates the accRewardsPerPrincipalDollar for a given pool,
      ///   when a interest payment is received by the protocol
      /// @param _interestPaymentAmount Atomic usdc amount of the interest payment
      function allocateRewards(uint256 _interestPaymentAmount) external;
      /// @notice callback for TranchedPools when they drawdown
      /// @param sliceIndex index of the tranched pool slice
      /// @dev initializes rewards info for the calling TranchedPool if it's the first
      ///  drawdown for the given slice
      function onTranchedPoolDrawdown(uint256 sliceIndex) external;
      /// @notice When a pool token is minted for multiple drawdowns,
      ///   set accRewardsPerPrincipalDollarAtMint to the current accRewardsPerPrincipalDollar price
      /// @param poolAddress Address of the pool associated with the pool token
      /// @param tokenId Pool token id
      function setPoolTokenAccRewardsPerPrincipalDollarAtMint(
        address poolAddress,
        uint256 tokenId
      ) external;
      /// @notice PoolToken request to withdraw all allocated rewards
      /// @param tokenId Pool token id
      /// @return amount of rewards withdrawn
      function withdraw(uint256 tokenId) external returns (uint256);
      /**
       * @notice Set BackerRewards and BackerStakingRewards metadata for tokens created by a pool token split.
       * @param originalBackerRewardsTokenInfo backer rewards info for the pool token that was split
       * @param originalStakingRewardsTokenInfo backer staking rewards info for the pool token that was split
       * @param newTokenId id of one of the tokens in the split
       * @param newRewardsClaimed rewardsClaimed value for the new token.
       */
      function setBackerAndStakingRewardsTokenInfoOnSplit(
        BackerRewardsTokenInfo memory originalBackerRewardsTokenInfo,
        StakingRewardsTokenInfo memory originalStakingRewardsTokenInfo,
        uint256 newTokenId,
        uint256 newRewardsClaimed
      ) external;
      /**
       * @notice Calculate the gross available gfi rewards for a PoolToken
       * @param tokenId Pool token id
       * @return The amount of GFI claimable
       */
      function poolTokenClaimableRewards(uint256 tokenId) external view returns (uint256);
      /// @notice Clear all BackerRewards and StakingRewards associated data for `tokenId`
      function clearTokenInfo(uint256 tokenId) external;
    }
    // SPDX-License-Identifier: MIT
    // Taken from https://github.com/compound-finance/compound-protocol/blob/master/contracts/CTokenInterfaces.sol
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import "./IERC20withDec.sol";
    interface ICUSDCContract is IERC20withDec {
      /*** User Interface ***/
      function mint(uint256 mintAmount) external returns (uint256);
      function redeem(uint256 redeemTokens) external returns (uint256);
      function redeemUnderlying(uint256 redeemAmount) external returns (uint256);
      function borrow(uint256 borrowAmount) external returns (uint256);
      function repayBorrow(uint256 repayAmount) external returns (uint256);
      function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns (uint256);
      function liquidateBorrow(
        address borrower,
        uint256 repayAmount,
        address cTokenCollateral
      ) external returns (uint256);
      function getAccountSnapshot(
        address account
      ) external view returns (uint256, uint256, uint256, uint256);
      function balanceOfUnderlying(address owner) external returns (uint256);
      function exchangeRateCurrent() external returns (uint256);
      /*** Admin Functions ***/
      function _addReserves(uint256 addAmount) external returns (uint256);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    interface ICreditLine {
      function borrower() external view returns (address);
      function limit() external view returns (uint256);
      function maxLimit() external view returns (uint256);
      function interestApr() external view returns (uint256);
      function paymentPeriodInDays() external view returns (uint256);
      function principalGracePeriodInDays() external view returns (uint256);
      function termInDays() external view returns (uint256);
      function lateFeeApr() external view returns (uint256);
      function isLate() external view returns (bool);
      function withinPrincipalGracePeriod() external view returns (bool);
      // Accounting variables
      function balance() external view returns (uint256);
      function interestOwed() external view returns (uint256);
      function principalOwed() external view returns (uint256);
      function termEndTime() external view returns (uint256);
      function nextDueTime() external view returns (uint256);
      function interestAccruedAsOf() external view returns (uint256);
      function lastFullPaymentTime() external view returns (uint256);
    }
    // SPDX-License-Identifier: MIT
    // Taken from https://github.com/compound-finance/compound-protocol/blob/master/contracts/CTokenInterfaces.sol
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    interface ICurveLP {
      function coins(uint256) external view returns (address);
      function token() external view returns (address);
      function calc_token_amount(uint256[2] calldata amounts) external view returns (uint256);
      function lp_price() external view returns (uint256);
      function add_liquidity(
        uint256[2] calldata amounts,
        uint256 min_mint_amount,
        bool use_eth,
        address receiver
      ) external returns (uint256);
      function remove_liquidity(
        uint256 _amount,
        uint256[2] calldata min_amounts
      ) external returns (uint256);
      function remove_liquidity_one_coin(
        uint256 token_amount,
        uint256 i,
        uint256 min_amount
      ) external returns (uint256);
      function get_dy(uint256 i, uint256 j, uint256 dx) external view returns (uint256);
      function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);
      function balances(uint256 arg0) external view returns (uint256);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import {IERC20} from "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol";
    /*
    Only addition is the `decimals` function, which we need, and which both our Fidu and USDC use, along with most ERC20's.
    */
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20withDec is IERC20 {
      /**
       * @dev Returns the number of decimals used for the token
       */
      function decimals() external view returns (uint8);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    interface IERC2981 {
      /// @notice Called with the sale price to determine how much royalty
      //          is owed and to whom.
      /// @param _tokenId - the NFT asset queried for royalty information
      /// @param _salePrice - the sale price of the NFT asset specified by _tokenId
      /// @return receiver - address of who should be sent the royalty payment
      /// @return royaltyAmount - the royalty payment amount for _salePrice
      function royaltyInfo(
        uint256 _tokenId,
        uint256 _salePrice
      ) external view returns (address receiver, uint256 royaltyAmount);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import "./IERC20withDec.sol";
    interface IFidu is IERC20withDec {
      function mintTo(address to, uint256 amount) external;
      function burnFrom(address to, uint256 amount) external;
      function renounceRole(bytes32 role, address account) external;
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    abstract contract IGo {
      uint256 public constant ID_TYPE_0 = 0;
      uint256 public constant ID_TYPE_1 = 1;
      uint256 public constant ID_TYPE_2 = 2;
      uint256 public constant ID_TYPE_3 = 3;
      uint256 public constant ID_TYPE_4 = 4;
      uint256 public constant ID_TYPE_5 = 5;
      uint256 public constant ID_TYPE_6 = 6;
      uint256 public constant ID_TYPE_7 = 7;
      uint256 public constant ID_TYPE_8 = 8;
      uint256 public constant ID_TYPE_9 = 9;
      uint256 public constant ID_TYPE_10 = 10;
      /// @notice Returns the address of the UniqueIdentity contract.
      function uniqueIdentity() external virtual returns (address);
      function go(address account) public view virtual returns (bool);
      function goOnlyIdTypes(
        address account,
        uint256[] calldata onlyIdTypes
      ) public view virtual returns (bool);
      /**
       * @notice Returns whether the provided account is go-listed for use of the SeniorPool on the Goldfinch protocol.
       * @param account The account whose go status to obtain
       * @return true if `account` is go listed
       */
      function goSeniorPool(address account) public view virtual returns (bool);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    interface IGoldfinchConfig {
      function getNumber(uint256 index) external returns (uint256);
      function getAddress(uint256 index) external returns (address);
      function setAddress(uint256 index, address newAddress) external returns (address);
      function setNumber(uint256 index, uint256 newNumber) external returns (uint256);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    interface IGoldfinchFactory {
      function createCreditLine() external returns (address);
      function createBorrower(address owner) external returns (address);
      function createPool(
        address _borrower,
        uint256 _juniorFeePercent,
        uint256 _limit,
        uint256 _interestApr,
        uint256 _paymentPeriodInDays,
        uint256 _termInDays,
        uint256 _lateFeeApr,
        uint256[] calldata _allowedUIDTypes
      ) external returns (address);
      function createMigratedPool(
        address _borrower,
        uint256 _juniorFeePercent,
        uint256 _limit,
        uint256 _interestApr,
        uint256 _paymentPeriodInDays,
        uint256 _termInDays,
        uint256 _lateFeeApr,
        uint256[] calldata _allowedUIDTypes
      ) external returns (address);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import "./openzeppelin/IERC721.sol";
    interface IPoolTokens is IERC721 {
      struct TokenInfo {
        address pool;
        uint256 tranche;
        uint256 principalAmount;
        uint256 principalRedeemed;
        uint256 interestRedeemed;
      }
      struct MintParams {
        uint256 principalAmount;
        uint256 tranche;
      }
      struct PoolInfo {
        uint256 totalMinted;
        uint256 totalPrincipalRedeemed;
        bool created;
      }
      /**
       * @notice Called by pool to create a debt position in a particular tranche and amount
       * @param params Struct containing the tranche and the amount
       * @param to The address that should own the position
       * @return tokenId The token ID (auto-incrementing integer across all pools)
       */
      function mint(MintParams calldata params, address to) external returns (uint256);
      /**
       * @notice Redeem principal and interest on a pool token. Called by valid pools as part of their redemption
       *  flow
       * @param tokenId pool token id
       * @param principalRedeemed principal to redeem. This cannot exceed the token's principal amount, and
       *  the redemption cannot cause the pool's total principal redeemed to exceed the pool's total minted
       *  principal
       * @param interestRedeemed interest to redeem.
       */
      function redeem(uint256 tokenId, uint256 principalRedeemed, uint256 interestRedeemed) external;
      /**
       * @notice Withdraw a pool token's principal up to the token's principalAmount. Called by valid pools
       *  as part of their withdraw flow before the pool is locked (i.e. before the principal is committed)
       * @param tokenId pool token id
       * @param principalAmount principal to withdraw
       */
      function withdrawPrincipal(uint256 tokenId, uint256 principalAmount) external;
      /**
       * @notice Burns a specific ERC721 token and removes deletes the token metadata for PoolTokens, BackerReards,
       *  and BackerStakingRewards
       * @param tokenId uint256 id of the ERC721 token to be burned.
       */
      function burn(uint256 tokenId) external;
      /**
       * @notice Called by the GoldfinchFactory to register the pool as a valid pool. Only valid pools can mint/redeem
       * tokens
       * @param newPool The address of the newly created pool
       */
      function onPoolCreated(address newPool) external;
      function getTokenInfo(uint256 tokenId) external view returns (TokenInfo memory);
      function getPoolInfo(address pool) external view returns (PoolInfo memory);
      /// @notice Query if `pool` is a valid pool. A pool is valid if it was created by the Goldfinch Factory
      function validPool(address pool) external view returns (bool);
      function isApprovedOrOwner(address spender, uint256 tokenId) external view returns (bool);
      /**
       * @notice Splits a pool token into two smaller positions. The original token is burned and all
       * its associated data is deleted.
       * @param tokenId id of the token to split.
       * @param newPrincipal1 principal amount for the first token in the split. The principal amount for the
       *  second token in the split is implicitly the original token's principal amount less newPrincipal1
       * @return tokenId1 id of the first token in the split
       * @return tokenId2 id of the second token in the split
       */
      function splitToken(
        uint256 tokenId,
        uint256 newPrincipal1
      ) external returns (uint256 tokenId1, uint256 tokenId2);
      /**
       * @notice Mint event emitted for a new TranchedPool deposit or when an existing pool token is
       *  split
       * @param owner address to which the token was minted
       * @param pool tranched pool that the deposit was in
       * @param tokenId ERC721 tokenId
       * @param amount the deposit amount
       * @param tranche id of the tranche of the deposit
       */
      event TokenMinted(
        address indexed owner,
        address indexed pool,
        uint256 indexed tokenId,
        uint256 amount,
        uint256 tranche
      );
      /**
       * @notice Redeem event emitted when interest and/or principal is redeemed in the token's pool
       * @param owner owner of the pool token
       * @param pool tranched pool that the token belongs to
       * @param principalRedeemed amount of principal redeemed from the pool
       * @param interestRedeemed amount of interest redeemed from the pool
       * @param tranche id of the tranche the token belongs to
       */
      event TokenRedeemed(
        address indexed owner,
        address indexed pool,
        uint256 indexed tokenId,
        uint256 principalRedeemed,
        uint256 interestRedeemed,
        uint256 tranche
      );
      /**
       * @notice Burn event emitted when the token owner/operator manually burns the token or burns
       *  it implicitly by splitting it
       * @param owner owner of the pool token
       * @param pool tranched pool that the token belongs to
       */
      event TokenBurned(address indexed owner, address indexed pool, uint256 indexed tokenId);
      /**
       * @notice Split event emitted when the token owner/operator splits the token
       * @param pool tranched pool to which the orginal and split tokens belong
       * @param tokenId id of the original token that was split
       * @param newTokenId1 id of the first split token
       * @param newPrincipal1 principalAmount of the first split token
       * @param newTokenId2 id of the second split token
       * @param newPrincipal2 principalAmount of the second split token
       */
      event TokenSplit(
        address indexed owner,
        address indexed pool,
        uint256 indexed tokenId,
        uint256 newTokenId1,
        uint256 newPrincipal1,
        uint256 newTokenId2,
        uint256 newPrincipal2
      );
      /**
       * @notice Principal Withdrawn event emitted when a token's principal is withdrawn from the pool
       *  BEFORE the pool's drawdown period
       * @param pool tranched pool of the token
       * @param principalWithdrawn amount of principal withdrawn from the pool
       */
      event TokenPrincipalWithdrawn(
        address indexed owner,
        address indexed pool,
        uint256 indexed tokenId,
        uint256 principalWithdrawn,
        uint256 tranche
      );
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import {ITranchedPool} from "./ITranchedPool.sol";
    import {ISeniorPoolEpochWithdrawals} from "./ISeniorPoolEpochWithdrawals.sol";
    abstract contract ISeniorPool is ISeniorPoolEpochWithdrawals {
      uint256 public sharePrice;
      uint256 public totalLoansOutstanding;
      uint256 public totalWritedowns;
      function deposit(uint256 amount) external virtual returns (uint256 depositShares);
      function depositWithPermit(
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
      ) external virtual returns (uint256 depositShares);
      /**
       * @notice Withdraw `usdcAmount` of USDC, bypassing the epoch withdrawal system. Callable
       * by Zapper only.
       */
      function withdraw(uint256 usdcAmount) external virtual returns (uint256 amount);
      /**
       * @notice Withdraw `fiduAmount` of FIDU converted to USDC at the current share price,
       * bypassing the epoch withdrawal system. Callable by Zapper only
       */
      function withdrawInFidu(uint256 fiduAmount) external virtual returns (uint256 amount);
      function invest(ITranchedPool pool) external virtual returns (uint256);
      function estimateInvestment(ITranchedPool pool) external view virtual returns (uint256);
      function redeem(uint256 tokenId) external virtual;
      function writedown(uint256 tokenId) external virtual;
      function calculateWritedown(
        uint256 tokenId
      ) external view virtual returns (uint256 writedownAmount);
      function sharesOutstanding() external view virtual returns (uint256);
      function assets() external view virtual returns (uint256);
      function getNumShares(uint256 amount) public view virtual returns (uint256);
      event DepositMade(address indexed capitalProvider, uint256 amount, uint256 shares);
      event WithdrawalMade(address indexed capitalProvider, uint256 userAmount, uint256 reserveAmount);
      event InterestCollected(address indexed payer, uint256 amount);
      event PrincipalCollected(address indexed payer, uint256 amount);
      event ReserveFundsCollected(address indexed user, uint256 amount);
      event ReserveSharesCollected(address indexed user, address indexed reserve, uint256 amount);
      event PrincipalWrittenDown(address indexed tranchedPool, int256 amount);
      event InvestmentMadeInSenior(address indexed tranchedPool, uint256 amount);
      event InvestmentMadeInJunior(address indexed tranchedPool, uint256 amount);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    interface ISeniorPoolEpochWithdrawals {
      /**
       * @notice A withdrawal epoch
       * @param endsAt timestamp the epoch ends
       * @param fiduRequested amount of fidu requested in the epoch, including fidu
       *                      carried over from previous epochs
       * @param fiduLiquidated Amount of fidu that was liquidated at the end of this epoch
       * @param usdcAllocated Amount of usdc that was allocated to liquidate fidu.
       *                      Does not consider withdrawal fees.
       */
      struct Epoch {
        uint256 endsAt;
        uint256 fiduRequested;
        uint256 fiduLiquidated;
        uint256 usdcAllocated;
      }
      /**
       * @notice A user's request for withdrawal
       * @param epochCursor id of next epoch the user can liquidate their request
       * @param fiduRequested amount of fidu left to liquidate since last checkpoint
       * @param usdcWithdrawable amount of usdc available for a user to withdraw
       */
      struct WithdrawalRequest {
        uint256 epochCursor;
        uint256 usdcWithdrawable;
        uint256 fiduRequested;
      }
      /**
       * @notice Returns the amount of unallocated usdc in the senior pool, taking into account
       *         usdc that _will_ be allocated to withdrawals when a checkpoint happens
       */
      function usdcAvailable() external view returns (uint256);
      /// @notice Current duration of withdrawal epochs, in seconds
      function epochDuration() external view returns (uint256);
      /// @notice Update epoch duration
      function setEpochDuration(uint256 newEpochDuration) external;
      /// @notice The current withdrawal epoch
      function currentEpoch() external view returns (Epoch memory);
      /// @notice Get request by tokenId. A request is considered active if epochCursor > 0.
      function withdrawalRequest(uint256 tokenId) external view returns (WithdrawalRequest memory);
      /**
       * @notice Submit a request to withdraw `fiduAmount` of FIDU. Request is rejected
       * if caller already owns a request token. A non-transferrable request token is
       * minted to the caller
       * @return tokenId token minted to caller
       */
      function requestWithdrawal(uint256 fiduAmount) external returns (uint256 tokenId);
      /**
       * @notice Add `fiduAmount` FIDU to a withdrawal request for `tokenId`. Caller
       * must own tokenId
       */
      function addToWithdrawalRequest(uint256 fiduAmount, uint256 tokenId) external;
      /**
       * @notice Cancel request for tokenId. The fiduRequested (minus a fee) is returned
       * to the caller. Caller must own tokenId.
       * @return fiduReceived the fidu amount returned to the caller
       */
      function cancelWithdrawalRequest(uint256 tokenId) external returns (uint256 fiduReceived);
      /**
       * @notice Transfer the usdcWithdrawable of request for tokenId to the caller.
       * Caller must own tokenId
       */
      function claimWithdrawalRequest(uint256 tokenId) external returns (uint256 usdcReceived);
      /// @notice Emitted when the epoch duration is changed
      event EpochDurationChanged(uint256 newDuration);
      /// @notice Emitted when a new withdraw request has been created
      event WithdrawalRequested(
        uint256 indexed epochId,
        uint256 indexed tokenId,
        address indexed operator,
        uint256 fiduRequested
      );
      /// @notice Emitted when a user adds to their existing withdraw request
      /// @param epochId epoch that the withdraw was added to
      /// @param tokenId id of token that represents the position being added to
      /// @param operator address that added to the request
      /// @param fiduRequested amount of additional fidu added to request
      event WithdrawalAddedTo(
        uint256 indexed epochId,
        uint256 indexed tokenId,
        address indexed operator,
        uint256 fiduRequested
      );
      /// @notice Emitted when a withdraw request has been canceled
      event WithdrawalCanceled(
        uint256 indexed epochId,
        uint256 indexed tokenId,
        address indexed operator,
        uint256 fiduCanceled,
        uint256 reserveFidu
      );
      /// @notice Emitted when an epoch has been checkpointed
      /// @param epochId id of epoch that ended
      /// @param endTime timestamp the epoch ended
      /// @param fiduRequested amount of FIDU oustanding when the epoch ended
      /// @param usdcAllocated amount of USDC allocated to liquidate FIDU
      /// @param fiduLiquidated amount of FIDU liquidated using `usdcAllocated`
      event EpochEnded(
        uint256 indexed epochId,
        uint256 endTime,
        uint256 fiduRequested,
        uint256 usdcAllocated,
        uint256 fiduLiquidated
      );
      /// @notice Emitted when an epoch could not be finalized and is extended instead
      /// @param epochId id of epoch that was extended
      /// @param newEndTime new epoch end time
      /// @param oldEndTime previous epoch end time
      event EpochExtended(uint256 indexed epochId, uint256 newEndTime, uint256 oldEndTime);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import "./ISeniorPool.sol";
    import "./ITranchedPool.sol";
    abstract contract ISeniorPoolStrategy {
      function getLeverageRatio(ITranchedPool pool) public view virtual returns (uint256);
      /**
       * @notice Determines how much money to invest in the senior tranche based on what is committed to the junior
       * tranche, what is committed to the senior tranche, and a leverage ratio to the junior tranche. Because
       * it takes into account what is already committed to the senior tranche, the value returned by this
       * function can be used "idempotently" to achieve the investment target amount without exceeding that target.
       * @param seniorPool The senior pool to invest from
       * @param pool The tranched pool to invest into (as the senior)
       * @return amount of money to invest into the tranched pool's senior tranche, from the senior pool
       */
      function invest(
        ISeniorPool seniorPool,
        ITranchedPool pool
      ) public view virtual returns (uint256 amount);
      /**
       * @notice A companion of `invest()`: determines how much would be returned by `invest()`, as the
       * value to invest into the senior tranche, if the junior tranche were locked and the senior tranche
       * were not locked.
       * @param seniorPool The senior pool to invest from
       * @param pool The tranched pool to invest into (as the senior)
       * @return The amount of money to invest into the tranched pool's senior tranche, from the senior pool
       */
      function estimateInvestment(
        ISeniorPool seniorPool,
        ITranchedPool pool
      ) public view virtual returns (uint256);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import {IERC721} from "./openzeppelin/IERC721.sol";
    import {IERC721Metadata} from "./openzeppelin/IERC721Metadata.sol";
    import {IERC721Enumerable} from "./openzeppelin/IERC721Enumerable.sol";
    interface IStakingRewards is IERC721, IERC721Metadata, IERC721Enumerable {
      /// @notice Get the staking rewards position
      /// @param tokenId id of the position token
      /// @return position the position
      function getPosition(uint256 tokenId) external view returns (StakedPosition memory position);
      /// @notice Unstake an amount of `stakingToken()` (FIDU, FiduUSDCCurveLP, etc) associated with
      ///   a given position and transfer to msg.sender. Any remaining staked amount will continue to
      ///   accrue rewards.
      /// @dev This function checkpoints rewards
      /// @param tokenId A staking position token ID
      /// @param amount Amount of `stakingToken()` to be unstaked from the position
      function unstake(uint256 tokenId, uint256 amount) external;
      /// @notice Add `amount` to an existing FIDU position (`tokenId`)
      /// @param tokenId A staking position token ID
      /// @param amount Amount of `stakingToken()` to be added to tokenId's position
      function addToStake(uint256 tokenId, uint256 amount) external;
      /// @notice Returns the staked balance of a given position token.
      /// @dev The value returned is the bare amount, not the effective amount. The bare amount represents
      ///   the number of tokens the user has staked for a given position. The effective amount is the bare
      ///   amount multiplied by the token's underlying asset type multiplier. This multiplier is a crypto-
      ///   economic parameter determined by governance.
      /// @param tokenId A staking position token ID
      /// @return Amount of staked tokens denominated in `stakingToken().decimals()`
      function stakedBalanceOf(uint256 tokenId) external view returns (uint256);
      /// @notice Deposit to FIDU and USDC into the Curve LP, and stake your Curve LP tokens in the same transaction.
      /// @param fiduAmount The amount of FIDU to deposit
      /// @param usdcAmount The amount of USDC to deposit
      function depositToCurveAndStakeFrom(
        address nftRecipient,
        uint256 fiduAmount,
        uint256 usdcAmount
      ) external;
      /// @notice "Kick" a user's reward multiplier. If they are past their lock-up period, their reward
      ///   multiplier will be reset to 1x.
      /// @dev This will also checkpoint their rewards up to the current time.
      function kick(uint256 tokenId) external;
      /// @notice Accumulated rewards per token at the last checkpoint
      function accumulatedRewardsPerToken() external view returns (uint256);
      /// @notice The block timestamp when rewards were last checkpointed
      function lastUpdateTime() external view returns (uint256);
      /// @notice Claim rewards for a given staked position
      /// @param tokenId A staking position token ID
      /// @return amount of rewards claimed
      function getReward(uint256 tokenId) external returns (uint256);
      /* ========== EVENTS ========== */
      event RewardAdded(uint256 reward);
      event Staked(
        address indexed user,
        uint256 indexed tokenId,
        uint256 amount,
        StakedPositionType positionType,
        uint256 baseTokenExchangeRate
      );
      event DepositedAndStaked(
        address indexed user,
        uint256 depositedAmount,
        uint256 indexed tokenId,
        uint256 amount
      );
      event DepositedToCurve(
        address indexed user,
        uint256 fiduAmount,
        uint256 usdcAmount,
        uint256 tokensReceived
      );
      event DepositedToCurveAndStaked(
        address indexed user,
        uint256 fiduAmount,
        uint256 usdcAmount,
        uint256 indexed tokenId,
        uint256 amount
      );
      event AddToStake(
        address indexed user,
        uint256 indexed tokenId,
        uint256 amount,
        StakedPositionType positionType
      );
      event Unstaked(
        address indexed user,
        uint256 indexed tokenId,
        uint256 amount,
        StakedPositionType positionType
      );
      event UnstakedMultiple(address indexed user, uint256[] tokenIds, uint256[] amounts);
      event RewardPaid(address indexed user, uint256 indexed tokenId, uint256 reward);
      event RewardsParametersUpdated(
        address indexed who,
        uint256 targetCapacity,
        uint256 minRate,
        uint256 maxRate,
        uint256 minRateAtPercent,
        uint256 maxRateAtPercent
      );
      event EffectiveMultiplierUpdated(
        address indexed who,
        StakedPositionType positionType,
        uint256 multiplier
      );
    }
    /// @notice Indicates which ERC20 is staked
    enum StakedPositionType {
      Fidu,
      CurveLP
    }
    struct Rewards {
      uint256 totalUnvested;
      uint256 totalVested;
      // @dev DEPRECATED (definition kept for storage slot)
      //   For legacy vesting positions, this was used in the case of slashing.
      //   For non-vesting positions, this is unused.
      uint256 totalPreviouslyVested;
      uint256 totalClaimed;
      uint256 startTime;
      // @dev DEPRECATED (definition kept for storage slot)
      //   For legacy vesting positions, this is the endTime of the vesting.
      //   For non-vesting positions, this is 0.
      uint256 endTime;
    }
    struct StakedPosition {
      // @notice Staked amount denominated in `stakingToken().decimals()`
      uint256 amount;
      // @notice Struct describing rewards owed with vesting
      Rewards rewards;
      // @notice Multiplier applied to staked amount when locking up position
      uint256 leverageMultiplier;
      // @notice Time in seconds after which position can be unstaked
      uint256 lockedUntil;
      // @notice Type of the staked position
      StakedPositionType positionType;
      // @notice Multiplier applied to staked amount to denominate in `baseStakingToken().decimals()`
      // @dev This field should not be used directly; it may be 0 for staked positions created prior to GIP-1.
      //  If you need this field, use `safeEffectiveMultiplier()`, which correctly handles old staked positions.
      uint256 unsafeEffectiveMultiplier;
      // @notice Exchange rate applied to staked amount to denominate in `baseStakingToken().decimals()`
      // @dev This field should not be used directly; it may be 0 for staked positions created prior to GIP-1.
      //  If you need this field, use `safeBaseTokenExchangeRate()`, which correctly handles old staked positions.
      uint256 unsafeBaseTokenExchangeRate;
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import {IV2CreditLine} from "./IV2CreditLine.sol";
    abstract contract ITranchedPool {
      IV2CreditLine public creditLine;
      uint256 public createdAt;
      enum Tranches {
        Reserved,
        Senior,
        Junior
      }
      struct TrancheInfo {
        uint256 id;
        uint256 principalDeposited;
        uint256 principalSharePrice;
        uint256 interestSharePrice;
        uint256 lockedUntil;
      }
      struct PoolSlice {
        TrancheInfo seniorTranche;
        TrancheInfo juniorTranche;
        uint256 totalInterestAccrued;
        uint256 principalDeployed;
      }
      function initialize(
        address _config,
        address _borrower,
        uint256 _juniorFeePercent,
        uint256 _limit,
        uint256 _interestApr,
        uint256 _paymentPeriodInDays,
        uint256 _termInDays,
        uint256 _lateFeeApr,
        uint256 _principalGracePeriodInDays,
        uint256 _fundableAt,
        uint256[] calldata _allowedUIDTypes
      ) public virtual;
      function getAllowedUIDTypes() external view virtual returns (uint256[] memory);
      function getTranche(uint256 tranche) external view virtual returns (TrancheInfo memory);
      function pay(uint256 amount) external virtual;
      function poolSlices(uint256 index) external view virtual returns (PoolSlice memory);
      function lockJuniorCapital() external virtual;
      function lockPool() external virtual;
      function initializeNextSlice(uint256 _fundableAt) external virtual;
      function totalJuniorDeposits() external view virtual returns (uint256);
      function drawdown(uint256 amount) external virtual;
      function setFundableAt(uint256 timestamp) external virtual;
      function deposit(uint256 tranche, uint256 amount) external virtual returns (uint256 tokenId);
      function assess() external virtual;
      function depositWithPermit(
        uint256 tranche,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
      ) external virtual returns (uint256 tokenId);
      function availableToWithdraw(
        uint256 tokenId
      ) external view virtual returns (uint256 interestRedeemable, uint256 principalRedeemable);
      function withdraw(
        uint256 tokenId,
        uint256 amount
      ) external virtual returns (uint256 interestWithdrawn, uint256 principalWithdrawn);
      function withdrawMax(
        uint256 tokenId
      ) external virtual returns (uint256 interestWithdrawn, uint256 principalWithdrawn);
      function withdrawMultiple(
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
      ) external virtual;
      function numSlices() external view virtual returns (uint256);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.12;
    pragma experimental ABIEncoderV2;
    import {ICreditLine} from "./ICreditLine.sol";
    abstract contract IV2CreditLine is ICreditLine {
      function principal() external view virtual returns (uint256);
      function totalInterestAccrued() external view virtual returns (uint256);
      function termStartTime() external view virtual returns (uint256);
      function setLimit(uint256 newAmount) external virtual;
      function setMaxLimit(uint256 newAmount) external virtual;
      function setBalance(uint256 newBalance) external virtual;
      function setPrincipal(uint256 _principal) external virtual;
      function setTotalInterestAccrued(uint256 _interestAccrued) external virtual;
      function drawdown(uint256 amount) external virtual;
      function assess() external virtual returns (uint256, uint256, uint256);
      function initialize(
        address _config,
        address owner,
        address _borrower,
        uint256 _limit,
        uint256 _interestApr,
        uint256 _paymentPeriodInDays,
        uint256 _termInDays,
        uint256 _lateFeeApr,
        uint256 _principalGracePeriodInDays
      ) public virtual;
      function setTermEndTime(uint256 newTermEndTime) external virtual;
      function setNextDueTime(uint256 newNextDueTime) external virtual;
      function setInterestOwed(uint256 newInterestOwed) external virtual;
      function setPrincipalOwed(uint256 newPrincipalOwed) external virtual;
      function setInterestAccruedAsOf(uint256 newInterestAccruedAsOf) external virtual;
      function setWritedownAmount(uint256 newWritedownAmount) external virtual;
      function setLastFullPaymentTime(uint256 newLastFullPaymentTime) external virtual;
      function setLateFeeApr(uint256 newLateFeeApr) external virtual;
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import {IERC721Enumerable} from "./openzeppelin/IERC721Enumerable.sol";
    interface IWithdrawalRequestToken is IERC721Enumerable {
      /// @notice Mint a withdrawal request token to `receiver`
      /// @dev succeeds if and only if called by senior pool
      function mint(address receiver) external returns (uint256 tokenId);
      /// @notice Burn token `tokenId`
      /// @dev suceeds if and only if called by senior pool
      function burn(uint256 tokenId) external;
    }
    pragma solidity >=0.6.0;
    // This file copied from OZ, but with the version pragma updated to use >=.
    /**
     * @dev Interface of the ERC165 standard, as defined in the
     * https://eips.ethereum.org/EIPS/eip-165[EIP].
     *
     * Implementers can declare support of contract interfaces, which can then be
     * queried by others ({ERC165Checker}).
     *
     * For an implementation, see {ERC165}.
     */
    interface IERC165 {
      /**
       * @dev Returns true if this contract implements the interface defined by
       * `interfaceId`. See the corresponding
       * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
       * to learn more about how these ids are created.
       *
       * This function call must use less than 30 000 gas.
       */
      function supportsInterface(bytes4 interfaceId) external view returns (bool);
    }
    pragma solidity >=0.6.2;
    // This file copied from OZ, but with the version pragma updated to use >= & reference other >= pragma interfaces.
    // NOTE: Modified to reference our updated pragma version of IERC165
    import "./IERC165.sol";
    /**
     * @dev Required interface of an ERC721 compliant contract.
     */
    interface IERC721 is IERC165 {
      event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
      event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
      event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
      /**
       * @dev Returns the number of NFTs in ``owner``'s account.
       */
      function balanceOf(address owner) external view returns (uint256 balance);
      /**
       * @dev Returns the owner of the NFT specified by `tokenId`.
       */
      function ownerOf(uint256 tokenId) external view returns (address owner);
      /**
       * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
       * another (`to`).
       *
       *
       *
       * Requirements:
       * - `from`, `to` cannot be zero.
       * - `tokenId` must be owned by `from`.
       * - If the caller is not `from`, it must be have been allowed to move this
       * NFT by either {approve} or {setApprovalForAll}.
       */
      function safeTransferFrom(address from, address to, uint256 tokenId) external;
      /**
       * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
       * another (`to`).
       *
       * Requirements:
       * - If the caller is not `from`, it must be approved to move this NFT by
       * either {approve} or {setApprovalForAll}.
       */
      function transferFrom(address from, address to, uint256 tokenId) external;
      function approve(address to, uint256 tokenId) external;
      function getApproved(uint256 tokenId) external view returns (address operator);
      function setApprovalForAll(address operator, bool _approved) external;
      function isApprovedForAll(address owner, address operator) external view returns (bool);
      function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
      ) external;
    }
    pragma solidity >=0.6.2;
    // This file copied from OZ, but with the version pragma updated to use >=.
    import "./IERC721.sol";
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721Enumerable is IERC721 {
      function totalSupply() external view returns (uint256);
      function tokenOfOwnerByIndex(
        address owner,
        uint256 index
      ) external view returns (uint256 tokenId);
      function tokenByIndex(uint256 index) external view returns (uint256);
    }
    pragma solidity >=0.6.2;
    // This file copied from OZ, but with the version pragma updated to use >=.
    import "./IERC721.sol";
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721Metadata is IERC721 {
      function name() external view returns (string memory);
      function symbol() external view returns (string memory);
      function tokenURI(uint256 tokenId) external view returns (string memory);
    }
    pragma solidity >=0.6.12;
    // This file copied from OZ, but with the version pragma updated to use >= & reference other >= pragma interfaces.
    /**
     * @title ERC721 token receiver interface
     * @dev Interface for any contract that wants to support safeTransfers
     * from ERC721 asset contracts.
     */
    interface IERC721Receiver {
      /**
       * @notice Handle the receipt of an NFT
       * @dev The ERC721 smart contract calls this function on the recipient
       * after a {IERC721-safeTransferFrom}. This function MUST return the function selector,
       * otherwise the caller will revert the transaction. The selector to be
       * returned can be obtained as `this.onERC721Received.selector`. This
       * function MAY throw to revert and reject the transfer.
       * Note: the ERC721 contract address is always the message sender.
       * @param operator The address which called `safeTransferFrom` function
       * @param from The address which previously owned the token
       * @param tokenId The NFT identifier which is being transferred
       * @param data Additional data with no specified format
       * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
       */
      function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
      ) external returns (bytes4);
    }
    pragma solidity >=0.6.12;
    // NOTE: this file exists only to remove the extremely long error messages in safe math.
    import {SafeMath as OzSafeMath} from "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
    /**
     * @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);
        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 OzSafeMath.sub(a, b, "");
      }
      /**
       * @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) {
        return OzSafeMath.sub(a, b, errorMessage);
      }
      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);
        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 OzSafeMath.div(a, b, "");
      }
      /**
       * @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) {
        return OzSafeMath.div(a, b, errorMessage);
      }
      /**
       * @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 OzSafeMath.mod(a, b, "");
      }
      function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        return OzSafeMath.mod(a, b, errorMessage);
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import {AccessControlUpgradeSafe} from "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol";
    import {ReentrancyGuardUpgradeSafe} from "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol";
    import {Initializable} from "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";
    import {SafeMath} from "../../library/SafeMath.sol";
    import {PauserPausable} from "./PauserPausable.sol";
    /**
     * @title BaseUpgradeablePausable contract
     * @notice This is our Base contract that most other contracts inherit from. It includes many standard
     *  useful abilities like upgradeability, pausability, access control, and re-entrancy guards.
     * @author Goldfinch
     */
    contract BaseUpgradeablePausable is
      Initializable,
      AccessControlUpgradeSafe,
      PauserPausable,
      ReentrancyGuardUpgradeSafe
    {
      bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE");
      using SafeMath for uint256;
      // Pre-reserving a few slots in the base contract in case we need to add things in the future.
      // This does not actually take up gas cost or storage cost, but it does reserve the storage slots.
      // See OpenZeppelin's use of this pattern here:
      // https://github.com/OpenZeppelin/openzeppelin-contracts-ethereum-package/blob/master/contracts/GSN/Context.sol#L37
      uint256[50] private __gap1;
      uint256[50] private __gap2;
      uint256[50] private __gap3;
      uint256[50] private __gap4;
      // solhint-disable-next-line func-name-mixedcase
      function __BaseUpgradeablePausable__init(address owner) public initializer {
        require(owner != address(0), "Owner cannot be the zero address");
        __AccessControl_init_unchained();
        __Pausable_init_unchained();
        __ReentrancyGuard_init_unchained();
        _setupRole(OWNER_ROLE, owner);
        _setupRole(PAUSER_ROLE, owner);
        _setRoleAdmin(PAUSER_ROLE, OWNER_ROLE);
        _setRoleAdmin(OWNER_ROLE, OWNER_ROLE);
      }
      function isAdmin() public view returns (bool) {
        return hasRole(OWNER_ROLE, _msgSender());
      }
      modifier onlyAdmin() {
        require(isAdmin(), "Must have admin role to perform this action");
        _;
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import {ImplementationRepository} from "./proxy/ImplementationRepository.sol";
    import {ConfigOptions} from "./ConfigOptions.sol";
    import {GoldfinchConfig} from "./GoldfinchConfig.sol";
    import {IFidu} from "../../interfaces/IFidu.sol";
    import {IWithdrawalRequestToken} from "../../interfaces/IWithdrawalRequestToken.sol";
    import {ISeniorPool} from "../../interfaces/ISeniorPool.sol";
    import {ISeniorPoolStrategy} from "../../interfaces/ISeniorPoolStrategy.sol";
    import {IERC20withDec} from "../../interfaces/IERC20withDec.sol";
    import {ICUSDCContract} from "../../interfaces/ICUSDCContract.sol";
    import {IPoolTokens} from "../../interfaces/IPoolTokens.sol";
    import {IBackerRewards} from "../../interfaces/IBackerRewards.sol";
    import {IGoldfinchFactory} from "../../interfaces/IGoldfinchFactory.sol";
    import {IGo} from "../../interfaces/IGo.sol";
    import {IStakingRewards} from "../../interfaces/IStakingRewards.sol";
    import {ICurveLP} from "../../interfaces/ICurveLP.sol";
    /**
     * @title ConfigHelper
     * @notice A convenience library for getting easy access to other contracts and constants within the
     *  protocol, through the use of the GoldfinchConfig contract
     * @author Goldfinch
     */
    library ConfigHelper {
      function getSeniorPool(GoldfinchConfig config) internal view returns (ISeniorPool) {
        return ISeniorPool(seniorPoolAddress(config));
      }
      function getSeniorPoolStrategy(
        GoldfinchConfig config
      ) internal view returns (ISeniorPoolStrategy) {
        return ISeniorPoolStrategy(seniorPoolStrategyAddress(config));
      }
      function getUSDC(GoldfinchConfig config) internal view returns (IERC20withDec) {
        return IERC20withDec(usdcAddress(config));
      }
      function getFidu(GoldfinchConfig config) internal view returns (IFidu) {
        return IFidu(fiduAddress(config));
      }
      function getFiduUSDCCurveLP(GoldfinchConfig config) internal view returns (ICurveLP) {
        return ICurveLP(fiduUSDCCurveLPAddress(config));
      }
      function getCUSDCContract(GoldfinchConfig config) internal view returns (ICUSDCContract) {
        return ICUSDCContract(cusdcContractAddress(config));
      }
      function getPoolTokens(GoldfinchConfig config) internal view returns (IPoolTokens) {
        return IPoolTokens(poolTokensAddress(config));
      }
      function getBackerRewards(GoldfinchConfig config) internal view returns (IBackerRewards) {
        return IBackerRewards(backerRewardsAddress(config));
      }
      function getGoldfinchFactory(GoldfinchConfig config) internal view returns (IGoldfinchFactory) {
        return IGoldfinchFactory(goldfinchFactoryAddress(config));
      }
      function getGFI(GoldfinchConfig config) internal view returns (IERC20withDec) {
        return IERC20withDec(gfiAddress(config));
      }
      function getGo(GoldfinchConfig config) internal view returns (IGo) {
        return IGo(goAddress(config));
      }
      function getStakingRewards(GoldfinchConfig config) internal view returns (IStakingRewards) {
        return IStakingRewards(stakingRewardsAddress(config));
      }
      function getTranchedPoolImplementationRepository(
        GoldfinchConfig config
      ) internal view returns (ImplementationRepository) {
        return
          ImplementationRepository(
            config.getAddress(uint256(ConfigOptions.Addresses.TranchedPoolImplementationRepository))
          );
      }
      function getWithdrawalRequestToken(
        GoldfinchConfig config
      ) internal view returns (IWithdrawalRequestToken) {
        return
          IWithdrawalRequestToken(
            config.getAddress(uint256(ConfigOptions.Addresses.WithdrawalRequestToken))
          );
      }
      function oneInchAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.OneInch));
      }
      function creditLineImplementationAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.CreditLineImplementation));
      }
      /// @dev deprecated because we no longer use GSN
      function trustedForwarderAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.TrustedForwarder));
      }
      function configAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.GoldfinchConfig));
      }
      function poolTokensAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.PoolTokens));
      }
      function backerRewardsAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.BackerRewards));
      }
      function seniorPoolAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.SeniorPool));
      }
      function seniorPoolStrategyAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.SeniorPoolStrategy));
      }
      function goldfinchFactoryAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.GoldfinchFactory));
      }
      function gfiAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.GFI));
      }
      function fiduAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.Fidu));
      }
      function fiduUSDCCurveLPAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.FiduUSDCCurveLP));
      }
      function cusdcContractAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.CUSDCContract));
      }
      function usdcAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.USDC));
      }
      function tranchedPoolAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.TranchedPoolImplementation));
      }
      function reserveAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.TreasuryReserve));
      }
      function protocolAdminAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.ProtocolAdmin));
      }
      function borrowerImplementationAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.BorrowerImplementation));
      }
      function goAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.Go));
      }
      function stakingRewardsAddress(GoldfinchConfig config) internal view returns (address) {
        return config.getAddress(uint256(ConfigOptions.Addresses.StakingRewards));
      }
      function getReserveDenominator(GoldfinchConfig config) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.ReserveDenominator));
      }
      function getWithdrawFeeDenominator(GoldfinchConfig config) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.WithdrawFeeDenominator));
      }
      function getLatenessGracePeriodInDays(GoldfinchConfig config) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.LatenessGracePeriodInDays));
      }
      function getLatenessMaxDays(GoldfinchConfig config) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.LatenessMaxDays));
      }
      function getDrawdownPeriodInSeconds(GoldfinchConfig config) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.DrawdownPeriodInSeconds));
      }
      function getTransferRestrictionPeriodInDays(
        GoldfinchConfig config
      ) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.TransferRestrictionPeriodInDays));
      }
      function getLeverageRatio(GoldfinchConfig config) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.LeverageRatio));
      }
      function getSeniorPoolWithdrawalCancelationFeeInBps(
        GoldfinchConfig config
      ) internal view returns (uint256) {
        return config.getNumber(uint256(ConfigOptions.Numbers.SeniorPoolWithdrawalCancelationFeeInBps));
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    /**
     * @title ConfigOptions
     * @notice A central place for enumerating the configurable options of our GoldfinchConfig contract
     * @author Goldfinch
     */
    library ConfigOptions {
      // NEVER EVER CHANGE THE ORDER OF THESE!
      // You can rename or append. But NEVER change the order.
      enum Numbers {
        TransactionLimit,
        /// @dev: TotalFundsLimit used to represent a total cap on senior pool deposits
        /// but is now deprecated
        TotalFundsLimit,
        MaxUnderwriterLimit,
        ReserveDenominator,
        WithdrawFeeDenominator,
        LatenessGracePeriodInDays,
        LatenessMaxDays,
        DrawdownPeriodInSeconds,
        TransferRestrictionPeriodInDays,
        LeverageRatio,
        /// A number in the range [0, 10000] representing basis points of FIDU taken as a fee
        /// when a withdrawal request is canceled.
        SeniorPoolWithdrawalCancelationFeeInBps
      }
      /// @dev TrustedForwarder is deprecated because we no longer use GSN. CreditDesk
      ///   and Pool are deprecated because they are no longer used in the protocol.
      enum Addresses {
        Pool, // deprecated
        CreditLineImplementation,
        GoldfinchFactory,
        CreditDesk, // deprecated
        Fidu,
        USDC,
        TreasuryReserve,
        ProtocolAdmin,
        OneInch,
        TrustedForwarder, // deprecated
        CUSDCContract,
        GoldfinchConfig,
        PoolTokens,
        TranchedPoolImplementation, // deprecated
        SeniorPool,
        SeniorPoolStrategy,
        MigratedTranchedPoolImplementation,
        BorrowerImplementation,
        GFI,
        Go,
        BackerRewards,
        StakingRewards,
        FiduUSDCCurveLP,
        TranchedPoolImplementationRepository,
        WithdrawalRequestToken
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import {SafeMath} from "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
    /// @notice Library to house logic around the ERC2981 royalty standard. Contracts
    ///   using this library should define a ConfigurableRoyaltyStandard.RoyaltyParams
    ///   state var and public functions that proxy to the logic here. Contracts should
    ///   take care to ensure that a public `setRoyaltyParams` method is only callable
    ///   by an admin.
    library ConfigurableRoyaltyStandard {
      using SafeMath for uint256;
      /// @dev bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
      bytes4 internal constant _INTERFACE_ID_ERC2981 = 0x2a55205a;
      uint256 internal constant _PERCENTAGE_DECIMALS = 1e18;
      struct RoyaltyParams {
        // The address that should receive royalties
        address receiver;
        // The percent of `salePrice` that should be taken for royalties.
        // Represented with `_PERCENTAGE_DECIMALS` where `_PERCENTAGE_DECIMALS` is 100%.
        uint256 royaltyPercent;
      }
      event RoyaltyParamsSet(address indexed sender, address newReceiver, uint256 newRoyaltyPercent);
      /// @notice Called with the sale price to determine how much royalty
      //    is owed and to whom.
      /// @param _tokenId The NFT asset queried for royalty information
      /// @param _salePrice The sale price of the NFT asset specified by _tokenId
      /// @return receiver Address that should receive royalties
      /// @return royaltyAmount The royalty payment amount for _salePrice
      function royaltyInfo(
        RoyaltyParams storage params,
        // solhint-disable-next-line no-unused-vars
        uint256 _tokenId,
        uint256 _salePrice
      ) internal view returns (address, uint256) {
        uint256 royaltyAmount = _salePrice.mul(params.royaltyPercent).div(_PERCENTAGE_DECIMALS);
        return (params.receiver, royaltyAmount);
      }
      /// @notice Set royalty params used in `royaltyInfo`. The calling contract should limit
      ///   public use of this function to owner or using some other access control scheme.
      /// @param newReceiver The new address which should receive royalties. See `receiver`.
      /// @param newRoyaltyPercent The new percent of `salePrice` that should be taken for royalties.
      ///   See `royaltyPercent`.
      /// @dev The receiver cannot be the null address
      function setRoyaltyParams(
        RoyaltyParams storage params,
        address newReceiver,
        uint256 newRoyaltyPercent
      ) internal {
        require(newReceiver != address(0), "Null receiver");
        params.receiver = newReceiver;
        params.royaltyPercent = newRoyaltyPercent;
        emit RoyaltyParamsSet(msg.sender, newReceiver, newRoyaltyPercent);
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import {BaseUpgradeablePausable} from "./BaseUpgradeablePausable.sol";
    import {IGoldfinchConfig} from "../../interfaces/IGoldfinchConfig.sol";
    import {ConfigOptions} from "./ConfigOptions.sol";
    /**
     * @title GoldfinchConfig
     * @notice This contract stores mappings of useful "protocol config state", giving a central place
     *  for all other contracts to access it. For example, the TransactionLimit, or the PoolAddress. These config vars
     *  are enumerated in the `ConfigOptions` library, and can only be changed by admins of the protocol.
     *  Note: While this inherits from BaseUpgradeablePausable, it is not deployed as an upgradeable contract (this
     *    is mostly to save gas costs of having each call go through a proxy)
     * @author Goldfinch
     */
    contract GoldfinchConfig is BaseUpgradeablePausable {
      bytes32 public constant GO_LISTER_ROLE = keccak256("GO_LISTER_ROLE");
      mapping(uint256 => address) public addresses;
      mapping(uint256 => uint256) public numbers;
      mapping(address => bool) public goList;
      event AddressUpdated(address owner, uint256 index, address oldValue, address newValue);
      event NumberUpdated(address owner, uint256 index, uint256 oldValue, uint256 newValue);
      event GoListed(address indexed member);
      event NoListed(address indexed member);
      bool public valuesInitialized;
      function initialize(address owner) public initializer {
        require(owner != address(0), "Owner address cannot be empty");
        __BaseUpgradeablePausable__init(owner);
        _setupRole(GO_LISTER_ROLE, owner);
        _setRoleAdmin(GO_LISTER_ROLE, OWNER_ROLE);
      }
      function setAddress(uint256 addressIndex, address newAddress) public onlyAdmin {
        require(addresses[addressIndex] == address(0), "Address has already been initialized");
        emit AddressUpdated(msg.sender, addressIndex, addresses[addressIndex], newAddress);
        addresses[addressIndex] = newAddress;
      }
      function setNumber(uint256 index, uint256 newNumber) public onlyAdmin {
        emit NumberUpdated(msg.sender, index, numbers[index], newNumber);
        numbers[index] = newNumber;
      }
      function setTreasuryReserve(address newTreasuryReserve) public onlyAdmin {
        uint256 key = uint256(ConfigOptions.Addresses.TreasuryReserve);
        emit AddressUpdated(msg.sender, key, addresses[key], newTreasuryReserve);
        addresses[key] = newTreasuryReserve;
      }
      function setSeniorPoolStrategy(address newStrategy) public onlyAdmin {
        uint256 key = uint256(ConfigOptions.Addresses.SeniorPoolStrategy);
        emit AddressUpdated(msg.sender, key, addresses[key], newStrategy);
        addresses[key] = newStrategy;
      }
      function setCreditLineImplementation(address newAddress) public onlyAdmin {
        uint256 key = uint256(ConfigOptions.Addresses.CreditLineImplementation);
        emit AddressUpdated(msg.sender, key, addresses[key], newAddress);
        addresses[key] = newAddress;
      }
      function setTranchedPoolImplementation(address newAddress) public onlyAdmin {
        uint256 key = uint256(ConfigOptions.Addresses.TranchedPoolImplementation);
        emit AddressUpdated(msg.sender, key, addresses[key], newAddress);
        addresses[key] = newAddress;
      }
      function setBorrowerImplementation(address newAddress) public onlyAdmin {
        uint256 key = uint256(ConfigOptions.Addresses.BorrowerImplementation);
        emit AddressUpdated(msg.sender, key, addresses[key], newAddress);
        addresses[key] = newAddress;
      }
      function setGoldfinchConfig(address newAddress) public onlyAdmin {
        uint256 key = uint256(ConfigOptions.Addresses.GoldfinchConfig);
        emit AddressUpdated(msg.sender, key, addresses[key], newAddress);
        addresses[key] = newAddress;
      }
      function initializeFromOtherConfig(
        address _initialConfig,
        uint256 numbersLength,
        uint256 addressesLength
      ) public onlyAdmin {
        require(!valuesInitialized, "Already initialized values");
        IGoldfinchConfig initialConfig = IGoldfinchConfig(_initialConfig);
        for (uint256 i = 0; i < numbersLength; i++) {
          setNumber(i, initialConfig.getNumber(i));
        }
        for (uint256 i = 0; i < addressesLength; i++) {
          if (getAddress(i) == address(0)) {
            setAddress(i, initialConfig.getAddress(i));
          }
        }
        valuesInitialized = true;
      }
      /**
       * @dev Adds a user to go-list
       * @param _member address to add to go-list
       */
      function addToGoList(address _member) public onlyGoListerRole {
        goList[_member] = true;
        emit GoListed(_member);
      }
      /**
       * @dev removes a user from go-list
       * @param _member address to remove from go-list
       */
      function removeFromGoList(address _member) public onlyGoListerRole {
        goList[_member] = false;
        emit NoListed(_member);
      }
      /**
       * @dev adds many users to go-list at once
       * @param _members addresses to ad to go-list
       */
      function bulkAddToGoList(address[] calldata _members) external onlyGoListerRole {
        for (uint256 i = 0; i < _members.length; i++) {
          addToGoList(_members[i]);
        }
      }
      /**
       * @dev removes many users from go-list at once
       * @param _members addresses to remove from go-list
       */
      function bulkRemoveFromGoList(address[] calldata _members) external onlyGoListerRole {
        for (uint256 i = 0; i < _members.length; i++) {
          removeFromGoList(_members[i]);
        }
      }
      /*
        Using custom getters in case we want to change underlying implementation later,
        or add checks or validations later on.
      */
      function getAddress(uint256 index) public view returns (address) {
        return addresses[index];
      }
      function getNumber(uint256 index) public view returns (uint256) {
        return numbers[index];
      }
      modifier onlyGoListerRole() {
        require(
          hasRole(GO_LISTER_ROLE, _msgSender()),
          "Must have go-lister role to perform this action"
        );
        _;
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    import "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol";
    /// @notice Base contract that provides an OWNER_ROLE and convenience function/modifier for
    ///   checking sender against this role. Inherting contracts must set up this role using
    ///   `_setupRole` and `_setRoleAdmin`.
    contract HasAdmin is AccessControlUpgradeSafe {
      /// @notice ID for OWNER_ROLE
      bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE");
      /// @notice Determine whether msg.sender has OWNER_ROLE
      /// @return isAdmin True when msg.sender has OWNER_ROLE
      function isAdmin() public view returns (bool) {
        return hasRole(OWNER_ROLE, msg.sender);
      }
      modifier onlyAdmin() {
        /// @dev AD: Must have admin role to perform this action
        require(isAdmin(), "AD");
        _;
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import "@openzeppelin/contracts-ethereum-package/contracts/utils/Pausable.sol";
    import "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol";
    /**
     * @title PauserPausable
     * @notice Inheriting from OpenZeppelin's Pausable contract, this does small
     *  augmentations to make it work with a PAUSER_ROLE, leveraging the AccessControl contract.
     *  It is meant to be inherited.
     * @author Goldfinch
     */
    contract PauserPausable is AccessControlUpgradeSafe, PausableUpgradeSafe {
      bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
      // solhint-disable-next-line func-name-mixedcase
      function __PauserPausable__init() public initializer {
        __Pausable_init_unchained();
      }
      /**
       * @dev Pauses all functions guarded by Pause
       *
       * See {Pausable-_pause}.
       *
       * Requirements:
       *
       * - the caller must have the PAUSER_ROLE.
       */
      function pause() public onlyPauserRole {
        _pause();
      }
      /**
       * @dev Unpauses the contract
       *
       * See {Pausable-_unpause}.
       *
       * Requirements:
       *
       * - the caller must have the Pauser role
       */
      function unpause() public onlyPauserRole {
        _unpause();
      }
      modifier onlyPauserRole() {
        /// @dev NA: not authorized
        require(hasRole(PAUSER_ROLE, _msgSender()), "NA");
        _;
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import {ERC721PresetMinterPauserAutoIdUpgradeSafe} from "../../external/ERC721PresetMinterPauserAutoId.sol";
    import {ERC165UpgradeSafe} from "../../external/ERC721PresetMinterPauserAutoId.sol";
    import {IERC165} from "../../external/ERC721PresetMinterPauserAutoId.sol";
    import {GoldfinchConfig} from "./GoldfinchConfig.sol";
    import {ConfigHelper} from "./ConfigHelper.sol";
    import {HasAdmin} from "./HasAdmin.sol";
    import {ConfigurableRoyaltyStandard} from "./ConfigurableRoyaltyStandard.sol";
    import {IERC2981} from "../../interfaces/IERC2981.sol";
    import {ITranchedPool} from "../../interfaces/ITranchedPool.sol";
    import {IPoolTokens} from "../../interfaces/IPoolTokens.sol";
    import {IBackerRewards} from "../../interfaces/IBackerRewards.sol";
    /**
     * @title PoolTokens
     * @notice PoolTokens is an ERC721 compliant contract, which can represent
     *  junior tranche or senior tranche shares of any of the borrower pools.
     * @author Goldfinch
     */
    contract PoolTokens is IPoolTokens, ERC721PresetMinterPauserAutoIdUpgradeSafe, HasAdmin, IERC2981 {
      bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
      bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
      bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
      bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
      GoldfinchConfig public config;
      using ConfigHelper for GoldfinchConfig;
      // tokenId => tokenInfo
      mapping(uint256 => TokenInfo) public tokens;
      // poolAddress => poolInfo
      mapping(address => PoolInfo) public pools;
      ConfigurableRoyaltyStandard.RoyaltyParams public royaltyParams;
      using ConfigurableRoyaltyStandard for ConfigurableRoyaltyStandard.RoyaltyParams;
      /*
        We are using our own initializer function so that OZ doesn't automatically
        set owner as msg.sender. Also, it lets us set our config contract
      */
      // solhint-disable-next-line func-name-mixedcase
      function __initialize__(address owner, GoldfinchConfig _config) external initializer {
        require(
          owner != address(0) && address(_config) != address(0),
          "Owner and config addresses cannot be empty"
        );
        __Context_init_unchained();
        __AccessControl_init_unchained();
        __ERC165_init_unchained();
        // This is setting name and symbol of the NFT's
        __ERC721_init_unchained("Goldfinch V2 Pool Tokens", "GFI-V2-PT");
        __Pausable_init_unchained();
        __ERC721Pausable_init_unchained();
        config = _config;
        _setupRole(PAUSER_ROLE, owner);
        _setupRole(OWNER_ROLE, owner);
        _setRoleAdmin(PAUSER_ROLE, OWNER_ROLE);
        _setRoleAdmin(OWNER_ROLE, OWNER_ROLE);
      }
      /// @inheritdoc IPoolTokens
      function mint(
        MintParams calldata params,
        address to
      ) external virtual override onlyPool whenNotPaused returns (uint256 tokenId) {
        address poolAddress = _msgSender();
        PoolInfo storage pool = pools[poolAddress];
        pool.totalMinted = pool.totalMinted.add(params.principalAmount);
        tokenId = _createToken({
          principalAmount: params.principalAmount,
          tranche: params.tranche,
          principalRedeemed: 0,
          interestRedeemed: 0,
          poolAddress: poolAddress,
          mintTo: to
        });
        config.getBackerRewards().setPoolTokenAccRewardsPerPrincipalDollarAtMint(_msgSender(), tokenId);
      }
      /// @inheritdoc IPoolTokens
      function redeem(
        uint256 tokenId,
        uint256 principalRedeemed,
        uint256 interestRedeemed
      ) external virtual override onlyPool whenNotPaused {
        TokenInfo storage token = tokens[tokenId];
        address poolAddr = token.pool;
        require(token.pool != address(0), "Invalid tokenId");
        require(_msgSender() == poolAddr, "Only the token's pool can redeem");
        PoolInfo storage pool = pools[poolAddr];
        pool.totalPrincipalRedeemed = pool.totalPrincipalRedeemed.add(principalRedeemed);
        require(pool.totalPrincipalRedeemed <= pool.totalMinted, "Cannot redeem more than we minted");
        token.principalRedeemed = token.principalRedeemed.add(principalRedeemed);
        require(
          token.principalRedeemed <= token.principalAmount,
          "Cannot redeem more than principal-deposited amount for token"
        );
        token.interestRedeemed = token.interestRedeemed.add(interestRedeemed);
        emit TokenRedeemed(
          ownerOf(tokenId),
          poolAddr,
          tokenId,
          principalRedeemed,
          interestRedeemed,
          token.tranche
        );
      }
      /** @notice reduce a given pool token's principalAmount and principalRedeemed by a specified amount
       *  @dev uses safemath to prevent underflow
       *  @dev this function is only intended for use as part of the v2.6.0 upgrade
       *    to rectify a bug that allowed users to create a PoolToken that had a
       *    larger amount of principal than they actually made available to the
       *    borrower.  This bug is fixed in v2.6.0 but still requires past pool tokens
       *    to have their principal redeemed and deposited to be rectified.
       *  @param tokenId id of token to decrease
       *  @param amount amount to decrease by
       */
      function reducePrincipalAmount(uint256 tokenId, uint256 amount) external onlyAdmin {
        TokenInfo storage tokenInfo = tokens[tokenId];
        tokenInfo.principalAmount = tokenInfo.principalAmount.sub(amount);
        tokenInfo.principalRedeemed = tokenInfo.principalRedeemed.sub(amount);
      }
      /// @inheritdoc IPoolTokens
      function withdrawPrincipal(
        uint256 tokenId,
        uint256 principalAmount
      ) external virtual override onlyPool whenNotPaused {
        TokenInfo storage token = tokens[tokenId];
        address poolAddr = token.pool;
        require(_msgSender() == poolAddr, "Invalid sender");
        require(token.principalRedeemed == 0, "Token redeemed");
        require(token.principalAmount >= principalAmount, "Insufficient principal");
        PoolInfo storage pool = pools[poolAddr];
        pool.totalMinted = pool.totalMinted.sub(principalAmount);
        require(pool.totalPrincipalRedeemed <= pool.totalMinted, "Cannot withdraw more than redeemed");
        token.principalAmount = token.principalAmount.sub(principalAmount);
        emit TokenPrincipalWithdrawn(
          ownerOf(tokenId),
          poolAddr,
          tokenId,
          principalAmount,
          token.tranche
        );
      }
      /// @inheritdoc IPoolTokens
      function burn(uint256 tokenId) external virtual override whenNotPaused {
        TokenInfo memory token = _getTokenInfo(tokenId);
        bool canBurn = _isApprovedOrOwner(_msgSender(), tokenId);
        bool fromTokenPool = _validPool(_msgSender()) && token.pool == _msgSender();
        address owner = ownerOf(tokenId);
        require(canBurn || fromTokenPool, "ERC721Burnable: caller cannot burn this token");
        require(
          token.principalRedeemed == token.principalAmount,
          "Can only burn fully redeemed tokens"
        );
        // If we let you burn with claimable backer rewards then it would blackhole your rewards,
        // so you must claim all rewards before burning
        require(config.getBackerRewards().poolTokenClaimableRewards(tokenId) == 0, "rewards>0");
        _destroyAndBurn(owner, address(token.pool), tokenId);
      }
      function getTokenInfo(uint256 tokenId) external view virtual override returns (TokenInfo memory) {
        return _getTokenInfo(tokenId);
      }
      function getPoolInfo(address pool) external view override returns (PoolInfo memory) {
        return pools[pool];
      }
      /// @inheritdoc IPoolTokens
      function onPoolCreated(address newPool) external override onlyGoldfinchFactory {
        pools[newPool].created = true;
      }
      /**
       * @notice Returns a boolean representing whether the spender is the owner or the approved spender of the token
       * @param spender The address to check
       * @param tokenId The token id to check for
       * @return True if approved to redeem/transfer/burn the token, false if not
       */
      function isApprovedOrOwner(
        address spender,
        uint256 tokenId
      ) external view override returns (bool) {
        return _isApprovedOrOwner(spender, tokenId);
      }
      /**
       * @inheritdoc IPoolTokens
       * @dev NA: Not Authorized
       * @dev IA: Invalid Amount - newPrincipal1 not in range (0, principalAmount)
       */
      function splitToken(
        uint256 tokenId,
        uint256 newPrincipal1
      ) external override returns (uint256 tokenId1, uint256 tokenId2) {
        require(_isApprovedOrOwner(msg.sender, tokenId), "NA");
        TokenInfo memory tokenInfo = _getTokenInfo(tokenId);
        require(0 < newPrincipal1 && newPrincipal1 < tokenInfo.principalAmount, "IA");
        IBackerRewards.BackerRewardsTokenInfo memory backerRewardsTokenInfo = config
          .getBackerRewards()
          .getTokenInfo(tokenId);
        IBackerRewards.StakingRewardsTokenInfo memory backerStakingRewardsTokenInfo = config
          .getBackerRewards()
          .getStakingRewardsTokenInfo(tokenId);
        // Burn the original token before calling out to other contracts to prevent possible reentrancy attacks.
        // A reentrancy guard on this function alone is insufficient because someone may be able to reenter the
        // protocol through a different contract that reads pool token metadata. Following checks-effects-interactions
        // here leads to a clunky implementation (fn's with many params) but guarding against potential reentrancy
        // is more important.
        address tokenOwner = ownerOf(tokenId);
        _destroyAndBurn(tokenOwner, address(tokenInfo.pool), tokenId);
        (tokenId1, tokenId2) = _createSplitTokens(tokenInfo, tokenOwner, newPrincipal1);
        _setBackerRewardsForSplitTokens(
          tokenInfo,
          backerRewardsTokenInfo,
          backerStakingRewardsTokenInfo,
          tokenId1,
          tokenId2,
          newPrincipal1
        );
        emit TokenSplit({
          owner: tokenOwner,
          pool: tokenInfo.pool,
          tokenId: tokenId,
          newTokenId1: tokenId1,
          newPrincipal1: newPrincipal1,
          newTokenId2: tokenId2,
          newPrincipal2: tokenInfo.principalAmount.sub(newPrincipal1)
        });
      }
      /// @notice Initialize the backer rewards metadata for split tokens
      function _setBackerRewardsForSplitTokens(
        TokenInfo memory tokenInfo,
        IBackerRewards.BackerRewardsTokenInfo memory backerRewardsTokenInfo,
        IBackerRewards.StakingRewardsTokenInfo memory stakingRewardsTokenInfo,
        uint256 newTokenId1,
        uint256 newTokenId2,
        uint256 newPrincipal1
      ) internal {
        uint256 rewardsClaimed1 = backerRewardsTokenInfo.rewardsClaimed.mul(newPrincipal1).div(
          tokenInfo.principalAmount
        );
        config.getBackerRewards().setBackerAndStakingRewardsTokenInfoOnSplit({
          originalBackerRewardsTokenInfo: backerRewardsTokenInfo,
          originalStakingRewardsTokenInfo: stakingRewardsTokenInfo,
          newTokenId: newTokenId1,
          newRewardsClaimed: rewardsClaimed1
        });
        config.getBackerRewards().setBackerAndStakingRewardsTokenInfoOnSplit({
          originalBackerRewardsTokenInfo: backerRewardsTokenInfo,
          originalStakingRewardsTokenInfo: stakingRewardsTokenInfo,
          newTokenId: newTokenId2,
          newRewardsClaimed: backerRewardsTokenInfo.rewardsClaimed.sub(rewardsClaimed1)
        });
      }
      /// @notice Split tokenId into two new tokens. Assumes that newPrincipal1 is valid for the token's principalAmount
      function _createSplitTokens(
        TokenInfo memory tokenInfo,
        address tokenOwner,
        uint256 newPrincipal1
      ) internal returns (uint256 newTokenId1, uint256 newTokenId2) {
        // All new vals are proportional to the new token's principal
        uint256 principalRedeemed1 = tokenInfo.principalRedeemed.mul(newPrincipal1).div(
          tokenInfo.principalAmount
        );
        uint256 interestRedeemed1 = tokenInfo.interestRedeemed.mul(newPrincipal1).div(
          tokenInfo.principalAmount
        );
        newTokenId1 = _createToken(
          newPrincipal1,
          tokenInfo.tranche,
          principalRedeemed1,
          interestRedeemed1,
          tokenInfo.pool,
          tokenOwner
        );
        newTokenId2 = _createToken(
          tokenInfo.principalAmount.sub(newPrincipal1),
          tokenInfo.tranche,
          tokenInfo.principalRedeemed.sub(principalRedeemed1),
          tokenInfo.interestRedeemed.sub(interestRedeemed1),
          tokenInfo.pool,
          tokenOwner
        );
      }
      /// @inheritdoc IPoolTokens
      function validPool(address sender) public view virtual override returns (bool) {
        return _validPool(sender);
      }
      /**
       * @notice Mint the token and save its metadata to storage
       * @param principalAmount token principal
       * @param tranche tranche of the pool to which the token belongs
       * @param principalRedeemed amount of principal already redeemed for the token. This is
       *  0 for tokens created from a deposit, and could be non-zero for tokens created from a split
       * @param interestRedeemed amount of interest already redeemed for the token. This is
       *  0 for tokens created from a deposit, and could be non-zero for tokens created from a split
       * @param poolAddress pool to which the token belongs
       * @param mintTo the token owner
       * @return tokenId id of the created token
       */
      function _createToken(
        uint256 principalAmount,
        uint256 tranche,
        uint256 principalRedeemed,
        uint256 interestRedeemed,
        address poolAddress,
        address mintTo
      ) internal returns (uint256 tokenId) {
        _tokenIdTracker.increment();
        tokenId = _tokenIdTracker.current();
        tokens[tokenId] = TokenInfo({
          pool: poolAddress,
          tranche: tranche,
          principalAmount: principalAmount,
          principalRedeemed: principalRedeemed,
          interestRedeemed: interestRedeemed
        });
        _mint(mintTo, tokenId);
        emit TokenMinted({
          owner: mintTo,
          pool: poolAddress,
          tokenId: tokenId,
          amount: principalAmount,
          tranche: tranche
        });
      }
      function _destroyAndBurn(address owner, address pool, uint256 tokenId) internal {
        delete tokens[tokenId];
        _burn(tokenId);
        config.getBackerRewards().clearTokenInfo(tokenId);
        emit TokenBurned(owner, pool, tokenId);
      }
      function _validPool(address poolAddress) internal view virtual returns (bool) {
        return pools[poolAddress].created;
      }
      function _getTokenInfo(uint256 tokenId) internal view returns (TokenInfo memory) {
        return tokens[tokenId];
      }
      /// @notice Called with the sale price to determine how much royalty
      //    is owed and to whom.
      /// @param _tokenId The NFT asset queried for royalty information
      /// @param _salePrice The sale price of the NFT asset specified by _tokenId
      /// @return receiver Address that should receive royalties
      /// @return royaltyAmount The royalty payment amount for _salePrice
      function royaltyInfo(
        uint256 _tokenId,
        uint256 _salePrice
      ) external view override returns (address, uint256) {
        return royaltyParams.royaltyInfo(_tokenId, _salePrice);
      }
      /// @notice Set royalty params used in `royaltyInfo`. This function is only callable by
      ///   an address with `OWNER_ROLE`.
      /// @param newReceiver The new address which should receive royalties. See `receiver`.
      /// @param newRoyaltyPercent The new percent of `salePrice` that should be taken for royalties.
      ///   See `royaltyPercent`.
      function setRoyaltyParams(address newReceiver, uint256 newRoyaltyPercent) external onlyAdmin {
        royaltyParams.setRoyaltyParams(newReceiver, newRoyaltyPercent);
      }
      function setBaseURI(string calldata baseURI_) external onlyAdmin {
        _setBaseURI(baseURI_);
      }
      function supportsInterface(
        bytes4 id
      ) public view override(ERC165UpgradeSafe, IERC165) returns (bool) {
        return (id == _INTERFACE_ID_ERC721 ||
          id == _INTERFACE_ID_ERC721_METADATA ||
          id == _INTERFACE_ID_ERC721_ENUMERABLE ||
          id == _INTERFACE_ID_ERC165 ||
          id == ConfigurableRoyaltyStandard._INTERFACE_ID_ERC2981);
      }
      modifier onlyGoldfinchFactory() {
        require(_msgSender() == config.goldfinchFactoryAddress(), "Only Goldfinch factory is allowed");
        _;
      }
      modifier onlyPool() {
        require(_validPool(_msgSender()), "Invalid pool!");
        _;
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity 0.6.12;
    pragma experimental ABIEncoderV2;
    import {BaseUpgradeablePausable} from "../BaseUpgradeablePausable.sol";
    import {Address} from "@openzeppelin/contracts/utils/Address.sol";
    /// @title User Controlled Upgrades (UCU) Proxy Repository
    /// A repository maintaing a collection of "lineages" of implementation contracts
    ///
    /// Lineages are a sequence of implementations each lineage can be thought of as
    /// a "major" revision of implementations. Implementations between lineages are
    /// considered incompatible.
    contract ImplementationRepository is BaseUpgradeablePausable {
      address internal constant INVALID_IMPL = address(0);
      uint256 internal constant INVALID_LINEAGE_ID = 0;
      /// @notice returns data that will be delegatedCalled when the given implementation
      ///           is upgraded to
      mapping(address => bytes) public upgradeDataFor;
      /// @dev mapping from one implementation to the succeeding implementation
      mapping(address => address) internal _nextImplementationOf;
      /// @notice Returns the id of the lineage a given implementation belongs to
      mapping(address => uint256) public lineageIdOf;
      /// @dev internal because we expose this through the `currentImplementation(uint256)` api
      mapping(uint256 => address) internal _currentOfLineage;
      /// @notice Returns the id of the most recently created lineage
      uint256 public currentLineageId;
      // //////// External ////////////////////////////////////////////////////////////
      /// @notice initialize the repository's state
      /// @dev reverts if `_owner` is the null address
      /// @dev reverts if `implementation` is not a contract
      /// @param _owner owner of the repository
      /// @param implementation initial implementation in the repository
      function initialize(address _owner, address implementation) external initializer {
        __BaseUpgradeablePausable__init(_owner);
        _createLineage(implementation);
        require(currentLineageId != INVALID_LINEAGE_ID);
      }
      /// @notice set data that will be delegate called when a proxy upgrades to the given `implementation`
      /// @dev reverts when caller is not an admin
      /// @dev reverts when the contract is paused
      /// @dev reverts if the given implementation isn't registered
      function setUpgradeDataFor(
        address implementation,
        bytes calldata data
      ) external onlyAdmin whenNotPaused {
        _setUpgradeDataFor(implementation, data);
      }
      /// @notice Create a new lineage of implementations.
      ///
      /// This creates a new "root" of a new lineage
      /// @dev reverts if `implementation` is not a contract
      /// @param implementation implementation that will be the first implementation in the lineage
      /// @return newly created lineage's id
      function createLineage(
        address implementation
      ) external onlyAdmin whenNotPaused returns (uint256) {
        return _createLineage(implementation);
      }
      /// @notice add a new implementation and set it as the current implementation
      /// @dev reverts if the sender is not an owner
      /// @dev reverts if the contract is paused
      /// @dev reverts if `implementation` is not a contract
      /// @param implementation implementation to append
      function append(address implementation) external onlyAdmin whenNotPaused {
        _append(implementation, currentLineageId);
      }
      /// @notice Append an implementation to a specified lineage
      /// @dev reverts if the contract is paused
      /// @dev reverts if the sender is not an owner
      /// @dev reverts if `implementation` is not a contract
      /// @param implementation implementation to append
      /// @param lineageId id of lineage to append to
      function append(address implementation, uint256 lineageId) external onlyAdmin whenNotPaused {
        _append(implementation, lineageId);
      }
      /// @notice Remove an implementation from the chain and "stitch" together its neighbors
      /// @dev If you have a chain of `A -> B -> C` and I call `remove(B, C)` it will result in `A -> C`
      /// @dev reverts if `previos` is not the ancestor of `toRemove`
      /// @dev we need to provide the previous implementation here to be able to successfully "stitch"
      ///       the chain back together. Because this is an admin action, we can source what the previous
      ///       version is from events.
      /// @param toRemove Implementation to remove
      /// @param previous Implementation that currently has `toRemove` as its successor
      function remove(address toRemove, address previous) external onlyAdmin whenNotPaused {
        _remove(toRemove, previous);
      }
      // //////// External view ////////////////////////////////////////////////////////////
      /// @notice Returns `true` if an implementation has a next implementation set
      /// @param implementation implementation to check
      /// @return The implementation following the given implementation
      function hasNext(address implementation) external view returns (bool) {
        return _nextImplementationOf[implementation] != INVALID_IMPL;
      }
      /// @notice Returns `true` if an implementation has already been added
      /// @param implementation Implementation to check existence of
      /// @return `true` if the implementation has already been added
      function has(address implementation) external view returns (bool) {
        return _has(implementation);
      }
      /// @notice Get the next implementation for a given implementation or
      ///           `address(0)` if it doesn't exist
      /// @dev reverts when contract is paused
      /// @param implementation implementation to get the upgraded implementation for
      /// @return Next Implementation
      function nextImplementationOf(
        address implementation
      ) external view whenNotPaused returns (address) {
        return _nextImplementationOf[implementation];
      }
      /// @notice Returns `true` if a given lineageId exists
      function lineageExists(uint256 lineageId) external view returns (bool) {
        return _lineageExists(lineageId);
      }
      /// @notice Return the current implementation of a lineage with the given `lineageId`
      function currentImplementation(uint256 lineageId) external view whenNotPaused returns (address) {
        return _currentImplementation(lineageId);
      }
      /// @notice return current implementaton of the current lineage
      function currentImplementation() external view whenNotPaused returns (address) {
        return _currentImplementation(currentLineageId);
      }
      // //////// Internal ////////////////////////////////////////////////////////////
      function _setUpgradeDataFor(address implementation, bytes memory data) internal {
        require(_has(implementation), "unknown impl");
        upgradeDataFor[implementation] = data;
        emit UpgradeDataSet(implementation, data);
      }
      function _createLineage(address implementation) internal virtual returns (uint256) {
        require(Address.isContract(implementation), "not a contract");
        // NOTE: impractical to overflow
        currentLineageId += 1;
        _currentOfLineage[currentLineageId] = implementation;
        lineageIdOf[implementation] = currentLineageId;
        emit Added(currentLineageId, implementation, address(0));
        return currentLineageId;
      }
      function _currentImplementation(uint256 lineageId) internal view returns (address) {
        return _currentOfLineage[lineageId];
      }
      /// @notice Returns `true` if an implementation has already been added
      /// @param implementation implementation to check for
      /// @return `true` if the implementation has already been added
      function _has(address implementation) internal view virtual returns (bool) {
        return lineageIdOf[implementation] != INVALID_LINEAGE_ID;
      }
      /// @notice Set an implementation to the current implementation
      /// @param implementation implementation to set as current implementation
      /// @param lineageId id of lineage to append to
      function _append(address implementation, uint256 lineageId) internal virtual {
        require(Address.isContract(implementation), "not a contract");
        require(!_has(implementation), "exists");
        require(_lineageExists(lineageId), "invalid lineageId");
        require(_currentOfLineage[lineageId] != INVALID_IMPL, "empty lineage");
        address oldImplementation = _currentOfLineage[lineageId];
        _currentOfLineage[lineageId] = implementation;
        lineageIdOf[implementation] = lineageId;
        _nextImplementationOf[oldImplementation] = implementation;
        emit Added(lineageId, implementation, oldImplementation);
      }
      function _remove(address toRemove, address previous) internal virtual {
        require(toRemove != INVALID_IMPL && previous != INVALID_IMPL, "ZERO");
        require(_nextImplementationOf[previous] == toRemove, "Not prev");
        uint256 lineageId = lineageIdOf[toRemove];
        // need to reset the head pointer to the previous version if we remove the head
        if (toRemove == _currentOfLineage[lineageId]) {
          _currentOfLineage[lineageId] = previous;
        }
        _setUpgradeDataFor(toRemove, ""); // reset upgrade data
        _nextImplementationOf[previous] = _nextImplementationOf[toRemove];
        _nextImplementationOf[toRemove] = INVALID_IMPL;
        lineageIdOf[toRemove] = INVALID_LINEAGE_ID;
        emit Removed(lineageId, toRemove);
      }
      function _lineageExists(uint256 lineageId) internal view returns (bool) {
        return lineageId != INVALID_LINEAGE_ID && lineageId <= currentLineageId;
      }
      // //////// Events //////////////////////////////////////////////////////////////
      event Added(
        uint256 indexed lineageId,
        address indexed newImplementation,
        address indexed oldImplementation
      );
      event Removed(uint256 indexed lineageId, address indexed implementation);
      event UpgradeDataSet(address indexed implementation, bytes data);
    }