ETH Price: $2,532.27 (-1.62%)

Transaction Decoder

Block:
18432076 at Oct-26-2023 04:41:47 AM +UTC
Transaction Fee:
0.00295323934016097 ETH $7.48
Gas Used:
134,505 Gas / 21.956353594 Gwei

Emitted Events:

170 ERC1967Proxy.0x33e6f269701b611439c5bd9eae485d1b2f10d29b632a6f0d5688c93c2d77af1f( 0x33e6f269701b611439c5bd9eae485d1b2f10d29b632a6f0d5688c93c2d77af1f, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 00000000000000000000000000000000000000000000000000000777c0a63b79, 000000000000000000000000000000000000000000000000000000006539ee0b )
171 ERC1967Proxy.0x31eeb6a0d26c29b4c243d704ff4ae6feebcc2e8b123df7ea0bd12083c3083cb8( 0x31eeb6a0d26c29b4c243d704ff4ae6feebcc2e8b123df7ea0bd12083c3083cb8, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 000000000000000000000000000000000000000000000000000001c69229e343, 0000000000000000000000000000000000000000000000000000000000000000 )
172 IlluviumERC20.Minted( _by=ERC1967Proxy, _to=[Sender] 0x5398bf1c06652a8d69795397dff5c3bcb4822708, _value=18709031959012747264 )
173 IlluviumERC20.Transferred( _by=ERC1967Proxy, _from=0x0000000000000000000000000000000000000000, _to=[Sender] 0x5398bf1c06652a8d69795397dff5c3bcb4822708, _value=18709031959012747264 )
174 IlluviumERC20.Transfer( _from=0x0000000000000000000000000000000000000000, _to=[Sender] 0x5398bf1c06652a8d69795397dff5c3bcb4822708, _value=18709031959012747264 )
175 ERC1967Proxy.0x30d438cf38db3f29630029343ab01e801276913697f489030c0613ddc4aadaaf( 0x30d438cf38db3f29630029343ab01e801276913697f489030c0613ddc4aadaaf, 0x0000000000000000000000005398bf1c06652a8d69795397dff5c3bcb4822708, 00000000000000000000000000000000000000000000000103a3d5712862a800, 0000000000000000000000000000000000000000000000000000000000000001 )

Account State Difference:

  Address   Before After State Difference Code
0x5398BF1C...cB4822708
2.270227935878031342 Eth
Nonce: 128
2.267274696537870372 Eth
Nonce: 129
0.00295323934016097
0x767FE9ED...959D7ca0E
0x7f5f854F...e2B34291D
(Illuvium: Migrate)
(beaverbuild)
15.78199359746399361 Eth15.78200704796399361 Eth0.0000134505

Execution Trace

