ETH Price: $2,483.78 (-0.19%)

Transaction Decoder

Block:
18534303 at Nov-09-2023 12:11:11 PM +UTC
Transaction Fee:
0.005247707704947312 ETH $13.03
Gas Used:
180,158 Gas / 29.128363464 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x1AB09747...74fC98edF
1.448876091930373379 Eth
Nonce: 575
1.443628384225426067 Eth
Nonce: 576
0.005247707704947312
(Titan Builder)
10.216708308445235671 Eth10.216717316345235671 Eth0.0000090079

Execution Trace

ERC1967Proxy.952e68cf( )
  • ILVPool.stake( _value=26846225153645135255, _lockDuration=2592000 )
    • IlluviumCorePool.getDeposit( _user=0x1AB097474fe82108670C7D1309caaC874fC98edF, _depositId=0 ) => ( [{name:tokenAmount, type:uint256, order:1, indexed:false, value:0, valueString:0}, {name:weight, type:uint256, order:2, indexed:false, value:0, valueString:0}, {name:lockedFrom, type:uint64, order:3, indexed:false, value:0, valueString:0}, {name:lockedUntil, type:uint64, order:4, indexed:false, value:0, valueString:0}, {name:isYield, type:bool, order:5, indexed:false, value:false, valueString:False}] )
    • ERC1967Proxy.STATICCALL( )
      • PoolFactory.DELEGATECALL( )
      • ERC1967Proxy.STATICCALL( )
        • PoolFactory.DELEGATECALL( )
        • ERC1967Proxy.STATICCALL( )
          • PoolFactory.DELEGATECALL( )
          • ERC1967Proxy.STATICCALL( )
            • PoolFactory.DELEGATECALL( )
            • IlluviumERC20.transferFrom( _from=0x1AB097474fe82108670C7D1309caaC874fC98edF, _to=0x7f5f854FfB6b7701540a00C69c4AB2De2B34291D, _value=26846225153645135255 )
              File 1 of 6: 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 6: 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 3 of 6: IlluviumCorePool
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "./IlluviumPoolBase.sol";
              /**
               * @title Illuvium Core Pool
               *
               * @notice Core pools represent permanent pools like ILV or ILV/ETH Pair pool,
               *      core pools allow staking for arbitrary periods of time up to 1 year
               *
               * @dev See IlluviumPoolBase for more details
               *
               * @author Pedro Bergamini, reviewed by Basil Gorin
               */
              contract IlluviumCorePool is IlluviumPoolBase {
                  /// @dev Flag indicating pool type, false means "core pool"
                  bool public constant override isFlashPool = false;
                  /// @dev Link to deployed IlluviumVault instance
                  address public 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 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 Fired in receiveVaultRewards()
                   *
                   * @param _by an address that sent the rewards, always a vault
                   * @param amount amount of tokens received
                   */
                  event VaultRewardsReceived(address indexed _by, uint256 amount);
                  /**
                   * @dev Fired in _processVaultRewards() and dependent functions, like processRewards()
                   *
                   * @param _by an address which executed the function
                   * @param _to an address which received a reward
                   * @param amount amount of reward received
                   */
                  event VaultRewardsClaimed(address indexed _by, address indexed _to, uint256 amount);
                  /**
                   * @dev Fired in setVault()
                   *
                   * @param _by an address which executed the function, always a factory owner
                   */
                  event VaultUpdated(address indexed _by, address _fromVal, address _toVal);
                  /**
                   * @dev Creates/deploys an instance of the core pool
                   *
                   * @param _ilv ILV ERC20 Token IlluviumERC20 address
                   * @param _silv sILV ERC20 Token EscrowedIlluviumERC20 address
                   * @param _factory Pool factory IlluviumPoolFactory instance/address
                   * @param _poolToken token the pool operates on, for example ILV or ILV/ETH pair
                   * @param _initBlock initial block used to calculate the rewards
                   * @param _weight number representing a weight of the pool, actual weight fraction
                   *      is calculated as that number divided by the total pools weight and doesn't exceed one
                   */
                  constructor(
                      address _ilv,
                      address _silv,
                      IlluviumPoolFactory _factory,
                      address _poolToken,
                      uint64 _initBlock,
                      uint32 _weight
                  ) IlluviumPoolBase(_ilv, _silv, _factory, _poolToken, _initBlock, _weight) {}
                  /**
                   * @notice Calculates current vault rewards value available for address specified
                   *
                   * @dev Performs calculations based on current smart contract state only,
                   *      not taking into account any additional time/blocks which might have passed
                   *
                   * @param _staker an address to calculate vault rewards value for
                   * @return pending calculated vault reward value for the given address
                   */
                  function pendingVaultRewards(address _staker) public view returns (uint256 pending) {
                      User memory user = users[_staker];
                      return weightToReward(user.totalWeight, vaultRewardsPerWeight) - user.subVaultRewards;
                  }
                  /**
                   * @dev Executed only by the factory owner to Set the vault
                   *
                   * @param _vault an address of deployed IlluviumVault instance
                   */
                  function setVault(address _vault) external {
                      // verify function is executed by the factory owner
                      require(factory.owner() == msg.sender, "access denied");
                      // verify input is set
                      require(_vault != address(0), "zero input");
                      // emit an event
                      emit VaultUpdated(msg.sender, vault, _vault);
                      // update vault address
                      vault = _vault;
                  }
                  /**
                   * @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 _rewardsAmount amount of ILV rewards to transfer into the pool
                   */
                  function receiveVaultRewards(uint256 _rewardsAmount) external {
                      require(msg.sender == vault, "access denied");
                      // return silently if there is no reward to receive
                      if (_rewardsAmount == 0) {
                          return;
                      }
                      require(usersLockingWeight > 0, "zero locking weight");
                      transferIlvFrom(msg.sender, address(this), _rewardsAmount);
                      vaultRewardsPerWeight += rewardToWeight(_rewardsAmount, usersLockingWeight);
                      // update `poolTokenReserve` only if this is a ILV Core Pool
                      if (poolToken == ilv) {
                          poolTokenReserve += _rewardsAmount;
                      }
                      emit VaultRewardsReceived(msg.sender, _rewardsAmount);
                  }
                  /**
                   * @notice Service function to calculate and pay pending vault and yield rewards to the sender
                   *
                   * @dev Internally executes similar function `_processRewards` from the parent smart contract
                   *      to calculate and pay yield rewards; adds vault rewards processing
                   *
                   * @dev Can be executed by anyone at any time, but has an effect only when
                   *      executed by deposit holder and when at least one block passes from the
                   *      previous reward processing
                   * @dev Executed internally when "staking as a pool" (`stakeAsPool`)
                   * @dev When timing conditions are not met (executed too frequently, or after factory
                   *      end block), function doesn't throw and exits silently
                   *
                   * @dev _useSILV flag has a context of yield rewards only
                   *
                   * @param _useSILV flag indicating whether to mint sILV token as a reward or not;
                   *      when set to true - sILV reward is minted immediately and sent to sender,
                   *      when set to false - new ILV reward deposit gets created if pool is an ILV pool
                   *      (poolToken is ILV token), or new pool deposit gets created together with sILV minted
                   *      when pool is not an ILV pool (poolToken is not an ILV token)
                   */
                  function processRewards(bool _useSILV) external override {
                      _processRewards(msg.sender, _useSILV, true);
                  }
                  /**
                   * @dev Executed internally by the pool itself (from the parent `IlluviumPoolBase` smart contract)
                   *      as part of yield rewards processing logic (`IlluviumPoolBase._processRewards` function)
                   * @dev Executed when _useSILV is false and pool is not an ILV pool - see `IlluviumPoolBase._processRewards`
                   *
                   * @param _staker an address which stakes (the yield reward)
                   * @param _amount amount to be staked (yield reward amount)
                   */
                  function stakeAsPool(address _staker, uint256 _amount) external {
                      require(factory.poolExists(msg.sender), "access denied");
                      _sync();
                      User storage user = users[_staker];
                      if (user.tokenAmount > 0) {
                          _processRewards(_staker, true, false);
                      }
                      uint256 depositWeight = _amount * YEAR_STAKE_WEIGHT_MULTIPLIER;
                      Deposit memory newDeposit =
                          Deposit({
                              tokenAmount: _amount,
                              lockedFrom: uint64(now256()),
                              lockedUntil: uint64(now256() + 365 days),
                              weight: depositWeight,
                              isYield: true
                          });
                      user.tokenAmount += _amount;
                      user.totalWeight += depositWeight;
                      user.deposits.push(newDeposit);
                      usersLockingWeight += depositWeight;
                      user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);
                      user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);
                      // update `poolTokenReserve` only if this is a LP Core Pool (stakeAsPool can be executed only for LP pool)
                      poolTokenReserve += _amount;
                  }
                  /**
                   * @inheritdoc IlluviumPoolBase
                   *
                   * @dev Additionally to the parent smart contract, updates vault rewards of the holder,
                   *      and updates (increases) pool token reserve (pool tokens value available in the pool)
                   */
                  function _stake(
                      address _staker,
                      uint256 _amount,
                      uint64 _lockedUntil,
                      bool _useSILV,
                      bool _isYield
                  ) internal override {
                      super._stake(_staker, _amount, _lockedUntil, _useSILV, _isYield);
                      User storage user = users[_staker];
                      user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);
                      poolTokenReserve += _amount;
                  }
                  /**
                   * @inheritdoc IlluviumPoolBase
                   *
                   * @dev Additionally to the parent smart contract, updates vault rewards of the holder,
                   *      and updates (decreases) pool token reserve (pool tokens value available in the pool)
                   */
                  function _unstake(
                      address _staker,
                      uint256 _depositId,
                      uint256 _amount,
                      bool _useSILV
                  ) internal override {
                      User storage user = users[_staker];
                      Deposit memory stakeDeposit = user.deposits[_depositId];
                      require(stakeDeposit.lockedFrom == 0 || now256() > stakeDeposit.lockedUntil, "deposit not yet unlocked");
                      poolTokenReserve -= _amount;
                      super._unstake(_staker, _depositId, _amount, _useSILV);
                      user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);
                  }
                  /**
                   * @inheritdoc IlluviumPoolBase
                   *
                   * @dev Additionally to the parent smart contract, processes vault rewards of the holder,
                   *      and for ILV pool updates (increases) pool token reserve (pool tokens value available in the pool)
                   */
                  function _processRewards(
                      address _staker,
                      bool _useSILV,
                      bool _withUpdate
                  ) internal override returns (uint256 pendingYield) {
                      _processVaultRewards(_staker);
                      pendingYield = super._processRewards(_staker, _useSILV, _withUpdate);
                      // update `poolTokenReserve` only if this is a ILV Core Pool
                      if (poolToken == ilv && !_useSILV) {
                          poolTokenReserve += pendingYield;
                      }
                  }
                  /**
                   * @dev Used internally to process vault rewards for the staker
                   *
                   * @param _staker address of the user (staker) to process rewards for
                   */
                  function _processVaultRewards(address _staker) private {
                      User storage user = users[_staker];
                      uint256 pendingVaultClaim = pendingVaultRewards(_staker);
                      if (pendingVaultClaim == 0) return;
                      // read ILV token balance of the pool via standard ERC20 interface
                      uint256 ilvBalance = IERC20(ilv).balanceOf(address(this));
                      require(ilvBalance >= pendingVaultClaim, "contract ILV balance too low");
                      // update `poolTokenReserve` only if this is a ILV Core Pool
                      if (poolToken == ilv) {
                          // protects against rounding errors
                          poolTokenReserve -= pendingVaultClaim > poolTokenReserve ? poolTokenReserve : pendingVaultClaim;
                      }
                      user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);
                      // transfer fails if pool ILV balance is not enough - which is a desired behavior
                      transferIlv(_staker, pendingVaultClaim);
                      emit VaultRewardsClaimed(msg.sender, _staker, pendingVaultClaim);
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "../interfaces/IPool.sol";
              import "../interfaces/ICorePool.sol";
              import "./ReentrancyGuard.sol";
              import "./IlluviumPoolFactory.sol";
              import "../utils/SafeERC20.sol";
              import "../token/EscrowedIlluviumERC20.sol";
              /**
               * @title Illuvium Pool Base
               *
               * @notice An abstract contract containing common logic for any pool,
               *      be it a flash pool (temporary pool like SNX) or a core pool (permanent pool like ILV/ETH or ILV pool)
               *
               * @dev Deployment and initialization.
               *      Any pool deployed must be bound to the deployed pool factory (IlluviumPoolFactory)
               *      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 10% for ILV pool and 90% for ILV/ETH pool.
               *      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 100 as weight and for ILV/ETH pool - 900.
               *
               * @author Pedro Bergamini, reviewed by Basil Gorin
               */
              abstract contract IlluviumPoolBase is IPool, IlluviumAware, ReentrancyGuard {
                  /// @dev Data structure representing token holder using a pool
                  struct User {
                      // @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
                      Deposit[] deposits;
                  }
                  /// @dev Token holder storage, maps token holder address to their data record
                  mapping(address => User) public users;
                  /// @dev Link to sILV ERC20 Token EscrowedIlluviumERC20 instance
                  address public immutable override silv;
                  /// @dev Link to the pool factory IlluviumPoolFactory instance
                  IlluviumPoolFactory public immutable factory;
                  /// @dev Link to the pool token instance, for example ILV or ILV/ETH pair
                  address public immutable override poolToken;
                  /// @dev Pool weight, 100 for ILV pool or 900 for ILV/ETH
                  uint32 public override weight;
                  /// @dev Block number of the last yield distribution event
                  uint64 public override lastYieldDistribution;
                  /// @dev Used to calculate yield 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 override yieldRewardsPerWeight;
                  /// @dev Used to calculate yield rewards, keeps track of the tokens weight locked in staking
                  uint256 public override usersLockingWeight;
                  /**
                   * @dev Stake weight is proportional to deposit amount and time locked, precisely
                   *      "deposit amount 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 deposit amount multiplied by 1e6
                   * @dev Corner case 2: if time locked is one year, fraction of the year locked is one, and
                   *      weight is a deposit amount multiplied by 2 * 1e6
                   */
                  uint256 internal constant WEIGHT_MULTIPLIER = 1e6;
                  /**
                   * @dev When we know beforehand that staking is done for a year, and fraction of the year locked is one,
                   *      we use simplified calculation and use the following constant instead previos one
                   */
                  uint256 internal constant YEAR_STAKE_WEIGHT_MULTIPLIER = 2 * WEIGHT_MULTIPLIER;
                  /**
                   * @dev Rewards per weight are stored multiplied by 1e12, as integers.
                   */
                  uint256 internal constant REWARD_PER_WEIGHT_MULTIPLIER = 1e12;
                  /**
                   * @dev Fired in _stake() and stake()
                   *
                   * @param _by an address which performed an operation, usually token holder
                   * @param _from token holder address, the tokens will be returned to that address
                   * @param amount amount of tokens staked
                   */
                  event Staked(address indexed _by, address indexed _from, uint256 amount);
                  /**
                   * @dev Fired in _updateStakeLock() and updateStakeLock()
                   *
                   * @param _by an address which performed an operation
                   * @param depositId updated deposit ID
                   * @param lockedFrom deposit locked from value
                   * @param lockedUntil updated deposit locked until value
                   */
                  event StakeLockUpdated(address indexed _by, uint256 depositId, uint64 lockedFrom, uint64 lockedUntil);
                  /**
                   * @dev Fired in _unstake() and unstake()
                   *
                   * @param _by an address which performed an operation, usually token holder
                   * @param _to an address which received the unstaked tokens, usually token holder
                   * @param amount amount of tokens unstaked
                   */
                  event Unstaked(address indexed _by, address indexed _to, uint256 amount);
                  /**
                   * @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 block number
                   */
                  event Synchronized(address indexed _by, uint256 yieldRewardsPerWeight, uint64 lastYieldDistribution);
                  /**
                   * @dev Fired in _processRewards(), processRewards() and dependent functions (stake, unstake, etc.)
                   *
                   * @param _by an address which performed an operation
                   * @param _to an address which claimed the yield reward
                   * @param sIlv flag indicating if reward was paid (minted) in sILV
                   * @param amount amount of yield paid
                   */
                  event YieldClaimed(address indexed _by, address indexed _to, bool sIlv, uint256 amount);
                  /**
                   * @dev Fired in setWeight()
                   *
                   * @param _by an address which performed an operation, always a factory
                   * @param _fromVal old pool weight value
                   * @param _toVal new pool weight value
                   */
                  event PoolWeightUpdated(address indexed _by, uint32 _fromVal, uint32 _toVal);
                  /**
                   * @dev Overridden in sub-contracts to construct the pool
                   *
                   * @param _ilv ILV ERC20 Token IlluviumERC20 address
                   * @param _silv sILV ERC20 Token EscrowedIlluviumERC20 address
                   * @param _factory Pool factory IlluviumPoolFactory instance/address
                   * @param _poolToken token the pool operates on, for example ILV or ILV/ETH pair
                   * @param _initBlock initial block used to calculate the rewards
                   *      note: _initBlock can be set to the future effectively meaning _sync() calls will do nothing
                   * @param _weight number representing a weight of the pool, actual weight fraction
                   *      is calculated as that number divided by the total pools weight and doesn't exceed one
                   */
                  constructor(
                      address _ilv,
                      address _silv,
                      IlluviumPoolFactory _factory,
                      address _poolToken,
                      uint64 _initBlock,
                      uint32 _weight
                  ) IlluviumAware(_ilv) {
                      // verify the inputs are set
                      require(_silv != address(0), "sILV address not set");
                      require(address(_factory) != address(0), "ILV Pool fct address not set");
                      require(_poolToken != address(0), "pool token address not set");
                      require(_initBlock > 0, "init block not set");
                      require(_weight > 0, "pool weight not set");
                      // verify sILV instance supplied
                      require(
                          EscrowedIlluviumERC20(_silv).TOKEN_UID() ==
                              0xac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d62,
                          "unexpected sILV TOKEN_UID"
                      );
                      // verify IlluviumPoolFactory instance supplied
                      require(
                          _factory.FACTORY_UID() == 0xc5cfd88c6e4d7e5c8a03c255f03af23c0918d8e82cac196f57466af3fd4a5ec7,
                          "unexpected FACTORY_UID"
                      );
                      // save the inputs into internal state variables
                      silv = _silv;
                      factory = _factory;
                      poolToken = _poolToken;
                      weight = _weight;
                      // init the dependent internal state variables
                      lastYieldDistribution = _initBlock;
                  }
                  /**
                   * @notice Calculates current yield rewards value available for address specified
                   *
                   * @param _staker an address to calculate yield rewards value for
                   * @return calculated yield reward value for the given address
                   */
                  function pendingYieldRewards(address _staker) external view override returns (uint256) {
                      // `newYieldRewardsPerWeight` will store stored or recalculated value for `yieldRewardsPerWeight`
                      uint256 newYieldRewardsPerWeight;
                      // 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 (blockNumber() > lastYieldDistribution && usersLockingWeight != 0) {
                          uint256 endBlock = factory.endBlock();
                          uint256 multiplier =
                              blockNumber() > endBlock ? endBlock - lastYieldDistribution : blockNumber() - lastYieldDistribution;
                          uint256 ilvRewards = (multiplier * weight * factory.ilvPerBlock()) / factory.totalWeight();
                          // recalculated value for `yieldRewardsPerWeight`
                          newYieldRewardsPerWeight = rewardToWeight(ilvRewards, usersLockingWeight) + yieldRewardsPerWeight;
                      } else {
                          // if smart contract state is up to date, we don't recalculate
                          newYieldRewardsPerWeight = yieldRewardsPerWeight;
                      }
                      // based on the rewards per weight value, calculate pending rewards;
                      User memory user = users[_staker];
                      uint256 pending = weightToReward(user.totalWeight, newYieldRewardsPerWeight) - user.subYieldRewards;
                      return pending;
                  }
                  /**
                   * @notice Returns total staked token balance for the given address
                   *
                   * @param _user an address to query balance for
                   * @return total staked token balance
                   */
                  function balanceOf(address _user) external view override returns (uint256) {
                      // read specified user token amount and return
                      return users[_user].tokenAmount;
                  }
                  /**
                   * @notice Returns information on the given deposit for the given address
                   *
                   * @dev See getDepositsLength
                   *
                   * @param _user an address to query deposit for
                   * @param _depositId zero-indexed deposit ID for the address specified
                   * @return deposit info as Deposit structure
                   */
                  function getDeposit(address _user, uint256 _depositId) external view override returns (Deposit memory) {
                      // read deposit at specified index and return
                      return users[_user].deposits[_depositId];
                  }
                  /**
                   * @notice Returns number of deposits for the given address. Allows iteration over deposits.
                   *
                   * @dev See getDeposit
                   *
                   * @param _user an address to query deposit length for
                   * @return number of deposits for the given address
                   */
                  function getDepositsLength(address _user) external view override returns (uint256) {
                      // read deposits array length and return
                      return users[_user].deposits.length;
                  }
                  /**
                   * @notice Stakes specified amount of tokens for the specified amount of time,
                   *      and pays pending yield rewards if any
                   *
                   * @dev Requires amount to stake to be greater than zero
                   *
                   * @param _amount amount of tokens to stake
                   * @param _lockUntil stake period as unix timestamp; zero means no locking
                   * @param _useSILV a flag indicating if previous reward to be paid as sILV
                   */
                  function stake(
                      uint256 _amount,
                      uint64 _lockUntil,
                      bool _useSILV
                  ) external override {
                      // delegate call to an internal function
                      _stake(msg.sender, _amount, _lockUntil, _useSILV, false);
                  }
                  /**
                   * @notice Unstakes specified amount of tokens, and pays pending yield rewards if any
                   *
                   * @dev Requires amount to unstake to be greater than zero
                   *
                   * @param _depositId deposit ID to unstake from, zero-indexed
                   * @param _amount amount of tokens to unstake
                   * @param _useSILV a flag indicating if reward to be paid as sILV
                   */
                  function unstake(
                      uint256 _depositId,
                      uint256 _amount,
                      bool _useSILV
                  ) external override {
                      // delegate call to an internal function
                      _unstake(msg.sender, _depositId, _amount, _useSILV);
                  }
                  /**
                   * @notice Extends locking period for a given deposit
                   *
                   * @dev Requires new lockedUntil value to be:
                   *      higher than the current one, and
                   *      in the future, but
                   *      no more than 1 year in the future
                   *
                   * @param depositId updated deposit ID
                   * @param lockedUntil updated deposit locked until value
                   * @param useSILV used for _processRewards check if it should use ILV or sILV
                   */
                  function updateStakeLock(
                      uint256 depositId,
                      uint64 lockedUntil,
                      bool useSILV
                  ) external {
                      // sync and call processRewards
                      _sync();
                      _processRewards(msg.sender, useSILV, false);
                      // delegate call to an internal function
                      _updateStakeLock(msg.sender, depositId, lockedUntil);
                  }
                  /**
                   * @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 block 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 block), function doesn't throw and exits silently
                   */
                  function sync() external override {
                      // delegate call to an internal function
                      _sync();
                  }
                  /**
                   * @notice Service function to calculate and pay pending yield rewards to the sender
                   *
                   * @dev Can be executed by anyone at any time, but has an effect only when
                   *      executed by deposit holder and when at least one block passes from the
                   *      previous reward processing
                   * @dev Executed internally when staking and unstaking, executes sync() under the hood
                   *      before making further calculations and payouts
                   * @dev When timing conditions are not met (executed too frequently, or after factory
                   *      end block), function doesn't throw and exits silently
                   *
                   * @param _useSILV flag indicating whether to mint sILV token as a reward or not;
                   *      when set to true - sILV reward is minted immediately and sent to sender,
                   *      when set to false - new ILV reward deposit gets created if pool is an ILV pool
                   *      (poolToken is ILV token), or new pool deposit gets created together with sILV minted
                   *      when pool is not an ILV pool (poolToken is not an ILV token)
                   */
                  function processRewards(bool _useSILV) external virtual override {
                      // delegate call to an internal function
                      _processRewards(msg.sender, _useSILV, true);
                  }
                  /**
                   * @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 override {
                      // verify function is executed by the factory
                      require(msg.sender == address(factory), "access denied");
                      // emit an event logging old and new weight values
                      emit PoolWeightUpdated(msg.sender, weight, _weight);
                      // set the new weight value
                      weight = _weight;
                  }
                  /**
                   * @dev Similar to public pendingYieldRewards, but performs calculations based on
                   *      current smart contract state only, not taking into account any additional
                   *      time/blocks which might have passed
                   *
                   * @param _staker an address to calculate yield rewards value for
                   * @return pending calculated yield reward value for the given address
                   */
                  function _pendingYieldRewards(address _staker) internal view returns (uint256 pending) {
                      // read user data structure into memory
                      User memory user = users[_staker];
                      // and perform the calculation using the values read
                      return weightToReward(user.totalWeight, yieldRewardsPerWeight) - user.subYieldRewards;
                  }
                  /**
                   * @dev Used internally, mostly by children implementations, see stake()
                   *
                   * @param _staker an address which stakes tokens and which will receive them back
                   * @param _amount amount of tokens to stake
                   * @param _lockUntil stake period as unix timestamp; zero means no locking
                   * @param _useSILV a flag indicating if previous reward to be paid as sILV
                   * @param _isYield a flag indicating if that stake is created to store yield reward
                   *      from the previously unstaked stake
                   */
                  function _stake(
                      address _staker,
                      uint256 _amount,
                      uint64 _lockUntil,
                      bool _useSILV,
                      bool _isYield
                  ) internal virtual {
                      // validate the inputs
                      require(_amount > 0, "zero amount");
                      require(
                          _lockUntil == 0 || (_lockUntil > now256() && _lockUntil - now256() <= 365 days),
                          "invalid lock interval"
                      );
                      // update smart contract state
                      _sync();
                      // get a link to user data struct, we will write to it later
                      User storage user = users[_staker];
                      // process current pending rewards if any
                      if (user.tokenAmount > 0) {
                          _processRewards(_staker, _useSILV, false);
                      }
                      // in most of the cases added amount `addedAmount` is simply `_amount`
                      // however for deflationary tokens this can be different
                      // read the current balance
                      uint256 previousBalance = IERC20(poolToken).balanceOf(address(this));
                      // transfer `_amount`; note: some tokens may get burnt here
                      transferPoolTokenFrom(address(msg.sender), address(this), _amount);
                      // read new balance, usually this is just the difference `previousBalance - _amount`
                      uint256 newBalance = IERC20(poolToken).balanceOf(address(this));
                      // calculate real amount taking into account deflation
                      uint256 addedAmount = newBalance - previousBalance;
                      // set the `lockFrom` and `lockUntil` taking into account that
                      // zero value for `_lockUntil` means "no locking" and leads to zero values
                      // for both `lockFrom` and `lockUntil`
                      uint64 lockFrom = _lockUntil > 0 ? uint64(now256()) : 0;
                      uint64 lockUntil = _lockUntil;
                      // stake weight formula rewards for locking
                      uint256 stakeWeight =
                          (((lockUntil - lockFrom) * WEIGHT_MULTIPLIER) / 365 days + WEIGHT_MULTIPLIER) * addedAmount;
                      // makes sure stakeWeight is valid
                      assert(stakeWeight > 0);
                      // create and save the deposit (append it to deposits array)
                      Deposit memory deposit =
                          Deposit({
                              tokenAmount: addedAmount,
                              weight: stakeWeight,
                              lockedFrom: lockFrom,
                              lockedUntil: lockUntil,
                              isYield: _isYield
                          });
                      // deposit ID is an index of the deposit in `deposits` array
                      user.deposits.push(deposit);
                      // update user record
                      user.tokenAmount += addedAmount;
                      user.totalWeight += stakeWeight;
                      user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);
                      // update global variable
                      usersLockingWeight += stakeWeight;
                      // emit an event
                      emit Staked(msg.sender, _staker, _amount);
                  }
                  /**
                   * @dev Used internally, mostly by children implementations, see unstake()
                   *
                   * @param _staker an address which unstakes tokens (which previously staked them)
                   * @param _depositId deposit ID to unstake from, zero-indexed
                   * @param _amount amount of tokens to unstake
                   * @param _useSILV a flag indicating if reward to be paid as sILV
                   */
                  function _unstake(
                      address _staker,
                      uint256 _depositId,
                      uint256 _amount,
                      bool _useSILV
                  ) internal virtual {
                      // verify an amount is set
                      require(_amount > 0, "zero amount");
                      // get a link to user data struct, we will write to it later
                      User storage user = users[_staker];
                      // get a link to the corresponding deposit, we may write to it later
                      Deposit storage stakeDeposit = user.deposits[_depositId];
                      // deposit structure may get deleted, so we save isYield flag to be able to use it
                      bool isYield = stakeDeposit.isYield;
                      // verify available balance
                      // if staker address ot deposit doesn't exist this check will fail as well
                      require(stakeDeposit.tokenAmount >= _amount, "amount exceeds stake");
                      // update smart contract state
                      _sync();
                      // and process current pending rewards if any
                      _processRewards(_staker, _useSILV, false);
                      // recalculate deposit weight
                      uint256 previousWeight = stakeDeposit.weight;
                      uint256 newWeight =
                          (((stakeDeposit.lockedUntil - stakeDeposit.lockedFrom) * WEIGHT_MULTIPLIER) /
                              365 days +
                              WEIGHT_MULTIPLIER) * (stakeDeposit.tokenAmount - _amount);
                      // update the deposit, or delete it if its depleted
                      if (stakeDeposit.tokenAmount - _amount == 0) {
                          delete user.deposits[_depositId];
                      } else {
                          stakeDeposit.tokenAmount -= _amount;
                          stakeDeposit.weight = newWeight;
                      }
                      // update user record
                      user.tokenAmount -= _amount;
                      user.totalWeight = user.totalWeight - previousWeight + newWeight;
                      user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);
                      // update global variable
                      usersLockingWeight = usersLockingWeight - previousWeight + newWeight;
                      // if the deposit was created by the pool itself as a yield reward
                      if (isYield) {
                          // mint the yield via the factory
                          factory.mintYieldTo(msg.sender, _amount);
                      } else {
                          // otherwise just return tokens back to holder
                          transferPoolToken(msg.sender, _amount);
                      }
                      // emit an event
                      emit Unstaked(msg.sender, _staker, _amount);
                  }
                  /**
                   * @dev Used internally, mostly by children implementations, see sync()
                   *
                   * @dev Updates smart contract state (`yieldRewardsPerWeight`, `lastYieldDistribution`),
                   *      updates factory state via `updateILVPerBlock`
                   */
                  function _sync() internal virtual {
                      // update ILV per block value in factory if required
                      if (factory.shouldUpdateRatio()) {
                          factory.updateILVPerBlock();
                      }
                      // check bound conditions and if these are not met -
                      // exit silently, without emitting an event
                      uint256 endBlock = factory.endBlock();
                      if (lastYieldDistribution >= endBlock) {
                          return;
                      }
                      if (blockNumber() <= lastYieldDistribution) {
                          return;
                      }
                      // if locking weight is zero - update only `lastYieldDistribution` and exit
                      if (usersLockingWeight == 0) {
                          lastYieldDistribution = uint64(blockNumber());
                          return;
                      }
                      // to calculate the reward we need to know how many blocks passed, and reward per block
                      uint256 currentBlock = blockNumber() > endBlock ? endBlock : blockNumber();
                      uint256 blocksPassed = currentBlock - lastYieldDistribution;
                      uint256 ilvPerBlock = factory.ilvPerBlock();
                      // calculate the reward
                      uint256 ilvReward = (blocksPassed * ilvPerBlock * weight) / factory.totalWeight();
                      // update rewards per weight and `lastYieldDistribution`
                      yieldRewardsPerWeight += rewardToWeight(ilvReward, usersLockingWeight);
                      lastYieldDistribution = uint64(currentBlock);
                      // emit an event
                      emit Synchronized(msg.sender, yieldRewardsPerWeight, lastYieldDistribution);
                  }
                  /**
                   * @dev Used internally, mostly by children implementations, see processRewards()
                   *
                   * @param _staker an address which receives the reward (which has staked some tokens earlier)
                   * @param _useSILV flag indicating whether to mint sILV token as a reward or not, see processRewards()
                   * @param _withUpdate flag allowing to disable synchronization (see sync()) if set to false
                   * @return pendingYield the rewards calculated and optionally re-staked
                   */
                  function _processRewards(
                      address _staker,
                      bool _useSILV,
                      bool _withUpdate
                  ) internal virtual returns (uint256 pendingYield) {
                      // update smart contract state if required
                      if (_withUpdate) {
                          _sync();
                      }
                      // calculate pending yield rewards, this value will be returned
                      pendingYield = _pendingYieldRewards(_staker);
                      // if pending yield is zero - just return silently
                      if (pendingYield == 0) return 0;
                      // get link to a user data structure, we will write into it later
                      User storage user = users[_staker];
                      // if sILV is requested
                      if (_useSILV) {
                          // - mint sILV
                          mintSIlv(_staker, pendingYield);
                      } else if (poolToken == ilv) {
                          // calculate pending yield weight,
                          // 2e6 is the bonus weight when staking for 1 year
                          uint256 depositWeight = pendingYield * YEAR_STAKE_WEIGHT_MULTIPLIER;
                          // if the pool is ILV Pool - create new ILV deposit
                          // and save it - push it into deposits array
                          Deposit memory newDeposit =
                              Deposit({
                                  tokenAmount: pendingYield,
                                  lockedFrom: uint64(now256()),
                                  lockedUntil: uint64(now256() + 365 days), // staking yield for 1 year
                                  weight: depositWeight,
                                  isYield: true
                              });
                          user.deposits.push(newDeposit);
                          // update user record
                          user.tokenAmount += pendingYield;
                          user.totalWeight += depositWeight;
                          // update global variable
                          usersLockingWeight += depositWeight;
                      } else {
                          // for other pools - stake as pool
                          address ilvPool = factory.getPoolAddress(ilv);
                          ICorePool(ilvPool).stakeAsPool(_staker, pendingYield);
                      }
                      // update users's record for `subYieldRewards` if requested
                      if (_withUpdate) {
                          user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);
                      }
                      // emit an event
                      emit YieldClaimed(msg.sender, _staker, _useSILV, pendingYield);
                  }
                  /**
                   * @dev See updateStakeLock()
                   *
                   * @param _staker an address to update stake lock
                   * @param _depositId updated deposit ID
                   * @param _lockedUntil updated deposit locked until value
                   */
                  function _updateStakeLock(
                      address _staker,
                      uint256 _depositId,
                      uint64 _lockedUntil
                  ) internal {
                      // validate the input time
                      require(_lockedUntil > now256(), "lock should be in the future");
                      // get a link to user data struct, we will write to it later
                      User storage user = users[_staker];
                      // get a link to the corresponding deposit, we may write to it later
                      Deposit storage stakeDeposit = user.deposits[_depositId];
                      // validate the input against deposit structure
                      require(_lockedUntil > stakeDeposit.lockedUntil, "invalid new lock");
                      // verify locked from and locked until values
                      if (stakeDeposit.lockedFrom == 0) {
                          require(_lockedUntil - now256() <= 365 days, "max lock period is 365 days");
                          stakeDeposit.lockedFrom = uint64(now256());
                      } else {
                          require(_lockedUntil - stakeDeposit.lockedFrom <= 365 days, "max lock period is 365 days");
                      }
                      // update locked until value, calculate new weight
                      stakeDeposit.lockedUntil = _lockedUntil;
                      uint256 newWeight =
                          (((stakeDeposit.lockedUntil - stakeDeposit.lockedFrom) * WEIGHT_MULTIPLIER) /
                              365 days +
                              WEIGHT_MULTIPLIER) * stakeDeposit.tokenAmount;
                      // save previous weight
                      uint256 previousWeight = stakeDeposit.weight;
                      // update weight
                      stakeDeposit.weight = newWeight;
                      // update user total weight and global locking weight
                      user.totalWeight = user.totalWeight - previousWeight + newWeight;
                      usersLockingWeight = usersLockingWeight - previousWeight + newWeight;
                      // emit an event
                      emit StakeLockUpdated(_staker, _depositId, stakeDeposit.lockedFrom, _lockedUntil);
                  }
                  /**
                   * @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
                   * @return reward value normalized to 10^12
                   */
                  function weightToReward(uint256 _weight, uint256 rewardPerWeight) public pure returns (uint256) {
                      // apply the formula and return
                      return (_weight * rewardPerWeight) / 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 rewardPerWeight reward/weight (or stake weight)
                   * @return stake weight (or reward/weight)
                   */
                  function rewardToWeight(uint256 reward, uint256 rewardPerWeight) public pure returns (uint256) {
                      // apply the reverse formula and return
                      return (reward * REWARD_PER_WEIGHT_MULTIPLIER) / rewardPerWeight;
                  }
                  /**
                   * @dev Testing time-dependent functionality is difficult and the best way of
                   *      doing it is to override block number in helper test smart contracts
                   *
                   * @return `block.number` in mainnet, custom values in testnets (if overridden)
                   */
                  function blockNumber() public view virtual returns (uint256) {
                      // return current block number
                      return block.number;
                  }
                  /**
                   * @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() public view virtual returns (uint256) {
                      // return current block timestamp
                      return block.timestamp;
                  }
                  /**
                   * @dev Executes EscrowedIlluviumERC20.mint(_to, _values)
                   *      on the bound EscrowedIlluviumERC20 instance
                   *
                   * @dev Reentrancy safe due to the EscrowedIlluviumERC20 design
                   */
                  function mintSIlv(address _to, uint256 _value) private {
                      // just delegate call to the target
                      EscrowedIlluviumERC20(silv).mint(_to, _value);
                  }
                  /**
                   * @dev Executes SafeERC20.safeTransfer on a pool token
                   *
                   * @dev Reentrancy safety enforced via `ReentrancyGuard.nonReentrant`
                   */
                  function transferPoolToken(address _to, uint256 _value) internal nonReentrant {
                      // just delegate call to the target
                      SafeERC20.safeTransfer(IERC20(poolToken), _to, _value);
                  }
                  /**
                   * @dev Executes SafeERC20.safeTransferFrom on a pool token
                   *
                   * @dev Reentrancy safety enforced via `ReentrancyGuard.nonReentrant`
                   */
                  function transferPoolTokenFrom(
                      address _from,
                      address _to,
                      uint256 _value
                  ) internal nonReentrant {
                      // just delegate call to the target
                      SafeERC20.safeTransferFrom(IERC20(poolToken), _from, _to, _value);
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "./ILinkedToILV.sol";
              /**
               * @title Illuvium Pool
               *
               * @notice An abstraction representing a pool, see IlluviumPoolBase for details
               *
               * @author Pedro Bergamini, reviewed by Basil Gorin
               */
              interface IPool is ILinkedToILV {
                  /**
                   * @dev Deposit is a key data structure used in staking,
                   *      it represents a unit of stake with its amount, weight and term (time interval)
                   */
                  struct Deposit {
                      // @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;
                  }
                  // for the rest of the functions see Soldoc in IlluviumPoolBase
                  function silv() external view returns (address);
                  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 usersLockingWeight() external view returns (uint256);
                  function pendingYieldRewards(address _user) external view returns (uint256);
                  function balanceOf(address _user) external view returns (uint256);
                  function getDeposit(address _user, uint256 _depositId) external view returns (Deposit memory);
                  function getDepositsLength(address _user) external view returns (uint256);
                  function stake(
                      uint256 _amount,
                      uint64 _lockedUntil,
                      bool useSILV
                  ) external;
                  function unstake(
                      uint256 _depositId,
                      uint256 _amount,
                      bool useSILV
                  ) external;
                  function sync() external;
                  function processRewards(bool useSILV) external;
                  function setWeight(uint32 _weight) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "./IPool.sol";
              interface ICorePool is IPool {
                  function vaultRewardsPerToken() external view returns (uint256);
                  function poolTokenReserve() external view returns (uint256);
                  function stakeAsPool(address _staker, uint256 _amount) external;
                  function receiveVaultRewards(uint256 _amount) external;
              }
              // https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/security/ReentrancyGuard.sol
              // #24a0bc23cfe3fbc76f8f2510b78af1e948ae6651
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              /**
               * @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 ReentrancyGuard {
                // 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;
                constructor () {
                  _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;
                }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "../interfaces/IPool.sol";
              import "./IlluviumAware.sol";
              import "./IlluviumCorePool.sol";
              import "../token/EscrowedIlluviumERC20.sol";
              import "../utils/Ownable.sol";
              /**
               * @title Illuvium Pool Factory
               *
               * @notice ILV Pool Factory manages Illuvium Yield farming 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.
               *
               * @notice 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 token to mint yield
               *      (see `mintYieldTo` function)
               *
               * @author Pedro Bergamini, reviewed by Basil Gorin
               */
              contract IlluviumPoolFactory is Ownable, IlluviumAware {
                  /**
                   * @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 FACTORY_UID = 0xc5cfd88c6e4d7e5c8a03c255f03af23c0918d8e82cac196f57466af3fd4a5ec7;
                  /// @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/block determines yield farming reward base
                   *      used by the yield pools controlled by the factory
                   */
                  uint192 public ilvPerBlock;
                  /**
                   * @dev The yield is distributed proportionally to pool weights;
                   *      total weight is here to help in determining the proportion
                   */
                  uint32 public totalWeight;
                  /**
                   * @dev ILV/block decreases by 3% every blocks/update (set to 91252 blocks during deployment);
                   *      an update is triggered by executing `updateILVPerBlock` public function
                   */
                  uint32 public immutable blocksPerUpdate;
                  /**
                   * @dev End block is the last block when ILV/block can be decreased;
                   *      it is implied that yield farming stops after that block
                   */
                  uint32 public endBlock;
                  /**
                   * @dev Each time the ILV/block ratio gets updated, the block number
                   *      when the operation has occurred gets recorded into `lastRatioUpdate`
                   * @dev This block number is then used to check if blocks/update `blocksPerUpdate`
                   *      has passed when decreasing yield reward by 3%
                   */
                  uint32 public lastRatioUpdate;
                  /// @dev sILV token address is used to create ILV core pool(s)
                  address public immutable 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 createPool() and 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 PoolRegistered(
                      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 WeightUpdated(address indexed _by, address indexed poolAddress, uint32 weight);
                  /**
                   * @dev Fired in updateILVPerBlock()
                   *
                   * @param _by an address which executed an action
                   * @param newIlvPerBlock new ILV/block value
                   */
                  event IlvRatioUpdated(address indexed _by, uint256 newIlvPerBlock);
                  /**
                   * @dev Creates/deploys a factory instance
                   *
                   * @param _ilv ILV ERC20 token address
                   * @param _silv sILV ERC20 token address
                   * @param _ilvPerBlock initial ILV/block value for rewards
                   * @param _blocksPerUpdate how frequently the rewards gets updated (decreased by 3%), blocks
                   * @param _initBlock block number to measure _blocksPerUpdate from
                   * @param _endBlock block number when farming stops and rewards cannot be updated anymore
                   */
                  constructor(
                      address _ilv,
                      address _silv,
                      uint192 _ilvPerBlock,
                      uint32 _blocksPerUpdate,
                      uint32 _initBlock,
                      uint32 _endBlock
                  ) IlluviumAware(_ilv) {
                      // verify the inputs are set
                      require(_silv != address(0), "sILV address not set");
                      require(_ilvPerBlock > 0, "ILV/block not set");
                      require(_blocksPerUpdate > 0, "blocks/update not set");
                      require(_initBlock > 0, "init block not set");
                      require(_endBlock > _initBlock, "invalid end block: must be greater than init block");
                      // verify sILV instance supplied
                      require(
                          EscrowedIlluviumERC20(_silv).TOKEN_UID() ==
                              0xac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d62,
                          "unexpected sILV TOKEN_UID"
                      );
                      // save the inputs into internal state variables
                      silv = _silv;
                      ilvPerBlock = _ilvPerBlock;
                      blocksPerUpdate = _blocksPerUpdate;
                      lastRatioUpdate = _initBlock;
                      endBlock = _endBlock;
                  }
                  /**
                   * @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 returns (address) {
                      // read the mapping and return
                      return 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 returns (PoolData memory) {
                      // get the pool address from the mapping
                      address poolAddr = pools[_poolToken];
                      // throw if there is no pool registered for the token specified
                      require(poolAddr != address(0), "pool not found");
                      // read pool information from the pool smart contract
                      // via the pool interface (IPool)
                      address poolToken = IPool(poolAddr).poolToken();
                      bool isFlashPool = IPool(poolAddr).isFlashPool();
                      uint32 weight = IPool(poolAddr).weight();
                      // create the in-memory structure and return it
                      return PoolData({ poolToken: poolToken, poolAddress: poolAddr, weight: weight, isFlashPool: isFlashPool });
                  }
                  /**
                   * @dev Verifies if `blocksPerUpdate` has passed since last ILV/block
                   *      ratio update and if ILV/block reward can be decreased by 3%
                   *
                   * @return true if enough time has passed and `updateILVPerBlock` can be executed
                   */
                  function shouldUpdateRatio() public view returns (bool) {
                      // if yield farming period has ended
                      if (blockNumber() > endBlock) {
                          // ILV/block reward cannot be updated anymore
                          return false;
                      }
                      // check if blocks/update (91252 blocks) have passed since last update
                      return blockNumber() >= lastRatioUpdate + blocksPerUpdate;
                  }
                  /**
                   * @dev Creates a core pool (IlluviumCorePool) and registers it within the factory
                   *
                   * @dev Can be executed by the pool factory owner only
                   *
                   * @param poolToken pool token address (like ILV, or ILV/ETH pair)
                   * @param initBlock init block to be used for the pool created
                   * @param weight weight of the pool to be created
                   */
                  function createPool(
                      address poolToken,
                      uint64 initBlock,
                      uint32 weight
                  ) external virtual onlyOwner {
                      // create/deploy new core pool instance
                      IPool pool = new IlluviumCorePool(ilv, silv, this, poolToken, initBlock, weight);
                      // register it within a factory
                      registerPool(address(pool));
                  }
                  /**
                   * @dev Registers an already deployed pool instance within the factory
                   *
                   * @dev Can be executed by the pool factory owner only
                   *
                   * @param poolAddr address of the already deployed pool instance
                   */
                  function registerPool(address poolAddr) public onlyOwner {
                      // read pool information from the pool smart contract
                      // via the pool interface (IPool)
                      address poolToken = IPool(poolAddr).poolToken();
                      bool isFlashPool = IPool(poolAddr).isFlashPool();
                      uint32 weight = IPool(poolAddr).weight();
                      // ensure that the pool is not already registered within the factory
                      require(pools[poolToken] == address(0), "this pool is already registered");
                      // create pool structure, register it within the factory
                      pools[poolToken] = poolAddr;
                      poolExists[poolAddr] = true;
                      // update total pool weight of the factory
                      totalWeight += weight;
                      // emit an event
                      emit PoolRegistered(msg.sender, poolToken, poolAddr, weight, isFlashPool);
                  }
                  /**
                   * @notice Decreases ILV/block reward by 3%, can be executed
                   *      no more than once per `blocksPerUpdate` blocks
                   */
                  function updateILVPerBlock() external {
                      // checks if ratio can be updated i.e. if blocks/update (91252 blocks) have passed
                      require(shouldUpdateRatio(), "too frequent");
                      // decreases ILV/block reward by 3%
                      ilvPerBlock = (ilvPerBlock * 97) / 100;
                      // set current block as the last ratio update block
                      lastRatioUpdate = uint32(blockNumber());
                      // emit an event
                      emit IlvRatioUpdated(msg.sender, ilvPerBlock);
                  }
                  /**
                   * @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 _amount amount of ILV tokens to mint
                   */
                  function mintYieldTo(address _to, uint256 _amount) external {
                      // verify that sender is a pool registered withing the factory
                      require(poolExists[msg.sender], "access denied");
                      // mint ILV tokens as required
                      mintIlv(_to, _amount);
                  }
                  /**
                   * @dev Changes the weight of the pool;
                   *      executed by the pool itself or by the factory owner
                   *
                   * @param poolAddr address of the pool to change weight for
                   * @param weight new weight value to set to
                   */
                  function changePoolWeight(address poolAddr, uint32 weight) external {
                      // verify function is executed either by factory owner or by the pool itself
                      require(msg.sender == owner() || poolExists[msg.sender]);
                      // recalculate total weight
                      totalWeight = totalWeight + weight - IPool(poolAddr).weight();
                      // set the new pool weight
                      IPool(poolAddr).setWeight(weight);
                      // emit an event
                      emit WeightUpdated(msg.sender, poolAddr, weight);
                  }
                  /**
                   * @dev Testing time-dependent functionality is difficult and the best way of
                   *      doing it is to override block number in helper test smart contracts
                   *
                   * @return `block.number` in mainnet, custom values in testnets (if overridden)
                   */
                  function blockNumber() public view virtual returns (uint256) {
                      // return current block number
                      return block.number;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "../interfaces/IERC20.sol";
              import "./Address.sol";
              /**
               * @title SafeERC20
               * @dev Wrappers around ERC20 operations that throw on failure (when the token
               * contract returns false). Tokens that return no value (and instead revert or
               * throw on failure) are also supported, non-reverting calls are assumed to be
               * successful.
               * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
               * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
               */
              library SafeERC20 {
                  using Address for address;
                  function safeTransfer(
                      IERC20 token,
                      address to,
                      uint256 value
                  ) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                  }
                  function safeTransferFrom(
                      IERC20 token,
                      address from,
                      address to,
                      uint256 value
                  ) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                  }
                  /**
                   * @dev Deprecated. This function has issues similar to the ones found in
                   * {IERC20-approve}, and its usage is discouraged.
                   *
                   * Whenever possible, use {safeIncreaseAllowance} and
                   * {safeDecreaseAllowance} instead.
                   */
                  function safeApprove(
                      IERC20 token,
                      address spender,
                      uint256 value
                  ) internal {
                      // safeApprove should only be called when setting an initial allowance,
                      // or when resetting it to zero. To increase and decrease it, use
                      // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                      // solhint-disable-next-line max-line-length
                      require(
                          (value == 0) || (token.allowance(address(this), spender) == 0),
                          "SafeERC20: approve from non-zero to non-zero allowance"
                      );
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                  }
                  function safeIncreaseAllowance(
                      IERC20 token,
                      address spender,
                      uint256 value
                  ) internal {
                      uint256 newAllowance = token.allowance(address(this), spender) + value;
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
                  function safeDecreaseAllowance(
                      IERC20 token,
                      address spender,
                      uint256 value
                  ) internal {
                      uint256 newAllowance = token.allowance(address(this), spender) - value;
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   * @param token The token targeted by the call.
                   * @param data The call data (encoded using abi.encode or one of its variants).
                   */
                  function _callOptionalReturn(IERC20 token, bytes memory data) private {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                      // the target address contains contract code and also asserts for success in the low-level call.
                      bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                      if (returndata.length > 0) {
                          // Return data is optional
                          // solhint-disable-next-line max-line-length
                          require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "../utils/ERC20.sol";
              import "../utils/AccessControl.sol";
              contract EscrowedIlluviumERC20 is ERC20("Escrowed Illuvium", "sILV"), 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 = 0xac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d62;
                /**
                 * @notice Must be called by ROLE_TOKEN_CREATOR addresses.
                 *
                 * @param recipient address to receive the tokens.
                 * @param amount number of tokens to be minted.
                 */
                function mint(address recipient, uint256 amount) external {
                  require(isSenderInRole(ROLE_TOKEN_CREATOR), "insufficient privileges (ROLE_TOKEN_CREATOR required)");
                  _mint(recipient, amount);
                }
                /**
                 * @param amount number of tokens to be burned.
                 */
                function burn(uint256 amount) external {
                  _burn(msg.sender, amount);
                }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              /**
               * @title Linked to ILV Marker Interface
               *
               * @notice Marks smart contracts which are linked to IlluviumERC20 token instance upon construction,
               *      all these smart contracts share a common ilv() address getter
               *
               * @notice Implementing smart contracts MUST verify that they get linked to real IlluviumERC20 instance
               *      and that ilv() getter returns this very same instance address
               *
               * @author Basil Gorin
               */
              interface ILinkedToILV {
                /**
                 * @notice Getter for a verified IlluviumERC20 instance address
                 *
                 * @return IlluviumERC20 token instance address smart contract is linked to
                 */
                function ilv() external view returns (address);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              import "../token/IlluviumERC20.sol";
              import "../interfaces/ILinkedToILV.sol";
              /**
               * @title Illuvium Aware
               *
               * @notice Helper smart contract to be inherited by other smart contracts requiring to
               *      be linked to verified IlluviumERC20 instance and performing some basic tasks on it
               *
               * @author Basil Gorin
               */
              abstract contract IlluviumAware is ILinkedToILV {
                /// @dev Link to ILV ERC20 Token IlluviumERC20 instance
                address public immutable override ilv;
                /**
                 * @dev Creates IlluviumAware instance, requiring to supply deployed IlluviumERC20 instance address
                 *
                 * @param _ilv deployed IlluviumERC20 instance address
                 */
                constructor(address _ilv) {
                  // verify ILV address is set and is correct
                  require(_ilv != address(0), "ILV address not set");
                  require(IlluviumERC20(_ilv).TOKEN_UID() == 0x83ecb176af7c4f35a45ff0018282e3a05a1018065da866182df12285866f5a2c, "unexpected TOKEN_UID");
                  // write ILV address
                  ilv = _ilv;
                }
                /**
                 * @dev Executes IlluviumERC20.safeTransferFrom(address(this), _to, _value, "")
                 *      on the bound IlluviumERC20 instance
                 *
                 * @dev Reentrancy safe due to the IlluviumERC20 design
                 */
                function transferIlv(address _to, uint256 _value) internal {
                  // just delegate call to the target
                  transferIlvFrom(address(this), _to, _value);
                }
                /**
                 * @dev Executes IlluviumERC20.transferFrom(_from, _to, _value)
                 *      on the bound IlluviumERC20 instance
                 *
                 * @dev Reentrancy safe due to the IlluviumERC20 design
                 */
                function transferIlvFrom(address _from, address _to, uint256 _value) internal {
                  // just delegate call to the target
                  IlluviumERC20(ilv).transferFrom(_from, _to, _value);
                }
                /**
                 * @dev Executes IlluviumERC20.mint(_to, _values)
                 *      on the bound IlluviumERC20 instance
                 *
                 * @dev Reentrancy safe due to the IlluviumERC20 design
                 */
                function mintIlv(address _to, uint256 _value) internal {
                  // just delegate call to the target
                  IlluviumERC20(ilv).mint(_to, _value);
                }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              /**
               * @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 {
                  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 = msg.sender;
                      _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() == msg.sender, "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;
                  }
              }
              // 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 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;
                }
              }
              // 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 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 "../interfaces/IERC20.sol";
              /**
               * @dev Implementation of the {IERC20} interface.
               *
               * This implementation is agnostic to the way tokens are created. This means
               * that a supply mechanism has to be added in a derived contract using {_mint}.
               * For a generic mechanism see {ERC20PresetMinterPauser}.
               *
               * TIP: For a detailed writeup see our guide
               * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
               * to implement supply mechanisms].
               *
               * We have followed general OpenZeppelin guidelines: functions revert instead
               * of returning `false` on failure. This behavior is nonetheless conventional
               * and does not conflict with the expectations of ERC20 applications.
               *
               * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
               * This allows applications to reconstruct the allowance for all accounts just
               * by listening to said events. Other implementations of the EIP may not emit
               * these events, as it isn't required by the specification.
               *
               * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
               * functions have been added to mitigate the well-known issues around setting
               * allowances. See {IERC20-approve}.
               */
              // Copied from Open Zeppelin
              contract ERC20 is IERC20 {
                  mapping(address => uint256) private _balances;
                  mapping(address => mapping(address => uint256)) private _allowances;
                  uint256 private _totalSupply;
                  string private _name;
                  string private _symbol;
                  uint8 private _decimals;
                  /**
                   * @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;
                  /**
                   * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
                   * a default value of 18.
                   *
                   * To select a different value for {decimals}, use {_setupDecimals}.
                   *
                   * All three of these values are immutable: they can only be set once during
                   * construction.
                   */
                  constructor(string memory name_, string memory symbol_) {
                      _name = name_;
                      _symbol = symbol_;
                      _decimals = 18;
                  }
                  /**
                   * @dev Returns the name of the token.
                   */
                  function name() public view virtual returns (string memory) {
                      return _name;
                  }
                  /**
                   * @dev Returns the symbol of the token, usually a shorter version of the
                   * name.
                   */
                  function symbol() public view virtual returns (string memory) {
                      return _symbol;
                  }
                  /**
                   * @dev Returns the number of decimals used to get its user representation.
                   * For example, if `decimals` equals `2`, a balance of `505` tokens should
                   * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                   *
                   * Tokens usually opt for a value of 18, imitating the relationship between
                   * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
                   * called.
                   *
                   * NOTE: This information is only used for _display_ purposes: it in
                   * no way affects any of the arithmetic of the contract, including
                   * {IERC20-balanceOf} and {IERC20-transfer}.
                   */
                  function decimals() public view virtual returns (uint8) {
                      return _decimals;
                  }
                  /**
                   * @dev See {IERC20-totalSupply}.
                   */
                  function totalSupply() public view virtual override returns (uint256) {
                      return _totalSupply;
                  }
                  /**
                   * @dev See {IERC20-balanceOf}.
                   */
                  function balanceOf(address account) public view virtual override returns (uint256) {
                      return _balances[account];
                  }
                  /**
                   * @dev See {IERC20-transfer}.
                   *
                   * Requirements:
                   *
                   * - `recipient` cannot be the zero address.
                   * - the caller must have a balance of at least `amount`.
                   */
                  function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                      _transfer(msg.sender, recipient, amount);
                      return true;
                  }
                  /**
                   * @dev See {IERC20-allowance}.
                   */
                  function allowance(address owner, address spender) public view virtual override returns (uint256) {
                      return _allowances[owner][spender];
                  }
                  /**
                   * @dev See {IERC20-approve}.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function approve(address spender, uint256 amount) public virtual override returns (bool) {
                      _approve(msg.sender, spender, amount);
                      return true;
                  }
                  /**
                   * @dev See {IERC20-transferFrom}.
                   *
                   * Emits an {Approval} event indicating the updated allowance. This is not
                   * required by the EIP. See the note at the beginning of {ERC20}.
                   *
                   * Requirements:
                   *
                   * - `sender` and `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   * - the caller must have allowance for ``sender``'s tokens of at least
                   * `amount`.
                   */
                  function transferFrom(
                      address sender,
                      address recipient,
                      uint256 amount
                  ) public virtual override returns (bool) {
                      _transfer(sender, recipient, amount);
                      _approve(sender, msg.sender, _allowances[sender][msg.sender] - amount);
                      return true;
                  }
                  /**
                   * @dev Atomically increases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   */
                  function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                      _approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
                      return true;
                  }
                  /**
                   * @dev Atomically decreases the allowance granted to `spender` by the caller.
                   *
                   * This is an alternative to {approve} that can be used as a mitigation for
                   * problems described in {IERC20-approve}.
                   *
                   * Emits an {Approval} event indicating the updated allowance.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   * - `spender` must have allowance for the caller of at least
                   * `subtractedValue`.
                   */
                  function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                      _approve(msg.sender, spender, _allowances[msg.sender][spender] - subtractedValue);
                      return true;
                  }
                  /**
                   * @dev Moves tokens `amount` from `sender` to `recipient`.
                   *
                   * This is internal function is equivalent to {transfer}, and can be used to
                   * e.g. implement automatic token fees, slashing mechanisms, etc.
                   *
                   * Emits a {Transfer} event.
                   *
                   * Requirements:
                   *
                   * - `sender` cannot be the zero address.
                   * - `recipient` cannot be the zero address.
                   * - `sender` must have a balance of at least `amount`.
                   */
                  function _transfer(
                      address sender,
                      address recipient,
                      uint256 amount
                  ) internal virtual {
                      require(sender != address(0), "ERC20: transfer from the zero address");
                      require(recipient != address(0), "ERC20: transfer to the zero address");
                      _beforeTokenTransfer(sender, recipient, amount);
                      _balances[sender] = _balances[sender] - amount;
                      _balances[recipient] = _balances[recipient] + amount;
                      emit Transfer(sender, recipient, amount);
                  }
                  /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                   * the total supply.
                   *
                   * Emits a {Transfer} event with `from` set to the zero address.
                   *
                   * Requirements:
                   *
                   * - `to` cannot be the zero address.
                   */
                  function _mint(address account, uint256 amount) internal virtual {
                      require(account != address(0), "ERC20: mint to the zero address");
                      _beforeTokenTransfer(address(0), account, amount);
                      _totalSupply = _totalSupply + amount;
                      _balances[account] = _balances[account] + amount;
                      emit Transfer(address(0), account, amount);
                  }
                  /**
                   * @dev Destroys `amount` tokens from `account`, reducing the
                   * total supply.
                   *
                   * Emits a {Transfer} event with `to` set to the zero address.
                   *
                   * Requirements:
                   *
                   * - `account` cannot be the zero address.
                   * - `account` must have at least `amount` tokens.
                   */
                  function _burn(address account, uint256 amount) internal virtual {
                      require(account != address(0), "ERC20: burn from the zero address");
                      _beforeTokenTransfer(account, address(0), amount);
                      _balances[account] = _balances[account] - amount;
                      _totalSupply = _totalSupply - amount;
                      emit Transfer(account, address(0), amount);
                  }
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
                   *
                   * This internal function is equivalent to `approve`, and can be used to
                   * e.g. set automatic allowances for certain subsystems, etc.
                   *
                   * Emits an {Approval} event.
                   *
                   * Requirements:
                   *
                   * - `owner` cannot be the zero address.
                   * - `spender` cannot be the zero address.
                   */
                  function _approve(
                      address owner,
                      address spender,
                      uint256 amount
                  ) internal virtual {
                      require(owner != address(0), "ERC20: approve from the zero address");
                      require(spender != address(0), "ERC20: approve to the zero address");
                      _allowances[owner][spender] = amount;
                      emit Approval(owner, spender, amount);
                  }
                  /**
                   * @dev Sets {decimals} to a value other than the default one of 18.
                   *
                   * WARNING: This function should only be called from the constructor. Most
                   * applications that interact with token contracts will not expect
                   * {decimals} to ever change, and may work incorrectly if it does.
                   */
                  function _setupDecimals(uint8 decimals_) internal virtual {
                      _decimals = decimals_;
                  }
                  /**
                   * @dev Hook that is called before any transfer of tokens. This includes
                   * minting and burning.
                   *
                   * Calling conditions:
                   *
                   * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                   * will be to transferred to `to`.
                   * - when `from` is zero, `amount` tokens will be minted for `to`.
                   * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
                   * - `from` and `to` are never both zero.
                   *
                   * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                   */
                  function _beforeTokenTransfer(
                      address from,
                      address to,
                      uint256 amount
                  ) internal virtual {}
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, uint256 amount) external returns (bool);
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
                  /**
                   * @dev Moves `amount` tokens from `sender` to `recipient` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(
                      address sender,
                      address recipient,
                      uint256 amount
                  ) external returns (bool);
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.1;
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   * ====
                   */
                  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.3._
                   */
                  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.3._
                   */
                  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);
                          }
                      }
                  }
              }
              

              File 4 of 6: 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 6: 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);
              }
              

              File 6 of 6: 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;
                }
              }