ETH Price: $2,515.77 (-1.16%)

Transaction Decoder

Block:
17512442 at Jun-19-2023 07:55:47 AM +UTC
Transaction Fee:
0.00164453684468895 ETH $4.14
Gas Used:
98,590 Gas / 16.680564405 Gwei

Emitted Events:

126 PoolToken.Transfer( from=TransparentUpgradeableProxyImmutable, to=[Sender] 0xa14a0111eaecfede37c2048213291c8b116b4311, value=70303793532822198265141 )
127 TransparentUpgradeableProxyImmutable.0x09cf8000f644f8fe85b5fa4e034c4611d089888d0760698d80e34e5a44354aa1( 0x09cf8000f644f8fe85b5fa4e034c4611d089888d0760698d80e34e5a44354aa1, 0x000000000000000000000000f629cbd94d3791c9250152bd8dfbdf380e2a3b9c, 0x000000000000000000000000a14a0111eaecfede37c2048213291c8b116b4311, 0x0000000000000000000000000000000000000000000000000000000000000a55, 000000000000000000000000000000000000000000000ee32d20f4a3bfd9a535, 000000000000000000000000000000000000000000000ee714bf6f9be7bc283e, 0000000000000000000000000000000000000000000000000000000001d88646 )

Account State Difference:

  Address   Before After State Difference Code
(builder0x69)
1.818180639995214968 Eth1.818195428495214968 Eth0.0000147885
0x857Eb0Eb...45EbA9B8a
(Bancor: Pending Withdrawals V3)
0x9250FD96...F5E846b20
0xA14a0111...b116b4311
0.147801485648224398 Eth
Nonce: 45
0.146156948803535448 Eth
Nonce: 46
0.00164453684468895

Execution Trace

