ETH Price: $2,761.95 (-1.15%)

Transaction Decoder

Block:
14214879 at Feb-16-2022 03:51:28 AM +UTC
Transaction Fee:
0.0184115291841824 ETH $50.85
Gas Used:
194,075 Gas / 94.868113792 Gwei

Emitted Events:

150 AdminUpgradeabilityProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000001a13b10c13650ee3c33f0d6488a84cbb8603b47e, 0000000000000000000000000000000000000000000000002a8b812039da1a6c )
151 AdminUpgradeabilityProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000001a13b10c13650ee3c33f0d6488a84cbb8603b47e, 0x00000000000000000000000036a1bcb47960c0e19de421f4715b41f87e789dbc, 00000000000000000000000000000000000000000000000000dea8b6d8e0e5ed )
152 AdminUpgradeabilityProxy.0xd6f2c8500df5b44f11e9e48b91ff9f1b9d81bc496d55570c2b1b75bf65243f51( 0xd6f2c8500df5b44f11e9e48b91ff9f1b9d81bc496d55570c2b1b75bf65243f51, 0x00000000000000000000000036a1bcb47960c0e19de421f4715b41f87e789dbc, 0x0000000000000000000000000000000000000000000000000000000000000005, 00000000000000000000000000000000000000000000000000dea8b6d8e0e5ed )
153 AdminUpgradeabilityProxy.0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568( 0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568, 0x00000000000000000000000036a1bcb47960c0e19de421f4715b41f87e789dbc, 0x0000000000000000000000000000000000000000000000000000000000000005, 00000000000000000000000000000000000000000000014a7e85079d67c8cbf9 )
154 UniswapV2Pair.Transfer( from=[Receiver] AdminUpgradeabilityProxy, to=[Sender] 0x36a1bcb47960c0e19de421f4715b41f87e789dbc, value=6096542245717465943033 )

Account State Difference:

  Address   Before After State Difference Code
0x18A5c094...A70cfcDDD
0x1A13B10C...B8603B47E
0x36A1Bcb4...87E789Dbc
0.221635657948545053 Eth
Nonce: 52
0.203224128764362653 Eth
Nonce: 53
0.0184115291841824
0x7fC3eC35...2575EBF2b
(F2Pool Old)
1,361.215338756028371676 Eth1,361.215629868528371676 Eth0.0002911125
0xa8aC2975...99BB4FF96

Execution Trace