ERC1967Proxy.1bfe80b8( )
  • ILVPool.unstakeMultiple( _stakes=, _unstakingYield=True )
    • ERC1967Proxy.STATICCALL( )
      • PoolFactory.DELEGATECALL( )
      • ERC1967Proxy.STATICCALL( )
        • PoolFactory.DELEGATECALL( )
        • ERC1967Proxy.STATICCALL( )
          • PoolFactory.DELEGATECALL( )
          • ERC1967Proxy.STATICCALL( )
            • PoolFactory.DELEGATECALL( )
            • ERC1967Proxy.c59e2aca( )
              • PoolFactory.mintYieldTo( _to=0x5398BF1C06652a8d69795397dff5C3BcB4822708, _value=18709031959012747264, _useSILV=False )
                • IlluviumERC20.mint( _to=0x5398BF1C06652a8d69795397dff5C3BcB4822708, _value=18709031959012747264 )
                  File 1 of 5: ERC1967Proxy
                  // SPDX-License-Identifier: MIT
                  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 internall call site, it will return directly to the external caller.
                       */
                      function _delegate(address implementation) internal virtual {
                          // solhint-disable-next-line no-inline-assembly
                          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 {
                      }
                  }
                  
                  
                  /**
                   * @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._
                   *
                   */
                  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 {
                          _setImplementation(newImplementation);
                          emit Upgraded(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 _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
                          address oldImplementation = _getImplementation();
                  
                          // Initial upgrade and setup call
                          _setImplementation(newImplementation);
                          if (data.length > 0 || forceCall) {
                              Address.functionDelegateCall(newImplementation, data);
                          }
                  
                          // Perform rollback test if not already in progress
                          StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
                          if (!rollbackTesting.value) {
                              // Trigger rollback using upgradeTo from the new implementation
                              rollbackTesting.value = true;
                              Address.functionDelegateCall(
                                  newImplementation,
                                  abi.encodeWithSignature(
                                      "upgradeTo(address)",
                                      oldImplementation
                                  )
                              );
                              rollbackTesting.value = false;
                              // Check rollback was effective
                              require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                              // Finally reset to the new implementation and log the upgrade
                              _setImplementation(newImplementation);
                              emit Upgraded(newImplementation);
                          }
                      }
                  
                      /**
                       * @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);
                          }
                      }
                  
                      /**
                       * @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 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);
                  }
                  
                  /**
                   * @dev Collection of functions related to the address type
                   */
                  library Address {
                      /**
                       * @dev Returns true if `account` is a contract.
                       *
                       * [IMPORTANT]
                       * ====
                       * It is unsafe to assume that an address for which this function returns
                       * false is an externally-owned account (EOA) and not a contract.
                       *
                       * Among others, `isContract` will return false for the following
                       * types of addresses:
                       *
                       *  - an externally-owned account
                       *  - a contract in construction
                       *  - an address where a contract will be created
                       *  - an address where a contract lived, but was destroyed
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize, which returns 0 for contracts in
                          // construction, since the code is only stored at the end of the
                          // constructor execution.
                  
                          uint256 size;
                          // solhint-disable-next-line no-inline-assembly
                          assembly { size := extcodesize(account) }
                          return size > 0;
                      }
                  
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                  
                          // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                          (bool success, ) = recipient.call{ value: amount }("");
                          require(success, "Address: unable to send value, recipient may have reverted");
                      }
                  
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain`call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          require(isContract(target), "Address: call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.call{ value: value }(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                          require(isContract(target), "Address: static call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          require(isContract(target), "Address: delegate call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              // Look for revert reason and bubble it up if present
                              if (returndata.length > 0) {
                                  // The easiest way to bubble the revert reason is using memory via assembly
                  
                                  // solhint-disable-next-line no-inline-assembly
                                  assembly {
                                      let returndata_size := mload(returndata)
                                      revert(add(32, returndata), returndata_size)
                                  }
                              } else {
                                  revert(errorMessage);
                              }
                          }
                      }
                  }
                  
                  /**
                   * @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
                          }
                      }
                  }
                  
                  /*
                   * @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) {
                          this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                          return msg.data;
                      }
                  }
                  
                  /**
                   * @dev Contract module which provides a basic access control mechanism, where
                   * there is an account (an owner) that can be granted exclusive access to
                   * specific functions.
                   *
                   * By default, the owner account will be the one that deploys the contract. This
                   * can later be changed with {transferOwnership}.
                   *
                   * This module is used through inheritance. It will make available the modifier
                   * `onlyOwner`, which can be applied to your functions to restrict their use to
                   * the owner.
                   */
                  abstract contract Ownable is Context {
                      address private _owner;
                  
                      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                  
                      /**
                       * @dev Initializes the contract setting the deployer as the initial owner.
                       */
                      constructor () {
                          address msgSender = _msgSender();
                          _owner = msgSender;
                          emit OwnershipTransferred(address(0), msgSender);
                      }
                  
                      /**
                       * @dev Returns the address of the current owner.
                       */
                      function owner() public view virtual returns (address) {
                          return _owner;
                      }
                  
                      /**
                       * @dev Throws if called by any account other than the owner.
                       */
                      modifier onlyOwner() {
                          require(owner() == _msgSender(), "Ownable: caller is not the owner");
                          _;
                      }
                  
                      /**
                       * @dev Leaves the contract without owner. It will not be possible to call
                       * `onlyOwner` functions anymore. Can only be called by the current owner.
                       *
                       * NOTE: Renouncing ownership will leave the contract without an owner,
                       * thereby removing any functionality that is only available to the owner.
                       */
                      function renounceOwnership() public virtual onlyOwner {
                          emit OwnershipTransferred(_owner, address(0));
                          _owner = address(0);
                      }
                  
                      /**
                       * @dev Transfers ownership of the contract to a new account (`newOwner`).
                       * Can only be called by the current owner.
                       */
                      function transferOwnership(address newOwner) public virtual onlyOwner {
                          require(newOwner != address(0), "Ownable: new owner is the zero address");
                          emit OwnershipTransferred(_owner, newOwner);
                          _owner = newOwner;
                      }
                  }
                  
                  /**
                   * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
                   * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
                   */
                  contract ProxyAdmin is Ownable {
                  
                      /**
                       * @dev Returns the current implementation of `proxy`.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                          // We need to manually run the static call since the getter cannot be flagged as view
                          // bytes4(keccak256("implementation()")) == 0x5c60da1b
                          (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
                          require(success);
                          return abi.decode(returndata, (address));
                      }
                  
                      /**
                       * @dev Returns the current admin of `proxy`.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                          // We need to manually run the static call since the getter cannot be flagged as view
                          // bytes4(keccak256("admin()")) == 0xf851a440
                          (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
                          require(success);
                          return abi.decode(returndata, (address));
                      }
                  
                      /**
                       * @dev Changes the admin of `proxy` to `newAdmin`.
                       *
                       * Requirements:
                       *
                       * - This contract must be the current admin of `proxy`.
                       */
                      function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
                          proxy.changeAdmin(newAdmin);
                      }
                  
                      /**
                       * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
                          proxy.upgradeTo(implementation);
                      }
                  
                      /**
                       * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
                       * {TransparentUpgradeableProxy-upgradeToAndCall}.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
                          proxy.upgradeToAndCall{value: msg.value}(implementation, data);
                      }
                  }
                  
                  
                  /**
                   * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
                   * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
                   * continuation of the upgradability.
                   *
                   * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
                   *
                   * _Available since v4.1._
                   */
                  abstract contract UUPSUpgradeable is ERC1967Upgrade {
                      function upgradeTo(address newImplementation) external virtual {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, bytes(""), false);
                      }
                  
                      function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, data, true);
                      }
                  
                      function _authorizeUpgrade(address newImplementation) internal virtual;
                  }
                  
                  
                  abstract contract Proxiable is UUPSUpgradeable {
                      function _authorizeUpgrade(address newImplementation) internal override {
                          _beforeUpgrade(newImplementation);
                      }
                  
                      function _beforeUpgrade(address newImplementation) internal virtual;
                  }
                  
                  contract ChildOfProxiable is Proxiable {
                      function _beforeUpgrade(address newImplementation) internal virtual override {}
                  }
                  
                  
                  /**
                   * @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();
                      }
                  }
                  
                  /**
                   * @dev This contract implements a proxy that is upgradeable by an admin.
                   *
                   * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
                   * clashing], which can potentially be used in an attack, this contract uses the
                   * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
                   * things that go hand in hand:
                   *
                   * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
                   * that call matches one of the admin functions exposed by the proxy itself.
                   * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
                   * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
                   * "admin cannot fallback to proxy target".
                   *
                   * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
                   * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
                   * to sudden errors when trying to call a function from the proxy implementation.
                   *
                   * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
                   * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
                   */
                  contract TransparentUpgradeableProxy is ERC1967Proxy {
                      /**
                       * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
                       * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
                       */
                      constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                          assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                          _changeAdmin(admin_);
                      }
                  
                      /**
                       * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
                       */
                      modifier ifAdmin() {
                          if (msg.sender == _getAdmin()) {
                              _;
                          } else {
                              _fallback();
                          }
                      }
                  
                      /**
                       * @dev Returns the current admin.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
                       *
                       * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                       * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                       * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                       */
                      function admin() external ifAdmin returns (address admin_) {
                          admin_ = _getAdmin();
                      }
                  
                      /**
                       * @dev Returns the current implementation.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
                       *
                       * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                       * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                       * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                       */
                      function implementation() external ifAdmin returns (address implementation_) {
                          implementation_ = _implementation();
                      }
                  
                      /**
                       * @dev Changes the admin of the proxy.
                       *
                       * Emits an {AdminChanged} event.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
                       */
                      function changeAdmin(address newAdmin) external virtual ifAdmin {
                          _changeAdmin(newAdmin);
                      }
                  
                      /**
                       * @dev Upgrade the implementation of the proxy.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
                       */
                      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.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
                       */
                      function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                          _upgradeToAndCall(newImplementation, data, true);
                      }
                  
                      /**
                       * @dev Returns the current admin.
                       */
                      function _admin() internal view virtual returns (address) {
                          return _getAdmin();
                      }
                  
                      /**
                       * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
                       */
                      function _beforeFallback() internal virtual override {
                          require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                          super._beforeFallback();
                      }
                  }
                  
                  
                  // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
                  contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
                      constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
                  }

                  File 2 of 5: IlluviumERC20
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.1;
                  /**
                   * @title ERC20 token receiver interface
                   *
                   * @dev Interface for any contract that wants to support safe transfers
                   *      from ERC20 token smart contracts.
                   * @dev Inspired by ERC721 and ERC223 token standards
                   *
                   * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
                   * @dev See https://github.com/ethereum/EIPs/issues/223
                   *
                   * @author Basil Gorin
                   */
                  interface ERC20Receiver {
                    /**
                     * @notice Handle the receipt of a ERC20 token(s)
                     * @dev The ERC20 smart contract calls this function on the recipient
                     *      after a successful transfer (`safeTransferFrom`).
                     *      This function MAY throw to revert and reject the transfer.
                     *      Return of other than the magic value MUST result in the transaction being reverted.
                     * @notice The contract address is always the message sender.
                     *      A wallet/broker/auction application MUST implement the wallet interface
                     *      if it will accept safe transfers.
                     * @param _operator The address which called `safeTransferFrom` function
                     * @param _from The address which previously owned the token
                     * @param _value amount of tokens which is being transferred
                     * @param _data additional data with no specified format
                     * @return `bytes4(keccak256("onERC20Received(address,address,uint256,bytes)"))` unless throwing
                     */
                    function onERC20Received(address _operator, address _from, uint256 _value, bytes calldata _data) external returns(bytes4);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.1;
                  import "../utils/AddressUtils.sol";
                  import "../utils/AccessControl.sol";
                  import "./ERC20Receiver.sol";
                  /**
                   * @title Illuvium (ILV) ERC20 token
                   *
                   * @notice Illuvium is a core ERC20 token powering the game.
                   *      It serves as an in-game currency, is tradable on exchanges,
                   *      it powers up the governance protocol (Illuvium DAO) and participates in Yield Farming.
                   *
                   * @dev Token Summary:
                   *      - Symbol: ILV
                   *      - Name: Illuvium
                   *      - Decimals: 18
                   *      - Initial token supply: 7,000,000 ILV
                   *      - Maximum final token supply: 10,000,000 ILV
                   *          - Up to 3,000,000 ILV may get minted in 3 years period via yield farming
                   *      - Mintable: total supply may increase
                   *      - Burnable: total supply may decrease
                   *
                   * @dev Token balances and total supply are effectively 192 bits long, meaning that maximum
                   *      possible total supply smart contract is able to track is 2^192 (close to 10^40 tokens)
                   *
                   * @dev Smart contract doesn't use safe math. All arithmetic operations are overflow/underflow safe.
                   *      Additionally, Solidity 0.8.1 enforces overflow/underflow safety.
                   *
                   * @dev ERC20: reviewed according to https://eips.ethereum.org/EIPS/eip-20
                   *
                   * @dev ERC20: contract has passed OpenZeppelin ERC20 tests,
                   *      see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/token/ERC20/ERC20.behavior.js
                   *      see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/token/ERC20/ERC20.test.js
                   *      see adopted copies of these tests in the `test` folder
                   *
                   * @dev ERC223/ERC777: not supported;
                   *      send tokens via `safeTransferFrom` and implement `ERC20Receiver.onERC20Received` on the receiver instead
                   *
                   * @dev Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) - resolved
                   *      Related events and functions are marked with "ISBN:978-1-7281-3027-9" tag:
                   *        - event Transferred(address indexed _by, address indexed _from, address indexed _to, uint256 _value)
                   *        - event Approved(address indexed _owner, address indexed _spender, uint256 _oldValue, uint256 _value)
                   *        - function increaseAllowance(address _spender, uint256 _value) public returns (bool)
                   *        - function decreaseAllowance(address _spender, uint256 _value) public returns (bool)
                   *      See: https://ieeexplore.ieee.org/document/8802438
                   *      See: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * @author Basil Gorin
                   */
                  contract IlluviumERC20 is AccessControl {
                    /**
                     * @dev Smart contract unique identifier, a random number
                     * @dev Should be regenerated each time smart contact source code is changed
                     *      and changes smart contract itself is to be redeployed
                     * @dev Generated using https://www.random.org/bytes/
                     */
                    uint256 public constant TOKEN_UID = 0x83ecb176af7c4f35a45ff0018282e3a05a1018065da866182df12285866f5a2c;
                    /**
                     * @notice Name of the token: Illuvium
                     *
                     * @notice ERC20 name of the token (long name)
                     *
                     * @dev ERC20 `function name() public view returns (string)`
                     *
                     * @dev Field is declared public: getter name() is created when compiled,
                     *      it returns the name of the token.
                     */
                    string public constant name = "Illuvium";
                    /**
                     * @notice Symbol of the token: ILV
                     *
                     * @notice ERC20 symbol of that token (short name)
                     *
                     * @dev ERC20 `function symbol() public view returns (string)`
                     *
                     * @dev Field is declared public: getter symbol() is created when compiled,
                     *      it returns the symbol of the token
                     */
                    string public constant symbol = "ILV";
                    /**
                     * @notice Decimals of the token: 18
                     *
                     * @dev ERC20 `function decimals() public view returns (uint8)`
                     *
                     * @dev Field is declared public: getter decimals() is created when compiled,
                     *      it returns the number of decimals used to get its user representation.
                     *      For example, if `decimals` equals `6`, a balance of `1,500,000` tokens should
                     *      be displayed to a user as `1,5` (`1,500,000 / 10 ** 6`).
                     *
                     * @dev NOTE: This information is only used for _display_ purposes: it in
                     *      no way affects any of the arithmetic of the contract, including balanceOf() and transfer().
                     */
                    uint8 public constant decimals = 18;
                    /**
                     * @notice Total supply of the token: initially 7,000,000,
                     *      with the potential to grow up to 10,000,000 during yield farming period (3 years)
                     *
                     * @dev ERC20 `function totalSupply() public view returns (uint256)`
                     *
                     * @dev Field is declared public: getter totalSupply() is created when compiled,
                     *      it returns the amount of tokens in existence.
                     */
                    uint256 public totalSupply; // is set to 7 million * 10^18 in the constructor
                    /**
                     * @dev A record of all the token balances
                     * @dev This mapping keeps record of all token owners:
                     *      owner => balance
                     */
                    mapping(address => uint256) public tokenBalances;
                    /**
                     * @notice A record of each account's voting delegate
                     *
                     * @dev Auxiliary data structure used to sum up an account's voting power
                     *
                     * @dev This mapping keeps record of all voting power delegations:
                     *      voting delegator (token owner) => voting delegate
                     */
                    mapping(address => address) public votingDelegates;
                    /**
                     * @notice A voting power record binds voting power of a delegate to a particular
                     *      block when the voting power delegation change happened
                     */
                    struct VotingPowerRecord {
                      /*
                       * @dev block.number when delegation has changed; starting from
                       *      that block voting power value is in effect
                       */
                      uint64 blockNumber;
                      /*
                       * @dev cumulative voting power a delegate has obtained starting
                       *      from the block stored in blockNumber
                       */
                      uint192 votingPower;
                    }
                    /**
                     * @notice A record of each account's voting power
                     *
                     * @dev Primarily data structure to store voting power for each account.
                     *      Voting power sums up from the account's token balance and delegated
                     *      balances.
                     *
                     * @dev Stores current value and entire history of its changes.
                     *      The changes are stored as an array of checkpoints.
                     *      Checkpoint is an auxiliary data structure containing voting
                     *      power (number of votes) and block number when the checkpoint is saved
                     *
                     * @dev Maps voting delegate => voting power record
                     */
                    mapping(address => VotingPowerRecord[]) public votingPowerHistory;
                    /**
                     * @dev A record of nonces for signing/validating signatures in `delegateWithSig`
                     *      for every delegate, increases after successful validation
                     *
                     * @dev Maps delegate address => delegate nonce
                     */
                    mapping(address => uint256) public nonces;
                    /**
                     * @notice A record of all the allowances to spend tokens on behalf
                     * @dev Maps token owner address to an address approved to spend
                     *      some tokens on behalf, maps approved address to that amount
                     * @dev owner => spender => value
                     */
                    mapping(address => mapping(address => uint256)) public transferAllowances;
                    /**
                     * @notice Enables ERC20 transfers of the tokens
                     *      (transfer by the token owner himself)
                     * @dev Feature FEATURE_TRANSFERS must be enabled in order for
                     *      `transfer()` function to succeed
                     */
                    uint32 public constant FEATURE_TRANSFERS = 0x0000_0001;
                    /**
                     * @notice Enables ERC20 transfers on behalf
                     *      (transfer by someone else on behalf of token owner)
                     * @dev Feature FEATURE_TRANSFERS_ON_BEHALF must be enabled in order for
                     *      `transferFrom()` function to succeed
                     * @dev Token owner must call `approve()` first to authorize
                     *      the transfer on behalf
                     */
                    uint32 public constant FEATURE_TRANSFERS_ON_BEHALF = 0x0000_0002;
                    /**
                     * @dev Defines if the default behavior of `transfer` and `transferFrom`
                     *      checks if the receiver smart contract supports ERC20 tokens
                     * @dev When feature FEATURE_UNSAFE_TRANSFERS is enabled the transfers do not
                     *      check if the receiver smart contract supports ERC20 tokens,
                     *      i.e. `transfer` and `transferFrom` behave like `unsafeTransferFrom`
                     * @dev When feature FEATURE_UNSAFE_TRANSFERS is disabled (default) the transfers
                     *      check if the receiver smart contract supports ERC20 tokens,
                     *      i.e. `transfer` and `transferFrom` behave like `safeTransferFrom`
                     */
                    uint32 public constant FEATURE_UNSAFE_TRANSFERS = 0x0000_0004;
                    /**
                     * @notice Enables token owners to burn their own tokens,
                     *      including locked tokens which are burnt first
                     * @dev Feature FEATURE_OWN_BURNS must be enabled in order for
                     *      `burn()` function to succeed when called by token owner
                     */
                    uint32 public constant FEATURE_OWN_BURNS = 0x0000_0008;
                    /**
                     * @notice Enables approved operators to burn tokens on behalf of their owners,
                     *      including locked tokens which are burnt first
                     * @dev Feature FEATURE_OWN_BURNS must be enabled in order for
                     *      `burn()` function to succeed when called by approved operator
                     */
                    uint32 public constant FEATURE_BURNS_ON_BEHALF = 0x0000_0010;
                    /**
                     * @notice Enables delegators to elect delegates
                     * @dev Feature FEATURE_DELEGATIONS must be enabled in order for
                     *      `delegate()` function to succeed
                     */
                    uint32 public constant FEATURE_DELEGATIONS = 0x0000_0020;
                    /**
                     * @notice Enables delegators to elect delegates on behalf
                     *      (via an EIP712 signature)
                     * @dev Feature FEATURE_DELEGATIONS must be enabled in order for
                     *      `delegateWithSig()` function to succeed
                     */
                    uint32 public constant FEATURE_DELEGATIONS_ON_BEHALF = 0x0000_0040;
                    /**
                     * @notice Token creator is responsible for creating (minting)
                     *      tokens to an arbitrary address
                     * @dev Role ROLE_TOKEN_CREATOR allows minting tokens
                     *      (calling `mint` function)
                     */
                    uint32 public constant ROLE_TOKEN_CREATOR = 0x0001_0000;
                    /**
                     * @notice Token destroyer is responsible for destroying (burning)
                     *      tokens owned by an arbitrary address
                     * @dev Role ROLE_TOKEN_DESTROYER allows burning tokens
                     *      (calling `burn` function)
                     */
                    uint32 public constant ROLE_TOKEN_DESTROYER = 0x0002_0000;
                    /**
                     * @notice ERC20 receivers are allowed to receive tokens without ERC20 safety checks,
                     *      which may be useful to simplify tokens transfers into "legacy" smart contracts
                     * @dev When `FEATURE_UNSAFE_TRANSFERS` is not enabled addresses having
                     *      `ROLE_ERC20_RECEIVER` permission are allowed to receive tokens
                     *      via `transfer` and `transferFrom` functions in the same way they
                     *      would via `unsafeTransferFrom` function
                     * @dev When `FEATURE_UNSAFE_TRANSFERS` is enabled `ROLE_ERC20_RECEIVER` permission
                     *      doesn't affect the transfer behaviour since
                     *      `transfer` and `transferFrom` behave like `unsafeTransferFrom` for any receiver
                     * @dev ROLE_ERC20_RECEIVER is a shortening for ROLE_UNSAFE_ERC20_RECEIVER
                     */
                    uint32 public constant ROLE_ERC20_RECEIVER = 0x0004_0000;
                    /**
                     * @notice ERC20 senders are allowed to send tokens without ERC20 safety checks,
                     *      which may be useful to simplify tokens transfers into "legacy" smart contracts
                     * @dev When `FEATURE_UNSAFE_TRANSFERS` is not enabled senders having
                     *      `ROLE_ERC20_SENDER` permission are allowed to send tokens
                     *      via `transfer` and `transferFrom` functions in the same way they
                     *      would via `unsafeTransferFrom` function
                     * @dev When `FEATURE_UNSAFE_TRANSFERS` is enabled `ROLE_ERC20_SENDER` permission
                     *      doesn't affect the transfer behaviour since
                     *      `transfer` and `transferFrom` behave like `unsafeTransferFrom` for any receiver
                     * @dev ROLE_ERC20_SENDER is a shortening for ROLE_UNSAFE_ERC20_SENDER
                     */
                    uint32 public constant ROLE_ERC20_SENDER = 0x0008_0000;
                    /**
                     * @dev Magic value to be returned by ERC20Receiver upon successful reception of token(s)
                     * @dev Equal to `bytes4(keccak256("onERC20Received(address,address,uint256,bytes)"))`,
                     *      which can be also obtained as `ERC20Receiver(address(0)).onERC20Received.selector`
                     */
                    bytes4 private constant ERC20_RECEIVED = 0x4fc35859;
                    /**
                     * @notice EIP-712 contract's domain typeHash, see https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash
                     */
                    bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
                    /**
                     * @notice EIP-712 delegation struct typeHash, see https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash
                     */
                    bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegate,uint256 nonce,uint256 expiry)");
                    /**
                     * @dev Fired in transfer(), transferFrom() and some other (non-ERC20) functions
                     *
                     * @dev ERC20 `event Transfer(address indexed _from, address indexed _to, uint256 _value)`
                     *
                     * @param _from an address tokens were consumed from
                     * @param _to an address tokens were sent to
                     * @param _value number of tokens transferred
                     */
                    event Transfer(address indexed _from, address indexed _to, uint256 _value);
                    /**
                     * @dev Fired in approve() and approveAtomic() functions
                     *
                     * @dev ERC20 `event Approval(address indexed _owner, address indexed _spender, uint256 _value)`
                     *
                     * @param _owner an address which granted a permission to transfer
                     *      tokens on its behalf
                     * @param _spender an address which received a permission to transfer
                     *      tokens on behalf of the owner `_owner`
                     * @param _value amount of tokens granted to transfer on behalf
                     */
                    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                    /**
                     * @dev Fired in mint() function
                     *
                     * @param _by an address which minted some tokens (transaction sender)
                     * @param _to an address the tokens were minted to
                     * @param _value an amount of tokens minted
                     */
                    event Minted(address indexed _by, address indexed _to, uint256 _value);
                    /**
                     * @dev Fired in burn() function
                     *
                     * @param _by an address which burned some tokens (transaction sender)
                     * @param _from an address the tokens were burnt from
                     * @param _value an amount of tokens burnt
                     */
                    event Burnt(address indexed _by, address indexed _from, uint256 _value);
                    /**
                     * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
                     *
                     * @dev Similar to ERC20 Transfer event, but also logs an address which executed transfer
                     *
                     * @dev Fired in transfer(), transferFrom() and some other (non-ERC20) functions
                     *
                     * @param _by an address which performed the transfer
                     * @param _from an address tokens were consumed from
                     * @param _to an address tokens were sent to
                     * @param _value number of tokens transferred
                     */
                    event Transferred(address indexed _by, address indexed _from, address indexed _to, uint256 _value);
                    /**
                     * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
                     *
                     * @dev Similar to ERC20 Approve event, but also logs old approval value
                     *
                     * @dev Fired in approve() and approveAtomic() functions
                     *
                     * @param _owner an address which granted a permission to transfer
                     *      tokens on its behalf
                     * @param _spender an address which received a permission to transfer
                     *      tokens on behalf of the owner `_owner`
                     * @param _oldValue previously granted amount of tokens to transfer on behalf
                     * @param _value new granted amount of tokens to transfer on behalf
                     */
                    event Approved(address indexed _owner, address indexed _spender, uint256 _oldValue, uint256 _value);
                    /**
                     * @dev Notifies that a key-value pair in `votingDelegates` mapping has changed,
                     *      i.e. a delegator address has changed its delegate address
                     *
                     * @param _of delegator address, a token owner
                     * @param _from old delegate, an address which delegate right is revoked
                     * @param _to new delegate, an address which received the voting power
                     */
                    event DelegateChanged(address indexed _of, address indexed _from, address indexed _to);
                    /**
                     * @dev Notifies that a key-value pair in `votingPowerHistory` mapping has changed,
                     *      i.e. a delegate's voting power has changed.
                     *
                     * @param _of delegate whose voting power has changed
                     * @param _fromVal previous number of votes delegate had
                     * @param _toVal new number of votes delegate has
                     */
                    event VotingPowerChanged(address indexed _of, uint256 _fromVal, uint256 _toVal);
                    /**
                     * @dev Deploys the token smart contract,
                     *      assigns initial token supply to the address specified
                     *
                     * @param _initialHolder owner of the initial token supply
                     */
                    constructor(address _initialHolder) {
                      // verify initial holder address non-zero (is set)
                      require(_initialHolder != address(0), "_initialHolder not set (zero address)");
                      // mint initial supply
                      mint(_initialHolder, 7_000_000e18);
                    }
                    // ===== Start: ERC20/ERC223/ERC777 functions =====
                    /**
                     * @notice Gets the balance of a particular address
                     *
                     * @dev ERC20 `function balanceOf(address _owner) public view returns (uint256 balance)`
                     *
                     * @param _owner the address to query the the balance for
                     * @return balance an amount of tokens owned by the address specified
                     */
                    function balanceOf(address _owner) public view returns (uint256 balance) {
                      // read the balance and return
                      return tokenBalances[_owner];
                    }
                    /**
                     * @notice Transfers some tokens to an external address or a smart contract
                     *
                     * @dev ERC20 `function transfer(address _to, uint256 _value) public returns (bool success)`
                     *
                     * @dev Called by token owner (an address which has a
                     *      positive token balance tracked by this smart contract)
                     * @dev Throws on any error like
                     *      * insufficient token balance or
                     *      * incorrect `_to` address:
                     *          * zero address or
                     *          * self address or
                     *          * smart contract which doesn't support ERC20
                     *
                     * @param _to an address to transfer tokens to,
                     *      must be either an external address or a smart contract,
                     *      compliant with the ERC20 standard
                     * @param _value amount of tokens to be transferred, must
                     *      be greater than zero
                     * @return success true on success, throws otherwise
                     */
                    function transfer(address _to, uint256 _value) public returns (bool success) {
                      // just delegate call to `transferFrom`,
                      // `FEATURE_TRANSFERS` is verified inside it
                      return transferFrom(msg.sender, _to, _value);
                    }
                    /**
                     * @notice Transfers some tokens on behalf of address `_from' (token owner)
                     *      to some other address `_to`
                     *
                     * @dev ERC20 `function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)`
                     *
                     * @dev Called by token owner on his own or approved address,
                     *      an address approved earlier by token owner to
                     *      transfer some amount of tokens on its behalf
                     * @dev Throws on any error like
                     *      * insufficient token balance or
                     *      * incorrect `_to` address:
                     *          * zero address or
                     *          * same as `_from` address (self transfer)
                     *          * smart contract which doesn't support ERC20
                     *
                     * @param _from token owner which approved caller (transaction sender)
                     *      to transfer `_value` of tokens on its behalf
                     * @param _to an address to transfer tokens to,
                     *      must be either an external address or a smart contract,
                     *      compliant with the ERC20 standard
                     * @param _value amount of tokens to be transferred, must
                     *      be greater than zero
                     * @return success true on success, throws otherwise
                     */
                    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
                      // depending on `FEATURE_UNSAFE_TRANSFERS` we execute either safe (default)
                      // or unsafe transfer
                      // if `FEATURE_UNSAFE_TRANSFERS` is enabled
                      // or receiver has `ROLE_ERC20_RECEIVER` permission
                      // or sender has `ROLE_ERC20_SENDER` permission
                      if(isFeatureEnabled(FEATURE_UNSAFE_TRANSFERS)
                        || isOperatorInRole(_to, ROLE_ERC20_RECEIVER)
                        || isSenderInRole(ROLE_ERC20_SENDER)) {
                        // we execute unsafe transfer - delegate call to `unsafeTransferFrom`,
                        // `FEATURE_TRANSFERS` is verified inside it
                        unsafeTransferFrom(_from, _to, _value);
                      }
                      // otherwise - if `FEATURE_UNSAFE_TRANSFERS` is disabled
                      // and receiver doesn't have `ROLE_ERC20_RECEIVER` permission
                      else {
                        // we execute safe transfer - delegate call to `safeTransferFrom`, passing empty `_data`,
                        // `FEATURE_TRANSFERS` is verified inside it
                        safeTransferFrom(_from, _to, _value, "");
                      }
                      // both `unsafeTransferFrom` and `safeTransferFrom` throw on any error, so
                      // if we're here - it means operation successful,
                      // just return true
                      return true;
                    }
                    /**
                     * @notice Transfers some tokens on behalf of address `_from' (token owner)
                     *      to some other address `_to`
                     *
                     * @dev Inspired by ERC721 safeTransferFrom, this function allows to
                     *      send arbitrary data to the receiver on successful token transfer
                     * @dev Called by token owner on his own or approved address,
                     *      an address approved earlier by token owner to
                     *      transfer some amount of tokens on its behalf
                     * @dev Throws on any error like
                     *      * insufficient token balance or
                     *      * incorrect `_to` address:
                     *          * zero address or
                     *          * same as `_from` address (self transfer)
                     *          * smart contract which doesn't support ERC20Receiver interface
                     * @dev Returns silently on success, throws otherwise
                     *
                     * @param _from token owner which approved caller (transaction sender)
                     *      to transfer `_value` of tokens on its behalf
                     * @param _to an address to transfer tokens to,
                     *      must be either an external address or a smart contract,
                     *      compliant with the ERC20 standard
                     * @param _value amount of tokens to be transferred, must
                     *      be greater than zero
                     * @param _data [optional] additional data with no specified format,
                     *      sent in onERC20Received call to `_to` in case if its a smart contract
                     */
                    function safeTransferFrom(address _from, address _to, uint256 _value, bytes memory _data) public {
                      // first delegate call to `unsafeTransferFrom`
                      // to perform the unsafe token(s) transfer
                      unsafeTransferFrom(_from, _to, _value);
                      // after the successful transfer - check if receiver supports
                      // ERC20Receiver and execute a callback handler `onERC20Received`,
                      // reverting whole transaction on any error:
                      // check if receiver `_to` supports ERC20Receiver interface
                      if(AddressUtils.isContract(_to)) {
                        // if `_to` is a contract - execute onERC20Received
                        bytes4 response = ERC20Receiver(_to).onERC20Received(msg.sender, _from, _value, _data);
                        // expected response is ERC20_RECEIVED
                        require(response == ERC20_RECEIVED, "invalid onERC20Received response");
                      }
                    }
                    /**
                     * @notice Transfers some tokens on behalf of address `_from' (token owner)
                     *      to some other address `_to`
                     *
                     * @dev In contrast to `safeTransferFrom` doesn't check recipient
                     *      smart contract to support ERC20 tokens (ERC20Receiver)
                     * @dev Designed to be used by developers when the receiver is known
                     *      to support ERC20 tokens but doesn't implement ERC20Receiver interface
                     * @dev Called by token owner on his own or approved address,
                     *      an address approved earlier by token owner to
                     *      transfer some amount of tokens on its behalf
                     * @dev Throws on any error like
                     *      * insufficient token balance or
                     *      * incorrect `_to` address:
                     *          * zero address or
                     *          * same as `_from` address (self transfer)
                     * @dev Returns silently on success, throws otherwise
                     *
                     * @param _from token owner which approved caller (transaction sender)
                     *      to transfer `_value` of tokens on its behalf
                     * @param _to an address to transfer tokens to,
                     *      must be either an external address or a smart contract,
                     *      compliant with the ERC20 standard
                     * @param _value amount of tokens to be transferred, must
                     *      be greater than zero
                     */
                    function unsafeTransferFrom(address _from, address _to, uint256 _value) public {
                      // if `_from` is equal to sender, require transfers feature to be enabled
                      // otherwise require transfers on behalf feature to be enabled
                      require(_from == msg.sender && isFeatureEnabled(FEATURE_TRANSFERS)
                           || _from != msg.sender && isFeatureEnabled(FEATURE_TRANSFERS_ON_BEHALF),
                              _from == msg.sender? "transfers are disabled": "transfers on behalf are disabled");
                      // non-zero source address check - Zeppelin
                      // obviously, zero source address is a client mistake
                      // it's not part of ERC20 standard but it's reasonable to fail fast
                      // since for zero value transfer transaction succeeds otherwise
                      require(_from != address(0), "ERC20: transfer from the zero address"); // Zeppelin msg
                      // non-zero recipient address check
                      require(_to != address(0), "ERC20: transfer to the zero address"); // Zeppelin msg
                      // sender and recipient cannot be the same
                      require(_from != _to, "sender and recipient are the same (_from = _to)");
                      // sending tokens to the token smart contract itself is a client mistake
                      require(_to != address(this), "invalid recipient (transfer to the token smart contract itself)");
                      // according to ERC-20 Token Standard, https://eips.ethereum.org/EIPS/eip-20
                      // "Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event."
                      if(_value == 0) {
                        // emit an ERC20 transfer event
                        emit Transfer(_from, _to, _value);
                        // don't forget to return - we're done
                        return;
                      }
                      // no need to make arithmetic overflow check on the _value - by design of mint()
                      // in case of transfer on behalf
                      if(_from != msg.sender) {
                        // read allowance value - the amount of tokens allowed to transfer - into the stack
                        uint256 _allowance = transferAllowances[_from][msg.sender];
                        // verify sender has an allowance to transfer amount of tokens requested
                        require(_allowance >= _value, "ERC20: transfer amount exceeds allowance"); // Zeppelin msg
                        // update allowance value on the stack
                        _allowance -= _value;
                        // update the allowance value in storage
                        transferAllowances[_from][msg.sender] = _allowance;
                        // emit an improved atomic approve event
                        emit Approved(_from, msg.sender, _allowance + _value, _allowance);
                        // emit an ERC20 approval event to reflect the decrease
                        emit Approval(_from, msg.sender, _allowance);
                      }
                      // verify sender has enough tokens to transfer on behalf
                      require(tokenBalances[_from] >= _value, "ERC20: transfer amount exceeds balance"); // Zeppelin msg
                      // perform the transfer:
                      // decrease token owner (sender) balance
                      tokenBalances[_from] -= _value;
                      // increase `_to` address (receiver) balance
                      tokenBalances[_to] += _value;
                      // move voting power associated with the tokens transferred
                      __moveVotingPower(votingDelegates[_from], votingDelegates[_to], _value);
                      // emit an improved transfer event
                      emit Transferred(msg.sender, _from, _to, _value);
                      // emit an ERC20 transfer event
                      emit Transfer(_from, _to, _value);
                    }
                    /**
                     * @notice Approves address called `_spender` to transfer some amount
                     *      of tokens on behalf of the owner
                     *
                     * @dev ERC20 `function approve(address _spender, uint256 _value) public returns (bool success)`
                     *
                     * @dev Caller must not necessarily own any tokens to grant the permission
                     *
                     * @param _spender an address approved by the caller (token owner)
                     *      to spend some tokens on its behalf
                     * @param _value an amount of tokens spender `_spender` is allowed to
                     *      transfer on behalf of the token owner
                     * @return success true on success, throws otherwise
                     */
                    function approve(address _spender, uint256 _value) public returns (bool success) {
                      // non-zero spender address check - Zeppelin
                      // obviously, zero spender address is a client mistake
                      // it's not part of ERC20 standard but it's reasonable to fail fast
                      require(_spender != address(0), "ERC20: approve to the zero address"); // Zeppelin msg
                      // read old approval value to emmit an improved event (ISBN:978-1-7281-3027-9)
                      uint256 _oldValue = transferAllowances[msg.sender][_spender];
                      // perform an operation: write value requested into the storage
                      transferAllowances[msg.sender][_spender] = _value;
                      // emit an improved atomic approve event (ISBN:978-1-7281-3027-9)
                      emit Approved(msg.sender, _spender, _oldValue, _value);
                      // emit an ERC20 approval event
                      emit Approval(msg.sender, _spender, _value);
                      // operation successful, return true
                      return true;
                    }
                    /**
                     * @notice Returns the amount which _spender is still allowed to withdraw from _owner.
                     *
                     * @dev ERC20 `function allowance(address _owner, address _spender) public view returns (uint256 remaining)`
                     *
                     * @dev A function to check an amount of tokens owner approved
                     *      to transfer on its behalf by some other address called "spender"
                     *
                     * @param _owner an address which approves transferring some tokens on its behalf
                     * @param _spender an address approved to transfer some tokens on behalf
                     * @return remaining an amount of tokens approved address `_spender` can transfer on behalf
                     *      of token owner `_owner`
                     */
                    function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
                      // read the value from storage and return
                      return transferAllowances[_owner][_spender];
                    }
                    // ===== End: ERC20/ERC223/ERC777 functions =====
                    // ===== Start: Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) =====
                    /**
                     * @notice Increases the allowance granted to `spender` by the transaction sender
                     *
                     * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
                     *
                     * @dev Throws if value to increase by is zero or too big and causes arithmetic overflow
                     *
                     * @param _spender an address approved by the caller (token owner)
                     *      to spend some tokens on its behalf
                     * @param _value an amount of tokens to increase by
                     * @return success true on success, throws otherwise
                     */
                    function increaseAllowance(address _spender, uint256 _value) public virtual returns (bool) {
                      // read current allowance value
                      uint256 currentVal = transferAllowances[msg.sender][_spender];
                      // non-zero _value and arithmetic overflow check on the allowance
                      require(currentVal + _value > currentVal, "zero value approval increase or arithmetic overflow");
                      // delegate call to `approve` with the new value
                      return approve(_spender, currentVal + _value);
                    }
                    /**
                     * @notice Decreases the allowance granted to `spender` by the caller.
                     *
                     * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
                     *
                     * @dev Throws if value to decrease by is zero or is bigger than currently allowed value
                     *
                     * @param _spender an address approved by the caller (token owner)
                     *      to spend some tokens on its behalf
                     * @param _value an amount of tokens to decrease by
                     * @return success true on success, throws otherwise
                     */
                    function decreaseAllowance(address _spender, uint256 _value) public virtual returns (bool) {
                      // read current allowance value
                      uint256 currentVal = transferAllowances[msg.sender][_spender];
                      // non-zero _value check on the allowance
                      require(_value > 0, "zero value approval decrease");
                      // verify allowance decrease doesn't underflow
                      require(currentVal >= _value, "ERC20: decreased allowance below zero");
                      // delegate call to `approve` with the new value
                      return approve(_spender, currentVal - _value);
                    }
                    // ===== End: Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) =====
                    // ===== Start: Minting/burning extension =====
                    /**
                     * @dev Mints (creates) some tokens to address specified
                     * @dev The value specified is treated as is without taking
                     *      into account what `decimals` value is
                     * @dev Behaves effectively as `mintTo` function, allowing
                     *      to specify an address to mint tokens to
                     * @dev Requires sender to have `ROLE_TOKEN_CREATOR` permission
                     *
                     * @dev Throws on overflow, if totalSupply + _value doesn't fit into uint256
                     *
                     * @param _to an address to mint tokens to
                     * @param _value an amount of tokens to mint (create)
                     */
                    function mint(address _to, uint256 _value) public {
                      // check if caller has sufficient permissions to mint tokens
                      require(isSenderInRole(ROLE_TOKEN_CREATOR), "insufficient privileges (ROLE_TOKEN_CREATOR required)");
                      // non-zero recipient address check
                      require(_to != address(0), "ERC20: mint to the zero address"); // Zeppelin msg
                      // non-zero _value and arithmetic overflow check on the total supply
                      // this check automatically secures arithmetic overflow on the individual balance
                      require(totalSupply + _value > totalSupply, "zero value mint or arithmetic overflow");
                      // uint192 overflow check (required by voting delegation)
                      require(totalSupply + _value <= type(uint192).max, "total supply overflow (uint192)");
                      // perform mint:
                      // increase total amount of tokens value
                      totalSupply += _value;
                      // increase `_to` address balance
                      tokenBalances[_to] += _value;
                      // create voting power associated with the tokens minted
                      __moveVotingPower(address(0), votingDelegates[_to], _value);
                      // fire a minted event
                      emit Minted(msg.sender, _to, _value);
                      // emit an improved transfer event
                      emit Transferred(msg.sender, address(0), _to, _value);
                      // fire ERC20 compliant transfer event
                      emit Transfer(address(0), _to, _value);
                    }
                    /**
                     * @dev Burns (destroys) some tokens from the address specified
                     * @dev The value specified is treated as is without taking
                     *      into account what `decimals` value is
                     * @dev Behaves effectively as `burnFrom` function, allowing
                     *      to specify an address to burn tokens from
                     * @dev Requires sender to have `ROLE_TOKEN_DESTROYER` permission
                     *
                     * @param _from an address to burn some tokens from
                     * @param _value an amount of tokens to burn (destroy)
                     */
                    function burn(address _from, uint256 _value) public {
                      // check if caller has sufficient permissions to burn tokens
                      // and if not - check for possibility to burn own tokens or to burn on behalf
                      if(!isSenderInRole(ROLE_TOKEN_DESTROYER)) {
                        // if `_from` is equal to sender, require own burns feature to be enabled
                        // otherwise require burns on behalf feature to be enabled
                        require(_from == msg.sender && isFeatureEnabled(FEATURE_OWN_BURNS)
                             || _from != msg.sender && isFeatureEnabled(FEATURE_BURNS_ON_BEHALF),
                                _from == msg.sender? "burns are disabled": "burns on behalf are disabled");
                        // in case of burn on behalf
                        if(_from != msg.sender) {
                          // read allowance value - the amount of tokens allowed to be burnt - into the stack
                          uint256 _allowance = transferAllowances[_from][msg.sender];
                          // verify sender has an allowance to burn amount of tokens requested
                          require(_allowance >= _value, "ERC20: burn amount exceeds allowance"); // Zeppelin msg
                          // update allowance value on the stack
                          _allowance -= _value;
                          // update the allowance value in storage
                          transferAllowances[_from][msg.sender] = _allowance;
                          // emit an improved atomic approve event
                          emit Approved(msg.sender, _from, _allowance + _value, _allowance);
                          // emit an ERC20 approval event to reflect the decrease
                          emit Approval(_from, msg.sender, _allowance);
                        }
                      }
                      // at this point we know that either sender is ROLE_TOKEN_DESTROYER or
                      // we burn own tokens or on behalf (in latest case we already checked and updated allowances)
                      // we have left to execute balance checks and burning logic itself
                      // non-zero burn value check
                      require(_value != 0, "zero value burn");
                      // non-zero source address check - Zeppelin
                      require(_from != address(0), "ERC20: burn from the zero address"); // Zeppelin msg
                      // verify `_from` address has enough tokens to destroy
                      // (basically this is a arithmetic overflow check)
                      require(tokenBalances[_from] >= _value, "ERC20: burn amount exceeds balance"); // Zeppelin msg
                      // perform burn:
                      // decrease `_from` address balance
                      tokenBalances[_from] -= _value;
                      // decrease total amount of tokens value
                      totalSupply -= _value;
                      // destroy voting power associated with the tokens burnt
                      __moveVotingPower(votingDelegates[_from], address(0), _value);
                      // fire a burnt event
                      emit Burnt(msg.sender, _from, _value);
                      // emit an improved transfer event
                      emit Transferred(msg.sender, _from, address(0), _value);
                      // fire ERC20 compliant transfer event
                      emit Transfer(_from, address(0), _value);
                    }
                    // ===== End: Minting/burning extension =====
                    // ===== Start: DAO Support (Compound-like voting delegation) =====
                    /**
                     * @notice Gets current voting power of the account `_of`
                     * @param _of the address of account to get voting power of
                     * @return current cumulative voting power of the account,
                     *      sum of token balances of all its voting delegators
                     */
                    function getVotingPower(address _of) public view returns (uint256) {
                      // get a link to an array of voting power history records for an address specified
                      VotingPowerRecord[] storage history = votingPowerHistory[_of];
                      // lookup the history and return latest element
                      return history.length == 0? 0: history[history.length - 1].votingPower;
                    }
                    /**
                     * @notice Gets past voting power of the account `_of` at some block `_blockNum`
                     * @dev Throws if `_blockNum` is not in the past (not the finalized block)
                     * @param _of the address of account to get voting power of
                     * @param _blockNum block number to get the voting power at
                     * @return past cumulative voting power of the account,
                     *      sum of token balances of all its voting delegators at block number `_blockNum`
                     */
                    function getVotingPowerAt(address _of, uint256 _blockNum) public view returns (uint256) {
                      // make sure block number is not in the past (not the finalized block)
                      require(_blockNum < block.number, "not yet determined"); // Compound msg
                      // get a link to an array of voting power history records for an address specified
                      VotingPowerRecord[] storage history = votingPowerHistory[_of];
                      // if voting power history for the account provided is empty
                      if(history.length == 0) {
                        // than voting power is zero - return the result
                        return 0;
                      }
                      // check latest voting power history record block number:
                      // if history was not updated after the block of interest
                      if(history[history.length - 1].blockNumber <= _blockNum) {
                        // we're done - return last voting power record
                        return getVotingPower(_of);
                      }
                      // check first voting power history record block number:
                      // if history was never updated before the block of interest
                      if(history[0].blockNumber > _blockNum) {
                        // we're done - voting power at the block num of interest was zero
                        return 0;
                      }
                      // `votingPowerHistory[_of]` is an array ordered by `blockNumber`, ascending;
                      // apply binary search on `votingPowerHistory[_of]` to find such an entry number `i`, that
                      // `votingPowerHistory[_of][i].blockNumber <= _blockNum`, but in the same time
                      // `votingPowerHistory[_of][i + 1].blockNumber > _blockNum`
                      // return the result - voting power found at index `i`
                      return history[__binaryLookup(_of, _blockNum)].votingPower;
                    }
                    /**
                     * @dev Reads an entire voting power history array for the delegate specified
                     *
                     * @param _of delegate to query voting power history for
                     * @return voting power history array for the delegate of interest
                     */
                    function getVotingPowerHistory(address _of) public view returns(VotingPowerRecord[] memory) {
                      // return an entire array as memory
                      return votingPowerHistory[_of];
                    }
                    /**
                     * @dev Returns length of the voting power history array for the delegate specified;
                     *      useful since reading an entire array just to get its length is expensive (gas cost)
                     *
                     * @param _of delegate to query voting power history length for
                     * @return voting power history array length for the delegate of interest
                     */
                    function getVotingPowerHistoryLength(address _of) public view returns(uint256) {
                      // read array length and return
                      return votingPowerHistory[_of].length;
                    }
                    /**
                     * @notice Delegates voting power of the delegator `msg.sender` to the delegate `_to`
                     *
                     * @dev Accepts zero value address to delegate voting power to, effectively
                     *      removing the delegate in that case
                     *
                     * @param _to address to delegate voting power to
                     */
                    function delegate(address _to) public {
                      // verify delegations are enabled
                      require(isFeatureEnabled(FEATURE_DELEGATIONS), "delegations are disabled");
                      // delegate call to `__delegate`
                      __delegate(msg.sender, _to);
                    }
                    /**
                     * @notice Delegates voting power of the delegator (represented by its signature) to the delegate `_to`
                     *
                     * @dev Accepts zero value address to delegate voting power to, effectively
                     *      removing the delegate in that case
                     *
                     * @dev Compliant with EIP-712: Ethereum typed structured data hashing and signing,
                     *      see https://eips.ethereum.org/EIPS/eip-712
                     *
                     * @param _to address to delegate voting power to
                     * @param _nonce nonce used to construct the signature, and used to validate it;
                     *      nonce is increased by one after successful signature validation and vote delegation
                     * @param _exp signature expiration time
                     * @param v the recovery byte of the signature
                     * @param r half of the ECDSA signature pair
                     * @param s half of the ECDSA signature pair
                     */
                    function delegateWithSig(address _to, uint256 _nonce, uint256 _exp, uint8 v, bytes32 r, bytes32 s) public {
                      // verify delegations on behalf are enabled
                      require(isFeatureEnabled(FEATURE_DELEGATIONS_ON_BEHALF), "delegations on behalf are disabled");
                      // build the EIP-712 contract domain separator
                      bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), block.chainid, address(this)));
                      // build the EIP-712 hashStruct of the delegation message
                      bytes32 hashStruct = keccak256(abi.encode(DELEGATION_TYPEHASH, _to, _nonce, _exp));
                      // calculate the EIP-712 digest "\\x19\\x01" ‖ domainSeparator ‖ hashStruct(message)
                      bytes32 digest = keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, hashStruct));
                      // recover the address who signed the message with v, r, s
                      address signer = ecrecover(digest, v, r, s);
                      // perform message integrity and security validations
                      require(signer != address(0), "invalid signature"); // Compound msg
                      require(_nonce == nonces[signer], "invalid nonce"); // Compound msg
                      require(block.timestamp < _exp, "signature expired"); // Compound msg
                      // update the nonce for that particular signer to avoid replay attack
                      nonces[signer]++;
                      // delegate call to `__delegate` - execute the logic required
                      __delegate(signer, _to);
                    }
                    /**
                     * @dev Auxiliary function to delegate delegator's `_from` voting power to the delegate `_to`
                     * @dev Writes to `votingDelegates` and `votingPowerHistory` mappings
                     *
                     * @param _from delegator who delegates his voting power
                     * @param _to delegate who receives the voting power
                     */
                    function __delegate(address _from, address _to) private {
                      // read current delegate to be replaced by a new one
                      address _fromDelegate = votingDelegates[_from];
                      // read current voting power (it is equal to token balance)
                      uint256 _value = tokenBalances[_from];
                      // reassign voting delegate to `_to`
                      votingDelegates[_from] = _to;
                      // update voting power for `_fromDelegate` and `_to`
                      __moveVotingPower(_fromDelegate, _to, _value);
                      // emit an event
                      emit DelegateChanged(_from, _fromDelegate, _to);
                    }
                    /**
                     * @dev Auxiliary function to move voting power `_value`
                     *      from delegate `_from` to the delegate `_to`
                     *
                     * @dev Doesn't have any effect if `_from == _to`, or if `_value == 0`
                     *
                     * @param _from delegate to move voting power from
                     * @param _to delegate to move voting power to
                     * @param _value voting power to move from `_from` to `_to`
                     */
                    function __moveVotingPower(address _from, address _to, uint256 _value) private {
                      // if there is no move (`_from == _to`) or there is nothing to move (`_value == 0`)
                      if(_from == _to || _value == 0) {
                        // return silently with no action
                        return;
                      }
                      // if source address is not zero - decrease its voting power
                      if(_from != address(0)) {
                        // read current source address voting power
                        uint256 _fromVal = getVotingPower(_from);
                        // calculate decreased voting power
                        // underflow is not possible by design:
                        // voting power is limited by token balance which is checked by the callee
                        uint256 _toVal = _fromVal - _value;
                        // update source voting power from `_fromVal` to `_toVal`
                        __updateVotingPower(_from, _fromVal, _toVal);
                      }
                      // if destination address is not zero - increase its voting power
                      if(_to != address(0)) {
                        // read current destination address voting power
                        uint256 _fromVal = getVotingPower(_to);
                        // calculate increased voting power
                        // overflow is not possible by design:
                        // max token supply limits the cumulative voting power
                        uint256 _toVal = _fromVal + _value;
                        // update destination voting power from `_fromVal` to `_toVal`
                        __updateVotingPower(_to, _fromVal, _toVal);
                      }
                    }
                    /**
                     * @dev Auxiliary function to update voting power of the delegate `_of`
                     *      from value `_fromVal` to value `_toVal`
                     *
                     * @param _of delegate to update its voting power
                     * @param _fromVal old voting power of the delegate
                     * @param _toVal new voting power of the delegate
                     */
                    function __updateVotingPower(address _of, uint256 _fromVal, uint256 _toVal) private {
                      // get a link to an array of voting power history records for an address specified
                      VotingPowerRecord[] storage history = votingPowerHistory[_of];
                      // if there is an existing voting power value stored for current block
                      if(history.length != 0 && history[history.length - 1].blockNumber == block.number) {
                        // update voting power which is already stored in the current block
                        history[history.length - 1].votingPower = uint192(_toVal);
                      }
                      // otherwise - if there is no value stored for current block
                      else {
                        // add new element into array representing the value for current block
                        history.push(VotingPowerRecord(uint64(block.number), uint192(_toVal)));
                      }
                      // emit an event
                      emit VotingPowerChanged(_of, _fromVal, _toVal);
                    }
                    /**
                     * @dev Auxiliary function to lookup an element in a sorted (asc) array of elements
                     *
                     * @dev This function finds the closest element in an array to the value
                     *      of interest (not exceeding that value) and returns its index within an array
                     *
                     * @dev An array to search in is `votingPowerHistory[_to][i].blockNumber`,
                     *      it is sorted in ascending order (blockNumber increases)
                     *
                     * @param _to an address of the delegate to get an array for
                     * @param n value of interest to look for
                     * @return an index of the closest element in an array to the value
                     *      of interest (not exceeding that value)
                     */
                    function __binaryLookup(address _to, uint256 n) private view returns(uint256) {
                      // get a link to an array of voting power history records for an address specified
                      VotingPowerRecord[] storage history = votingPowerHistory[_to];
                      // left bound of the search interval, originally start of the array
                      uint256 i = 0;
                      // right bound of the search interval, originally end of the array
                      uint256 j = history.length - 1;
                      // the iteration process narrows down the bounds by
                      // splitting the interval in a half oce per each iteration
                      while(j > i) {
                        // get an index in the middle of the interval [i, j]
                        uint256 k = j - (j - i) / 2;
                        // read an element to compare it with the value of interest
                        VotingPowerRecord memory cp = history[k];
                        // if we've got a strict equal - we're lucky and done
                        if(cp.blockNumber == n) {
                          // just return the result - index `k`
                          return k;
                        }
                        // if the value of interest is bigger - move left bound to the middle
                        else if (cp.blockNumber < n) {
                          // move left bound `i` to the middle position `k`
                          i = k;
                        }
                        // otherwise, when the value of interest is smaller - move right bound to the middle
                        else {
                          // move right bound `j` to the middle position `k - 1`:
                          // element at position `k` is bigger and cannot be the result
                          j = k - 1;
                        }
                      }
                      // reaching that point means no exact match found
                      // since we're interested in the element which is not bigger than the
                      // element of interest, we return the lower bound `i`
                      return i;
                    }
                  }
                  // ===== End: DAO Support (Compound-like voting delegation) =====
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.1;
                  /**
                   * @title Access Control List
                   *
                   * @notice Access control smart contract provides an API to check
                   *      if specific operation is permitted globally and/or
                   *      if particular user has a permission to execute it.
                   *
                   * @notice It deals with two main entities: features and roles.
                   *
                   * @notice Features are designed to be used to enable/disable specific
                   *      functions (public functions) of the smart contract for everyone.
                   * @notice User roles are designed to restrict access to specific
                   *      functions (restricted functions) of the smart contract to some users.
                   *
                   * @notice Terms "role", "permissions" and "set of permissions" have equal meaning
                   *      in the documentation text and may be used interchangeably.
                   * @notice Terms "permission", "single permission" implies only one permission bit set.
                   *
                   * @dev This smart contract is designed to be inherited by other
                   *      smart contracts which require access control management capabilities.
                   *
                   * @author Basil Gorin
                   */
                  contract AccessControl {
                    /**
                     * @notice Access manager is responsible for assigning the roles to users,
                     *      enabling/disabling global features of the smart contract
                     * @notice Access manager can add, remove and update user roles,
                     *      remove and update global features
                     *
                     * @dev Role ROLE_ACCESS_MANAGER allows modifying user roles and global features
                     * @dev Role ROLE_ACCESS_MANAGER has single bit at position 255 enabled
                     */
                    uint256 public constant ROLE_ACCESS_MANAGER = 0x8000000000000000000000000000000000000000000000000000000000000000;
                    /**
                     * @dev Bitmask representing all the possible permissions (super admin role)
                     * @dev Has all the bits are enabled (2^256 - 1 value)
                     */
                    uint256 private constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF...
                    /**
                     * @notice Privileged addresses with defined roles/permissions
                     * @notice In the context of ERC20/ERC721 tokens these can be permissions to
                     *      allow minting or burning tokens, transferring on behalf and so on
                     *
                     * @dev Maps user address to the permissions bitmask (role), where each bit
                     *      represents a permission
                     * @dev Bitmask 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
                     *      represents all possible permissions
                     * @dev Zero address mapping represents global features of the smart contract
                     */
                    mapping(address => uint256) public userRoles;
                    /**
                     * @dev Fired in updateRole() and updateFeatures()
                     *
                     * @param _by operator which called the function
                     * @param _to address which was granted/revoked permissions
                     * @param _requested permissions requested
                     * @param _actual permissions effectively set
                     */
                    event RoleUpdated(address indexed _by, address indexed _to, uint256 _requested, uint256 _actual);
                    /**
                     * @notice Creates an access control instance,
                     *      setting contract creator to have full privileges
                     */
                    constructor() {
                      // contract creator has full privileges
                      userRoles[msg.sender] = FULL_PRIVILEGES_MASK;
                    }
                    /**
                     * @notice Retrieves globally set of features enabled
                     *
                     * @dev Auxiliary getter function to maintain compatibility with previous
                     *      versions of the Access Control List smart contract, where
                     *      features was a separate uint256 public field
                     *
                     * @return 256-bit bitmask of the features enabled
                     */
                    function features() public view returns(uint256) {
                      // according to new design features are stored in zero address
                      // mapping of `userRoles` structure
                      return userRoles[address(0)];
                    }
                    /**
                     * @notice Updates set of the globally enabled features (`features`),
                     *      taking into account sender's permissions
                     *
                     * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
                     * @dev Function is left for backward compatibility with older versions
                     *
                     * @param _mask bitmask representing a set of features to enable/disable
                     */
                    function updateFeatures(uint256 _mask) public {
                      // delegate call to `updateRole`
                      updateRole(address(0), _mask);
                    }
                    /**
                     * @notice Updates set of permissions (role) for a given user,
                     *      taking into account sender's permissions.
                     *
                     * @dev Setting role to zero is equivalent to removing an all permissions
                     * @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to
                     *      copying senders' permissions (role) to the user
                     * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
                     *
                     * @param operator address of a user to alter permissions for or zero
                     *      to alter global features of the smart contract
                     * @param role bitmask representing a set of permissions to
                     *      enable/disable for a user specified
                     */
                    function updateRole(address operator, uint256 role) public {
                      // caller must have a permission to update user roles
                      require(isSenderInRole(ROLE_ACCESS_MANAGER), "insufficient privileges (ROLE_ACCESS_MANAGER required)");
                      // evaluate the role and reassign it
                      userRoles[operator] = evaluateBy(msg.sender, userRoles[operator], role);
                      // fire an event
                      emit RoleUpdated(msg.sender, operator, role, userRoles[operator]);
                    }
                    /**
                     * @notice Determines the permission bitmask an operator can set on the
                     *      target permission set
                     * @notice Used to calculate the permission bitmask to be set when requested
                     *     in `updateRole` and `updateFeatures` functions
                     *
                     * @dev Calculated based on:
                     *      1) operator's own permission set read from userRoles[operator]
                     *      2) target permission set - what is already set on the target
                     *      3) desired permission set - what do we want set target to
                     *
                     * @dev Corner cases:
                     *      1) Operator is super admin and its permission set is `FULL_PRIVILEGES_MASK`:
                     *        `desired` bitset is returned regardless of the `target` permission set value
                     *        (what operator sets is what they get)
                     *      2) Operator with no permissions (zero bitset):
                     *        `target` bitset is returned regardless of the `desired` value
                     *        (operator has no authority and cannot modify anything)
                     *
                     * @dev Example:
                     *      Consider an operator with the permissions bitmask     00001111
                     *      is about to modify the target permission set          01010101
                     *      Operator wants to set that permission set to          00110011
                     *      Based on their role, an operator has the permissions
                     *      to update only lowest 4 bits on the target, meaning that
                     *      high 4 bits of the target set in this example is left
                     *      unchanged and low 4 bits get changed as desired:      01010011
                     *
                     * @param operator address of the contract operator which is about to set the permissions
                     * @param target input set of permissions to operator is going to modify
                     * @param desired desired set of permissions operator would like to set
                     * @return resulting set of permissions given operator will set
                     */
                    function evaluateBy(address operator, uint256 target, uint256 desired) public view returns(uint256) {
                      // read operator's permissions
                      uint256 p = userRoles[operator];
                      // taking into account operator's permissions,
                      // 1) enable the permissions desired on the `target`
                      target |= p & desired;
                      // 2) disable the permissions desired on the `target`
                      target &= FULL_PRIVILEGES_MASK ^ (p & (FULL_PRIVILEGES_MASK ^ desired));
                      // return calculated result
                      return target;
                    }
                    /**
                     * @notice Checks if requested set of features is enabled globally on the contract
                     *
                     * @param required set of features to check against
                     * @return true if all the features requested are enabled, false otherwise
                     */
                    function isFeatureEnabled(uint256 required) public view returns(bool) {
                      // delegate call to `__hasRole`, passing `features` property
                      return __hasRole(features(), required);
                    }
                    /**
                     * @notice Checks if transaction sender `msg.sender` has all the permissions required
                     *
                     * @param required set of permissions (role) to check against
                     * @return true if all the permissions requested are enabled, false otherwise
                     */
                    function isSenderInRole(uint256 required) public view returns(bool) {
                      // delegate call to `isOperatorInRole`, passing transaction sender
                      return isOperatorInRole(msg.sender, required);
                    }
                    /**
                     * @notice Checks if operator has all the permissions (role) required
                     *
                     * @param operator address of the user to check role for
                     * @param required set of permissions (role) to check
                     * @return true if all the permissions requested are enabled, false otherwise
                     */
                    function isOperatorInRole(address operator, uint256 required) public view returns(bool) {
                      // delegate call to `__hasRole`, passing operator's permissions (role)
                      return __hasRole(userRoles[operator], required);
                    }
                    /**
                     * @dev Checks if role `actual` contains all the permissions required `required`
                     *
                     * @param actual existent role
                     * @param required required role
                     * @return true if actual has required role (all permissions), false otherwise
                     */
                    function __hasRole(uint256 actual, uint256 required) internal pure returns(bool) {
                      // check the bitmask for the role required and return the result
                      return actual & required == required;
                    }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.1;
                  /**
                   * @title Address Utils
                   *
                   * @dev Utility library of inline functions on addresses
                   *
                   * @author Basil Gorin
                   */
                  library AddressUtils {
                    /**
                     * @notice Checks if the target address is a contract
                     * @dev This function will return false if invoked during the constructor of a contract,
                     *      as the code is not actually created until after the constructor finishes.
                     * @param addr address to check
                     * @return whether the target address is a contract
                     */
                    function isContract(address addr) internal view returns (bool) {
                      // a variable to load `extcodesize` to
                      uint256 size = 0;
                      // XXX Currently there is no better way to check if there is a contract in an address
                      // than to check the size of the code at that address.
                      // See https://ethereum.stackexchange.com/a/14016/36603 for more details about how this works.
                      // TODO: Check this again before the Serenity release, because all addresses will be contracts.
                      // solium-disable-next-line security/no-inline-assembly
                      assembly {
                        // retrieve the size of the code at address `addr`
                        size := extcodesize(addr)
                      }
                      // positive size indicates a smart contract address
                      return size > 0;
                    }
                  }
                  

                  File 3 of 5: ILVPool
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
                  import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
                  import { SafeCast } from "./libraries/SafeCast.sol";
                  import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
                  import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
                  import { V2Migrator } from "./base/V2Migrator.sol";
                  import { CorePool } from "./base/CorePool.sol";
                  import { ErrorHandler } from "./libraries/ErrorHandler.sol";
                  import { Stake } from "./libraries/Stake.sol";
                  import { IFactory } from "./interfaces/IFactory.sol";
                  import { ICorePool } from "./interfaces/ICorePool.sol";
                  import { ICorePoolV1 } from "./interfaces/ICorePoolV1.sol";
                  import { SushiLPPool } from "./SushiLPPool.sol";
                  /**
                   * @title ILV Pool
                   *
                   * @dev ILV Pool contract to be deployed, with all base contracts inherited.
                   * @dev Extends functionality working as a router to SushiLP Pool and deployed flash pools.
                   *      through functions like `claimYieldRewardsMultiple()` and `claimVaultRewardsMultiple()`,
                   *      ILV Pool is trusted by other pools and verified by the factory to aggregate functions
                   *      and add quality of life features for stakers.
                   */
                  contract ILVPool is Initializable, V2Migrator {
                      using ErrorHandler for bytes4;
                      using Stake for uint256;
                      using SafeERC20Upgradeable for IERC20Upgradeable;
                      using SafeCast for uint256;
                      using BitMaps for BitMaps.BitMap;
                      /// @dev stores merkle root related to users yield weight in v1.
                      bytes32 public merkleRoot;
                      /// @dev bitmap mapping merkle tree user indexes to a bit that tells
                      ///      whether a user has already migrated yield or not.
                      BitMaps.BitMap internal _usersMigrated;
                      /// @dev maps `keccak256(userAddress,stakeId)` to a bool value that tells
                      ///      if a v1 yield has already been minted by v2 contract.
                      mapping(address => mapping(uint256 => bool)) public v1YieldMinted;
                      /// @dev Used to calculate vault (revenue distribution) rewards, keeps track
                      ///      of the correct ILV balance in the v1 core pool.
                      uint256 public v1PoolTokenReserve;
                      /**
                       * @dev logs `_migratePendingRewards()`
                       *
                       * @param from user address
                       * @param pendingRewardsMigrated value of pending rewards migrated
                       * @param useSILV whether user claimed v1 pending rewards as ILV or sILV
                       */
                      event LogMigratePendingRewards(address indexed from, uint256 pendingRewardsMigrated, bool useSILV);
                      /**
                       * @dev logs `_migrateYieldWeights()`
                       *
                       * @param from user address
                       * @param yieldWeightMigrated total amount of weight coming from yield in v1
                       *
                       */
                      event LogMigrateYieldWeight(address indexed from, uint256 yieldWeightMigrated);
                      /**
                       * @dev logs `mintV1YieldMultiple()`.
                       *
                       * @param from user address
                       * @param value number of ILV tokens minted
                       *
                       */
                      event LogV1YieldMintedMultiple(address indexed from, uint256 value);
                      /// @dev Calls `__V2Migrator_init()`.
                      function initialize(
                          address ilv_,
                          address silv_,
                          address _poolToken,
                          address factory_,
                          uint64 _initTime,
                          uint32 _weight,
                          address _corePoolV1,
                          uint256 v1StakeMaxPeriod_
                      ) external initializer {
                          // calls internal v2 migrator initializer
                          __V2Migrator_init(ilv_, silv_, _poolToken, _corePoolV1, factory_, _initTime, _weight, v1StakeMaxPeriod_);
                      }
                      /**
                       * @dev Updates value that keeps track of v1 global locked tokens weight.
                       *
                       * @param _v1PoolTokenReserve new value to be stored
                       */
                      function setV1PoolTokenReserve(uint256 _v1PoolTokenReserve) external virtual {
                          // only factory controller can update the _v1GlobalWeight
                          _requireIsFactoryController();
                          // update v1PoolTokenReserve state variable
                          v1PoolTokenReserve = _v1PoolTokenReserve;
                      }
                      /// @inheritdoc CorePool
                      function getTotalReserves() external view virtual override returns (uint256 totalReserves) {
                          totalReserves = poolTokenReserve + v1PoolTokenReserve;
                      }
                      /**
                       * @dev Sets the yield weight tree root.
                       * @notice Can only be called by the eDAO.
                       *
                       * @param _merkleRoot 32 bytes tree root.
                       */
                      function setMerkleRoot(bytes32 _merkleRoot) external virtual {
                          // checks if function is being called by PoolFactory.owner()
                          _requireIsFactoryController();
                          // stores the merkle root
                          merkleRoot = _merkleRoot;
                      }
                      /**
                       * @dev Returns whether an user of a given _index in the bitmap has already
                       *      migrated v1 yield weight stored in the merkle tree or not.
                       *
                       * @param _index user index in the bitmap, can be checked in the off-chain
                       *               merkle tree
                       * @return whether user has already migrated yield weights or not
                       */
                      function hasMigratedYield(uint256 _index) public view returns (bool) {
                          // checks if the merkle tree index linked to a user address has a bit of
                          // value 0 or 1
                          return _usersMigrated.get(_index);
                      }
                      /**
                       * @dev Executed by other core pools and flash pools
                       *      as part of yield rewards processing logic (`_claimYieldRewards()` function).
                       * @dev Executed when _useSILV is false and pool is not an ILV pool -
                       *      see `CorePool._processRewards()`.
                       *
                       * @param _staker an address which stakes (the yield reward)
                       * @param _value amount to be staked (yield reward amount)
                       */
                      function stakeAsPool(address _staker, uint256 _value) external nonReentrant {
                          // checks if contract is paused
                          _requireNotPaused();
                          // expects caller to be a valid contract registered by the pool factory
                          this.stakeAsPool.selector.verifyAccess(_factory.poolExists(msg.sender));
                          // gets storage pointer to user
                          User storage user = users[_staker];
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(_staker);
                          // update user state
                          _updateReward(_staker, v1WeightToAdd);
                          // calculates take weight based on how much yield has been generated
                          // (by checking _value) and multiplies by the 2e6 constant, since
                          // yield is always locked for a year.
                          uint256 stakeWeight = _value * Stake.YIELD_STAKE_WEIGHT_MULTIPLIER;
                          // initialize new yield stake being created in memory
                          Stake.Data memory newStake = Stake.Data({
                              value: (_value).toUint120(),
                              lockedFrom: (_now256()).toUint64(),
                              lockedUntil: (_now256() + Stake.MAX_STAKE_PERIOD).toUint64(),
                              isYield: true
                          });
                          // sum new yield stake weight to user's total weight
                          user.totalWeight += (stakeWeight).toUint248();
                          // add the new yield stake to storage
                          user.stakes.push(newStake);
                          // update global weight and global pool token count
                          globalWeight += stakeWeight;
                          poolTokenReserve += _value;
                          // emits an event
                          emit LogStake(
                              msg.sender,
                              _staker,
                              (user.stakes.length - 1),
                              _value,
                              (_now256() + Stake.MAX_STAKE_PERIOD).toUint64()
                          );
                      }
                      /**
                       * @dev Calls internal `_migrateLockedStakes`,  `_migrateYieldWeights`
                       *      and `_migratePendingRewards` functions for a complete migration
                       *      of a v1 user to v2.
                       * @dev See `_migrateLockedStakes` and _`migrateYieldWeights`.
                       */
                      function executeMigration(
                          bytes32[] calldata _proof,
                          uint256 _index,
                          uint248 _yieldWeight,
                          uint256 _pendingV1Rewards,
                          bool _useSILV,
                          uint256[] calldata _stakeIds
                      ) external virtual {
                          // verifies that user isn't a v1 blacklisted user
                          _requireNotBlacklisted(msg.sender);
                          // checks if contract is paused
                          _requireNotPaused();
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(msg.sender);
                          // update user state
                          _updateReward(msg.sender, v1WeightToAdd);
                          // call internal migrate locked stake function
                          // which does the loop to store each v1 stake
                          // reference in v2 and all required data
                          _migrateLockedStakes(_stakeIds);
                          // checks if user is also migrating the v1 yield accumulated weight
                          if (_yieldWeight > 0) {
                              // if that's the case, passes the merkle proof with the user index
                              // in the merkle tree, and the yield weight being migrated
                              // which will be verified, and then update user state values by the
                              // internal function
                              _migrateYieldWeights(_proof, _index, _yieldWeight, _pendingV1Rewards, _useSILV);
                          }
                      }
                      /**
                       * @dev Calls multiple pools claimYieldRewardsFromRouter() in order to claim yield
                       * in 1 transaction.
                       *
                       * @notice ILV pool works as a router for claiming multiple pools registered
                       *         in the factory.
                       *
                       * @param _pools array of pool addresses
                       * @param _useSILV array of bool values telling if the pool should claim reward
                       *                 as ILV or sILV
                       */
                      function claimYieldRewardsMultiple(address[] calldata _pools, bool[] calldata _useSILV) external virtual {
                          // checks if contract is paused
                          _requireNotPaused();
                          // we're using selector to simplify input and access validation
                          bytes4 fnSelector = this.claimYieldRewardsMultiple.selector;
                          // checks if user passed the correct number of inputs
                          fnSelector.verifyInput(_pools.length == _useSILV.length, 0);
                          // loops over each pool passed to execute the necessary checks, and call
                          // the functions according to the pool
                          for (uint256 i = 0; i < _pools.length; i++) {
                              // gets current pool in the loop
                              address pool = _pools[i];
                              // verifies that the given pool is a valid pool registered by the pool
                              // factory contract
                              fnSelector.verifyAccess(IFactory(_factory).poolExists(pool));
                              // if the pool passed is the ILV pool (i.e this contract),
                              // just calls an internal function
                              if (ICorePool(pool).poolToken() == _ilv) {
                                  // call internal _claimYieldRewards
                                  _claimYieldRewards(msg.sender, _useSILV[i]);
                              } else {
                                  // if not, executes a calls to the other core pool which will handle
                                  // the other pool reward claim
                                  SushiLPPool(pool).claimYieldRewardsFromRouter(msg.sender, _useSILV[i]);
                              }
                          }
                      }
                      /**
                       * @dev Calls multiple pools claimVaultRewardsFromRouter() in order to claim yield
                       * in 1 transaction.
                       *
                       * @notice ILV pool works as a router for claiming multiple pools registered
                       *         in the factory
                       *
                       * @param _pools array of pool addresses
                       */
                      function claimVaultRewardsMultiple(address[] calldata _pools) external virtual {
                          // checks if contract is paused
                          _requireNotPaused();
                          // loops over each pool passed to execute the necessary checks, and call
                          // the functions according to the pool
                          for (uint256 i = 0; i < _pools.length; i++) {
                              // gets current pool in the loop
                              address pool = _pools[i];
                              // we're using selector to simplify input and state validation
                              // checks if the given pool is a valid one registred by the pool
                              // factory contract
                              this.claimVaultRewardsMultiple.selector.verifyAccess(IFactory(_factory).poolExists(pool));
                              // if the pool passed is the ILV pool (i.e this contract),
                              // just calls an internal function
                              if (ICorePool(pool).poolToken() == _ilv) {
                                  // call internal _claimVaultRewards
                                  _claimVaultRewards(msg.sender);
                              } else {
                                  // if not, executes a calls to the other core pool which will handle
                                  // the other pool reward claim
                                  SushiLPPool(pool).claimVaultRewardsFromRouter(msg.sender);
                              }
                          }
                      }
                      /**
                       * @dev Aggregates in one single mint call multiple yield stakeIds from v1.
                       * @dev reads v1 ILV pool to execute checks, if everything is correct, it stores
                       *      in memory total amount of yield to be minted and calls the PoolFactory to mint
                       *      it to msg.sender.
                       *
                       * @notice V1 won't be able to mint ILV yield anymore. This mean only this function
                       *         in the V2 contract is able to mint previously accumulated V1 yield.
                       *
                       * @param _stakeIds array of yield ids in v1 from msg.sender user
                       */
                      function mintV1YieldMultiple(uint256[] calldata _stakeIds) external virtual {
                          // we're using function selector to simplify validation
                          bytes4 fnSelector = this.mintV1YieldMultiple.selector;
                          // verifies that user isn't a v1 blacklisted user
                          _requireNotBlacklisted(msg.sender);
                          // checks if contract is paused
                          _requireNotPaused();
                          // gets storage pointer to the user
                          User storage user = users[msg.sender];
                          // initialize variables that will be used inside the loop
                          // to store how much yield needs to be minted and how much
                          // weight needs to be removed from the user
                          uint256 amountToMint;
                          uint256 weightToRemove;
                          // initializes variable that will store how much v1 weight the user has
                          uint256 v1WeightToAdd;
                          // avoids stack too deep error
                          {
                              // uses v1 weight values for rewards calculations
                              uint256 _v1WeightToAdd = _useV1Weight(msg.sender);
                              // update user state
                              _updateReward(msg.sender, _v1WeightToAdd);
                              v1WeightToAdd = _v1WeightToAdd;
                          }
                          // loops over each stake id, doing the necessary checks and
                          // updating the mapping that keep tracks of v1 yield mints.
                          for (uint256 i = 0; i < _stakeIds.length; i++) {
                              // gets current stake id in the loop
                              uint256 _stakeId = _stakeIds[i];
                              // call v1 core pool to get all required data associated with
                              // the passed v1 stake id
                              (uint256 tokenAmount, uint256 _weight, uint64 lockedFrom, uint64 lockedUntil, bool isYield) = ICorePoolV1(
                                  corePoolV1
                              ).getDeposit(msg.sender, _stakeId);
                              // checks if the obtained v1 stake (through getDeposit)
                              // is indeed yield
                              fnSelector.verifyState(isYield, i * 3);
                              // expects the yield v1 stake to be unlocked
                              fnSelector.verifyState(_now256() > lockedUntil, i * 4 + 1);
                              // expects that the v1 stake hasn't been minted yet
                              fnSelector.verifyState(!v1YieldMinted[msg.sender][_stakeId], i * 5 + 2);
                              // verifies if the yield has been created before v2 launches
                              fnSelector.verifyState(lockedFrom < _v1StakeMaxPeriod, i * 6 + 3);
                              // marks v1 yield as minted
                              v1YieldMinted[msg.sender][_stakeId] = true;
                              // updates variables that will be used for minting yield and updating
                              // user struct later
                              amountToMint += tokenAmount;
                              weightToRemove += _weight;
                          }
                          // subtracts value accumulated during the loop
                          user.totalWeight -= (weightToRemove).toUint248();
                          // subtracts weight and token value from global variables
                          globalWeight -= weightToRemove;
                          // gets token value by dividing by yield weight multiplier
                          poolTokenReserve -= (weightToRemove) / Stake.YIELD_STAKE_WEIGHT_MULTIPLIER;
                          // expects the factory to mint ILV yield to the msg.sender user
                          // after all checks and calculations have been successfully
                          // executed
                          _factory.mintYieldTo(msg.sender, amountToMint, false);
                          // emits an event
                          emit LogV1YieldMintedMultiple(msg.sender, amountToMint);
                      }
                      /**
                       * @dev Verifies a proof from the yield weights merkle, and if it's valid,
                       *      adds the v1 user yield weight to the v2 `user.totalWeight`.
                       * @dev The yield weights merkle tree will be published after the initial contracts
                       *      deployment, and then the merkle root is added through `setMerkleRoot` function.
                       *
                       * @param _proof bytes32 array with the proof generated off-chain
                       * @param _index user index in the merkle tree
                       * @param _yieldWeight user yield weight in v1 stored by the merkle tree
                       * @param _pendingV1Rewards user pending rewards in v1 stored by the merkle tree
                       * @param _useSILV whether the user wants rewards in sILV token or in a v2 ILV yield stake
                       */
                      function _migrateYieldWeights(
                          bytes32[] calldata _proof,
                          uint256 _index,
                          uint256 _yieldWeight,
                          uint256 _pendingV1Rewards,
                          bool _useSILV
                      ) internal virtual {
                          // gets storage pointer to the user
                          User storage user = users[msg.sender];
                          // bytes4(keccak256("_migrateYieldWeights(bytes32[],uint256,uint256)")))
                          bytes4 fnSelector = 0x660e5908;
                          // requires that the user hasn't migrated the yield yet
                          fnSelector.verifyAccess(!hasMigratedYield(_index));
                          // compute leaf and verify merkle proof
                          bytes32 leaf = keccak256(abi.encodePacked(_index, msg.sender, _yieldWeight, _pendingV1Rewards));
                          // verifies the merkle proof and requires the return value to be true
                          fnSelector.verifyInput(MerkleProof.verify(_proof, merkleRoot, leaf), 0);
                          // gets the value compounded into v2 as ILV yield to be added into v2 user.totalWeight
                          uint256 pendingRewardsCompounded = _migratePendingRewards(_pendingV1Rewards, _useSILV);
                          uint256 weightCompounded = pendingRewardsCompounded * Stake.YIELD_STAKE_WEIGHT_MULTIPLIER;
                          uint256 ilvYieldMigrated = _yieldWeight / Stake.YIELD_STAKE_WEIGHT_MULTIPLIER;
                          // add v1 yield weight to the v2 user
                          user.totalWeight += (_yieldWeight + weightCompounded).toUint248();
                          // adds v1 pending yield compounded + v1 total yield to global weight
                          // and poolTokenReserve in the v2 contract.
                          globalWeight += (weightCompounded + _yieldWeight);
                          poolTokenReserve += (pendingRewardsCompounded + ilvYieldMigrated);
                          // set user as claimed in bitmap
                          _usersMigrated.set(_index);
                          // emits an event
                          emit LogMigrateYieldWeight(msg.sender, _yieldWeight);
                      }
                      /**
                       * @dev Gets pending rewards in the v1 ilv pool and v1 lp pool stored in the merkle tree,
                       *      and allows the v1 users of those pools to claim them as ILV compounded in the v2 pool or
                       *      sILV minted to their wallet.
                       * @dev Eligible users are filtered and stored in the merkle tree.
                       *
                       * @param _pendingV1Rewards user pending rewards in v1 stored by the merkle tree
                       * @param _useSILV whether the user wants rewards in sILV token or in a v2 ILV yield stake
                       *
                       * @return pendingRewardsCompounded returns the value compounded into the v2 pool (if the user selects ILV)
                       */
                      function _migratePendingRewards(uint256 _pendingV1Rewards, bool _useSILV)
                          internal
                          virtual
                          returns (uint256 pendingRewardsCompounded)
                      {
                          // gets pointer to user
                          User storage user = users[msg.sender];
                          // if the user (msg.sender) wants to mint pending rewards as sILV, simply mint
                          if (_useSILV) {
                              // calls the factory to mint sILV
                              _factory.mintYieldTo(msg.sender, _pendingV1Rewards, _useSILV);
                          } else {
                              // otherwise we create a new v2 yield stake (ILV)
                              Stake.Data memory stake = Stake.Data({
                                  value: (_pendingV1Rewards).toUint120(),
                                  lockedFrom: (_now256()).toUint64(),
                                  lockedUntil: (_now256() + Stake.MAX_STAKE_PERIOD).toUint64(),
                                  isYield: true
                              });
                              // adds new ILV yield stake to user array
                              // notice that further values will be updated later in execution
                              // (user.totalWeight, user.subYieldRewards, user.subVaultRewards, ...)
                              user.stakes.push(stake);
                              // updates function's return value
                              pendingRewardsCompounded = _pendingV1Rewards;
                          }
                          // emits an event
                          emit LogMigratePendingRewards(msg.sender, _pendingV1Rewards, _useSILV);
                      }
                      /**
                       * @inheritdoc CorePool
                       * @dev In the ILV Pool we verify that the user isn't coming from v1.
                       * @dev If user has weight in v1, we can't allow them to call this
                       *      function, otherwise it would throw an error in the new address when calling
                       *      mintV1YieldMultiple if the user migrates.
                       */
                      function moveFundsFromWallet(address _to) public virtual override {
                          // we're using function selector to simplify validation
                          bytes4 fnSelector = this.moveFundsFromWallet.selector;
                          // we query v1 ilv pool contract
                          (, uint256 totalWeight, , ) = ICorePoolV1(corePoolV1).users(msg.sender);
                          // we check that the v1 total weight is 0 i.e the user can't have any yield
                          fnSelector.verifyState(totalWeight == 0, 0);
                          // call parent moveFundsFromWalet which contains further checks and the actual
                          // execution
                          super.moveFundsFromWallet(_to);
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[46] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @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 a proxied contract can't have 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.
                   */
                  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() {
                          require(_initializing || !_initialized, "Initializable: contract is already initialized");
                          bool isTopLevelCall = !_initializing;
                          if (isTopLevelCall) {
                              _initializing = true;
                              _initialized = true;
                          }
                          _;
                          if (isTopLevelCall) {
                              _initializing = false;
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @dev Interface of the ERC20 standard as defined in the EIP.
                   */
                  interface IERC20Upgradeable {
                      /**
                       * @dev Returns the amount of tokens in existence.
                       */
                      function totalSupply() external view returns (uint256);
                      /**
                       * @dev Returns the amount of tokens owned by `account`.
                       */
                      function balanceOf(address account) external view returns (uint256);
                      /**
                       * @dev Moves `amount` tokens from the caller's account to `recipient`.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transfer(address recipient, uint256 amount) external returns (bool);
                      /**
                       * @dev Returns the remaining number of tokens that `spender` will be
                       * allowed to spend on behalf of `owner` through {transferFrom}. This is
                       * zero by default.
                       *
                       * This value changes when {approve} or {transferFrom} are called.
                       */
                      function allowance(address owner, address spender) external view returns (uint256);
                      /**
                       * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * IMPORTANT: Beware that changing an allowance with this method brings the risk
                       * that someone may use both the old and the new allowance by unfortunate
                       * transaction ordering. One possible solution to mitigate this race
                       * condition is to first reduce the spender's allowance to 0 and set the
                       * desired value afterwards:
                       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                       *
                       * Emits an {Approval} event.
                       */
                      function approve(address spender, uint256 amount) external returns (bool);
                      /**
                       * @dev Moves `amount` tokens from `sender` to `recipient` using the
                       * allowance mechanism. `amount` is then deducted from the caller's
                       * allowance.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transferFrom(
                          address sender,
                          address recipient,
                          uint256 amount
                      ) external returns (bool);
                      /**
                       * @dev Emitted when `value` tokens are moved from one account (`from`) to
                       * another (`to`).
                       *
                       * Note that `value` may be zero.
                       */
                      event Transfer(address indexed from, address indexed to, uint256 value);
                      /**
                       * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                       * a call to {approve}. `value` is the new allowance.
                       */
                      event Approval(address indexed owner, address indexed spender, uint256 value);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  import "../IERC20Upgradeable.sol";
                  import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
                      using AddressUpgradeable for address;
                      function safeTransfer(
                          IERC20Upgradeable token,
                          address to,
                          uint256 value
                      ) internal {
                          _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                      }
                      function safeTransferFrom(
                          IERC20Upgradeable 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(
                          IERC20Upgradeable 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(
                          IERC20Upgradeable 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(
                          IERC20Upgradeable 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(IERC20Upgradeable 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 v4.4.1 (utils/math/SafeCast.sol)
                  pragma solidity 0.8.4;
                  import { ErrorHandler } from "./ErrorHandler.sol";
                  /**
                   * @notice Copied from OpenZeppelin's SafeCast.sol, adapted to use just in the required
                   * uint sizes.
                   * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
                   * checks.
                   *
                   * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
                   * easily result in undesired exploitation or bugs, since developers usually
                   * assume that overflows raise errors. `SafeCast` restores this intuition by
                   * reverting the transaction when such an operation overflows.
                   *
                   * Using this library instead of the unchecked operations eliminates an entire
                   * class of bugs, so it's recommended to use it always.
                   *
                   * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
                   * all math on `uint256` and `int256` and then downcasting.
                   */
                  library SafeCast {
                      using ErrorHandler for bytes4;
                      /**
                       * @dev Returns the downcasted uint248 from uint256, reverting on
                       * overflow (when the input is greater than largest uint248).
                       *
                       * Counterpart to Solidity's `uint248` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 248 bits
                       */
                      function toUint248(uint256 _value) internal pure returns (uint248) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint248(uint256))"))`
                          bytes4 fnSelector = 0x3fd72672;
                          fnSelector.verifyInput(_value <= type(uint248).max, 0);
                          return uint248(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint128 from uint256, reverting on
                       * overflow (when the input is greater than largest uint128).
                       *
                       * Counterpart to Solidity's `uint128` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 128 bits
                       */
                      function toUint128(uint256 _value) internal pure returns (uint128) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint128(uint256))"))`
                          bytes4 fnSelector = 0x809fdd33;
                          fnSelector.verifyInput(_value <= type(uint128).max, 0);
                          return uint128(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint120 from uint256, reverting on
                       * overflow (when the input is greater than largest uint120).
                       *
                       * Counterpart to Solidity's `uint120` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 120 bits
                       */
                      function toUint120(uint256 _value) internal pure returns (uint120) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint120(uint256))"))`
                          bytes4 fnSelector = 0x1e4e4bad;
                          fnSelector.verifyInput(_value <= type(uint120).max, 0);
                          return uint120(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint64 from uint256, reverting on
                       * overflow (when the input is greater than largest uint64).
                       *
                       * Counterpart to Solidity's `uint64` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 64 bits
                       */
                      function toUint64(uint256 _value) internal pure returns (uint64) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint64(uint256))"))`
                          bytes4 fnSelector = 0x2665fad0;
                          fnSelector.verifyInput(_value <= type(uint64).max, 0);
                          return uint64(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint32 from uint256, reverting on
                       * overflow (when the input is greater than largest uint32).
                       *
                       * Counterpart to Solidity's `uint32` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 32 bits
                       */
                      function toUint32(uint256 _value) internal pure returns (uint32) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint32(uint256))"))`
                          bytes4 fnSelector = 0xc8193255;
                          fnSelector.verifyInput(_value <= type(uint32).max, 0);
                          return uint32(_value);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential.
                   * Largelly inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor].
                   */
                  library BitMaps {
                      struct BitMap {
                          mapping(uint256 => uint256) _data;
                      }
                      /**
                       * @dev Returns whether the bit at `index` is set.
                       */
                      function get(BitMap storage bitmap, uint256 index) internal view returns (bool) {
                          uint256 bucket = index >> 8;
                          uint256 mask = 1 << (index & 0xff);
                          return bitmap._data[bucket] & mask != 0;
                      }
                      /**
                       * @dev Sets the bit at `index` to the boolean `value`.
                       */
                      function setTo(
                          BitMap storage bitmap,
                          uint256 index,
                          bool value
                      ) internal {
                          if (value) {
                              set(bitmap, index);
                          } else {
                              unset(bitmap, index);
                          }
                      }
                      /**
                       * @dev Sets the bit at `index`.
                       */
                      function set(BitMap storage bitmap, uint256 index) internal {
                          uint256 bucket = index >> 8;
                          uint256 mask = 1 << (index & 0xff);
                          bitmap._data[bucket] |= mask;
                      }
                      /**
                       * @dev Unsets the bit at `index`.
                       */
                      function unset(BitMap storage bitmap, uint256 index) internal {
                          uint256 bucket = index >> 8;
                          uint256 mask = 1 << (index & 0xff);
                          bitmap._data[bucket] &= ~mask;
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @dev These functions deal with verification of Merkle Trees proofs.
                   *
                   * The proofs can be generated using the JavaScript library
                   * https://github.com/miguelmota/merkletreejs[merkletreejs].
                   * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
                   *
                   * See `test/utils/cryptography/MerkleProof.test.js` for some examples.
                   */
                  library MerkleProof {
                      /**
                       * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
                       * defined by `root`. For this, a `proof` must be provided, containing
                       * sibling hashes on the branch from the leaf to the root of the tree. Each
                       * pair of leaves and each pair of pre-images are assumed to be sorted.
                       */
                      function verify(
                          bytes32[] memory proof,
                          bytes32 root,
                          bytes32 leaf
                      ) internal pure returns (bool) {
                          bytes32 computedHash = leaf;
                          for (uint256 i = 0; i < proof.length; i++) {
                              bytes32 proofElement = proof[i];
                              if (computedHash <= proofElement) {
                                  // Hash(current computed hash + current element of the proof)
                                  computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
                              } else {
                                  // Hash(current element of the proof + current computed hash)
                                  computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
                              }
                          }
                          // Check if the computed hash (root) is equal to the provided root
                          return computedHash == root;
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import { ICorePoolV1 } from "../interfaces/ICorePoolV1.sol";
                  import { ErrorHandler } from "../libraries/ErrorHandler.sol";
                  import { Stake } from "../libraries/Stake.sol";
                  import { CorePool } from "./CorePool.sol";
                  /**
                   * @title V2Migrator
                   *
                   * @dev V2Migrator inherits all CorePool base contract functionaltiy, and adds
                   *      v1 to v2 migration related functions. This is a core smart contract of
                   *      Sushi LP and ILV pools, and manages users locked and yield weights coming
                   *      from v1.
                   * @dev Parameters need to be reviewed carefully before deployment for the migration process.
                   * @dev Users will migrate their locked stakes, which are stored in the contract,
                   *      and v1 total yield weights by data stored in a merkle tree using merkle proofs.
                   */
                  abstract contract V2Migrator is Initializable, CorePool {
                      using ErrorHandler for bytes4;
                      using Stake for uint256;
                      /// @dev Maps v1 addresses that are black listed for v2 migration.
                      mapping(address => bool) public isBlacklisted;
                      /// @dev Stores maximum timestamp of a v1 stake (yield) accepted in v2.
                      uint256 internal _v1StakeMaxPeriod;
                      /// @dev Stores maximum timestamp of a v1 stake (deposit) accepted in v2.
                      uint256 internal constant _v1DepositMaxPeriod = 1648150500;
                      /**
                       * @dev logs `_migrateLockedStakes()`
                       *
                       * @param from user address
                       * @param totalV1WeightAdded total amount of weight coming from locked stakes in v1
                       *
                       */
                      event LogMigrateLockedStakes(address indexed from, uint256 totalV1WeightAdded);
                      /**
                       * @dev V2Migrator initializer function.
                       *
                       * @param v1StakeMaxPeriod_ max timestamp that we accept _lockedFrom values
                       *                         in v1 stakes
                       */
                      function __V2Migrator_init(
                          address ilv_,
                          address silv_,
                          address _poolToken,
                          address _corePoolV1,
                          address factory_,
                          uint64 _initTime,
                          uint32 _weight,
                          uint256 v1StakeMaxPeriod_
                      ) internal initializer {
                          // call internal core pool intializar
                          __CorePool_init(ilv_, silv_, _poolToken, _corePoolV1, factory_, _initTime, _weight);
                          // sets max period for upgrading to V2 contracts i.e migrating
                          _v1StakeMaxPeriod = v1StakeMaxPeriod_;
                      }
                      /**
                       * @notice Blacklists a list of v1 user addresses by setting the
                       *         _isBlacklisted flag to true.
                       *
                       * @dev The intention is to prevent addresses that exploited v1 to be able to move
                       *      stake ids to the v2 contracts and to be able to mint any yield from a v1
                       *      stake id with the isYield flag set to true.
                       *
                       * @param _users v1 users address array
                       */
                      function blacklistUsers(address[] calldata _users) external virtual {
                          // only the factory controller can blacklist users
                          _requireIsFactoryController();
                          // we're using selector to simplify validation
                          bytes4 fnSelector = this.blacklistUsers.selector;
                          // gets each user in the array to be blacklisted
                          for (uint256 i = 0; i < _users.length; i++) {
                              // makes sure user passed isn't the address 0
                              fnSelector.verifyInput(_users[i] != address(0), 0);
                              // updates mapping
                              isBlacklisted[_users[i]] = true;
                          }
                      }
                      /**
                       * @dev External migrateLockedStakes call, used in the Sushi LP pool contract.
                       * @dev The function is used by users that want to migrate locked stakes in v1,
                       *      but have no yield in the pool. This happens in two scenarios:
                       *
                       *      1 - The user pool is the Sushi LP pool, which only has stakes;
                       *      2 - The user joined ILV pool recently, doesn't have much yield and
                       *          doesn't want to migrate their yield weight in the pool;
                       * @notice Most of the times this function will be used in the inherited Sushi
                       *         LP pool contract (called by the v1 user coming from sushi pool),
                       *         but it's possible that a v1 user coming from the ILV pool decides
                       *         to use this function instead of `executeMigration()` defined in
                       *         the ILV pool contract.
                       *
                       * @param _stakeIds array of v1 stake ids
                       */
                      function migrateLockedStakes(uint256[] calldata _stakeIds) external virtual {
                          // verifies that user isn't a v1 blacklisted user
                          _requireNotBlacklisted(msg.sender);
                          // checks if contract is paused
                          _requireNotPaused();
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(msg.sender);
                          // update user state
                          _updateReward(msg.sender, v1WeightToAdd);
                          // call internal migrate locked stake function
                          // which does the loop to store each v1 stake
                          // reference in v2 and all required data
                          _migrateLockedStakes(_stakeIds);
                      }
                      /**
                       * @dev Reads v1 core pool locked stakes data (by looping through the `_stakeIds` array),
                       *      checks if it's a valid v1 stake to migrate and save the id to v2 user struct.
                       *
                       * @dev Only `msg.sender` can migrate v1 stakes to v2.
                       *
                       * @param _stakeIds array of v1 stake ids
                       */
                      function _migrateLockedStakes(uint256[] calldata _stakeIds) internal virtual {
                          User storage user = users[msg.sender];
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("_migrateLockedStakes(uint256[])"))`
                          bytes4 fnSelector = 0x80812525;
                          // initializes variable which will tell how much
                          // weight in v1 the user is bringing to v2
                          uint256 totalV1WeightAdded;
                          // loops over each v1 stake id passed to do the necessary validity checks
                          // and store the values required in v2 to keep track of v1 weight in order
                          // to include it in v2 rewards (yield and revenue distribution) calculations
                          for (uint256 i = 0; i < _stakeIds.length; i++) {
                              // reads the v1 stake by calling the v1 core pool getDeposit and separates
                              // all required data in the struct to be used
                              (, uint256 _weight, uint64 lockedFrom, , bool isYield) = ICorePoolV1(corePoolV1).getDeposit(
                                  msg.sender,
                                  _stakeIds[i]
                              );
                              // checks if the v1 stake is in the valid period for migration
                              fnSelector.verifyState(lockedFrom <= _v1DepositMaxPeriod, i * 3);
                              // checks if the v1 stake has been locked originally and isn't a yield
                              // stake, which are the requirements for moving to v2 through this function
                              fnSelector.verifyState(lockedFrom > 0 && !isYield, i * 3 + 1);
                              // checks if the user has already brought those v1 stakes to v2
                              fnSelector.verifyState(v1StakesWeights[msg.sender][_stakeIds[i]] == 0, i * 3 + 2);
                              // adds v1 weight to the dynamic mapping which will be used in calculations
                              v1StakesWeights[msg.sender][_stakeIds[i]] = _weight;
                              // updates the variable keeping track of the total weight migrated
                              totalV1WeightAdded += _weight;
                              // update value keeping track of v1 stakes ids mapping length
                              user.v1IdsLength++;
                              // adds stake id to mapping keeping track of each v1 stake id
                              user.v1StakesIds[user.v1IdsLength - 1] = _stakeIds[i];
                          }
                          // emits an event
                          emit LogMigrateLockedStakes(msg.sender, totalV1WeightAdded);
                      }
                      /**
                       * @dev Utility used by functions that can't allow blacklisted users to call.
                       * @dev Blocks user addresses stored in the _isBlacklisted mapping to call actions like
                       *      minting v1 yield stake ids and migrating locked stakes.
                       */
                      function _requireNotBlacklisted(address _user) internal view virtual {
                          // we're using selector to simplify input and access validation
                          bytes4 fnSelector = this.migrateLockedStakes.selector;
                          // makes sure that msg.sender isn't a blacklisted address
                          fnSelector.verifyAccess(!isBlacklisted[_user]);
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[48] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
                  import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
                  import { SafeCast } from "../libraries/SafeCast.sol";
                  import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
                  import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
                  import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
                  import { Timestamp } from "./Timestamp.sol";
                  import { VaultRecipient } from "./VaultRecipient.sol";
                  import { ErrorHandler } from "../libraries/ErrorHandler.sol";
                  import { Stake } from "../libraries/Stake.sol";
                  import { IILVPool } from "../interfaces/IILVPool.sol";
                  import { IFactory } from "../interfaces/IFactory.sol";
                  import { ICorePool } from "../interfaces/ICorePool.sol";
                  import { ICorePoolV1 } from "../interfaces/ICorePoolV1.sol";
                  /**
                   * @title Core Pool
                   *
                   * @notice An abstract contract containing common logic for ILV and ILV/ETH SLP pools.
                   *
                   * @dev Base smart contract for ILV and LP pool. Stores each pool user by mapping
                   *      its address to the user struct. User struct stores v2 stakes, which fit
                   *      in 1 storage slot each (by using the Stake lib), total weights, pending
                   *      yield and revenue distributions, and v1 stake ids. ILV and LP stakes can
                   *      be made through flexible stake mode, which only increments the flexible
                   *      balance of a given user, or through locked staking. Locked staking creates
                   *      a new Stake element fitting 1 storage slot with its value and lock duration.
                   *      When calculating pending rewards, CorePool checks v1 locked stakes weights
                   *      to increment in the calculations and stores pending yield and pending revenue
                   *      distributions. Every time a stake or unstake related function is called,
                   *      it updates pending values, but don't require instant claimings. Rewards
                   *      claiming are executed in separate functions, and in the case of yield,
                   *      it also requires the user checking whether ILV or sILV is wanted as the yield reward.
                   *
                   * @dev Deployment and initialization.
                   *      After proxy is deployed and attached to the implementation, it should be
                   *      registered by the PoolFactory contract
                   *      Additionally, 3 token instance addresses must be defined on deployment:
                   *          - ILV token address
                   *          - sILV token address, used to mint sILV rewards
                   *          - pool token address, it can be ILV token address, ILV/ETH pair address, and others
                   *
                   * @dev Pool weight defines the fraction of the yield current pool receives among the other pools,
                   *      pool factory is responsible for the weight synchronization between the pools.
                   * @dev The weight is logically 20% for ILV pool and 80% for ILV/ETH pool initially.
                   *      It can be changed through ICCPs and new flash pools added in the protocol.
                   *      Since Solidity doesn't support fractions the weight is defined by the division of
                   *      pool weight by total pools weight (sum of all registered pools within the factory).
                   * @dev For ILV Pool we use 200 as weight and for ILV/ETH SLP pool - 800.
                   *
                   */
                  abstract contract CorePool is
                      Initializable,
                      UUPSUpgradeable,
                      VaultRecipient,
                      ReentrancyGuardUpgradeable,
                      PausableUpgradeable,
                      Timestamp
                  {
                      using SafeERC20Upgradeable for IERC20Upgradeable;
                      using SafeCast for uint256;
                      using Stake for Stake.Data;
                      using ErrorHandler for bytes4;
                      using Stake for uint256;
                      /// @dev Data structure representing token holder using a pool.
                      struct User {
                          /// @dev pending yield rewards to be claimed
                          uint128 pendingYield;
                          /// @dev pending revenue distribution to be claimed
                          uint128 pendingRevDis;
                          /// @dev Total weight
                          uint248 totalWeight;
                          /// @dev number of v1StakesIds
                          uint8 v1IdsLength;
                          /// @dev Checkpoint variable for yield calculation
                          uint256 yieldRewardsPerWeightPaid;
                          /// @dev Checkpoint variable for vault rewards calculation
                          uint256 vaultRewardsPerWeightPaid;
                          /// @dev An array of holder's stakes
                          Stake.Data[] stakes;
                          /// @dev A mapping of holder's stakes ids in V1
                          mapping(uint256 => uint256) v1StakesIds;
                      }
                      /// @dev Data structure used in `unstakeLockedMultiple()` function.
                      struct UnstakeParameter {
                          uint256 stakeId;
                          uint256 value;
                      }
                      /// @dev Token holder storage, maps token holder address to their data record.
                      mapping(address => User) public users;
                      /// @dev Maps `keccak256(userAddress,stakeId)` to a uint256 value that tells
                      ///      a v1 locked stake weight that has already been migrated to v2
                      ///      and is updated through _useV1Weight.
                      mapping(address => mapping(uint256 => uint256)) public v1StakesWeights;
                      /// @dev Link to sILV ERC20 Token instance.
                      address internal _silv;
                      /// @dev Link to ILV ERC20 Token instance.
                      address internal _ilv;
                      /// @dev Address of v1 core pool with same poolToken.
                      address internal corePoolV1;
                      /// @dev Link to the pool token instance, for example ILV or ILV/ETH pair.
                      address public poolToken;
                      /// @dev Pool weight, initial values are 200 for ILV pool and 800 for ILV/ETH.
                      uint32 public weight;
                      /// @dev Timestamp of the last yield distribution event.
                      uint64 public lastYieldDistribution;
                      /// @dev Used to calculate yield rewards.
                      /// @dev This value is different from "reward per token" used in flash pool.
                      /// @dev Note: stakes are different in duration and "weight" reflects that.
                      uint256 public yieldRewardsPerWeight;
                      /// @dev Used to calculate rewards, keeps track of the tokens weight locked in staking.
                      uint256 public globalWeight;
                      /// @dev Used to calculate rewards, keeps track of the correct token weight in the v1
                      ///      core pool.
                      uint256 public v1GlobalWeight;
                      /// @dev Pool tokens value available in the pool;
                      ///      pool token examples are ILV (ILV core pool) or ILV/ETH pair (LP core pool).
                      /// @dev For LP core pool this value doesnt' count for ILV tokens received as Vault rewards
                      ///      while for ILV core pool it does count for such tokens as well.
                      uint256 public poolTokenReserve;
                      /// @dev Flag indicating pool type, false means "core pool".
                      bool public constant isFlashPool = false;
                      /**
                       * @dev Fired in _stake() and stakeAsPool() in ILVPool contract.
                       * @param by address that executed the stake function (user or pool)
                       * @param from token holder address, the tokens will be returned to that address
                       * @param stakeId id of the new stake created
                       * @param value value of tokens staked
                       * @param lockUntil timestamp indicating when tokens should unlock (max 2 years)
                       */
                      event LogStake(address indexed by, address indexed from, uint256 stakeId, uint256 value, uint64 lockUntil);
                      /**
                       * @dev Fired in `unstakeLocked()`.
                       *
                       * @param to address receiving the tokens (user)
                       * @param stakeId id value of the stake
                       * @param value number of tokens unstaked
                       * @param isYield whether stake struct unstaked was coming from yield or not
                       */
                      event LogUnstakeLocked(address indexed to, uint256 stakeId, uint256 value, bool isYield);
                      /**
                       * @dev Fired in `unstakeLockedMultiple()`.
                       *
                       * @param to address receiving the tokens (user)
                       * @param totalValue total number of tokens unstaked
                       * @param unstakingYield whether unstaked tokens had isYield flag true or false
                       */
                      event LogUnstakeLockedMultiple(address indexed to, uint256 totalValue, bool unstakingYield);
                      /**
                       * @dev Fired in `_sync()`, `sync()` and dependent functions (stake, unstake, etc.).
                       *
                       * @param by an address which performed an operation
                       * @param yieldRewardsPerWeight updated yield rewards per weight value
                       * @param lastYieldDistribution usually, current timestamp
                       */
                      event LogSync(address indexed by, uint256 yieldRewardsPerWeight, uint64 lastYieldDistribution);
                      /**
                       * @dev Fired in `_claimYieldRewards()`.
                       *
                       * @param by an address which claimed the rewards (staker or ilv pool contract
                       *            in case of a multiple claim call)
                       * @param from an address which received the yield
                       * @param sILV flag indicating if reward was paid (minted) in sILV
                       * @param value value of yield paid
                       */
                      event LogClaimYieldRewards(address indexed by, address indexed from, bool sILV, uint256 value);
                      /**
                       * @dev Fired in `_claimVaultRewards()`.
                       *
                       * @param by an address which claimed the rewards (staker or ilv pool contract
                       *            in case of a multiple claim call)
                       * @param from an address which received the yield
                       * @param value value of yield paid
                       */
                      event LogClaimVaultRewards(address indexed by, address indexed from, uint256 value);
                      /**
                       * @dev Fired in `_updateRewards()`.
                       *
                       * @param by an address which processed the rewards (staker or ilv pool contract
                       *            in case of a multiple claim call)
                       * @param from an address which received the yield
                       * @param yieldValue value of yield processed
                       * @param revDisValue value of revenue distribution processed
                       */
                      event LogUpdateRewards(address indexed by, address indexed from, uint256 yieldValue, uint256 revDisValue);
                      /**
                       * @dev fired in `moveFundsFromWallet()`.
                       *
                       * @param from user asking migration
                       * @param to new user address
                       * @param previousTotalWeight total weight of `from` before moving to a new address
                       * @param newTotalWeight total weight of `to` after moving to a new address
                       * @param previousYield pending yield of `from` before moving to a new address
                       * @param newYield pending yield of `to` after moving to a new address
                       * @param previousRevDis pending revenue distribution of `from` before moving to a new address
                       * @param newRevDis pending revenue distribution of `to` after moving to a new address
                       */
                      event LogMoveFundsFromWallet(
                          address indexed from,
                          address indexed to,
                          uint248 previousTotalWeight,
                          uint248 newTotalWeight,
                          uint128 previousYield,
                          uint128 newYield,
                          uint128 previousRevDis,
                          uint128 newRevDis
                      );
                      /**
                       * @dev Fired in `receiveVaultRewards()`.
                       *
                       * @param by an address that sent the rewards, always a vault
                       * @param value amount of tokens received
                       */
                      event LogReceiveVaultRewards(address indexed by, uint256 value);
                      /**
                       * @dev Used in child contracts to initialize the pool.
                       *
                       * @param ilv_ ILV ERC20 Token address
                       * @param silv_ sILV ERC20 Token address
                       * @param _poolToken token the pool operates on, for example ILV or ILV/ETH pair
                       * @param _corePoolV1 v1 core pool address
                       * @param factory_ PoolFactory contract address
                       * @param _initTime initial timestamp used to calculate the rewards
                       *      note: _initTime is set to the future effectively meaning _sync() calls will do nothing
                       *           before _initTime
                       * @param _weight number representing the pool's weight, which in _sync calls
                       *        is used by checking the total pools weight in the PoolFactory contract
                       */
                      function __CorePool_init(
                          address ilv_,
                          address silv_,
                          address _poolToken,
                          address _corePoolV1,
                          address factory_,
                          uint64 _initTime,
                          uint32 _weight
                      ) internal initializer {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is
                          // `bytes4(keccak256("__CorePool_init(address,address,address,address,address,uint64,uint32)"))`
                          bytes4 fnSelector = 0x1512be06;
                          // verify the inputs
                          fnSelector.verifyNonZeroInput(uint160(_poolToken), 2);
                          fnSelector.verifyNonZeroInput(uint160(_corePoolV1), 3);
                          fnSelector.verifyNonZeroInput(_initTime, 5);
                          fnSelector.verifyNonZeroInput(_weight, 6);
                          __FactoryControlled_init(factory_);
                          __ReentrancyGuard_init();
                          __Pausable_init();
                          // save the inputs into internal state variables
                          _ilv = ilv_;
                          _silv = silv_;
                          poolToken = _poolToken;
                          corePoolV1 = _corePoolV1;
                          weight = _weight;
                          // init the dependent internal state variables
                          lastYieldDistribution = _initTime;
                      }
                      /**
                       * @notice Calculates current yield rewards value available for address specified.
                       *
                       * @dev See `_pendingRewards()` for further details.
                       *
                       * @dev External `pendingRewards()` returns pendingYield and pendingRevDis
                       *         accumulated with already stored user.pendingYield and user.pendingRevDis.
                       *
                       * @param _staker an address to calculate yield rewards value for
                       */
                      function pendingRewards(address _staker)
                          external
                          view
                          virtual
                          returns (uint256 pendingYield, uint256 pendingRevDis)
                      {
                          this.pendingRewards.selector.verifyNonZeroInput(uint160(_staker), 0);
                          // `newYieldRewardsPerWeight` will be the stored or recalculated value for `yieldRewardsPerWeight`
                          uint256 newYieldRewardsPerWeight;
                          // gas savings
                          uint256 _lastYieldDistribution = lastYieldDistribution;
                          // based on the rewards per weight value, calculate pending rewards;
                          User storage user = users[_staker];
                          // initializes both variables from one storage slot
                          (uint256 v1StakesLength, uint256 userWeight) = (uint256(user.v1IdsLength), uint256(user.totalWeight));
                          // total user v1 weight to be used
                          uint256 totalV1Weight;
                          if (v1StakesLength > 0) {
                              // loops through v1StakesIds and adds v1 weight
                              for (uint256 i = 0; i < v1StakesLength; i++) {
                                  uint256 stakeId = user.v1StakesIds[i];
                                  (, uint256 _weight, , , ) = ICorePoolV1(corePoolV1).getDeposit(_staker, stakeId);
                                  uint256 storedWeight = v1StakesWeights[_staker][stakeId];
                                  totalV1Weight += _weight <= storedWeight ? _weight : storedWeight;
                              }
                          }
                          // if smart contract state was not updated recently, `yieldRewardsPerWeight` value
                          // is outdated and we need to recalculate it in order to calculate pending rewards correctly
                          if (_now256() > _lastYieldDistribution && globalWeight != 0) {
                              uint256 endTime = _factory.endTime();
                              uint256 multiplier = _now256() > endTime
                                  ? endTime - _lastYieldDistribution
                                  : _now256() - _lastYieldDistribution;
                              uint256 ilvRewards = (multiplier * weight * _factory.ilvPerSecond()) / _factory.totalWeight();
                              // recalculated value for `yieldRewardsPerWeight`
                              newYieldRewardsPerWeight =
                                  ilvRewards.getRewardPerWeight((globalWeight + v1GlobalWeight)) +
                                  yieldRewardsPerWeight;
                          } else {
                              // if smart contract state is up to date, we don't recalculate
                              newYieldRewardsPerWeight = yieldRewardsPerWeight;
                          }
                          pendingYield =
                              (userWeight + totalV1Weight).earned(newYieldRewardsPerWeight, user.yieldRewardsPerWeightPaid) +
                              user.pendingYield;
                          pendingRevDis =
                              (userWeight + totalV1Weight).earned(vaultRewardsPerWeight, user.vaultRewardsPerWeightPaid) +
                              user.pendingRevDis;
                      }
                      /**
                       * @notice Returns total staked token balance for the given address.
                       * @dev Loops through stakes and returns total balance.
                       * @notice Expected to be called externally through `eth_call`. Gas shouldn't
                       *         be an issue here.
                       *
                       * @param _user an address to query balance for
                       * @return balance total staked token balance
                       */
                      function balanceOf(address _user) external view virtual returns (uint256 balance) {
                          // gets storage pointer to _user
                          User storage user = users[_user];
                          // loops over each user stake and adds to the total balance.
                          for (uint256 i = 0; i < user.stakes.length; i++) {
                              balance += user.stakes[i].value;
                          }
                      }
                      /**
                       * @dev Returns the sum of poolTokenReserve with the deposit reserves in v1.
                       * @dev In ILV Pool contract the eDAO stores the v1 reserve value, and
                       *      in the SLP pool we're able to query it from the v1 lp pool contract.
                       */
                      function getTotalReserves() external view virtual returns (uint256 totalReserves);
                      /**
                       * @notice Returns information on the given stake for the given address.
                       *
                       * @dev See getStakesLength.
                       *
                       * @param _user an address to query stake for
                       * @param _stakeId zero-indexed stake ID for the address specified
                       * @return stake info as Stake structure
                       */
                      function getStake(address _user, uint256 _stakeId) external view virtual returns (Stake.Data memory) {
                          // read stake at specified index and return
                          return users[_user].stakes[_stakeId];
                      }
                      /**
                       * @notice Returns a v1 stake id in the `user.v1StakesIds` array.
                       *
                       * @dev Get v1 stake id position through `getV1StakePosition()`.
                       *
                       * @param _user an address to query stake for
                       * @param _position position index in the array
                       * @return stakeId value
                       */
                      function getV1StakeId(address _user, uint256 _position) external view virtual returns (uint256) {
                          // returns the v1 stake id indicated at _position value
                          return users[_user].v1StakesIds[_position];
                      }
                      /**
                       * @notice Returns a v1 stake position in the `user.v1StakesIds` array.
                       *
                       * @dev Helper function to call `getV1StakeId()`.
                       * @dev Reverts if stakeId isn't found.
                       *
                       * @param _user an address to query stake for
                       * @param _desiredId desired stakeId position in the array to find
                       * @return position stake info as Stake structure
                       */
                      function getV1StakePosition(address _user, uint256 _desiredId) external view virtual returns (uint256 position) {
                          // gets storage pointer to user
                          User storage user = users[_user];
                          // loops over each v1 stake id and checks if it's the one
                          // that the caller is looking for
                          for (uint256 i = 0; i < user.v1IdsLength; i++) {
                              if (user.v1StakesIds[i] == _desiredId) {
                                  // if it's the desired stake id, return the array index (i.e position)
                                  return i;
                              }
                          }
                          revert();
                      }
                      /**
                       * @notice Returns number of stakes for the given address. Allows iteration over stakes.
                       *
                       * @dev See `getStake()`.
                       *
                       * @param _user an address to query stake length for
                       * @return number of stakes for the given address
                       */
                      function getStakesLength(address _user) external view virtual returns (uint256) {
                          // read stakes array length and return
                          return users[_user].stakes.length;
                      }
                      /**
                       * @dev Set paused/unpaused state in the pool contract.
                       *
                       * @param _shouldPause whether the contract should be paused/unpausd
                       */
                      function pause(bool _shouldPause) external {
                          // checks if caller is authorized to pause
                          _requireIsFactoryController();
                          // checks bool input and pause/unpause the contract depending on
                          // msg.sender's request
                          if (_shouldPause) {
                              _pause();
                          } else {
                              _unpause();
                          }
                      }
                      /**
                       * @notice Stakes specified value of tokens for the specified value of time,
                       *      and pays pending yield rewards if any.
                       *
                       * @dev Requires value to stake and lock duration to be greater than zero.
                       *
                       * @param _value value of tokens to stake
                       * @param _lockDuration stake duration as unix timestamp
                       */
                      function stake(uint256 _value, uint64 _lockDuration) external virtual nonReentrant {
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // we're using selector to simplify input and state validation
                          bytes4 fnSelector = this.stake.selector;
                          // validate the inputs
                          fnSelector.verifyNonZeroInput(_value, 1);
                          fnSelector.verifyInput(_lockDuration >= Stake.MIN_STAKE_PERIOD && _lockDuration <= Stake.MAX_STAKE_PERIOD, 2);
                          // get a link to user data struct, we will write to it later
                          User storage user = users[msg.sender];
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(msg.sender);
                          // update user state
                          _updateReward(msg.sender, v1WeightToAdd);
                          // calculates until when a stake is going to be locked
                          uint64 lockUntil = (_now256()).toUint64() + _lockDuration;
                          // stake weight formula rewards for locking
                          uint256 stakeWeight = (((lockUntil - _now256()) * Stake.WEIGHT_MULTIPLIER) /
                              Stake.MAX_STAKE_PERIOD +
                              Stake.BASE_WEIGHT) * _value;
                          // makes sure stakeWeight is valid
                          assert(stakeWeight > 0);
                          // create and save the stake (append it to stakes array)
                          Stake.Data memory userStake = Stake.Data({
                              value: (_value).toUint120(),
                              lockedFrom: (_now256()).toUint64(),
                              lockedUntil: lockUntil,
                              isYield: false
                          });
                          // pushes new stake to `stakes` array
                          user.stakes.push(userStake);
                          // update user weight
                          user.totalWeight += (stakeWeight).toUint248();
                          // update global weight value and global pool token count
                          globalWeight += stakeWeight;
                          poolTokenReserve += _value;
                          // transfer `_value`
                          IERC20Upgradeable(poolToken).safeTransferFrom(address(msg.sender), address(this), _value);
                          // emit an event
                          emit LogStake(msg.sender, msg.sender, (user.stakes.length - 1), _value, lockUntil);
                      }
                      /**
                       * @dev Moves msg.sender stake data to a new address.
                       * @dev V1 stakes are never migrated to the new address. We process all rewards,
                       *      clean the previous user (msg.sender), add the previous user data to
                       *      the desired address and update subYieldRewards/subVaultRewards values
                       *      in order to make sure both addresses will have rewards cleaned.
                       *
                       * @param _to new user address, needs to be a fresh address with no stakes
                       */
                      function moveFundsFromWallet(address _to) public virtual {
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // gets storage pointer to msg.sender user struct
                          User storage previousUser = users[msg.sender];
                          // gets storage pointer to desired address user struct
                          User storage newUser = users[_to];
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(msg.sender);
                          // We process update global and user's rewards
                          // before moving the user funds to a new wallet.
                          // This way we can ensure that all v1 ids weight have been used before the v2
                          // stakes to a new address.
                          _updateReward(msg.sender, v1WeightToAdd);
                          // we're using selector to simplify input and state validation
                          bytes4 fnSelector = this.moveFundsFromWallet.selector;
                          // validate input is set
                          fnSelector.verifyNonZeroInput(uint160(_to), 0);
                          // verify new user records are empty
                          fnSelector.verifyState(
                              newUser.totalWeight == 0 &&
                                  newUser.v1IdsLength == 0 &&
                                  newUser.stakes.length == 0 &&
                                  newUser.yieldRewardsPerWeightPaid == 0 &&
                                  newUser.vaultRewardsPerWeightPaid == 0,
                              0
                          );
                          // saves previous user total weight
                          uint248 previousTotalWeight = previousUser.totalWeight;
                          // saves previous user pending yield
                          uint128 previousYield = previousUser.pendingYield;
                          // saves previous user pending rev dis
                          uint128 previousRevDis = previousUser.pendingRevDis;
                          // It's expected to have all previous user values
                          // migrated to the new user address (_to).
                          // We recalculate yield and vault rewards values
                          // to make sure new user pending yield and pending rev dis to be stored
                          // at newUser.pendingYield and newUser.pendingRevDis is 0, since we just processed
                          // all pending rewards calling _updateReward.
                          newUser.totalWeight = previousTotalWeight;
                          newUser.pendingYield = previousYield;
                          newUser.pendingRevDis = previousRevDis;
                          newUser.yieldRewardsPerWeightPaid = yieldRewardsPerWeight;
                          newUser.vaultRewardsPerWeightPaid = vaultRewardsPerWeight;
                          newUser.stakes = previousUser.stakes;
                          delete previousUser.totalWeight;
                          delete previousUser.pendingYield;
                          delete previousUser.pendingRevDis;
                          delete previousUser.stakes;
                          // emits an event
                          emit LogMoveFundsFromWallet(
                              msg.sender,
                              _to,
                              previousTotalWeight,
                              newUser.totalWeight,
                              previousYield,
                              newUser.pendingYield,
                              previousRevDis,
                              newUser.pendingRevDis
                          );
                      }
                      /**
                       * @notice Service function to synchronize pool state with current time.
                       *
                       * @dev Can be executed by anyone at any time, but has an effect only when
                       *      at least one second passes between synchronizations.
                       * @dev Executed internally when staking, unstaking, processing rewards in order
                       *      for calculations to be correct and to reflect state progress of the contract.
                       * @dev When timing conditions are not met (executed too frequently, or after factory
                       *      end time), function doesn't throw and exits silently.
                       */
                      function sync() external virtual {
                          _requireNotPaused();
                          // calls internal function
                          _sync();
                      }
                      /**
                       * @dev Calls internal `_claimYieldRewards()` passing `msg.sender` as `_staker`.
                       *
                       * @notice Pool state is updated before calling the internal function.
                       */
                      function claimYieldRewards(bool _useSILV) external virtual {
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // calls internal function
                          _claimYieldRewards(msg.sender, _useSILV);
                      }
                      /**
                       * @dev Calls internal `_claimVaultRewards()` passing `msg.sender` as `_staker`.
                       *
                       * @notice Pool state is updated before calling the internal function.
                       */
                      function claimVaultRewards() external virtual {
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // calls internal function
                          _claimVaultRewards(msg.sender);
                      }
                      /**
                       * @dev Claims both revenue distribution and yield rewards in one call.
                       *
                       */
                      function claimAllRewards(bool _useSILV) external virtual {
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // calls internal yield and vault rewards functions
                          _claimVaultRewards(msg.sender);
                          _claimYieldRewards(msg.sender, _useSILV);
                      }
                      /**
                       * @dev Executed by the vault to transfer vault rewards ILV from the vault
                       *      into the pool.
                       *
                       * @dev This function is executed only for ILV core pools.
                       *
                       * @param _value amount of ILV rewards to transfer into the pool
                       */
                      function receiveVaultRewards(uint256 _value) external virtual {
                          // always sync the pool state vars before moving forward
                          _sync();
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // checks if msg.sender is the vault contract
                          _requireIsVault();
                          // we're using selector to simplify input and state validation
                          bytes4 fnSelector = this.receiveVaultRewards.selector;
                          // return silently if there is no reward to receive
                          if (_value == 0) {
                              return;
                          }
                          // verify weight is not zero
                          fnSelector.verifyState(globalWeight > 0 || v1GlobalWeight > 0, 0);
                          // we update vaultRewardsPerWeight value using v1 and v2 global weight,
                          // expecting to distribute revenue distribution correctly to all users
                          // coming from v1 and new v2 users.
                          vaultRewardsPerWeight += _value.getRewardPerWeight(globalWeight + v1GlobalWeight);
                          // transfers ILV from the Vault contract to the pool
                          IERC20Upgradeable(_ilv).safeTransferFrom(msg.sender, address(this), _value);
                          // emits an event
                          emit LogReceiveVaultRewards(msg.sender, _value);
                      }
                      /**
                       * @dev Updates value that keeps track of v1 global locked tokens weight.
                       *
                       * @param _v1GlobalWeight new value to be stored
                       */
                      function setV1GlobalWeight(uint256 _v1GlobalWeight) external virtual {
                          // only factory controller can update the _v1GlobalWeight
                          _requireIsFactoryController();
                          // update v1GlobalWeight state variable
                          v1GlobalWeight = _v1GlobalWeight;
                      }
                      /**
                       * @dev Executed by the factory to modify pool weight; the factory is expected
                       *      to keep track of the total pools weight when updating.
                       *
                       * @dev Set weight to zero to disable the pool.
                       *
                       * @param _weight new weight to set for the pool
                       */
                      function setWeight(uint32 _weight) external virtual {
                          // update pool state using current weight value
                          _sync();
                          // verify function is executed by the factory
                          this.setWeight.selector.verifyAccess(msg.sender == address(_factory));
                          // set the new weight value
                          weight = _weight;
                      }
                      /**
                       * @dev Unstakes a stake that has been previously locked, and is now in an unlocked
                       *      state. If the stake has the isYield flag set to true, then the contract
                       *      requests ILV to be minted by the PoolFactory. Otherwise it transfers ILV or LP
                       *      from the contract balance.
                       *
                       * @param _stakeId stake ID to unstake from, zero-indexed
                       * @param _value value of tokens to unstake
                       */
                      function unstake(uint256 _stakeId, uint256 _value) external virtual {
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // we're using selector to simplify input and state validation
                          bytes4 fnSelector = this.unstake.selector;
                          // verify a value is set
                          fnSelector.verifyNonZeroInput(_value, 0);
                          // get a link to user data struct, we will write to it later
                          User storage user = users[msg.sender];
                          // get a link to the corresponding stake, we may write to it later
                          Stake.Data storage userStake = user.stakes[_stakeId];
                          // checks if stake is unlocked already
                          fnSelector.verifyState(_now256() > userStake.lockedUntil, 0);
                          // stake structure may get deleted, so we save isYield flag to be able to use it
                          // we also save stakeValue for gasSavings
                          (uint120 stakeValue, bool isYield) = (userStake.value, userStake.isYield);
                          // verify available balance
                          fnSelector.verifyInput(stakeValue >= _value, 1);
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(msg.sender);
                          // and process current pending rewards if any
                          _updateReward(msg.sender, v1WeightToAdd);
                          // store stake weight
                          uint256 previousWeight = userStake.weight();
                          // value used to save new weight after updates in storage
                          uint256 newWeight;
                          // update the stake, or delete it if its depleted
                          if (stakeValue - _value == 0) {
                              // deletes stake struct, no need to save new weight because it stays 0
                              delete user.stakes[_stakeId];
                          } else {
                              userStake.value -= (_value).toUint120();
                              // saves new weight to memory
                              newWeight = userStake.weight();
                          }
                          // update user record
                          user.totalWeight = uint248(user.totalWeight - previousWeight + newWeight);
                          // update global weight variable
                          globalWeight = globalWeight - previousWeight + newWeight;
                          // update global pool token count
                          poolTokenReserve -= _value;
                          // if the stake was created by the pool itself as a yield reward
                          if (isYield) {
                              // mint the yield via the factory
                              _factory.mintYieldTo(msg.sender, _value, false);
                          } else {
                              // otherwise just return tokens back to holder
                              IERC20Upgradeable(poolToken).safeTransfer(msg.sender, _value);
                          }
                          // emits an event
                          emit LogUnstakeLocked(msg.sender, _stakeId, _value, isYield);
                      }
                      /**
                       * @dev Executes unstake on multiple stakeIds. See `unstakeLocked()`.
                       * @dev Optimizes gas by requiring all unstakes to be made either in yield stakes
                       *      or in non yield stakes. That way we can transfer or mint tokens in one call.
                       *
                       * @notice User is required to either mint ILV or unstake pool tokens in the function call.
                       *         There's no way to do both operations in one call.
                       *
                       * @param _stakes array of stakeIds and values to be unstaked in each stake from
                       *                the msg.sender
                       * @param _unstakingYield whether all stakeIds have isYield flag set to true or false,
                       *                        i.e if we're minting ILV or transferring pool tokens
                       */
                      function unstakeMultiple(UnstakeParameter[] calldata _stakes, bool _unstakingYield) external virtual {
                          // checks if the contract is in a paused state
                          _requireNotPaused();
                          // we're using selector to simplify input and state validation
                          bytes4 fnSelector = this.unstakeMultiple.selector;
                          // verifies if user has passed any value to be unstaked
                          fnSelector.verifyNonZeroInput(_stakes.length, 0);
                          // gets storage pointer to the user
                          User storage user = users[msg.sender];
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(msg.sender);
                          _updateReward(msg.sender, v1WeightToAdd);
                          // initialize variables that expect to receive the total
                          // weight to be removed from the user and the value to be
                          // unstaked from the pool.
                          uint256 weightToRemove;
                          uint256 valueToUnstake;
                          for (uint256 i = 0; i < _stakes.length; i++) {
                              // destructure calldata parameters
                              (uint256 _stakeId, uint256 _value) = (_stakes[i].stakeId, _stakes[i].value);
                              Stake.Data storage userStake = user.stakes[_stakeId];
                              // checks if stake is unlocked already
                              fnSelector.verifyState(_now256() > userStake.lockedUntil, i * 3);
                              // checks if unstaking value is valid
                              fnSelector.verifyNonZeroInput(_value, 1);
                              // stake structure may get deleted, so we save isYield flag to be able to use it
                              // we also save stakeValue for gas savings
                              (uint120 stakeValue, bool isYield) = (userStake.value, userStake.isYield);
                              // verifies if the selected stake is yield (i.e ILV to be minted)
                              // or not, the function needs to either mint yield or transfer tokens
                              // and can't do both operations at the same time.
                              fnSelector.verifyState(isYield == _unstakingYield, i * 3 + 1);
                              // checks if there's enough tokens to unstake
                              fnSelector.verifyState(stakeValue >= _value, i * 3 + 2);
                              // store stake weight
                              uint256 previousWeight = userStake.weight();
                              // value used to save new weight after updates in storage
                              uint256 newWeight;
                              // update the stake, or delete it if its depleted
                              if (stakeValue - _value == 0) {
                                  // deletes stake struct, no need to save new weight because it stays 0
                                  delete user.stakes[_stakeId];
                              } else {
                                  // removes _value from the stake with safe cast
                                  userStake.value -= (_value).toUint120();
                                  // saves new weight to memory
                                  newWeight = userStake.weight();
                              }
                              // updates the values initialized earlier with the amounts that
                              // need to be subtracted (weight) and transferred (value to unstake)
                              weightToRemove += previousWeight - newWeight;
                              valueToUnstake += _value;
                          }
                          // subtracts weight
                          user.totalWeight -= (weightToRemove).toUint248();
                          // update global variable
                          globalWeight -= weightToRemove;
                          // update pool token count
                          poolTokenReserve -= valueToUnstake;
                          // if the stake was created by the pool itself as a yield reward
                          if (_unstakingYield) {
                              // mint the yield via the factory
                              _factory.mintYieldTo(msg.sender, valueToUnstake, false);
                          } else {
                              // otherwise just return tokens back to holder
                              IERC20Upgradeable(poolToken).safeTransfer(msg.sender, valueToUnstake);
                          }
                          // emits an event
                          emit LogUnstakeLockedMultiple(msg.sender, valueToUnstake, _unstakingYield);
                      }
                      /**
                       * @dev Used internally, mostly by children implementations, see `sync()`.
                       *
                       * @dev Updates smart contract state (`yieldRewardsPerWeight`, `lastYieldDistribution`),
                       *      updates factory state via `updateILVPerSecond`
                       */
                      function _sync() internal virtual {
                          // gas savings
                          IFactory factory_ = _factory;
                          // update ILV per second value in factory if required
                          if (factory_.shouldUpdateRatio()) {
                              factory_.updateILVPerSecond();
                          }
                          // check bound conditions and if these are not met -
                          // exit silently, without emitting an event
                          uint256 endTime = factory_.endTime();
                          if (lastYieldDistribution >= endTime) {
                              return;
                          }
                          if (_now256() <= lastYieldDistribution) {
                              return;
                          }
                          // if locking weight is zero - update only `lastYieldDistribution` and exit
                          if (globalWeight == 0 && v1GlobalWeight == 0) {
                              lastYieldDistribution = (_now256()).toUint64();
                              return;
                          }
                          // to calculate the reward we need to know how many seconds passed, and reward per second
                          uint256 currentTimestamp = _now256() > endTime ? endTime : _now256();
                          uint256 secondsPassed = currentTimestamp - lastYieldDistribution;
                          uint256 ilvPerSecond = factory_.ilvPerSecond();
                          // calculate the reward
                          uint256 ilvReward = (secondsPassed * ilvPerSecond * weight) / factory_.totalWeight();
                          // update rewards per weight and `lastYieldDistribution`
                          yieldRewardsPerWeight += ilvReward.getRewardPerWeight((globalWeight + v1GlobalWeight));
                          lastYieldDistribution = (currentTimestamp).toUint64();
                          // emit an event
                          emit LogSync(msg.sender, yieldRewardsPerWeight, lastYieldDistribution);
                      }
                      /**
                       * @dev claims all pendingYield from _staker using ILV or sILV.
                       *
                       * @notice sILV is minted straight away to _staker wallet, ILV is created as
                       *         a new stake and locked for Stake.MAX_STAKE_PERIOD.
                       *
                       * @param _staker user address
                       * @param _useSILV whether the user wants to claim ILV or sILV
                       */
                      function _claimYieldRewards(address _staker, bool _useSILV) internal virtual {
                          // get link to a user data structure, we will write into it later
                          User storage user = users[_staker];
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(_staker);
                          // update user state
                          _updateReward(_staker, v1WeightToAdd);
                          // check pending yield rewards to claim and save to memory
                          uint256 pendingYieldToClaim = uint256(user.pendingYield);
                          // if pending yield is zero - just return silently
                          if (pendingYieldToClaim == 0) return;
                          // clears user pending yield
                          user.pendingYield = 0;
                          // if sILV is requested
                          if (_useSILV) {
                              // - mint sILV
                              _factory.mintYieldTo(_staker, pendingYieldToClaim, true);
                          } else if (poolToken == _ilv) {
                              // calculate pending yield weight,
                              // 2e6 is the bonus weight when staking for 1 year
                              uint256 stakeWeight = pendingYieldToClaim * Stake.YIELD_STAKE_WEIGHT_MULTIPLIER;
                              // if the pool is ILV Pool - create new ILV stake
                              // and save it - push it into stakes array
                              Stake.Data memory newStake = Stake.Data({
                                  value: (pendingYieldToClaim).toUint120(),
                                  lockedFrom: (_now256()).toUint64(),
                                  lockedUntil: (_now256() + Stake.MAX_STAKE_PERIOD).toUint64(), // staking yield for 1 year
                                  isYield: true
                              });
                              // add memory stake to storage
                              user.stakes.push(newStake);
                              // updates total user weight with the newly created stake's weight
                              user.totalWeight += (stakeWeight).toUint248();
                              // update global variable
                              globalWeight += stakeWeight;
                              // update reserve count
                              poolTokenReserve += pendingYieldToClaim;
                          } else {
                              // for other pools - stake as pool
                              address ilvPool = _factory.getPoolAddress(_ilv);
                              IILVPool(ilvPool).stakeAsPool(_staker, pendingYieldToClaim);
                          }
                          // emits an event
                          emit LogClaimYieldRewards(msg.sender, _staker, _useSILV, pendingYieldToClaim);
                      }
                      /**
                       * @dev Claims all pendingRevDis from _staker using ILV.
                       * @dev ILV is sent straight away to _staker address.
                       *
                       * @param _staker user address
                       */
                      function _claimVaultRewards(address _staker) internal virtual {
                          // get link to a user data structure, we will write into it later
                          User storage user = users[_staker];
                          // uses v1 weight values for rewards calculations
                          uint256 v1WeightToAdd = _useV1Weight(_staker);
                          // update user state
                          _updateReward(_staker, v1WeightToAdd);
                          // check pending yield rewards to claim and save to memory
                          uint256 pendingRevDis = uint256(user.pendingRevDis);
                          // if pending yield is zero - just return silently
                          if (pendingRevDis == 0) return;
                          // clears user pending revenue distribution
                          user.pendingRevDis = 0;
                          IERC20Upgradeable(_ilv).safeTransfer(_staker, pendingRevDis);
                          // emits an event
                          emit LogClaimVaultRewards(msg.sender, _staker, pendingRevDis);
                      }
                      /**
                       * @dev Calls CorePoolV1 contract, gets v1 stake ids weight and returns.
                       * @dev Used by `_pendingRewards()` to calculate yield and revenue distribution
                       *      rewards taking v1 weights into account.
                       *
                       * @notice If v1 weights have changed since last call, we use latest v1 weight for
                       *         yield and revenue distribution rewards calculations, and recalculate
                       *         user sub rewards values in order to have correct rewards estimations.
                       *
                       * @param _staker user address passed
                       *
                       * @return totalV1Weight uint256 value of v1StakesIds weights
                       */
                      function _useV1Weight(address _staker) internal virtual returns (uint256 totalV1Weight) {
                          // gets user storage pointer
                          User storage user = users[_staker];
                          // gas savings
                          uint256 v1StakesLength = user.v1IdsLength;
                          // checks if user has any migrated stake from v1
                          if (v1StakesLength > 0) {
                              // loops through v1StakesIds and adds v1 weight
                              for (uint256 i = 0; i < v1StakesLength; i++) {
                                  // saves v1 stake id to memory
                                  uint256 stakeId = user.v1StakesIds[i];
                                  (, uint256 _weight, , , ) = ICorePoolV1(corePoolV1).getDeposit(_staker, stakeId);
                                  // gets weight stored initially in the v1StakesWeights mapping
                                  // through V2Migrator contract
                                  uint256 storedWeight = v1StakesWeights[_staker][stakeId];
                                  // only stores the current v1 weight that is going to be used for calculations
                                  // if current v1 weight is equal to or less than the stored weight.
                                  // This way we make sure that v1 weight never increases for any reason
                                  // (e.g increasing a v1 stake lock through v1 contract) and messes up calculations.
                                  totalV1Weight += _weight <= storedWeight ? _weight : storedWeight;
                                  // if _weight has updated in v1 to a lower value, we also update
                                  // stored weight in v2 for next calculations
                                  if (storedWeight > _weight) {
                                      // if deposit has been completely unstaked in v1, set stake id weight to 1
                                      // so we can keep track that it has been already migrated.
                                      // otherwise just update value to _weight
                                      v1StakesWeights[_staker][stakeId] = _weight == 0 ? 1 : _weight;
                                  }
                              }
                          }
                      }
                      /**
                       * @dev Checks if pool is paused.
                       * @dev We use this internal function instead of the modifier coming from
                       *      Pausable contract in order to decrease contract's bytecode size.
                       */
                      function _requireNotPaused() internal view virtual {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("_requireNotPaused()"))`
                          bytes4 fnSelector = 0xabb87a6f;
                          // checks paused variable value from Pausable Open Zeppelin
                          fnSelector.verifyState(!paused(), 0);
                      }
                      /**
                       * @dev Must be called every time user.totalWeight is changed.
                       * @dev Syncs the global pool state, processes the user pending rewards (if any),
                       *      and updates check points values stored in the user struct.
                       * @dev If user is coming from v1 pool, it expects to receive this v1 user weight
                       *      to include in rewards calculations.
                       *
                       * @param _staker user address
                       * @param _v1WeightToAdd v1 weight to be added to calculations
                       */
                      function _updateReward(address _staker, uint256 _v1WeightToAdd) internal virtual {
                          // update pool state
                          _sync();
                          // gets storage reference to the user
                          User storage user = users[_staker];
                          // gas savings
                          uint256 userTotalWeight = uint256(user.totalWeight) + _v1WeightToAdd;
                          // calculates pending yield to be added
                          uint256 pendingYield = userTotalWeight.earned(yieldRewardsPerWeight, user.yieldRewardsPerWeightPaid);
                          // calculates pending reenue distribution to be added
                          uint256 pendingRevDis = userTotalWeight.earned(vaultRewardsPerWeight, user.vaultRewardsPerWeightPaid);
                          // increases stored user.pendingYield with value returned
                          user.pendingYield += pendingYield.toUint128();
                          // increases stored user.pendingRevDis with value returned
                          user.pendingRevDis += pendingRevDis.toUint128();
                          // updates user checkpoint values for future calculations
                          user.yieldRewardsPerWeightPaid = yieldRewardsPerWeight;
                          user.vaultRewardsPerWeightPaid = vaultRewardsPerWeight;
                          // emit an event
                          emit LogUpdateRewards(msg.sender, _staker, pendingYield, pendingRevDis);
                      }
                      /**
                       * @dev See UUPSUpgradeable `_authorizeUpgrade()`.
                       * @dev Just checks if `msg.sender` == `factory.owner()` i.e eDAO multisig address.
                       * @dev eDAO multisig is responsible by handling upgrades and executing other
                       *      admin actions approved by the Council.
                       */
                      function _authorizeUpgrade(address) internal view virtual override {
                          // checks caller is factory.owner()
                          _requireIsFactoryController();
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[39] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  /**
                   * @title Errors Library.
                   *
                   * @notice Introduces some very common input and state validation for smart contracts,
                   *      such as non-zero input validation, general boolean expression validation, access validation.
                   *
                   * @notice Throws pre-defined errors instead of string error messages to reduce gas costs.
                   *
                   * @notice Since the library handles only very common errors, concrete smart contracts may
                   *      also introduce their own error types and handling.
                   *
                   * @author Basil Gorin
                   */
                  library ErrorHandler {
                      /**
                       * @notice Thrown on zero input at index specified in a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param paramIndex function parameter index which caused an error thrown
                       */
                      error ZeroInput(bytes4 fnSelector, uint8 paramIndex);
                      /**
                       * @notice Thrown on invalid input at index specified in a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param paramIndex function parameter index which caused an error thrown
                       */
                      error InvalidInput(bytes4 fnSelector, uint8 paramIndex);
                      /**
                       * @notice Thrown on invalid state in a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param errorCode unique error code determining the exact place in code where error was thrown
                       */
                      error InvalidState(bytes4 fnSelector, uint256 errorCode);
                      /**
                       * @notice Thrown on invalid access to a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param addr an address which access was denied, usually transaction sender
                       */
                      error AccessDenied(bytes4 fnSelector, address addr);
                      /**
                       * @notice Verifies an input is set (non-zero).
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param value a value to check if it's set (non-zero)
                       * @param paramIndex function parameter index which is verified
                       */
                      function verifyNonZeroInput(
                          bytes4 fnSelector,
                          uint256 value,
                          uint8 paramIndex
                      ) internal pure {
                          if (value == 0) {
                              revert ZeroInput(fnSelector, paramIndex);
                          }
                      }
                      /**
                       * @notice Verifies an input is correct.
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param expr a boolean expression used to verify the input
                       * @param paramIndex function parameter index which is verified
                       */
                      function verifyInput(
                          bytes4 fnSelector,
                          bool expr,
                          uint8 paramIndex
                      ) internal pure {
                          if (!expr) {
                              revert InvalidInput(fnSelector, paramIndex);
                          }
                      }
                      /**
                       * @notice Verifies smart contract state is correct.
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param expr a boolean expression used to verify the contract state
                       * @param errorCode unique error code determining the exact place in code which is verified
                       */
                      function verifyState(
                          bytes4 fnSelector,
                          bool expr,
                          uint256 errorCode
                      ) internal pure {
                          if (!expr) {
                              revert InvalidState(fnSelector, errorCode);
                          }
                      }
                      /**
                       * @notice Verifies an access to the function.
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param expr a boolean expression used to verify the access
                       */
                      function verifyAccess(bytes4 fnSelector, bool expr) internal view {
                          if (!expr) {
                              revert AccessDenied(fnSelector, msg.sender);
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  /**
                   * @dev Stake library used by ILV pool and Sushi LP Pool.
                   *
                   * @dev Responsible to manage weight calculation and store important constants
                   *      related to stake period, base weight and multipliers utilized.
                   */
                  library Stake {
                      struct Data {
                          /// @dev token amount staked
                          uint120 value;
                          /// @dev locking period - from
                          uint64 lockedFrom;
                          /// @dev locking period - until
                          uint64 lockedUntil;
                          /// @dev indicates if the stake was created as a yield reward
                          bool isYield;
                      }
                      /**
                       * @dev Stake weight is proportional to stake value and time locked, precisely
                       *      "stake value wei multiplied by (fraction of the year locked plus one)".
                       * @dev To avoid significant precision loss due to multiplication by "fraction of the year" [0, 1],
                       *      weight is stored multiplied by 1e6 constant, as an integer.
                       * @dev Corner case 1: if time locked is zero, weight is stake value multiplied by 1e6 + base weight
                       * @dev Corner case 2: if time locked is two years, division of
                              (lockedUntil - lockedFrom) / MAX_STAKE_PERIOD is 1e6, and
                       *      weight is a stake value multiplied by 2 * 1e6.
                       */
                      uint256 internal constant WEIGHT_MULTIPLIER = 1e6;
                      /**
                       * @dev Minimum weight value, if result of multiplication using WEIGHT_MULTIPLIER
                       *      is 0 (e.g stake flexible), then BASE_WEIGHT is used.
                       */
                      uint256 internal constant BASE_WEIGHT = 1e6;
                      /**
                       * @dev Minimum period that someone can lock a stake for.
                       */
                      uint256 internal constant MIN_STAKE_PERIOD = 30 days;
                      /**
                       * @dev Maximum period that someone can lock a stake for.
                       */
                      uint256 internal constant MAX_STAKE_PERIOD = 365 days;
                      /**
                       * @dev Rewards per weight are stored multiplied by 1e20 as uint.
                       */
                      uint256 internal constant REWARD_PER_WEIGHT_MULTIPLIER = 1e20;
                      /**
                       * @dev When we know beforehand that staking is done for yield instead of
                       *      executing `weight()` function we use the following constant.
                       */
                      uint256 internal constant YIELD_STAKE_WEIGHT_MULTIPLIER = 2 * 1e6;
                      function weight(Data storage _self) internal view returns (uint256) {
                          return
                              uint256(
                                  (((_self.lockedUntil - _self.lockedFrom) * WEIGHT_MULTIPLIER) / MAX_STAKE_PERIOD + BASE_WEIGHT) *
                                      _self.value
                              );
                      }
                      /**
                       * @dev Converts stake weight (not to be mixed with the pool weight) to
                       *      ILV reward value, applying the 10^12 division on weight
                       *
                       * @param _weight stake weight
                       * @param _rewardPerWeight ILV reward per weight
                       * @param _rewardPerWeightPaid last reward per weight value used for user earnings
                       * @return reward value normalized to 10^12
                       */
                      function earned(
                          uint256 _weight,
                          uint256 _rewardPerWeight,
                          uint256 _rewardPerWeightPaid
                      ) internal pure returns (uint256) {
                          // apply the formula and return
                          return (_weight * (_rewardPerWeight - _rewardPerWeightPaid)) / REWARD_PER_WEIGHT_MULTIPLIER;
                      }
                      /**
                       * @dev Converts reward ILV value to stake weight (not to be mixed with the pool weight),
                       *      applying the 10^12 multiplication on the reward.
                       *      - OR -
                       * @dev Converts reward ILV value to reward/weight if stake weight is supplied as second
                       *      function parameter instead of reward/weight.
                       *
                       * @param _reward yield reward
                       * @param _globalWeight total weight in the pool
                       * @return reward per weight value
                       */
                      function getRewardPerWeight(uint256 _reward, uint256 _globalWeight) internal pure returns (uint256) {
                          // apply the reverse formula and return
                          return (_reward * REWARD_PER_WEIGHT_MULTIPLIER) / _globalWeight;
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { ICorePool } from "./ICorePool.sol";
                  interface IFactory {
                      function owner() external view returns (address);
                      function ilvPerSecond() external view returns (uint192);
                      function totalWeight() external view returns (uint32);
                      function secondsPerUpdate() external view returns (uint32);
                      function endTime() external view returns (uint32);
                      function lastRatioUpdate() external view returns (uint32);
                      function pools(address _poolToken) external view returns (ICorePool);
                      function poolExists(address _poolAddress) external view returns (bool);
                      function getPoolAddress(address poolToken) external view returns (address);
                      function getPoolData(address _poolToken)
                          external
                          view
                          returns (
                              address,
                              address,
                              uint32,
                              bool
                          );
                      function shouldUpdateRatio() external view returns (bool);
                      function registerPool(ICorePool pool) external;
                      function updateILVPerSecond() external;
                      function mintYieldTo(
                          address _to,
                          uint256 _value,
                          bool _useSILV
                      ) external;
                      function changePoolWeight(address pool, uint32 weight) external;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Stake } from "../libraries/Stake.sol";
                  interface ICorePool {
                      function users(address _user)
                          external
                          view
                          returns (
                              uint128,
                              uint128,
                              uint128,
                              uint248,
                              uint8,
                              uint256,
                              uint256
                          );
                      function poolToken() external view returns (address);
                      function isFlashPool() external view returns (bool);
                      function weight() external view returns (uint32);
                      function lastYieldDistribution() external view returns (uint64);
                      function yieldRewardsPerWeight() external view returns (uint256);
                      function globalWeight() external view returns (uint256);
                      function pendingRewards(address _user) external view returns (uint256, uint256);
                      function poolTokenReserve() external view returns (uint256);
                      function balanceOf(address _user) external view returns (uint256);
                      function getTotalReserves() external view returns (uint256);
                      function getStake(address _user, uint256 _stakeId) external view returns (Stake.Data memory);
                      function getStakesLength(address _user) external view returns (uint256);
                      function sync() external;
                      function setWeight(uint32 _weight) external;
                      function receiveVaultRewards(uint256 value) external;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  interface ICorePoolV1 {
                      struct V1Stake {
                          // @dev token amount staked
                          uint256 tokenAmount;
                          // @dev stake weight
                          uint256 weight;
                          // @dev locking period - from
                          uint64 lockedFrom;
                          // @dev locking period - until
                          uint64 lockedUntil;
                          // @dev indicates if the stake was created as a yield reward
                          bool isYield;
                      }
                      struct V1User {
                          // @dev Total staked amount
                          uint256 tokenAmount;
                          // @dev Total weight
                          uint256 totalWeight;
                          // @dev Auxiliary variable for yield calculation
                          uint256 subYieldRewards;
                          // @dev Auxiliary variable for vault rewards calculation
                          uint256 subVaultRewards;
                          // @dev An array of holder's deposits
                          V1Stake[] deposits;
                      }
                      function users(address _who)
                          external
                          view
                          returns (
                              uint256,
                              uint256,
                              uint256,
                              uint256
                          );
                      function getDeposit(address _from, uint256 _stakeId)
                          external
                          view
                          returns (
                              uint256,
                              uint256,
                              uint64,
                              uint64,
                              bool
                          );
                      function poolToken() external view returns (address);
                      function usersLockingWeight() external view returns (uint256);
                      function poolTokenReserve() external view returns (uint256);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import { V2Migrator } from "./base/V2Migrator.sol";
                  import { CorePool } from "./base/CorePool.sol";
                  import { ErrorHandler } from "./libraries/ErrorHandler.sol";
                  import { ICorePoolV1 } from "./interfaces/ICorePoolV1.sol";
                  /**
                   * @title The Sushi LP Pool.
                   *
                   * @dev Extends all functionality from V2Migrator contract, there isn't a lot of
                   *      additions compared to ILV pool. Sushi LP pool basically needs to be able
                   *      to be called by ILV pool in batch calls where we claim rewards from multiple
                   *      pools.
                   */
                  contract SushiLPPool is Initializable, V2Migrator {
                      using ErrorHandler for bytes4;
                      /// @dev Calls __V2Migrator_init().
                      function initialize(
                          address ilv_,
                          address silv_,
                          address _poolToken,
                          address _factory,
                          uint64 _initTime,
                          uint32 _weight,
                          address _corePoolV1,
                          uint256 v1StakeMaxPeriod_
                      ) external initializer {
                          __V2Migrator_init(ilv_, silv_, _poolToken, _corePoolV1, _factory, _initTime, _weight, v1StakeMaxPeriod_);
                      }
                      /// @inheritdoc CorePool
                      function getTotalReserves() external view virtual override returns (uint256 totalReserves) {
                          totalReserves = poolTokenReserve + ICorePoolV1(corePoolV1).poolTokenReserve();
                      }
                      /**
                       * @notice This function can be called only by ILV core pool.
                       *
                       * @dev Uses ILV pool as a router by receiving the _staker address and executing
                       *      the internal `_claimYieldRewards()`.
                       * @dev Its usage allows claiming multiple pool contracts in one transaction.
                       *
                       * @param _staker user address
                       * @param _useSILV whether it should claim pendingYield as ILV or sILV
                       */
                      function claimYieldRewardsFromRouter(address _staker, bool _useSILV) external virtual {
                          // checks if contract is paused
                          _requireNotPaused();
                          // checks if caller is the ILV pool
                          _requirePoolIsValid();
                          // calls internal _claimYieldRewards function (in CorePool.sol)
                          _claimYieldRewards(_staker, _useSILV);
                      }
                      /**
                       * @notice This function can be called only by ILV core pool.
                       *
                       * @dev Uses ILV pool as a router by receiving the _staker address and executing
                       *      the internal `_claimVaultRewards()`.
                       * @dev Its usage allows claiming multiple pool contracts in one transaction.
                       *
                       * @param _staker user address
                       */
                      function claimVaultRewardsFromRouter(address _staker) external virtual {
                          // checks if contract is paused
                          _requireNotPaused();
                          // checks if caller is the ILV pool
                          _requirePoolIsValid();
                          // calls internal _claimVaultRewards function (in CorePool.sol)
                          _claimVaultRewards(_staker);
                      }
                      /**
                       * @dev Checks if caller is ILV pool.
                       * @dev We are using an internal function instead of a modifier in order to
                       *      reduce the contract's bytecode size.
                       */
                      function _requirePoolIsValid() internal view virtual {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("_requirePoolIsValid()"))`
                          bytes4 fnSelector = 0x250f303f;
                          // checks if pool is the ILV pool
                          bool poolIsValid = address(_factory.pools(_ilv)) == msg.sender;
                          fnSelector.verifyState(poolIsValid, 0);
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @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
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize, which returns 0 for contracts in
                          // construction, since the code is only stored at the end of the
                          // constructor execution.
                          uint256 size;
                          assembly {
                              size := extcodesize(account)
                          }
                          return size > 0;
                      }
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                          (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
                  pragma solidity ^0.8.0;
                  import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
                  import "./Initializable.sol";
                  /**
                   * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
                   * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
                   *
                   * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
                   * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
                   * `UUPSUpgradeable` with a custom implementation of upgrades.
                   *
                   * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
                   *
                   * _Available since v4.1._
                   */
                  abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable {
                      function __UUPSUpgradeable_init() internal initializer {
                          __ERC1967Upgrade_init_unchained();
                          __UUPSUpgradeable_init_unchained();
                      }
                      function __UUPSUpgradeable_init_unchained() internal initializer {
                      }
                      /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
                      address private immutable __self = address(this);
                      /**
                       * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
                       * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
                       * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
                       * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
                       * fail.
                       */
                      modifier onlyProxy() {
                          require(address(this) != __self, "Function must be called through delegatecall");
                          require(_getImplementation() == __self, "Function must be called through active proxy");
                          _;
                      }
                      /**
                       * @dev Upgrade the implementation of the proxy to `newImplementation`.
                       *
                       * Calls {_authorizeUpgrade}.
                       *
                       * Emits an {Upgraded} event.
                       */
                      function upgradeTo(address newImplementation) external virtual onlyProxy {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, new bytes(0), false);
                      }
                      /**
                       * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
                       * encoded in `data`.
                       *
                       * Calls {_authorizeUpgrade}.
                       *
                       * Emits an {Upgraded} event.
                       */
                      function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, data, true);
                      }
                      /**
                       * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
                       * {upgradeTo} and {upgradeToAndCall}.
                       *
                       * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
                       *
                       * ```solidity
                       * function _authorizeUpgrade(address) internal override onlyOwner {}
                       * ```
                       */
                      function _authorizeUpgrade(address newImplementation) internal virtual;
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  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 initializer {
                          __ReentrancyGuard_init_unchained();
                      }
                      function __ReentrancyGuard_init_unchained() internal initializer {
                          _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 make 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;
                      }
                      uint256[49] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  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 initializer {
                          __Context_init_unchained();
                          __Pausable_init_unchained();
                      }
                      function __Pausable_init_unchained() internal initializer {
                          _paused = false;
                      }
                      /**
                       * @dev Returns true if the contract is paused, and false otherwise.
                       */
                      function paused() public view 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());
                      }
                      uint256[49] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  /// @title Function for getting block timestamp.
                  /// @dev Base contract that is overridden for tests.
                  abstract contract Timestamp {
                      /**
                       * @dev Testing time-dependent functionality is difficult and the best way of
                       *      doing it is to override time in helper test smart contracts.
                       *
                       * @return `block.timestamp` in mainnet, custom values in testnets (if overridden).
                       */
                      function _now256() internal view virtual returns (uint256) {
                          // return current block timestamp
                          return block.timestamp;
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import { FactoryControlled } from "./FactoryControlled.sol";
                  import { ErrorHandler } from "../libraries/ErrorHandler.sol";
                  abstract contract VaultRecipient is Initializable, FactoryControlled {
                      using ErrorHandler for bytes4;
                      /// @dev Link to deployed IlluviumVault instance.
                      address internal _vault;
                      /// @dev Used to calculate vault rewards.
                      /// @dev This value is different from "reward per token" used in locked pool.
                      /// @dev Note: stakes are different in duration and "weight" reflects that,
                      uint256 public vaultRewardsPerWeight;
                      /**
                       * @dev Fired in `setVault()`.
                       *
                       * @param by an address which executed the function, always a factory owner
                       * @param previousVault previous vault contract address
                       * @param newVault new vault address
                       */
                      event LogSetVault(address indexed by, address previousVault, address newVault);
                      /**
                       * @dev Executed only by the factory owner to Set the vault.
                       *
                       * @param vault_ an address of deployed IlluviumVault instance
                       */
                      function setVault(address vault_) external virtual {
                          // we're using selector to simplify input and state validation
                          bytes4 fnSelector = this.setVault.selector;
                          // verify function is executed by the factory owner
                          fnSelector.verifyState(_factory.owner() == msg.sender, 0);
                          // verify input is set
                          fnSelector.verifyInput(vault_ != address(0), 0);
                          // saves current vault to memory
                          address previousVault = vault_;
                          // update vault address
                          _vault = vault_;
                          // emit an event
                          emit LogSetVault(msg.sender, previousVault, _vault);
                      }
                      /// @dev Utility function to check if caller is the Vault contract
                      function _requireIsVault() internal view virtual {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("_requireIsVault()"))`
                          bytes4 fnSelector = 0xeeea774b;
                          // checks if caller is the same stored vault address
                          fnSelector.verifyAccess(msg.sender == _vault);
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[48] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { ICorePool } from "./ICorePool.sol";
                  interface IILVPool is ICorePool {
                      function stakeAsPool(address _staker, uint256 _value) external;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.2;
                  import "../beacon/IBeaconUpgradeable.sol";
                  import "../../utils/AddressUpgradeable.sol";
                  import "../../utils/StorageSlotUpgradeable.sol";
                  import "../utils/Initializable.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 ERC1967UpgradeUpgradeable is Initializable {
                      function __ERC1967Upgrade_init() internal initializer {
                          __ERC1967Upgrade_init_unchained();
                      }
                      function __ERC1967Upgrade_init_unchained() internal initializer {
                      }
                      // 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 StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                      }
                      /**
                       * @dev Stores a new address in the EIP1967 implementation slot.
                       */
                      function _setImplementation(address newImplementation) private {
                          require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                          StorageSlotUpgradeable.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) {
                              _functionDelegateCall(newImplementation, data);
                          }
                      }
                      /**
                       * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
                       *
                       * Emits an {Upgraded} event.
                       */
                      function _upgradeToAndCallSecure(
                          address newImplementation,
                          bytes memory data,
                          bool forceCall
                      ) internal {
                          address oldImplementation = _getImplementation();
                          // Initial upgrade and setup call
                          _setImplementation(newImplementation);
                          if (data.length > 0 || forceCall) {
                              _functionDelegateCall(newImplementation, data);
                          }
                          // Perform rollback test if not already in progress
                          StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT);
                          if (!rollbackTesting.value) {
                              // Trigger rollback using upgradeTo from the new implementation
                              rollbackTesting.value = true;
                              _functionDelegateCall(
                                  newImplementation,
                                  abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
                              );
                              rollbackTesting.value = false;
                              // Check rollback was effective
                              require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                              // Finally reset to the new implementation and log the upgrade
                              _upgradeTo(newImplementation);
                          }
                      }
                      /**
                       * @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 StorageSlotUpgradeable.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");
                          StorageSlotUpgradeable.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 StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
                      }
                      /**
                       * @dev Stores a new beacon in the EIP1967 beacon slot.
                       */
                      function _setBeacon(address newBeacon) private {
                          require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract");
                          require(
                              AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
                              "ERC1967: beacon implementation is not a contract"
                          );
                          StorageSlotUpgradeable.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) {
                              _functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
                          }
                      }
                      /**
                       * @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) private returns (bytes memory) {
                          require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed");
                      }
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @dev This is the interface that {BeaconProxy} expects of its beacon.
                   */
                  interface IBeaconUpgradeable {
                      /**
                       * @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
                  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 StorageSlotUpgradeable {
                      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: MIT
                  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 initializer {
                          __Context_init_unchained();
                      }
                      function __Context_init_unchained() internal initializer {
                      }
                      function _msgSender() internal view virtual returns (address) {
                          return msg.sender;
                      }
                      function _msgData() internal view virtual returns (bytes calldata) {
                          return msg.data;
                      }
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import { IFactory } from "../interfaces/IFactory.sol";
                  import { ErrorHandler } from "../libraries/ErrorHandler.sol";
                  /**
                   * @title FactoryControlled
                   *
                   * @dev Abstract smart contract responsible to hold IFactory factory address.
                   * @dev Stores PoolFactory address on initialization.
                   *
                   */
                  abstract contract FactoryControlled is Initializable {
                      using ErrorHandler for bytes4;
                      /// @dev Link to the pool factory IlluviumPoolFactory instance.
                      IFactory internal _factory;
                      /// @dev Attachs PoolFactory address to the FactoryControlled contract.
                      function __FactoryControlled_init(address factory_) internal initializer {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("__FactoryControlled_init(address)"))`
                          bytes4 fnSelector = 0xbb6c0dbf;
                          fnSelector.verifyNonZeroInput(uint160(factory_), 0);
                          _factory = IFactory(factory_);
                      }
                      /// @dev checks if caller is factory admin (eDAO multisig address).
                      function _requireIsFactoryController() internal view virtual {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("_requireIsFactoryController()"))`
                          bytes4 fnSelector = 0x39e71deb;
                          fnSelector.verifyAccess(msg.sender == _factory.owner());
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[49] private __gap;
                  }
                  

                  File 4 of 5: ERC1967Proxy
                  // SPDX-License-Identifier: MIT
                  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 internall call site, it will return directly to the external caller.
                       */
                      function _delegate(address implementation) internal virtual {
                          // solhint-disable-next-line no-inline-assembly
                          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 {
                      }
                  }
                  
                  
                  /**
                   * @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._
                   *
                   */
                  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 {
                          _setImplementation(newImplementation);
                          emit Upgraded(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 _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
                          address oldImplementation = _getImplementation();
                  
                          // Initial upgrade and setup call
                          _setImplementation(newImplementation);
                          if (data.length > 0 || forceCall) {
                              Address.functionDelegateCall(newImplementation, data);
                          }
                  
                          // Perform rollback test if not already in progress
                          StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
                          if (!rollbackTesting.value) {
                              // Trigger rollback using upgradeTo from the new implementation
                              rollbackTesting.value = true;
                              Address.functionDelegateCall(
                                  newImplementation,
                                  abi.encodeWithSignature(
                                      "upgradeTo(address)",
                                      oldImplementation
                                  )
                              );
                              rollbackTesting.value = false;
                              // Check rollback was effective
                              require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                              // Finally reset to the new implementation and log the upgrade
                              _setImplementation(newImplementation);
                              emit Upgraded(newImplementation);
                          }
                      }
                  
                      /**
                       * @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);
                          }
                      }
                  
                      /**
                       * @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 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);
                  }
                  
                  /**
                   * @dev Collection of functions related to the address type
                   */
                  library Address {
                      /**
                       * @dev Returns true if `account` is a contract.
                       *
                       * [IMPORTANT]
                       * ====
                       * It is unsafe to assume that an address for which this function returns
                       * false is an externally-owned account (EOA) and not a contract.
                       *
                       * Among others, `isContract` will return false for the following
                       * types of addresses:
                       *
                       *  - an externally-owned account
                       *  - a contract in construction
                       *  - an address where a contract will be created
                       *  - an address where a contract lived, but was destroyed
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize, which returns 0 for contracts in
                          // construction, since the code is only stored at the end of the
                          // constructor execution.
                  
                          uint256 size;
                          // solhint-disable-next-line no-inline-assembly
                          assembly { size := extcodesize(account) }
                          return size > 0;
                      }
                  
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                  
                          // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                          (bool success, ) = recipient.call{ value: amount }("");
                          require(success, "Address: unable to send value, recipient may have reverted");
                      }
                  
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain`call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          require(isContract(target), "Address: call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.call{ value: value }(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                          require(isContract(target), "Address: static call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          require(isContract(target), "Address: delegate call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              // Look for revert reason and bubble it up if present
                              if (returndata.length > 0) {
                                  // The easiest way to bubble the revert reason is using memory via assembly
                  
                                  // solhint-disable-next-line no-inline-assembly
                                  assembly {
                                      let returndata_size := mload(returndata)
                                      revert(add(32, returndata), returndata_size)
                                  }
                              } else {
                                  revert(errorMessage);
                              }
                          }
                      }
                  }
                  
                  /**
                   * @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
                          }
                      }
                  }
                  
                  /*
                   * @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) {
                          this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                          return msg.data;
                      }
                  }
                  
                  /**
                   * @dev Contract module which provides a basic access control mechanism, where
                   * there is an account (an owner) that can be granted exclusive access to
                   * specific functions.
                   *
                   * By default, the owner account will be the one that deploys the contract. This
                   * can later be changed with {transferOwnership}.
                   *
                   * This module is used through inheritance. It will make available the modifier
                   * `onlyOwner`, which can be applied to your functions to restrict their use to
                   * the owner.
                   */
                  abstract contract Ownable is Context {
                      address private _owner;
                  
                      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                  
                      /**
                       * @dev Initializes the contract setting the deployer as the initial owner.
                       */
                      constructor () {
                          address msgSender = _msgSender();
                          _owner = msgSender;
                          emit OwnershipTransferred(address(0), msgSender);
                      }
                  
                      /**
                       * @dev Returns the address of the current owner.
                       */
                      function owner() public view virtual returns (address) {
                          return _owner;
                      }
                  
                      /**
                       * @dev Throws if called by any account other than the owner.
                       */
                      modifier onlyOwner() {
                          require(owner() == _msgSender(), "Ownable: caller is not the owner");
                          _;
                      }
                  
                      /**
                       * @dev Leaves the contract without owner. It will not be possible to call
                       * `onlyOwner` functions anymore. Can only be called by the current owner.
                       *
                       * NOTE: Renouncing ownership will leave the contract without an owner,
                       * thereby removing any functionality that is only available to the owner.
                       */
                      function renounceOwnership() public virtual onlyOwner {
                          emit OwnershipTransferred(_owner, address(0));
                          _owner = address(0);
                      }
                  
                      /**
                       * @dev Transfers ownership of the contract to a new account (`newOwner`).
                       * Can only be called by the current owner.
                       */
                      function transferOwnership(address newOwner) public virtual onlyOwner {
                          require(newOwner != address(0), "Ownable: new owner is the zero address");
                          emit OwnershipTransferred(_owner, newOwner);
                          _owner = newOwner;
                      }
                  }
                  
                  /**
                   * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
                   * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
                   */
                  contract ProxyAdmin is Ownable {
                  
                      /**
                       * @dev Returns the current implementation of `proxy`.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                          // We need to manually run the static call since the getter cannot be flagged as view
                          // bytes4(keccak256("implementation()")) == 0x5c60da1b
                          (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
                          require(success);
                          return abi.decode(returndata, (address));
                      }
                  
                      /**
                       * @dev Returns the current admin of `proxy`.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                          // We need to manually run the static call since the getter cannot be flagged as view
                          // bytes4(keccak256("admin()")) == 0xf851a440
                          (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
                          require(success);
                          return abi.decode(returndata, (address));
                      }
                  
                      /**
                       * @dev Changes the admin of `proxy` to `newAdmin`.
                       *
                       * Requirements:
                       *
                       * - This contract must be the current admin of `proxy`.
                       */
                      function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
                          proxy.changeAdmin(newAdmin);
                      }
                  
                      /**
                       * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
                          proxy.upgradeTo(implementation);
                      }
                  
                      /**
                       * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
                       * {TransparentUpgradeableProxy-upgradeToAndCall}.
                       *
                       * Requirements:
                       *
                       * - This contract must be the admin of `proxy`.
                       */
                      function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
                          proxy.upgradeToAndCall{value: msg.value}(implementation, data);
                      }
                  }
                  
                  
                  /**
                   * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
                   * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
                   * continuation of the upgradability.
                   *
                   * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
                   *
                   * _Available since v4.1._
                   */
                  abstract contract UUPSUpgradeable is ERC1967Upgrade {
                      function upgradeTo(address newImplementation) external virtual {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, bytes(""), false);
                      }
                  
                      function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, data, true);
                      }
                  
                      function _authorizeUpgrade(address newImplementation) internal virtual;
                  }
                  
                  
                  abstract contract Proxiable is UUPSUpgradeable {
                      function _authorizeUpgrade(address newImplementation) internal override {
                          _beforeUpgrade(newImplementation);
                      }
                  
                      function _beforeUpgrade(address newImplementation) internal virtual;
                  }
                  
                  contract ChildOfProxiable is Proxiable {
                      function _beforeUpgrade(address newImplementation) internal virtual override {}
                  }
                  
                  
                  /**
                   * @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();
                      }
                  }
                  
                  /**
                   * @dev This contract implements a proxy that is upgradeable by an admin.
                   *
                   * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
                   * clashing], which can potentially be used in an attack, this contract uses the
                   * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
                   * things that go hand in hand:
                   *
                   * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
                   * that call matches one of the admin functions exposed by the proxy itself.
                   * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
                   * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
                   * "admin cannot fallback to proxy target".
                   *
                   * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
                   * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
                   * to sudden errors when trying to call a function from the proxy implementation.
                   *
                   * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
                   * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
                   */
                  contract TransparentUpgradeableProxy is ERC1967Proxy {
                      /**
                       * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
                       * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
                       */
                      constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                          assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                          _changeAdmin(admin_);
                      }
                  
                      /**
                       * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
                       */
                      modifier ifAdmin() {
                          if (msg.sender == _getAdmin()) {
                              _;
                          } else {
                              _fallback();
                          }
                      }
                  
                      /**
                       * @dev Returns the current admin.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
                       *
                       * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                       * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                       * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                       */
                      function admin() external ifAdmin returns (address admin_) {
                          admin_ = _getAdmin();
                      }
                  
                      /**
                       * @dev Returns the current implementation.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
                       *
                       * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                       * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                       * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                       */
                      function implementation() external ifAdmin returns (address implementation_) {
                          implementation_ = _implementation();
                      }
                  
                      /**
                       * @dev Changes the admin of the proxy.
                       *
                       * Emits an {AdminChanged} event.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
                       */
                      function changeAdmin(address newAdmin) external virtual ifAdmin {
                          _changeAdmin(newAdmin);
                      }
                  
                      /**
                       * @dev Upgrade the implementation of the proxy.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
                       */
                      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.
                       *
                       * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
                       */
                      function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                          _upgradeToAndCall(newImplementation, data, true);
                      }
                  
                      /**
                       * @dev Returns the current admin.
                       */
                      function _admin() internal view virtual returns (address) {
                          return _getAdmin();
                      }
                  
                      /**
                       * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
                       */
                      function _beforeFallback() internal virtual override {
                          require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                          super._beforeFallback();
                      }
                  }
                  
                  
                  // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
                  contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
                      constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
                  }

                  File 5 of 5: PoolFactory
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
                  import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
                  import { SafeCast } from "./libraries/SafeCast.sol";
                  import { Timestamp } from "./base/Timestamp.sol";
                  import { ICorePool } from "./interfaces/ICorePool.sol";
                  import { IERC20Mintable } from "./interfaces/IERC20Mintable.sol";
                  import { ErrorHandler } from "./libraries/ErrorHandler.sol";
                  /**
                   * @title Pool Factory V2
                   *
                   * @dev Pool Factory manages Illuvium staking pools, provides a single
                   *      public interface to access the pools, provides an interface for the pools
                   *      to mint yield rewards, access pool-related info, update weights, etc.
                   *
                   * @dev The factory is authorized (via its owner) to register new pools, change weights
                   *      of the existing pools, removing the pools (by changing their weights to zero).
                   *
                   * @dev The factory requires ROLE_TOKEN_CREATOR permission on the ILV and sILV tokens to mint yield
                   *      (see `mintYieldTo` function).
                   *
                   * @notice The contract uses Ownable implementation, so only the eDAO is able to handle
                   *         admin activities, such as registering new pools, doing contract upgrades,
                   *         changing pool weights, managing emission schedules and so on.
                   *
                   */
                  contract PoolFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable, Timestamp {
                      using ErrorHandler for bytes4;
                      using SafeCast for uint256;
                      /// @dev Auxiliary data structure used only in getPoolData() view function
                      struct PoolData {
                          // @dev pool token address (like ILV)
                          address poolToken;
                          // @dev pool address (like deployed core pool instance)
                          address poolAddress;
                          // @dev pool weight (200 for ILV pools, 800 for ILV/ETH pools - set during deployment)
                          uint32 weight;
                          // @dev flash pool flag
                          bool isFlashPool;
                      }
                      /**
                       * @dev ILV/second determines yield farming reward base
                       *      used by the yield pools controlled by the factory.
                       */
                      uint192 public ilvPerSecond;
                      /**
                       * @dev The yield is distributed proportionally to pool weights;
                       *      total weight is here to help in determining the proportion.
                       */
                      uint32 public totalWeight;
                      /**
                       * @dev ILV/second decreases by 3% every seconds/update
                       *      an update is triggered by executing `updateILVPerSecond` public function.
                       */
                      uint32 public secondsPerUpdate;
                      /**
                       * @dev End time is the last timestamp when ILV/second can be decreased;
                       *      it is implied that yield farming stops after that timestamp.
                       */
                      uint32 public endTime;
                      /**
                       * @dev Each time the ILV/second ratio gets updated, the timestamp
                       *      when the operation has occurred gets recorded into `lastRatioUpdate`.
                       * @dev This timestamp is then used to check if seconds/update `secondsPerUpdate`
                       *      has passed when decreasing yield reward by 3%.
                       */
                      uint32 public lastRatioUpdate;
                      /// @dev ILV token address.
                      address private _ilv;
                      /// @dev sILV token address
                      address private _silv;
                      /// @dev Maps pool token address (like ILV) -> pool address (like core pool instance).
                      mapping(address => address) public pools;
                      /// @dev Keeps track of registered pool addresses, maps pool address -> exists flag.
                      mapping(address => bool) public poolExists;
                      /**
                       * @dev Fired in registerPool()
                       *
                       * @param by an address which executed an action
                       * @param poolToken pool token address (like ILV)
                       * @param poolAddress deployed pool instance address
                       * @param weight pool weight
                       * @param isFlashPool flag indicating if pool is a flash pool
                       */
                      event LogRegisterPool(
                          address indexed by,
                          address indexed poolToken,
                          address indexed poolAddress,
                          uint64 weight,
                          bool isFlashPool
                      );
                      /**
                       * @dev Fired in `changePoolWeight()`.
                       *
                       * @param by an address which executed an action
                       * @param poolAddress deployed pool instance address
                       * @param weight new pool weight
                       */
                      event LogChangePoolWeight(address indexed by, address indexed poolAddress, uint32 weight);
                      /**
                       * @dev Fired in `updateILVPerSecond()`.
                       *
                       * @param by an address which executed an action
                       * @param newIlvPerSecond new ILV/second value
                       */
                      event LogUpdateILVPerSecond(address indexed by, uint256 newIlvPerSecond);
                      /**
                       * @dev Fired in `setEndTime()`.
                       *
                       * @param by an address which executed the action
                       * @param endTime new endTime value
                       */
                      event LogSetEndTime(address indexed by, uint32 endTime);
                      /**
                       * @dev Initializes a factory instance
                       *
                       * @param ilv_ ILV ERC20 token address
                       * @param silv_ sILV ERC20 token address
                       * @param _ilvPerSecond initial ILV/second value for rewards
                       * @param _secondsPerUpdate how frequently the rewards gets updated (decreased by 3%), seconds
                       * @param _initTime timestamp to measure _secondsPerUpdate from
                       * @param _endTime timestamp number when farming stops and rewards cannot be updated anymore
                       */
                      function initialize(
                          address ilv_,
                          address silv_,
                          uint192 _ilvPerSecond,
                          uint32 _secondsPerUpdate,
                          uint32 _initTime,
                          uint32 _endTime
                      ) external initializer {
                          bytes4 fnSelector = this.initialize.selector;
                          // verify the inputs are set correctly
                          fnSelector.verifyNonZeroInput(uint160(ilv_), 0);
                          fnSelector.verifyNonZeroInput(uint160(silv_), 1);
                          fnSelector.verifyNonZeroInput(_ilvPerSecond, 2);
                          fnSelector.verifyNonZeroInput(_secondsPerUpdate, 3);
                          fnSelector.verifyNonZeroInput(_initTime, 4);
                          fnSelector.verifyInput(_endTime > _now256(), 5);
                          __Ownable_init();
                          // save the inputs into internal state variables
                          _ilv = ilv_;
                          _silv = silv_;
                          ilvPerSecond = _ilvPerSecond;
                          secondsPerUpdate = _secondsPerUpdate;
                          lastRatioUpdate = _initTime;
                          endTime = _endTime;
                      }
                      /**
                       * @notice Given a pool token retrieves corresponding pool address.
                       *
                       * @dev A shortcut for `pools` mapping.
                       *
                       * @param poolToken pool token address (like ILV) to query pool address for
                       * @return pool address for the token specified
                       */
                      function getPoolAddress(address poolToken) external view virtual returns (address) {
                          // read the mapping and return
                          return address(pools[poolToken]);
                      }
                      /**
                       * @notice Reads pool information for the pool defined by its pool token address,
                       *      designed to simplify integration with the front ends.
                       *
                       * @param _poolToken pool token address to query pool information for.
                       * @return pool information packed in a PoolData struct.
                       */
                      function getPoolData(address _poolToken) public view virtual returns (PoolData memory) {
                          bytes4 fnSelector = this.getPoolData.selector;
                          // get the pool address from the mapping
                          ICorePool pool = ICorePool(pools[_poolToken]);
                          // throw if there is no pool registered for the token specified
                          fnSelector.verifyState(uint160(address(pool)) != 0, 0);
                          // read pool information from the pool smart contract
                          // via the pool interface (ICorePool)
                          address poolToken = pool.poolToken();
                          bool isFlashPool = pool.isFlashPool();
                          uint32 weight = pool.weight();
                          // create the in-memory structure and return it
                          return PoolData({ poolToken: poolToken, poolAddress: address(pool), weight: weight, isFlashPool: isFlashPool });
                      }
                      /**
                       * @dev Verifies if `secondsPerUpdate` has passed since last ILV/second
                       *      ratio update and if ILV/second reward can be decreased by 3%.
                       *
                       * @return true if enough time has passed and `updateILVPerSecond` can be executed.
                       */
                      function shouldUpdateRatio() public view virtual returns (bool) {
                          // if yield farming period has ended
                          if (_now256() > endTime) {
                              // ILV/second reward cannot be updated anymore
                              return false;
                          }
                          // check if seconds/update have passed since last update
                          return _now256() >= lastRatioUpdate + secondsPerUpdate;
                      }
                      /**
                       * @dev Registers an already deployed pool instance within the factory.
                       *
                       * @dev Can be executed by the pool factory owner only.
                       *
                       * @param pool address of the already deployed pool instance
                       */
                      function registerPool(address pool) public virtual onlyOwner {
                          // read pool information from the pool smart contract
                          // via the pool interface (ICorePool)
                          address poolToken = ICorePool(pool).poolToken();
                          bool isFlashPool = ICorePool(pool).isFlashPool();
                          uint32 weight = ICorePool(pool).weight();
                          // create pool structure, register it within the factory
                          pools[poolToken] = pool;
                          poolExists[pool] = true;
                          // update total pool weight of the factory
                          totalWeight += weight;
                          // emit an event
                          emit LogRegisterPool(msg.sender, poolToken, address(pool), weight, isFlashPool);
                      }
                      /**
                       * @notice Decreases ILV/second reward by 3%, can be executed
                       *      no more than once per `secondsPerUpdate` seconds.
                       */
                      function updateILVPerSecond() external virtual {
                          bytes4 fnSelector = this.updateILVPerSecond.selector;
                          // checks if ratio can be updated i.e. if seconds/update have passed
                          fnSelector.verifyState(shouldUpdateRatio(), 0);
                          // decreases ILV/second reward by 3%.
                          // To achieve that we multiply by 97 and then
                          // divide by 100
                          ilvPerSecond = (ilvPerSecond * 97) / 100;
                          // set current timestamp as the last ratio update timestamp
                          lastRatioUpdate = (_now256()).toUint32();
                          // emit an event
                          emit LogUpdateILVPerSecond(msg.sender, ilvPerSecond);
                      }
                      /**
                       * @dev Mints ILV tokens; executed by ILV Pool only.
                       *
                       * @dev Requires factory to have ROLE_TOKEN_CREATOR permission
                       *      on the ILV ERC20 token instance.
                       *
                       * @param _to an address to mint tokens to
                       * @param _value amount of ILV tokens to mint
                       * @param _useSILV whether ILV or sILV should be minted
                       */
                      function mintYieldTo(
                          address _to,
                          uint256 _value,
                          bool _useSILV
                      ) external virtual {
                          bytes4 fnSelector = this.mintYieldTo.selector;
                          // verify that sender is a pool registered withing the factory
                          fnSelector.verifyState(poolExists[msg.sender], 0);
                          // mints the requested token to the indicated address
                          if (!_useSILV) {
                              IERC20Mintable(_ilv).mint(_to, _value);
                          } else {
                              IERC20Mintable(_silv).mint(_to, _value);
                          }
                      }
                      /**
                       * @dev Changes the weight of the pool;
                       *      executed by the pool itself or by the factory owner.
                       *
                       * @param pool address of the pool to change weight for
                       * @param weight new weight value to set to
                       */
                      function changePoolWeight(address pool, uint32 weight) external virtual {
                          bytes4 fnSelector = this.changePoolWeight.selector;
                          // verify function is executed either by factory owner or by the pool itself
                          fnSelector.verifyAccess(msg.sender == owner() || poolExists[msg.sender]);
                          // recalculate total weight
                          totalWeight = totalWeight + weight - ICorePool(pool).weight();
                          // set the new pool weight
                          ICorePool(pool).setWeight(weight);
                          // emit an event
                          emit LogChangePoolWeight(msg.sender, address(pool), weight);
                      }
                      /**
                       * @dev Updates yield generation ending timestamp.
                       *
                       * @param _endTime new end time value to be stored
                       */
                      function setEndTime(uint32 _endTime) external virtual onlyOwner {
                          bytes4 fnSelector = this.setEndTime.selector;
                          // checks if _endTime is a timestap after the last time that
                          // ILV/second has been updated
                          fnSelector.verifyInput(_endTime > lastRatioUpdate, 0);
                          // updates endTime state var
                          endTime = _endTime;
                          // emits an event
                          emit LogSetEndTime(msg.sender, _endTime);
                      }
                      /**
                       * @dev Overrides `Ownable.renounceOwnership()`, to avoid accidentally
                       *      renouncing ownership of the PoolFactory contract.
                       */
                      function renounceOwnership() public virtual override {}
                      /// @dev See `CorePool._authorizeUpgrade()`
                      function _authorizeUpgrade(address) internal virtual override onlyOwner {}
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[45] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @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 a proxied contract can't have 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.
                   */
                  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() {
                          require(_initializing || !_initialized, "Initializable: contract is already initialized");
                          bool isTopLevelCall = !_initializing;
                          if (isTopLevelCall) {
                              _initializing = true;
                              _initialized = true;
                          }
                          _;
                          if (isTopLevelCall) {
                              _initializing = false;
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
                  import "./Initializable.sol";
                  /**
                   * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
                   * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
                   *
                   * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
                   * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
                   * `UUPSUpgradeable` with a custom implementation of upgrades.
                   *
                   * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
                   *
                   * _Available since v4.1._
                   */
                  abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable {
                      function __UUPSUpgradeable_init() internal initializer {
                          __ERC1967Upgrade_init_unchained();
                          __UUPSUpgradeable_init_unchained();
                      }
                      function __UUPSUpgradeable_init_unchained() internal initializer {
                      }
                      /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
                      address private immutable __self = address(this);
                      /**
                       * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
                       * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
                       * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
                       * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
                       * fail.
                       */
                      modifier onlyProxy() {
                          require(address(this) != __self, "Function must be called through delegatecall");
                          require(_getImplementation() == __self, "Function must be called through active proxy");
                          _;
                      }
                      /**
                       * @dev Upgrade the implementation of the proxy to `newImplementation`.
                       *
                       * Calls {_authorizeUpgrade}.
                       *
                       * Emits an {Upgraded} event.
                       */
                      function upgradeTo(address newImplementation) external virtual onlyProxy {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, new bytes(0), false);
                      }
                      /**
                       * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
                       * encoded in `data`.
                       *
                       * Calls {_authorizeUpgrade}.
                       *
                       * Emits an {Upgraded} event.
                       */
                      function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy {
                          _authorizeUpgrade(newImplementation);
                          _upgradeToAndCallSecure(newImplementation, data, true);
                      }
                      /**
                       * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
                       * {upgradeTo} and {upgradeToAndCall}.
                       *
                       * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
                       *
                       * ```solidity
                       * function _authorizeUpgrade(address) internal override onlyOwner {}
                       * ```
                       */
                      function _authorizeUpgrade(address newImplementation) internal virtual;
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  import "../utils/ContextUpgradeable.sol";
                  import "../proxy/utils/Initializable.sol";
                  /**
                   * @dev Contract module which provides a basic access control mechanism, where
                   * there is an account (an owner) that can be granted exclusive access to
                   * specific functions.
                   *
                   * By default, the owner account will be the one that deploys the contract. This
                   * can later be changed with {transferOwnership}.
                   *
                   * This module is used through inheritance. It will make available the modifier
                   * `onlyOwner`, which can be applied to your functions to restrict their use to
                   * the owner.
                   */
                  abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
                      address private _owner;
                      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                      /**
                       * @dev Initializes the contract setting the deployer as the initial owner.
                       */
                      function __Ownable_init() internal initializer {
                          __Context_init_unchained();
                          __Ownable_init_unchained();
                      }
                      function __Ownable_init_unchained() internal initializer {
                          _setOwner(_msgSender());
                      }
                      /**
                       * @dev Returns the address of the current owner.
                       */
                      function owner() public view virtual returns (address) {
                          return _owner;
                      }
                      /**
                       * @dev Throws if called by any account other than the owner.
                       */
                      modifier onlyOwner() {
                          require(owner() == _msgSender(), "Ownable: caller is not the owner");
                          _;
                      }
                      /**
                       * @dev Leaves the contract without owner. It will not be possible to call
                       * `onlyOwner` functions anymore. Can only be called by the current owner.
                       *
                       * NOTE: Renouncing ownership will leave the contract without an owner,
                       * thereby removing any functionality that is only available to the owner.
                       */
                      function renounceOwnership() public virtual onlyOwner {
                          _setOwner(address(0));
                      }
                      /**
                       * @dev Transfers ownership of the contract to a new account (`newOwner`).
                       * Can only be called by the current owner.
                       */
                      function transferOwnership(address newOwner) public virtual onlyOwner {
                          require(newOwner != address(0), "Ownable: new owner is the zero address");
                          _setOwner(newOwner);
                      }
                      function _setOwner(address newOwner) private {
                          address oldOwner = _owner;
                          _owner = newOwner;
                          emit OwnershipTransferred(oldOwner, newOwner);
                      }
                      uint256[49] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)
                  pragma solidity 0.8.4;
                  import { ErrorHandler } from "./ErrorHandler.sol";
                  /**
                   * @notice Copied from OpenZeppelin's SafeCast.sol, adapted to use just in the required
                   * uint sizes.
                   * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
                   * checks.
                   *
                   * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
                   * easily result in undesired exploitation or bugs, since developers usually
                   * assume that overflows raise errors. `SafeCast` restores this intuition by
                   * reverting the transaction when such an operation overflows.
                   *
                   * Using this library instead of the unchecked operations eliminates an entire
                   * class of bugs, so it's recommended to use it always.
                   *
                   * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
                   * all math on `uint256` and `int256` and then downcasting.
                   */
                  library SafeCast {
                      using ErrorHandler for bytes4;
                      /**
                       * @dev Returns the downcasted uint248 from uint256, reverting on
                       * overflow (when the input is greater than largest uint248).
                       *
                       * Counterpart to Solidity's `uint248` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 248 bits
                       */
                      function toUint248(uint256 _value) internal pure returns (uint248) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint248(uint256))"))`
                          bytes4 fnSelector = 0x3fd72672;
                          fnSelector.verifyInput(_value <= type(uint248).max, 0);
                          return uint248(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint128 from uint256, reverting on
                       * overflow (when the input is greater than largest uint128).
                       *
                       * Counterpart to Solidity's `uint128` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 128 bits
                       */
                      function toUint128(uint256 _value) internal pure returns (uint128) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint128(uint256))"))`
                          bytes4 fnSelector = 0x809fdd33;
                          fnSelector.verifyInput(_value <= type(uint128).max, 0);
                          return uint128(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint120 from uint256, reverting on
                       * overflow (when the input is greater than largest uint120).
                       *
                       * Counterpart to Solidity's `uint120` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 120 bits
                       */
                      function toUint120(uint256 _value) internal pure returns (uint120) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint120(uint256))"))`
                          bytes4 fnSelector = 0x1e4e4bad;
                          fnSelector.verifyInput(_value <= type(uint120).max, 0);
                          return uint120(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint64 from uint256, reverting on
                       * overflow (when the input is greater than largest uint64).
                       *
                       * Counterpart to Solidity's `uint64` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 64 bits
                       */
                      function toUint64(uint256 _value) internal pure returns (uint64) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint64(uint256))"))`
                          bytes4 fnSelector = 0x2665fad0;
                          fnSelector.verifyInput(_value <= type(uint64).max, 0);
                          return uint64(_value);
                      }
                      /**
                       * @dev Returns the downcasted uint32 from uint256, reverting on
                       * overflow (when the input is greater than largest uint32).
                       *
                       * Counterpart to Solidity's `uint32` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 32 bits
                       */
                      function toUint32(uint256 _value) internal pure returns (uint32) {
                          // we're using selector to simplify input and state validation
                          // internal function simulated selector is `bytes4(keccak256("toUint32(uint256))"))`
                          bytes4 fnSelector = 0xc8193255;
                          fnSelector.verifyInput(_value <= type(uint32).max, 0);
                          return uint32(_value);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  /// @title Function for getting block timestamp.
                  /// @dev Base contract that is overridden for tests.
                  abstract contract Timestamp {
                      /**
                       * @dev Testing time-dependent functionality is difficult and the best way of
                       *      doing it is to override time in helper test smart contracts.
                       *
                       * @return `block.timestamp` in mainnet, custom values in testnets (if overridden).
                       */
                      function _now256() internal view virtual returns (uint256) {
                          // return current block timestamp
                          return block.timestamp;
                      }
                      /**
                       * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
                       *      the amount of storage used by a contract always adds up to the 50.
                       *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { Stake } from "../libraries/Stake.sol";
                  interface ICorePool {
                      function users(address _user)
                          external
                          view
                          returns (
                              uint128,
                              uint128,
                              uint128,
                              uint248,
                              uint8,
                              uint256,
                              uint256
                          );
                      function poolToken() external view returns (address);
                      function isFlashPool() external view returns (bool);
                      function weight() external view returns (uint32);
                      function lastYieldDistribution() external view returns (uint64);
                      function yieldRewardsPerWeight() external view returns (uint256);
                      function globalWeight() external view returns (uint256);
                      function pendingRewards(address _user) external view returns (uint256, uint256);
                      function poolTokenReserve() external view returns (uint256);
                      function balanceOf(address _user) external view returns (uint256);
                      function getTotalReserves() external view returns (uint256);
                      function getStake(address _user, uint256 _stakeId) external view returns (Stake.Data memory);
                      function getStakesLength(address _user) external view returns (uint256);
                      function sync() external;
                      function setWeight(uint32 _weight) external;
                      function receiveVaultRewards(uint256 value) external;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
                  interface IERC20Mintable is IERC20Upgradeable {
                      function mint(address _to, uint256 _value) external;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  /**
                   * @title Errors Library.
                   *
                   * @notice Introduces some very common input and state validation for smart contracts,
                   *      such as non-zero input validation, general boolean expression validation, access validation.
                   *
                   * @notice Throws pre-defined errors instead of string error messages to reduce gas costs.
                   *
                   * @notice Since the library handles only very common errors, concrete smart contracts may
                   *      also introduce their own error types and handling.
                   *
                   * @author Basil Gorin
                   */
                  library ErrorHandler {
                      /**
                       * @notice Thrown on zero input at index specified in a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param paramIndex function parameter index which caused an error thrown
                       */
                      error ZeroInput(bytes4 fnSelector, uint8 paramIndex);
                      /**
                       * @notice Thrown on invalid input at index specified in a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param paramIndex function parameter index which caused an error thrown
                       */
                      error InvalidInput(bytes4 fnSelector, uint8 paramIndex);
                      /**
                       * @notice Thrown on invalid state in a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param errorCode unique error code determining the exact place in code where error was thrown
                       */
                      error InvalidState(bytes4 fnSelector, uint256 errorCode);
                      /**
                       * @notice Thrown on invalid access to a function specified.
                       *
                       * @param fnSelector function selector, defines a function where error was thrown
                       * @param addr an address which access was denied, usually transaction sender
                       */
                      error AccessDenied(bytes4 fnSelector, address addr);
                      /**
                       * @notice Verifies an input is set (non-zero).
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param value a value to check if it's set (non-zero)
                       * @param paramIndex function parameter index which is verified
                       */
                      function verifyNonZeroInput(
                          bytes4 fnSelector,
                          uint256 value,
                          uint8 paramIndex
                      ) internal pure {
                          if (value == 0) {
                              revert ZeroInput(fnSelector, paramIndex);
                          }
                      }
                      /**
                       * @notice Verifies an input is correct.
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param expr a boolean expression used to verify the input
                       * @param paramIndex function parameter index which is verified
                       */
                      function verifyInput(
                          bytes4 fnSelector,
                          bool expr,
                          uint8 paramIndex
                      ) internal pure {
                          if (!expr) {
                              revert InvalidInput(fnSelector, paramIndex);
                          }
                      }
                      /**
                       * @notice Verifies smart contract state is correct.
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param expr a boolean expression used to verify the contract state
                       * @param errorCode unique error code determining the exact place in code which is verified
                       */
                      function verifyState(
                          bytes4 fnSelector,
                          bool expr,
                          uint256 errorCode
                      ) internal pure {
                          if (!expr) {
                              revert InvalidState(fnSelector, errorCode);
                          }
                      }
                      /**
                       * @notice Verifies an access to the function.
                       *
                       * @param fnSelector function selector, defines a function which called the verification
                       * @param expr a boolean expression used to verify the access
                       */
                      function verifyAccess(bytes4 fnSelector, bool expr) internal view {
                          if (!expr) {
                              revert AccessDenied(fnSelector, msg.sender);
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.2;
                  import "../beacon/IBeaconUpgradeable.sol";
                  import "../../utils/AddressUpgradeable.sol";
                  import "../../utils/StorageSlotUpgradeable.sol";
                  import "../utils/Initializable.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 ERC1967UpgradeUpgradeable is Initializable {
                      function __ERC1967Upgrade_init() internal initializer {
                          __ERC1967Upgrade_init_unchained();
                      }
                      function __ERC1967Upgrade_init_unchained() internal initializer {
                      }
                      // 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 StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                      }
                      /**
                       * @dev Stores a new address in the EIP1967 implementation slot.
                       */
                      function _setImplementation(address newImplementation) private {
                          require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                          StorageSlotUpgradeable.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) {
                              _functionDelegateCall(newImplementation, data);
                          }
                      }
                      /**
                       * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
                       *
                       * Emits an {Upgraded} event.
                       */
                      function _upgradeToAndCallSecure(
                          address newImplementation,
                          bytes memory data,
                          bool forceCall
                      ) internal {
                          address oldImplementation = _getImplementation();
                          // Initial upgrade and setup call
                          _setImplementation(newImplementation);
                          if (data.length > 0 || forceCall) {
                              _functionDelegateCall(newImplementation, data);
                          }
                          // Perform rollback test if not already in progress
                          StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT);
                          if (!rollbackTesting.value) {
                              // Trigger rollback using upgradeTo from the new implementation
                              rollbackTesting.value = true;
                              _functionDelegateCall(
                                  newImplementation,
                                  abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
                              );
                              rollbackTesting.value = false;
                              // Check rollback was effective
                              require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                              // Finally reset to the new implementation and log the upgrade
                              _upgradeTo(newImplementation);
                          }
                      }
                      /**
                       * @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 StorageSlotUpgradeable.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");
                          StorageSlotUpgradeable.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 StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
                      }
                      /**
                       * @dev Stores a new beacon in the EIP1967 beacon slot.
                       */
                      function _setBeacon(address newBeacon) private {
                          require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract");
                          require(
                              AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
                              "ERC1967: beacon implementation is not a contract"
                          );
                          StorageSlotUpgradeable.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) {
                              _functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
                          }
                      }
                      /**
                       * @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) private returns (bytes memory) {
                          require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed");
                      }
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @dev This is the interface that {BeaconProxy} expects of its beacon.
                   */
                  interface IBeaconUpgradeable {
                      /**
                       * @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
                  pragma solidity ^0.8.0;
                  /**
                   * @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
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize, which returns 0 for contracts in
                          // construction, since the code is only stored at the end of the
                          // constructor execution.
                          uint256 size;
                          assembly {
                              size := extcodesize(account)
                          }
                          return size > 0;
                      }
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                          (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
                  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 StorageSlotUpgradeable {
                      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: MIT
                  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 initializer {
                          __Context_init_unchained();
                      }
                      function __Context_init_unchained() internal initializer {
                      }
                      function _msgSender() internal view virtual returns (address) {
                          return msg.sender;
                      }
                      function _msgData() internal view virtual returns (bytes calldata) {
                          return msg.data;
                      }
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.4;
                  /**
                   * @dev Stake library used by ILV pool and Sushi LP Pool.
                   *
                   * @dev Responsible to manage weight calculation and store important constants
                   *      related to stake period, base weight and multipliers utilized.
                   */
                  library Stake {
                      struct Data {
                          /// @dev token amount staked
                          uint120 value;
                          /// @dev locking period - from
                          uint64 lockedFrom;
                          /// @dev locking period - until
                          uint64 lockedUntil;
                          /// @dev indicates if the stake was created as a yield reward
                          bool isYield;
                      }
                      /**
                       * @dev Stake weight is proportional to stake value and time locked, precisely
                       *      "stake value wei multiplied by (fraction of the year locked plus one)".
                       * @dev To avoid significant precision loss due to multiplication by "fraction of the year" [0, 1],
                       *      weight is stored multiplied by 1e6 constant, as an integer.
                       * @dev Corner case 1: if time locked is zero, weight is stake value multiplied by 1e6 + base weight
                       * @dev Corner case 2: if time locked is two years, division of
                              (lockedUntil - lockedFrom) / MAX_STAKE_PERIOD is 1e6, and
                       *      weight is a stake value multiplied by 2 * 1e6.
                       */
                      uint256 internal constant WEIGHT_MULTIPLIER = 1e6;
                      /**
                       * @dev Minimum weight value, if result of multiplication using WEIGHT_MULTIPLIER
                       *      is 0 (e.g stake flexible), then BASE_WEIGHT is used.
                       */
                      uint256 internal constant BASE_WEIGHT = 1e6;
                      /**
                       * @dev Minimum period that someone can lock a stake for.
                       */
                      uint256 internal constant MIN_STAKE_PERIOD = 30 days;
                      /**
                       * @dev Maximum period that someone can lock a stake for.
                       */
                      uint256 internal constant MAX_STAKE_PERIOD = 365 days;
                      /**
                       * @dev Rewards per weight are stored multiplied by 1e20 as uint.
                       */
                      uint256 internal constant REWARD_PER_WEIGHT_MULTIPLIER = 1e20;
                      /**
                       * @dev When we know beforehand that staking is done for yield instead of
                       *      executing `weight()` function we use the following constant.
                       */
                      uint256 internal constant YIELD_STAKE_WEIGHT_MULTIPLIER = 2 * 1e6;
                      function weight(Data storage _self) internal view returns (uint256) {
                          return
                              uint256(
                                  (((_self.lockedUntil - _self.lockedFrom) * WEIGHT_MULTIPLIER) / MAX_STAKE_PERIOD + BASE_WEIGHT) *
                                      _self.value
                              );
                      }
                      /**
                       * @dev Converts stake weight (not to be mixed with the pool weight) to
                       *      ILV reward value, applying the 10^12 division on weight
                       *
                       * @param _weight stake weight
                       * @param _rewardPerWeight ILV reward per weight
                       * @param _rewardPerWeightPaid last reward per weight value used for user earnings
                       * @return reward value normalized to 10^12
                       */
                      function earned(
                          uint256 _weight,
                          uint256 _rewardPerWeight,
                          uint256 _rewardPerWeightPaid
                      ) internal pure returns (uint256) {
                          // apply the formula and return
                          return (_weight * (_rewardPerWeight - _rewardPerWeightPaid)) / REWARD_PER_WEIGHT_MULTIPLIER;
                      }
                      /**
                       * @dev Converts reward ILV value to stake weight (not to be mixed with the pool weight),
                       *      applying the 10^12 multiplication on the reward.
                       *      - OR -
                       * @dev Converts reward ILV value to reward/weight if stake weight is supplied as second
                       *      function parameter instead of reward/weight.
                       *
                       * @param _reward yield reward
                       * @param _globalWeight total weight in the pool
                       * @return reward per weight value
                       */
                      function getRewardPerWeight(uint256 _reward, uint256 _globalWeight) internal pure returns (uint256) {
                          // apply the reverse formula and return
                          return (_reward * REWARD_PER_WEIGHT_MULTIPLIER) / _globalWeight;
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  /**
                   * @dev Interface of the ERC20 standard as defined in the EIP.
                   */
                  interface IERC20Upgradeable {
                      /**
                       * @dev Returns the amount of tokens in existence.
                       */
                      function totalSupply() external view returns (uint256);
                      /**
                       * @dev Returns the amount of tokens owned by `account`.
                       */
                      function balanceOf(address account) external view returns (uint256);
                      /**
                       * @dev Moves `amount` tokens from the caller's account to `recipient`.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transfer(address recipient, uint256 amount) external returns (bool);
                      /**
                       * @dev Returns the remaining number of tokens that `spender` will be
                       * allowed to spend on behalf of `owner` through {transferFrom}. This is
                       * zero by default.
                       *
                       * This value changes when {approve} or {transferFrom} are called.
                       */
                      function allowance(address owner, address spender) external view returns (uint256);
                      /**
                       * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * IMPORTANT: Beware that changing an allowance with this method brings the risk
                       * that someone may use both the old and the new allowance by unfortunate
                       * transaction ordering. One possible solution to mitigate this race
                       * condition is to first reduce the spender's allowance to 0 and set the
                       * desired value afterwards:
                       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                       *
                       * Emits an {Approval} event.
                       */
                      function approve(address spender, uint256 amount) external returns (bool);
                      /**
                       * @dev Moves `amount` tokens from `sender` to `recipient` using the
                       * allowance mechanism. `amount` is then deducted from the caller's
                       * allowance.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transferFrom(
                          address sender,
                          address recipient,
                          uint256 amount
                      ) external returns (bool);
                      /**
                       * @dev Emitted when `value` tokens are moved from one account (`from`) to
                       * another (`to`).
                       *
                       * Note that `value` may be zero.
                       */
                      event Transfer(address indexed from, address indexed to, uint256 value);
                      /**
                       * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                       * a call to {approve}. `value` is the new allowance.
                       */
                      event Approval(address indexed owner, address indexed spender, uint256 value);
                  }