TransparentUpgradeableProxyImmutable.3efcfda4( )
  • BancorNetwork.cancelWithdrawal( id=2645 ) => ( 70303793532822198265141 )
    • TransparentUpgradeableProxyImmutable.be476d8a( )
      • PendingWithdrawals.cancelWithdrawal( provider=0xA14a0111eAecFEde37c2048213291c8b116b4311, id=2645 ) => ( 70303793532822198265141 )
        • PoolToken.transfer( to=0xA14a0111eAecFEde37c2048213291c8b116b4311, amount=70303793532822198265141 ) => ( True )
          cancelWithdrawal[BancorNetwork (ln:2609)]
          File 1 of 5: TransparentUpgradeableProxyImmutable
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
           * proxy whose upgrades are fully controlled by the current implementation.
           */
          interface IERC1822Proxiable {
              /**
               * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
               * address.
               *
               * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
               * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
               * function revert if invoked through a proxy.
               */
              function proxiableUUID() external view returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)
          pragma solidity ^0.8.0;
          import "../Proxy.sol";
          import "./ERC1967Upgrade.sol";
          /**
           * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
           * implementation address that can be changed. This address is stored in storage in the location specified by
           * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
           * implementation behind the proxy.
           */
          contract ERC1967Proxy is Proxy, ERC1967Upgrade {
              /**
               * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
               *
               * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
               * function call, and allows initializating the storage of the proxy like a Solidity constructor.
               */
              constructor(address _logic, bytes memory _data) payable {
                  assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                  _upgradeToAndCall(_logic, _data, false);
              }
              /**
               * @dev Returns the current implementation address.
               */
              function _implementation() internal view virtual override returns (address impl) {
                  return ERC1967Upgrade._getImplementation();
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (proxy/ERC1967/ERC1967Upgrade.sol)
          pragma solidity ^0.8.2;
          import "../beacon/IBeacon.sol";
          import "../../interfaces/draft-IERC1822.sol";
          import "../../utils/Address.sol";
          import "../../utils/StorageSlot.sol";
          /**
           * @dev This abstract contract provides getters and event emitting update functions for
           * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
           *
           * _Available since v4.1._
           *
           * @custom:oz-upgrades-unsafe-allow delegatecall
           */
          abstract contract ERC1967Upgrade {
              // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
              bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
              /**
               * @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 Emitted when the implementation is upgraded.
               */
              event Upgraded(address indexed implementation);
              /**
               * @dev Returns the current implementation address.
               */
              function _getImplementation() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
              }
              /**
               * @dev Stores a new address in the EIP1967 implementation slot.
               */
              function _setImplementation(address newImplementation) private {
                  require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                  StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
              }
              /**
               * @dev Perform implementation upgrade
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeTo(address newImplementation) internal {
                  _setImplementation(newImplementation);
                  emit Upgraded(newImplementation);
              }
              /**
               * @dev Perform implementation upgrade with additional setup call.
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeToAndCall(
                  address newImplementation,
                  bytes memory data,
                  bool forceCall
              ) internal {
                  _upgradeTo(newImplementation);
                  if (data.length > 0 || forceCall) {
                      Address.functionDelegateCall(newImplementation, data);
                  }
              }
              /**
               * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeToAndCallUUPS(
                  address newImplementation,
                  bytes memory data,
                  bool forceCall
              ) internal {
                  // Upgrades from old implementations will perform a rollback test. This test requires the new
                  // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
                  // this special case will break upgrade paths from old UUPS implementation to new ones.
                  if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
                      _setImplementation(newImplementation);
                  } else {
                      try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                          require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
                      } catch {
                          revert("ERC1967Upgrade: new implementation is not UUPS");
                      }
                      _upgradeToAndCall(newImplementation, data, forceCall);
                  }
              }
              /**
               * @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 Emitted when the admin account has changed.
               */
              event AdminChanged(address previousAdmin, address newAdmin);
              /**
               * @dev Returns the current admin.
               */
              function _getAdmin() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
              }
              /**
               * @dev Stores a new address in the EIP1967 admin slot.
               */
              function _setAdmin(address newAdmin) private {
                  require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                  StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
              }
              /**
               * @dev Changes the admin of the proxy.
               *
               * Emits an {AdminChanged} event.
               */
              function _changeAdmin(address newAdmin) internal {
                  emit AdminChanged(_getAdmin(), newAdmin);
                  _setAdmin(newAdmin);
              }
              /**
               * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
               * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
               */
              bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
              /**
               * @dev Emitted when the beacon is upgraded.
               */
              event BeaconUpgraded(address indexed beacon);
              /**
               * @dev Returns the current beacon.
               */
              function _getBeacon() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
              }
              /**
               * @dev Stores a new beacon in the EIP1967 beacon slot.
               */
              function _setBeacon(address newBeacon) private {
                  require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
                  require(
                      Address.isContract(IBeacon(newBeacon).implementation()),
                      "ERC1967: beacon implementation is not a contract"
                  );
                  StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
              }
              /**
               * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
               * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
               *
               * Emits a {BeaconUpgraded} event.
               */
              function _upgradeBeaconToAndCall(
                  address newBeacon,
                  bytes memory data,
                  bool forceCall
              ) internal {
                  _setBeacon(newBeacon);
                  emit BeaconUpgraded(newBeacon);
                  if (data.length > 0 || forceCall) {
                      Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (proxy/Proxy.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
           * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
           * be specified by overriding the virtual {_implementation} function.
           *
           * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
           * different contract through the {_delegate} function.
           *
           * The success and return data of the delegated call will be returned back to the caller of the proxy.
           */
          abstract contract Proxy {
              /**
               * @dev Delegates the current call to `implementation`.
               *
               * This function does not return to its internal call site, it will return directly to the external caller.
               */
              function _delegate(address implementation) internal virtual {
                  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 This is a virtual function that should be overriden so it returns the address to which the fallback function
               * and {_fallback} should delegate.
               */
              function _implementation() internal view virtual returns (address);
              /**
               * @dev Delegates the current call to the address returned by `_implementation()`.
               *
               * This function does not return to its internall call site, it will return directly to the external caller.
               */
              function _fallback() internal virtual {
                  _beforeFallback();
                  _delegate(_implementation());
              }
              /**
               * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
               * function in the contract matches the call data.
               */
              fallback() external payable virtual {
                  _fallback();
              }
              /**
               * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
               * is empty.
               */
              receive() external payable virtual {
                  _fallback();
              }
              /**
               * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
               * call, or as part of the Solidity `fallback` or `receive` functions.
               *
               * If overriden should call `super._beforeFallback()`.
               */
              function _beforeFallback() internal virtual {}
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev This is the interface that {BeaconProxy} expects of its beacon.
           */
          interface IBeacon {
              /**
               * @dev Must return an address that can be used as a delegate call target.
               *
               * {BeaconProxy} will check that this address is a contract.
               */
              function implementation() external view returns (address);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @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
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 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");
                  (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");
                  (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");
                  (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");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal 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
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Library for reading and writing primitive types to specific storage slots.
           *
           * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
           * This library helps with reading and writing to such slots without the need for inline assembly.
           *
           * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
           *
           * Example usage to set ERC1967 implementation slot:
           * ```
           * contract ERC1967 {
           *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
           *
           *     function _getImplementation() internal view returns (address) {
           *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
           *     }
           *
           *     function _setImplementation(address newImplementation) internal {
           *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
           *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
           *     }
           * }
           * ```
           *
           * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
           */
          library StorageSlot {
              struct AddressSlot {
                  address value;
              }
              struct BooleanSlot {
                  bool value;
              }
              struct Bytes32Slot {
                  bytes32 value;
              }
              struct Uint256Slot {
                  uint256 value;
              }
              /**
               * @dev Returns an `AddressSlot` with member `value` located at `slot`.
               */
              function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
               */
              function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
               */
              function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
               */
              function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          uint32 constant PPM_RESOLUTION = 1000000;
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
          import { AccessDenied, Utils } from "./Utils.sol";
          /**
           * @dev this contract is a slightly optimized version of the original TransparentUpgradeableProxy solely designed to
           * work with the ProxyAdmin contract:
           *
           * - the address of the admin is stored as an immutable state variables and as the result:
           * - the address of the admin can't be change, so the changeAdmin() function was subsequently removed
           */
          contract TransparentUpgradeableProxyImmutable is ERC1967Proxy, Utils {
              address internal immutable _admin;
              /**
               * @dev initializes an upgradeable proxy managed by `initAdmin`, backed by the implementation at `logic`, and
               * optionally initialized with `data` as explained in {ERC1967Proxy-constructor}
               */
              constructor(
                  address logic,
                  address initAdmin,
                  bytes memory data
              ) payable ERC1967Proxy(logic, data) validAddress(initAdmin) {
                  _admin = initAdmin;
                  // still store it to work with EIP-1967
                  _changeAdmin(initAdmin);
              }
              modifier ifAdmin() {
                  if (msg.sender == _admin) {
                      _;
                  } else {
                      _fallback();
                  }
              }
              /**
               * @dev returns the current admin
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function admin() external ifAdmin returns (address) {
                  return _admin;
              }
              /**
               * @dev returns the current implementation.
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function implementation() external ifAdmin returns (address) {
                  return _implementation();
              }
              /**
               * @dev upgrades the implementation of the proxy
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function upgradeTo(address newImplementation) external ifAdmin {
                  _upgradeToAndCall(newImplementation, bytes(""), false);
              }
              /**
               * @dev upgrade the implementation of the proxy, and then call a function from the new implementation as specified
               * by data, which should be an encoded function call. This is useful to initialize new storage variables in the
               * proxied contract
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                  _upgradeToAndCall(newImplementation, data, true);
              }
              /**
               * @dev makes sure the admin cannot access the fallback function
               */
              function _beforeFallback() internal virtual override {
                  if (msg.sender == _admin) {
                      revert AccessDenied();
                  }
                  super._beforeFallback();
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { PPM_RESOLUTION } from "./Constants.sol";
          error AccessDenied();
          error AlreadyExists();
          error DoesNotExist();
          error InvalidAddress();
          error InvalidExternalAddress();
          error InvalidFee();
          error InvalidPool();
          error InvalidPoolCollection();
          error InvalidStakedBalance();
          error InvalidToken();
          error InvalidType();
          error InvalidParam();
          error NotEmpty();
          error NotPayable();
          error ZeroValue();
          /**
           * @dev common utilities
           */
          contract Utils {
              // allows execution by the caller only
              modifier only(address caller) {
                  _only(caller);
                  _;
              }
              function _only(address caller) internal view {
                  if (msg.sender != caller) {
                      revert AccessDenied();
                  }
              }
              // verifies that a value is greater than zero
              modifier greaterThanZero(uint256 value) {
                  _greaterThanZero(value);
                  _;
              }
              // error message binary size optimization
              function _greaterThanZero(uint256 value) internal pure {
                  if (value == 0) {
                      revert ZeroValue();
                  }
              }
              // validates an address - currently only checks that it isn't null
              modifier validAddress(address addr) {
                  _validAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validAddress(address addr) internal pure {
                  if (addr == address(0)) {
                      revert InvalidAddress();
                  }
              }
              // validates an external address - currently only checks that it isn't null or this
              modifier validExternalAddress(address addr) {
                  _validExternalAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validExternalAddress(address addr) internal view {
                  if (addr == address(0) || addr == address(this)) {
                      revert InvalidExternalAddress();
                  }
              }
              // ensures that the fee is valid
              modifier validFee(uint32 fee) {
                  _validFee(fee);
                  _;
              }
              // error message binary size optimization
              function _validFee(uint32 fee) internal pure {
                  if (fee > PPM_RESOLUTION) {
                      revert InvalidFee();
                  }
              }
          }
          

          File 2 of 5: TransparentUpgradeableProxyImmutable
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
           * proxy whose upgrades are fully controlled by the current implementation.
           */
          interface IERC1822Proxiable {
              /**
               * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
               * address.
               *
               * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
               * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
               * function revert if invoked through a proxy.
               */
              function proxiableUUID() external view returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)
          pragma solidity ^0.8.0;
          import "../Proxy.sol";
          import "./ERC1967Upgrade.sol";
          /**
           * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
           * implementation address that can be changed. This address is stored in storage in the location specified by
           * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
           * implementation behind the proxy.
           */
          contract ERC1967Proxy is Proxy, ERC1967Upgrade {
              /**
               * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
               *
               * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
               * function call, and allows initializating the storage of the proxy like a Solidity constructor.
               */
              constructor(address _logic, bytes memory _data) payable {
                  assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                  _upgradeToAndCall(_logic, _data, false);
              }
              /**
               * @dev Returns the current implementation address.
               */
              function _implementation() internal view virtual override returns (address impl) {
                  return ERC1967Upgrade._getImplementation();
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (proxy/ERC1967/ERC1967Upgrade.sol)
          pragma solidity ^0.8.2;
          import "../beacon/IBeacon.sol";
          import "../../interfaces/draft-IERC1822.sol";
          import "../../utils/Address.sol";
          import "../../utils/StorageSlot.sol";
          /**
           * @dev This abstract contract provides getters and event emitting update functions for
           * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
           *
           * _Available since v4.1._
           *
           * @custom:oz-upgrades-unsafe-allow delegatecall
           */
          abstract contract ERC1967Upgrade {
              // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
              bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
              /**
               * @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 Emitted when the implementation is upgraded.
               */
              event Upgraded(address indexed implementation);
              /**
               * @dev Returns the current implementation address.
               */
              function _getImplementation() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
              }
              /**
               * @dev Stores a new address in the EIP1967 implementation slot.
               */
              function _setImplementation(address newImplementation) private {
                  require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                  StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
              }
              /**
               * @dev Perform implementation upgrade
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeTo(address newImplementation) internal {
                  _setImplementation(newImplementation);
                  emit Upgraded(newImplementation);
              }
              /**
               * @dev Perform implementation upgrade with additional setup call.
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeToAndCall(
                  address newImplementation,
                  bytes memory data,
                  bool forceCall
              ) internal {
                  _upgradeTo(newImplementation);
                  if (data.length > 0 || forceCall) {
                      Address.functionDelegateCall(newImplementation, data);
                  }
              }
              /**
               * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeToAndCallUUPS(
                  address newImplementation,
                  bytes memory data,
                  bool forceCall
              ) internal {
                  // Upgrades from old implementations will perform a rollback test. This test requires the new
                  // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
                  // this special case will break upgrade paths from old UUPS implementation to new ones.
                  if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
                      _setImplementation(newImplementation);
                  } else {
                      try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                          require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
                      } catch {
                          revert("ERC1967Upgrade: new implementation is not UUPS");
                      }
                      _upgradeToAndCall(newImplementation, data, forceCall);
                  }
              }
              /**
               * @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 Emitted when the admin account has changed.
               */
              event AdminChanged(address previousAdmin, address newAdmin);
              /**
               * @dev Returns the current admin.
               */
              function _getAdmin() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
              }
              /**
               * @dev Stores a new address in the EIP1967 admin slot.
               */
              function _setAdmin(address newAdmin) private {
                  require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                  StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
              }
              /**
               * @dev Changes the admin of the proxy.
               *
               * Emits an {AdminChanged} event.
               */
              function _changeAdmin(address newAdmin) internal {
                  emit AdminChanged(_getAdmin(), newAdmin);
                  _setAdmin(newAdmin);
              }
              /**
               * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
               * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
               */
              bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
              /**
               * @dev Emitted when the beacon is upgraded.
               */
              event BeaconUpgraded(address indexed beacon);
              /**
               * @dev Returns the current beacon.
               */
              function _getBeacon() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
              }
              /**
               * @dev Stores a new beacon in the EIP1967 beacon slot.
               */
              function _setBeacon(address newBeacon) private {
                  require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
                  require(
                      Address.isContract(IBeacon(newBeacon).implementation()),
                      "ERC1967: beacon implementation is not a contract"
                  );
                  StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
              }
              /**
               * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
               * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
               *
               * Emits a {BeaconUpgraded} event.
               */
              function _upgradeBeaconToAndCall(
                  address newBeacon,
                  bytes memory data,
                  bool forceCall
              ) internal {
                  _setBeacon(newBeacon);
                  emit BeaconUpgraded(newBeacon);
                  if (data.length > 0 || forceCall) {
                      Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (proxy/Proxy.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
           * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
           * be specified by overriding the virtual {_implementation} function.
           *
           * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
           * different contract through the {_delegate} function.
           *
           * The success and return data of the delegated call will be returned back to the caller of the proxy.
           */
          abstract contract Proxy {
              /**
               * @dev Delegates the current call to `implementation`.
               *
               * This function does not return to its internal call site, it will return directly to the external caller.
               */
              function _delegate(address implementation) internal virtual {
                  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 This is a virtual function that should be overriden so it returns the address to which the fallback function
               * and {_fallback} should delegate.
               */
              function _implementation() internal view virtual returns (address);
              /**
               * @dev Delegates the current call to the address returned by `_implementation()`.
               *
               * This function does not return to its internall call site, it will return directly to the external caller.
               */
              function _fallback() internal virtual {
                  _beforeFallback();
                  _delegate(_implementation());
              }
              /**
               * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
               * function in the contract matches the call data.
               */
              fallback() external payable virtual {
                  _fallback();
              }
              /**
               * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
               * is empty.
               */
              receive() external payable virtual {
                  _fallback();
              }
              /**
               * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
               * call, or as part of the Solidity `fallback` or `receive` functions.
               *
               * If overriden should call `super._beforeFallback()`.
               */
              function _beforeFallback() internal virtual {}
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev This is the interface that {BeaconProxy} expects of its beacon.
           */
          interface IBeacon {
              /**
               * @dev Must return an address that can be used as a delegate call target.
               *
               * {BeaconProxy} will check that this address is a contract.
               */
              function implementation() external view returns (address);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @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
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 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");
                  (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");
                  (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");
                  (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");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal 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
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Library for reading and writing primitive types to specific storage slots.
           *
           * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
           * This library helps with reading and writing to such slots without the need for inline assembly.
           *
           * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
           *
           * Example usage to set ERC1967 implementation slot:
           * ```
           * contract ERC1967 {
           *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
           *
           *     function _getImplementation() internal view returns (address) {
           *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
           *     }
           *
           *     function _setImplementation(address newImplementation) internal {
           *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
           *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
           *     }
           * }
           * ```
           *
           * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
           */
          library StorageSlot {
              struct AddressSlot {
                  address value;
              }
              struct BooleanSlot {
                  bool value;
              }
              struct Bytes32Slot {
                  bytes32 value;
              }
              struct Uint256Slot {
                  uint256 value;
              }
              /**
               * @dev Returns an `AddressSlot` with member `value` located at `slot`.
               */
              function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
               */
              function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
               */
              function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
               */
              function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          uint32 constant PPM_RESOLUTION = 1000000;
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
          import { AccessDenied, Utils } from "./Utils.sol";
          /**
           * @dev this contract is a slightly optimized version of the original TransparentUpgradeableProxy solely designed to
           * work with the ProxyAdmin contract:
           *
           * - the address of the admin is stored as an immutable state variables and as the result:
           * - the address of the admin can't be change, so the changeAdmin() function was subsequently removed
           */
          contract TransparentUpgradeableProxyImmutable is ERC1967Proxy, Utils {
              address internal immutable _admin;
              /**
               * @dev initializes an upgradeable proxy managed by `initAdmin`, backed by the implementation at `logic`, and
               * optionally initialized with `data` as explained in {ERC1967Proxy-constructor}
               */
              constructor(
                  address logic,
                  address initAdmin,
                  bytes memory data
              ) payable ERC1967Proxy(logic, data) validAddress(initAdmin) {
                  _admin = initAdmin;
                  // still store it to work with EIP-1967
                  _changeAdmin(initAdmin);
              }
              modifier ifAdmin() {
                  if (msg.sender == _admin) {
                      _;
                  } else {
                      _fallback();
                  }
              }
              /**
               * @dev returns the current admin
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function admin() external ifAdmin returns (address) {
                  return _admin;
              }
              /**
               * @dev returns the current implementation.
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function implementation() external ifAdmin returns (address) {
                  return _implementation();
              }
              /**
               * @dev upgrades the implementation of the proxy
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function upgradeTo(address newImplementation) external ifAdmin {
                  _upgradeToAndCall(newImplementation, bytes(""), false);
              }
              /**
               * @dev upgrade the implementation of the proxy, and then call a function from the new implementation as specified
               * by data, which should be an encoded function call. This is useful to initialize new storage variables in the
               * proxied contract
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                  _upgradeToAndCall(newImplementation, data, true);
              }
              /**
               * @dev makes sure the admin cannot access the fallback function
               */
              function _beforeFallback() internal virtual override {
                  if (msg.sender == _admin) {
                      revert AccessDenied();
                  }
                  super._beforeFallback();
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { PPM_RESOLUTION } from "./Constants.sol";
          error AccessDenied();
          error AlreadyExists();
          error DoesNotExist();
          error InvalidAddress();
          error InvalidExternalAddress();
          error InvalidFee();
          error InvalidPool();
          error InvalidPoolCollection();
          error InvalidStakedBalance();
          error InvalidToken();
          error InvalidType();
          error InvalidParam();
          error NotEmpty();
          error NotPayable();
          error ZeroValue();
          /**
           * @dev common utilities
           */
          contract Utils {
              // allows execution by the caller only
              modifier only(address caller) {
                  _only(caller);
                  _;
              }
              function _only(address caller) internal view {
                  if (msg.sender != caller) {
                      revert AccessDenied();
                  }
              }
              // verifies that a value is greater than zero
              modifier greaterThanZero(uint256 value) {
                  _greaterThanZero(value);
                  _;
              }
              // error message binary size optimization
              function _greaterThanZero(uint256 value) internal pure {
                  if (value == 0) {
                      revert ZeroValue();
                  }
              }
              // validates an address - currently only checks that it isn't null
              modifier validAddress(address addr) {
                  _validAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validAddress(address addr) internal pure {
                  if (addr == address(0)) {
                      revert InvalidAddress();
                  }
              }
              // validates an external address - currently only checks that it isn't null or this
              modifier validExternalAddress(address addr) {
                  _validExternalAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validExternalAddress(address addr) internal view {
                  if (addr == address(0) || addr == address(this)) {
                      revert InvalidExternalAddress();
                  }
              }
              // ensures that the fee is valid
              modifier validFee(uint32 fee) {
                  _validFee(fee);
                  _;
              }
              // error message binary size optimization
              function _validFee(uint32 fee) internal pure {
                  if (fee > PPM_RESOLUTION) {
                      revert InvalidFee();
                  }
              }
          }
          

          File 3 of 5: PoolToken
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
          import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
          import { Token } from "../token/Token.sol";
          import { ERC20Burnable } from "../token/ERC20Burnable.sol";
          import { IVersioned } from "../utility/interfaces/IVersioned.sol";
          import { Owned } from "../utility/Owned.sol";
          import { Utils } from "../utility/Utils.sol";
          import { IPoolToken } from "./interfaces/IPoolToken.sol";
          /**
           * @dev Pool Token contract
           */
          contract PoolToken is IPoolToken, ERC20Permit, ERC20Burnable, Owned, Utils {
              Token private immutable _reserveToken;
              uint8 private _decimals;
              /**
               * @dev initializes a new PoolToken contract
               */
              constructor(
                  string memory name,
                  string memory symbol,
                  uint8 initDecimals,
                  Token initReserveToken
              ) ERC20(name, symbol) ERC20Permit(name) validAddress(address(initReserveToken)) {
                  _decimals = initDecimals;
                  _reserveToken = initReserveToken;
              }
              /**
               * @inheritdoc IVersioned
               */
              function version() external pure returns (uint16) {
                  return 1;
              }
              /**
               * @dev returns the number of decimals used to get its user representation
               */
              function decimals() public view virtual override returns (uint8) {
                  return _decimals;
              }
              /**
               * @inheritdoc IPoolToken
               */
              function reserveToken() external view returns (Token) {
                  return _reserveToken;
              }
              /**
               * @inheritdoc IPoolToken
               */
              function mint(address recipient, uint256 amount) external onlyOwner validExternalAddress(recipient) {
                  _mint(recipient, amount);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol)
          pragma solidity ^0.8.0;
          import "./IERC20.sol";
          import "./extensions/IERC20Metadata.sol";
          import "../../utils/Context.sol";
          /**
           * @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 Contracts guidelines: functions revert
           * instead 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, IERC20Metadata {
              mapping(address => uint256) private _balances;
              mapping(address => mapping(address => uint256)) private _allowances;
              uint256 private _totalSupply;
              string private _name;
              string private _symbol;
              /**
               * @dev Sets the values for {name} and {symbol}.
               *
               * The default value of {decimals} is 18. To select a different value for
               * {decimals} you should overload it.
               *
               * All two of these values are immutable: they can only be set once during
               * construction.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev Returns the name of the token.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev Returns the symbol of the token, usually a shorter version of the
               * name.
               */
              function symbol() public view virtual override 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 this function is
               * overridden;
               *
               * 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 override returns (uint8) {
                  return 18;
              }
              /**
               * @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:
               *
               * - `to` cannot be the zero address.
               * - the caller must have a balance of at least `amount`.
               */
              function transfer(address to, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _transfer(owner, to, 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}.
               *
               * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
               * `transferFrom`. This is semantically equivalent to an infinite approval.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               */
              function approve(address spender, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _approve(owner, 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}.
               *
               * NOTE: Does not update the allowance if the current allowance
               * is the maximum `uint256`.
               *
               * Requirements:
               *
               * - `from` and `to` cannot be the zero address.
               * - `from` must have a balance of at least `amount`.
               * - the caller must have allowance for ``from``'s tokens of at least
               * `amount`.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 amount
              ) public virtual override returns (bool) {
                  address spender = _msgSender();
                  _spendAllowance(from, spender, amount);
                  _transfer(from, to, amount);
                  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) {
                  address owner = _msgSender();
                  _approve(owner, spender, _allowances[owner][spender] + 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) {
                  address owner = _msgSender();
                  uint256 currentAllowance = _allowances[owner][spender];
                  require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
                  unchecked {
                      _approve(owner, spender, currentAllowance - subtractedValue);
                  }
                  return true;
              }
              /**
               * @dev Moves `amount` of tokens from `sender` to `recipient`.
               *
               * This 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:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `from` must have a balance of at least `amount`.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {
                  require(from != address(0), "ERC20: transfer from the zero address");
                  require(to != address(0), "ERC20: transfer to the zero address");
                  _beforeTokenTransfer(from, to, amount);
                  uint256 fromBalance = _balances[from];
                  require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
                  unchecked {
                      _balances[from] = fromBalance - amount;
                  }
                  _balances[to] += amount;
                  emit Transfer(from, to, amount);
                  _afterTokenTransfer(from, to, 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:
               *
               * - `account` 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 += amount;
                  _balances[account] += amount;
                  emit Transfer(address(0), account, amount);
                  _afterTokenTransfer(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);
                  uint256 accountBalance = _balances[account];
                  require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
                  unchecked {
                      _balances[account] = accountBalance - amount;
                  }
                  _totalSupply -= amount;
                  emit Transfer(account, address(0), amount);
                  _afterTokenTransfer(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 Spend `amount` form the allowance of `owner` toward `spender`.
               *
               * Does not update the allowance amount in case of infinite allowance.
               * Revert if not enough allowance is available.
               *
               * Might emit an {Approval} event.
               */
              function _spendAllowance(
                  address owner,
                  address spender,
                  uint256 amount
              ) internal virtual {
                  uint256 currentAllowance = allowance(owner, spender);
                  if (currentAllowance != type(uint256).max) {
                      require(currentAllowance >= amount, "ERC20: insufficient allowance");
                      unchecked {
                          _approve(owner, spender, currentAllowance - amount);
                      }
                  }
              }
              /**
               * @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 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 Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * has been transferred to `to`.
               * - when `from` is zero, `amount` tokens have been minted for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens have been 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 _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {}
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-ERC20Permit.sol)
          pragma solidity ^0.8.0;
          import "./draft-IERC20Permit.sol";
          import "../ERC20.sol";
          import "../../../utils/cryptography/draft-EIP712.sol";
          import "../../../utils/cryptography/ECDSA.sol";
          import "../../../utils/Counters.sol";
          /**
           * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
           * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
           *
           * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
           * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
           * need to send a transaction, and thus is not required to hold Ether at all.
           *
           * _Available since v3.4._
           */
          abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
              using Counters for Counters.Counter;
              mapping(address => Counters.Counter) private _nonces;
              // solhint-disable-next-line var-name-mixedcase
              bytes32 private immutable _PERMIT_TYPEHASH =
                  keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
              /**
               * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
               *
               * It's a good idea to use the same `name` that is defined as the ERC20 token name.
               */
              constructor(string memory name) EIP712(name, "1") {}
              /**
               * @dev See {IERC20Permit-permit}.
               */
              function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) public virtual override {
                  require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
                  bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
                  bytes32 hash = _hashTypedDataV4(structHash);
                  address signer = ECDSA.recover(hash, v, r, s);
                  require(signer == owner, "ERC20Permit: invalid signature");
                  _approve(owner, spender, value);
              }
              /**
               * @dev See {IERC20Permit-nonces}.
               */
              function nonces(address owner) public view virtual override returns (uint256) {
                  return _nonces[owner].current();
              }
              /**
               * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
               */
              // solhint-disable-next-line func-name-mixedcase
              function DOMAIN_SEPARATOR() external view override returns (bytes32) {
                  return _domainSeparatorV4();
              }
              /**
               * @dev "Consume a nonce": return the current value and increment.
               *
               * _Available since v4.1._
               */
              function _useNonce(address owner) internal virtual returns (uint256 current) {
                  Counters.Counter storage nonce = _nonces[owner];
                  current = nonce.current();
                  nonce.increment();
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev the main purpose of the Token interfaces is to ensure artificially that we won't use ERC20's standard functions,
           * but only their safe versions, which are provided by SafeERC20 and SafeERC20Ex via the TokenLibrary contract
           */
          interface Token {
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
          import { IERC20Burnable } from "./interfaces/IERC20Burnable.sol";
          /**
           * @dev this is an adapted clone of the OZ's ERC20Burnable extension which is unfortunately required so that it can be
           * explicitly specified in interfaces via our new IERC20Burnable interface.
           *
           * We have also removed the explicit use of Context and updated the code to our style.
           */
          abstract contract ERC20Burnable is ERC20, IERC20Burnable {
              /**
               * @inheritdoc IERC20Burnable
               */
              function burn(uint256 amount) external virtual {
                  _burn(msg.sender, amount);
              }
              /**
               * @inheritdoc IERC20Burnable
               */
              function burnFrom(address recipient, uint256 amount) external virtual {
                  _approve(recipient, msg.sender, allowance(recipient, msg.sender) - amount);
                  _burn(recipient, amount);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev an interface for a versioned contract
           */
          interface IVersioned {
              function version() external view returns (uint16);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IOwned } from "./interfaces/IOwned.sol";
          import { AccessDenied } from "./Utils.sol";
          /**
           * @dev this contract provides support and utilities for contract ownership
           */
          abstract contract Owned is IOwned {
              error SameOwner();
              address private _owner;
              address private _newOwner;
              /**
               * @dev triggered when the owner is updated
               */
              event OwnerUpdate(address indexed prevOwner, address indexed newOwner);
              // solhint-disable func-name-mixedcase
              /**
               * @dev initializes the contract
               */
              constructor() {
                  _setOwnership(msg.sender);
              }
              // solhint-enable func-name-mixedcase
              // allows execution by the owner only
              modifier onlyOwner() {
                  _onlyOwner();
                  _;
              }
              // error message binary size optimization
              function _onlyOwner() private view {
                  if (msg.sender != _owner) {
                      revert AccessDenied();
                  }
              }
              /**
               * @inheritdoc IOwned
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @inheritdoc IOwned
               */
              function transferOwnership(address ownerCandidate) public virtual onlyOwner {
                  if (ownerCandidate == _owner) {
                      revert SameOwner();
                  }
                  _newOwner = ownerCandidate;
              }
              /**
               * @inheritdoc IOwned
               */
              function acceptOwnership() public virtual {
                  if (msg.sender != _newOwner) {
                      revert AccessDenied();
                  }
                  _setOwnership(_newOwner);
              }
              /**
               * @dev returns the address of the new owner candidate
               */
              function newOwner() external view returns (address) {
                  return _newOwner;
              }
              /**
               * @dev sets the new owner internally
               */
              function _setOwnership(address ownerCandidate) private {
                  address prevOwner = _owner;
                  _owner = ownerCandidate;
                  _newOwner = address(0);
                  emit OwnerUpdate({ prevOwner: prevOwner, newOwner: ownerCandidate });
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { PPM_RESOLUTION } from "./Constants.sol";
          error AccessDenied();
          error AlreadyExists();
          error DoesNotExist();
          error InvalidAddress();
          error InvalidExternalAddress();
          error InvalidFee();
          error InvalidPool();
          error InvalidPoolCollection();
          error InvalidStakedBalance();
          error InvalidToken();
          error InvalidParam();
          error NotEmpty();
          error NotPayable();
          error ZeroValue();
          /**
           * @dev common utilities
           */
          abstract contract Utils {
              // allows execution by the caller only
              modifier only(address caller) {
                  _only(caller);
                  _;
              }
              function _only(address caller) internal view {
                  if (msg.sender != caller) {
                      revert AccessDenied();
                  }
              }
              // verifies that a value is greater than zero
              modifier greaterThanZero(uint256 value) {
                  _greaterThanZero(value);
                  _;
              }
              // error message binary size optimization
              function _greaterThanZero(uint256 value) internal pure {
                  if (value == 0) {
                      revert ZeroValue();
                  }
              }
              // validates an address - currently only checks that it isn't null
              modifier validAddress(address addr) {
                  _validAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validAddress(address addr) internal pure {
                  if (addr == address(0)) {
                      revert InvalidAddress();
                  }
              }
              // validates an external address - currently only checks that it isn't null or this
              modifier validExternalAddress(address addr) {
                  _validExternalAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validExternalAddress(address addr) internal view {
                  if (addr == address(0) || addr == address(this)) {
                      revert InvalidExternalAddress();
                  }
              }
              // ensures that the fee is valid
              modifier validFee(uint32 fee) {
                  _validFee(fee);
                  _;
              }
              // error message binary size optimization
              function _validFee(uint32 fee) internal pure {
                  if (fee > PPM_RESOLUTION) {
                      revert InvalidFee();
                  }
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
          import { IERC20Burnable } from "../../token/interfaces/IERC20Burnable.sol";
          import { Token } from "../../token/Token.sol";
          import { IVersioned } from "../../utility/interfaces/IVersioned.sol";
          import { IOwned } from "../../utility/interfaces/IOwned.sol";
          /**
           * @dev Pool Token interface
           */
          interface IPoolToken is IVersioned, IOwned, IERC20, IERC20Permit, IERC20Burnable {
              /**
               * @dev returns the address of the reserve token
               */
              function reserveToken() external view returns (Token);
              /**
               * @dev increases the token supply and sends the new tokens to the given account
               *
               * requirements:
               *
               * - the caller must be the owner of the contract
               */
              function mint(address recipient, uint256 amount) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @dev Returns the amount of tokens in existence.
               */
              function totalSupply() external view returns (uint256);
              /**
               * @dev Returns the amount of tokens owned by `account`.
               */
              function balanceOf(address account) external view returns (uint256);
              /**
               * @dev Moves `amount` tokens from the caller's account to `to`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address to, 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 `from` to `to` 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 from,
                  address to,
                  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
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          /**
           * @dev Interface for the optional metadata functions from the ERC20 standard.
           *
           * _Available since v4.1._
           */
          interface IERC20Metadata is IERC20 {
              /**
               * @dev Returns the name of the token.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the symbol of the token.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the decimals places of the token.
               */
              function decimals() external view returns (uint8);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with 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) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
           * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
           *
           * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
           * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
           * need to send a transaction, and thus is not required to hold Ether at all.
           */
          interface IERC20Permit {
              /**
               * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
               * given ``owner``'s signed approval.
               *
               * IMPORTANT: The same issues {IERC20-approve} has related to transaction
               * ordering also apply here.
               *
               * Emits an {Approval} event.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               * - `deadline` must be a timestamp in the future.
               * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
               * over the EIP712-formatted function arguments.
               * - the signature must use ``owner``'s current nonce (see {nonces}).
               *
               * For more information on the signature format, see the
               * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
               * section].
               */
              function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external;
              /**
               * @dev Returns the current nonce for `owner`. This value must be
               * included whenever a signature is generated for {permit}.
               *
               * Every successful call to {permit} increases ``owner``'s nonce by one. This
               * prevents a signature from being used multiple times.
               */
              function nonces(address owner) external view returns (uint256);
              /**
               * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
               */
              // solhint-disable-next-line func-name-mixedcase
              function DOMAIN_SEPARATOR() external view returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol)
          pragma solidity ^0.8.0;
          import "./ECDSA.sol";
          /**
           * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
           *
           * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
           * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
           * they need in their contracts using a combination of `abi.encode` and `keccak256`.
           *
           * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
           * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
           * ({_hashTypedDataV4}).
           *
           * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
           * the chain id to protect against replay attacks on an eventual fork of the chain.
           *
           * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
           * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
           *
           * _Available since v3.4._
           */
          abstract contract EIP712 {
              /* solhint-disable var-name-mixedcase */
              // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
              // invalidate the cached domain separator if the chain id changes.
              bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
              uint256 private immutable _CACHED_CHAIN_ID;
              address private immutable _CACHED_THIS;
              bytes32 private immutable _HASHED_NAME;
              bytes32 private immutable _HASHED_VERSION;
              bytes32 private immutable _TYPE_HASH;
              /* solhint-enable var-name-mixedcase */
              /**
               * @dev Initializes the domain separator and parameter caches.
               *
               * The meaning of `name` and `version` is specified in
               * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
               *
               * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
               * - `version`: the current major version of the signing domain.
               *
               * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
               * contract upgrade].
               */
              constructor(string memory name, string memory version) {
                  bytes32 hashedName = keccak256(bytes(name));
                  bytes32 hashedVersion = keccak256(bytes(version));
                  bytes32 typeHash = keccak256(
                      "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                  );
                  _HASHED_NAME = hashedName;
                  _HASHED_VERSION = hashedVersion;
                  _CACHED_CHAIN_ID = block.chainid;
                  _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
                  _CACHED_THIS = address(this);
                  _TYPE_HASH = typeHash;
              }
              /**
               * @dev Returns the domain separator for the current chain.
               */
              function _domainSeparatorV4() internal view returns (bytes32) {
                  if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
                      return _CACHED_DOMAIN_SEPARATOR;
                  } else {
                      return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
                  }
              }
              function _buildDomainSeparator(
                  bytes32 typeHash,
                  bytes32 nameHash,
                  bytes32 versionHash
              ) private view returns (bytes32) {
                  return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
              }
              /**
               * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
               * function returns the hash of the fully encoded EIP712 message for this domain.
               *
               * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
               *
               * ```solidity
               * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
               *     keccak256("Mail(address to,string contents)"),
               *     mailTo,
               *     keccak256(bytes(mailContents))
               * )));
               * address signer = ECDSA.recover(digest, signature);
               * ```
               */
              function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
                  return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)
          pragma solidity ^0.8.0;
          import "../Strings.sol";
          /**
           * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
           *
           * These functions can be used to verify that a message was signed by the holder
           * of the private keys of a given address.
           */
          library ECDSA {
              enum RecoverError {
                  NoError,
                  InvalidSignature,
                  InvalidSignatureLength,
                  InvalidSignatureS,
                  InvalidSignatureV
              }
              function _throwError(RecoverError error) private pure {
                  if (error == RecoverError.NoError) {
                      return; // no error: do nothing
                  } else if (error == RecoverError.InvalidSignature) {
                      revert("ECDSA: invalid signature");
                  } else if (error == RecoverError.InvalidSignatureLength) {
                      revert("ECDSA: invalid signature length");
                  } else if (error == RecoverError.InvalidSignatureS) {
                      revert("ECDSA: invalid signature 's' value");
                  } else if (error == RecoverError.InvalidSignatureV) {
                      revert("ECDSA: invalid signature 'v' value");
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature` or error string. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               *
               * Documentation for signature generation:
               * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
               * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
               *
               * _Available since v4.3._
               */
              function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
                  // Check the signature length
                  // - case 65: r,s,v signature (standard)
                  // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
                  if (signature.length == 65) {
                      bytes32 r;
                      bytes32 s;
                      uint8 v;
                      // ecrecover takes the signature parameters, and the only way to get them
                      // currently is to use assembly.
                      assembly {
                          r := mload(add(signature, 0x20))
                          s := mload(add(signature, 0x40))
                          v := byte(0, mload(add(signature, 0x60)))
                      }
                      return tryRecover(hash, v, r, s);
                  } else if (signature.length == 64) {
                      bytes32 r;
                      bytes32 vs;
                      // ecrecover takes the signature parameters, and the only way to get them
                      // currently is to use assembly.
                      assembly {
                          r := mload(add(signature, 0x20))
                          vs := mload(add(signature, 0x40))
                      }
                      return tryRecover(hash, r, vs);
                  } else {
                      return (address(0), RecoverError.InvalidSignatureLength);
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature`. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               */
              function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, signature);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
               *
               * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address, RecoverError) {
                  bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                  uint8 v = uint8((uint256(vs) >> 255) + 27);
                  return tryRecover(hash, v, r, s);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
               *
               * _Available since v4.2._
               */
              function recover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, r, vs);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
               * `r` and `s` signature fields separately.
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address, RecoverError) {
                  // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                  // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                  // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                  // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                  //
                  // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                  // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                  // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                  // these malleable signatures as well.
                  if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                      return (address(0), RecoverError.InvalidSignatureS);
                  }
                  if (v != 27 && v != 28) {
                      return (address(0), RecoverError.InvalidSignatureV);
                  }
                  // If the signature is valid (and not malleable), return the signer address
                  address signer = ecrecover(hash, v, r, s);
                  if (signer == address(0)) {
                      return (address(0), RecoverError.InvalidSignature);
                  }
                  return (signer, RecoverError.NoError);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `v`,
               * `r` and `s` signature fields separately.
               */
              function recover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from a `hash`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                  // 32 is the length in bytes of hash,
                  // enforced by the type signature above
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          32", hash));
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from `s`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          ", Strings.toString(s.length), s));
              }
              /**
               * @dev Returns an Ethereum Signed Typed Data, created from a
               * `domainSeparator` and a `structHash`. This produces hash corresponding
               * to the one signed with the
               * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
               * JSON-RPC method as part of EIP-712.
               *
               * See {recover}.
               */
              function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
          pragma solidity ^0.8.0;
          /**
           * @title Counters
           * @author Matt Condon (@shrugs)
           * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
           * of elements in a mapping, issuing ERC721 ids, or counting request ids.
           *
           * Include with `using Counters for Counters.Counter;`
           */
          library Counters {
              struct Counter {
                  // This variable should never be directly accessed by users of the library: interactions must be restricted to
                  // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
                  // this feature: see https://github.com/ethereum/solidity/issues/4637
                  uint256 _value; // default: 0
              }
              function current(Counter storage counter) internal view returns (uint256) {
                  return counter._value;
              }
              function increment(Counter storage counter) internal {
                  unchecked {
                      counter._value += 1;
                  }
              }
              function decrement(Counter storage counter) internal {
                  uint256 value = counter._value;
                  require(value > 0, "Counter: decrement overflow");
                  unchecked {
                      counter._value = value - 1;
                  }
              }
              function reset(Counter storage counter) internal {
                  counter._value = 0;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  if (value == 0) {
                      return "0x00";
                  }
                  uint256 temp = value;
                  uint256 length = 0;
                  while (temp != 0) {
                      length++;
                      temp >>= 8;
                  }
                  return toHexString(value, length);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev burnable ERC20 interface
           */
          interface IERC20Burnable {
              /**
               * @dev Destroys tokens from the caller.
               */
              function burn(uint256 amount) external;
              /**
               * @dev Destroys tokens from a recipient, deducting from the caller's allowance
               *
               * requirements:
               *
               * - the caller must have allowance for recipient's tokens of at least the specified amount
               */
              function burnFrom(address recipient, uint256 amount) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev Owned interface
           */
          interface IOwned {
              /**
               * @dev returns the address of the current owner
               */
              function owner() external view returns (address);
              /**
               * @dev allows transferring the contract ownership
               *
               * requirements:
               *
               * - the caller must be the owner of the contract
               * - the new owner still needs to accept the transfer
               */
              function transferOwnership(address ownerCandidate) external;
              /**
               * @dev used by a new owner to accept an ownership transfer
               */
              function acceptOwnership() external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          uint32 constant PPM_RESOLUTION = 1_000_000;
          

          File 4 of 5: BancorNetwork
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.6.12;
          /// @title Claimable contract interface
          interface IClaimable {
              function owner() external view returns (address);
              function transferOwnership(address newOwner) external;
              function acceptOwnership() external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.6.12;
          import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import "./IClaimable.sol";
          /// @title Mintable Token interface
          interface IMintableToken is IERC20, IClaimable {
              function issue(address to, uint256 amount) external;
              function destroy(address from, uint256 amount) external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.6.12;
          import "./IMintableToken.sol";
          /// @title The interface for mintable/burnable token governance.
          interface ITokenGovernance {
              // The address of the mintable ERC20 token.
              function token() external view returns (IMintableToken);
              /// @dev Mints new tokens.
              ///
              /// @param to Account to receive the new amount.
              /// @param amount Amount to increase the supply by.
              ///
              function mint(address to, uint256 amount) external;
              /// @dev Burns tokens from the caller.
              ///
              /// @param amount Amount to decrease the supply by.
              ///
              function burn(uint256 amount) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlEnumerableUpgradeable.sol";
          import "./AccessControlUpgradeable.sol";
          import "../utils/structs/EnumerableSetUpgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Extension of {AccessControl} that allows enumerating the members of each role.
           */
          abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerableUpgradeable, AccessControlUpgradeable {
              function __AccessControlEnumerable_init() internal onlyInitializing {
              }
              function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
              }
              using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
              mapping(bytes32 => EnumerableSetUpgradeable.AddressSet) private _roleMembers;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlEnumerableUpgradeable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
                  return _roleMembers[role].at(index);
              }
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
                  return _roleMembers[role].length();
              }
              /**
               * @dev Overload {_grantRole} to track enumerable memberships
               */
              function _grantRole(bytes32 role, address account) internal virtual override {
                  super._grantRole(role, account);
                  _roleMembers[role].add(account);
              }
              /**
               * @dev Overload {_revokeRole} to track enumerable memberships
               */
              function _revokeRole(bytes32 role, address account) internal virtual override {
                  super._revokeRole(role, account);
                  _roleMembers[role].remove(account);
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlUpgradeable.sol";
          import "../utils/ContextUpgradeable.sol";
          import "../utils/StringsUpgradeable.sol";
          import "../utils/introspection/ERC165Upgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module that allows children to implement role-based access
           * control mechanisms. This is a lightweight version that doesn't allow enumerating role
           * members except through off-chain means by accessing the contract event logs. Some
           * applications may benefit from on-chain enumerability, for those cases see
           * {AccessControlEnumerable}.
           *
           * Roles are referred to by their `bytes32` identifier. These should be exposed
           * in the external API and be unique. The best way to achieve this is by
           * using `public constant` hash digests:
           *
           * ```
           * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
           * ```
           *
           * Roles can be used to represent a set of permissions. To restrict access to a
           * function call, use {hasRole}:
           *
           * ```
           * function foo() public {
           *     require(hasRole(MY_ROLE, msg.sender));
           *     ...
           * }
           * ```
           *
           * Roles can be granted and revoked dynamically via the {grantRole} and
           * {revokeRole} functions. Each role has an associated admin role, and only
           * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
           *
           * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
           * that only accounts with this role will be able to grant or revoke other
           * roles. More complex role relationships can be created by using
           * {_setRoleAdmin}.
           *
           * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
           * grant and revoke this role. Extra precautions should be taken to secure
           * accounts that have been granted it.
           */
          abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
              function __AccessControl_init() internal onlyInitializing {
              }
              function __AccessControl_init_unchained() internal onlyInitializing {
              }
              struct RoleData {
                  mapping(address => bool) members;
                  bytes32 adminRole;
              }
              mapping(bytes32 => RoleData) private _roles;
              bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
              /**
               * @dev Modifier that checks that an account has a specific role. Reverts
               * with a standardized message including the required role.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               *
               * _Available since v4.1._
               */
              modifier onlyRole(bytes32 role) {
                  _checkRole(role, _msgSender());
                  _;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                  return _roles[role].members[account];
              }
              /**
               * @dev Revert with a standard message if `account` is missing `role`.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               */
              function _checkRole(bytes32 role, address account) internal view virtual {
                  if (!hasRole(role, account)) {
                      revert(
                          string(
                              abi.encodePacked(
                                  "AccessControl: account ",
                                  StringsUpgradeable.toHexString(uint160(account), 20),
                                  " is missing role ",
                                  StringsUpgradeable.toHexString(uint256(role), 32)
                              )
                          )
                      );
                  }
              }
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                  return _roles[role].adminRole;
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _grantRole(role, account);
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _revokeRole(role, account);
              }
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been revoked `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               */
              function renounceRole(bytes32 role, address account) public virtual override {
                  require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                  _revokeRole(role, account);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event. Note that unlike {grantRole}, this function doesn't perform any
               * checks on the calling account.
               *
               * [WARNING]
               * ====
               * This function should only be called from the constructor when setting
               * up the initial roles for the system.
               *
               * Using this function in any other way is effectively circumventing the admin
               * system imposed by {AccessControl}.
               * ====
               *
               * NOTE: This function is deprecated in favor of {_grantRole}.
               */
              function _setupRole(bytes32 role, address account) internal virtual {
                  _grantRole(role, account);
              }
              /**
               * @dev Sets `adminRole` as ``role``'s admin role.
               *
               * Emits a {RoleAdminChanged} event.
               */
              function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                  bytes32 previousAdminRole = getRoleAdmin(role);
                  _roles[role].adminRole = adminRole;
                  emit RoleAdminChanged(role, previousAdminRole, adminRole);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * Internal function without access restriction.
               */
              function _grantRole(bytes32 role, address account) internal virtual {
                  if (!hasRole(role, account)) {
                      _roles[role].members[account] = true;
                      emit RoleGranted(role, account, _msgSender());
                  }
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * Internal function without access restriction.
               */
              function _revokeRole(bytes32 role, address account) internal virtual {
                  if (hasRole(role, account)) {
                      _roles[role].members[account] = false;
                      emit RoleRevoked(role, account, _msgSender());
                  }
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlUpgradeable.sol";
          /**
           * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
           */
          interface IAccessControlEnumerableUpgradeable is IAccessControlUpgradeable {
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) external view returns (address);
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev External interface of AccessControl declared to support ERC165 detection.
           */
          interface IAccessControlUpgradeable {
              /**
               * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
               *
               * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
               * {RoleAdminChanged} not being emitted signaling this.
               *
               * _Available since v3.1._
               */
              event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
              /**
               * @dev Emitted when `account` is granted `role`.
               *
               * `sender` is the account that originated the contract call, an admin role
               * bearer except when using {AccessControl-_setupRole}.
               */
              event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Emitted when `account` is revoked `role`.
               *
               * `sender` is the account that originated the contract call:
               *   - if using `revokeRole`, it is the admin role bearer
               *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
               */
              event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) external view returns (bool);
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {AccessControl-_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) external view returns (bytes32);
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function grantRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function revokeRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been granted `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               */
              function renounceRole(bytes32 role, address account) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)
          pragma solidity ^0.8.0;
          import "../../utils/AddressUpgradeable.sol";
          /**
           * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
           * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
           * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
           * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
           *
           * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
           * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
           *
           * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
           * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
           *
           * [CAUTION]
           * ====
           * Avoid leaving a contract uninitialized.
           *
           * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
           * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
           * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * /// @custom:oz-upgrades-unsafe-allow constructor
           * constructor() initializer {}
           * ```
           * ====
           */
          abstract 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 protect an initializer function from being invoked twice.
               */
              modifier initializer() {
                  // If the contract is initializing we ignore whether _initialized is set in order to support multiple
                  // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
                  // contract may have been reentered.
                  require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");
                  bool isTopLevelCall = !_initializing;
                  if (isTopLevelCall) {
                      _initializing = true;
                      _initialized = true;
                  }
                  _;
                  if (isTopLevelCall) {
                      _initializing = false;
                  }
              }
              /**
               * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
               * {initializer} modifier, directly or indirectly.
               */
              modifier onlyInitializing() {
                  require(_initializing, "Initializable: contract is not initializing");
                  _;
              }
              function _isConstructor() private view returns (bool) {
                  return !AddressUpgradeable.isContract(address(this));
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/ContextUpgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
              /**
               * @dev Emitted when the pause is triggered by `account`.
               */
              event Paused(address account);
              /**
               * @dev Emitted when the pause is lifted by `account`.
               */
              event Unpaused(address account);
              bool private _paused;
              /**
               * @dev Initializes the contract in unpaused state.
               */
              function __Pausable_init() internal onlyInitializing {
                  __Pausable_init_unchained();
              }
              function __Pausable_init_unchained() internal onlyInitializing {
                  _paused = false;
              }
              /**
               * @dev Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  require(!paused(), "Pausable: paused");
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  require(paused(), "Pausable: not paused");
                  _;
              }
              /**
               * @dev Triggers stopped state.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              function _pause() internal virtual whenNotPaused {
                  _paused = true;
                  emit Paused(_msgSender());
              }
              /**
               * @dev Returns to normal state.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              function _unpause() internal virtual whenPaused {
                  _paused = false;
                  emit Unpaused(_msgSender());
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuardUpgradeable is Initializable {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              function __ReentrancyGuard_init() internal onlyInitializing {
                  __ReentrancyGuard_init_unchained();
              }
              function __ReentrancyGuard_init_unchained() internal onlyInitializing {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  // On the first call to nonReentrant, _notEntered will be true
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
                  _;
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library AddressUpgradeable {
              /**
               * @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
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 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");
                  (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");
                  (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");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal 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
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with 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 ContextUpgradeable is Initializable {
              function __Context_init() internal onlyInitializing {
              }
              function __Context_init_unchained() internal onlyInitializing {
              }
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev String operations.
           */
          library StringsUpgradeable {
              bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  if (value == 0) {
                      return "0x00";
                  }
                  uint256 temp = value;
                  uint256 length = 0;
                  while (temp != 0) {
                      length++;
                      temp >>= 8;
                  }
                  return toHexString(value, length);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165Upgradeable.sol";
          import "../../proxy/utils/Initializable.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
              function __ERC165_init() internal onlyInitializing {
              }
              function __ERC165_init_unchained() internal onlyInitializing {
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165Upgradeable).interfaceId;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165Upgradeable {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Library for managing
           * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
           * types.
           *
           * Sets have the following properties:
           *
           * - Elements are added, removed, and checked for existence in constant time
           * (O(1)).
           * - Elements are enumerated in O(n). No guarantees are made on the ordering.
           *
           * ```
           * contract Example {
           *     // Add the library methods
           *     using EnumerableSet for EnumerableSet.AddressSet;
           *
           *     // Declare a set state variable
           *     EnumerableSet.AddressSet private mySet;
           * }
           * ```
           *
           * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
           * and `uint256` (`UintSet`) are supported.
           */
          library EnumerableSetUpgradeable {
              // 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;
                      if (lastIndex != toDeleteIndex) {
                          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] = valueIndex; // Replace lastvalue's index to valueIndex
                      }
                      // 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) {
                  return set._values[index];
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function _values(Set storage set) private view returns (bytes32[] memory) {
                  return set._values;
              }
              // 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);
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
                  return _values(set._inner);
              }
              // 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))));
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(AddressSet storage set) internal view returns (address[] memory) {
                  bytes32[] memory store = _values(set._inner);
                  address[] memory result;
                  assembly {
                      result := store
                  }
                  return result;
              }
              // 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));
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(UintSet storage set) internal view returns (uint256[] memory) {
                  bytes32[] memory store = _values(set._inner);
                  uint256[] memory result;
                  assembly {
                      result := store
                  }
                  return result;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol)
          pragma solidity ^0.8.0;
          import "./IERC20.sol";
          import "./extensions/IERC20Metadata.sol";
          import "../../utils/Context.sol";
          /**
           * @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 Contracts guidelines: functions revert
           * instead 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, IERC20Metadata {
              mapping(address => uint256) private _balances;
              mapping(address => mapping(address => uint256)) private _allowances;
              uint256 private _totalSupply;
              string private _name;
              string private _symbol;
              /**
               * @dev Sets the values for {name} and {symbol}.
               *
               * The default value of {decimals} is 18. To select a different value for
               * {decimals} you should overload it.
               *
               * All two of these values are immutable: they can only be set once during
               * construction.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev Returns the name of the token.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev Returns the symbol of the token, usually a shorter version of the
               * name.
               */
              function symbol() public view virtual override 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 this function is
               * overridden;
               *
               * 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 override returns (uint8) {
                  return 18;
              }
              /**
               * @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:
               *
               * - `to` cannot be the zero address.
               * - the caller must have a balance of at least `amount`.
               */
              function transfer(address to, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _transfer(owner, to, 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}.
               *
               * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
               * `transferFrom`. This is semantically equivalent to an infinite approval.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               */
              function approve(address spender, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _approve(owner, 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}.
               *
               * NOTE: Does not update the allowance if the current allowance
               * is the maximum `uint256`.
               *
               * Requirements:
               *
               * - `from` and `to` cannot be the zero address.
               * - `from` must have a balance of at least `amount`.
               * - the caller must have allowance for ``from``'s tokens of at least
               * `amount`.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 amount
              ) public virtual override returns (bool) {
                  address spender = _msgSender();
                  _spendAllowance(from, spender, amount);
                  _transfer(from, to, amount);
                  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) {
                  address owner = _msgSender();
                  _approve(owner, spender, _allowances[owner][spender] + 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) {
                  address owner = _msgSender();
                  uint256 currentAllowance = _allowances[owner][spender];
                  require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
                  unchecked {
                      _approve(owner, spender, currentAllowance - subtractedValue);
                  }
                  return true;
              }
              /**
               * @dev Moves `amount` of tokens from `sender` to `recipient`.
               *
               * This 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:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `from` must have a balance of at least `amount`.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {
                  require(from != address(0), "ERC20: transfer from the zero address");
                  require(to != address(0), "ERC20: transfer to the zero address");
                  _beforeTokenTransfer(from, to, amount);
                  uint256 fromBalance = _balances[from];
                  require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
                  unchecked {
                      _balances[from] = fromBalance - amount;
                  }
                  _balances[to] += amount;
                  emit Transfer(from, to, amount);
                  _afterTokenTransfer(from, to, 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:
               *
               * - `account` 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 += amount;
                  _balances[account] += amount;
                  emit Transfer(address(0), account, amount);
                  _afterTokenTransfer(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);
                  uint256 accountBalance = _balances[account];
                  require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
                  unchecked {
                      _balances[account] = accountBalance - amount;
                  }
                  _totalSupply -= amount;
                  emit Transfer(account, address(0), amount);
                  _afterTokenTransfer(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 Spend `amount` form the allowance of `owner` toward `spender`.
               *
               * Does not update the allowance amount in case of infinite allowance.
               * Revert if not enough allowance is available.
               *
               * Might emit an {Approval} event.
               */
              function _spendAllowance(
                  address owner,
                  address spender,
                  uint256 amount
              ) internal virtual {
                  uint256 currentAllowance = allowance(owner, spender);
                  if (currentAllowance != type(uint256).max) {
                      require(currentAllowance >= amount, "ERC20: insufficient allowance");
                      unchecked {
                          _approve(owner, spender, currentAllowance - amount);
                      }
                  }
              }
              /**
               * @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 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 Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * has been transferred to `to`.
               * - when `from` is zero, `amount` tokens have been minted for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens have been 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 _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {}
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @dev Returns the amount of tokens in existence.
               */
              function totalSupply() external view returns (uint256);
              /**
               * @dev Returns the amount of tokens owned by `account`.
               */
              function balanceOf(address account) external view returns (uint256);
              /**
               * @dev Moves `amount` tokens from the caller's account to `to`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address to, 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 `from` to `to` 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 from,
                  address to,
                  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
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          /**
           * @dev Interface for the optional metadata functions from the ERC20 standard.
           *
           * _Available since v4.1._
           */
          interface IERC20Metadata is IERC20 {
              /**
               * @dev Returns the name of the token.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the symbol of the token.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the decimals places of the token.
               */
              function decimals() external view returns (uint8);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
           * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
           *
           * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
           * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
           * need to send a transaction, and thus is not required to hold Ether at all.
           */
          interface IERC20Permit {
              /**
               * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
               * given ``owner``'s signed approval.
               *
               * IMPORTANT: The same issues {IERC20-approve} has related to transaction
               * ordering also apply here.
               *
               * Emits an {Approval} event.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               * - `deadline` must be a timestamp in the future.
               * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
               * over the EIP712-formatted function arguments.
               * - the signature must use ``owner``'s current nonce (see {nonces}).
               *
               * For more information on the signature format, see the
               * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
               * section].
               */
              function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external;
              /**
               * @dev Returns the current nonce for `owner`. This value must be
               * included whenever a signature is generated for {permit}.
               *
               * Every successful call to {permit} increases ``owner``'s nonce by one. This
               * prevents a signature from being used multiple times.
               */
              function nonces(address owner) external view returns (uint256);
              /**
               * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
               */
              // solhint-disable-next-line func-name-mixedcase
              function DOMAIN_SEPARATOR() external view returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          import "../../../utils/Address.sol";
          /**
           * @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 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'
                  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) + value;
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
              }
              function safeDecreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  unchecked {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                      uint256 newAllowance = oldAllowance - 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
                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @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
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 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");
                  (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");
                  (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");
                  (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");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal 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
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with 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) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @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.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a / b + (a % b == 0 ? 0 : 1);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { Address } from "@openzeppelin/contracts/utils/Address.sol";
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import { EnumerableSetUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
          import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
          import { ITokenGovernance } from "@bancor/token-governance/contracts/ITokenGovernance.sol";
          import { IVersioned } from "../utility/interfaces/IVersioned.sol";
          import { PPM_RESOLUTION } from "../utility/Constants.sol";
          import { Upgradeable } from "../utility/Upgradeable.sol";
          import { Time } from "../utility/Time.sol";
          import { MathEx } from "../utility/MathEx.sol";
          // prettier-ignore
          import {
              Utils,
              AlreadyExists,
              DoesNotExist,
              InvalidToken,
              InvalidPool,
              InvalidPoolCollection,
              NotEmpty
          } from "../utility/Utils.sol";
          import { ROLE_ASSET_MANAGER } from "../vaults/interfaces/IVault.sol";
          import { IMasterVault } from "../vaults/interfaces/IMasterVault.sol";
          import { IExternalProtectionVault } from "../vaults/interfaces/IExternalProtectionVault.sol";
          import { Token } from "../token/Token.sol";
          import { TokenLibrary } from "../token/TokenLibrary.sol";
          import { IPoolCollection, TradeAmountAndFee } from "../pools/interfaces/IPoolCollection.sol";
          import { IPoolMigrator } from "../pools/interfaces/IPoolMigrator.sol";
          // prettier-ignore
          import {
              IBNTPool,
              ROLE_BNT_MANAGER,
              ROLE_VAULT_MANAGER,
              ROLE_FUNDING_MANAGER
          } from "../pools/interfaces/IBNTPool.sol";
          import { IPoolToken } from "../pools/interfaces/IPoolToken.sol";
          import { INetworkSettings, NotWhitelisted } from "./interfaces/INetworkSettings.sol";
          import { IPendingWithdrawals, CompletedWithdrawal } from "./interfaces/IPendingWithdrawals.sol";
          import { IBancorNetwork, IFlashLoanRecipient } from "./interfaces/IBancorNetwork.sol";
          /**
           * @dev Bancor Network contract
           */
          contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable, Time, Utils {
              using Address for address payable;
              using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
              using TokenLibrary for Token;
              using SafeERC20 for IPoolToken;
              error DeadlineExpired();
              error DepositingDisabled();
              error NativeTokenAmountMismatch();
              error InsufficientFlashLoanReturn();
              struct TradeParams {
                  uint256 amount;
                  uint256 limit;
                  bool bySourceAmount;
                  bool ignoreFees;
              }
              struct TradeResult {
                  uint256 sourceAmount;
                  uint256 targetAmount;
                  uint256 tradingFeeAmount;
                  uint256 networkFeeAmount;
              }
              struct TradeTokens {
                  Token sourceToken;
                  Token targetToken;
              }
              struct TraderInfo {
                  address trader;
                  address beneficiary;
              }
              // the migration manager role is required for migrating liquidity
              bytes32 private constant ROLE_MIGRATION_MANAGER = keccak256("ROLE_MIGRATION_MANAGER");
              // the emergency manager role is required to pause/unpause the network
              bytes32 private constant ROLE_EMERGENCY_STOPPER = keccak256("ROLE_EMERGENCY_STOPPER");
              // the network fee manager role is required to pull the accumulated pending network fees
              bytes32 private constant ROLE_NETWORK_FEE_MANAGER = keccak256("ROLE_NETWORK_FEE_MANAGER");
              // the address of the BNT token
              IERC20 private immutable _bnt;
              // the address of the BNT token governance
              ITokenGovernance private immutable _bntGovernance;
              // the address of the vBNT token
              IERC20 private immutable _vbnt;
              // the address of the vBNT token governance
              ITokenGovernance private immutable _vbntGovernance;
              // the network settings contract
              INetworkSettings private immutable _networkSettings;
              // the master vault contract
              IMasterVault private immutable _masterVault;
              // the address of the external protection vault
              IExternalProtectionVault private immutable _externalProtectionVault;
              // the BNT pool token
              IPoolToken internal immutable _bntPoolToken;
              // the Bancor arbitrage contract
              address internal immutable _bancorArbitrage;
              // the BNT pool contract
              IBNTPool internal _bntPool;
              // the pending withdrawals contract
              IPendingWithdrawals internal _pendingWithdrawals;
              // the pool migrator contract
              IPoolMigrator internal _poolMigrator;
              // the set of all valid pool collections
              EnumerableSetUpgradeable.AddressSet private _poolCollections;
              // DEPRECATED (mapping(uint16 => IPoolCollection) _latestPoolCollections)
              uint256 private _deprecated0;
              // the set of all pools
              EnumerableSetUpgradeable.AddressSet private _liquidityPools;
              // a mapping between pools and their respective pool collections
              mapping(Token => IPoolCollection) private _collectionByPool;
              // the pending network fee amount to be burned by the vortex
              uint256 internal _pendingNetworkFeeAmount;
              bool private _depositingEnabled = true;
              // upgrade forward-compatibility storage gap
              uint256[MAX_GAP - 11] private __gap;
              /**
               * @dev triggered when a new pool collection is added
               */
              event PoolCollectionAdded(uint16 indexed poolType, IPoolCollection indexed poolCollection);
              /**
               * @dev triggered when an existing pool collection is removed
               */
              event PoolCollectionRemoved(uint16 indexed poolType, IPoolCollection indexed poolCollection);
              /**
               * @dev triggered when a pool is created
               */
              event PoolCreated(Token indexed pool, IPoolCollection indexed poolCollection);
              /**
               * @dev triggered when a new pool is added to a pool collection
               */
              event PoolAdded(Token indexed pool, IPoolCollection indexed poolCollection);
              /**
               * @dev triggered when a new pool is removed from a pool collection
               */
              event PoolRemoved(Token indexed pool, IPoolCollection indexed poolCollection);
              /**
               * @dev triggered when funds are migrated
               */
              event FundsMigrated(
                  bytes32 indexed contextId,
                  Token indexed token,
                  address indexed provider,
                  uint256 amount,
                  uint256 availableAmount,
                  uint256 originalAmount
              );
              /**
               * @dev triggered on a successful trade
               */
              event TokensTraded(
                  bytes32 indexed contextId,
                  Token indexed sourceToken,
                  Token indexed targetToken,
                  uint256 sourceAmount,
                  uint256 targetAmount,
                  uint256 bntAmount,
                  uint256 targetFeeAmount,
                  uint256 bntFeeAmount,
                  address trader
              );
              /**
               * @dev triggered when a flash-loan is completed
               */
              event FlashLoanCompleted(Token indexed token, address indexed borrower, uint256 amount, uint256 feeAmount);
              /**
               * @dev triggered when network fees are withdrawn
               */
              event NetworkFeesWithdrawn(address indexed caller, address indexed recipient, uint256 amount);
              /**
               * @dev a "virtual" constructor that is only used to set immutable state variables
               */
              constructor(
                  ITokenGovernance initBNTGovernance,
                  ITokenGovernance initVBNTGovernance,
                  INetworkSettings initNetworkSettings,
                  IMasterVault initMasterVault,
                  IExternalProtectionVault initExternalProtectionVault,
                  IPoolToken initBNTPoolToken,
                  address bancorArbitrage
              )
                  validAddress(address(initBNTGovernance))
                  validAddress(address(initVBNTGovernance))
                  validAddress(address(initNetworkSettings))
                  validAddress(address(initMasterVault))
                  validAddress(address(initExternalProtectionVault))
                  validAddress(address(initBNTPoolToken))
                  validAddress(address(bancorArbitrage))
              {
                  _bntGovernance = initBNTGovernance;
                  _bnt = initBNTGovernance.token();
                  _vbntGovernance = initVBNTGovernance;
                  _vbnt = initVBNTGovernance.token();
                  _networkSettings = initNetworkSettings;
                  _masterVault = initMasterVault;
                  _externalProtectionVault = initExternalProtectionVault;
                  _bntPoolToken = initBNTPoolToken;
                  _bancorArbitrage = bancorArbitrage;
              }
              /**
               * @dev fully initializes the contract and its parents
               */
              function initialize(
                  IBNTPool initBNTPool,
                  IPendingWithdrawals initPendingWithdrawals,
                  IPoolMigrator initPoolMigrator
              )
                  external
                  validAddress(address(initBNTPool))
                  validAddress(address(initPendingWithdrawals))
                  validAddress(address(initPoolMigrator))
                  initializer
              {
                  __BancorNetwork_init(initBNTPool, initPendingWithdrawals, initPoolMigrator);
              }
              // solhint-disable func-name-mixedcase
              /**
               * @dev initializes the contract and its parents
               */
              function __BancorNetwork_init(
                  IBNTPool initBNTPool,
                  IPendingWithdrawals initPendingWithdrawals,
                  IPoolMigrator initPoolMigrator
              ) internal onlyInitializing {
                  __Upgradeable_init();
                  __ReentrancyGuard_init();
                  __Pausable_init();
                  __BancorNetwork_init_unchained(initBNTPool, initPendingWithdrawals, initPoolMigrator);
              }
              /**
               * @dev performs contract-specific initialization
               */
              function __BancorNetwork_init_unchained(
                  IBNTPool initBNTPool,
                  IPendingWithdrawals initPendingWithdrawals,
                  IPoolMigrator initPoolMigrator
              ) internal onlyInitializing {
                  _bntPool = initBNTPool;
                  _pendingWithdrawals = initPendingWithdrawals;
                  _poolMigrator = initPoolMigrator;
                  // set up administrative roles
                  _setRoleAdmin(ROLE_MIGRATION_MANAGER, ROLE_ADMIN);
                  _setRoleAdmin(ROLE_EMERGENCY_STOPPER, ROLE_ADMIN);
                  _setRoleAdmin(ROLE_NETWORK_FEE_MANAGER, ROLE_ADMIN);
                  _depositingEnabled = true;
              }
              // solhint-enable func-name-mixedcase
              modifier depositsEnabled() {
                  _depositsEnabled();
                  _;
              }
              function _depositsEnabled() internal view {
                  if (!_depositingEnabled) {
                      revert DepositingDisabled();
                  }
              }
              receive() external payable {}
              /**
               * @inheritdoc Upgradeable
               */
              function version() public pure override(IVersioned, Upgradeable) returns (uint16) {
                  return 8;
              }
              /**
               * @dev returns the migration manager role
               */
              function roleMigrationManager() external pure returns (bytes32) {
                  return ROLE_MIGRATION_MANAGER;
              }
              /**
               * @dev returns the emergency stopper role
               */
              function roleEmergencyStopper() external pure returns (bytes32) {
                  return ROLE_EMERGENCY_STOPPER;
              }
              /**
               * @dev returns the network fee manager role
               */
              function roleNetworkFeeManager() external pure returns (bytes32) {
                  return ROLE_NETWORK_FEE_MANAGER;
              }
              /**
               * @dev returns the pending network fee amount to be burned by the vortex
               */
              function pendingNetworkFeeAmount() external view returns (uint256) {
                  return _pendingNetworkFeeAmount;
              }
              /**
               * @dev registers new pool collection with the network
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function registerPoolCollection(
                  IPoolCollection newPoolCollection
              ) external validAddress(address(newPoolCollection)) onlyAdmin nonReentrant {
                  // verify that there is no pool collection of the same type and version
                  uint16 newPoolType = newPoolCollection.poolType();
                  uint16 newPoolVersion = newPoolCollection.version();
                  IPoolCollection poolCollection = _findPoolCollection(newPoolType, newPoolVersion);
                  if (poolCollection != IPoolCollection(address(0)) || !_poolCollections.add(address(newPoolCollection))) {
                      revert AlreadyExists();
                  }
                  _setAccessRoles(newPoolCollection, true);
                  emit PoolCollectionAdded({ poolType: newPoolCollection.poolType(), poolCollection: newPoolCollection });
              }
              /**
               * @dev unregisters an existing pool collection from the network
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function unregisterPoolCollection(
                  IPoolCollection poolCollection
              ) external validAddress(address(poolCollection)) onlyAdmin nonReentrant {
                  // verify that no pools are associated with the specified pool collection
                  if (poolCollection.poolCount() != 0) {
                      revert NotEmpty();
                  }
                  if (!_poolCollections.remove(address(poolCollection))) {
                      revert DoesNotExist();
                  }
                  _setAccessRoles(poolCollection, false);
                  emit PoolCollectionRemoved({ poolType: poolCollection.poolType(), poolCollection: poolCollection });
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function poolCollections() external view returns (IPoolCollection[] memory) {
                  uint256 length = _poolCollections.length();
                  IPoolCollection[] memory list = new IPoolCollection[](length);
                  for (uint256 i = 0; i < length; i++) {
                      list[i] = IPoolCollection(_poolCollections.at(i));
                  }
                  return list;
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function liquidityPools() external view returns (Token[] memory) {
                  uint256 length = _liquidityPools.length();
                  Token[] memory list = new Token[](length);
                  for (uint256 i = 0; i < length; i++) {
                      list[i] = Token(_liquidityPools.at(i));
                  }
                  return list;
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function collectionByPool(Token pool) external view returns (IPoolCollection) {
                  return _collectionByPool[pool];
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function createPools(
                  Token[] calldata tokens,
                  IPoolCollection poolCollection
              ) external validAddress(address(poolCollection)) onlyAdmin nonReentrant {
                  if (!_poolCollections.contains(address(poolCollection))) {
                      revert DoesNotExist();
                  }
                  uint256 length = tokens.length;
                  for (uint256 i = 0; i < length; i++) {
                      _createPool(tokens[i], poolCollection);
                  }
              }
              /**
               * @dev creates a new pool
               */
              function _createPool(Token token, IPoolCollection poolCollection) private {
                  _validAddress(address(token));
                  if (token.isEqual(_bnt)) {
                      revert InvalidToken();
                  }
                  if (!_liquidityPools.add(address(token))) {
                      revert AlreadyExists();
                  }
                  // this is where the magic happens...
                  poolCollection.createPool(token);
                  // add the pool collection to the reverse pool collection lookup
                  _collectionByPool[token] = poolCollection;
                  emit PoolCreated({ pool: token, poolCollection: poolCollection });
                  emit PoolAdded({ pool: token, poolCollection: poolCollection });
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function migratePools(Token[] calldata pools, IPoolCollection newPoolCollection) external nonReentrant {
                  if (!_poolCollections.contains(address(newPoolCollection))) {
                      revert DoesNotExist();
                  }
                  uint256 length = pools.length;
                  for (uint256 i = 0; i < length; i++) {
                      Token pool = pools[i];
                      // request the pool migrator to migrate the pool to the new pool collection
                      _poolMigrator.migratePool(pool, newPoolCollection);
                      IPoolCollection prevPoolCollection = _collectionByPool[pool];
                      // update the mapping between pools and their respective pool collections
                      _collectionByPool[pool] = newPoolCollection;
                      emit PoolRemoved(pool, prevPoolCollection);
                      emit PoolAdded(pool, newPoolCollection);
                  }
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function depositFor(
                  address provider,
                  Token pool,
                  uint256 tokenAmount
              )
                  external
                  payable
                  depositsEnabled
                  validAddress(provider)
                  validAddress(address(pool))
                  greaterThanZero(tokenAmount)
                  whenNotPaused
                  nonReentrant
                  returns (uint256)
              {
                  return _depositFor(provider, pool, tokenAmount, msg.sender);
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function deposit(
                  Token pool,
                  uint256 tokenAmount
              )
                  external
                  payable
                  depositsEnabled
                  validAddress(address(pool))
                  greaterThanZero(tokenAmount)
                  whenNotPaused
                  nonReentrant
                  returns (uint256)
              {
                  return _depositFor(msg.sender, pool, tokenAmount, msg.sender);
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function initWithdrawal(
                  IPoolToken poolToken,
                  uint256 poolTokenAmount
              )
                  external
                  validAddress(address(poolToken))
                  greaterThanZero(poolTokenAmount)
                  whenNotPaused
                  nonReentrant
                  returns (uint256)
              {
                  return _initWithdrawal(msg.sender, poolToken, poolTokenAmount);
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function cancelWithdrawal(uint256 id) external whenNotPaused nonReentrant returns (uint256) {
                  return _pendingWithdrawals.cancelWithdrawal(msg.sender, id);
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function withdraw(uint256 id) external whenNotPaused nonReentrant returns (uint256) {
                  address provider = msg.sender;
                  bytes32 contextId = _withdrawContextId(id, provider);
                  // complete the withdrawal and claim the locked pool tokens
                  CompletedWithdrawal memory completedRequest = _pendingWithdrawals.completeWithdrawal(contextId, provider, id);
                  if (completedRequest.poolToken == _bntPoolToken) {
                      return _withdrawBNT(contextId, provider, completedRequest);
                  }
                  return _withdrawBaseToken(contextId, provider, completedRequest);
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function tradeBySourceAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable whenNotPaused nonReentrant returns (uint256) {
                  return
                      _tradeBySourceAmount(
                          sourceToken,
                          targetToken,
                          sourceAmount,
                          minReturnAmount,
                          deadline,
                          beneficiary,
                          msg.sender
                      );
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function tradeByTargetAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable whenNotPaused nonReentrant returns (uint256) {
                  return
                      _tradeByTargetAmount(
                          sourceToken,
                          targetToken,
                          targetAmount,
                          maxSourceAmount,
                          deadline,
                          beneficiary,
                          msg.sender
                      );
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function tradeBySourceAmountArb(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable whenNotPaused only(_bancorArbitrage) returns (uint256) {
                  return
                      _tradeBySourceAmount(
                          sourceToken,
                          targetToken,
                          sourceAmount,
                          minReturnAmount,
                          deadline,
                          beneficiary,
                          msg.sender
                      );
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function tradeByTargetAmountArb(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable whenNotPaused only(_bancorArbitrage) returns (uint256) {
                  return
                      _tradeByTargetAmount(
                          sourceToken,
                          targetToken,
                          targetAmount,
                          maxSourceAmount,
                          deadline,
                          beneficiary,
                          msg.sender
                      );
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function flashLoan(
                  Token token,
                  uint256 amount,
                  IFlashLoanRecipient recipient,
                  bytes calldata data
              )
                  external
                  validAddress(address(token))
                  greaterThanZero(amount)
                  validAddress(address(recipient))
                  whenNotPaused
                  nonReentrant
              {
                  if (!token.isEqual(_bnt) && !_networkSettings.isTokenWhitelisted(token)) {
                      revert NotWhitelisted();
                  }
                  uint256 feeAmount;
                  if (msg.sender == _bancorArbitrage) {
                      // exempt arb contract from fees
                      feeAmount = 0;
                  } else {
                      feeAmount = MathEx.mulDivF(amount, _networkSettings.flashLoanFeePPM(token), PPM_RESOLUTION);
                  }
                  // save the current balance
                  uint256 prevBalance = token.balanceOf(address(this));
                  // transfer the amount from the master vault to the recipient
                  _masterVault.withdrawFunds(token, payable(address(recipient)), amount);
                  // invoke the recipient's callback
                  recipient.onFlashLoan(msg.sender, token.toIERC20(), amount, feeAmount, data);
                  // ensure that the tokens + fee have been deposited back to the network
                  uint256 returnedAmount = token.balanceOf(address(this)) - prevBalance;
                  if (returnedAmount < amount + feeAmount) {
                      revert InsufficientFlashLoanReturn();
                  }
                  // transfer the amount and the fee back to the vault
                  if (token.isNative()) {
                      payable(address(_masterVault)).sendValue(returnedAmount);
                  } else {
                      token.safeTransfer(payable(address(_masterVault)), returnedAmount);
                  }
                  // notify the pool of accrued fees
                  if (token.isEqual(_bnt)) {
                      IBNTPool cachedBNTPool = _bntPool;
                      cachedBNTPool.onFeesCollected(token, feeAmount, false);
                  } else {
                      // get the pool and verify that it exists
                      IPoolCollection poolCollection = _poolCollection(token);
                      poolCollection.onFeesCollected(token, feeAmount);
                  }
                  emit FlashLoanCompleted({ token: token, borrower: msg.sender, amount: amount, feeAmount: feeAmount });
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function migrateLiquidity(
                  Token token,
                  address provider,
                  uint256 amount,
                  uint256 availableAmount,
                  uint256 originalAmount
              ) external payable whenNotPaused onlyRoleMember(ROLE_MIGRATION_MANAGER) nonReentrant {
                  bytes32 contextId = keccak256(
                      abi.encodePacked(msg.sender, _time(), token, provider, amount, availableAmount, originalAmount)
                  );
                  if (token.isEqual(_bnt)) {
                      _depositBNTFor(contextId, provider, amount, msg.sender, true, originalAmount);
                  } else {
                      _depositBaseTokenFor(contextId, provider, token, amount, msg.sender, availableAmount);
                  }
                  emit FundsMigrated(contextId, token, provider, amount, availableAmount, originalAmount);
              }
              /**
               * @inheritdoc IBancorNetwork
               */
              function withdrawNetworkFees(
                  address recipient
              )
                  external
                  whenNotPaused
                  onlyRoleMember(ROLE_NETWORK_FEE_MANAGER)
                  validAddress(recipient)
                  nonReentrant
                  returns (uint256)
              {
                  uint256 currentPendingNetworkFeeAmount = _pendingNetworkFeeAmount;
                  if (currentPendingNetworkFeeAmount == 0) {
                      return 0;
                  }
                  _pendingNetworkFeeAmount = 0;
                  _masterVault.withdrawFunds(Token(address(_bnt)), payable(recipient), currentPendingNetworkFeeAmount);
                  emit NetworkFeesWithdrawn(msg.sender, recipient, currentPendingNetworkFeeAmount);
                  return currentPendingNetworkFeeAmount;
              }
              /**
               * @dev pauses the network
               *
               * requirements:
               *
               * - the caller must have the ROLE_EMERGENCY_STOPPER privilege
               */
              function pause() external onlyRoleMember(ROLE_EMERGENCY_STOPPER) {
                  _pause();
              }
              /**
               * @dev resumes the network
               *
               * requirements:
               *
               * - the caller must have the ROLE_EMERGENCY_STOPPER privilege
               */
              function resume() external onlyRoleMember(ROLE_EMERGENCY_STOPPER) {
                  _unpause();
              }
              /**
               * @dev returns whether deposits are enabled
               */
              function depositingEnabled() external view returns (bool) {
                  return _depositingEnabled;
              }
              /**
               * @dev enables/disables depositing into a given pool
               *
               * requirements:
               *
               * - the caller must be the owner of the contract
               */
              function enableDepositing(bool status) external onlyAdmin {
                  if (_depositingEnabled == status) {
                      return;
                  }
                  _depositingEnabled = status;
              }
              /**
               * @dev generates context ID for a deposit request
               */
              function _depositContextId(
                  address provider,
                  Token pool,
                  uint256 tokenAmount,
                  address caller
              ) private view returns (bytes32) {
                  return keccak256(abi.encodePacked(caller, _time(), provider, pool, tokenAmount));
              }
              /**
               * @dev generates context ID for a withdraw request
               */
              function _withdrawContextId(uint256 id, address caller) private view returns (bytes32) {
                  return keccak256(abi.encodePacked(caller, _time(), id));
              }
              /**
               * @dev deposits liquidity for the specified provider from caller
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the liquidity tokens on its behalf
               */
              function _depositFor(address provider, Token pool, uint256 tokenAmount, address caller) private returns (uint256) {
                  bytes32 contextId = _depositContextId(provider, pool, tokenAmount, caller);
                  if (pool.isEqual(_bnt)) {
                      return _depositBNTFor(contextId, provider, tokenAmount, caller, false, 0);
                  }
                  return _depositBaseTokenFor(contextId, provider, pool, tokenAmount, caller, tokenAmount);
              }
              /**
               * @dev deposits BNT liquidity for the specified provider from caller
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer BNT on its behalf
               */
              function _depositBNTFor(
                  bytes32 contextId,
                  address provider,
                  uint256 bntAmount,
                  address caller,
                  bool isMigrating,
                  uint256 originalAmount
              ) private returns (uint256) {
                  if (msg.value > 0) {
                      revert NativeTokenAmountMismatch();
                  }
                  IBNTPool cachedBNTPool = _bntPool;
                  // transfer the tokens from the caller to the BNT pool
                  _bnt.transferFrom(caller, address(cachedBNTPool), bntAmount);
                  // process BNT pool deposit
                  return cachedBNTPool.depositFor(contextId, provider, bntAmount, isMigrating, originalAmount);
              }
              /**
               * @dev deposits base token liquidity for the specified provider from sender
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer base tokens to on its behalf
               */
              function _depositBaseTokenFor(
                  bytes32 contextId,
                  address provider,
                  Token pool,
                  uint256 tokenAmount,
                  address caller,
                  uint256 availableAmount
              ) private returns (uint256) {
                  // transfer the tokens from the sender to the vault
                  _depositToMasterVault(pool, caller, availableAmount);
                  // get the pool collection that managed this pool
                  IPoolCollection poolCollection = _poolCollection(pool);
                  // process deposit to the base token pool (includes the native token pool)
                  return poolCollection.depositFor(contextId, provider, pool, tokenAmount);
              }
              /**
               * @dev handles BNT withdrawal
               */
              function _withdrawBNT(
                  bytes32 contextId,
                  address provider,
                  CompletedWithdrawal memory completedRequest
              ) private returns (uint256) {
                  IBNTPool cachedBNTPool = _bntPool;
                  // transfer the pool tokens to from the pending withdrawals contract to the BNT pool
                  completedRequest.poolToken.transferFrom(
                      address(_pendingWithdrawals),
                      address(cachedBNTPool),
                      completedRequest.poolTokenAmount
                  );
                  // transfer vBNT from the caller to the BNT pool
                  _vbnt.transferFrom(provider, address(cachedBNTPool), completedRequest.poolTokenAmount);
                  // call withdraw on the BNT pool
                  return
                      cachedBNTPool.withdraw(
                          contextId,
                          provider,
                          completedRequest.poolTokenAmount,
                          completedRequest.reserveTokenAmount
                      );
              }
              /**
               * @dev handles base token withdrawal
               */
              function _withdrawBaseToken(
                  bytes32 contextId,
                  address provider,
                  CompletedWithdrawal memory completedRequest
              ) private returns (uint256) {
                  Token pool = completedRequest.poolToken.reserveToken();
                  // get the pool collection that manages this pool
                  IPoolCollection poolCollection = _poolCollection(pool);
                  // transfer the pool tokens to from the pending withdrawals contract to the pool collection
                  completedRequest.poolToken.transferFrom(
                      address(_pendingWithdrawals),
                      address(poolCollection),
                      completedRequest.poolTokenAmount
                  );
                  // call withdraw on the base token pool - returns the amounts/breakdown
                  return
                      poolCollection.withdraw(
                          contextId,
                          provider,
                          pool,
                          completedRequest.poolTokenAmount,
                          completedRequest.reserveTokenAmount
                      );
              }
              /**
               * @dev verifies that the provided trade params are valid
               */
              function _verifyTradeParams(
                  Token sourceToken,
                  Token targetToken,
                  uint256 amount,
                  uint256 limit,
                  uint256 deadline
              ) internal view {
                  _validAddress(address(sourceToken));
                  _validAddress(address(targetToken));
                  if (sourceToken == targetToken) {
                      revert InvalidToken();
                  }
                  _greaterThanZero(amount);
                  _greaterThanZero(limit);
                  if (deadline < _time()) {
                      revert DeadlineExpired();
                  }
              }
              /**
               * @dev internal trade by source amount logic
               */
              function _tradeBySourceAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  uint256 deadline,
                  address beneficiary,
                  address sender
              ) private returns (uint256) {
                  _verifyTradeParams(sourceToken, targetToken, sourceAmount, minReturnAmount, deadline);
                  bool _ignoreFees = false;
                  if (sender == _bancorArbitrage) {
                      _ignoreFees = true;
                  }
                  return
                      _trade(
                          TradeTokens({ sourceToken: sourceToken, targetToken: targetToken }),
                          TradeParams({
                              bySourceAmount: true,
                              amount: sourceAmount,
                              limit: minReturnAmount,
                              ignoreFees: _ignoreFees
                          }),
                          TraderInfo({ trader: sender, beneficiary: beneficiary }),
                          deadline
                      );
              }
              /**
               * @dev internal trade by target amount logic
               */
              function _tradeByTargetAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  uint256 deadline,
                  address beneficiary,
                  address sender
              ) private returns (uint256) {
                  _verifyTradeParams(sourceToken, targetToken, targetAmount, maxSourceAmount, deadline);
                  bool _ignoreFees = false;
                  if (sender == _bancorArbitrage) {
                      _ignoreFees = true;
                  }
                  return
                      _trade(
                          TradeTokens({ sourceToken: sourceToken, targetToken: targetToken }),
                          TradeParams({
                              bySourceAmount: false,
                              amount: targetAmount,
                              limit: maxSourceAmount,
                              ignoreFees: _ignoreFees
                          }),
                          TraderInfo({ trader: sender, beneficiary: beneficiary }),
                          deadline
                      );
              }
              /**
               * @dev performs a trade by providing either the source or target amount:
               *
               * - when trading by the source amount, the amount represents the source amount and the limit is the minimum return
               *   amount
               * - when trading by the target amount, the amount represents the target amount and the limit is the maximum source
               *   amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the source tokens on its behalf (except for in the
               *   native token case)
               */
              function _trade(
                  TradeTokens memory tokens,
                  TradeParams memory params,
                  TraderInfo memory traderInfo,
                  uint256 deadline
              ) private returns (uint256) {
                  // ensure the beneficiary is set
                  if (traderInfo.beneficiary == address(0)) {
                      traderInfo.beneficiary = traderInfo.trader;
                  }
                  bytes32 contextId = keccak256(
                      abi.encodePacked(
                          traderInfo.trader,
                          _time(),
                          tokens.sourceToken,
                          tokens.targetToken,
                          params.amount,
                          params.limit,
                          params.bySourceAmount,
                          deadline,
                          traderInfo.beneficiary
                      )
                  );
                  // perform either a single or double hop trade, based on the source and the target pool
                  TradeResult memory firstHopTradeResult;
                  TradeResult memory lastHopTradeResult;
                  uint256 networkFeeAmount;
                  if (tokens.sourceToken.isEqual(_bnt)) {
                      lastHopTradeResult = _tradeBNT(contextId, tokens.targetToken, true, params);
                      firstHopTradeResult = lastHopTradeResult;
                      networkFeeAmount = lastHopTradeResult.networkFeeAmount;
                      emit TokensTraded({
                          contextId: contextId,
                          sourceToken: tokens.sourceToken,
                          targetToken: tokens.targetToken,
                          sourceAmount: lastHopTradeResult.sourceAmount,
                          targetAmount: lastHopTradeResult.targetAmount,
                          bntAmount: lastHopTradeResult.sourceAmount,
                          targetFeeAmount: lastHopTradeResult.tradingFeeAmount,
                          bntFeeAmount: 0,
                          trader: traderInfo.trader
                      });
                  } else if (tokens.targetToken.isEqual(_bnt)) {
                      lastHopTradeResult = _tradeBNT(contextId, tokens.sourceToken, false, params);
                      firstHopTradeResult = lastHopTradeResult;
                      networkFeeAmount = lastHopTradeResult.networkFeeAmount;
                      emit TokensTraded({
                          contextId: contextId,
                          sourceToken: tokens.sourceToken,
                          targetToken: tokens.targetToken,
                          sourceAmount: lastHopTradeResult.sourceAmount,
                          targetAmount: lastHopTradeResult.targetAmount,
                          bntAmount: lastHopTradeResult.targetAmount,
                          targetFeeAmount: lastHopTradeResult.tradingFeeAmount,
                          bntFeeAmount: lastHopTradeResult.tradingFeeAmount,
                          trader: traderInfo.trader
                      });
                  } else {
                      (firstHopTradeResult, lastHopTradeResult) = _tradeBaseTokens(contextId, tokens, params);
                      networkFeeAmount = firstHopTradeResult.networkFeeAmount + lastHopTradeResult.networkFeeAmount;
                      emit TokensTraded({
                          contextId: contextId,
                          sourceToken: tokens.sourceToken,
                          targetToken: tokens.targetToken,
                          sourceAmount: firstHopTradeResult.sourceAmount,
                          targetAmount: lastHopTradeResult.targetAmount,
                          bntAmount: firstHopTradeResult.targetAmount,
                          targetFeeAmount: lastHopTradeResult.tradingFeeAmount,
                          bntFeeAmount: firstHopTradeResult.tradingFeeAmount,
                          trader: traderInfo.trader
                      });
                  }
                  // transfer the tokens from the trader to the vault
                  _depositToMasterVault(tokens.sourceToken, traderInfo.trader, firstHopTradeResult.sourceAmount);
                  // transfer the target tokens/native token to the beneficiary
                  _masterVault.withdrawFunds(
                      tokens.targetToken,
                      payable(traderInfo.beneficiary),
                      lastHopTradeResult.targetAmount
                  );
                  // update the pending network fee amount to be burned by the vortex
                  _pendingNetworkFeeAmount += networkFeeAmount;
                  return params.bySourceAmount ? lastHopTradeResult.targetAmount : firstHopTradeResult.sourceAmount;
              }
              /**
               * @dev performs a single hop between BNT and a base token trade by providing either the source or the target amount
               *
               * - when trading by the source amount, the amount represents the source amount and the limit is the minimum return
               *   amount
               * - when trading by the target amount, the amount represents the target amount and the limit is the maximum source
               *   amount
               */
              function _tradeBNT(
                  bytes32 contextId,
                  Token pool,
                  bool fromBNT,
                  TradeParams memory params
              ) private returns (TradeResult memory) {
                  TradeTokens memory tokens = fromBNT
                      ? TradeTokens({ sourceToken: Token(address(_bnt)), targetToken: pool })
                      : TradeTokens({ sourceToken: pool, targetToken: Token(address(_bnt)) });
                  TradeAmountAndFee memory tradeAmountsAndFee = params.bySourceAmount
                      ? _poolCollection(pool).tradeBySourceAmount(
                          contextId,
                          tokens.sourceToken,
                          tokens.targetToken,
                          params.amount,
                          params.limit,
                          params.ignoreFees
                      )
                      : _poolCollection(pool).tradeByTargetAmount(
                          contextId,
                          tokens.sourceToken,
                          tokens.targetToken,
                          params.amount,
                          params.limit,
                          params.ignoreFees
                      );
                  // if the target token is BNT, notify the BNT pool on collected fees (which shouldn't include the network fee
                  // amount, so we have to deduct it explicitly from the full trading fee amount)
                  if (!fromBNT) {
                      _bntPool.onFeesCollected(
                          pool,
                          tradeAmountsAndFee.tradingFeeAmount - tradeAmountsAndFee.networkFeeAmount,
                          true
                      );
                  }
                  return
                      TradeResult({
                          sourceAmount: params.bySourceAmount ? params.amount : tradeAmountsAndFee.amount,
                          targetAmount: params.bySourceAmount ? tradeAmountsAndFee.amount : params.amount,
                          tradingFeeAmount: tradeAmountsAndFee.tradingFeeAmount,
                          networkFeeAmount: tradeAmountsAndFee.networkFeeAmount
                      });
              }
              /**
               * @dev performs a double hop trade between two base tokens by providing either the source or the target amount
               *
               * - when trading by the source amount, the amount represents the source amount and the limit is the minimum return
               *   amount
               * - when trading by the target amount, the amount represents the target amount and the limit is the maximum source
               *   amount
               */
              function _tradeBaseTokens(
                  bytes32 contextId,
                  TradeTokens memory tokens,
                  TradeParams memory params
              ) private returns (TradeResult memory, TradeResult memory) {
                  if (params.bySourceAmount) {
                      uint256 sourceAmount = params.amount;
                      uint256 minReturnAmount = params.limit;
                      // trade source tokens to BNT (while accepting any return amount)
                      TradeResult memory targetHop1 = _tradeBNT(
                          contextId,
                          tokens.sourceToken,
                          false,
                          TradeParams({ bySourceAmount: true, amount: sourceAmount, limit: 1, ignoreFees: params.ignoreFees })
                      );
                      // trade the received BNT target amount to target tokens (while respecting the minimum return amount)
                      TradeResult memory targetHop2 = _tradeBNT(
                          contextId,
                          tokens.targetToken,
                          true,
                          TradeParams({
                              bySourceAmount: true,
                              amount: targetHop1.targetAmount,
                              limit: minReturnAmount,
                              ignoreFees: params.ignoreFees
                          })
                      );
                      return (targetHop1, targetHop2);
                  }
                  uint256 targetAmount = params.amount;
                  uint256 maxSourceAmount = params.limit;
                  // trade any amount of BNT to get the requested target amount (we will use the actual traded amount to restrict
                  // the trade from the source)
                  TradeResult memory sourceHop2 = _tradeBNT(
                      contextId,
                      tokens.targetToken,
                      true,
                      TradeParams({
                          bySourceAmount: false,
                          amount: targetAmount,
                          limit: type(uint256).max,
                          ignoreFees: params.ignoreFees
                      })
                  );
                  // trade source tokens to the required amount of BNT (while respecting the maximum source amount)
                  TradeResult memory sourceHop1 = _tradeBNT(
                      contextId,
                      tokens.sourceToken,
                      false,
                      TradeParams({
                          bySourceAmount: false,
                          amount: sourceHop2.sourceAmount,
                          limit: maxSourceAmount,
                          ignoreFees: params.ignoreFees
                      })
                  );
                  return (sourceHop1, sourceHop2);
              }
              /**
               * @dev deposits tokens to the master vault and verifies that msg.value corresponds to its type
               */
              function _depositToMasterVault(Token token, address caller, uint256 amount) private {
                  if (token.isNative()) {
                      if (msg.value < amount) {
                          revert NativeTokenAmountMismatch();
                      }
                      // using a regular transfer here would revert due to exceeding the 2300 gas limit which is why we're using
                      // call instead (via sendValue), which the 2300 gas limit does not apply for
                      payable(address(_masterVault)).sendValue(amount);
                      // refund the caller for the remaining native token amount
                      if (msg.value > amount) {
                          payable(address(caller)).sendValue(msg.value - amount);
                      }
                  } else {
                      if (msg.value > 0) {
                          revert NativeTokenAmountMismatch();
                      }
                      token.safeTransferFrom(caller, address(_masterVault), amount);
                  }
              }
              /**
               * @dev verifies that the specified pool is managed by a valid pool collection and returns it
               */
              function _poolCollection(Token token) private view returns (IPoolCollection) {
                  // verify that the pool is managed by a valid pool collection
                  IPoolCollection poolCollection = _collectionByPool[token];
                  if (address(poolCollection) == address(0)) {
                      revert InvalidToken();
                  }
                  return poolCollection;
              }
              /**
               * @dev initiates liquidity withdrawal
               */
              function _initWithdrawal(
                  address provider,
                  IPoolToken poolToken,
                  uint256 poolTokenAmount
              ) private returns (uint256) {
                  if (poolToken != _bntPoolToken) {
                      Token reserveToken = poolToken.reserveToken();
                      if (_poolCollection(reserveToken).poolToken(reserveToken) != poolToken) {
                          revert InvalidPool();
                      }
                  }
                  // transfer the pool tokens from the provider (we aren't using safeTransferFrom, since the PoolToken is a fully
                  // compliant ERC20 token contract)
                  poolToken.transferFrom(provider, address(_pendingWithdrawals), poolTokenAmount);
                  return _pendingWithdrawals.initWithdrawal(provider, poolToken, poolTokenAmount);
              }
              /**
               * @dev grants/revokes required roles to/from a pool collection
               */
              function _setAccessRoles(IPoolCollection poolCollection, bool set) private {
                  address poolCollectionAddress = address(poolCollection);
                  if (set) {
                      _bntPool.grantRole(ROLE_BNT_MANAGER, poolCollectionAddress);
                      _bntPool.grantRole(ROLE_VAULT_MANAGER, poolCollectionAddress);
                      _bntPool.grantRole(ROLE_FUNDING_MANAGER, poolCollectionAddress);
                      _masterVault.grantRole(ROLE_ASSET_MANAGER, poolCollectionAddress);
                      _externalProtectionVault.grantRole(ROLE_ASSET_MANAGER, poolCollectionAddress);
                  } else {
                      _bntPool.revokeRole(ROLE_BNT_MANAGER, poolCollectionAddress);
                      _bntPool.revokeRole(ROLE_VAULT_MANAGER, poolCollectionAddress);
                      _bntPool.revokeRole(ROLE_FUNDING_MANAGER, poolCollectionAddress);
                      _masterVault.revokeRole(ROLE_ASSET_MANAGER, poolCollectionAddress);
                      _externalProtectionVault.revokeRole(ROLE_ASSET_MANAGER, poolCollectionAddress);
                  }
              }
              /*
               * @dev finds a pool collection with the given type and version
               */
              function _findPoolCollection(uint16 poolType, uint16 poolVersion) private view returns (IPoolCollection) {
                  // note that there's no risk of using an unbounded loop here since the list of all the active pool collections
                  // is always going to remain sufficiently small
                  uint256 length = _poolCollections.length();
                  for (uint256 i = 0; i < length; i++) {
                      IPoolCollection poolCollection = IPoolCollection(_poolCollections.at(i));
                      if ((poolCollection.poolType() == poolType && poolCollection.version() == poolVersion)) {
                          return poolCollection;
                      }
                  }
                  return IPoolCollection(address(0));
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { IUpgradeable } from "../../utility/interfaces/IUpgradeable.sol";
          import { Token } from "../../token/Token.sol";
          import { IPoolCollection } from "../../pools/interfaces/IPoolCollection.sol";
          import { IPoolToken } from "../../pools/interfaces/IPoolToken.sol";
          /**
           * @dev Flash-loan recipient interface
           */
          interface IFlashLoanRecipient {
              /**
               * @dev a flash-loan recipient callback after each the caller must return the borrowed amount and an additional fee
               */
              function onFlashLoan(
                  address caller,
                  IERC20 erc20Token,
                  uint256 amount,
                  uint256 feeAmount,
                  bytes memory data
              ) external;
          }
          /**
           * @dev Bancor Network interface
           */
          interface IBancorNetwork is IUpgradeable {
              /**
               * @dev returns the set of all valid pool collections
               */
              function poolCollections() external view returns (IPoolCollection[] memory);
              /**
               * @dev returns the set of all liquidity pools
               */
              function liquidityPools() external view returns (Token[] memory);
              /**
               * @dev returns the respective pool collection for the provided pool
               */
              function collectionByPool(Token pool) external view returns (IPoolCollection);
              /**
               * @dev creates new pools
               *
               * requirements:
               *
               * - none of the pools already exists
               */
              function createPools(Token[] calldata tokens, IPoolCollection poolCollection) external;
              /**
               * @dev migrates a list of pools between pool collections
               *
               * notes:
               *
               * - invalid or incompatible pools will be skipped gracefully
               */
              function migratePools(Token[] calldata pools, IPoolCollection newPoolCollection) external;
              /**
               * @dev deposits liquidity for the specified provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the tokens on its behalf (except for in the
               *   native token case)
               */
              function depositFor(
                  address provider,
                  Token pool,
                  uint256 tokenAmount
              ) external payable returns (uint256);
              /**
               * @dev deposits liquidity for the current provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the tokens on its behalf (except for in the
               *   native token case)
               */
              function deposit(Token pool, uint256 tokenAmount) external payable returns (uint256);
              /**
               * @dev initiates liquidity withdrawal
               *
               * requirements:
               *
               * - the caller must have approved the contract to transfer the pool token amount on its behalf
               */
              function initWithdrawal(IPoolToken poolToken, uint256 poolTokenAmount) external returns (uint256);
              /**
               * @dev cancels a withdrawal request, and returns the number of pool token amount associated with the withdrawal
               * request
               *
               * requirements:
               *
               * - the caller must have already initiated a withdrawal and received the specified id
               */
              function cancelWithdrawal(uint256 id) external returns (uint256);
              /**
               * @dev withdraws liquidity and returns the withdrawn amount
               *
               * requirements:
               *
               * - the provider must have already initiated a withdrawal and received the specified id
               * - the specified withdrawal request is eligible for completion
               * - the provider must have approved the network to transfer vBNT amount on its behalf, when withdrawing BNT
               * liquidity
               */
              function withdraw(uint256 id) external returns (uint256);
              /**
               * @dev performs a trade by providing the input source amount, sends the proceeds to the optional beneficiary (or
               * to the address of the caller, in case it's not supplied), and returns the trade target amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the source tokens on its behalf (except for in the
               *   native token case)
               */
              function tradeBySourceAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable returns (uint256);
              /**
               * @dev performs a trade by providing the output target amount, sends the proceeds to the optional beneficiary (or
               * to the address of the caller, in case it's not supplied), and returns the trade source amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the source tokens on its behalf (except for in the
               *   native token case)
               */
              function tradeByTargetAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable returns (uint256);
              /**
               * @dev performs a trade by providing the input source amount, sends the proceeds to the optional beneficiary (or
               * to the address of the caller, in case it's not supplied), and returns the trade target amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the source tokens on its behalf (except for in the
               *   native token case)
               * - the caller must be the _bancorArbitrage contract
               */
              function tradeBySourceAmountArb(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable returns (uint256);
              /**
               * @dev performs a trade by providing the output target amount, sends the proceeds to the optional beneficiary (or
               * to the address of the caller, in case it's not supplied), and returns the trade source amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the source tokens on its behalf (except for in the
               *   native token case)
               * - the caller must be the _bancorArbitrage contract
               */
              function tradeByTargetAmountArb(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable returns (uint256);
              /**
               * @dev provides a flash-loan
               *
               * requirements:
               *
               * - the recipient's callback must return *at least* the borrowed amount and fee back to the specified return address
               */
              function flashLoan(
                  Token token,
                  uint256 amount,
                  IFlashLoanRecipient recipient,
                  bytes calldata data
              ) external;
              /**
               * @dev deposits liquidity during a migration
               */
              function migrateLiquidity(
                  Token token,
                  address provider,
                  uint256 amount,
                  uint256 availableAmount,
                  uint256 originalAmount
              ) external payable;
              /**
               * @dev withdraws pending network fees, and returns the amount of fees withdrawn
               *
               * requirements:
               *
               * - the caller must have the ROLE_NETWORK_FEE_MANAGER privilege
               */
              function withdrawNetworkFees(address recipient) external returns (uint256);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IUpgradeable } from "../../utility/interfaces/IUpgradeable.sol";
          import { Token } from "../../token/Token.sol";
          error NotWhitelisted();
          struct VortexRewards {
              // the percentage of converted BNT to be sent to the initiator of the burning event (in units of PPM)
              uint32 burnRewardPPM;
              // the maximum burn reward to be sent to the initiator of the burning event
              uint256 burnRewardMaxAmount;
          }
          /**
           * @dev Network Settings interface
           */
          interface INetworkSettings is IUpgradeable {
              /**
               * @dev returns the protected tokens whitelist
               */
              function protectedTokenWhitelist() external view returns (Token[] memory);
              /**
               * @dev checks whether a given token is whitelisted
               */
              function isTokenWhitelisted(Token pool) external view returns (bool);
              /**
               * @dev returns the BNT funding limit for a given pool
               */
              function poolFundingLimit(Token pool) external view returns (uint256);
              /**
               * @dev returns the minimum BNT trading liquidity required before the system enables trading in the relevant pool
               */
              function minLiquidityForTrading() external view returns (uint256);
              /**
               * @dev returns the withdrawal fee (in units of PPM)
               */
              function withdrawalFeePPM() external view returns (uint32);
              /**
               * @dev returns the default flash-loan fee (in units of PPM)
               */
              function defaultFlashLoanFeePPM() external view returns (uint32);
              /**
               * @dev returns the flash-loan fee (in units of PPM) of a pool
               */
              function flashLoanFeePPM(Token pool) external view returns (uint32);
              /**
               * @dev returns the vortex settings
               */
              function vortexRewards() external view returns (VortexRewards memory);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IPoolToken } from "../../pools/interfaces/IPoolToken.sol";
          import { Token } from "../../token/Token.sol";
          import { IUpgradeable } from "../../utility/interfaces/IUpgradeable.sol";
          /**
           * @dev the data struct representing a pending withdrawal request
           */
          struct WithdrawalRequest {
              address provider; // the liquidity provider
              IPoolToken poolToken; // the locked pool token
              Token reserveToken; // the reserve token to withdraw
              uint32 createdAt; // the time when the request was created (Unix timestamp)
              uint256 poolTokenAmount; // the locked pool token amount
              uint256 reserveTokenAmount; // the expected reserve token amount to withdraw
          }
          /**
           * @dev the data struct representing a completed withdrawal request
           */
          struct CompletedWithdrawal {
              IPoolToken poolToken; // the withdraw pool token
              uint256 poolTokenAmount; // the original pool token amount in the withdrawal request
              uint256 reserveTokenAmount; // the original reserve token amount at the time of the withdrawal init request
          }
          /**
           * @dev Pending Withdrawals interface
           */
          interface IPendingWithdrawals is IUpgradeable {
              /**
               * @dev returns the lock duration
               */
              function lockDuration() external view returns (uint32);
              /**
               * @dev returns the pending withdrawal requests count for a specific provider
               */
              function withdrawalRequestCount(address provider) external view returns (uint256);
              /**
               * @dev returns the pending withdrawal requests IDs for a specific provider
               */
              function withdrawalRequestIds(address provider) external view returns (uint256[] memory);
              /**
               * @dev returns the pending withdrawal request with the specified ID
               */
              function withdrawalRequest(uint256 id) external view returns (WithdrawalRequest memory);
              /**
               * @dev initiates liquidity withdrawal
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function initWithdrawal(
                  address provider,
                  IPoolToken poolToken,
                  uint256 poolTokenAmount
              ) external returns (uint256);
              /**
               * @dev cancels a withdrawal request, and returns the number of pool tokens which were sent back to the provider
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the provider must have already initiated a withdrawal and received the specified id
               */
              function cancelWithdrawal(address provider, uint256 id) external returns (uint256);
              /**
               * @dev completes a withdrawal request, and returns the pool token and its transferred amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the provider must have already initiated a withdrawal and received the specified id
               * - the lock duration has ended
               */
              function completeWithdrawal(
                  bytes32 contextId,
                  address provider,
                  uint256 id
              ) external returns (CompletedWithdrawal memory);
              /**
               * @dev returns whether the given request is ready for withdrawal
               */
              function isReadyForWithdrawal(uint256 id) external view returns (bool);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IPoolToken } from "./IPoolToken.sol";
          import { Token } from "../../token/Token.sol";
          import { IVault } from "../../vaults/interfaces/IVault.sol";
          // the BNT pool token manager role is required to access the BNT pool tokens
          bytes32 constant ROLE_BNT_POOL_TOKEN_MANAGER = keccak256("ROLE_BNT_POOL_TOKEN_MANAGER");
          // the BNT manager role is required to request the BNT pool to mint BNT
          bytes32 constant ROLE_BNT_MANAGER = keccak256("ROLE_BNT_MANAGER");
          // the vault manager role is required to request the BNT pool to burn BNT from the master vault
          bytes32 constant ROLE_VAULT_MANAGER = keccak256("ROLE_VAULT_MANAGER");
          // the funding manager role is required to request or renounce funding from the BNT pool
          bytes32 constant ROLE_FUNDING_MANAGER = keccak256("ROLE_FUNDING_MANAGER");
          /**
           * @dev BNT Pool interface
           */
          interface IBNTPool is IVault {
              /**
               * @dev returns the BNT pool token contract
               */
              function poolToken() external view returns (IPoolToken);
              /**
               * @dev returns the total staked BNT balance in the network
               */
              function stakedBalance() external view returns (uint256);
              /**
               * @dev returns the current funding of given pool
               */
              function currentPoolFunding(Token pool) external view returns (uint256);
              /**
               * @dev returns the available BNT funding for a given pool
               */
              function availableFunding(Token pool) external view returns (uint256);
              /**
               * @dev converts the specified pool token amount to the underlying BNT amount
               */
              function poolTokenToUnderlying(uint256 poolTokenAmount) external view returns (uint256);
              /**
               * @dev converts the specified underlying BNT amount to pool token amount
               */
              function underlyingToPoolToken(uint256 bntAmount) external view returns (uint256);
              /**
               * @dev returns the number of pool token to burn in order to increase everyone's underlying value by the specified
               * amount
               */
              function poolTokenAmountToBurn(uint256 bntAmountToDistribute) external view returns (uint256);
              /**
               * @dev mints BNT to the recipient
               *
               * requirements:
               *
               * - the caller must have the ROLE_BNT_MANAGER role
               */
              function mint(address recipient, uint256 bntAmount) external;
              /**
               * @dev burns BNT from the vault
               *
               * requirements:
               *
               * - the caller must have the ROLE_VAULT_MANAGER role
               */
              function burnFromVault(uint256 bntAmount) external;
              /**
               * @dev deposits BNT liquidity on behalf of a specific provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - BNT tokens must have been already deposited into the contract
               */
              function depositFor(
                  bytes32 contextId,
                  address provider,
                  uint256 bntAmount,
                  bool isMigrating,
                  uint256 originalVBNTAmount
              ) external returns (uint256);
              /**
               * @dev withdraws BNT liquidity on behalf of a specific provider and returns the withdrawn BNT amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - bnBNT token must have been already deposited into the contract
               * - vBNT token must have been already deposited into the contract
               */
              function withdraw(
                  bytes32 contextId,
                  address provider,
                  uint256 poolTokenAmount,
                  uint256 bntAmount
              ) external returns (uint256);
              /**
               * @dev returns the withdrawn BNT amount
               */
              function withdrawalAmount(uint256 poolTokenAmount) external view returns (uint256);
              /**
               * @dev requests BNT funding
               *
               * requirements:
               *
               * - the caller must have the ROLE_FUNDING_MANAGER role
               * - the token must have been whitelisted
               * - the request amount should be below the funding limit for a given pool
               * - the average rate of the pool must not deviate too much from its spot rate
               */
              function requestFunding(
                  bytes32 contextId,
                  Token pool,
                  uint256 bntAmount
              ) external;
              /**
               * @dev renounces BNT funding
               *
               * requirements:
               *
               * - the caller must have the ROLE_FUNDING_MANAGER role
               * - the token must have been whitelisted
               * - the average rate of the pool must not deviate too much from its spot rate
               */
              function renounceFunding(
                  bytes32 contextId,
                  Token pool,
                  uint256 bntAmount
              ) external;
              /**
               * @dev notifies the pool of accrued fees
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function onFeesCollected(
                  Token pool,
                  uint256 feeAmount,
                  bool isTradeFee
              ) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IVersioned } from "../../utility/interfaces/IVersioned.sol";
          import { Fraction112 } from "../../utility/FractionLibrary.sol";
          import { Token } from "../../token/Token.sol";
          import { IPoolToken } from "./IPoolToken.sol";
          struct PoolLiquidity {
              uint128 bntTradingLiquidity; // the BNT trading liquidity
              uint128 baseTokenTradingLiquidity; // the base token trading liquidity
              uint256 stakedBalance; // the staked balance
          }
          struct AverageRates {
              uint32 blockNumber;
              Fraction112 rate;
              Fraction112 invRate;
          }
          struct Pool {
              IPoolToken poolToken; // the pool token of the pool
              uint32 tradingFeePPM; // the trading fee (in units of PPM)
              bool tradingEnabled; // whether trading is enabled
              bool depositingEnabled; // whether depositing is enabled
              AverageRates averageRates; // the recent average rates
              PoolLiquidity liquidity; // the overall liquidity in the pool
          }
          struct WithdrawalAmounts {
              uint256 totalAmount;
              uint256 baseTokenAmount;
              uint256 bntAmount;
          }
          // trading enabling/disabling reasons
          uint8 constant TRADING_STATUS_UPDATE_DEFAULT = 0;
          uint8 constant TRADING_STATUS_UPDATE_ADMIN = 1;
          uint8 constant TRADING_STATUS_UPDATE_MIN_LIQUIDITY = 2;
          uint8 constant TRADING_STATUS_UPDATE_INVALID_STATE = 3;
          struct TradeAmountAndFee {
              uint256 amount; // the source/target amount (depending on the context) resulting from the trade
              uint256 tradingFeeAmount; // the trading fee amount
              uint256 networkFeeAmount; // the network fee amount (always in units of BNT)
          }
          /**
           * @dev Pool Collection interface
           */
          interface IPoolCollection is IVersioned {
              /**
               * @dev returns the type of the pool
               */
              function poolType() external view returns (uint16);
              /**
               * @dev returns the default trading fee (in units of PPM)
               */
              function defaultTradingFeePPM() external view returns (uint32);
              /**
               * @dev returns the network fee (in units of PPM)
               */
              function networkFeePPM() external view returns (uint32);
              /**
               * @dev returns all the pools which are managed by this pool collection
               */
              function pools() external view returns (Token[] memory);
              /**
               * @dev returns the number of all the pools which are managed by this pool collection
               */
              function poolCount() external view returns (uint256);
              /**
               * @dev returns whether a pool is valid
               */
              function isPoolValid(Token pool) external view returns (bool);
              /**
               * @dev returns the overall liquidity in the pool
               */
              function poolLiquidity(Token pool) external view returns (PoolLiquidity memory);
              /**
               * @dev returns the pool token of the pool
               */
              function poolToken(Token pool) external view returns (IPoolToken);
              /**
               * @dev returns the trading fee (in units of PPM)
               */
              function tradingFeePPM(Token pool) external view returns (uint32);
              /**
               * @dev returns whether trading is enabled
               */
              function tradingEnabled(Token pool) external view returns (bool);
              /**
               * @dev returns whether depositing is enabled
               */
              function depositingEnabled(Token pool) external view returns (bool);
              /**
               * @dev returns whether the pool is stable
               */
              function isPoolStable(Token pool) external view returns (bool);
              /**
               * @dev converts the specified pool token amount to the underlying base token amount
               */
              function poolTokenToUnderlying(Token pool, uint256 poolTokenAmount) external view returns (uint256);
              /**
               * @dev converts the specified underlying base token amount to pool token amount
               */
              function underlyingToPoolToken(Token pool, uint256 baseTokenAmount) external view returns (uint256);
              /**
               * @dev returns the number of pool token to burn in order to increase everyone's underlying value by the specified
               * amount
               */
              function poolTokenAmountToBurn(
                  Token pool,
                  uint256 baseTokenAmountToDistribute,
                  uint256 protocolPoolTokenAmount
              ) external view returns (uint256);
              /**
               * @dev creates a new pool
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the pool should have been whitelisted
               * - the pool isn't already defined in the collection
               */
              function createPool(Token token) external;
              /**
               * @dev deposits base token liquidity on behalf of a specific provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - assumes that the base token has been already deposited in the vault
               */
              function depositFor(
                  bytes32 contextId,
                  address provider,
                  Token pool,
                  uint256 baseTokenAmount
              ) external returns (uint256);
              /**
               * @dev handles some of the withdrawal-related actions and returns the withdrawn base token amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the caller must have approved the collection to transfer/burn the pool token amount on its behalf
               */
              function withdraw(
                  bytes32 contextId,
                  address provider,
                  Token pool,
                  uint256 poolTokenAmount,
                  uint256 baseTokenAmount
              ) external returns (uint256);
              /**
               * @dev returns the amounts that would be returned if the position is currently withdrawn,
               * along with the breakdown of the base token and the BNT compensation
               */
              function withdrawalAmounts(Token pool, uint256 poolTokenAmount) external view returns (WithdrawalAmounts memory);
              /**
               * @dev performs a trade by providing the source amount and returns the target amount and the associated fee
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function tradeBySourceAmount(
                  bytes32 contextId,
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  bool ignoreFees
              ) external returns (TradeAmountAndFee memory);
              /**
               * @dev performs a trade by providing the target amount and returns the required source amount and the associated fee
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function tradeByTargetAmount(
                  bytes32 contextId,
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  bool ignoreFees
              ) external returns (TradeAmountAndFee memory);
              /**
               * @dev returns the output amount and fee when trading by providing the source amount
               */
              function tradeOutputAndFeeBySourceAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount
              ) external view returns (TradeAmountAndFee memory);
              /**
               * @dev returns the input amount and fee when trading by providing the target amount
               */
              function tradeInputAndFeeByTargetAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount
              ) external view returns (TradeAmountAndFee memory);
              /**
               * @dev notifies the pool of accrued fees
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function onFeesCollected(Token pool, uint256 feeAmount) external;
              /**
               * @dev migrates a pool to this pool collection
               *
               * requirements:
               *
               * - the caller must be the pool migrator contract
               */
              function migratePoolIn(Token pool, Pool calldata data) external;
              /**
               * @dev migrates a pool from this pool collection
               *
               * requirements:
               *
               * - the caller must be the pool migrator contract
               */
              function migratePoolOut(Token pool, IPoolCollection targetPoolCollection) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { Token } from "../../token/Token.sol";
          import { IVersioned } from "../../utility/interfaces/IVersioned.sol";
          import { IPoolCollection } from "./IPoolCollection.sol";
          /**
           * @dev Pool Migrator interface
           */
          interface IPoolMigrator is IVersioned {
              /**
               * @dev migrates a pool and returns the new pool collection it exists in
               *
               * notes:
               *
               * - invalid or incompatible pools will be skipped gracefully
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function migratePool(Token pool, IPoolCollection newPoolCollection) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
          import { IERC20Burnable } from "../../token/interfaces/IERC20Burnable.sol";
          import { Token } from "../../token/Token.sol";
          import { IVersioned } from "../../utility/interfaces/IVersioned.sol";
          import { IOwned } from "../../utility/interfaces/IOwned.sol";
          /**
           * @dev Pool Token interface
           */
          interface IPoolToken is IVersioned, IOwned, IERC20, IERC20Permit, IERC20Burnable {
              /**
               * @dev returns the address of the reserve token
               */
              function reserveToken() external view returns (Token);
              /**
               * @dev increases the token supply and sends the new tokens to the given account
               *
               * requirements:
               *
               * - the caller must be the owner of the contract
               */
              function mint(address recipient, uint256 amount) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          /**
           * @dev extends the SafeERC20 library with additional operations
           */
          library SafeERC20Ex {
              using SafeERC20 for IERC20;
              /**
               * @dev ensures that the spender has sufficient allowance
               */
              function ensureApprove(IERC20 token, address spender, uint256 amount) internal {
                  if (amount == 0) {
                      return;
                  }
                  uint256 allowance = token.allowance(address(this), spender);
                  if (allowance >= amount) {
                      return;
                  }
                  if (allowance > 0) {
                      token.safeApprove(spender, 0);
                  }
                  token.safeApprove(spender, amount);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev the main purpose of the Token interfaces is to ensure artificially that we won't use ERC20's standard functions,
           * but only their safe versions, which are provided by SafeERC20 and SafeERC20Ex via the TokenLibrary contract
           */
          interface Token {
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
          import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
          import { SafeERC20Ex } from "./SafeERC20Ex.sol";
          import { Token } from "./Token.sol";
          /**
           * @dev This library implements ERC20 and SafeERC20 utilities for both the native token and for ERC20 tokens
           */
          library TokenLibrary {
              using SafeERC20 for IERC20;
              using SafeERC20Ex for IERC20;
              error PermitUnsupported();
              // the address that represents the native token reserve
              address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
              // the symbol that represents the native token
              string private constant NATIVE_TOKEN_SYMBOL = "ETH";
              // the decimals for the native token
              uint8 private constant NATIVE_TOKEN_DECIMALS = 18;
              // the token representing the native token
              Token public constant NATIVE_TOKEN = Token(NATIVE_TOKEN_ADDRESS);
              /**
               * @dev returns whether the provided token represents an ERC20 or the native token reserve
               */
              function isNative(Token token) internal pure returns (bool) {
                  return address(token) == NATIVE_TOKEN_ADDRESS;
              }
              /**
               * @dev returns the symbol of the native token/ERC20 token
               */
              function symbol(Token token) internal view returns (string memory) {
                  if (isNative(token)) {
                      return NATIVE_TOKEN_SYMBOL;
                  }
                  return toERC20(token).symbol();
              }
              /**
               * @dev returns the decimals of the native token/ERC20 token
               */
              function decimals(Token token) internal view returns (uint8) {
                  if (isNative(token)) {
                      return NATIVE_TOKEN_DECIMALS;
                  }
                  return toERC20(token).decimals();
              }
              /**
               * @dev returns the balance of the native token/ERC20 token
               */
              function balanceOf(Token token, address account) internal view returns (uint256) {
                  if (isNative(token)) {
                      return account.balance;
                  }
                  return toIERC20(token).balanceOf(account);
              }
              /**
               * @dev transfers a specific amount of the native token/ERC20 token
               */
              function safeTransfer(Token token, address to, uint256 amount) internal {
                  if (amount == 0) {
                      return;
                  }
                  if (isNative(token)) {
                      payable(to).transfer(amount);
                  } else {
                      toIERC20(token).safeTransfer(to, amount);
                  }
              }
              /**
               * @dev transfers a specific amount of the native token/ERC20 token from a specific holder using the allowance mechanism
               *
               * note that the function does not perform any action if the native token is provided
               */
              function safeTransferFrom(Token token, address from, address to, uint256 amount) internal {
                  if (amount == 0 || isNative(token)) {
                      return;
                  }
                  toIERC20(token).safeTransferFrom(from, to, amount);
              }
              /**
               * @dev approves a specific amount of the native token/ERC20 token from a specific holder
               *
               * note that the function does not perform any action if the native token is provided
               */
              function safeApprove(Token token, address spender, uint256 amount) internal {
                  if (isNative(token)) {
                      return;
                  }
                  toIERC20(token).safeApprove(spender, amount);
              }
              /**
               * @dev increases allowance of the native token/ERC20 token from a specific holder
               *
               * note that the function does not perform any action if the native token is provided
               */
              function safeIncreaseAllowance(Token token, address spender, uint256 amount) internal {
                  if (isNative(token)) {
                      return;
                  }
                  toIERC20(token).safeIncreaseAllowance(spender, amount);
              }
              /**
               * @dev ensures that the spender has sufficient allowance
               *
               * note that the function does not perform any action if the native token is provided
               */
              function ensureApprove(Token token, address spender, uint256 amount) internal {
                  if (isNative(token)) {
                      return;
                  }
                  toIERC20(token).ensureApprove(spender, amount);
              }
              /**
               * @dev compares between a token and another raw ERC20 token
               */
              function isEqual(Token token, IERC20 erc20Token) internal pure returns (bool) {
                  return toIERC20(token) == erc20Token;
              }
              /**
               * @dev utility function that converts a token to an IERC20
               */
              function toIERC20(Token token) internal pure returns (IERC20) {
                  return IERC20(address(token));
              }
              /**
               * @dev utility function that converts a token to an ERC20
               */
              function toERC20(Token token) internal pure returns (ERC20) {
                  return ERC20(address(token));
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev burnable ERC20 interface
           */
          interface IERC20Burnable {
              /**
               * @dev Destroys tokens from the caller.
               */
              function burn(uint256 amount) external;
              /**
               * @dev Destroys tokens from a recipient, deducting from the caller's allowance
               *
               * requirements:
               *
               * - the caller must have allowance for recipient's tokens of at least the specified amount
               */
              function burnFrom(address recipient, uint256 amount) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          uint32 constant PPM_RESOLUTION = 1_000_000;
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          struct Fraction {
              uint256 n;
              uint256 d;
          }
          struct Fraction112 {
              uint112 n;
              uint112 d;
          }
          error InvalidFraction();
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { Fraction, Fraction112, InvalidFraction } from "./Fraction.sol";
          import { MathEx } from "./MathEx.sol";
          // solhint-disable-next-line func-visibility
          function zeroFraction() pure returns (Fraction memory) {
              return Fraction({ n: 0, d: 1 });
          }
          // solhint-disable-next-line func-visibility
          function zeroFraction112() pure returns (Fraction112 memory) {
              return Fraction112({ n: 0, d: 1 });
          }
          /**
           * @dev this library provides a set of fraction operations
           */
          library FractionLibrary {
              /**
               * @dev returns whether a standard fraction is valid
               */
              function isValid(Fraction memory fraction) internal pure returns (bool) {
                  return fraction.d != 0;
              }
              /**
               * @dev returns whether a 112-bit fraction is valid
               */
              function isValid(Fraction112 memory fraction) internal pure returns (bool) {
                  return fraction.d != 0;
              }
              /**
               * @dev returns whether a standard fraction is positive
               */
              function isPositive(Fraction memory fraction) internal pure returns (bool) {
                  return isValid(fraction) && fraction.n != 0;
              }
              /**
               * @dev returns whether a 112-bit fraction is positive
               */
              function isPositive(Fraction112 memory fraction) internal pure returns (bool) {
                  return isValid(fraction) && fraction.n != 0;
              }
              /**
               * @dev returns the inverse of a given fraction
               */
              function inverse(Fraction memory fraction) internal pure returns (Fraction memory) {
                  Fraction memory invFraction = Fraction({ n: fraction.d, d: fraction.n });
                  if (!isValid(invFraction)) {
                      revert InvalidFraction();
                  }
                  return invFraction;
              }
              /**
               * @dev returns the inverse of a given fraction
               */
              function inverse(Fraction112 memory fraction) internal pure returns (Fraction112 memory) {
                  Fraction112 memory invFraction = Fraction112({ n: fraction.d, d: fraction.n });
                  if (!isValid(invFraction)) {
                      revert InvalidFraction();
                  }
                  return invFraction;
              }
              /**
               * @dev reduces a standard fraction to a 112-bit fraction
               */
              function toFraction112(Fraction memory fraction) internal pure returns (Fraction112 memory) {
                  Fraction memory truncatedFraction = MathEx.truncatedFraction(fraction, type(uint112).max);
                  return Fraction112({ n: uint112(truncatedFraction.n), d: uint112(truncatedFraction.d) });
              }
              /**
               * @dev expands a 112-bit fraction to a standard fraction
               */
              function fromFraction112(Fraction112 memory fraction) internal pure returns (Fraction memory) {
                  return Fraction({ n: fraction.n, d: fraction.d });
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
          import { Fraction, InvalidFraction } from "./Fraction.sol";
          import { PPM_RESOLUTION } from "./Constants.sol";
          uint256 constant ONE = 0x80000000000000000000000000000000;
          uint256 constant LN2 = 0x58b90bfbe8e7bcd5e4f1d9cc01f97b57;
          struct Uint512 {
              uint256 hi; // 256 most significant bits
              uint256 lo; // 256 least significant bits
          }
          struct Sint256 {
              uint256 value;
              bool isNeg;
          }
          /**
           * @dev this library provides a set of complex math operations
           */
          library MathEx {
              error Overflow();
              /**
               * @dev returns `2 ^ f` by calculating `e ^ (f * ln(2))`, where `e` is Euler's number:
               * - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible
               * - The exponentiation of each binary exponent is given (pre-calculated)
               * - The exponentiation of r is calculated via Taylor series for e^x, where x = r
               * - The exponentiation of the input is calculated by multiplying the intermediate results above
               * - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859
               */
              function exp2(Fraction memory f) internal pure returns (Fraction memory) {
                  uint256 x = MathEx.mulDivF(LN2, f.n, f.d);
                  uint256 y;
                  uint256 z;
                  uint256 n;
                  if (x >= (ONE << 4)) {
                      revert Overflow();
                  }
                  unchecked {
                      z = y = x % (ONE >> 3); // get the input modulo 2^(-3)
                      z = (z * y) / ONE;
                      n += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
                      z = (z * y) / ONE;
                      n += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
                      z = (z * y) / ONE;
                      n += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
                      z = (z * y) / ONE;
                      n += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
                      z = (z * y) / ONE;
                      n += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
                      z = (z * y) / ONE;
                      n += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
                      z = (z * y) / ONE;
                      n += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
                      z = (z * y) / ONE;
                      n += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
                      z = (z * y) / ONE;
                      n += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
                      z = (z * y) / ONE;
                      n += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
                      z = (z * y) / ONE;
                      n += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
                      z = (z * y) / ONE;
                      n += z * 0x000000000001c638; // add y^16 * (20! / 16!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
                      z = (z * y) / ONE;
                      n += z * 0x000000000000017c; // add y^18 * (20! / 18!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000000000014; // add y^19 * (20! / 19!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000000000001; // add y^20 * (20! / 20!)
                      n = n / 0x21c3677c82b40000 + y + ONE; // divide by 20! and then add y^1 / 1! + y^0 / 0!
                      if ((x & (ONE >> 3)) != 0)
                          n = (n * 0x1c3d6a24ed82218787d624d3e5eba95f9) / 0x18ebef9eac820ae8682b9793ac6d1e776; // multiply by e^(2^-3)
                      if ((x & (ONE >> 2)) != 0)
                          n = (n * 0x18ebef9eac820ae8682b9793ac6d1e778) / 0x1368b2fc6f9609fe7aceb46aa619baed4; // multiply by e^(2^-2)
                      if ((x & (ONE >> 1)) != 0)
                          n = (n * 0x1368b2fc6f9609fe7aceb46aa619baed5) / 0x0bc5ab1b16779be3575bd8f0520a9f21f; // multiply by e^(2^-1)
                      if ((x & (ONE << 0)) != 0)
                          n = (n * 0x0bc5ab1b16779be3575bd8f0520a9f21e) / 0x0454aaa8efe072e7f6ddbab84b40a55c9; // multiply by e^(2^+0)
                      if ((x & (ONE << 1)) != 0)
                          n = (n * 0x0454aaa8efe072e7f6ddbab84b40a55c5) / 0x00960aadc109e7a3bf4578099615711ea; // multiply by e^(2^+1)
                      if ((x & (ONE << 2)) != 0)
                          n = (n * 0x00960aadc109e7a3bf4578099615711d7) / 0x0002bf84208204f5977f9a8cf01fdce3d; // multiply by e^(2^+2)
                      if ((x & (ONE << 3)) != 0)
                          n = (n * 0x0002bf84208204f5977f9a8cf01fdc307) / 0x0000003c6ab775dd0b95b4cbee7e65d11; // multiply by e^(2^+3)
                  }
                  return Fraction({ n: n, d: ONE });
              }
              /**
               * @dev returns a fraction with truncated components
               * note that since the input value is truncated, the use of the method incurs precision loss
               */
              function truncatedFraction(Fraction memory fraction, uint256 max) internal pure returns (Fraction memory) {
                  uint256 scale = Math.ceilDiv(Math.max(fraction.n, fraction.d), max);
                  Fraction memory truncated = Fraction({ n: fraction.n / scale, d: fraction.d / scale });
                  if (truncated.d == 0) {
                      revert InvalidFraction();
                  }
                  return truncated;
              }
              /**
               * @dev returns the weighted average of two fractions
               */
              function weightedAverage(
                  Fraction memory fraction1,
                  Fraction memory fraction2,
                  uint256 weight1,
                  uint256 weight2
              ) internal pure returns (Fraction memory) {
                  return
                      Fraction({
                          n: fraction1.n * fraction2.d * weight1 + fraction1.d * fraction2.n * weight2,
                          d: fraction1.d * fraction2.d * (weight1 + weight2)
                      });
              }
              /**
               * @dev returns whether or not the deviation of an offset sample from a base sample is within a permitted range
               * for example, if the maximum permitted deviation is 5%, then evaluate `95% * base <= offset <= 105% * base`
               */
              function isInRange(
                  Fraction memory baseSample,
                  Fraction memory offsetSample,
                  uint32 maxDeviationPPM
              ) internal pure returns (bool) {
                  Uint512 memory min = mul512(baseSample.n, offsetSample.d * (PPM_RESOLUTION - maxDeviationPPM));
                  Uint512 memory mid = mul512(baseSample.d, offsetSample.n * PPM_RESOLUTION);
                  Uint512 memory max = mul512(baseSample.n, offsetSample.d * (PPM_RESOLUTION + maxDeviationPPM));
                  return lte512(min, mid) && lte512(mid, max);
              }
              /**
               * @dev returns an `Sint256` positive representation of an unsigned integer
               */
              function toPos256(uint256 n) internal pure returns (Sint256 memory) {
                  return Sint256({ value: n, isNeg: false });
              }
              /**
               * @dev returns an `Sint256` negative representation of an unsigned integer
               */
              function toNeg256(uint256 n) internal pure returns (Sint256 memory) {
                  return Sint256({ value: n, isNeg: true });
              }
              /**
               * @dev returns the largest integer smaller than or equal to `x * y / z`
               */
              function mulDivF(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) {
                  Uint512 memory xy = mul512(x, y);
                  // if `x * y < 2 ^ 256`
                  if (xy.hi == 0) {
                      return xy.lo / z;
                  }
                  // assert `x * y / z < 2 ^ 256`
                  if (xy.hi >= z) {
                      revert Overflow();
                  }
                  uint256 m = _mulMod(x, y, z); // `m = x * y % z`
                  Uint512 memory n = _sub512(xy, m); // `n = x * y - m` hence `n / z = floor(x * y / z)`
                  // if `n < 2 ^ 256`
                  if (n.hi == 0) {
                      return n.lo / z;
                  }
                  uint256 p = _unsafeSub(0, z) & z; // `p` is the largest power of 2 which `z` is divisible by
                  uint256 q = _div512(n, p); // `n` is divisible by `p` because `n` is divisible by `z` and `z` is divisible by `p`
                  uint256 r = _inv256(z / p); // `z / p = 1 mod 2` hence `inverse(z / p) = 1 mod 2 ^ 256`
                  return _unsafeMul(q, r); // `q * r = (n / p) * inverse(z / p) = n / z`
              }
              /**
               * @dev returns the smallest integer larger than or equal to `x * y / z`
               */
              function mulDivC(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) {
                  uint256 w = mulDivF(x, y, z);
                  if (_mulMod(x, y, z) > 0) {
                      if (w >= type(uint256).max) {
                          revert Overflow();
                      }
                      return w + 1;
                  }
                  return w;
              }
              /**
               * @dev returns the maximum of `n1 - n2` and 0
               */
              function subMax0(uint256 n1, uint256 n2) internal pure returns (uint256) {
                  return n1 > n2 ? n1 - n2 : 0;
              }
              /**
               * @dev returns the value of `x > y`
               */
              function gt512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return x.hi > y.hi || (x.hi == y.hi && x.lo > y.lo);
              }
              /**
               * @dev returns the value of `x < y`
               */
              function lt512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return x.hi < y.hi || (x.hi == y.hi && x.lo < y.lo);
              }
              /**
               * @dev returns the value of `x >= y`
               */
              function gte512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return !lt512(x, y);
              }
              /**
               * @dev returns the value of `x <= y`
               */
              function lte512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return !gt512(x, y);
              }
              /**
               * @dev returns the value of `x * y`
               */
              function mul512(uint256 x, uint256 y) internal pure returns (Uint512 memory) {
                  uint256 p = _mulModMax(x, y);
                  uint256 q = _unsafeMul(x, y);
                  if (p >= q) {
                      return Uint512({ hi: p - q, lo: q });
                  }
                  return Uint512({ hi: _unsafeSub(p, q) - 1, lo: q });
              }
              /**
               * @dev returns the value of `x - y`, given that `x >= y`
               */
              function _sub512(Uint512 memory x, uint256 y) private pure returns (Uint512 memory) {
                  if (x.lo >= y) {
                      return Uint512({ hi: x.hi, lo: x.lo - y });
                  }
                  return Uint512({ hi: x.hi - 1, lo: _unsafeSub(x.lo, y) });
              }
              /**
               * @dev returns the value of `x / pow2n`, given that `x` is divisible by `pow2n`
               */
              function _div512(Uint512 memory x, uint256 pow2n) private pure returns (uint256) {
                  uint256 pow2nInv = _unsafeAdd(_unsafeSub(0, pow2n) / pow2n, 1); // `1 << (256 - n)`
                  return _unsafeMul(x.hi, pow2nInv) | (x.lo / pow2n); // `(x.hi << (256 - n)) | (x.lo >> n)`
              }
              /**
               * @dev returns the inverse of `d` modulo `2 ^ 256`, given that `d` is congruent to `1` modulo `2`
               */
              function _inv256(uint256 d) private pure returns (uint256) {
                  // approximate the root of `f(x) = 1 / x - d` using the newton–raphson convergence method
                  uint256 x = 1;
                  for (uint256 i = 0; i < 8; i++) {
                      x = _unsafeMul(x, _unsafeSub(2, _unsafeMul(x, d))); // `x = x * (2 - x * d) mod 2 ^ 256`
                  }
                  return x;
              }
              /**
               * @dev returns `(x + y) % 2 ^ 256`
               */
              function _unsafeAdd(uint256 x, uint256 y) private pure returns (uint256) {
                  unchecked {
                      return x + y;
                  }
              }
              /**
               * @dev returns `(x - y) % 2 ^ 256`
               */
              function _unsafeSub(uint256 x, uint256 y) private pure returns (uint256) {
                  unchecked {
                      return x - y;
                  }
              }
              /**
               * @dev returns `(x * y) % 2 ^ 256`
               */
              function _unsafeMul(uint256 x, uint256 y) private pure returns (uint256) {
                  unchecked {
                      return x * y;
                  }
              }
              /**
               * @dev returns `x * y % (2 ^ 256 - 1)`
               */
              function _mulModMax(uint256 x, uint256 y) private pure returns (uint256) {
                  return mulmod(x, y, type(uint256).max);
              }
              /**
               * @dev returns `x * y % z`
               */
              function _mulMod(uint256 x, uint256 y, uint256 z) private pure returns (uint256) {
                  return mulmod(x, y, z);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev this contract abstracts the block timestamp in order to allow for more flexible control in tests
           */
          abstract contract Time {
              /**
               * @dev returns the current time
               */
              function _time() internal view virtual returns (uint32) {
                  return uint32(block.timestamp);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
          import { IUpgradeable } from "./interfaces/IUpgradeable.sol";
          import { AccessDenied } from "./Utils.sol";
          /**
           * @dev this contract provides common utilities for upgradeable contracts
           *
           * note that we're using the Transparent Upgradeable Proxy pattern and *not* the Universal Upgradeable Proxy Standard
           * (UUPS) pattern, therefore initializing the implementation contracts is not necessary or required
           */
          abstract contract Upgradeable is IUpgradeable, AccessControlEnumerableUpgradeable {
              error AlreadyInitialized();
              // the admin role is used to allow a non-proxy admin to perform additional initialization/setup during contract
              // upgrades
              bytes32 internal constant ROLE_ADMIN = keccak256("ROLE_ADMIN");
              uint32 internal constant MAX_GAP = 50;
              uint16 internal _initializations;
              // upgrade forward-compatibility storage gap
              uint256[MAX_GAP - 1] private __gap;
              // solhint-disable func-name-mixedcase
              /**
               * @dev initializes the contract and its parents
               */
              function __Upgradeable_init() internal onlyInitializing {
                  __AccessControl_init();
                  __Upgradeable_init_unchained();
              }
              /**
               * @dev performs contract-specific initialization
               */
              function __Upgradeable_init_unchained() internal onlyInitializing {
                  _initializations = 1;
                  // set up administrative roles
                  _setRoleAdmin(ROLE_ADMIN, ROLE_ADMIN);
                  // allow the deployer to initially be the admin of the contract
                  _setupRole(ROLE_ADMIN, msg.sender);
              }
              // solhint-enable func-name-mixedcase
              modifier onlyAdmin() {
                  _hasRole(ROLE_ADMIN, msg.sender);
                  _;
              }
              modifier onlyRoleMember(bytes32 role) {
                  _hasRole(role, msg.sender);
                  _;
              }
              function version() public view virtual override returns (uint16);
              /**
               * @dev returns the admin role
               */
              function roleAdmin() external pure returns (bytes32) {
                  return ROLE_ADMIN;
              }
              /**
               * @dev performs post-upgrade initialization
               *
               * requirements:
               *
               * - this must can be called only once per-upgrade
               */
              function postUpgrade(bytes calldata data) external {
                  uint16 initializations = _initializations + 1;
                  if (initializations != version()) {
                      revert AlreadyInitialized();
                  }
                  _initializations = initializations;
                  _postUpgrade(data);
              }
              /**
               * @dev an optional post-upgrade callback that can be implemented by child contracts
               */
              function _postUpgrade(bytes calldata /* data */) internal virtual {}
              function _hasRole(bytes32 role, address account) internal view {
                  if (!hasRole(role, account)) {
                      revert AccessDenied();
                  }
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { PPM_RESOLUTION } from "./Constants.sol";
          error AccessDenied();
          error AlreadyExists();
          error DoesNotExist();
          error InvalidAddress();
          error InvalidExternalAddress();
          error InvalidFee();
          error InvalidPool();
          error InvalidPoolCollection();
          error InvalidStakedBalance();
          error InvalidToken();
          error InvalidParam();
          error NotEmpty();
          error NotPayable();
          error ZeroValue();
          /**
           * @dev common utilities
           */
          abstract contract Utils {
              // allows execution by the caller only
              modifier only(address caller) {
                  _only(caller);
                  _;
              }
              function _only(address caller) internal view {
                  if (msg.sender != caller) {
                      revert AccessDenied();
                  }
              }
              // verifies that a value is greater than zero
              modifier greaterThanZero(uint256 value) {
                  _greaterThanZero(value);
                  _;
              }
              // error message binary size optimization
              function _greaterThanZero(uint256 value) internal pure {
                  if (value == 0) {
                      revert ZeroValue();
                  }
              }
              // validates an address - currently only checks that it isn't null
              modifier validAddress(address addr) {
                  _validAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validAddress(address addr) internal pure {
                  if (addr == address(0)) {
                      revert InvalidAddress();
                  }
              }
              // validates an external address - currently only checks that it isn't null or this
              modifier validExternalAddress(address addr) {
                  _validExternalAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validExternalAddress(address addr) internal view {
                  if (addr == address(0) || addr == address(this)) {
                      revert InvalidExternalAddress();
                  }
              }
              // ensures that the fee is valid
              modifier validFee(uint32 fee) {
                  _validFee(fee);
                  _;
              }
              // error message binary size optimization
              function _validFee(uint32 fee) internal pure {
                  if (fee > PPM_RESOLUTION) {
                      revert InvalidFee();
                  }
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev Owned interface
           */
          interface IOwned {
              /**
               * @dev returns the address of the current owner
               */
              function owner() external view returns (address);
              /**
               * @dev allows transferring the contract ownership
               *
               * requirements:
               *
               * - the caller must be the owner of the contract
               * - the new owner still needs to accept the transfer
               */
              function transferOwnership(address ownerCandidate) external;
              /**
               * @dev used by a new owner to accept an ownership transfer
               */
              function acceptOwnership() external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IVersioned } from "./IVersioned.sol";
          import { IAccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/IAccessControlEnumerableUpgradeable.sol";
          /**
           * @dev this is the common interface for upgradeable contracts
           */
          interface IUpgradeable is IAccessControlEnumerableUpgradeable, IVersioned {
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev an interface for a versioned contract
           */
          interface IVersioned {
              function version() external view returns (uint16);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IVault } from "./IVault.sol";
          interface IExternalProtectionVault is IVault {}
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IVault } from "./IVault.sol";
          interface IMasterVault is IVault {}
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IUpgradeable } from "../../utility/interfaces/IUpgradeable.sol";
          import { Token } from "../../token/Token.sol";
          // the asset manager role is required to access all the funds
          bytes32 constant ROLE_ASSET_MANAGER = keccak256("ROLE_ASSET_MANAGER");
          interface IVault is IUpgradeable {
              /**
               * @dev triggered when tokens have been withdrawn from the vault
               */
              event FundsWithdrawn(Token indexed token, address indexed caller, address indexed target, uint256 amount);
              /**
               * @dev triggered when tokens have been burned from the vault
               */
              event FundsBurned(Token indexed token, address indexed caller, uint256 amount);
              /**
               * @dev tells whether the vault accepts native token deposits
               */
              function isPayable() external view returns (bool);
              /**
               * @dev withdraws funds held by the contract and sends them to an account
               */
              function withdrawFunds(
                  Token token,
                  address payable target,
                  uint256 amount
              ) external;
              /**
               * @dev burns funds held by the contract
               */
              function burn(Token token, uint256 amount) external;
          }
          

          File 5 of 5: PendingWithdrawals
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlEnumerableUpgradeable.sol";
          import "./AccessControlUpgradeable.sol";
          import "../utils/structs/EnumerableSetUpgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Extension of {AccessControl} that allows enumerating the members of each role.
           */
          abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerableUpgradeable, AccessControlUpgradeable {
              function __AccessControlEnumerable_init() internal onlyInitializing {
              }
              function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
              }
              using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
              mapping(bytes32 => EnumerableSetUpgradeable.AddressSet) private _roleMembers;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlEnumerableUpgradeable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
                  return _roleMembers[role].at(index);
              }
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
                  return _roleMembers[role].length();
              }
              /**
               * @dev Overload {_grantRole} to track enumerable memberships
               */
              function _grantRole(bytes32 role, address account) internal virtual override {
                  super._grantRole(role, account);
                  _roleMembers[role].add(account);
              }
              /**
               * @dev Overload {_revokeRole} to track enumerable memberships
               */
              function _revokeRole(bytes32 role, address account) internal virtual override {
                  super._revokeRole(role, account);
                  _roleMembers[role].remove(account);
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlUpgradeable.sol";
          import "../utils/ContextUpgradeable.sol";
          import "../utils/StringsUpgradeable.sol";
          import "../utils/introspection/ERC165Upgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module that allows children to implement role-based access
           * control mechanisms. This is a lightweight version that doesn't allow enumerating role
           * members except through off-chain means by accessing the contract event logs. Some
           * applications may benefit from on-chain enumerability, for those cases see
           * {AccessControlEnumerable}.
           *
           * Roles are referred to by their `bytes32` identifier. These should be exposed
           * in the external API and be unique. The best way to achieve this is by
           * using `public constant` hash digests:
           *
           * ```
           * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
           * ```
           *
           * Roles can be used to represent a set of permissions. To restrict access to a
           * function call, use {hasRole}:
           *
           * ```
           * function foo() public {
           *     require(hasRole(MY_ROLE, msg.sender));
           *     ...
           * }
           * ```
           *
           * Roles can be granted and revoked dynamically via the {grantRole} and
           * {revokeRole} functions. Each role has an associated admin role, and only
           * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
           *
           * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
           * that only accounts with this role will be able to grant or revoke other
           * roles. More complex role relationships can be created by using
           * {_setRoleAdmin}.
           *
           * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
           * grant and revoke this role. Extra precautions should be taken to secure
           * accounts that have been granted it.
           */
          abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
              function __AccessControl_init() internal onlyInitializing {
              }
              function __AccessControl_init_unchained() internal onlyInitializing {
              }
              struct RoleData {
                  mapping(address => bool) members;
                  bytes32 adminRole;
              }
              mapping(bytes32 => RoleData) private _roles;
              bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
              /**
               * @dev Modifier that checks that an account has a specific role. Reverts
               * with a standardized message including the required role.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               *
               * _Available since v4.1._
               */
              modifier onlyRole(bytes32 role) {
                  _checkRole(role, _msgSender());
                  _;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                  return _roles[role].members[account];
              }
              /**
               * @dev Revert with a standard message if `account` is missing `role`.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               */
              function _checkRole(bytes32 role, address account) internal view virtual {
                  if (!hasRole(role, account)) {
                      revert(
                          string(
                              abi.encodePacked(
                                  "AccessControl: account ",
                                  StringsUpgradeable.toHexString(uint160(account), 20),
                                  " is missing role ",
                                  StringsUpgradeable.toHexString(uint256(role), 32)
                              )
                          )
                      );
                  }
              }
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                  return _roles[role].adminRole;
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _grantRole(role, account);
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _revokeRole(role, account);
              }
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been revoked `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               */
              function renounceRole(bytes32 role, address account) public virtual override {
                  require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                  _revokeRole(role, account);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event. Note that unlike {grantRole}, this function doesn't perform any
               * checks on the calling account.
               *
               * [WARNING]
               * ====
               * This function should only be called from the constructor when setting
               * up the initial roles for the system.
               *
               * Using this function in any other way is effectively circumventing the admin
               * system imposed by {AccessControl}.
               * ====
               *
               * NOTE: This function is deprecated in favor of {_grantRole}.
               */
              function _setupRole(bytes32 role, address account) internal virtual {
                  _grantRole(role, account);
              }
              /**
               * @dev Sets `adminRole` as ``role``'s admin role.
               *
               * Emits a {RoleAdminChanged} event.
               */
              function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                  bytes32 previousAdminRole = getRoleAdmin(role);
                  _roles[role].adminRole = adminRole;
                  emit RoleAdminChanged(role, previousAdminRole, adminRole);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * Internal function without access restriction.
               */
              function _grantRole(bytes32 role, address account) internal virtual {
                  if (!hasRole(role, account)) {
                      _roles[role].members[account] = true;
                      emit RoleGranted(role, account, _msgSender());
                  }
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * Internal function without access restriction.
               */
              function _revokeRole(bytes32 role, address account) internal virtual {
                  if (hasRole(role, account)) {
                      _roles[role].members[account] = false;
                      emit RoleRevoked(role, account, _msgSender());
                  }
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlUpgradeable.sol";
          /**
           * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
           */
          interface IAccessControlEnumerableUpgradeable is IAccessControlUpgradeable {
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) external view returns (address);
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev External interface of AccessControl declared to support ERC165 detection.
           */
          interface IAccessControlUpgradeable {
              /**
               * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
               *
               * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
               * {RoleAdminChanged} not being emitted signaling this.
               *
               * _Available since v3.1._
               */
              event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
              /**
               * @dev Emitted when `account` is granted `role`.
               *
               * `sender` is the account that originated the contract call, an admin role
               * bearer except when using {AccessControl-_setupRole}.
               */
              event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Emitted when `account` is revoked `role`.
               *
               * `sender` is the account that originated the contract call:
               *   - if using `revokeRole`, it is the admin role bearer
               *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
               */
              event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) external view returns (bool);
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {AccessControl-_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) external view returns (bytes32);
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function grantRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function revokeRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been granted `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               */
              function renounceRole(bytes32 role, address account) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)
          pragma solidity ^0.8.0;
          import "../../utils/AddressUpgradeable.sol";
          /**
           * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
           * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
           * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
           * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
           *
           * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
           * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
           *
           * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
           * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
           *
           * [CAUTION]
           * ====
           * Avoid leaving a contract uninitialized.
           *
           * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
           * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
           * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * /// @custom:oz-upgrades-unsafe-allow constructor
           * constructor() initializer {}
           * ```
           * ====
           */
          abstract 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 protect an initializer function from being invoked twice.
               */
              modifier initializer() {
                  // If the contract is initializing we ignore whether _initialized is set in order to support multiple
                  // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
                  // contract may have been reentered.
                  require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");
                  bool isTopLevelCall = !_initializing;
                  if (isTopLevelCall) {
                      _initializing = true;
                      _initialized = true;
                  }
                  _;
                  if (isTopLevelCall) {
                      _initializing = false;
                  }
              }
              /**
               * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
               * {initializer} modifier, directly or indirectly.
               */
              modifier onlyInitializing() {
                  require(_initializing, "Initializable: contract is not initializing");
                  _;
              }
              function _isConstructor() private view returns (bool) {
                  return !AddressUpgradeable.isContract(address(this));
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library AddressUpgradeable {
              /**
               * @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
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 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");
                  (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");
                  (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");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal 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
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with 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 ContextUpgradeable is Initializable {
              function __Context_init() internal onlyInitializing {
              }
              function __Context_init_unchained() internal onlyInitializing {
              }
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev String operations.
           */
          library StringsUpgradeable {
              bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  if (value == 0) {
                      return "0x00";
                  }
                  uint256 temp = value;
                  uint256 length = 0;
                  while (temp != 0) {
                      length++;
                      temp >>= 8;
                  }
                  return toHexString(value, length);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165Upgradeable.sol";
          import "../../proxy/utils/Initializable.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
              function __ERC165_init() internal onlyInitializing {
              }
              function __ERC165_init_unchained() internal onlyInitializing {
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165Upgradeable).interfaceId;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165Upgradeable {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Library for managing
           * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
           * types.
           *
           * Sets have the following properties:
           *
           * - Elements are added, removed, and checked for existence in constant time
           * (O(1)).
           * - Elements are enumerated in O(n). No guarantees are made on the ordering.
           *
           * ```
           * contract Example {
           *     // Add the library methods
           *     using EnumerableSet for EnumerableSet.AddressSet;
           *
           *     // Declare a set state variable
           *     EnumerableSet.AddressSet private mySet;
           * }
           * ```
           *
           * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
           * and `uint256` (`UintSet`) are supported.
           */
          library EnumerableSetUpgradeable {
              // 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;
                      if (lastIndex != toDeleteIndex) {
                          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] = valueIndex; // Replace lastvalue's index to valueIndex
                      }
                      // 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) {
                  return set._values[index];
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function _values(Set storage set) private view returns (bytes32[] memory) {
                  return set._values;
              }
              // 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);
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
                  return _values(set._inner);
              }
              // 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))));
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(AddressSet storage set) internal view returns (address[] memory) {
                  bytes32[] memory store = _values(set._inner);
                  address[] memory result;
                  assembly {
                      result := store
                  }
                  return result;
              }
              // 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));
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(UintSet storage set) internal view returns (uint256[] memory) {
                  bytes32[] memory store = _values(set._inner);
                  uint256[] memory result;
                  assembly {
                      result := store
                  }
                  return result;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol)
          pragma solidity ^0.8.0;
          import "./IERC20.sol";
          import "./extensions/IERC20Metadata.sol";
          import "../../utils/Context.sol";
          /**
           * @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 Contracts guidelines: functions revert
           * instead 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, IERC20Metadata {
              mapping(address => uint256) private _balances;
              mapping(address => mapping(address => uint256)) private _allowances;
              uint256 private _totalSupply;
              string private _name;
              string private _symbol;
              /**
               * @dev Sets the values for {name} and {symbol}.
               *
               * The default value of {decimals} is 18. To select a different value for
               * {decimals} you should overload it.
               *
               * All two of these values are immutable: they can only be set once during
               * construction.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev Returns the name of the token.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev Returns the symbol of the token, usually a shorter version of the
               * name.
               */
              function symbol() public view virtual override 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 this function is
               * overridden;
               *
               * 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 override returns (uint8) {
                  return 18;
              }
              /**
               * @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:
               *
               * - `to` cannot be the zero address.
               * - the caller must have a balance of at least `amount`.
               */
              function transfer(address to, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _transfer(owner, to, 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}.
               *
               * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
               * `transferFrom`. This is semantically equivalent to an infinite approval.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               */
              function approve(address spender, uint256 amount) public virtual override returns (bool) {
                  address owner = _msgSender();
                  _approve(owner, 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}.
               *
               * NOTE: Does not update the allowance if the current allowance
               * is the maximum `uint256`.
               *
               * Requirements:
               *
               * - `from` and `to` cannot be the zero address.
               * - `from` must have a balance of at least `amount`.
               * - the caller must have allowance for ``from``'s tokens of at least
               * `amount`.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 amount
              ) public virtual override returns (bool) {
                  address spender = _msgSender();
                  _spendAllowance(from, spender, amount);
                  _transfer(from, to, amount);
                  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) {
                  address owner = _msgSender();
                  _approve(owner, spender, _allowances[owner][spender] + 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) {
                  address owner = _msgSender();
                  uint256 currentAllowance = _allowances[owner][spender];
                  require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
                  unchecked {
                      _approve(owner, spender, currentAllowance - subtractedValue);
                  }
                  return true;
              }
              /**
               * @dev Moves `amount` of tokens from `sender` to `recipient`.
               *
               * This 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:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `from` must have a balance of at least `amount`.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {
                  require(from != address(0), "ERC20: transfer from the zero address");
                  require(to != address(0), "ERC20: transfer to the zero address");
                  _beforeTokenTransfer(from, to, amount);
                  uint256 fromBalance = _balances[from];
                  require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
                  unchecked {
                      _balances[from] = fromBalance - amount;
                  }
                  _balances[to] += amount;
                  emit Transfer(from, to, amount);
                  _afterTokenTransfer(from, to, 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:
               *
               * - `account` 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 += amount;
                  _balances[account] += amount;
                  emit Transfer(address(0), account, amount);
                  _afterTokenTransfer(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);
                  uint256 accountBalance = _balances[account];
                  require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
                  unchecked {
                      _balances[account] = accountBalance - amount;
                  }
                  _totalSupply -= amount;
                  emit Transfer(account, address(0), amount);
                  _afterTokenTransfer(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 Spend `amount` form the allowance of `owner` toward `spender`.
               *
               * Does not update the allowance amount in case of infinite allowance.
               * Revert if not enough allowance is available.
               *
               * Might emit an {Approval} event.
               */
              function _spendAllowance(
                  address owner,
                  address spender,
                  uint256 amount
              ) internal virtual {
                  uint256 currentAllowance = allowance(owner, spender);
                  if (currentAllowance != type(uint256).max) {
                      require(currentAllowance >= amount, "ERC20: insufficient allowance");
                      unchecked {
                          _approve(owner, spender, currentAllowance - amount);
                      }
                  }
              }
              /**
               * @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 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 Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * has been transferred to `to`.
               * - when `from` is zero, `amount` tokens have been minted for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens have been 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 _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {}
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @dev Returns the amount of tokens in existence.
               */
              function totalSupply() external view returns (uint256);
              /**
               * @dev Returns the amount of tokens owned by `account`.
               */
              function balanceOf(address account) external view returns (uint256);
              /**
               * @dev Moves `amount` tokens from the caller's account to `to`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address to, 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 `from` to `to` 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 from,
                  address to,
                  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
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          /**
           * @dev Interface for the optional metadata functions from the ERC20 standard.
           *
           * _Available since v4.1._
           */
          interface IERC20Metadata is IERC20 {
              /**
               * @dev Returns the name of the token.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the symbol of the token.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the decimals places of the token.
               */
              function decimals() external view returns (uint8);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
           * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
           *
           * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
           * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
           * need to send a transaction, and thus is not required to hold Ether at all.
           */
          interface IERC20Permit {
              /**
               * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
               * given ``owner``'s signed approval.
               *
               * IMPORTANT: The same issues {IERC20-approve} has related to transaction
               * ordering also apply here.
               *
               * Emits an {Approval} event.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               * - `deadline` must be a timestamp in the future.
               * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
               * over the EIP712-formatted function arguments.
               * - the signature must use ``owner``'s current nonce (see {nonces}).
               *
               * For more information on the signature format, see the
               * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
               * section].
               */
              function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external;
              /**
               * @dev Returns the current nonce for `owner`. This value must be
               * included whenever a signature is generated for {permit}.
               *
               * Every successful call to {permit} increases ``owner``'s nonce by one. This
               * prevents a signature from being used multiple times.
               */
              function nonces(address owner) external view returns (uint256);
              /**
               * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
               */
              // solhint-disable-next-line func-name-mixedcase
              function DOMAIN_SEPARATOR() external view returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          import "../../../utils/Address.sol";
          /**
           * @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 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'
                  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) + value;
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
              }
              function safeDecreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  unchecked {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                      uint256 newAllowance = oldAllowance - 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
                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @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
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 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");
                  (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");
                  (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");
                  (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");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal 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
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with 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) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @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.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a / b + (a % b == 0 ? 0 : 1);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { EnumerableSetUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import { Token } from "../token/Token.sol";
          import { TokenLibrary } from "../token/TokenLibrary.sol";
          import { IVersioned } from "../utility/interfaces/IVersioned.sol";
          import { Upgradeable } from "../utility/Upgradeable.sol";
          import { Utils, AccessDenied, AlreadyExists, DoesNotExist } from "../utility/Utils.sol";
          import { Time } from "../utility/Time.sol";
          import { MathEx } from "../utility/MathEx.sol";
          import { IPoolToken } from "../pools/interfaces/IPoolToken.sol";
          import { IBNTPool } from "../pools/interfaces/IBNTPool.sol";
          import { IBancorNetwork } from "./interfaces/IBancorNetwork.sol";
          import { IPendingWithdrawals, WithdrawalRequest, CompletedWithdrawal } from "./interfaces/IPendingWithdrawals.sol";
          /**
           * @dev Pending Withdrawals contract
           */
          contract PendingWithdrawals is IPendingWithdrawals, Upgradeable, Time, Utils {
              using SafeERC20 for IPoolToken;
              using EnumerableSetUpgradeable for EnumerableSetUpgradeable.UintSet;
              using TokenLibrary for Token;
              error WithdrawalNotAllowed();
              uint32 private constant DEFAULT_LOCK_DURATION = 7 days;
              // the network contract
              IBancorNetwork private immutable _network;
              // the BNT contract
              IERC20 private immutable _bnt;
              // the BNT pool contract
              IBNTPool private immutable _bntPool;
              // the lock duration
              uint32 private _lockDuration;
              // a mapping between accounts and their pending withdrawal requests
              uint256 private _nextWithdrawalRequestId;
              mapping(address => EnumerableSetUpgradeable.UintSet) private _withdrawalRequestIdsByProvider;
              mapping(uint256 => WithdrawalRequest) private _withdrawalRequests;
              // upgrade forward-compatibility storage gap
              uint256[MAX_GAP - 4] private __gap;
              /**
               * @dev triggered when the lock duration is updated
               */
              event LockDurationUpdated(uint32 prevLockDuration, uint32 newLockDuration);
              /**
               * @dev triggered when a provider requests to initiate a liquidity withdrawal
               */
              event WithdrawalInitiated(
                  Token indexed pool,
                  address indexed provider,
                  uint256 indexed requestId,
                  uint256 poolTokenAmount,
                  uint256 reserveTokenAmount
              );
              /**
               * @dev triggered when a provider cancels a liquidity withdrawal request
               */
              event WithdrawalCancelled(
                  Token indexed pool,
                  address indexed provider,
                  uint256 indexed requestId,
                  uint256 poolTokenAmount,
                  uint256 reserveTokenAmount,
                  uint32 timeElapsed
              );
              /**
               * @dev triggered when a liquidity withdrawal request has been completed
               */
              event WithdrawalCompleted(
                  bytes32 indexed contextId,
                  Token indexed pool,
                  address indexed provider,
                  uint256 requestId,
                  uint256 poolTokenAmount,
                  uint256 reserveTokenAmount,
                  uint32 timeElapsed
              );
              /**
               * @dev a "virtual" constructor that is only used to set immutable state variables
               */
              constructor(
                  IBancorNetwork initNetwork,
                  IERC20 initBNT,
                  IBNTPool initBNTPool
              ) validAddress(address(initNetwork)) validAddress(address(initBNT)) validAddress(address(initBNTPool)) {
                  _network = initNetwork;
                  _bnt = initBNT;
                  _bntPool = initBNTPool;
              }
              /**
               * @dev fully initializes the contract and its parents
               */
              function initialize() external initializer {
                  __PendingWithdrawals_init();
              }
              // solhint-disable func-name-mixedcase
              /**
               * @dev initializes the contract and its parents
               */
              function __PendingWithdrawals_init() internal onlyInitializing {
                  __Upgradeable_init();
                  __PendingWithdrawals_init_unchained();
              }
              /**
               * @dev performs contract-specific initialization
               */
              function __PendingWithdrawals_init_unchained() internal onlyInitializing {
                  _setLockDuration(DEFAULT_LOCK_DURATION);
              }
              // solhint-enable func-name-mixedcase
              /**
               * @inheritdoc Upgradeable
               */
              function version() public pure override(IVersioned, Upgradeable) returns (uint16) {
                  return 4;
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function lockDuration() external view returns (uint32) {
                  return _lockDuration;
              }
              /**
               * @dev sets the lock duration
               *
               * notes:
               *
               * - updating it will affect existing locked positions retroactively
               *
               * requirements:
               *
               * - the caller must be the admin of the contract
               */
              function setLockDuration(uint32 newLockDuration) external onlyAdmin {
                  _setLockDuration(newLockDuration);
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function withdrawalRequestCount(address provider) external view returns (uint256) {
                  return _withdrawalRequestIdsByProvider[provider].length();
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function withdrawalRequestIds(address provider) external view returns (uint256[] memory) {
                  return _withdrawalRequestIdsByProvider[provider].values();
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function withdrawalRequest(uint256 id) external view returns (WithdrawalRequest memory) {
                  return _withdrawalRequests[id];
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function initWithdrawal(
                  address provider,
                  IPoolToken poolToken,
                  uint256 poolTokenAmount
              )
                  external
                  validAddress(address(poolToken))
                  greaterThanZero(poolTokenAmount)
                  only(address(_network))
                  returns (uint256)
              {
                  return _initWithdrawal(provider, poolToken, poolTokenAmount);
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function cancelWithdrawal(address provider, uint256 id) external only(address(_network)) returns (uint256) {
                  WithdrawalRequest memory request = _withdrawalRequests[id];
                  if (request.provider != provider) {
                      revert AccessDenied();
                  }
                  return _cancelWithdrawal(request, id);
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function completeWithdrawal(
                  bytes32 contextId,
                  address provider,
                  uint256 id
              ) external only(address(_network)) returns (CompletedWithdrawal memory) {
                  WithdrawalRequest memory request = _withdrawalRequests[id];
                  if (provider != request.provider) {
                      revert AccessDenied();
                  }
                  uint32 currentTime = _time();
                  if (!_canWithdrawAt(currentTime, request.createdAt)) {
                      revert WithdrawalNotAllowed();
                  }
                  // remove the withdrawal request and its id from the storage
                  _removeWithdrawalRequest(provider, id);
                  // approve the caller to transfer the locked pool tokens
                  request.poolToken.approve(msg.sender, request.poolTokenAmount);
                  emit WithdrawalCompleted({
                      contextId: contextId,
                      pool: request.reserveToken,
                      provider: provider,
                      requestId: id,
                      poolTokenAmount: request.poolTokenAmount,
                      reserveTokenAmount: request.reserveTokenAmount,
                      timeElapsed: currentTime - request.createdAt
                  });
                  return
                      CompletedWithdrawal({
                          poolToken: request.poolToken,
                          poolTokenAmount: request.poolTokenAmount,
                          reserveTokenAmount: request.reserveTokenAmount
                      });
              }
              /**
               * @inheritdoc IPendingWithdrawals
               */
              function isReadyForWithdrawal(uint256 id) external view returns (bool) {
                  WithdrawalRequest storage request = _withdrawalRequests[id];
                  return request.provider != address(0) && _canWithdrawAt(_time(), request.createdAt);
              }
              /**
               * @dev sets the lock duration
               *
               * notes:
               *
               * - updating it will affect existing locked positions retroactively
               *
               */
              function _setLockDuration(uint32 newLockDuration) private {
                  uint32 prevLockDuration = _lockDuration;
                  if (prevLockDuration == newLockDuration) {
                      return;
                  }
                  _lockDuration = newLockDuration;
                  emit LockDurationUpdated({ prevLockDuration: prevLockDuration, newLockDuration: newLockDuration });
              }
              /**
               * @dev initiates liquidity withdrawal
               */
              function _initWithdrawal(
                  address provider,
                  IPoolToken poolToken,
                  uint256 poolTokenAmount
              ) private returns (uint256) {
                  // record the current withdrawal request alongside previous pending withdrawal requests
                  uint256 id = _nextWithdrawalRequestId++;
                  // get the pool token value in reserve/pool tokens
                  Token pool = poolToken.reserveToken();
                  uint256 reserveTokenAmount = _poolTokenToUnderlying(pool, poolTokenAmount);
                  _withdrawalRequests[id] = WithdrawalRequest({
                      provider: provider,
                      poolToken: poolToken,
                      reserveToken: pool,
                      poolTokenAmount: poolTokenAmount,
                      reserveTokenAmount: reserveTokenAmount,
                      createdAt: _time()
                  });
                  if (!_withdrawalRequestIdsByProvider[provider].add(id)) {
                      revert AlreadyExists();
                  }
                  emit WithdrawalInitiated({
                      pool: pool,
                      provider: provider,
                      requestId: id,
                      poolTokenAmount: poolTokenAmount,
                      reserveTokenAmount: reserveTokenAmount
                  });
                  return id;
              }
              /**
               * @dev returns the pool token value in tokens
               */
              function _poolTokenToUnderlying(Token pool, uint256 poolTokenAmount) private view returns (uint256) {
                  if (pool.isEqual(_bnt)) {
                      return _bntPool.poolTokenToUnderlying(poolTokenAmount);
                  }
                  return _network.collectionByPool(pool).poolTokenToUnderlying(pool, poolTokenAmount);
              }
              /**
               * @dev cancels a withdrawal request
               */
              function _cancelWithdrawal(WithdrawalRequest memory request, uint256 id) private returns (uint256) {
                  // remove the withdrawal request and its id from the storage
                  _removeWithdrawalRequest(request.provider, id);
                  // transfer the locked pool tokens back to the provider
                  request.poolToken.safeTransfer(request.provider, request.poolTokenAmount);
                  emit WithdrawalCancelled({
                      pool: request.reserveToken,
                      provider: request.provider,
                      requestId: id,
                      poolTokenAmount: request.poolTokenAmount,
                      reserveTokenAmount: request.reserveTokenAmount,
                      timeElapsed: _time() - request.createdAt
                  });
                  return request.poolTokenAmount;
              }
              /**
               * @dev removes withdrawal request
               */
              function _removeWithdrawalRequest(address provider, uint256 id) private {
                  if (!_withdrawalRequestIdsByProvider[provider].remove(id)) {
                      revert DoesNotExist();
                  }
                  delete _withdrawalRequests[id];
              }
              /**
               * @dev returns whether it's possible to withdraw a request at the provided time
               */
              function _canWithdrawAt(uint32 time, uint32 createdAt) private view returns (bool) {
                  return createdAt + _lockDuration <= time;
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { IUpgradeable } from "../../utility/interfaces/IUpgradeable.sol";
          import { Token } from "../../token/Token.sol";
          import { IPoolCollection } from "../../pools/interfaces/IPoolCollection.sol";
          import { IPoolToken } from "../../pools/interfaces/IPoolToken.sol";
          /**
           * @dev Flash-loan recipient interface
           */
          interface IFlashLoanRecipient {
              /**
               * @dev a flash-loan recipient callback after each the caller must return the borrowed amount and an additional fee
               */
              function onFlashLoan(
                  address caller,
                  IERC20 erc20Token,
                  uint256 amount,
                  uint256 feeAmount,
                  bytes memory data
              ) external;
          }
          /**
           * @dev Bancor Network interface
           */
          interface IBancorNetwork is IUpgradeable {
              /**
               * @dev returns the set of all valid pool collections
               */
              function poolCollections() external view returns (IPoolCollection[] memory);
              /**
               * @dev returns the most recent collection that was added to the pool collections set for a specific type
               */
              function latestPoolCollection(uint16 poolType) external view returns (IPoolCollection);
              /**
               * @dev returns the set of all liquidity pools
               */
              function liquidityPools() external view returns (Token[] memory);
              /**
               * @dev returns the respective pool collection for the provided pool
               */
              function collectionByPool(Token pool) external view returns (IPoolCollection);
              /**
               * @dev creates a new pool
               *
               * requirements:
               *
               * - the pool doesn't already exist
               */
              function createPool(uint16 poolType, Token token) external;
              /**
               * @dev creates new pools
               *
               * requirements:
               *
               * - none of the pools already exists
               */
              function createPools(uint16 poolType, Token[] calldata tokens) external;
              /**
               * @dev migrates a list of pools between pool collections
               *
               * notes:
               *
               * - invalid or incompatible pools will be skipped gracefully
               */
              function migratePools(Token[] calldata pools) external;
              /**
               * @dev deposits liquidity for the specified provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the tokens on its behalf (except for in the
               *   native token case)
               */
              function depositFor(
                  address provider,
                  Token pool,
                  uint256 tokenAmount
              ) external payable returns (uint256);
              /**
               * @dev deposits liquidity for the current provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the tokens on its behalf (except for in the
               *   native token case)
               */
              function deposit(Token pool, uint256 tokenAmount) external payable returns (uint256);
              /**
               * @dev deposits liquidity for the specified provider by providing an EIP712 typed signature for an EIP2612 permit
               * request and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must have provided a valid and unused EIP712 typed signature
               */
              function depositForPermitted(
                  address provider,
                  Token pool,
                  uint256 tokenAmount,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external returns (uint256);
              /**
               * @dev deposits liquidity by providing an EIP712 typed signature for an EIP2612 permit request and returns the
               * respective pool token amount
               *
               * requirements:
               *
               * - the caller must have provided a valid and unused EIP712 typed signature
               */
              function depositPermitted(
                  Token pool,
                  uint256 tokenAmount,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external returns (uint256);
              /**
               * @dev initiates liquidity withdrawal
               *
               * requirements:
               *
               * - the caller must have approved the contract to transfer the pool token amount on its behalf
               */
              function initWithdrawal(IPoolToken poolToken, uint256 poolTokenAmount) external returns (uint256);
              /**
               * @dev initiates liquidity withdrawal by providing an EIP712 typed signature for an EIP2612 permit request
               *
               * requirements:
               *
               * - the caller must have provided a valid and unused EIP712 typed signature
               */
              function initWithdrawalPermitted(
                  IPoolToken poolToken,
                  uint256 poolTokenAmount,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external returns (uint256);
              /**
               * @dev cancels a withdrawal request, and returns the number of pool token amount associated with the withdrawal
               * request
               *
               * requirements:
               *
               * - the caller must have already initiated a withdrawal and received the specified id
               */
              function cancelWithdrawal(uint256 id) external returns (uint256);
              /**
               * @dev withdraws liquidity and returns the withdrawn amount
               *
               * requirements:
               *
               * - the provider must have already initiated a withdrawal and received the specified id
               * - the specified withdrawal request is eligible for completion
               * - the provider must have approved the network to transfer vBNT amount on its behalf, when withdrawing BNT
               * liquidity
               */
              function withdraw(uint256 id) external returns (uint256);
              /**
               * @dev performs a trade by providing the input source amount, and returns the trade target amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the source tokens on its behalf (except for in the
               *   native token case)
               */
              function tradeBySourceAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable returns (uint256);
              /**
               * @dev performs a trade by providing the input source amount and providing an EIP712 typed signature for an
               * EIP2612 permit request, and returns the trade target amount
               *
               * requirements:
               *
               * - the caller must have provided a valid and unused EIP712 typed signature
               */
              function tradeBySourceAmountPermitted(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount,
                  uint256 deadline,
                  address beneficiary,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external returns (uint256);
              /**
               * @dev performs a trade by providing the output target amount, and returns the trade source amount
               *
               * requirements:
               *
               * - the caller must have approved the network to transfer the source tokens on its behalf (except for in the
               *   native token case)
               */
              function tradeByTargetAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  uint256 deadline,
                  address beneficiary
              ) external payable returns (uint256);
              /**
               * @dev performs a trade by providing the output target amount and providing an EIP712 typed signature for an
               * EIP2612 permit request and returns the target amount and fee, and returns the trade source amount
               *
               * requirements:
               *
               * - the caller must have provided a valid and unused EIP712 typed signature
               */
              function tradeByTargetAmountPermitted(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount,
                  uint256 deadline,
                  address beneficiary,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) external returns (uint256);
              /**
               * @dev provides a flash-loan
               *
               * requirements:
               *
               * - the recipient's callback must return *at least* the borrowed amount and fee back to the specified return address
               */
              function flashLoan(
                  Token token,
                  uint256 amount,
                  IFlashLoanRecipient recipient,
                  bytes calldata data
              ) external;
              /**
               * @dev deposits liquidity during a migration
               */
              function migrateLiquidity(
                  Token token,
                  address provider,
                  uint256 amount,
                  uint256 availableAmount,
                  uint256 originalAmount
              ) external payable;
              /**
               * @dev withdraws pending network fees, and returns the amount of fees withdrawn
               *
               * requirements:
               *
               * - the caller must have the ROLE_NETWORK_FEE_MANAGER privilege
               */
              function withdrawNetworkFees(address recipient) external returns (uint256);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IPoolToken } from "../../pools/interfaces/IPoolToken.sol";
          import { Token } from "../../token/Token.sol";
          import { IUpgradeable } from "../../utility/interfaces/IUpgradeable.sol";
          /**
           * @dev the data struct representing a pending withdrawal request
           */
          struct WithdrawalRequest {
              address provider; // the liquidity provider
              IPoolToken poolToken; // the locked pool token
              Token reserveToken; // the reserve token to withdraw
              uint32 createdAt; // the time when the request was created (Unix timestamp)
              uint256 poolTokenAmount; // the locked pool token amount
              uint256 reserveTokenAmount; // the expected reserve token amount to withdraw
          }
          /**
           * @dev the data struct representing a completed withdrawal request
           */
          struct CompletedWithdrawal {
              IPoolToken poolToken; // the withdraw pool token
              uint256 poolTokenAmount; // the original pool token amount in the withdrawal request
              uint256 reserveTokenAmount; // the original reserve token amount at the time of the withdrawal init request
          }
          /**
           * @dev Pending Withdrawals interface
           */
          interface IPendingWithdrawals is IUpgradeable {
              /**
               * @dev returns the lock duration
               */
              function lockDuration() external view returns (uint32);
              /**
               * @dev returns the pending withdrawal requests count for a specific provider
               */
              function withdrawalRequestCount(address provider) external view returns (uint256);
              /**
               * @dev returns the pending withdrawal requests IDs for a specific provider
               */
              function withdrawalRequestIds(address provider) external view returns (uint256[] memory);
              /**
               * @dev returns the pending withdrawal request with the specified ID
               */
              function withdrawalRequest(uint256 id) external view returns (WithdrawalRequest memory);
              /**
               * @dev initiates liquidity withdrawal
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function initWithdrawal(
                  address provider,
                  IPoolToken poolToken,
                  uint256 poolTokenAmount
              ) external returns (uint256);
              /**
               * @dev cancels a withdrawal request, and returns the number of pool tokens which were sent back to the provider
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the provider must have already initiated a withdrawal and received the specified id
               */
              function cancelWithdrawal(address provider, uint256 id) external returns (uint256);
              /**
               * @dev completes a withdrawal request, and returns the pool token and its transferred amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the provider must have already initiated a withdrawal and received the specified id
               * - the lock duration has ended
               */
              function completeWithdrawal(
                  bytes32 contextId,
                  address provider,
                  uint256 id
              ) external returns (CompletedWithdrawal memory);
              /**
               * @dev returns whether the given request is ready for withdrawal
               */
              function isReadyForWithdrawal(uint256 id) external view returns (bool);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IPoolToken } from "./IPoolToken.sol";
          import { Token } from "../../token/Token.sol";
          import { IVault } from "../../vaults/interfaces/IVault.sol";
          // the BNT pool token manager role is required to access the BNT pool tokens
          bytes32 constant ROLE_BNT_POOL_TOKEN_MANAGER = keccak256("ROLE_BNT_POOL_TOKEN_MANAGER");
          // the BNT manager role is required to request the BNT pool to mint BNT
          bytes32 constant ROLE_BNT_MANAGER = keccak256("ROLE_BNT_MANAGER");
          // the vault manager role is required to request the BNT pool to burn BNT from the master vault
          bytes32 constant ROLE_VAULT_MANAGER = keccak256("ROLE_VAULT_MANAGER");
          // the funding manager role is required to request or renounce funding from the BNT pool
          bytes32 constant ROLE_FUNDING_MANAGER = keccak256("ROLE_FUNDING_MANAGER");
          /**
           * @dev BNT Pool interface
           */
          interface IBNTPool is IVault {
              /**
               * @dev returns the BNT pool token contract
               */
              function poolToken() external view returns (IPoolToken);
              /**
               * @dev returns the total staked BNT balance in the network
               */
              function stakedBalance() external view returns (uint256);
              /**
               * @dev returns the current funding of given pool
               */
              function currentPoolFunding(Token pool) external view returns (uint256);
              /**
               * @dev returns the available BNT funding for a given pool
               */
              function availableFunding(Token pool) external view returns (uint256);
              /**
               * @dev converts the specified pool token amount to the underlying BNT amount
               */
              function poolTokenToUnderlying(uint256 poolTokenAmount) external view returns (uint256);
              /**
               * @dev converts the specified underlying BNT amount to pool token amount
               */
              function underlyingToPoolToken(uint256 bntAmount) external view returns (uint256);
              /**
               * @dev returns the number of pool token to burn in order to increase everyone's underlying value by the specified
               * amount
               */
              function poolTokenAmountToBurn(uint256 bntAmountToDistribute) external view returns (uint256);
              /**
               * @dev mints BNT to the recipient
               *
               * requirements:
               *
               * - the caller must have the ROLE_BNT_MANAGER role
               */
              function mint(address recipient, uint256 bntAmount) external;
              /**
               * @dev burns BNT from the vault
               *
               * requirements:
               *
               * - the caller must have the ROLE_VAULT_MANAGER role
               */
              function burnFromVault(uint256 bntAmount) external;
              /**
               * @dev deposits BNT liquidity on behalf of a specific provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - BNT tokens must have been already deposited into the contract
               */
              function depositFor(
                  bytes32 contextId,
                  address provider,
                  uint256 bntAmount,
                  bool isMigrating,
                  uint256 originalVBNTAmount
              ) external returns (uint256);
              /**
               * @dev withdraws BNT liquidity on behalf of a specific provider and returns the withdrawn BNT amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - bnBNT token must have been already deposited into the contract
               * - vBNT token must have been already deposited into the contract
               */
              function withdraw(
                  bytes32 contextId,
                  address provider,
                  uint256 poolTokenAmount,
                  uint256 bntAmount
              ) external returns (uint256);
              /**
               * @dev returns the withdrawn BNT amount
               */
              function withdrawalAmount(uint256 poolTokenAmount) external view returns (uint256);
              /**
               * @dev requests BNT funding
               *
               * requirements:
               *
               * - the caller must have the ROLE_FUNDING_MANAGER role
               * - the token must have been whitelisted
               * - the request amount should be below the funding limit for a given pool
               * - the average rate of the pool must not deviate too much from its spot rate
               */
              function requestFunding(
                  bytes32 contextId,
                  Token pool,
                  uint256 bntAmount
              ) external;
              /**
               * @dev renounces BNT funding
               *
               * requirements:
               *
               * - the caller must have the ROLE_FUNDING_MANAGER role
               * - the token must have been whitelisted
               * - the average rate of the pool must not deviate too much from its spot rate
               */
              function renounceFunding(
                  bytes32 contextId,
                  Token pool,
                  uint256 bntAmount
              ) external;
              /**
               * @dev notifies the pool of accrued fees
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function onFeesCollected(
                  Token pool,
                  uint256 feeAmount,
                  bool isTradeFee
              ) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IVersioned } from "../../utility/interfaces/IVersioned.sol";
          import { Fraction112 } from "../../utility/FractionLibrary.sol";
          import { Token } from "../../token/Token.sol";
          import { IPoolToken } from "./IPoolToken.sol";
          struct PoolLiquidity {
              uint128 bntTradingLiquidity; // the BNT trading liquidity
              uint128 baseTokenTradingLiquidity; // the base token trading liquidity
              uint256 stakedBalance; // the staked balance
          }
          struct AverageRates {
              uint32 blockNumber;
              Fraction112 rate;
              Fraction112 invRate;
          }
          struct Pool {
              IPoolToken poolToken; // the pool token of the pool
              uint32 tradingFeePPM; // the trading fee (in units of PPM)
              bool tradingEnabled; // whether trading is enabled
              bool depositingEnabled; // whether depositing is enabled
              AverageRates averageRates; // the recent average rates
              PoolLiquidity liquidity; // the overall liquidity in the pool
          }
          struct WithdrawalAmounts {
              uint256 totalAmount;
              uint256 baseTokenAmount;
              uint256 bntAmount;
          }
          // trading enabling/disabling reasons
          uint8 constant TRADING_STATUS_UPDATE_DEFAULT = 0;
          uint8 constant TRADING_STATUS_UPDATE_ADMIN = 1;
          uint8 constant TRADING_STATUS_UPDATE_MIN_LIQUIDITY = 2;
          struct TradeAmountAndFee {
              uint256 amount; // the source/target amount (depending on the context) resulting from the trade
              uint256 tradingFeeAmount; // the trading fee amount
              uint256 networkFeeAmount; // the network fee amount (always in units of BNT)
          }
          /**
           * @dev Pool Collection interface
           */
          interface IPoolCollection is IVersioned {
              /**
               * @dev returns the type of the pool
               */
              function poolType() external pure returns (uint16);
              /**
               * @dev returns the default trading fee (in units of PPM)
               */
              function defaultTradingFeePPM() external view returns (uint32);
              /**
               * @dev returns all the pools which are managed by this pool collection
               */
              function pools() external view returns (Token[] memory);
              /**
               * @dev returns the number of all the pools which are managed by this pool collection
               */
              function poolCount() external view returns (uint256);
              /**
               * @dev returns whether a pool is valid
               */
              function isPoolValid(Token pool) external view returns (bool);
              /**
               * @dev returns the overall liquidity in the pool
               */
              function poolLiquidity(Token pool) external view returns (PoolLiquidity memory);
              /**
               * @dev returns the pool token of the pool
               */
              function poolToken(Token pool) external view returns (IPoolToken);
              /**
               * @dev returns the trading fee (in units of PPM)
               */
              function tradingFeePPM(Token pool) external view returns (uint32);
              /**
               * @dev returns whether trading is enabled
               */
              function tradingEnabled(Token pool) external view returns (bool);
              /**
               * @dev returns whether depositing is enabled
               */
              function depositingEnabled(Token pool) external view returns (bool);
              /**
               * @dev returns whether the pool is stable
               */
              function isPoolStable(Token pool) external view returns (bool);
              /**
               * @dev converts the specified pool token amount to the underlying base token amount
               */
              function poolTokenToUnderlying(Token pool, uint256 poolTokenAmount) external view returns (uint256);
              /**
               * @dev converts the specified underlying base token amount to pool token amount
               */
              function underlyingToPoolToken(Token pool, uint256 tokenAmount) external view returns (uint256);
              /**
               * @dev returns the number of pool token to burn in order to increase everyone's underlying value by the specified
               * amount
               */
              function poolTokenAmountToBurn(
                  Token pool,
                  uint256 tokenAmountToDistribute,
                  uint256 protocolPoolTokenAmount
              ) external view returns (uint256);
              /**
               * @dev creates a new pool
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the pool should have been whitelisted
               * - the pool isn't already defined in the collection
               */
              function createPool(Token token) external;
              /**
               * @dev deposits base token liquidity on behalf of a specific provider and returns the respective pool token amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - assumes that the base token has been already deposited in the vault
               */
              function depositFor(
                  bytes32 contextId,
                  address provider,
                  Token pool,
                  uint256 tokenAmount
              ) external returns (uint256);
              /**
               * @dev handles some of the withdrawal-related actions and returns the withdrawn base token amount
               *
               * requirements:
               *
               * - the caller must be the network contract
               * - the caller must have approved the collection to transfer/burn the pool token amount on its behalf
               */
              function withdraw(
                  bytes32 contextId,
                  address provider,
                  Token pool,
                  uint256 poolTokenAmount,
                  uint256 baseTokenAmount
              ) external returns (uint256);
              /**
               * @dev returns the amounts that would be returned if the position is currently withdrawn,
               * along with the breakdown of the base token and the BNT compensation
               */
              function withdrawalAmounts(Token pool, uint256 poolTokenAmount) external view returns (WithdrawalAmounts memory);
              /**
               * @dev performs a trade by providing the source amount and returns the target amount and the associated fee
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function tradeBySourceAmount(
                  bytes32 contextId,
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount,
                  uint256 minReturnAmount
              ) external returns (TradeAmountAndFee memory);
              /**
               * @dev performs a trade by providing the target amount and returns the required source amount and the associated fee
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function tradeByTargetAmount(
                  bytes32 contextId,
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount,
                  uint256 maxSourceAmount
              ) external returns (TradeAmountAndFee memory);
              /**
               * @dev returns the output amount and fee when trading by providing the source amount
               */
              function tradeOutputAndFeeBySourceAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 sourceAmount
              ) external view returns (TradeAmountAndFee memory);
              /**
               * @dev returns the input amount and fee when trading by providing the target amount
               */
              function tradeInputAndFeeByTargetAmount(
                  Token sourceToken,
                  Token targetToken,
                  uint256 targetAmount
              ) external view returns (TradeAmountAndFee memory);
              /**
               * @dev notifies the pool of accrued fees
               *
               * requirements:
               *
               * - the caller must be the network contract
               */
              function onFeesCollected(Token pool, uint256 feeAmount) external;
              /**
               * @dev migrates a pool to this pool collection
               *
               * requirements:
               *
               * - the caller must be the pool migrator contract
               */
              function migratePoolIn(Token pool, Pool calldata data) external;
              /**
               * @dev migrates a pool from this pool collection
               *
               * requirements:
               *
               * - the caller must be the pool migrator contract
               */
              function migratePoolOut(Token pool, IPoolCollection targetPoolCollection) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
          import { IERC20Burnable } from "../../token/interfaces/IERC20Burnable.sol";
          import { Token } from "../../token/Token.sol";
          import { IVersioned } from "../../utility/interfaces/IVersioned.sol";
          import { IOwned } from "../../utility/interfaces/IOwned.sol";
          /**
           * @dev Pool Token interface
           */
          interface IPoolToken is IVersioned, IOwned, IERC20, IERC20Permit, IERC20Burnable {
              /**
               * @dev returns the address of the reserve token
               */
              function reserveToken() external view returns (Token);
              /**
               * @dev increases the token supply and sends the new tokens to the given account
               *
               * requirements:
               *
               * - the caller must be the owner of the contract
               */
              function mint(address recipient, uint256 amount) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          /**
           * @dev extends the SafeERC20 library with additional operations
           */
          library SafeERC20Ex {
              using SafeERC20 for IERC20;
              /**
               * @dev ensures that the spender has sufficient allowance
               */
              function ensureApprove(
                  IERC20 token,
                  address spender,
                  uint256 amount
              ) internal {
                  if (amount == 0) {
                      return;
                  }
                  uint256 allowance = token.allowance(address(this), spender);
                  if (allowance >= amount) {
                      return;
                  }
                  if (allowance > 0) {
                      token.safeApprove(spender, 0);
                  }
                  token.safeApprove(spender, amount);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev the main purpose of the Token interfaces is to ensure artificially that we won't use ERC20's standard functions,
           * but only their safe versions, which are provided by SafeERC20 and SafeERC20Ex via the TokenLibrary contract
           */
          interface Token {
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
          import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
          import { SafeERC20Ex } from "./SafeERC20Ex.sol";
          import { Token } from "./Token.sol";
          struct Signature {
              uint8 v;
              bytes32 r;
              bytes32 s;
          }
          /**
           * @dev This library implements ERC20 and SafeERC20 utilities for both the native token and for ERC20 tokens
           */
          library TokenLibrary {
              using SafeERC20 for IERC20;
              using SafeERC20Ex for IERC20;
              error PermitUnsupported();
              // the address that represents the native token reserve
              address public constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
              // the symbol that represents the native token
              string private constant NATIVE_TOKEN_SYMBOL = "ETH";
              // the decimals for the native token
              uint8 private constant NATIVE_TOKEN_DECIMALS = 18;
              /**
               * @dev returns whether the provided token represents an ERC20 or the native token reserve
               */
              function isNative(Token token) internal pure returns (bool) {
                  return address(token) == NATIVE_TOKEN_ADDRESS;
              }
              /**
               * @dev returns the symbol of the native token/ERC20 token
               */
              function symbol(Token token) internal view returns (string memory) {
                  if (isNative(token)) {
                      return NATIVE_TOKEN_SYMBOL;
                  }
                  return toERC20(token).symbol();
              }
              /**
               * @dev returns the decimals of the native token/ERC20 token
               */
              function decimals(Token token) internal view returns (uint8) {
                  if (isNative(token)) {
                      return NATIVE_TOKEN_DECIMALS;
                  }
                  return toERC20(token).decimals();
              }
              /**
               * @dev returns the balance of the native token/ERC20 token
               */
              function balanceOf(Token token, address account) internal view returns (uint256) {
                  if (isNative(token)) {
                      return account.balance;
                  }
                  return toIERC20(token).balanceOf(account);
              }
              /**
               * @dev transfers a specific amount of the native token/ERC20 token
               */
              function safeTransfer(
                  Token token,
                  address to,
                  uint256 amount
              ) internal {
                  if (amount == 0) {
                      return;
                  }
                  if (isNative(token)) {
                      payable(to).transfer(amount);
                  } else {
                      toIERC20(token).safeTransfer(to, amount);
                  }
              }
              /**
               * @dev transfers a specific amount of the native token/ERC20 token from a specific holder using the allowance mechanism
               *
               * note that the function does not perform any action if the native token is provided
               */
              function safeTransferFrom(
                  Token token,
                  address from,
                  address to,
                  uint256 amount
              ) internal {
                  if (amount == 0 || isNative(token)) {
                      return;
                  }
                  toIERC20(token).safeTransferFrom(from, to, amount);
              }
              /**
               * @dev approves a specific amount of the native token/ERC20 token from a specific holder
               *
               * note that the function does not perform any action if the native token is provided
               */
              function safeApprove(
                  Token token,
                  address spender,
                  uint256 amount
              ) internal {
                  if (isNative(token)) {
                      return;
                  }
                  toIERC20(token).safeApprove(spender, amount);
              }
              /**
               * @dev ensures that the spender has sufficient allowance
               *
               * note that the function does not perform any action if the native token is provided
               */
              function ensureApprove(
                  Token token,
                  address spender,
                  uint256 amount
              ) internal {
                  if (isNative(token)) {
                      return;
                  }
                  toIERC20(token).ensureApprove(spender, amount);
              }
              /**
               * @dev performs an EIP2612 permit
               */
              function permit(
                  Token token,
                  address owner,
                  address spender,
                  uint256 tokenAmount,
                  uint256 deadline,
                  Signature memory signature
              ) internal {
                  if (isNative(token)) {
                      revert PermitUnsupported();
                  }
                  // permit the amount the owner is trying to deposit. Please note, that if the base token doesn't support
                  // EIP2612 permit - either this call or the inner safeTransferFrom will revert
                  IERC20Permit(address(token)).permit(
                      owner,
                      spender,
                      tokenAmount,
                      deadline,
                      signature.v,
                      signature.r,
                      signature.s
                  );
              }
              /**
               * @dev compares between a token and another raw ERC20 token
               */
              function isEqual(Token token, IERC20 erc20Token) internal pure returns (bool) {
                  return toIERC20(token) == erc20Token;
              }
              /**
               * @dev utility function that converts a token to an IERC20
               */
              function toIERC20(Token token) internal pure returns (IERC20) {
                  return IERC20(address(token));
              }
              /**
               * @dev utility function that converts a token to an ERC20
               */
              function toERC20(Token token) internal pure returns (ERC20) {
                  return ERC20(address(token));
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev burnable ERC20 interface
           */
          interface IERC20Burnable {
              /**
               * @dev Destroys tokens from the caller.
               */
              function burn(uint256 amount) external;
              /**
               * @dev Destroys tokens from a recipient, deducting from the caller's allowance
               *
               * requirements:
               *
               * - the caller must have allowance for recipient's tokens of at least the specified amount
               */
              function burnFrom(address recipient, uint256 amount) external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          uint32 constant PPM_RESOLUTION = 1_000_000;
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          struct Fraction {
              uint256 n;
              uint256 d;
          }
          struct Fraction112 {
              uint112 n;
              uint112 d;
          }
          error InvalidFraction();
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { Fraction, Fraction112, InvalidFraction } from "./Fraction.sol";
          import { MathEx } from "./MathEx.sol";
          // solhint-disable-next-line func-visibility
          function zeroFraction() pure returns (Fraction memory) {
              return Fraction({ n: 0, d: 1 });
          }
          // solhint-disable-next-line func-visibility
          function zeroFraction112() pure returns (Fraction112 memory) {
              return Fraction112({ n: 0, d: 1 });
          }
          /**
           * @dev this library provides a set of fraction operations
           */
          library FractionLibrary {
              /**
               * @dev returns whether a standard fraction is valid
               */
              function isValid(Fraction memory fraction) internal pure returns (bool) {
                  return fraction.d != 0;
              }
              /**
               * @dev returns whether a 112-bit fraction is valid
               */
              function isValid(Fraction112 memory fraction) internal pure returns (bool) {
                  return fraction.d != 0;
              }
              /**
               * @dev returns whether a standard fraction is positive
               */
              function isPositive(Fraction memory fraction) internal pure returns (bool) {
                  return isValid(fraction) && fraction.n != 0;
              }
              /**
               * @dev returns whether a 112-bit fraction is positive
               */
              function isPositive(Fraction112 memory fraction) internal pure returns (bool) {
                  return isValid(fraction) && fraction.n != 0;
              }
              /**
               * @dev returns the inverse of a given fraction
               */
              function inverse(Fraction memory fraction) internal pure returns (Fraction memory) {
                  Fraction memory invFraction = Fraction({ n: fraction.d, d: fraction.n });
                  if (!isValid(invFraction)) {
                      revert InvalidFraction();
                  }
                  return invFraction;
              }
              /**
               * @dev returns the inverse of a given fraction
               */
              function inverse(Fraction112 memory fraction) internal pure returns (Fraction112 memory) {
                  Fraction112 memory invFraction = Fraction112({ n: fraction.d, d: fraction.n });
                  if (!isValid(invFraction)) {
                      revert InvalidFraction();
                  }
                  return invFraction;
              }
              /**
               * @dev reduces a standard fraction to a 112-bit fraction
               */
              function toFraction112(Fraction memory fraction) internal pure returns (Fraction112 memory) {
                  Fraction memory reducedFraction = MathEx.reducedFraction(fraction, type(uint112).max);
                  return Fraction112({ n: uint112(reducedFraction.n), d: uint112(reducedFraction.d) });
              }
              /**
               * @dev expands a 112-bit fraction to a standard fraction
               */
              function fromFraction112(Fraction112 memory fraction) internal pure returns (Fraction memory) {
                  return Fraction({ n: fraction.n, d: fraction.d });
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
          import { Fraction, InvalidFraction } from "./Fraction.sol";
          import { PPM_RESOLUTION } from "./Constants.sol";
          uint256 constant ONE = 0x80000000000000000000000000000000;
          uint256 constant LN2 = 0x58b90bfbe8e7bcd5e4f1d9cc01f97b57;
          struct Uint512 {
              uint256 hi; // 256 most significant bits
              uint256 lo; // 256 least significant bits
          }
          struct Sint256 {
              uint256 value;
              bool isNeg;
          }
          /**
           * @dev this library provides a set of complex math operations
           */
          library MathEx {
              error Overflow();
              /**
               * @dev returns `2 ^ f` by calculating `e ^ (f * ln(2))`, where `e` is Euler's number:
               * - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible
               * - The exponentiation of each binary exponent is given (pre-calculated)
               * - The exponentiation of r is calculated via Taylor series for e^x, where x = r
               * - The exponentiation of the input is calculated by multiplying the intermediate results above
               * - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859
               */
              function exp2(Fraction memory f) internal pure returns (Fraction memory) {
                  uint256 x = MathEx.mulDivF(LN2, f.n, f.d);
                  uint256 y;
                  uint256 z;
                  uint256 n;
                  if (x >= (ONE << 4)) {
                      revert Overflow();
                  }
                  unchecked {
                      z = y = x % (ONE >> 3); // get the input modulo 2^(-3)
                      z = (z * y) / ONE;
                      n += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
                      z = (z * y) / ONE;
                      n += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
                      z = (z * y) / ONE;
                      n += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
                      z = (z * y) / ONE;
                      n += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
                      z = (z * y) / ONE;
                      n += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
                      z = (z * y) / ONE;
                      n += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
                      z = (z * y) / ONE;
                      n += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
                      z = (z * y) / ONE;
                      n += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
                      z = (z * y) / ONE;
                      n += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
                      z = (z * y) / ONE;
                      n += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
                      z = (z * y) / ONE;
                      n += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
                      z = (z * y) / ONE;
                      n += z * 0x000000000001c638; // add y^16 * (20! / 16!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
                      z = (z * y) / ONE;
                      n += z * 0x000000000000017c; // add y^18 * (20! / 18!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000000000014; // add y^19 * (20! / 19!)
                      z = (z * y) / ONE;
                      n += z * 0x0000000000000001; // add y^20 * (20! / 20!)
                      n = n / 0x21c3677c82b40000 + y + ONE; // divide by 20! and then add y^1 / 1! + y^0 / 0!
                      if ((x & (ONE >> 3)) != 0)
                          n = (n * 0x1c3d6a24ed82218787d624d3e5eba95f9) / 0x18ebef9eac820ae8682b9793ac6d1e776; // multiply by e^(2^-3)
                      if ((x & (ONE >> 2)) != 0)
                          n = (n * 0x18ebef9eac820ae8682b9793ac6d1e778) / 0x1368b2fc6f9609fe7aceb46aa619baed4; // multiply by e^(2^-2)
                      if ((x & (ONE >> 1)) != 0)
                          n = (n * 0x1368b2fc6f9609fe7aceb46aa619baed5) / 0x0bc5ab1b16779be3575bd8f0520a9f21f; // multiply by e^(2^-1)
                      if ((x & (ONE << 0)) != 0)
                          n = (n * 0x0bc5ab1b16779be3575bd8f0520a9f21e) / 0x0454aaa8efe072e7f6ddbab84b40a55c9; // multiply by e^(2^+0)
                      if ((x & (ONE << 1)) != 0)
                          n = (n * 0x0454aaa8efe072e7f6ddbab84b40a55c5) / 0x00960aadc109e7a3bf4578099615711ea; // multiply by e^(2^+1)
                      if ((x & (ONE << 2)) != 0)
                          n = (n * 0x00960aadc109e7a3bf4578099615711d7) / 0x0002bf84208204f5977f9a8cf01fdce3d; // multiply by e^(2^+2)
                      if ((x & (ONE << 3)) != 0)
                          n = (n * 0x0002bf84208204f5977f9a8cf01fdc307) / 0x0000003c6ab775dd0b95b4cbee7e65d11; // multiply by e^(2^+3)
                  }
                  return Fraction({ n: n, d: ONE });
              }
              /**
               * @dev returns a fraction with reduced components
               */
              function reducedFraction(Fraction memory fraction, uint256 max) internal pure returns (Fraction memory) {
                  uint256 scale = Math.ceilDiv(Math.max(fraction.n, fraction.d), max);
                  Fraction memory reduced = Fraction({ n: fraction.n / scale, d: fraction.d / scale });
                  if (reduced.d == 0) {
                      revert InvalidFraction();
                  }
                  return reduced;
              }
              /**
               * @dev returns the weighted average of two fractions
               */
              function weightedAverage(
                  Fraction memory fraction1,
                  Fraction memory fraction2,
                  uint256 weight1,
                  uint256 weight2
              ) internal pure returns (Fraction memory) {
                  return
                      Fraction({
                          n: fraction1.n * fraction2.d * weight1 + fraction1.d * fraction2.n * weight2,
                          d: fraction1.d * fraction2.d * (weight1 + weight2)
                      });
              }
              /**
               * @dev returns whether or not the deviation of an offset sample from a base sample is within a permitted range
               * for example, if the maximum permitted deviation is 5%, then evaluate `95% * base <= offset <= 105% * base`
               */
              function isInRange(
                  Fraction memory baseSample,
                  Fraction memory offsetSample,
                  uint32 maxDeviationPPM
              ) internal pure returns (bool) {
                  Uint512 memory min = mul512(baseSample.n, offsetSample.d * (PPM_RESOLUTION - maxDeviationPPM));
                  Uint512 memory mid = mul512(baseSample.d, offsetSample.n * PPM_RESOLUTION);
                  Uint512 memory max = mul512(baseSample.n, offsetSample.d * (PPM_RESOLUTION + maxDeviationPPM));
                  return lte512(min, mid) && lte512(mid, max);
              }
              /**
               * @dev returns an `Sint256` positive representation of an unsigned integer
               */
              function toPos256(uint256 n) internal pure returns (Sint256 memory) {
                  return Sint256({ value: n, isNeg: false });
              }
              /**
               * @dev returns an `Sint256` negative representation of an unsigned integer
               */
              function toNeg256(uint256 n) internal pure returns (Sint256 memory) {
                  return Sint256({ value: n, isNeg: true });
              }
              /**
               * @dev returns the largest integer smaller than or equal to `x * y / z`
               */
              function mulDivF(
                  uint256 x,
                  uint256 y,
                  uint256 z
              ) internal pure returns (uint256) {
                  Uint512 memory xy = mul512(x, y);
                  // if `x * y < 2 ^ 256`
                  if (xy.hi == 0) {
                      return xy.lo / z;
                  }
                  // assert `x * y / z < 2 ^ 256`
                  if (xy.hi >= z) {
                      revert Overflow();
                  }
                  uint256 m = _mulMod(x, y, z); // `m = x * y % z`
                  Uint512 memory n = _sub512(xy, m); // `n = x * y - m` hence `n / z = floor(x * y / z)`
                  // if `n < 2 ^ 256`
                  if (n.hi == 0) {
                      return n.lo / z;
                  }
                  uint256 p = _unsafeSub(0, z) & z; // `p` is the largest power of 2 which `z` is divisible by
                  uint256 q = _div512(n, p); // `n` is divisible by `p` because `n` is divisible by `z` and `z` is divisible by `p`
                  uint256 r = _inv256(z / p); // `z / p = 1 mod 2` hence `inverse(z / p) = 1 mod 2 ^ 256`
                  return _unsafeMul(q, r); // `q * r = (n / p) * inverse(z / p) = n / z`
              }
              /**
               * @dev returns the smallest integer larger than or equal to `x * y / z`
               */
              function mulDivC(
                  uint256 x,
                  uint256 y,
                  uint256 z
              ) internal pure returns (uint256) {
                  uint256 w = mulDivF(x, y, z);
                  if (_mulMod(x, y, z) > 0) {
                      if (w >= type(uint256).max) {
                          revert Overflow();
                      }
                      return w + 1;
                  }
                  return w;
              }
              /**
               * @dev returns the maximum of `n1 - n2` and 0
               */
              function subMax0(uint256 n1, uint256 n2) internal pure returns (uint256) {
                  return n1 > n2 ? n1 - n2 : 0;
              }
              /**
               * @dev returns the value of `x > y`
               */
              function gt512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return x.hi > y.hi || (x.hi == y.hi && x.lo > y.lo);
              }
              /**
               * @dev returns the value of `x < y`
               */
              function lt512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return x.hi < y.hi || (x.hi == y.hi && x.lo < y.lo);
              }
              /**
               * @dev returns the value of `x >= y`
               */
              function gte512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return !lt512(x, y);
              }
              /**
               * @dev returns the value of `x <= y`
               */
              function lte512(Uint512 memory x, Uint512 memory y) internal pure returns (bool) {
                  return !gt512(x, y);
              }
              /**
               * @dev returns the value of `x * y`
               */
              function mul512(uint256 x, uint256 y) internal pure returns (Uint512 memory) {
                  uint256 p = _mulModMax(x, y);
                  uint256 q = _unsafeMul(x, y);
                  if (p >= q) {
                      return Uint512({ hi: p - q, lo: q });
                  }
                  return Uint512({ hi: _unsafeSub(p, q) - 1, lo: q });
              }
              /**
               * @dev returns the value of `x - y`, given that `x >= y`
               */
              function _sub512(Uint512 memory x, uint256 y) private pure returns (Uint512 memory) {
                  if (x.lo >= y) {
                      return Uint512({ hi: x.hi, lo: x.lo - y });
                  }
                  return Uint512({ hi: x.hi - 1, lo: _unsafeSub(x.lo, y) });
              }
              /**
               * @dev returns the value of `x / pow2n`, given that `x` is divisible by `pow2n`
               */
              function _div512(Uint512 memory x, uint256 pow2n) private pure returns (uint256) {
                  uint256 pow2nInv = _unsafeAdd(_unsafeSub(0, pow2n) / pow2n, 1); // `1 << (256 - n)`
                  return _unsafeMul(x.hi, pow2nInv) | (x.lo / pow2n); // `(x.hi << (256 - n)) | (x.lo >> n)`
              }
              /**
               * @dev returns the inverse of `d` modulo `2 ^ 256`, given that `d` is congruent to `1` modulo `2`
               */
              function _inv256(uint256 d) private pure returns (uint256) {
                  // approximate the root of `f(x) = 1 / x - d` using the newton–raphson convergence method
                  uint256 x = 1;
                  for (uint256 i = 0; i < 8; i++) {
                      x = _unsafeMul(x, _unsafeSub(2, _unsafeMul(x, d))); // `x = x * (2 - x * d) mod 2 ^ 256`
                  }
                  return x;
              }
              /**
               * @dev returns `(x + y) % 2 ^ 256`
               */
              function _unsafeAdd(uint256 x, uint256 y) private pure returns (uint256) {
                  unchecked {
                      return x + y;
                  }
              }
              /**
               * @dev returns `(x - y) % 2 ^ 256`
               */
              function _unsafeSub(uint256 x, uint256 y) private pure returns (uint256) {
                  unchecked {
                      return x - y;
                  }
              }
              /**
               * @dev returns `(x * y) % 2 ^ 256`
               */
              function _unsafeMul(uint256 x, uint256 y) private pure returns (uint256) {
                  unchecked {
                      return x * y;
                  }
              }
              /**
               * @dev returns `x * y % (2 ^ 256 - 1)`
               */
              function _mulModMax(uint256 x, uint256 y) private pure returns (uint256) {
                  return mulmod(x, y, type(uint256).max);
              }
              /**
               * @dev returns `x * y % z`
               */
              function _mulMod(
                  uint256 x,
                  uint256 y,
                  uint256 z
              ) private pure returns (uint256) {
                  return mulmod(x, y, z);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev this contract abstracts the block timestamp in order to allow for more flexible control in tests
           */
          abstract contract Time {
              /**
               * @dev returns the current time
               */
              function _time() internal view virtual returns (uint32) {
                  return uint32(block.timestamp);
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
          import { IUpgradeable } from "./interfaces/IUpgradeable.sol";
          import { AccessDenied } from "./Utils.sol";
          /**
           * @dev this contract provides common utilities for upgradeable contracts
           */
          abstract contract Upgradeable is IUpgradeable, AccessControlEnumerableUpgradeable {
              error AlreadyInitialized();
              // the admin role is used to allow a non-proxy admin to perform additional initialization/setup during contract
              // upgrades
              bytes32 internal constant ROLE_ADMIN = keccak256("ROLE_ADMIN");
              uint32 internal constant MAX_GAP = 50;
              uint16 internal _initializations;
              // upgrade forward-compatibility storage gap
              uint256[MAX_GAP - 1] private __gap;
              // solhint-disable func-name-mixedcase
              /**
               * @dev initializes the contract and its parents
               */
              function __Upgradeable_init() internal onlyInitializing {
                  __AccessControl_init();
                  __Upgradeable_init_unchained();
              }
              /**
               * @dev performs contract-specific initialization
               */
              function __Upgradeable_init_unchained() internal onlyInitializing {
                  _initializations = 1;
                  // set up administrative roles
                  _setRoleAdmin(ROLE_ADMIN, ROLE_ADMIN);
                  // allow the deployer to initially be the admin of the contract
                  _setupRole(ROLE_ADMIN, msg.sender);
              }
              // solhint-enable func-name-mixedcase
              modifier onlyAdmin() {
                  _hasRole(ROLE_ADMIN, msg.sender);
                  _;
              }
              modifier onlyRoleMember(bytes32 role) {
                  _hasRole(role, msg.sender);
                  _;
              }
              function version() public view virtual override returns (uint16);
              /**
               * @dev returns the admin role
               */
              function roleAdmin() external pure returns (bytes32) {
                  return ROLE_ADMIN;
              }
              /**
               * @dev performs post-upgrade initialization
               *
               * requirements:
               *
               * - this must can be called only once per-upgrade
               */
              function postUpgrade(bytes calldata data) external {
                  uint16 initializations = _initializations + 1;
                  if (initializations != version()) {
                      revert AlreadyInitialized();
                  }
                  _initializations = initializations;
                  _postUpgrade(data);
              }
              /**
               * @dev an optional post-upgrade callback that can be implemented by child contracts
               */
              function _postUpgrade(
                  bytes calldata /* data */
              ) internal virtual {}
              function _hasRole(bytes32 role, address account) internal view {
                  if (!hasRole(role, account)) {
                      revert AccessDenied();
                  }
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { PPM_RESOLUTION } from "./Constants.sol";
          error AccessDenied();
          error AlreadyExists();
          error DoesNotExist();
          error InvalidAddress();
          error InvalidExternalAddress();
          error InvalidFee();
          error InvalidPool();
          error InvalidPoolCollection();
          error InvalidStakedBalance();
          error InvalidToken();
          error InvalidType();
          error InvalidParam();
          error NotEmpty();
          error NotPayable();
          error ZeroValue();
          /**
           * @dev common utilities
           */
          abstract contract Utils {
              // allows execution by the caller only
              modifier only(address caller) {
                  _only(caller);
                  _;
              }
              function _only(address caller) internal view {
                  if (msg.sender != caller) {
                      revert AccessDenied();
                  }
              }
              // verifies that a value is greater than zero
              modifier greaterThanZero(uint256 value) {
                  _greaterThanZero(value);
                  _;
              }
              // error message binary size optimization
              function _greaterThanZero(uint256 value) internal pure {
                  if (value == 0) {
                      revert ZeroValue();
                  }
              }
              // validates an address - currently only checks that it isn't null
              modifier validAddress(address addr) {
                  _validAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validAddress(address addr) internal pure {
                  if (addr == address(0)) {
                      revert InvalidAddress();
                  }
              }
              // validates an external address - currently only checks that it isn't null or this
              modifier validExternalAddress(address addr) {
                  _validExternalAddress(addr);
                  _;
              }
              // error message binary size optimization
              function _validExternalAddress(address addr) internal view {
                  if (addr == address(0) || addr == address(this)) {
                      revert InvalidExternalAddress();
                  }
              }
              // ensures that the fee is valid
              modifier validFee(uint32 fee) {
                  _validFee(fee);
                  _;
              }
              // error message binary size optimization
              function _validFee(uint32 fee) internal pure {
                  if (fee > PPM_RESOLUTION) {
                      revert InvalidFee();
                  }
              }
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev Owned interface
           */
          interface IOwned {
              /**
               * @dev returns the address of the current owner
               */
              function owner() external view returns (address);
              /**
               * @dev allows transferring the contract ownership
               *
               * requirements:
               *
               * - the caller must be the owner of the contract
               * - the new owner still needs to accept the transfer
               */
              function transferOwnership(address ownerCandidate) external;
              /**
               * @dev used by a new owner to accept an ownership transfer
               */
              function acceptOwnership() external;
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IVersioned } from "./IVersioned.sol";
          import { IAccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/IAccessControlEnumerableUpgradeable.sol";
          /**
           * @dev this is the common interface for upgradeable contracts
           */
          interface IUpgradeable is IAccessControlEnumerableUpgradeable, IVersioned {
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          /**
           * @dev an interface for a versioned contract
           */
          interface IVersioned {
              function version() external view returns (uint16);
          }
          // SPDX-License-Identifier: SEE LICENSE IN LICENSE
          pragma solidity 0.8.13;
          import { IUpgradeable } from "../../utility/interfaces/IUpgradeable.sol";
          import { Token } from "../../token/Token.sol";
          // the asset manager role is required to access all the funds
          bytes32 constant ROLE_ASSET_MANAGER = keccak256("ROLE_ASSET_MANAGER");
          interface IVault is IUpgradeable {
              /**
               * @dev triggered when tokens have been withdrawn from the vault
               */
              event FundsWithdrawn(Token indexed token, address indexed caller, address indexed target, uint256 amount);
              /**
               * @dev triggered when tokens have been burned from the vault
               */
              event FundsBurned(Token indexed token, address indexed caller, uint256 amount);
              /**
               * @dev tells whether the vault accepts native token deposits
               */
              function isPayable() external view returns (bool);
              /**
               * @dev withdraws funds held by the contract and sends them to an account
               */
              function withdrawFunds(
                  Token token,
                  address payable target,
                  uint256 amount
              ) external;
              /**
               * @dev burns funds held by the contract
               */
              function burn(Token token, uint256 amount) external;
          }