AdminUpgradeabilityProxy.8244bd15( )
  • Mlp.directWithdraw( _amount=6096542245717465943033 )
    • AdminUpgradeabilityProxy.2f8d3a66( )
      • PopReward.getPoolSupply( pool=0xa8aC2975d4AB7D48124DF89C37E25DE99BB4FF96 ) => ( 298216106472218564014705 )
      • AdminUpgradeabilityProxy.2f8d3a66( )
        • PopReward.getPoolSupply( pool=0xa8aC2975d4AB7D48124DF89C37E25DE99BB4FF96 ) => ( 298216106472218564014705 )
        • AdminUpgradeabilityProxy.9f191659( )
          • PopReward.getUserAmount( pool=0xa8aC2975d4AB7D48124DF89C37E25DE99BB4FF96, user=0x36A1Bcb47960C0e19DE421F4715b41F87E789Dbc ) => ( 6096542245717465943033 )
          • AdminUpgradeabilityProxy.2f8d3a66( )
            • PopReward.getPoolSupply( pool=0xa8aC2975d4AB7D48124DF89C37E25DE99BB4FF96 ) => ( 298216106472218564014705 )
            • AdminUpgradeabilityProxy.2f8d3a66( )
              • PopReward.getPoolSupply( pool=0xa8aC2975d4AB7D48124DF89C37E25DE99BB4FF96 ) => ( 298216106472218564014705 )
              • AdminUpgradeabilityProxy.8600e40b( )
                • PopReward.notifyWithdraw( _account=0x36A1Bcb47960C0e19DE421F4715b41F87E789Dbc, _amount=6096542245717465943033 )
                  • AdminUpgradeabilityProxy.449a52f8( )
                    • PopTokenV3.mintTo( _account=0x1A13B10C13650eE3C33F0D6488a84CBB8603B47E, _amount=3065685946765285996 )
                    • AdminUpgradeabilityProxy.70a08231( )
                      • PopTokenV3.balanceOf( account=0x1A13B10C13650eE3C33F0D6488a84CBB8603B47E ) => ( 1956486399460569024799929 )
                      • AdminUpgradeabilityProxy.a9059cbb( )
                        • PopTokenV3.transfer( recipient=0x36A1Bcb47960C0e19DE421F4715b41F87E789Dbc, amount=62672948105897453 ) => ( True )
                        • UniswapV2Pair.transfer( to=0x36A1Bcb47960C0e19DE421F4715b41F87E789Dbc, value=6096542245717465943033 ) => ( True )
                          File 1 of 7: AdminUpgradeabilityProxy
                          pragma solidity 0.6.6;
                          
                          
                          // SPDX-License-Identifier: MIT
                          /**
                           * @title Proxy
                           * @dev Implements delegation of calls to other contracts, with proper
                           * forwarding of return values and bubbling of failures.
                           * It defines a fallback function that delegates all calls to the address
                           * returned by the abstract _implementation() internal function.
                           */
                          abstract contract Proxy {
                              /**
                               * @dev Fallback function.
                               * Implemented entirely in `_fallback`.
                               */
                              fallback() external payable {
                                  _fallback();
                              }
                          
                              /**
                               * @dev Receive function.
                               * Implemented entirely in `_fallback`.
                               */
                              receive() external payable {
                                  _fallback();
                              }
                          
                              /**
                               * @return The Address of the implementation.
                               */
                              function _implementation() internal view virtual returns (address);
                          
                              /**
                               * @dev Delegates execution to an implementation contract.
                               * This is a low level function that doesn't return to its internal call site.
                               * It will return to the external caller whatever the implementation returns.
                               * @param implementation Address to delegate.
                               */
                              function _delegate(address implementation) internal {
                                  assembly {
                                      // Copy msg.data. We take full control of memory in this inline assembly
                                      // block because it will not return to Solidity code. We overwrite the
                                      // Solidity scratch pad at memory position 0.
                                      calldatacopy(0, 0, calldatasize())
                          
                                      // Call the implementation.
                                      // out and outsize are 0 because we don't know the size yet.
                                      let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                          
                                      // Copy the returned data.
                                      returndatacopy(0, 0, returndatasize())
                          
                                      switch result
                                          // delegatecall returns 0 on error.
                                          case 0 {
                                              revert(0, returndatasize())
                                          }
                                          default {
                                              return(0, returndatasize())
                                          }
                                  }
                              }
                          
                              /**
                               * @dev Function that is run as the first thing in the fallback function.
                               * Can be redefined in derived contracts to add functionality.
                               * Redefinitions must call super._willFallback().
                               */
                              function _willFallback() internal virtual {}
                          
                              /**
                               * @dev fallback implementation.
                               * Extracted to enable manual triggering.
                               */
                              function _fallback() internal {
                                  _willFallback();
                                  _delegate(_implementation());
                              }
                          }
                          
                          /**
                           * @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 in 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");
                                  return _functionCallWithValue(target, data, value, errorMessage);
                              }
                          
                              function _functionCallWithValue(
                                  address target,
                                  bytes memory data,
                                  uint256 weiValue,
                                  string memory errorMessage
                              ) private returns (bytes memory) {
                                  require(isContract(target), "Address: call to non-contract");
                          
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (bool success, bytes memory returndata) = target.call{value: weiValue}(data);
                                  if (success) {
                                      return returndata;
                                  } else {
                                      // Look for revert reason and bubble it up if present
                                      if (returndata.length > 0) {
                                          // The easiest way to bubble the revert reason is using memory via assembly
                          
                                          // solhint-disable-next-line no-inline-assembly
                                          assembly {
                                              let returndata_size := mload(returndata)
                                              revert(add(32, returndata), returndata_size)
                                          }
                                      } else {
                                          revert(errorMessage);
                                      }
                                  }
                              }
                          }
                          
                          /**
                           * @title UpgradeabilityProxy
                           * @dev This contract implements a proxy that allows to change the
                           * implementation address to which it will delegate.
                           * Such a change is called an implementation upgrade.
                           */
                          contract UpgradeabilityProxy is Proxy {
                              /**
                               * @dev Contract constructor.
                               * @param _logic Address of the initial implementation.
                               * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                               */
                              constructor(address _logic, bytes memory _data) public payable {
                                  assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                                  _setImplementation(_logic);
                                  if (_data.length > 0) {
                                      (bool success, ) = _logic.delegatecall(_data);
                                      require(success);
                                  }
                              }
                          
                              /**
                               * @dev Emitted when the implementation is upgraded.
                               * @param implementation Address of the new implementation.
                               */
                              event Upgraded(address indexed implementation);
                          
                              /**
                               * @dev Storage slot with the address of the current implementation.
                               * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                               * validated in the constructor.
                               */
                              bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                          
                              /**
                               * @dev Returns the current implementation.
                               * @return impl Address of the current implementation
                               */
                              function _implementation() internal view override returns (address impl) {
                                  bytes32 slot = IMPLEMENTATION_SLOT;
                                  assembly {
                                      impl := sload(slot)
                                  }
                              }
                          
                              /**
                               * @dev Upgrades the proxy to a new implementation.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _upgradeTo(address newImplementation) internal {
                                  _setImplementation(newImplementation);
                                  emit Upgraded(newImplementation);
                              }
                          
                              /**
                               * @dev Sets the implementation address of the proxy.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _setImplementation(address newImplementation) internal {
                                  require(Address.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                          
                                  bytes32 slot = IMPLEMENTATION_SLOT;
                          
                                  assembly {
                                      sstore(slot, newImplementation)
                                  }
                              }
                          }
                          
                          /**
                           * @title AdminUpgradeabilityProxy
                           * @dev This contract combines an upgradeability proxy with an authorization
                           * mechanism for administrative tasks.
                           * All external functions in this contract must be guarded by the
                           * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                           * feature proposal that would enable this to be done automatically.
                           */
                          contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
                              /**
                               * Contract constructor.
                               * @param _logic address of the initial implementation.
                               * @param _admin Address of the proxy administrator.
                               * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                               */
                              constructor(
                                  address _logic,
                                  address _admin,
                                  bytes memory _data
                              ) public payable UpgradeabilityProxy(_logic, _data) {
                                  assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                                  _setAdmin(_admin);
                              }
                          
                              /**
                               * @dev Emitted when the administration has been transferred.
                               * @param previousAdmin Address of the previous admin.
                               * @param newAdmin Address of the new admin.
                               */
                              event AdminChanged(address previousAdmin, address newAdmin);
                          
                              /**
                               * @dev Storage slot with the admin of the contract.
                               * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                               * validated in the constructor.
                               */
                          
                              bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                          
                              /**
                               * @dev Modifier to check whether the `msg.sender` is the admin.
                               * If it is, it will run the function. Otherwise, it will delegate the call
                               * to the implementation.
                               */
                              modifier ifAdmin() {
                                  if (msg.sender == _admin()) {
                                      _;
                                  } else {
                                      _fallback();
                                  }
                              }
                          
                              /**
                               * @return The address of the proxy admin.
                               */
                              function admin() external ifAdmin returns (address) {
                                  return _admin();
                              }
                          
                              /**
                               * @return The address of the implementation.
                               */
                              function implementation() external ifAdmin returns (address) {
                                  return _implementation();
                              }
                          
                              /**
                               * @dev Changes the admin of the proxy.
                               * Only the current admin can call this function.
                               * @param newAdmin Address to transfer proxy administration to.
                               */
                              function changeAdmin(address newAdmin) external ifAdmin {
                                  require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                                  emit AdminChanged(_admin(), newAdmin);
                                  _setAdmin(newAdmin);
                              }
                          
                              /**
                               * @dev Upgrade the backing implementation of the proxy.
                               * Only the admin can call this function.
                               * @param newImplementation Address of the new implementation.
                               */
                              function upgradeTo(address newImplementation) external ifAdmin {
                                  _upgradeTo(newImplementation);
                              }
                          
                              /**
                               * @dev Upgrade the backing implementation of the proxy and call a function
                               * on the new implementation.
                               * This is useful to initialize the proxied contract.
                               * @param newImplementation Address of the new implementation.
                               * @param data Data to send as msg.data in the low level call.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               */
                              function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                                  _upgradeTo(newImplementation);
                                  (bool success, ) = newImplementation.delegatecall(data);
                                  require(success);
                              }
                          
                              /**
                               * @return adm The admin slot.
                               */
                              function _admin() internal view returns (address adm) {
                                  bytes32 slot = ADMIN_SLOT;
                                  assembly {
                                      adm := sload(slot)
                                  }
                              }
                          
                              /**
                               * @dev Sets the address of the proxy admin.
                               * @param newAdmin Address of the new proxy admin.
                               */
                              function _setAdmin(address newAdmin) internal {
                                  bytes32 slot = ADMIN_SLOT;
                          
                                  assembly {
                                      sstore(slot, newAdmin)
                                  }
                              }
                          
                              /**
                               * @dev Only fall back when the sender is not the admin.
                               */
                              function _willFallback() internal virtual override {
                                  require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                                  super._willFallback();
                              }
                          }

                          File 2 of 7: AdminUpgradeabilityProxy
                          // SPDX-License-Identifier: MIT
                          
                          pragma solidity 0.6.12;
                          
                          /**
                           * @title Proxy
                           * @dev Implements delegation of calls to other contracts, with proper
                           * forwarding of return values and bubbling of failures.
                           * It defines a fallback function that delegates all calls to the address
                           * returned by the abstract _implementation() internal function.
                           */
                          abstract contract Proxy {
                              /**
                               * @dev Fallback function.
                               * Implemented entirely in `_fallback`.
                               */
                              fallback() external payable {
                                  _fallback();
                              }
                          
                              /**
                               * @dev Receive function.
                               * Implemented entirely in `_fallback`.
                               */
                              receive() external payable {
                                  _fallback();
                              }
                          
                              /**
                               * @return The Address of the implementation.
                               */
                              function _implementation() internal view virtual returns (address);
                          
                              /**
                               * @dev Delegates execution to an implementation contract.
                               * This is a low level function that doesn't return to its internal call site.
                               * It will return to the external caller whatever the implementation returns.
                               * @param implementation Address to delegate.
                               */
                              function _delegate(address implementation) internal {
                                  assembly {
                                      // Copy msg.data. We take full control of memory in this inline assembly
                                      // block because it will not return to Solidity code. We overwrite the
                                      // Solidity scratch pad at memory position 0.
                                      calldatacopy(0, 0, calldatasize())
                          
                                      // Call the implementation.
                                      // out and outsize are 0 because we don't know the size yet.
                                      let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                          
                                      // Copy the returned data.
                                      returndatacopy(0, 0, returndatasize())
                          
                                      switch result
                                          // delegatecall returns 0 on error.
                                          case 0 {
                                              revert(0, returndatasize())
                                          }
                                          default {
                                              return(0, returndatasize())
                                          }
                                  }
                              }
                          
                              /**
                               * @dev Function that is run as the first thing in the fallback function.
                               * Can be redefined in derived contracts to add functionality.
                               * Redefinitions must call super._willFallback().
                               */
                              function _willFallback() internal virtual {}
                          
                              /**
                               * @dev fallback implementation.
                               * Extracted to enable manual triggering.
                               */
                              function _fallback() internal {
                                  _willFallback();
                                  _delegate(_implementation());
                              }
                          }
                          
                          /**
                           * @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 in 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");
                                  return _functionCallWithValue(target, data, value, errorMessage);
                              }
                          
                              function _functionCallWithValue(
                                  address target,
                                  bytes memory data,
                                  uint256 weiValue,
                                  string memory errorMessage
                              ) private returns (bytes memory) {
                                  require(isContract(target), "Address: call to non-contract");
                          
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (bool success, bytes memory returndata) = target.call{value: weiValue}(data);
                                  if (success) {
                                      return returndata;
                                  } else {
                                      // Look for revert reason and bubble it up if present
                                      if (returndata.length > 0) {
                                          // The easiest way to bubble the revert reason is using memory via assembly
                          
                                          // solhint-disable-next-line no-inline-assembly
                                          assembly {
                                              let returndata_size := mload(returndata)
                                              revert(add(32, returndata), returndata_size)
                                          }
                                      } else {
                                          revert(errorMessage);
                                      }
                                  }
                              }
                          }
                          
                          /**
                           * @title UpgradeabilityProxy
                           * @dev This contract implements a proxy that allows to change the
                           * implementation address to which it will delegate.
                           * Such a change is called an implementation upgrade.
                           */
                          contract UpgradeabilityProxy is Proxy {
                              /**
                               * @dev Contract constructor.
                               * @param _logic Address of the initial implementation.
                               * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                               */
                              constructor(address _logic, bytes memory _data) public payable {
                                  assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                                  _setImplementation(_logic);
                                  if (_data.length > 0) {
                                      (bool success, ) = _logic.delegatecall(_data);
                                      require(success);
                                  }
                              }
                          
                              /**
                               * @dev Emitted when the implementation is upgraded.
                               * @param implementation Address of the new implementation.
                               */
                              event Upgraded(address indexed implementation);
                          
                              /**
                               * @dev Storage slot with the address of the current implementation.
                               * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                               * validated in the constructor.
                               */
                              bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                          
                              /**
                               * @dev Returns the current implementation.
                               * @return impl Address of the current implementation
                               */
                              function _implementation() internal view override returns (address impl) {
                                  bytes32 slot = IMPLEMENTATION_SLOT;
                                  assembly {
                                      impl := sload(slot)
                                  }
                              }
                          
                              /**
                               * @dev Upgrades the proxy to a new implementation.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _upgradeTo(address newImplementation) internal {
                                  _setImplementation(newImplementation);
                                  emit Upgraded(newImplementation);
                              }
                          
                              /**
                               * @dev Sets the implementation address of the proxy.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _setImplementation(address newImplementation) internal {
                                  require(Address.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                          
                                  bytes32 slot = IMPLEMENTATION_SLOT;
                          
                                  assembly {
                                      sstore(slot, newImplementation)
                                  }
                              }
                          }
                          
                          /**
                           * @title AdminUpgradeabilityProxy
                           * @dev This contract combines an upgradeability proxy with an authorization
                           * mechanism for administrative tasks.
                           * All external functions in this contract must be guarded by the
                           * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                           * feature proposal that would enable this to be done automatically.
                           */
                          contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
                              /**
                               * Contract constructor.
                               * @param _logic address of the initial implementation.
                               * @param _admin Address of the proxy administrator.
                               * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                               */
                              constructor(
                                  address _logic,
                                  address _admin,
                                  bytes memory _data
                              ) public payable UpgradeabilityProxy(_logic, _data) {
                                  assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                                  _setAdmin(_admin);
                              }
                          
                              /**
                               * @dev Emitted when the administration has been transferred.
                               * @param previousAdmin Address of the previous admin.
                               * @param newAdmin Address of the new admin.
                               */
                              event AdminChanged(address previousAdmin, address newAdmin);
                          
                              /**
                               * @dev Storage slot with the admin of the contract.
                               * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                               * validated in the constructor.
                               */
                          
                              bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                          
                              /**
                               * @dev Modifier to check whether the `msg.sender` is the admin.
                               * If it is, it will run the function. Otherwise, it will delegate the call
                               * to the implementation.
                               */
                              modifier ifAdmin() {
                                  if (msg.sender == _admin()) {
                                      _;
                                  } else {
                                      _fallback();
                                  }
                              }
                          
                              /**
                               * @return The address of the proxy admin.
                               */
                              function admin() external ifAdmin returns (address) {
                                  return _admin();
                              }
                          
                              /**
                               * @return The address of the implementation.
                               */
                              function implementation() external ifAdmin returns (address) {
                                  return _implementation();
                              }
                          
                              /**
                               * @dev Changes the admin of the proxy.
                               * Only the current admin can call this function.
                               * @param newAdmin Address to transfer proxy administration to.
                               */
                              function changeAdmin(address newAdmin) external ifAdmin {
                                  require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                                  emit AdminChanged(_admin(), newAdmin);
                                  _setAdmin(newAdmin);
                              }
                          
                              /**
                               * @dev Upgrade the backing implementation of the proxy.
                               * Only the admin can call this function.
                               * @param newImplementation Address of the new implementation.
                               */
                              function upgradeTo(address newImplementation) external ifAdmin {
                                  _upgradeTo(newImplementation);
                              }
                          
                              /**
                               * @dev Upgrade the backing implementation of the proxy and call a function
                               * on the new implementation.
                               * This is useful to initialize the proxied contract.
                               * @param newImplementation Address of the new implementation.
                               * @param data Data to send as msg.data in the low level call.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               */
                              function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                                  _upgradeTo(newImplementation);
                                  (bool success, ) = newImplementation.delegatecall(data);
                                  require(success);
                              }
                          
                              /**
                               * @return adm The admin slot.
                               */
                              function _admin() internal view returns (address adm) {
                                  bytes32 slot = ADMIN_SLOT;
                                  assembly {
                                      adm := sload(slot)
                                  }
                              }
                          
                              /**
                               * @dev Sets the address of the proxy admin.
                               * @param newAdmin Address of the new proxy admin.
                               */
                              function _setAdmin(address newAdmin) internal {
                                  bytes32 slot = ADMIN_SLOT;
                          
                                  assembly {
                                      sstore(slot, newAdmin)
                                  }
                              }
                          
                              /**
                               * @dev Only fall back when the sender is not the admin.
                               */
                              function _willFallback() internal virtual override {
                                  require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                                  super._willFallback();
                              }
                          }

                          File 3 of 7: AdminUpgradeabilityProxy
                          // SPDX-License-Identifier: MIT
                          
                          pragma solidity 0.6.12;
                          
                          /**
                           * @title Proxy
                           * @dev Implements delegation of calls to other contracts, with proper
                           * forwarding of return values and bubbling of failures.
                           * It defines a fallback function that delegates all calls to the address
                           * returned by the abstract _implementation() internal function.
                           */
                          abstract contract Proxy {
                              /**
                               * @dev Fallback function.
                               * Implemented entirely in `_fallback`.
                               */
                              fallback() external payable {
                                  _fallback();
                              }
                          
                              /**
                               * @dev Receive function.
                               * Implemented entirely in `_fallback`.
                               */
                              receive() external payable {
                                  _fallback();
                              }
                          
                              /**
                               * @return The Address of the implementation.
                               */
                              function _implementation() internal view virtual returns (address);
                          
                              /**
                               * @dev Delegates execution to an implementation contract.
                               * This is a low level function that doesn't return to its internal call site.
                               * It will return to the external caller whatever the implementation returns.
                               * @param implementation Address to delegate.
                               */
                              function _delegate(address implementation) internal {
                                  assembly {
                                      // Copy msg.data. We take full control of memory in this inline assembly
                                      // block because it will not return to Solidity code. We overwrite the
                                      // Solidity scratch pad at memory position 0.
                                      calldatacopy(0, 0, calldatasize())
                          
                                      // Call the implementation.
                                      // out and outsize are 0 because we don't know the size yet.
                                      let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                          
                                      // Copy the returned data.
                                      returndatacopy(0, 0, returndatasize())
                          
                                      switch result
                                          // delegatecall returns 0 on error.
                                          case 0 {
                                              revert(0, returndatasize())
                                          }
                                          default {
                                              return(0, returndatasize())
                                          }
                                  }
                              }
                          
                              /**
                               * @dev Function that is run as the first thing in the fallback function.
                               * Can be redefined in derived contracts to add functionality.
                               * Redefinitions must call super._willFallback().
                               */
                              function _willFallback() internal virtual {}
                          
                              /**
                               * @dev fallback implementation.
                               * Extracted to enable manual triggering.
                               */
                              function _fallback() internal {
                                  _willFallback();
                                  _delegate(_implementation());
                              }
                          }
                          
                          /**
                           * @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 in 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");
                                  return _functionCallWithValue(target, data, value, errorMessage);
                              }
                          
                              function _functionCallWithValue(
                                  address target,
                                  bytes memory data,
                                  uint256 weiValue,
                                  string memory errorMessage
                              ) private returns (bytes memory) {
                                  require(isContract(target), "Address: call to non-contract");
                          
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (bool success, bytes memory returndata) = target.call{value: weiValue}(data);
                                  if (success) {
                                      return returndata;
                                  } else {
                                      // Look for revert reason and bubble it up if present
                                      if (returndata.length > 0) {
                                          // The easiest way to bubble the revert reason is using memory via assembly
                          
                                          // solhint-disable-next-line no-inline-assembly
                                          assembly {
                                              let returndata_size := mload(returndata)
                                              revert(add(32, returndata), returndata_size)
                                          }
                                      } else {
                                          revert(errorMessage);
                                      }
                                  }
                              }
                          }
                          
                          /**
                           * @title UpgradeabilityProxy
                           * @dev This contract implements a proxy that allows to change the
                           * implementation address to which it will delegate.
                           * Such a change is called an implementation upgrade.
                           */
                          contract UpgradeabilityProxy is Proxy {
                              /**
                               * @dev Contract constructor.
                               * @param _logic Address of the initial implementation.
                               * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                               */
                              constructor(address _logic, bytes memory _data) public payable {
                                  assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                                  _setImplementation(_logic);
                                  if (_data.length > 0) {
                                      (bool success, ) = _logic.delegatecall(_data);
                                      require(success);
                                  }
                              }
                          
                              /**
                               * @dev Emitted when the implementation is upgraded.
                               * @param implementation Address of the new implementation.
                               */
                              event Upgraded(address indexed implementation);
                          
                              /**
                               * @dev Storage slot with the address of the current implementation.
                               * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                               * validated in the constructor.
                               */
                              bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                          
                              /**
                               * @dev Returns the current implementation.
                               * @return impl Address of the current implementation
                               */
                              function _implementation() internal view override returns (address impl) {
                                  bytes32 slot = IMPLEMENTATION_SLOT;
                                  assembly {
                                      impl := sload(slot)
                                  }
                              }
                          
                              /**
                               * @dev Upgrades the proxy to a new implementation.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _upgradeTo(address newImplementation) internal {
                                  _setImplementation(newImplementation);
                                  emit Upgraded(newImplementation);
                              }
                          
                              /**
                               * @dev Sets the implementation address of the proxy.
                               * @param newImplementation Address of the new implementation.
                               */
                              function _setImplementation(address newImplementation) internal {
                                  require(Address.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                          
                                  bytes32 slot = IMPLEMENTATION_SLOT;
                          
                                  assembly {
                                      sstore(slot, newImplementation)
                                  }
                              }
                          }
                          
                          /**
                           * @title AdminUpgradeabilityProxy
                           * @dev This contract combines an upgradeability proxy with an authorization
                           * mechanism for administrative tasks.
                           * All external functions in this contract must be guarded by the
                           * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                           * feature proposal that would enable this to be done automatically.
                           */
                          contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
                              /**
                               * Contract constructor.
                               * @param _logic address of the initial implementation.
                               * @param _admin Address of the proxy administrator.
                               * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                               */
                              constructor(
                                  address _logic,
                                  address _admin,
                                  bytes memory _data
                              ) public payable UpgradeabilityProxy(_logic, _data) {
                                  assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                                  _setAdmin(_admin);
                              }
                          
                              /**
                               * @dev Emitted when the administration has been transferred.
                               * @param previousAdmin Address of the previous admin.
                               * @param newAdmin Address of the new admin.
                               */
                              event AdminChanged(address previousAdmin, address newAdmin);
                          
                              /**
                               * @dev Storage slot with the admin of the contract.
                               * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                               * validated in the constructor.
                               */
                          
                              bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                          
                              /**
                               * @dev Modifier to check whether the `msg.sender` is the admin.
                               * If it is, it will run the function. Otherwise, it will delegate the call
                               * to the implementation.
                               */
                              modifier ifAdmin() {
                                  if (msg.sender == _admin()) {
                                      _;
                                  } else {
                                      _fallback();
                                  }
                              }
                          
                              /**
                               * @return The address of the proxy admin.
                               */
                              function admin() external ifAdmin returns (address) {
                                  return _admin();
                              }
                          
                              /**
                               * @return The address of the implementation.
                               */
                              function implementation() external ifAdmin returns (address) {
                                  return _implementation();
                              }
                          
                              /**
                               * @dev Changes the admin of the proxy.
                               * Only the current admin can call this function.
                               * @param newAdmin Address to transfer proxy administration to.
                               */
                              function changeAdmin(address newAdmin) external ifAdmin {
                                  require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                                  emit AdminChanged(_admin(), newAdmin);
                                  _setAdmin(newAdmin);
                              }
                          
                              /**
                               * @dev Upgrade the backing implementation of the proxy.
                               * Only the admin can call this function.
                               * @param newImplementation Address of the new implementation.
                               */
                              function upgradeTo(address newImplementation) external ifAdmin {
                                  _upgradeTo(newImplementation);
                              }
                          
                              /**
                               * @dev Upgrade the backing implementation of the proxy and call a function
                               * on the new implementation.
                               * This is useful to initialize the proxied contract.
                               * @param newImplementation Address of the new implementation.
                               * @param data Data to send as msg.data in the low level call.
                               * It should include the signature and the parameters of the function to be called, as described in
                               * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                               */
                              function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                                  _upgradeTo(newImplementation);
                                  (bool success, ) = newImplementation.delegatecall(data);
                                  require(success);
                              }
                          
                              /**
                               * @return adm The admin slot.
                               */
                              function _admin() internal view returns (address adm) {
                                  bytes32 slot = ADMIN_SLOT;
                                  assembly {
                                      adm := sload(slot)
                                  }
                              }
                          
                              /**
                               * @dev Sets the address of the proxy admin.
                               * @param newAdmin Address of the new proxy admin.
                               */
                              function _setAdmin(address newAdmin) internal {
                                  bytes32 slot = ADMIN_SLOT;
                          
                                  assembly {
                                      sstore(slot, newAdmin)
                                  }
                              }
                          
                              /**
                               * @dev Only fall back when the sender is not the admin.
                               */
                              function _willFallback() internal virtual override {
                                  require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                                  super._willFallback();
                              }
                          }

                          File 4 of 7: UniswapV2Pair
                          // File: contracts/interfaces/IUniswapV2Pair.sol
                          
                          pragma solidity >=0.5.0;
                          
                          interface IUniswapV2Pair {
                              event Approval(address indexed owner, address indexed spender, uint value);
                              event Transfer(address indexed from, address indexed to, uint value);
                          
                              function name() external pure returns (string memory);
                              function symbol() external pure returns (string memory);
                              function decimals() external pure returns (uint8);
                              function totalSupply() external view returns (uint);
                              function balanceOf(address owner) external view returns (uint);
                              function allowance(address owner, address spender) external view returns (uint);
                          
                              function approve(address spender, uint value) external returns (bool);
                              function transfer(address to, uint value) external returns (bool);
                              function transferFrom(address from, address to, uint value) external returns (bool);
                          
                              function DOMAIN_SEPARATOR() external view returns (bytes32);
                              function PERMIT_TYPEHASH() external pure returns (bytes32);
                              function nonces(address owner) external view returns (uint);
                          
                              function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
                          
                              event Mint(address indexed sender, uint amount0, uint amount1);
                              event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
                              event Swap(
                                  address indexed sender,
                                  uint amount0In,
                                  uint amount1In,
                                  uint amount0Out,
                                  uint amount1Out,
                                  address indexed to
                              );
                              event Sync(uint112 reserve0, uint112 reserve1);
                          
                              function MINIMUM_LIQUIDITY() external pure returns (uint);
                              function factory() external view returns (address);
                              function token0() external view returns (address);
                              function token1() external view returns (address);
                              function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
                              function price0CumulativeLast() external view returns (uint);
                              function price1CumulativeLast() external view returns (uint);
                              function kLast() external view returns (uint);
                          
                              function mint(address to) external returns (uint liquidity);
                              function burn(address to) external returns (uint amount0, uint amount1);
                              function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
                              function skim(address to) external;
                              function sync() external;
                          
                              function initialize(address, address) external;
                          }
                          
                          // File: contracts/interfaces/IUniswapV2ERC20.sol
                          
                          pragma solidity >=0.5.0;
                          
                          interface IUniswapV2ERC20 {
                              event Approval(address indexed owner, address indexed spender, uint value);
                              event Transfer(address indexed from, address indexed to, uint value);
                          
                              function name() external pure returns (string memory);
                              function symbol() external pure returns (string memory);
                              function decimals() external pure returns (uint8);
                              function totalSupply() external view returns (uint);
                              function balanceOf(address owner) external view returns (uint);
                              function allowance(address owner, address spender) external view returns (uint);
                          
                              function approve(address spender, uint value) external returns (bool);
                              function transfer(address to, uint value) external returns (bool);
                              function transferFrom(address from, address to, uint value) external returns (bool);
                          
                              function DOMAIN_SEPARATOR() external view returns (bytes32);
                              function PERMIT_TYPEHASH() external pure returns (bytes32);
                              function nonces(address owner) external view returns (uint);
                          
                              function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
                          }
                          
                          // File: contracts/libraries/SafeMath.sol
                          
                          pragma solidity =0.5.16;
                          
                          // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
                          
                          library SafeMath {
                              function add(uint x, uint y) internal pure returns (uint z) {
                                  require((z = x + y) >= x, 'ds-math-add-overflow');
                              }
                          
                              function sub(uint x, uint y) internal pure returns (uint z) {
                                  require((z = x - y) <= x, 'ds-math-sub-underflow');
                              }
                          
                              function mul(uint x, uint y) internal pure returns (uint z) {
                                  require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
                              }
                          }
                          
                          // File: contracts/UniswapV2ERC20.sol
                          
                          pragma solidity =0.5.16;
                          
                          
                          
                          contract UniswapV2ERC20 is IUniswapV2ERC20 {
                              using SafeMath for uint;
                          
                              string public constant name = 'Uniswap V2';
                              string public constant symbol = 'UNI-V2';
                              uint8 public constant decimals = 18;
                              uint  public totalSupply;
                              mapping(address => uint) public balanceOf;
                              mapping(address => mapping(address => uint)) public allowance;
                          
                              bytes32 public DOMAIN_SEPARATOR;
                              // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
                              bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
                              mapping(address => uint) public nonces;
                          
                              event Approval(address indexed owner, address indexed spender, uint value);
                              event Transfer(address indexed from, address indexed to, uint value);
                          
                              constructor() public {
                                  uint chainId;
                                  assembly {
                                      chainId := chainid
                                  }
                                  DOMAIN_SEPARATOR = keccak256(
                                      abi.encode(
                                          keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
                                          keccak256(bytes(name)),
                                          keccak256(bytes('1')),
                                          chainId,
                                          address(this)
                                      )
                                  );
                              }
                          
                              function _mint(address to, uint value) internal {
                                  totalSupply = totalSupply.add(value);
                                  balanceOf[to] = balanceOf[to].add(value);
                                  emit Transfer(address(0), to, value);
                              }
                          
                              function _burn(address from, uint value) internal {
                                  balanceOf[from] = balanceOf[from].sub(value);
                                  totalSupply = totalSupply.sub(value);
                                  emit Transfer(from, address(0), value);
                              }
                          
                              function _approve(address owner, address spender, uint value) private {
                                  allowance[owner][spender] = value;
                                  emit Approval(owner, spender, value);
                              }
                          
                              function _transfer(address from, address to, uint value) private {
                                  balanceOf[from] = balanceOf[from].sub(value);
                                  balanceOf[to] = balanceOf[to].add(value);
                                  emit Transfer(from, to, value);
                              }
                          
                              function approve(address spender, uint value) external returns (bool) {
                                  _approve(msg.sender, spender, value);
                                  return true;
                              }
                          
                              function transfer(address to, uint value) external returns (bool) {
                                  _transfer(msg.sender, to, value);
                                  return true;
                              }
                          
                              function transferFrom(address from, address to, uint value) external returns (bool) {
                                  if (allowance[from][msg.sender] != uint(-1)) {
                                      allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
                                  }
                                  _transfer(from, to, value);
                                  return true;
                              }
                          
                              function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
                                  require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
                                  bytes32 digest = keccak256(
                                      abi.encodePacked(
                                          '\x19\x01',
                                          DOMAIN_SEPARATOR,
                                          keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
                                      )
                                  );
                                  address recoveredAddress = ecrecover(digest, v, r, s);
                                  require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
                                  _approve(owner, spender, value);
                              }
                          }
                          
                          // File: contracts/libraries/Math.sol
                          
                          pragma solidity =0.5.16;
                          
                          // a library for performing various math operations
                          
                          library Math {
                              function min(uint x, uint y) internal pure returns (uint z) {
                                  z = x < y ? x : y;
                              }
                          
                              // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
                              function sqrt(uint y) internal pure returns (uint z) {
                                  if (y > 3) {
                                      z = y;
                                      uint x = y / 2 + 1;
                                      while (x < z) {
                                          z = x;
                                          x = (y / x + x) / 2;
                                      }
                                  } else if (y != 0) {
                                      z = 1;
                                  }
                              }
                          }
                          
                          // File: contracts/libraries/UQ112x112.sol
                          
                          pragma solidity =0.5.16;
                          
                          // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))
                          
                          // range: [0, 2**112 - 1]
                          // resolution: 1 / 2**112
                          
                          library UQ112x112 {
                              uint224 constant Q112 = 2**112;
                          
                              // encode a uint112 as a UQ112x112
                              function encode(uint112 y) internal pure returns (uint224 z) {
                                  z = uint224(y) * Q112; // never overflows
                              }
                          
                              // divide a UQ112x112 by a uint112, returning a UQ112x112
                              function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
                                  z = x / uint224(y);
                              }
                          }
                          
                          // File: contracts/interfaces/IERC20.sol
                          
                          pragma solidity >=0.5.0;
                          
                          interface IERC20 {
                              event Approval(address indexed owner, address indexed spender, uint value);
                              event Transfer(address indexed from, address indexed to, uint value);
                          
                              function name() external view returns (string memory);
                              function symbol() external view returns (string memory);
                              function decimals() external view returns (uint8);
                              function totalSupply() external view returns (uint);
                              function balanceOf(address owner) external view returns (uint);
                              function allowance(address owner, address spender) external view returns (uint);
                          
                              function approve(address spender, uint value) external returns (bool);
                              function transfer(address to, uint value) external returns (bool);
                              function transferFrom(address from, address to, uint value) external returns (bool);
                          }
                          
                          // File: contracts/interfaces/IUniswapV2Factory.sol
                          
                          pragma solidity >=0.5.0;
                          
                          interface IUniswapV2Factory {
                              event PairCreated(address indexed token0, address indexed token1, address pair, uint);
                          
                              function feeTo() external view returns (address);
                              function feeToSetter() external view returns (address);
                          
                              function getPair(address tokenA, address tokenB) external view returns (address pair);
                              function allPairs(uint) external view returns (address pair);
                              function allPairsLength() external view returns (uint);
                          
                              function createPair(address tokenA, address tokenB) external returns (address pair);
                          
                              function setFeeTo(address) external;
                              function setFeeToSetter(address) external;
                          }
                          
                          // File: contracts/interfaces/IUniswapV2Callee.sol
                          
                          pragma solidity >=0.5.0;
                          
                          interface IUniswapV2Callee {
                              function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
                          }
                          
                          // File: contracts/UniswapV2Pair.sol
                          
                          pragma solidity =0.5.16;
                          
                          
                          
                          
                          
                          
                          
                          
                          contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
                              using SafeMath  for uint;
                              using UQ112x112 for uint224;
                          
                              uint public constant MINIMUM_LIQUIDITY = 10**3;
                              bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
                          
                              address public factory;
                              address public token0;
                              address public token1;
                          
                              uint112 private reserve0;           // uses single storage slot, accessible via getReserves
                              uint112 private reserve1;           // uses single storage slot, accessible via getReserves
                              uint32  private blockTimestampLast; // uses single storage slot, accessible via getReserves
                          
                              uint public price0CumulativeLast;
                              uint public price1CumulativeLast;
                              uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
                          
                              uint private unlocked = 1;
                              modifier lock() {
                                  require(unlocked == 1, 'UniswapV2: LOCKED');
                                  unlocked = 0;
                                  _;
                                  unlocked = 1;
                              }
                          
                              function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
                                  _reserve0 = reserve0;
                                  _reserve1 = reserve1;
                                  _blockTimestampLast = blockTimestampLast;
                              }
                          
                              function _safeTransfer(address token, address to, uint value) private {
                                  (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
                                  require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
                              }
                          
                              event Mint(address indexed sender, uint amount0, uint amount1);
                              event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
                              event Swap(
                                  address indexed sender,
                                  uint amount0In,
                                  uint amount1In,
                                  uint amount0Out,
                                  uint amount1Out,
                                  address indexed to
                              );
                              event Sync(uint112 reserve0, uint112 reserve1);
                          
                              constructor() public {
                                  factory = msg.sender;
                              }
                          
                              // called once by the factory at time of deployment
                              function initialize(address _token0, address _token1) external {
                                  require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
                                  token0 = _token0;
                                  token1 = _token1;
                              }
                          
                              // update reserves and, on the first call per block, price accumulators
                              function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
                                  require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
                                  uint32 blockTimestamp = uint32(block.timestamp % 2**32);
                                  uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
                                  if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
                                      // * never overflows, and + overflow is desired
                                      price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
                                      price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
                                  }
                                  reserve0 = uint112(balance0);
                                  reserve1 = uint112(balance1);
                                  blockTimestampLast = blockTimestamp;
                                  emit Sync(reserve0, reserve1);
                              }
                          
                              // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
                              function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
                                  address feeTo = IUniswapV2Factory(factory).feeTo();
                                  feeOn = feeTo != address(0);
                                  uint _kLast = kLast; // gas savings
                                  if (feeOn) {
                                      if (_kLast != 0) {
                                          uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
                                          uint rootKLast = Math.sqrt(_kLast);
                                          if (rootK > rootKLast) {
                                              uint numerator = totalSupply.mul(rootK.sub(rootKLast));
                                              uint denominator = rootK.mul(5).add(rootKLast);
                                              uint liquidity = numerator / denominator;
                                              if (liquidity > 0) _mint(feeTo, liquidity);
                                          }
                                      }
                                  } else if (_kLast != 0) {
                                      kLast = 0;
                                  }
                              }
                          
                              // this low-level function should be called from a contract which performs important safety checks
                              function mint(address to) external lock returns (uint liquidity) {
                                  (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
                                  uint balance0 = IERC20(token0).balanceOf(address(this));
                                  uint balance1 = IERC20(token1).balanceOf(address(this));
                                  uint amount0 = balance0.sub(_reserve0);
                                  uint amount1 = balance1.sub(_reserve1);
                          
                                  bool feeOn = _mintFee(_reserve0, _reserve1);
                                  uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
                                  if (_totalSupply == 0) {
                                      liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
                                     _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
                                  } else {
                                      liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
                                  }
                                  require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
                                  _mint(to, liquidity);
                          
                                  _update(balance0, balance1, _reserve0, _reserve1);
                                  if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
                                  emit Mint(msg.sender, amount0, amount1);
                              }
                          
                              // this low-level function should be called from a contract which performs important safety checks
                              function burn(address to) external lock returns (uint amount0, uint amount1) {
                                  (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
                                  address _token0 = token0;                                // gas savings
                                  address _token1 = token1;                                // gas savings
                                  uint balance0 = IERC20(_token0).balanceOf(address(this));
                                  uint balance1 = IERC20(_token1).balanceOf(address(this));
                                  uint liquidity = balanceOf[address(this)];
                          
                                  bool feeOn = _mintFee(_reserve0, _reserve1);
                                  uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
                                  amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
                                  amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
                                  require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
                                  _burn(address(this), liquidity);
                                  _safeTransfer(_token0, to, amount0);
                                  _safeTransfer(_token1, to, amount1);
                                  balance0 = IERC20(_token0).balanceOf(address(this));
                                  balance1 = IERC20(_token1).balanceOf(address(this));
                          
                                  _update(balance0, balance1, _reserve0, _reserve1);
                                  if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
                                  emit Burn(msg.sender, amount0, amount1, to);
                              }
                          
                              // this low-level function should be called from a contract which performs important safety checks
                              function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
                                  require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
                                  (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
                                  require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
                          
                                  uint balance0;
                                  uint balance1;
                                  { // scope for _token{0,1}, avoids stack too deep errors
                                  address _token0 = token0;
                                  address _token1 = token1;
                                  require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
                                  if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
                                  if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
                                  if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
                                  balance0 = IERC20(_token0).balanceOf(address(this));
                                  balance1 = IERC20(_token1).balanceOf(address(this));
                                  }
                                  uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
                                  uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
                                  require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
                                  { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
                                  uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
                                  uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
                                  require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
                                  }
                          
                                  _update(balance0, balance1, _reserve0, _reserve1);
                                  emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
                              }
                          
                              // force balances to match reserves
                              function skim(address to) external lock {
                                  address _token0 = token0; // gas savings
                                  address _token1 = token1; // gas savings
                                  _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
                                  _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
                              }
                          
                              // force reserves to match balances
                              function sync() external lock {
                                  _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
                              }
                          }

                          File 5 of 7: Mlp
                          pragma solidity 0.6.6;
                          
                          
                          interface IUniswapV2Pair {
                              event Approval(address indexed owner, address indexed spender, uint value);
                              event Transfer(address indexed from, address indexed to, uint value);
                          
                              function name() external pure returns (string memory);
                              function symbol() external pure returns (string memory);
                              function decimals() external pure returns (uint8);
                              function totalSupply() external view returns (uint);
                              function balanceOf(address owner) external view returns (uint);
                              function allowance(address owner, address spender) external view returns (uint);
                          
                              function approve(address spender, uint value) external returns (bool);
                              function transfer(address to, uint value) external returns (bool);
                              function transferFrom(address from, address to, uint value) external returns (bool);
                          
                              function DOMAIN_SEPARATOR() external view returns (bytes32);
                              function PERMIT_TYPEHASH() external pure returns (bytes32);
                              function nonces(address owner) external view returns (uint);
                          
                              function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
                          
                              event Mint(address indexed sender, uint amount0, uint amount1);
                              event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
                              event Swap(
                                  address indexed sender,
                                  uint amount0In,
                                  uint amount1In,
                                  uint amount0Out,
                                  uint amount1Out,
                                  address indexed to
                              );
                              event Sync(uint112 reserve0, uint112 reserve1);
                          
                              function MINIMUM_LIQUIDITY() external pure returns (uint);
                              function factory() external view returns (address);
                              function token0() external view returns (address);
                              function token1() external view returns (address);
                              function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
                              function price0CumulativeLast() external view returns (uint);
                              function price1CumulativeLast() external view returns (uint);
                              function kLast() external view returns (uint);
                          
                              function mint(address to) external returns (uint liquidity);
                              function burn(address to) external returns (uint amount0, uint amount1);
                              function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
                              function skim(address to) external;
                              function sync() external;
                          
                              function initialize(address, address) external;
                          }
                          
                          interface IUniswapV2Router01 {
                              function factory() external pure returns (address);
                              function WETH() external pure returns (address);
                          
                              function addLiquidity(
                                  address tokenA,
                                  address tokenB,
                                  uint amountADesired,
                                  uint amountBDesired,
                                  uint amountAMin,
                                  uint amountBMin,
                                  address to,
                                  uint deadline
                              ) external returns (uint amountA, uint amountB, uint liquidity);
                              function addLiquidityETH(
                                  address token,
                                  uint amountTokenDesired,
                                  uint amountTokenMin,
                                  uint amountETHMin,
                                  address to,
                                  uint deadline
                              ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
                              function removeLiquidity(
                                  address tokenA,
                                  address tokenB,
                                  uint liquidity,
                                  uint amountAMin,
                                  uint amountBMin,
                                  address to,
                                  uint deadline
                              ) external returns (uint amountA, uint amountB);
                              function removeLiquidityETH(
                                  address token,
                                  uint liquidity,
                                  uint amountTokenMin,
                                  uint amountETHMin,
                                  address to,
                                  uint deadline
                              ) external returns (uint amountToken, uint amountETH);
                              function removeLiquidityWithPermit(
                                  address tokenA,
                                  address tokenB,
                                  uint liquidity,
                                  uint amountAMin,
                                  uint amountBMin,
                                  address to,
                                  uint deadline,
                                  bool approveMax, uint8 v, bytes32 r, bytes32 s
                              ) external returns (uint amountA, uint amountB);
                              function removeLiquidityETHWithPermit(
                                  address token,
                                  uint liquidity,
                                  uint amountTokenMin,
                                  uint amountETHMin,
                                  address to,
                                  uint deadline,
                                  bool approveMax, uint8 v, bytes32 r, bytes32 s
                              ) external returns (uint amountToken, uint amountETH);
                              function swapExactTokensForTokens(
                                  uint amountIn,
                                  uint amountOutMin,
                                  address[] calldata path,
                                  address to,
                                  uint deadline
                              ) external returns (uint[] memory amounts);
                              function swapTokensForExactTokens(
                                  uint amountOut,
                                  uint amountInMax,
                                  address[] calldata path,
                                  address to,
                                  uint deadline
                              ) external returns (uint[] memory amounts);
                              function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
                                  external
                                  payable
                                  returns (uint[] memory amounts);
                              function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
                                  external
                                  returns (uint[] memory amounts);
                              function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
                                  external
                                  returns (uint[] memory amounts);
                              function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
                                  external
                                  payable
                                  returns (uint[] memory amounts);
                          
                              function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
                              function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
                              function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
                              function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
                              function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
                          }
                          
                          interface IUniswapV2Router02 is IUniswapV2Router01 {
                              function removeLiquidityETHSupportingFeeOnTransferTokens(
                                  address token,
                                  uint liquidity,
                                  uint amountTokenMin,
                                  uint amountETHMin,
                                  address to,
                                  uint deadline
                              ) external returns (uint amountETH);
                              function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
                                  address token,
                                  uint liquidity,
                                  uint amountTokenMin,
                                  uint amountETHMin,
                                  address to,
                                  uint deadline,
                                  bool approveMax, uint8 v, bytes32 r, bytes32 s
                              ) external returns (uint amountETH);
                          
                              function swapExactTokensForTokensSupportingFeeOnTransferTokens(
                                  uint amountIn,
                                  uint amountOutMin,
                                  address[] calldata path,
                                  address to,
                                  uint deadline
                              ) external;
                              function swapExactETHForTokensSupportingFeeOnTransferTokens(
                                  uint amountOutMin,
                                  address[] calldata path,
                                  address to,
                                  uint deadline
                              ) external payable;
                              function swapExactTokensForETHSupportingFeeOnTransferTokens(
                                  uint amountIn,
                                  uint amountOutMin,
                                  address[] calldata path,
                                  address to,
                                  uint deadline
                              ) external;
                          }
                          
                          /**
                           * @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;
                          }
                          
                          /*
                           * @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;
                          }
                          
                          /**
                           * @dev Contract module which provides a basic access control mechanism, where
                           * there is an account (an owner) that can be granted exclusive access to
                           * specific functions.
                           *
                           * By default, the owner account will be the one that deploys the contract. This
                           * can later be changed with {transferOwnership}.
                           *
                           * This module is used through inheritance. It will make available the modifier
                           * `onlyOwner`, which can be applied to your functions to restrict their use to
                           * the owner.
                           */
                          contract OwnableUpgradeSafe is Initializable, ContextUpgradeSafe {
                              address private _owner;
                          
                              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                          
                              /**
                               * @dev Initializes the contract setting the deployer as the initial owner.
                               */
                          
                              function __Ownable_init() internal initializer {
                                  __Context_init_unchained();
                                  __Ownable_init_unchained();
                              }
                          
                              function __Ownable_init_unchained() internal initializer {
                          
                          
                                  address msgSender = _msgSender();
                                  _owner = msgSender;
                                  emit OwnershipTransferred(address(0), msgSender);
                          
                              }
                          
                          
                              /**
                               * @dev Returns the address of the current owner.
                               */
                              function owner() public view returns (address) {
                                  return _owner;
                              }
                          
                              /**
                               * @dev Throws if called by any account other than the owner.
                               */
                              modifier onlyOwner() {
                                  require(_owner == _msgSender(), "Ownable: caller is not the owner");
                                  _;
                              }
                          
                              /**
                               * @dev Leaves the contract without owner. It will not be possible to call
                               * `onlyOwner` functions anymore. Can only be called by the current owner.
                               *
                               * NOTE: Renouncing ownership will leave the contract without an owner,
                               * thereby removing any functionality that is only available to the owner.
                               */
                              function renounceOwnership() public virtual onlyOwner {
                                  emit OwnershipTransferred(_owner, address(0));
                                  _owner = address(0);
                              }
                          
                              /**
                               * @dev Transfers ownership of the contract to a new account (`newOwner`).
                               * Can only be called by the current owner.
                               */
                              function transferOwnership(address newOwner) public virtual onlyOwner {
                                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                                  emit OwnershipTransferred(_owner, newOwner);
                                  _owner = newOwner;
                              }
                          
                              uint256[49] private __gap;
                          }
                          
                          // SPDX-License-Identifier: MIT
                          /**
                           * @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);
                          }
                          
                          // SPDX-License-Identifier: MIT
                          /**
                           * @dev Standard math utilities missing in the Solidity language.
                           */
                          library Math {
                              /**
                               * @dev Returns the largest of two numbers.
                               */
                              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                                  return a >= b ? a : b;
                              }
                          
                              /**
                               * @dev Returns the smallest of two numbers.
                               */
                              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                                  return a < b ? a : b;
                              }
                          
                              /**
                               * @dev Returns the average of two numbers. The result is rounded towards
                               * zero.
                               */
                              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                                  // (a + b) / 2 can overflow, so we distribute
                                  return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
                              }
                          }
                          
                          // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
                          library SafeMath {
                              function add(uint x, uint y) internal pure returns (uint z) {
                                  require((z = x + y) >= x, 'ds-math-add-overflow');
                              }
                          
                              function sub(uint x, uint y) internal pure returns (uint z) {
                                  require((z = x - y) <= x, 'ds-math-sub-underflow');
                              }
                          
                              function mul(uint x, uint y) internal pure returns (uint z) {
                                  require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
                              }
                          }
                          
                          // SPDX-License-Identifier: MIT
                          /**
                           * @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
                          /**
                           * @title SafeERC20
                           * @dev Wrappers around ERC20 operations that throw on failure (when the token
                           * contract returns false). Tokens that return no value (and instead revert or
                           * throw on failure) are also supported, non-reverting calls are assumed to be
                           * successful.
                           * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
                           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                           */
                          library SafeERC20 {
                              using SafeMath for uint256;
                              using Address for address;
                          
                              function safeTransfer(IERC20 token, address to, uint256 value) internal {
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                              }
                          
                              function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                              }
                          
                              /**
                               * @dev Deprecated. This function has issues similar to the ones found in
                               * {IERC20-approve}, and its usage is discouraged.
                               *
                               * Whenever possible, use {safeIncreaseAllowance} and
                               * {safeDecreaseAllowance} instead.
                               */
                              function safeApprove(IERC20 token, address spender, uint256 value) internal {
                                  // safeApprove should only be called when setting an initial allowance,
                                  // or when resetting it to zero. To increase and decrease it, use
                                  // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                                  // solhint-disable-next-line max-line-length
                                  require((value == 0) || (token.allowance(address(this), spender) == 0),
                                      "SafeERC20: approve from non-zero to non-zero allowance"
                                  );
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                              }
                          
                              function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                  uint256 newAllowance = token.allowance(address(this), spender).add(value);
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                          
                              function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                  uint256 newAllowance = token.allowance(address(this), spender).sub(value);
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                          
                              /**
                               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                               * on the return value: the return value is optional (but if data is returned, it must not be false).
                               * @param token The token targeted by the call.
                               * @param data The call data (encoded using abi.encode or one of its variants).
                               */
                              function _callOptionalReturn(IERC20 token, bytes memory data) private {
                                  // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                                  // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                                  // the target address contains contract code and also asserts for success in the low-level call.
                          
                                  bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                                  if (returndata.length > 0) { // Return data is optional
                                      // solhint-disable-next-line max-line-length
                                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                  }
                              }
                          }
                          
                          // SPDX-License-Identifier: UNLICENSED
                          abstract contract IMlp {
                              function makeOffer(address _token, uint _amount, uint _unlockDate, uint _endDate, uint _slippageTolerancePpm, uint _maxPriceVariationPpm) external virtual returns (uint offerId);
                          
                              function takeOffer(uint _pendingOfferId, uint _amount, uint _deadline) external virtual returns (uint activeOfferId);
                          
                              function cancelOffer(uint _offerId) external virtual;
                          
                              function release(uint256 _offerId, uint256 amount0Min, uint256 amount1Min, uint256 slippage, uint256 _deadline) external virtual;
                          }
                          
                          // SPDX-License-Identifier: UNLICENSED
                          abstract contract IFeesController {
                              function feesTo() public view virtual returns (address);
                              function setFeesTo(address) public virtual;
                          
                              function feesPpm() public view virtual returns (uint);
                              function setFeesPpm(uint) public virtual;
                          }
                          
                          // SPDX-License-Identifier: UNLICENSED
                          abstract contract IRewardManager {
                              function add(uint256 _allocPoint, address _newMlp) public virtual;
                              function notifyDeposit(address _account, uint256 _amount) public virtual;
                              function notifyWithdraw(address _account, uint256 _amount) public virtual;
                              function getPoolSupply(address pool) public view virtual returns(uint);
                              function getUserAmount(address pool, address user) public view virtual returns(uint);
                          }
                          
                          // SPDX-License-Identifier: UNLICENSED
                          contract Mlp is IMlp, Initializable, OwnableUpgradeSafe {
                              using SafeERC20 for IERC20;
                              using SafeMath for uint256;
                          
                              uint256 constant private RATIO_MULTIPLIER = 1_000_000_000_000_000_000;
                              uint256 constant public FULL_SLIPPAGE = 1000;
                              uint256 constant public MAX_SLIPPAGE_TOLERANCE = 75;
                          
                              uint256 public endDate;
                              address public submitter;
                              uint256 public exceedingLiquidity;
                              uint256 public bonusToken0;
                              uint256 public reward0Rate;
                              uint256 public reward0PerTokenStored;
                              uint256 public bonusToken1;
                              uint256 public reward1Rate;
                              uint256 public reward1PerTokenStored;
                              uint256 public lastUpdateTime;
                              uint256 public pendingOfferCount;
                              uint256 public activeOfferCount;
                          
                              IRewardManager public rewardManager;
                              IUniswapV2Pair public uniswapPair;
                              IFeesController public feesController;
                              IUniswapV2Router02 public uniswapRouter;
                          
                              IUniswapV2Pair public safetyPair0;
                              bool public isFirstTokenInSafetyPair0;
                              IUniswapV2Pair public safetyPair1;
                              bool public isFirstTokenInSafetyPair1;
                          
                              bool public doCheckSafetyPools;
                          
                              mapping(address => uint256) public userReward0PerTokenPaid;
                              mapping(address => uint256) public userRewards0;
                              mapping(address => uint256) public userReward1PerTokenPaid;
                              mapping(address => uint256) public userRewards1;
                              mapping(address => uint256) public directStakeBalances;
                              mapping(uint256 => PendingOffer) public getPendingOffer;
                              mapping(uint256 => ActiveOffer) public getActiveOffer;
                          
                              enum OfferStatus {PENDING, TAKEN, CANCELED}
                          
                              event OfferMade(uint256 id);
                              event OfferTaken(uint256 pendingOfferId, uint256 activeOfferId);
                              event OfferCanceled(uint256 id);
                              event OfferReleased(uint256 offerId);
                              event PayReward(uint256 amount0, uint256 amount1);
                          
                              struct PendingOffer {
                                  address owner;
                                  address token;
                                  uint256 amount;
                                  uint256 unlockDate;
                                  uint256 endDate;
                                  OfferStatus status;
                                  uint256 slippageTolerancePpm;
                                  uint256 maxPriceVariationPpm;
                              }
                          
                              struct ActiveOffer {
                                  address user0;
                                  uint256 originalAmount0;
                                  address user1;
                                  uint256 originalAmount1;
                                  uint256 unlockDate;
                                  uint256 liquidity;
                                  bool released;
                                  uint256 maxPriceVariationPpm;
                              }
                          
                              function initialize (
                                  address _feesController,
                                  address _uniswapPair,
                                  address _submitter,
                                  uint256 _endDate,
                                  address _uniswapRouter,
                                  address _rewardManager,
                                  uint256 _bonusTokenAmount0,
                                  uint256 _bonusTokenAmount1,
                                  address _safetyPair0,
                                  address _safetyPair1,
                                  bool _isFirstTokenInPair0,
                                  bool _isFirstTokenInPair1
                              ) public initializer {
                                  OwnableUpgradeSafe.__Ownable_init();
                                  feesController = IFeesController(_feesController);
                                  uniswapPair = IUniswapV2Pair(_uniswapPair);
                                  endDate = _endDate;
                                  submitter = _submitter;
                                  uniswapRouter = IUniswapV2Router02(_uniswapRouter);
                                  rewardManager = IRewardManager(_rewardManager);
                          
                                  uint256 remainingTime = _endDate.sub(block.timestamp);
                                  bonusToken0 = _bonusTokenAmount0;
                                  reward0Rate = _bonusTokenAmount0 / remainingTime;
                                  bonusToken1 = _bonusTokenAmount1;
                                  reward1Rate = _bonusTokenAmount1 / remainingTime;
                                  lastUpdateTime = block.timestamp;
                          
                                  safetyPair0 = IUniswapV2Pair(_safetyPair0);
                                  isFirstTokenInSafetyPair0 = _isFirstTokenInPair0;
                                  safetyPair1 = IUniswapV2Pair(_safetyPair1);
                                  isFirstTokenInSafetyPair1 = _isFirstTokenInPair1;
                          
                                  doCheckSafetyPools = true;
                              }
                          
                              function lastTimeRewardApplicable() public view returns (uint256) {
                                  return Math.min(block.timestamp, endDate);
                              }
                          
                              function reward0PerToken() public view returns (uint256) {
                                  uint256 totalSupply = rewardManager.getPoolSupply(address(this));
                                  if (totalSupply == 0) {
                                      return reward0PerTokenStored;
                                  }
                                  return
                                      reward0PerTokenStored.add(
                                          lastTimeRewardApplicable()
                                              .sub(lastUpdateTime)
                                              .mul(reward0Rate)
                                              .mul(1e18) / totalSupply
                                      );
                              }
                          
                              function reward1PerToken() public view returns (uint256) {
                                  uint256 totalSupply = rewardManager.getPoolSupply(address(this));
                                  if (totalSupply == 0) {
                                      return reward1PerTokenStored;
                                  }
                                  return
                                      reward1PerTokenStored.add(
                                          lastTimeRewardApplicable()
                                              .sub(lastUpdateTime)
                                              .mul(reward1Rate)
                                              .mul(1e18) / totalSupply
                                      );
                              }
                          
                              function rewardEarned(address account)
                                  public
                                  view
                                  returns (uint256 reward0Earned, uint256 reward1Earned)
                              {
                                  uint256 balance = rewardManager.getUserAmount(address(this), account);
                                  reward0Earned = (balance.mul(
                                      reward0PerToken().sub(userReward0PerTokenPaid[account])
                                  ) / 1e18)
                                      .add(userRewards0[account]);
                                  reward1Earned = (balance.mul(
                                      reward1PerToken().sub(userReward1PerTokenPaid[account])
                                  ) / 1e18)
                                      .add(userRewards1[account]);
                              }
                          
                              function updateRewards(address account) internal {
                                  reward0PerTokenStored = reward0PerToken();
                                  reward1PerTokenStored = reward1PerToken();
                                  lastUpdateTime = lastTimeRewardApplicable();
                                  if (account != address(0)) {
                                      (uint256 earned0, uint256 earned1) = rewardEarned(account);
                                      userRewards0[account] = earned0;
                                      userRewards1[account] = earned1;
                                      userReward0PerTokenPaid[account] = reward0PerTokenStored;
                                      userReward1PerTokenPaid[account] = reward1PerTokenStored;
                                  }
                              }
                          
                              function payRewards(address account) public {
                                  updateRewards(account);
                                  (uint256 reward0, uint256 reward1) = rewardEarned(account);
                                  if (reward0 > 0) {
                                      userRewards0[account] = 0;
                                      IERC20(uniswapPair.token0()).safeTransfer(account, reward0);
                                  }
                                  if (reward1 > 0) {
                                      userRewards1[account] = 0;
                                      IERC20(uniswapPair.token1()).safeTransfer(account, reward1);
                                  }
                                  if (reward0 > 0 || reward1 > 0) {
                                      emit PayReward(reward0, reward1);
                                  }
                              }
                          
                              function _notifyDeposit(address account, uint256 amount) internal {
                                  updateRewards(account);
                                  rewardManager.notifyDeposit(account, amount);
                              }
                          
                              function _notifyWithdraw(address account, uint256 amount) internal {
                                  updateRewards(account);
                                  rewardManager.notifyWithdraw(account, amount);
                              }
                          
                              function makeOffer(
                                  address _token,
                                  uint256 _amount,
                                  uint256 _unlockDate,
                                  uint256 _endDate,
                                  uint256 _slippageTolerancePpm,
                                  uint256 _maxPriceVariationPpm
                              ) external override returns (uint256 offerId) {
                                  require(_amount > 0);
                                  require(_endDate > now);
                                  require(_endDate <= _unlockDate);
                                  offerId = pendingOfferCount;
                                  pendingOfferCount++;
                                  getPendingOffer[offerId] = PendingOffer(
                                      msg.sender,
                                      _token,
                                      _amount,
                                      _unlockDate,
                                      _endDate,
                                      OfferStatus.PENDING,
                                      _slippageTolerancePpm,
                                      _maxPriceVariationPpm
                                  );
                                  IERC20 token;
                                  if (_token == address(uniswapPair.token0())) {
                                      token = IERC20(uniswapPair.token0());
                                  } else if (_token == address(uniswapPair.token1())) {
                                      token = IERC20(uniswapPair.token1());
                                  } else {
                                      require(false, "unknown token");
                                  }
                          
                                  token.safeTransferFrom(msg.sender, address(this), _amount);
                                  emit OfferMade(offerId);
                              }
                          
                              struct ProviderInfo {
                                  address user;
                                  uint256 amount;
                                  IERC20 token;
                              }
                          
                              struct OfferInfo {
                                  uint256 deadline;
                                  uint256 slippageTolerancePpm;
                              }
                          
                              function takeOffer(
                                  uint256 _pendingOfferId,
                                  uint256 _amount,
                                  uint256 _deadline
                              ) external override returns (uint256 activeOfferId) {
                                  PendingOffer storage pendingOffer = getPendingOffer[_pendingOfferId];
                                  require(pendingOffer.status == OfferStatus.PENDING);
                                  require(pendingOffer.endDate > now);
                                  pendingOffer.status = OfferStatus.TAKEN;
                          
                                  // Sort the users, tokens, and amount
                                  ProviderInfo memory provider0;
                                  ProviderInfo memory provider1;
                                  {
                                      if (pendingOffer.token == uniswapPair.token0()) {
                                          provider0 = ProviderInfo(
                                              pendingOffer.owner,
                                              pendingOffer.amount,
                                              IERC20(uniswapPair.token0())
                                          );
                                          provider1 = ProviderInfo(
                                              msg.sender,
                                              _amount,
                                              IERC20(uniswapPair.token1())
                                          );
                          
                                          provider1.token.safeTransferFrom(
                                              provider1.user,
                                              address(this),
                                              provider1.amount
                                          );
                                      } else {
                                          provider0 = ProviderInfo(
                                              msg.sender,
                                              _amount,
                                              IERC20(uniswapPair.token0())
                                          );
                                          provider1 = ProviderInfo(
                                              pendingOffer.owner,
                                              pendingOffer.amount,
                                              IERC20(uniswapPair.token1())
                                          );
                          
                                          provider0.token.safeTransferFrom(
                                              provider0.user,
                                              address(this),
                                              provider0.amount
                                          );
                                      }
                                  }
                          
                                  // calculate fees
                                  uint256 feesAmount0 =
                                      provider0.amount.mul(feesController.feesPpm()) / 1000;
                                  uint256 feesAmount1 =
                                      provider1.amount.mul(feesController.feesPpm()) / 1000;
                          
                                  // take fees
                                  provider0.amount = provider0.amount.sub(feesAmount0);
                                  provider1.amount = provider1.amount.sub(feesAmount1);
                          
                                  // send fees
                                  provider0.token.safeTransfer(feesController.feesTo(), feesAmount0);
                                  provider1.token.safeTransfer(feesController.feesTo(), feesAmount1);
                          
                                  uint256 spentAmount0;
                                  uint256 spentAmount1;
                                  uint256 liquidity;
                                  uint256[] memory returnedValues = new uint256[](3);
                          
                                  // send tokens to uniswap
                                  {
                                      returnedValues = _provideLiquidity(
                                          provider0,
                                          provider1,
                                          OfferInfo(_deadline, pendingOffer.slippageTolerancePpm)
                                      );
                                      liquidity = returnedValues[0];
                                      spentAmount0 = returnedValues[1];
                                      spentAmount1 = returnedValues[2];
                                  }
                          
                                  // stake liquidity
                                  _notifyDeposit(provider0.user, liquidity / 2);
                                  _notifyDeposit(provider1.user, liquidity / 2);
                          
                                  if (liquidity % 2 != 0) {
                                      exceedingLiquidity = exceedingLiquidity.add(1);
                                  }
                          
                                  // Record the active offer
                                  activeOfferId = activeOfferCount;
                                  activeOfferCount++;
                          
                                  getActiveOffer[activeOfferId] = ActiveOffer(
                                      provider0.user,
                                      spentAmount0,
                                      provider1.user,
                                      spentAmount1,
                                      pendingOffer.unlockDate,
                                      liquidity,
                                      false,
                                      pendingOffer.maxPriceVariationPpm
                                  );
                          
                                  emit OfferTaken(_pendingOfferId, activeOfferId);
                          
                                  return activeOfferId;
                              }
                          
                              function _provideLiquidity(
                                  ProviderInfo memory _provider0,
                                  ProviderInfo memory _provider1,
                                  OfferInfo memory _info
                              ) internal returns (uint256[] memory) {
                                  _provider0.token.safeApprove(address(uniswapRouter), 0);
                                  _provider1.token.safeApprove(address(uniswapRouter), 0);
                          
                                  _provider0.token.safeApprove(address(uniswapRouter), _provider0.amount);
                                  _provider1.token.safeApprove(address(uniswapRouter), _provider1.amount);
                          
                                  uint256 amountMin0 =
                                      _provider0.amount.sub(
                                          _provider0.amount.mul(_info.slippageTolerancePpm) / 1000
                                      );
                                  uint256 amountMin1 =
                                      _provider1.amount.sub(
                                          _provider1.amount.mul(_info.slippageTolerancePpm) / 1000
                                      );
                          
                                  // Add the liquidity to Uniswap
                                  uint256 spentAmount0;
                                  uint256 spentAmount1;
                                  uint256 liquidity;
                                  {
                                      (spentAmount0, spentAmount1, liquidity) = uniswapRouter
                                          .addLiquidity(
                                          address(_provider0.token),
                                          address(_provider1.token),
                                          _provider0.amount,
                                          _provider1.amount,
                                          amountMin0,
                                          amountMin1,
                                          address(this),
                                          _info.deadline
                                      );
                                  }
                                  // Give back the exceeding tokens
                                  if (spentAmount0 < _provider0.amount) {
                                      _provider0.token.safeTransfer(
                                          _provider0.user,
                                          _provider0.amount - spentAmount0
                                      );
                                  }
                                  if (spentAmount1 < _provider1.amount) {
                                      _provider1.token.safeTransfer(
                                          _provider1.user,
                                          _provider1.amount - spentAmount1
                                      );
                                  }
                                  uint256[] memory liq = new uint256[](3);
                                  liq[0] = liquidity;
                                  liq[1] = spentAmount0;
                                  liq[2] = spentAmount1;
                                  return (liq);
                              }
                          
                              function cancelOffer(uint256 _offerId) external override {
                                  PendingOffer storage pendingOffer = getPendingOffer[_offerId];
                                  require(pendingOffer.status == OfferStatus.PENDING);
                                  pendingOffer.status = OfferStatus.CANCELED;
                                  IERC20(pendingOffer.token).safeTransfer(
                                      pendingOffer.owner,
                                      pendingOffer.amount
                                  );
                                  emit OfferCanceled(_offerId);
                              }
                          
                              function release(uint256 _offerId, uint256 amount0Min, uint256 amount1Min, uint256 slippageTolerance, uint256 _deadline) external override {
                                  ActiveOffer storage offer = getActiveOffer[_offerId];
                          
                                  require(
                                      msg.sender == offer.user0 || msg.sender == offer.user1,
                                      "unauthorized"
                                  );
                                  require(now > offer.unlockDate, "locked");
                                  require(!offer.released, "already released");
                                  require(slippageTolerance <= MAX_SLIPPAGE_TOLERANCE, "release: SLIPPAGE_TOO_BIG");
                          
                                  // check if ETH or USDT pools are in correct ratio
                                  if (doCheckSafetyPools) {
                                      uint256 totalSlippage = _getPriceSlippage();
                                      require(totalSlippage <= slippageTolerance, "release: BAD_SLIPPAGE_RATIO");
                                  }
                          
                                  offer.released = true;
                          
                                  IERC20 token0 = IERC20(uniswapPair.token0());
                                  IERC20 token1 = IERC20(uniswapPair.token1());
                          
                                  IERC20(address(uniswapPair)).safeApprove(address(uniswapRouter), 0);
                          
                                  IERC20(address(uniswapPair)).safeApprove(
                                      address(uniswapRouter),
                                      offer.liquidity
                                  );
                                  (uint256 amount0, uint256 amount1) =
                                      uniswapRouter.removeLiquidity(
                                          address(token0),
                                          address(token1),
                                          offer.liquidity,
                                          amount0Min,
                                          amount1Min,
                                          address(this),
                                          _deadline
                                      );
                          
                                  _notifyWithdraw(offer.user0, offer.liquidity / 2);
                                  _notifyWithdraw(offer.user1, offer.liquidity / 2);
                          
                                  if (
                                      _getPriceVariation(offer.originalAmount0, amount0) >
                                      offer.maxPriceVariationPpm
                                  ) {
                                      if (amount0 > offer.originalAmount0) {
                                          uint256 toSwap = amount0.sub(offer.originalAmount0);
                                          address[] memory path = new address[](2);
                                          path[0] = uniswapPair.token0();
                                          path[1] = uniswapPair.token1();
                                          token0.safeApprove(address(uniswapRouter), 0);
                                          token0.safeApprove(address(uniswapRouter), toSwap);
                          
                                          uint256[] memory newAmounts =
                                              uniswapRouter.swapExactTokensForTokens(
                                                  toSwap,
                                                  0,
                                                  path,
                                                  address(this),
                                                  _deadline
                                              );
                                          amount0 = amount0.sub(toSwap);
                                          amount1 = amount1.add(newAmounts[1]);
                                      }
                                  }
                                  if (
                                      _getPriceVariation(offer.originalAmount1, amount1) >
                                      offer.maxPriceVariationPpm
                                  ) {
                                      if (amount1 > offer.originalAmount1) {
                                          uint256 toSwap = amount1.sub(offer.originalAmount1);
                                          address[] memory path = new address[](2);
                                          path[0] = uniswapPair.token1();
                                          path[1] = uniswapPair.token0();
                                          token1.safeApprove(address(uniswapRouter), 0);
                                          token1.safeApprove(address(uniswapRouter), toSwap);
                                          uint256[] memory newAmounts =
                                              uniswapRouter.swapExactTokensForTokens(
                                                  toSwap,
                                                  0,
                                                  path,
                                                  address(this),
                                                  _deadline
                                              );
                                          amount1 = amount1.sub(toSwap);
                                          amount0 = amount0.add(newAmounts[1]);
                                      }
                                  }
                          
                                  token0.safeTransfer(offer.user0, amount0);
                                  payRewards(offer.user0);
                                  token1.safeTransfer(offer.user1, amount1);
                                  payRewards(offer.user1);
                          
                                  emit OfferReleased(_offerId);
                              }
                          
                              function setDoCheckSafetyPools(bool _doCheckSafetyPools) external onlyOwner {
                                  doCheckSafetyPools = _doCheckSafetyPools;
                              }
                          
                              function _getPriceSlippage() private view returns (uint256) {
                          
                                  uint256 safetyRatio0 = _calculateSafetyPairRatio(safetyPair0, isFirstTokenInSafetyPair0);
                                  uint256 safetyRatio1 = _calculateSafetyPairRatio(safetyPair1, isFirstTokenInSafetyPair1);
                          
                                  uint256 safetyRatio = safetyRatio0.mul(RATIO_MULTIPLIER) / safetyRatio1;
                                  uint256 mlpTokensRatio = _calculateMlpPairRatio();
                          
                                  uint256 totalSlippage = _getAbsoluteSubstraction(safetyRatio, mlpTokensRatio).mul(FULL_SLIPPAGE) / safetyRatio;
                          
                                  return totalSlippage;
                              }
                          
                              function _getAbsoluteSubstraction(uint256 x, uint256 y) private pure returns (uint256) {
                                  if (x > y) {
                                      return x - y;
                                  } else {
                                      return y - x;
                                  }
                              }
                          
                              function _calculateSafetyPairRatio(IUniswapV2Pair safetyPair, bool isFirstTokenInPair) private view returns(uint256) {
                                  uint112 mlpTokenReserve;
                                  uint112 safetyTokenReserve;
                          
                                  if (isFirstTokenInPair) {
                                      (mlpTokenReserve, safetyTokenReserve,) = safetyPair.getReserves();
                                  } else {
                                      (safetyTokenReserve, mlpTokenReserve,) = safetyPair.getReserves();
                                  }
                          
                                  return (uint256(mlpTokenReserve)).mul(RATIO_MULTIPLIER) / (uint256(safetyTokenReserve));
                              }
                          
                              function _calculateMlpPairRatio() private view returns(uint256) {
                                  (uint112 reserve0, uint112 reserve1,) = uniswapPair.getReserves();
                          
                                  return (uint256(reserve0)).mul(RATIO_MULTIPLIER) / (uint256(reserve1));
                              }
                          
                              function _getPriceVariation(uint256 a, uint256 b)
                                  internal
                                  pure
                                  returns (uint256)
                              {
                                  uint256 sub;
                                  if (a > b) {
                                      sub = a.sub(b);
                                      return sub.mul(1000) / a;
                                  } else {
                                      sub = b.sub(a);
                                      return sub.mul(1000) / b;
                                  }
                              }
                          
                              function directStake(uint256 _amount) external {
                                  require(_amount > 0, "cannot stake 0");
                                  IERC20(address(uniswapPair)).safeTransferFrom(
                                      msg.sender,
                                      address(this),
                                      _amount
                                  );
                          
                                  // calculate fees
                                  uint256 feesAmount = _amount.mul(feesController.feesPpm()) / 1000;
                          
                                  // take fees
                                  uint256 stakeAmount = _amount.sub(feesAmount);
                          
                                  // send fees
                                  IERC20(address(uniswapPair)).safeTransfer(feesController.feesTo(), feesAmount);
                          
                                  _notifyDeposit(msg.sender, stakeAmount);
                                  directStakeBalances[msg.sender] = directStakeBalances[msg.sender].add(
                                      stakeAmount
                                  );
                              }
                          
                              function directWithdraw(uint256 _amount) external {
                                  require(_amount > 0, "cannot withdraw 0");
                                  _notifyWithdraw(msg.sender, _amount);
                                  directStakeBalances[msg.sender] = directStakeBalances[msg.sender].sub(
                                      _amount
                                  );
                                  IERC20(address(uniswapPair)).safeTransfer(msg.sender, _amount);
                              }
                          
                              function transferExceedingLiquidity() external {
                                  require(exceedingLiquidity != 0);
                                  IERC20(address(uniswapPair)).safeTransfer(
                                      feesController.feesTo(),
                                      exceedingLiquidity
                                  );
                                  exceedingLiquidity = 0;
                              }
                          }

                          File 6 of 7: PopReward
                          // SPDX-License-Identifier: MIT
                          
                          pragma solidity >=0.6.6;
                          
                          /**
                           * @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);
                          }
                          
                          /**
                           * @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, with an overflow flag.
                               *
                               * _Available since v3.4._
                               */
                              function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                  uint256 c = a + b;
                                  if (c < a) return (false, 0);
                                  return (true, c);
                              }
                          
                              /**
                               * @dev Returns the substraction of two unsigned integers, with an overflow flag.
                               *
                               * _Available since v3.4._
                               */
                              function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                  if (b > a) return (false, 0);
                                  return (true, a - b);
                              }
                          
                              /**
                               * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
                               *
                               * _Available since v3.4._
                               */
                              function tryMul(uint256 a, uint256 b) internal pure returns (bool, 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 (true, 0);
                                  uint256 c = a * b;
                                  if (c / a != b) return (false, 0);
                                  return (true, c);
                              }
                          
                              /**
                               * @dev Returns the division of two unsigned integers, with a division by zero flag.
                               *
                               * _Available since v3.4._
                               */
                              function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                  if (b == 0) return (false, 0);
                                  return (true, a / b);
                              }
                          
                              /**
                               * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
                               *
                               * _Available since v3.4._
                               */
                              function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                                  if (b == 0) return (false, 0);
                                  return (true, a % b);
                              }
                          
                              /**
                               * @dev Returns the addition of two unsigned integers, reverting on
                               * overflow.
                               *
                               * Counterpart to Solidity's `+` operator.
                               *
                               * Requirements:
                               *
                               * - Addition cannot overflow.
                               */
                              function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                  uint256 c = a + b;
                                  require(c >= a, "SafeMath: addition overflow");
                                  return c;
                              }
                          
                              /**
                               * @dev Returns the subtraction of two unsigned integers, reverting on
                               * overflow (when the result is negative).
                               *
                               * Counterpart to Solidity's `-` operator.
                               *
                               * Requirements:
                               *
                               * - Subtraction cannot overflow.
                               */
                              function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                  require(b <= a, "SafeMath: subtraction overflow");
                                  return a - b;
                              }
                          
                              /**
                               * @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) {
                                  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, reverting 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) {
                                  require(b > 0, "SafeMath: division by zero");
                                  return a / b;
                              }
                          
                              /**
                               * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                               * reverting when dividing by zero.
                               *
                               * Counterpart to Solidity's `%` operator. This function uses a `revert`
                               * opcode (which leaves remaining gas untouched) while Solidity uses an
                               * invalid opcode to revert (consuming all remaining gas).
                               *
                               * Requirements:
                               *
                               * - The divisor cannot be zero.
                               */
                              function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                                  require(b > 0, "SafeMath: modulo by zero");
                                  return a % b;
                              }
                          
                              /**
                               * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                               * overflow (when the result is negative).
                               *
                               * CAUTION: This function is deprecated because it requires allocating memory for the error
                               * message unnecessarily. For custom revert reasons use {trySub}.
                               *
                               * 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);
                                  return a - b;
                              }
                          
                              /**
                               * @dev Returns the integer division of two unsigned integers, reverting with custom message on
                               * division by zero. The result is rounded towards zero.
                               *
                               * CAUTION: This function is deprecated because it requires allocating memory for the error
                               * message unnecessarily. For custom revert reasons use {tryDiv}.
                               *
                               * Counterpart to Solidity's `/` operator. Note: this function uses a
                               * `revert` opcode (which leaves remaining gas untouched) while Solidity
                               * uses an invalid opcode to revert (consuming all remaining gas).
                               *
                               * Requirements:
                               *
                               * - The divisor cannot be zero.
                               */
                              function div(
                                  uint256 a,
                                  uint256 b,
                                  string memory errorMessage
                              ) internal pure returns (uint256) {
                                  require(b > 0, errorMessage);
                                  return a / b;
                              }
                          
                              /**
                               * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                               * reverting with custom message when dividing by zero.
                               *
                               * CAUTION: This function is deprecated because it requires allocating memory for the error
                               * message unnecessarily. For custom revert reasons use {tryMod}.
                               *
                               * 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;
                              }
                          }
                          
                          /**
                           * @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);
                                      }
                                  }
                              }
                          }
                          
                          /**
                           * @title SafeERC20
                           * @dev Wrappers around ERC20 operations that throw on failure (when the token
                           * contract returns false). Tokens that return no value (and instead revert or
                           * throw on failure) are also supported, non-reverting calls are assumed to be
                           * successful.
                           * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
                           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                           */
                          library SafeERC20 {
                              using SafeMath for uint256;
                              using Address for address;
                          
                              function safeTransfer(
                                  IERC20 token,
                                  address to,
                                  uint256 value
                              ) internal {
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                              }
                          
                              function safeTransferFrom(
                                  IERC20 token,
                                  address from,
                                  address to,
                                  uint256 value
                              ) internal {
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                              }
                          
                              /**
                               * @dev Deprecated. This function has issues similar to the ones found in
                               * {IERC20-approve}, and its usage is discouraged.
                               *
                               * Whenever possible, use {safeIncreaseAllowance} and
                               * {safeDecreaseAllowance} instead.
                               */
                              function safeApprove(
                                  IERC20 token,
                                  address spender,
                                  uint256 value
                              ) internal {
                                  // safeApprove should only be called when setting an initial allowance,
                                  // or when resetting it to zero. To increase and decrease it, use
                                  // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                                  // solhint-disable-next-line max-line-length
                                  require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance");
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                              }
                          
                              function safeIncreaseAllowance(
                                  IERC20 token,
                                  address spender,
                                  uint256 value
                              ) internal {
                                  uint256 newAllowance = token.allowance(address(this), spender).add(value);
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                          
                              function safeDecreaseAllowance(
                                  IERC20 token,
                                  address spender,
                                  uint256 value
                              ) internal {
                                  uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                          
                              /**
                               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                               * on the return value: the return value is optional (but if data is returned, it must not be false).
                               * @param token The token targeted by the call.
                               * @param data The call data (encoded using abi.encode or one of its variants).
                               */
                              function _callOptionalReturn(IERC20 token, bytes memory data) private {
                                  // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                                  // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                                  // the target address contains contract code and also asserts for success in the low-level call.
                          
                                  bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                                  if (returndata.length > 0) {
                                      // Return data is optional
                                      // solhint-disable-next-line max-line-length
                                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                  }
                              }
                          }
                          
                          /**
                           * @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.3.0, sets of type `bytes32` (`Bytes32Set`), `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];
                              }
                          
                              // Bytes32Set
                          
                              struct Bytes32Set {
                                  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(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                                  return _add(set._inner, 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(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                                  return _remove(set._inner, value);
                              }
                          
                              /**
                               * @dev Returns true if the value is in the set. O(1).
                               */
                              function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
                                  return _contains(set._inner, value);
                              }
                          
                              /**
                               * @dev Returns the number of values in the set. O(1).
                               */
                              function length(Bytes32Set 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(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
                                  return _at(set._inner, 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(uint160(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(uint160(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(uint160(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(uint160(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));
                              }
                          }
                          
                          /**
                           * @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;
                          }
                          
                          /*
                           * @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;
                          }
                          
                          /**
                           * @dev Contract module which provides a basic access control mechanism, where
                           * there is an account (an owner) that can be granted exclusive access to
                           * specific functions.
                           *
                           * By default, the owner account will be the one that deploys the contract. This
                           * can later be changed with {transferOwnership}.
                           *
                           * This module is used through inheritance. It will make available the modifier
                           * `onlyOwner`, which can be applied to your functions to restrict their use to
                           * the owner.
                           */
                          contract OwnableUpgradeSafe is Initializable, ContextUpgradeSafe {
                              address private _owner;
                          
                              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                          
                              /**
                               * @dev Initializes the contract setting the deployer as the initial owner.
                               */
                          
                              function __Ownable_init() internal initializer {
                                  __Context_init_unchained();
                                  __Ownable_init_unchained();
                              }
                          
                              function __Ownable_init_unchained() internal initializer {
                                  address msgSender = _msgSender();
                                  _owner = msgSender;
                                  emit OwnershipTransferred(address(0), msgSender);
                              }
                          
                              /**
                               * @dev Returns the address of the current owner.
                               */
                              function owner() public view returns (address) {
                                  return _owner;
                              }
                          
                              /**
                               * @dev Throws if called by any account other than the owner.
                               */
                              modifier onlyOwner() {
                                  require(_owner == _msgSender(), "Ownable: caller is not the owner");
                                  _;
                              }
                          
                              /**
                               * @dev Leaves the contract without owner. It will not be possible to call
                               * `onlyOwner` functions anymore. Can only be called by the current owner.
                               *
                               * NOTE: Renouncing ownership will leave the contract without an owner,
                               * thereby removing any functionality that is only available to the owner.
                               */
                              function renounceOwnership() public virtual onlyOwner {
                                  emit OwnershipTransferred(_owner, address(0));
                                  _owner = address(0);
                              }
                          
                              /**
                               * @dev Transfers ownership of the contract to a new account (`newOwner`).
                               * Can only be called by the current owner.
                               */
                              function transferOwnership(address newOwner) public virtual onlyOwner {
                                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                                  emit OwnershipTransferred(_owner, newOwner);
                                  _owner = newOwner;
                              }
                          
                              uint256[49] private __gap;
                          }
                          
                          /*
                           * @dev Provides information about the current execution context, including the
                           * sender of the transaction and its data. While these are generally available
                           * via msg.sender and msg.data, they should not be accessed in such a direct
                           * manner, since when dealing with GSN meta-transactions the account sending and
                           * paying for execution may not be the actual sender (as far as an application
                           * is concerned).
                           *
                           * This contract is only required for intermediate, library-like contracts.
                           */
                          abstract contract Context {
                              function _msgSender() internal view virtual returns (address payable) {
                                  return msg.sender;
                              }
                          
                              function _msgData() internal view virtual returns (bytes memory) {
                                  this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                                  return msg.data;
                              }
                          }
                          
                          /**
                           * @dev Implementation of the {IERC20} interface.
                           *
                           * This implementation is agnostic to the way tokens are created. This means
                           * that a supply mechanism has to be added in a derived contract using {_mint}.
                           * For a generic mechanism see {ERC20PresetMinterPauser}.
                           *
                           * TIP: For a detailed writeup see our guide
                           * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
                           * to implement supply mechanisms].
                           *
                           * We have followed general OpenZeppelin guidelines: functions revert instead
                           * of returning `false` on failure. This behavior is nonetheless conventional
                           * and does not conflict with the expectations of ERC20 applications.
                           *
                           * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
                           * This allows applications to reconstruct the allowance for all accounts just
                           * by listening to said events. Other implementations of the EIP may not emit
                           * these events, as it isn't required by the specification.
                           *
                           * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
                           * functions have been added to mitigate the well-known issues around setting
                           * allowances. See {IERC20-approve}.
                           */
                          contract ERC20 is Context, IERC20 {
                              using SafeMath for uint256;
                          
                              mapping(address => uint256) private _balances;
                          
                              mapping(address => mapping(address => uint256)) private _allowances;
                          
                              uint256 private _totalSupply;
                          
                              string private _name;
                              string private _symbol;
                              uint8 private _decimals;
                          
                              /**
                               * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
                               * a default value of 18.
                               *
                               * To select a different value for {decimals}, use {_setupDecimals}.
                               *
                               * All three of these values are immutable: they can only be set once during
                               * construction.
                               */
                              constructor(string memory name_, string memory symbol_) public {
                                  _name = name_;
                                  _symbol = symbol_;
                                  _decimals = 18;
                              }
                          
                              /**
                               * @dev Returns the name of the token.
                               */
                              function name() public view virtual returns (string memory) {
                                  return _name;
                              }
                          
                              /**
                               * @dev Returns the symbol of the token, usually a shorter version of the
                               * name.
                               */
                              function symbol() public view virtual returns (string memory) {
                                  return _symbol;
                              }
                          
                              /**
                               * @dev Returns the number of decimals used to get its user representation.
                               * For example, if `decimals` equals `2`, a balance of `505` tokens should
                               * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                               *
                               * Tokens usually opt for a value of 18, imitating the relationship between
                               * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
                               * called.
                               *
                               * NOTE: This information is only used for _display_ purposes: it in
                               * no way affects any of the arithmetic of the contract, including
                               * {IERC20-balanceOf} and {IERC20-transfer}.
                               */
                              function decimals() public view virtual returns (uint8) {
                                  return _decimals;
                              }
                          
                              /**
                               * @dev See {IERC20-totalSupply}.
                               */
                              function totalSupply() public view virtual override returns (uint256) {
                                  return _totalSupply;
                              }
                          
                              /**
                               * @dev See {IERC20-balanceOf}.
                               */
                              function balanceOf(address account) public view virtual override returns (uint256) {
                                  return _balances[account];
                              }
                          
                              /**
                               * @dev See {IERC20-transfer}.
                               *
                               * Requirements:
                               *
                               * - `recipient` cannot be the zero address.
                               * - the caller must have a balance of at least `amount`.
                               */
                              function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                                  _transfer(_msgSender(), recipient, amount);
                                  return true;
                              }
                          
                              /**
                               * @dev See {IERC20-allowance}.
                               */
                              function allowance(address owner, address spender) public view virtual override returns (uint256) {
                                  return _allowances[owner][spender];
                              }
                          
                              /**
                               * @dev See {IERC20-approve}.
                               *
                               * Requirements:
                               *
                               * - `spender` cannot be the zero address.
                               */
                              function approve(address spender, uint256 amount) public virtual override returns (bool) {
                                  _approve(_msgSender(), spender, amount);
                                  return true;
                              }
                          
                              /**
                               * @dev See {IERC20-transferFrom}.
                               *
                               * Emits an {Approval} event indicating the updated allowance. This is not
                               * required by the EIP. See the note at the beginning of {ERC20}.
                               *
                               * Requirements:
                               *
                               * - `sender` and `recipient` cannot be the zero address.
                               * - `sender` must have a balance of at least `amount`.
                               * - the caller must have allowance for ``sender``'s tokens of at least
                               * `amount`.
                               */
                              function transferFrom(
                                  address sender,
                                  address recipient,
                                  uint256 amount
                              ) public virtual override returns (bool) {
                                  _transfer(sender, recipient, amount);
                                  _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                                  return true;
                              }
                          
                              /**
                               * @dev Atomically increases the allowance granted to `spender` by the caller.
                               *
                               * This is an alternative to {approve} that can be used as a mitigation for
                               * problems described in {IERC20-approve}.
                               *
                               * Emits an {Approval} event indicating the updated allowance.
                               *
                               * Requirements:
                               *
                               * - `spender` cannot be the zero address.
                               */
                              function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                                  _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                                  return true;
                              }
                          
                              /**
                               * @dev Atomically decreases the allowance granted to `spender` by the caller.
                               *
                               * This is an alternative to {approve} that can be used as a mitigation for
                               * problems described in {IERC20-approve}.
                               *
                               * Emits an {Approval} event indicating the updated allowance.
                               *
                               * Requirements:
                               *
                               * - `spender` cannot be the zero address.
                               * - `spender` must have allowance for the caller of at least
                               * `subtractedValue`.
                               */
                              function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                                  _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                                  return true;
                              }
                          
                              /**
                               * @dev Moves tokens `amount` from `sender` to `recipient`.
                               *
                               * This is internal function is equivalent to {transfer}, and can be used to
                               * e.g. implement automatic token fees, slashing mechanisms, etc.
                               *
                               * Emits a {Transfer} event.
                               *
                               * Requirements:
                               *
                               * - `sender` cannot be the zero address.
                               * - `recipient` cannot be the zero address.
                               * - `sender` must have a balance of at least `amount`.
                               */
                              function _transfer(
                                  address sender,
                                  address recipient,
                                  uint256 amount
                              ) internal virtual {
                                  require(sender != address(0), "ERC20: transfer from the zero address");
                                  require(recipient != address(0), "ERC20: transfer to the zero address");
                          
                                  _beforeTokenTransfer(sender, recipient, amount);
                          
                                  _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                                  _balances[recipient] = _balances[recipient].add(amount);
                                  emit Transfer(sender, recipient, amount);
                              }
                          
                              /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                               * the total supply.
                               *
                               * Emits a {Transfer} event with `from` set to the zero address.
                               *
                               * Requirements:
                               *
                               * - `to` cannot be the zero address.
                               */
                              function _mint(address account, uint256 amount) internal virtual {
                                  require(account != address(0), "ERC20: mint to the zero address");
                          
                                  _beforeTokenTransfer(address(0), account, amount);
                          
                                  _totalSupply = _totalSupply.add(amount);
                                  _balances[account] = _balances[account].add(amount);
                                  emit Transfer(address(0), account, amount);
                              }
                          
                              /**
                               * @dev Destroys `amount` tokens from `account`, reducing the
                               * total supply.
                               *
                               * Emits a {Transfer} event with `to` set to the zero address.
                               *
                               * Requirements:
                               *
                               * - `account` cannot be the zero address.
                               * - `account` must have at least `amount` tokens.
                               */
                              function _burn(address account, uint256 amount) internal virtual {
                                  require(account != address(0), "ERC20: burn from the zero address");
                          
                                  _beforeTokenTransfer(account, address(0), amount);
                          
                                  _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                                  _totalSupply = _totalSupply.sub(amount);
                                  emit Transfer(account, address(0), amount);
                              }
                          
                              /**
                               * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
                               *
                               * This internal function is equivalent to `approve`, and can be used to
                               * e.g. set automatic allowances for certain subsystems, etc.
                               *
                               * Emits an {Approval} event.
                               *
                               * Requirements:
                               *
                               * - `owner` cannot be the zero address.
                               * - `spender` cannot be the zero address.
                               */
                              function _approve(
                                  address owner,
                                  address spender,
                                  uint256 amount
                              ) internal virtual {
                                  require(owner != address(0), "ERC20: approve from the zero address");
                                  require(spender != address(0), "ERC20: approve to the zero address");
                          
                                  _allowances[owner][spender] = amount;
                                  emit Approval(owner, spender, amount);
                              }
                          
                              /**
                               * @dev Sets {decimals} to a value other than the default one of 18.
                               *
                               * WARNING: This function should only be called from the constructor. Most
                               * applications that interact with token contracts will not expect
                               * {decimals} to ever change, and may work incorrectly if it does.
                               */
                              function _setupDecimals(uint8 decimals_) internal virtual {
                                  _decimals = decimals_;
                              }
                          
                              /**
                               * @dev Hook that is called before any transfer of tokens. This includes
                               * minting and burning.
                               *
                               * Calling conditions:
                               *
                               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                               * will be to transferred to `to`.
                               * - when `from` is zero, `amount` tokens will be minted for `to`.
                               * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
                               * - `from` and `to` are never both zero.
                               *
                               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                               */
                              function _beforeTokenTransfer(
                                  address from,
                                  address to,
                                  uint256 amount
                              ) internal virtual {}
                          }
                          
                          /**
                           * @dev Contract module which provides a basic access control mechanism, where
                           * there is an account (an owner) that can be granted exclusive access to
                           * specific functions.
                           *
                           * By default, the owner account will be the one that deploys the contract. This
                           * can later be changed with {transferOwnership}.
                           *
                           * This module is used through inheritance. It will make available the modifier
                           * `onlyOwner`, which can be applied to your functions to restrict their use to
                           * the owner.
                           */
                          abstract contract Ownable is Context {
                              address private _owner;
                          
                              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                          
                              /**
                               * @dev Initializes the contract setting the deployer as the initial owner.
                               */
                              constructor() internal {
                                  address msgSender = _msgSender();
                                  _owner = msgSender;
                                  emit OwnershipTransferred(address(0), msgSender);
                              }
                          
                              /**
                               * @dev Returns the address of the current owner.
                               */
                              function owner() public view virtual returns (address) {
                                  return _owner;
                              }
                          
                              /**
                               * @dev Throws if called by any account other than the owner.
                               */
                              modifier onlyOwner() {
                                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
                                  _;
                              }
                          
                              /**
                               * @dev Leaves the contract without owner. It will not be possible to call
                               * `onlyOwner` functions anymore. Can only be called by the current owner.
                               *
                               * NOTE: Renouncing ownership will leave the contract without an owner,
                               * thereby removing any functionality that is only available to the owner.
                               */
                              function renounceOwnership() public virtual onlyOwner {
                                  emit OwnershipTransferred(_owner, address(0));
                                  _owner = address(0);
                              }
                          
                              /**
                               * @dev Transfers ownership of the contract to a new account (`newOwner`).
                               * Can only be called by the current owner.
                               */
                              function transferOwnership(address newOwner) public virtual onlyOwner {
                                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                                  emit OwnershipTransferred(_owner, newOwner);
                                  _owner = newOwner;
                              }
                          }
                          
                          abstract contract IMintableERC20 is IERC20 {
                              function mint(uint256 amount) public virtual;
                          
                              function mintTo(address account, uint256 amount) public virtual;
                          
                              function burn(uint256 amount) public virtual;
                          
                              function setMinter(address account, bool isMinter) public virtual;
                          }
                          
                          contract PopToken is ERC20("POP Token", "POP!"), IMintableERC20, Ownable {
                              mapping(address => bool) public minter;
                              uint256 public maxSupply;
                          
                              event MinterUpdate(address indexed account, bool isMinter);
                          
                              constructor(uint256 _maxSupply) public {
                                  maxSupply = _maxSupply;
                              }
                          
                              modifier onlyMinter() {
                                  require(minter[_msgSender()], "User is not a minter.");
                                  _;
                              }
                          
                              function mint(uint256 _amount) public override onlyMinter {
                                  require(totalSupply().add(_amount) <= maxSupply, "cannot mint more than maxSupply");
                                  _mint(_msgSender(), _amount);
                              }
                          
                              function mintTo(address _account, uint256 _amount) public override onlyMinter {
                                  require(totalSupply().add(_amount) <= maxSupply, "cannot mint more than maxSupply");
                                  _mint(_account, _amount);
                              }
                          
                              function setMinter(address _account, bool _isMinter) public override onlyOwner {
                                  require(_account != address(0), "address can not be 0");
                                  minter[_account] = _isMinter;
                                  emit MinterUpdate(_account, _isMinter);
                              }
                          
                              function burn(uint256 _amount) public override {
                                  _burn(_msgSender(), _amount);
                              }
                          }
                          
                          abstract contract IRewardManager {
                              function add(uint256 _allocPoint, address _newMlp) public virtual;
                          
                              function notifyDeposit(address _account, uint256 _amount) public virtual;
                          
                              function notifyWithdraw(address _account, uint256 _amount) public virtual;
                          
                              function getPoolSupply(address pool) public view virtual returns (uint256);
                          
                              function getUserAmount(address pool, address user) public view virtual returns (uint256);
                          }
                          
                          // stolen from Sushiswap MasterChef: https://github.com/sushiswap/sushiswap/blob/master/contracts/MasterChef.sol
                          contract PopReward is Initializable, OwnableUpgradeSafe, IRewardManager {
                              using SafeMath for uint256;
                              using SafeERC20 for IERC20;
                          
                              // Info of each user.
                              struct UserInfo {
                                  uint256 amount; // How many LP tokens the user has provided.
                                  uint256 rewardDebt; // Reward debt
                              }
                          
                              // Info of each pool.
                              struct PoolInfo {
                                  uint256 allocPoint; // How many allocation points assigned to this pool. POPs to distribute per block.
                                  uint256 lastRewardBlock; // Last block number that POPs distribution occurs.
                                  uint256 accPopPerShare; // Accumulated POPs per share, times 1e12. See below.
                                  uint256 lpSupply; // Total amount of lp token staked
                              }
                          
                              PopToken public pop;
                              // Block number when bonus POP period ends.
                              uint256 public bonusEndBlock;
                              // POP tokens created per block.
                              uint256 public popPerBlock;
                              // Bonus multiplier for early pop makers.
                              uint256 public constant BONUS_MULTIPLIER = 10;
                              // Info of each pool.
                              PoolInfo[] public poolInfo;
                              // Info of each user that stakes LP tokens.
                              mapping(uint256 => mapping(address => UserInfo)) public userInfo;
                              // Total allocation points. Must be the sum of all allocation points in all pools.
                              uint256 public totalAllocPoint = 0;
                              // The block number when POP mining starts.
                              uint256 public startBlock;
                              // authorized MLPs
                              uint256 private constant UINT256_MAX = ~uint256(0);
                              mapping(address => uint256) public pidByAddress;
                              address public popMarketplace;
                              mapping(address => bool) public authorizedMlp;
                          
                              PoolInfo public popVault;
                              mapping(address => UserInfo) public popStaker;
                          
                              event PopDeposit(address indexed user, uint256 amount);
                              event PopWithdraw(address indexed user, uint256 amount);
                          
                              event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
                              event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
                              event RewardPaid(address indexed user, uint256 indexed pid, uint256 amount);
                          
                              function initialize(
                                  PopToken _pop,
                                  uint256 _popPerBlock,
                                  uint256 _startBlock,
                                  uint256 _bonusEndBlock,
                                  uint256 _popAllocPoint,
                                  uint256 _popVaultStarts
                              ) public initializer {
                                  OwnableUpgradeSafe.__Ownable_init();
                                  pop = _pop;
                                  popPerBlock = _popPerBlock;
                                  bonusEndBlock = _bonusEndBlock;
                                  startBlock = _startBlock;
                                  popVault = PoolInfo({allocPoint: _popAllocPoint, lastRewardBlock: _popVaultStarts, accPopPerShare: 0, lpSupply: 0});
                                  totalAllocPoint = _popAllocPoint;
                              }
                          
                              function poolLength() external view returns (uint256) {
                                  return poolInfo.length;
                              }
                          
                              function getPoolSupply(address pool) public view override returns (uint256) {
                                  return poolInfo[pidByAddress[pool]].lpSupply;
                              }
                          
                              function getUserAmount(address pool, address user) public view override returns (uint256) {
                                  return userInfo[pidByAddress[pool]][user].amount;
                              }
                          
                              function setPopMarketplace(address _newMarketplace) public onlyOwner {
                                  require(_newMarketplace != address(0), "Address can not be 0");
                                  popMarketplace = _newMarketplace;
                              }
                          
                              // Add a new pool. Can only be called by the PopMarketplace.
                              function add(uint256 _allocPoint, address _newMlp) public override {
                                  require(msg.sender == popMarketplace, "only the marketplace can add a pool");
                                  uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
                                  totalAllocPoint = totalAllocPoint.add(_allocPoint);
                                  poolInfo.push(PoolInfo({allocPoint: _allocPoint, lastRewardBlock: lastRewardBlock, accPopPerShare: 0, lpSupply: 0}));
                                  pidByAddress[_newMlp] = poolInfo.length - 1;
                                  authorizedMlp[_newMlp] = true;
                              }
                          
                              // Update the given pool's POP allocation point. Can only be called by the owner.
                              function set(
                                  uint256 _pid,
                                  uint256 _allocPoint,
                                  bool _withUpdate
                              ) public onlyOwner {
                                  PoolInfo storage pool = poolInfo[_pid];
                                  _set(pool, _allocPoint, _withUpdate);
                              }
                          
                              function _set(
                                  PoolInfo storage pool,
                                  uint256 _allocPoint,
                                  bool _withUpdate
                              ) private {
                                  if (_withUpdate) {
                                      massUpdatePools();
                                  }
                                  totalAllocPoint = totalAllocPoint.sub(pool.allocPoint).add(_allocPoint);
                                  pool.allocPoint = _allocPoint;
                              }
                          
                              // Return reward multiplier over the given _from to _to block.
                              function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
                                  if (_to <= bonusEndBlock) {
                                      return _to.sub(_from).mul(BONUS_MULTIPLIER);
                                  } else if (_from >= bonusEndBlock) {
                                      return _to.sub(_from);
                                  } else {
                                      return bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add(_to.sub(bonusEndBlock));
                                  }
                              }
                          
                              // View function to see pending POPs on frontend.
                              function pendingPop(uint256 _pid, address _user) external view returns (uint256) {
                                  PoolInfo storage pool = poolInfo[_pid];
                                  UserInfo storage user = userInfo[_pid][_user];
                                  return _pendingPop(pool, user);
                              }
                          
                              function _pendingPop(PoolInfo storage pool, UserInfo storage user) private view returns (uint256) {
                                  uint256 accPopPerShare = pool.accPopPerShare;
                                  uint256 lpSupply = pool.lpSupply;
                                  if (block.number > pool.lastRewardBlock && lpSupply != 0) {
                                      uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
                                      uint256 popReward = multiplier.mul(popPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
                                      accPopPerShare = accPopPerShare.add(popReward.mul(1e12).div(lpSupply));
                                  }
                                  return user.amount.mul(accPopPerShare).div(1e12).sub(user.rewardDebt);
                              }
                          
                              function vaultPendingPop(address _account) external view returns (uint256) {
                                  UserInfo storage user = popStaker[_account];
                                  return _pendingPop(popVault, user);
                              }
                          
                              // Update reward variables for all pools. Be careful of gas spending!
                              function massUpdatePools() public {
                                  uint256 length = poolInfo.length;
                                  for (uint256 pid = 0; pid < length; ++pid) {
                                      updatePool(pid);
                                  }
                                  updatePopVault();
                              }
                          
                              // Update reward variables of the given pool to be up-to-date.
                              function updatePool(uint256 _pid) public {
                                  PoolInfo storage pool = poolInfo[_pid];
                                  return _updatePool(pool);
                              }
                          
                              function _updatePool(PoolInfo storage pool) private {
                                  if (block.number <= pool.lastRewardBlock) {
                                      return;
                                  }
                                  uint256 lpSupply = pool.lpSupply;
                                  if (lpSupply == 0) {
                                      pool.lastRewardBlock = block.number;
                                      return;
                                  }
                                  uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
                                  uint256 popReward = multiplier.mul(popPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
                                  pop.mintTo(address(this), popReward);
                                  pool.accPopPerShare = pool.accPopPerShare.add(popReward.mul(1e12).div(lpSupply));
                                  pool.lastRewardBlock = block.number;
                              }
                          
                              // Notify the amount of LP tokens staked in the PopVault.
                              function notifyDeposit(address _account, uint256 _amount) public override {
                                  require(authorizedMlp[msg.sender], "unauthorized sender");
                                  uint256 _pid = pidByAddress[msg.sender];
                                  PoolInfo storage pool = poolInfo[_pid];
                                  UserInfo storage user = userInfo[_pid][_account];
                                  updatePool(_pid);
                                  _notifyDeposit(_account, pool, user, _amount);
                                  emit Deposit(_account, _pid, _amount);
                              }
                          
                              function _notifyDeposit(
                                  address _account,
                                  PoolInfo storage pool,
                                  UserInfo storage user,
                                  uint256 _amount
                              ) private {
                                  if (user.amount > 0) {
                                      uint256 pending = user.amount.mul(pool.accPopPerShare).div(1e12).sub(user.rewardDebt);
                                      safePopTransfer(_account, pending);
                                  }
                                  pool.lpSupply = pool.lpSupply.add(_amount);
                                  user.amount = user.amount.add(_amount);
                                  user.rewardDebt = user.amount.mul(pool.accPopPerShare).div(1e12);
                              }
                          
                              // Notify the amount of LP token withdrawn from the PopVault.
                              function notifyWithdraw(address _account, uint256 _amount) public override {
                                  require(authorizedMlp[msg.sender], "unauthorized sender");
                                  uint256 _pid = pidByAddress[msg.sender];
                                  PoolInfo storage pool = poolInfo[_pid];
                                  UserInfo storage user = userInfo[_pid][_account];
                                  updatePool(_pid);
                                  _notifyWithdraw(_account, pool, user, _amount, _pid);
                                  emit Withdraw(_account, _pid, _amount);
                              }
                          
                              function _notifyWithdraw(
                                  address _account,
                                  PoolInfo storage pool,
                                  UserInfo storage user,
                                  uint256 _amount,
                                  uint256 _pid
                              ) private {
                                  require(user.amount >= _amount, "withdraw: not good");
                                  uint256 pending = user.amount.mul(pool.accPopPerShare).div(1e12).sub(user.rewardDebt);
                                  if (pending > 0) {
                                      safePopTransfer(_account, pending);
                                  }
                                  user.amount = user.amount.sub(_amount);
                                  user.rewardDebt = user.amount.mul(pool.accPopPerShare).div(1e12);
                                  pool.lpSupply = pool.lpSupply.sub(_amount);
                                  emit RewardPaid(_account, _pid, pending);
                              }
                          
                              // Safe pop transfer function, just in case if rounding error causes pool to not have enough POPs.
                              function safePopTransfer(address _to, uint256 _amount) internal {
                                  uint256 popBal = pop.balanceOf(address(this)).sub(popVault.lpSupply);
                                  if (_amount > popBal) {
                                      pop.transfer(_to, popBal);
                                  } else {
                                      pop.transfer(_to, _amount);
                                  }
                              }
                          
                              function stakePop(uint256 _amount) public {
                                  UserInfo storage user = popStaker[msg.sender];
                                  updatePopVault();
                                  _notifyDeposit(msg.sender, popVault, user, _amount);
                                  require(pop.transferFrom(msg.sender, address(this), _amount), "transfer failed");
                                  emit PopDeposit(msg.sender, _amount);
                              }
                          
                              function withdrawPop(uint256 _amount) public {
                                  UserInfo storage user = popStaker[msg.sender];
                                  updatePopVault();
                                  _notifyWithdraw(msg.sender, popVault, user, _amount, UINT256_MAX);
                                  require(pop.transfer(msg.sender, _amount), "transfer failed");
                                  emit PopWithdraw(msg.sender, _amount);
                              }
                          
                              function updatePopVault() public {
                                  _updatePool(popVault);
                              }
                          
                              function setPopVault(uint256 _allocPoint) public onlyOwner {
                                  _set(popVault, _allocPoint, false);
                              }
                          
                              function setPopVaultStartBlock(uint256 _startBlock) external onlyOwner {
                                  popVault.lastRewardBlock = _startBlock;
                              }
                          
                              function claimRewards(uint256 _poolId) external {
                                  updatePool(_poolId);
                                  _harvest(_poolId);
                              }
                          
                              function claimPopRewards() external {
                                  updatePopVault();
                                  _harvestPopRewards(popVault);
                              }
                          
                              function _harvestPopRewards(PoolInfo storage pool) internal {
                                  UserInfo storage user = popStaker[msg.sender];
                                  if (user.amount == 0) return;
                                  uint256 pending = user.amount.mul(pool.accPopPerShare).div(1e12).sub(user.rewardDebt);
                                  if (pending > 0) {
                                      user.rewardDebt = user.amount.mul(pool.accPopPerShare).div(1e12);
                                      // Pay out the pending rewards
                                      safePopTransfer(msg.sender, pending);
                                      emit RewardPaid(msg.sender, UINT256_MAX, pending);
                                      return;
                                  }
                                  user.rewardDebt = user.amount.mul(pool.accPopPerShare).div(1e12);
                              }
                          
                              function _harvest(uint256 _poolId) internal {
                                  PoolInfo storage pool = poolInfo[_poolId];
                                  UserInfo storage user = userInfo[_poolId][msg.sender];
                                  if (user.amount == 0) return;
                                  uint256 pending = user.amount.mul(pool.accPopPerShare).div(1e12).sub(user.rewardDebt);
                                  if (pending > 0) {
                                      user.rewardDebt = user.amount.mul(pool.accPopPerShare).div(1e12);
                                      // Pay out the pending rewards
                                      safePopTransfer(msg.sender, pending);
                                      emit RewardPaid(msg.sender, _poolId, pending);
                                      return;
                                  }
                                  user.rewardDebt = user.amount.mul(pool.accPopPerShare).div(1e12);
                              }
                          }

                          File 7 of 7: PopTokenV3
                          // SPDX-License-Identifier: MIT
                          
                          pragma solidity >=0.6.6;
                          
                          /**
                           * @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);
                          }
                          
                          /**
                           * @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;
                              }
                          }
                          
                          /**
                           * @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;
                          }
                          
                          /*
                           * @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;
                          }
                          
                          /**
                           * @dev Collection of functions related to the address type
                           */
                          library Address {
                              /**
                               * @dev Returns true if `account` is a contract.
                               *
                               * [IMPORTANT]
                               * ====
                               * It is unsafe to assume that an address for which this function returns
                               * false is an externally-owned account (EOA) and not a contract.
                               *
                               * Among others, `isContract` will return false for the following
                               * types of addresses:
                               *
                               *  - an externally-owned account
                               *  - a contract in construction
                               *  - an address where a contract will be created
                               *  - an address where a contract lived, but was destroyed
                               * ====
                               */
                              function isContract(address account) internal view returns (bool) {
                                  // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                                  // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                                  // for accounts without code, i.e. `keccak256('')`
                                  bytes32 codehash;
                                  bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                                  // solhint-disable-next-line no-inline-assembly
                                  assembly {
                                      codehash := extcodehash(account)
                                  }
                                  return (codehash != accountHash && codehash != 0x0);
                              }
                          
                              /**
                               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                               * `recipient`, forwarding all available gas and reverting on errors.
                               *
                               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                               * of certain opcodes, possibly making contracts go over the 2300 gas limit
                               * imposed by `transfer`, making them unable to receive funds via
                               * `transfer`. {sendValue} removes this limitation.
                               *
                               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                               *
                               * IMPORTANT: because control is transferred to `recipient`, care must be
                               * taken to not create reentrancy vulnerabilities. Consider using
                               * {ReentrancyGuard} or the
                               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                               */
                              function sendValue(address payable recipient, uint256 amount) internal {
                                  require(address(this).balance >= amount, "Address: insufficient balance");
                          
                                  // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                                  (bool success, ) = recipient.call{value: amount}("");
                                  require(success, "Address: unable to send value, recipient may have reverted");
                              }
                          }
                          
                          /**
                           * @dev Implementation of the {IERC20} interface.
                           *
                           * This implementation is agnostic to the way tokens are created. This means
                           * that a supply mechanism has to be added in a derived contract using {_mint}.
                           * For a generic mechanism see {ERC20MinterPauser}.
                           *
                           * TIP: For a detailed writeup see our guide
                           * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
                           * to implement supply mechanisms].
                           *
                           * We have followed general OpenZeppelin guidelines: functions revert instead
                           * of returning `false` on failure. This behavior is nonetheless conventional
                           * and does not conflict with the expectations of ERC20 applications.
                           *
                           * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
                           * This allows applications to reconstruct the allowance for all accounts just
                           * by listening to said events. Other implementations of the EIP may not emit
                           * these events, as it isn't required by the specification.
                           *
                           * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
                           * functions have been added to mitigate the well-known issues around setting
                           * allowances. See {IERC20-approve}.
                           */
                          contract ERC20UpgradeSafe is Initializable, ContextUpgradeSafe, IERC20 {
                              using SafeMath for uint256;
                              using Address for address;
                          
                              mapping(address => uint256) private _balances;
                          
                              mapping(address => mapping(address => uint256)) private _allowances;
                          
                              uint256 private _totalSupply;
                          
                              string private _name;
                              string private _symbol;
                              uint8 private _decimals;
                          
                              /**
                               * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
                               * a default value of 18.
                               *
                               * To select a different value for {decimals}, use {_setupDecimals}.
                               *
                               * All three of these values are immutable: they can only be set once during
                               * construction.
                               */
                          
                              function __ERC20_init(string memory name, string memory symbol) internal initializer {
                                  __Context_init_unchained();
                                  __ERC20_init_unchained(name, symbol);
                              }
                          
                              function __ERC20_init_unchained(string memory name, string memory symbol) internal initializer {
                                  _name = name;
                                  _symbol = symbol;
                                  _decimals = 18;
                              }
                          
                              /**
                               * @dev Returns the name of the token.
                               */
                              function name() public view returns (string memory) {
                                  return _name;
                              }
                          
                              /**
                               * @dev Returns the symbol of the token, usually a shorter version of the
                               * name.
                               */
                              function symbol() public view returns (string memory) {
                                  return _symbol;
                              }
                          
                              /**
                               * @dev Returns the number of decimals used to get its user representation.
                               * For example, if `decimals` equals `2`, a balance of `505` tokens should
                               * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                               *
                               * Tokens usually opt for a value of 18, imitating the relationship between
                               * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
                               * called.
                               *
                               * NOTE: This information is only used for _display_ purposes: it in
                               * no way affects any of the arithmetic of the contract, including
                               * {IERC20-balanceOf} and {IERC20-transfer}.
                               */
                              function decimals() public view returns (uint8) {
                                  return _decimals;
                              }
                          
                              /**
                               * @dev See {IERC20-totalSupply}.
                               */
                              function totalSupply() public view override returns (uint256) {
                                  return _totalSupply;
                              }
                          
                              /**
                               * @dev See {IERC20-balanceOf}.
                               */
                              function balanceOf(address account) public view override returns (uint256) {
                                  return _balances[account];
                              }
                          
                              /**
                               * @dev See {IERC20-transfer}.
                               *
                               * Requirements:
                               *
                               * - `recipient` cannot be the zero address.
                               * - the caller must have a balance of at least `amount`.
                               */
                              function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                                  _transfer(_msgSender(), recipient, amount);
                                  return true;
                              }
                          
                              /**
                               * @dev See {IERC20-allowance}.
                               */
                              function allowance(address owner, address spender) public view virtual override returns (uint256) {
                                  return _allowances[owner][spender];
                              }
                          
                              /**
                               * @dev See {IERC20-approve}.
                               *
                               * Requirements:
                               *
                               * - `spender` cannot be the zero address.
                               */
                              function approve(address spender, uint256 amount) public virtual override returns (bool) {
                                  _approve(_msgSender(), spender, amount);
                                  return true;
                              }
                          
                              /**
                               * @dev See {IERC20-transferFrom}.
                               *
                               * Emits an {Approval} event indicating the updated allowance. This is not
                               * required by the EIP. See the note at the beginning of {ERC20};
                               *
                               * Requirements:
                               * - `sender` and `recipient` cannot be the zero address.
                               * - `sender` must have a balance of at least `amount`.
                               * - the caller must have allowance for ``sender``'s tokens of at least
                               * `amount`.
                               */
                              function transferFrom(
                                  address sender,
                                  address recipient,
                                  uint256 amount
                              ) public virtual override returns (bool) {
                                  _transfer(sender, recipient, amount);
                                  _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                                  return true;
                              }
                          
                              /**
                               * @dev Atomically increases the allowance granted to `spender` by the caller.
                               *
                               * This is an alternative to {approve} that can be used as a mitigation for
                               * problems described in {IERC20-approve}.
                               *
                               * Emits an {Approval} event indicating the updated allowance.
                               *
                               * Requirements:
                               *
                               * - `spender` cannot be the zero address.
                               */
                              function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                                  _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                                  return true;
                              }
                          
                              /**
                               * @dev Atomically decreases the allowance granted to `spender` by the caller.
                               *
                               * This is an alternative to {approve} that can be used as a mitigation for
                               * problems described in {IERC20-approve}.
                               *
                               * Emits an {Approval} event indicating the updated allowance.
                               *
                               * Requirements:
                               *
                               * - `spender` cannot be the zero address.
                               * - `spender` must have allowance for the caller of at least
                               * `subtractedValue`.
                               */
                              function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                                  _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                                  return true;
                              }
                          
                              /**
                               * @dev Moves tokens `amount` from `sender` to `recipient`.
                               *
                               * This is internal function is equivalent to {transfer}, and can be used to
                               * e.g. implement automatic token fees, slashing mechanisms, etc.
                               *
                               * Emits a {Transfer} event.
                               *
                               * Requirements:
                               *
                               * - `sender` cannot be the zero address.
                               * - `recipient` cannot be the zero address.
                               * - `sender` must have a balance of at least `amount`.
                               */
                              function _transfer(
                                  address sender,
                                  address recipient,
                                  uint256 amount
                              ) internal virtual {
                                  require(sender != address(0), "ERC20: transfer from the zero address");
                                  require(recipient != address(0), "ERC20: transfer to the zero address");
                          
                                  _beforeTokenTransfer(sender, recipient, amount);
                          
                                  _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                                  _balances[recipient] = _balances[recipient].add(amount);
                                  emit Transfer(sender, recipient, amount);
                              }
                          
                              /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                               * the total supply.
                               *
                               * Emits a {Transfer} event with `from` set to the zero address.
                               *
                               * Requirements
                               *
                               * - `to` cannot be the zero address.
                               */
                              function _mint(address account, uint256 amount) internal virtual {
                                  require(account != address(0), "ERC20: mint to the zero address");
                          
                                  _beforeTokenTransfer(address(0), account, amount);
                          
                                  _totalSupply = _totalSupply.add(amount);
                                  _balances[account] = _balances[account].add(amount);
                                  emit Transfer(address(0), account, amount);
                              }
                          
                              /**
                               * @dev Destroys `amount` tokens from `account`, reducing the
                               * total supply.
                               *
                               * Emits a {Transfer} event with `to` set to the zero address.
                               *
                               * Requirements
                               *
                               * - `account` cannot be the zero address.
                               * - `account` must have at least `amount` tokens.
                               */
                              function _burn(address account, uint256 amount) internal virtual {
                                  require(account != address(0), "ERC20: burn from the zero address");
                          
                                  _beforeTokenTransfer(account, address(0), amount);
                          
                                  _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                                  _totalSupply = _totalSupply.sub(amount);
                                  emit Transfer(account, address(0), amount);
                              }
                          
                              /**
                               * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
                               *
                               * This is internal function is equivalent to `approve`, and can be used to
                               * e.g. set automatic allowances for certain subsystems, etc.
                               *
                               * Emits an {Approval} event.
                               *
                               * Requirements:
                               *
                               * - `owner` cannot be the zero address.
                               * - `spender` cannot be the zero address.
                               */
                              function _approve(
                                  address owner,
                                  address spender,
                                  uint256 amount
                              ) internal virtual {
                                  require(owner != address(0), "ERC20: approve from the zero address");
                                  require(spender != address(0), "ERC20: approve to the zero address");
                          
                                  _allowances[owner][spender] = amount;
                                  emit Approval(owner, spender, amount);
                              }
                          
                              /**
                               * @dev Sets {decimals} to a value other than the default one of 18.
                               *
                               * WARNING: This function should only be called from the constructor. Most
                               * applications that interact with token contracts will not expect
                               * {decimals} to ever change, and may work incorrectly if it does.
                               */
                              function _setupDecimals(uint8 decimals_) internal {
                                  _decimals = decimals_;
                              }
                          
                              /**
                               * @dev Hook that is called before any transfer of tokens. This includes
                               * minting and burning.
                               *
                               * Calling conditions:
                               *
                               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                               * will be to transferred to `to`.
                               * - when `from` is zero, `amount` tokens will be minted for `to`.
                               * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
                               * - `from` and `to` are never both zero.
                               *
                               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                               */
                              function _beforeTokenTransfer(
                                  address from,
                                  address to,
                                  uint256 amount
                              ) internal virtual {}
                          
                              uint256[44] private __gap;
                          }
                          
                          /**
                           * @title SafeERC20
                           * @dev Wrappers around ERC20 operations that throw on failure (when the token
                           * contract returns false). Tokens that return no value (and instead revert or
                           * throw on failure) are also supported, non-reverting calls are assumed to be
                           * successful.
                           * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                           */
                          library SafeERC20 {
                              using SafeMath for uint256;
                              using Address for address;
                          
                              function safeTransfer(
                                  IERC20 token,
                                  address to,
                                  uint256 value
                              ) internal {
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                              }
                          
                              function safeTransferFrom(
                                  IERC20 token,
                                  address from,
                                  address to,
                                  uint256 value
                              ) internal {
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                              }
                          
                              function safeApprove(
                                  IERC20 token,
                                  address spender,
                                  uint256 value
                              ) internal {
                                  // safeApprove should only be called when setting an initial allowance,
                                  // or when resetting it to zero. To increase and decrease it, use
                                  // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                                  // solhint-disable-next-line max-line-length
                                  require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance");
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                              }
                          
                              function safeIncreaseAllowance(
                                  IERC20 token,
                                  address spender,
                                  uint256 value
                              ) internal {
                                  uint256 newAllowance = token.allowance(address(this), spender).add(value);
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                          
                              function safeDecreaseAllowance(
                                  IERC20 token,
                                  address spender,
                                  uint256 value
                              ) internal {
                                  uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                          
                              /**
                               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                               * on the return value: the return value is optional (but if data is returned, it must not be false).
                               * @param token The token targeted by the call.
                               * @param data The call data (encoded using abi.encode or one of its variants).
                               */
                              function _callOptionalReturn(IERC20 token, bytes memory data) private {
                                  // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                                  // we're implementing it ourselves.
                          
                                  // A Solidity high level call has three parts:
                                  //  1. The target address is checked to verify it contains contract code
                                  //  2. The call itself is made, and success asserted
                                  //  3. The return value is decoded, which in turn checks the size of the returned data.
                                  // solhint-disable-next-line max-line-length
                                  require(address(token).isContract(), "SafeERC20: call to non-contract");
                          
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (bool success, bytes memory returndata) = address(token).call(data);
                                  require(success, "SafeERC20: low-level call failed");
                          
                                  if (returndata.length > 0) {
                                      // Return data is optional
                                      // solhint-disable-next-line max-line-length
                                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                  }
                              }
                          }
                          
                          /**
                           * @dev Contract module which provides a basic access control mechanism, where
                           * there is an account (an owner) that can be granted exclusive access to
                           * specific functions.
                           *
                           * By default, the owner account will be the one that deploys the contract. This
                           * can later be changed with {transferOwnership}.
                           *
                           * This module is used through inheritance. It will make available the modifier
                           * `onlyOwner`, which can be applied to your functions to restrict their use to
                           * the owner.
                           */
                          contract OwnableUpgradeSafe is Initializable, ContextUpgradeSafe {
                              address private _owner;
                          
                              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                          
                              /**
                               * @dev Initializes the contract setting the deployer as the initial owner.
                               */
                          
                              function __Ownable_init() internal initializer {
                                  __Context_init_unchained();
                                  __Ownable_init_unchained();
                              }
                          
                              function __Ownable_init_unchained() internal initializer {
                                  address msgSender = _msgSender();
                                  _owner = msgSender;
                                  emit OwnershipTransferred(address(0), msgSender);
                              }
                          
                              /**
                               * @dev Returns the address of the current owner.
                               */
                              function owner() public view returns (address) {
                                  return _owner;
                              }
                          
                              /**
                               * @dev Throws if called by any account other than the owner.
                               */
                              modifier onlyOwner() {
                                  require(_owner == _msgSender(), "Ownable: caller is not the owner");
                                  _;
                              }
                          
                              /**
                               * @dev Leaves the contract without owner. It will not be possible to call
                               * `onlyOwner` functions anymore. Can only be called by the current owner.
                               *
                               * NOTE: Renouncing ownership will leave the contract without an owner,
                               * thereby removing any functionality that is only available to the owner.
                               */
                              function renounceOwnership() public virtual onlyOwner {
                                  emit OwnershipTransferred(_owner, address(0));
                                  _owner = address(0);
                              }
                          
                              /**
                               * @dev Transfers ownership of the contract to a new account (`newOwner`).
                               * Can only be called by the current owner.
                               */
                              function transferOwnership(address newOwner) public virtual onlyOwner {
                                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                                  emit OwnershipTransferred(_owner, newOwner);
                                  _owner = newOwner;
                              }
                          
                              uint256[49] private __gap;
                          }
                          
                          interface IBurnable {
                              function burn(uint256 amount) external;
                          }
                          
                          contract PopTokenV3 is ERC20UpgradeSafe, OwnableUpgradeSafe {
                              using SafeERC20 for IERC20;
                              using SafeMath for uint256;
                          
                              /* ========== STATE VARIABLES ========== */
                          
                              address public governance;
                          
                              address public legacyPopToken;
                          
                              mapping(address => bool) public minter;
                              uint256 public cap;
                          
                              uint256 public startBlock;
                              uint256 public endBlock;
                          
                              /* ========== EVENTS ========== */
                              event MinterUpdate(address indexed account, bool isMinter);
                          
                              /* ========== Modifiers =============== */
                          
                              modifier onlyMinter() {
                                  require(minter[msg.sender], "!minter");
                                  _;
                              }
                          
                              /* ========== GOVERNANCE ========== */
                          
                              function initialize(address _legacyPopToken, uint256 _cap, uint256 _startBlock, uint256 _endBlock) public initializer {
                                  OwnableUpgradeSafe.__Ownable_init();
                                  __ERC20_init("POP!", "POP!");
                                  legacyPopToken = _legacyPopToken;
                                  cap = _cap;
                                  startBlock = _startBlock;
                                  endBlock = _endBlock;
                              }
                          
                              function setCap(uint256 _cap) external onlyOwner {
                                  require(_cap >= totalSupply(), "_cap is below current total supply");
                                  cap = _cap;
                              }
                          
                              function setStartBlock(uint256 _startBlock) external onlyOwner {
                                  startBlock = _startBlock;
                              }
                          
                              function setEndBlock(uint256 _endBlock) external onlyOwner {
                                  endBlock = _endBlock;
                              }
                          
                              function setMinter(address _account, bool _isMinter) external onlyOwner {
                                  require(_account != address(0), "address can not be 0");
                                  minter[_account] = _isMinter;
                                  emit MinterUpdate(_account, _isMinter);
                              }
                          
                              function mint(uint256 _amount) external onlyMinter {
                                  _mint(msg.sender, _amount);
                              }
                          
                              function mintTo(address _account, uint256 _amount) external onlyMinter {
                                  _mint(_account, _amount);
                              }
                          
                              function burn(uint256 _amount) external {
                                  _burn(msg.sender, _amount);
                              }
                          
                              /**
                               * @dev See {ERC20-_beforeTokenTransfer}.
                               *
                               * Requirements:
                               *
                               * - minted tokens must not cause the total supply to go over the cap.
                               */
                              function _beforeTokenTransfer(
                                  address from,
                                  address to,
                                  uint256 amount
                              ) internal virtual override {
                                  super._beforeTokenTransfer(from, to, amount);
                          
                                  if (from == address(0)) {
                                      // When minting tokens
                                      require(totalSupply().add(amount) <= cap, "cap exceeded");
                                  }
                              }
                          
                              // Migrate from old POP
                              function migrate(uint256 _amount) external {
                                  require(block.number >= startBlock, "migration has not been opened yet");
                                  require(block.number <= endBlock, "migration has been closed");
                                  IERC20(legacyPopToken).safeTransferFrom(msg.sender, address(this), _amount);
                                  IBurnable(legacyPopToken).burn(_amount);
                                  _mint(msg.sender, _amount);
                              }
                          }