creation_sourcecode: // Sources flattened with hardhat v2.6.2 https://hardhat.org
  
  // File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.3.2
  
  // SPDX-License-Identifier: MIT
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface of the ERC20 standard as defined in the EIP.
   */
  interface IERC20 {
      /**
       * @dev Returns the amount of tokens in existence.
       */
      function totalSupply() external view returns (uint256);
  
      /**
       * @dev Returns the amount of tokens owned by `account`.
       */
      function balanceOf(address account) external view returns (uint256);
  
      /**
       * @dev Moves `amount` tokens from the caller's account to `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 @openzeppelin/contracts/utils/math/Math.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Standard math utilities missing in the Solidity language.
   */
  library Math {
      /**
       * @dev Returns the largest of two numbers.
       */
      function max(uint256 a, uint256 b) internal pure returns (uint256) {
          return a >= b ? a : b;
      }
  
      /**
       * @dev Returns the smallest of two numbers.
       */
      function min(uint256 a, uint256 b) internal pure returns (uint256) {
          return a < b ? a : b;
      }
  
      /**
       * @dev Returns the average of two numbers. The result is rounded towards
       * zero.
       */
      function average(uint256 a, uint256 b) internal pure returns (uint256) {
          // (a + b) / 2 can overflow.
          return (a & b) + (a ^ b) / 2;
      }
  
      /**
       * @dev Returns the ceiling of the division of two numbers.
       *
       * This differs from standard division with `/` in that it rounds up instead
       * of rounding down.
       */
      function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
          // (a + b - 1) / b can overflow on addition, so we distribute.
          return a / b + (a % b == 0 ? 0 : 1);
      }
  }
  
  
  // File @openzeppelin/contracts/utils/Address.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Collection of functions related to the address type
   */
  library Address {
      /**
       * @dev Returns true if `account` is a contract.
       *
       * [IMPORTANT]
       * ====
       * It is unsafe to assume that an address for which this function returns
       * false is an externally-owned account (EOA) and not a contract.
       *
       * Among others, `isContract` will return false for the following
       * types of addresses:
       *
       *  - an externally-owned account
       *  - a contract in construction
       *  - an address where a contract will be created
       *  - an address where a contract lived, but was destroyed
       * ====
       */
      function isContract(address account) internal view returns (bool) {
          // This method relies on extcodesize, which returns 0 for contracts in
          // construction, since the code is only stored at the end of the
          // constructor execution.
  
          uint256 size;
          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 Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
       * but performing a delegate call.
       *
       * _Available since v3.4._
       */
      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
      }
  
      /**
       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
       * but performing a delegate call.
       *
       * _Available since v3.4._
       */
      function functionDelegateCall(
          address target,
          bytes memory data,
          string memory errorMessage
      ) internal returns (bytes memory) {
          require(isContract(target), "Address: delegate call to non-contract");
  
          (bool success, bytes memory returndata) = target.delegatecall(data);
          return verifyCallResult(success, returndata, errorMessage);
      }
  
      /**
       * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
       * revert reason using the provided one.
       *
       * _Available since v4.3._
       */
      function verifyCallResult(
          bool success,
          bytes memory returndata,
          string memory errorMessage
      ) internal pure returns (bytes memory) {
          if (success) {
              return returndata;
          } else {
              // Look for revert reason and bubble it up if present
              if (returndata.length > 0) {
                  // The easiest way to bubble the revert reason is using memory via assembly
  
                  assembly {
                      let returndata_size := mload(returndata)
                      revert(add(32, returndata), returndata_size)
                  }
              } else {
                  revert(errorMessage);
              }
          }
      }
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  
  /**
   * @title SafeERC20
   * @dev Wrappers around ERC20 operations that throw on failure (when the token
   * contract returns false). Tokens that return no value (and instead revert or
   * throw on failure) are also supported, non-reverting calls are assumed to be
   * successful.
   * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
   * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
   */
  library SafeERC20 {
      using Address for address;
  
      function safeTransfer(
          IERC20 token,
          address to,
          uint256 value
      ) internal {
          _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
      }
  
      function safeTransferFrom(
          IERC20 token,
          address from,
          address to,
          uint256 value
      ) internal {
          _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
      }
  
      /**
       * @dev Deprecated. This function has issues similar to the ones found in
       * {IERC20-approve}, and its usage is discouraged.
       *
       * Whenever possible, use {safeIncreaseAllowance} and
       * {safeDecreaseAllowance} instead.
       */
      function safeApprove(
          IERC20 token,
          address spender,
          uint256 value
      ) internal {
          // safeApprove should only be called when setting an initial allowance,
          // or when resetting it to zero. To increase and decrease it, use
          // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
          require(
              (value == 0) || (token.allowance(address(this), spender) == 0),
              "SafeERC20: approve from non-zero to non-zero allowance"
          );
          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
      }
  
      function safeIncreaseAllowance(
          IERC20 token,
          address spender,
          uint256 value
      ) internal {
          uint256 newAllowance = token.allowance(address(this), spender) + value;
          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
      }
  
      function safeDecreaseAllowance(
          IERC20 token,
          address spender,
          uint256 value
      ) internal {
          unchecked {
              uint256 oldAllowance = token.allowance(address(this), spender);
              require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
              uint256 newAllowance = oldAllowance - value;
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
      }
  
      /**
       * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
       * on the return value: the return value is optional (but if data is returned, it must not be false).
       * @param token The token targeted by the call.
       * @param data The call data (encoded using abi.encode or one of its variants).
       */
      function _callOptionalReturn(IERC20 token, bytes memory data) private {
          // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
          // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
          // the target address contains contract code and also asserts for success in the low-level call.
  
          bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
          if (returndata.length > 0) {
              // Return data is optional
              require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
          }
      }
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
   * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
   *
   * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
   * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
   * need to send a transaction, and thus is not required to hold Ether at all.
   */
  interface IERC20Permit {
      /**
       * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
       * given ``owner``'s signed approval.
       *
       * IMPORTANT: The same issues {IERC20-approve} has related to transaction
       * ordering also apply here.
       *
       * Emits an {Approval} event.
       *
       * Requirements:
       *
       * - `spender` cannot be the zero address.
       * - `deadline` must be a timestamp in the future.
       * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
       * over the EIP712-formatted function arguments.
       * - the signature must use ``owner``'s current nonce (see {nonces}).
       *
       * For more information on the signature format, see the
       * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
       * section].
       */
      function permit(
          address owner,
          address spender,
          uint256 value,
          uint256 deadline,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) external;
  
      /**
       * @dev Returns the current nonce for `owner`. This value must be
       * included whenever a signature is generated for {permit}.
       *
       * Every successful call to {permit} increases ``owner``'s nonce by one. This
       * prevents a signature from being used multiple times.
       */
      function nonces(address owner) external view returns (uint256);
  
      /**
       * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
       */
      // solhint-disable-next-line func-name-mixedcase
      function DOMAIN_SEPARATOR() external view returns (bytes32);
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface for the optional metadata functions from the ERC20 standard.
   *
   * _Available since v4.1._
   */
  interface IERC20Metadata is IERC20 {
      /**
       * @dev Returns the name of the token.
       */
      function name() external view returns (string memory);
  
      /**
       * @dev Returns the symbol of the token.
       */
      function symbol() external view returns (string memory);
  
      /**
       * @dev Returns the decimals places of the token.
       */
      function decimals() external view returns (uint8);
  }
  
  
  // File @openzeppelin/contracts/utils/Context.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Provides information about the current execution context, including the
   * sender of the transaction and its data. While these are generally available
   * via msg.sender and msg.data, they should not be accessed in such a direct
   * manner, since when dealing with meta-transactions the account sending and
   * paying for execution may not be the actual sender (as far as an application
   * is concerned).
   *
   * This contract is only required for intermediate, library-like contracts.
   */
  abstract contract Context {
      function _msgSender() internal view virtual returns (address) {
          return msg.sender;
      }
  
      function _msgData() internal view virtual returns (bytes calldata) {
          return msg.data;
      }
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/ERC20.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  
  
  /**
   * @dev Implementation of the {IERC20} interface.
   *
   * This implementation is agnostic to the way tokens are created. This means
   * that a supply mechanism has to be added in a derived contract using {_mint}.
   * For a generic mechanism see {ERC20PresetMinterPauser}.
   *
   * TIP: For a detailed writeup see our guide
   * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
   * to implement supply mechanisms].
   *
   * We have followed general OpenZeppelin Contracts guidelines: functions revert
   * instead returning `false` on failure. This behavior is nonetheless
   * conventional and does not conflict with the expectations of ERC20
   * applications.
   *
   * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
   * This allows applications to reconstruct the allowance for all accounts just
   * by listening to said events. Other implementations of the EIP may not emit
   * these events, as it isn't required by the specification.
   *
   * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
   * functions have been added to mitigate the well-known issues around setting
   * allowances. See {IERC20-approve}.
   */
  contract ERC20 is Context, IERC20, IERC20Metadata {
      mapping(address => uint256) private _balances;
  
      mapping(address => mapping(address => uint256)) private _allowances;
  
      uint256 private _totalSupply;
  
      string private _name;
      string private _symbol;
  
      /**
       * @dev Sets the values for {name} and {symbol}.
       *
       * The default value of {decimals} is 18. To select a different value for
       * {decimals} you should overload it.
       *
       * All two of these values are immutable: they can only be set once during
       * construction.
       */
      constructor(string memory name_, string memory symbol_) {
          _name = name_;
          _symbol = symbol_;
      }
  
      /**
       * @dev Returns the name of the token.
       */
      function name() public view virtual override returns (string memory) {
          return _name;
      }
  
      /**
       * @dev Returns the symbol of the token, usually a shorter version of the
       * name.
       */
      function symbol() public view virtual override returns (string memory) {
          return _symbol;
      }
  
      /**
       * @dev Returns the number of decimals used to get its user representation.
       * For example, if `decimals` equals `2`, a balance of `505` tokens should
       * be displayed to a user as `5.05` (`505 / 10 ** 2`).
       *
       * Tokens usually opt for a value of 18, imitating the relationship between
       * Ether and Wei. This is the value {ERC20} uses, unless this function is
       * overridden;
       *
       * NOTE: This information is only used for _display_ purposes: it in
       * no way affects any of the arithmetic of the contract, including
       * {IERC20-balanceOf} and {IERC20-transfer}.
       */
      function decimals() public view virtual override returns (uint8) {
          return 18;
      }
  
      /**
       * @dev See {IERC20-totalSupply}.
       */
      function totalSupply() public view virtual override returns (uint256) {
          return _totalSupply;
      }
  
      /**
       * @dev See {IERC20-balanceOf}.
       */
      function balanceOf(address account) public view virtual override returns (uint256) {
          return _balances[account];
      }
  
      /**
       * @dev See {IERC20-transfer}.
       *
       * Requirements:
       *
       * - `recipient` cannot be the zero address.
       * - the caller must have a balance of at least `amount`.
       */
      function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
          _transfer(_msgSender(), recipient, amount);
          return true;
      }
  
      /**
       * @dev See {IERC20-allowance}.
       */
      function allowance(address owner, address spender) public view virtual override returns (uint256) {
          return _allowances[owner][spender];
      }
  
      /**
       * @dev See {IERC20-approve}.
       *
       * Requirements:
       *
       * - `spender` cannot be the zero address.
       */
      function approve(address spender, uint256 amount) public virtual override returns (bool) {
          _approve(_msgSender(), spender, amount);
          return true;
      }
  
      /**
       * @dev See {IERC20-transferFrom}.
       *
       * Emits an {Approval} event indicating the updated allowance. This is not
       * required by the EIP. See the note at the beginning of {ERC20}.
       *
       * Requirements:
       *
       * - `sender` and `recipient` cannot be the zero address.
       * - `sender` must have a balance of at least `amount`.
       * - the caller must have allowance for ``sender``'s tokens of at least
       * `amount`.
       */
      function transferFrom(
          address sender,
          address recipient,
          uint256 amount
      ) public virtual override returns (bool) {
          _transfer(sender, recipient, amount);
  
          uint256 currentAllowance = _allowances[sender][_msgSender()];
          require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
          unchecked {
              _approve(sender, _msgSender(), currentAllowance - 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(_msgSender(), spender, _allowances[_msgSender()][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) {
          uint256 currentAllowance = _allowances[_msgSender()][spender];
          require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
          unchecked {
              _approve(_msgSender(), spender, currentAllowance - subtractedValue);
          }
  
          return true;
      }
  
      /**
       * @dev Moves `amount` of tokens from `sender` to `recipient`.
       *
       * This internal function is equivalent to {transfer}, and can be used to
       * e.g. implement automatic token fees, slashing mechanisms, etc.
       *
       * Emits a {Transfer} event.
       *
       * Requirements:
       *
       * - `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);
  
          uint256 senderBalance = _balances[sender];
          require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
          unchecked {
              _balances[sender] = senderBalance - amount;
          }
          _balances[recipient] += amount;
  
          emit Transfer(sender, recipient, amount);
  
          _afterTokenTransfer(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:
       *
       * - `account` cannot be the zero address.
       */
      function _mint(address account, uint256 amount) internal virtual {
          require(account != address(0), "ERC20: mint to the zero address");
  
          _beforeTokenTransfer(address(0), account, amount);
  
          _totalSupply += amount;
          _balances[account] += amount;
          emit Transfer(address(0), account, amount);
  
          _afterTokenTransfer(address(0), account, amount);
      }
  
      /**
       * @dev Destroys `amount` tokens from `account`, reducing the
       * total supply.
       *
       * Emits a {Transfer} event with `to` set to the zero address.
       *
       * Requirements:
       *
       * - `account` cannot be the zero address.
       * - `account` must have at least `amount` tokens.
       */
      function _burn(address account, uint256 amount) internal virtual {
          require(account != address(0), "ERC20: burn from the zero address");
  
          _beforeTokenTransfer(account, address(0), amount);
  
          uint256 accountBalance = _balances[account];
          require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
          unchecked {
              _balances[account] = accountBalance - amount;
          }
          _totalSupply -= amount;
  
          emit Transfer(account, address(0), amount);
  
          _afterTokenTransfer(account, address(0), amount);
      }
  
      /**
       * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
       *
       * This internal function is equivalent to `approve`, and can be used to
       * e.g. set automatic allowances for certain subsystems, etc.
       *
       * Emits an {Approval} event.
       *
       * Requirements:
       *
       * - `owner` cannot be the zero address.
       * - `spender` cannot be the zero address.
       */
      function _approve(
          address owner,
          address spender,
          uint256 amount
      ) internal virtual {
          require(owner != address(0), "ERC20: approve from the zero address");
          require(spender != address(0), "ERC20: approve to the zero address");
  
          _allowances[owner][spender] = amount;
          emit Approval(owner, spender, amount);
      }
  
      /**
       * @dev Hook that is called before any transfer of tokens. This includes
       * minting and burning.
       *
       * Calling conditions:
       *
       * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
       * will be transferred to `to`.
       * - when `from` is zero, `amount` tokens will be minted for `to`.
       * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
       * - `from` and `to` are never both zero.
       *
       * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
       */
      function _beforeTokenTransfer(
          address from,
          address to,
          uint256 amount
      ) internal virtual {}
  
      /**
       * @dev Hook that is called after any transfer of tokens. This includes
       * minting and burning.
       *
       * Calling conditions:
       *
       * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
       * has been transferred to `to`.
       * - when `from` is zero, `amount` tokens have been minted for `to`.
       * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
       * - `from` and `to` are never both zero.
       *
       * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
       */
      function _afterTokenTransfer(
          address from,
          address to,
          uint256 amount
      ) internal virtual {}
  }
  
  
  // File @openzeppelin/contracts/utils/cryptography/ECDSA.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
   *
   * These functions can be used to verify that a message was signed by the holder
   * of the private keys of a given address.
   */
  library ECDSA {
      enum RecoverError {
          NoError,
          InvalidSignature,
          InvalidSignatureLength,
          InvalidSignatureS,
          InvalidSignatureV
      }
  
      function _throwError(RecoverError error) private pure {
          if (error == RecoverError.NoError) {
              return; // no error: do nothing
          } else if (error == RecoverError.InvalidSignature) {
              revert("ECDSA: invalid signature");
          } else if (error == RecoverError.InvalidSignatureLength) {
              revert("ECDSA: invalid signature length");
          } else if (error == RecoverError.InvalidSignatureS) {
              revert("ECDSA: invalid signature 's' value");
          } else if (error == RecoverError.InvalidSignatureV) {
              revert("ECDSA: invalid signature 'v' value");
          }
      }
  
      /**
       * @dev Returns the address that signed a hashed message (`hash`) with
       * `signature` or error string. This address can then be used for verification purposes.
       *
       * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
       * this function rejects them by requiring the `s` value to be in the lower
       * half order, and the `v` value to be either 27 or 28.
       *
       * IMPORTANT: `hash` _must_ be the result of a hash operation for the
       * verification to be secure: it is possible to craft signatures that
       * recover to arbitrary addresses for non-hashed data. A safe way to ensure
       * this is by receiving a hash of the original message (which may otherwise
       * be too long), and then calling {toEthSignedMessageHash} on it.
       *
       * Documentation for signature generation:
       * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
       * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
       *
       * _Available since v4.3._
       */
      function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
          // Check the signature length
          // - case 65: r,s,v signature (standard)
          // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
          if (signature.length == 65) {
              bytes32 r;
              bytes32 s;
              uint8 v;
              // ecrecover takes the signature parameters, and the only way to get them
              // currently is to use assembly.
              assembly {
                  r := mload(add(signature, 0x20))
                  s := mload(add(signature, 0x40))
                  v := byte(0, mload(add(signature, 0x60)))
              }
              return tryRecover(hash, v, r, s);
          } else if (signature.length == 64) {
              bytes32 r;
              bytes32 vs;
              // ecrecover takes the signature parameters, and the only way to get them
              // currently is to use assembly.
              assembly {
                  r := mload(add(signature, 0x20))
                  vs := mload(add(signature, 0x40))
              }
              return tryRecover(hash, r, vs);
          } else {
              return (address(0), RecoverError.InvalidSignatureLength);
          }
      }
  
      /**
       * @dev Returns the address that signed a hashed message (`hash`) with
       * `signature`. This address can then be used for verification purposes.
       *
       * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
       * this function rejects them by requiring the `s` value to be in the lower
       * half order, and the `v` value to be either 27 or 28.
       *
       * IMPORTANT: `hash` _must_ be the result of a hash operation for the
       * verification to be secure: it is possible to craft signatures that
       * recover to arbitrary addresses for non-hashed data. A safe way to ensure
       * this is by receiving a hash of the original message (which may otherwise
       * be too long), and then calling {toEthSignedMessageHash} on it.
       */
      function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
          (address recovered, RecoverError error) = tryRecover(hash, signature);
          _throwError(error);
          return recovered;
      }
  
      /**
       * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
       *
       * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
       *
       * _Available since v4.3._
       */
      function tryRecover(
          bytes32 hash,
          bytes32 r,
          bytes32 vs
      ) internal pure returns (address, RecoverError) {
          bytes32 s;
          uint8 v;
          assembly {
              s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
              v := add(shr(255, vs), 27)
          }
          return tryRecover(hash, v, r, s);
      }
  
      /**
       * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
       *
       * _Available since v4.2._
       */
      function recover(
          bytes32 hash,
          bytes32 r,
          bytes32 vs
      ) internal pure returns (address) {
          (address recovered, RecoverError error) = tryRecover(hash, r, vs);
          _throwError(error);
          return recovered;
      }
  
      /**
       * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
       * `r` and `s` signature fields separately.
       *
       * _Available since v4.3._
       */
      function tryRecover(
          bytes32 hash,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) internal pure returns (address, RecoverError) {
          // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
          // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
          // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
          // signatures from current libraries generate a unique signature with an s-value in the lower half order.
          //
          // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
          // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
          // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
          // these malleable signatures as well.
          if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
              return (address(0), RecoverError.InvalidSignatureS);
          }
          if (v != 27 && v != 28) {
              return (address(0), RecoverError.InvalidSignatureV);
          }
  
          // If the signature is valid (and not malleable), return the signer address
          address signer = ecrecover(hash, v, r, s);
          if (signer == address(0)) {
              return (address(0), RecoverError.InvalidSignature);
          }
  
          return (signer, RecoverError.NoError);
      }
  
      /**
       * @dev Overload of {ECDSA-recover} that receives the `v`,
       * `r` and `s` signature fields separately.
       */
      function recover(
          bytes32 hash,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) internal pure returns (address) {
          (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
          _throwError(error);
          return recovered;
      }
  
      /**
       * @dev Returns an Ethereum Signed Message, created from a `hash`. This
       * produces hash corresponding to the one signed with the
       * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
       * JSON-RPC method as part of EIP-191.
       *
       * See {recover}.
       */
      function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
          // 32 is the length in bytes of hash,
          // enforced by the type signature above
          return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
      }
  
      /**
       * @dev Returns an Ethereum Signed Typed Data, created from a
       * `domainSeparator` and a `structHash`. This produces hash corresponding
       * to the one signed with the
       * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
       * JSON-RPC method as part of EIP-712.
       *
       * See {recover}.
       */
      function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
          return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
      }
  }
  
  
  // File @openzeppelin/contracts/utils/cryptography/draft-EIP712.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
   *
   * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
   * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
   * they need in their contracts using a combination of `abi.encode` and `keccak256`.
   *
   * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
   * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
   * ({_hashTypedDataV4}).
   *
   * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
   * the chain id to protect against replay attacks on an eventual fork of the chain.
   *
   * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
   * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
   *
   * _Available since v3.4._
   */
  abstract contract EIP712 {
      /* solhint-disable var-name-mixedcase */
      // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
      // invalidate the cached domain separator if the chain id changes.
      bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
      uint256 private immutable _CACHED_CHAIN_ID;
  
      bytes32 private immutable _HASHED_NAME;
      bytes32 private immutable _HASHED_VERSION;
      bytes32 private immutable _TYPE_HASH;
  
      /* solhint-enable var-name-mixedcase */
  
      /**
       * @dev Initializes the domain separator and parameter caches.
       *
       * The meaning of `name` and `version` is specified in
       * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
       *
       * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
       * - `version`: the current major version of the signing domain.
       *
       * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
       * contract upgrade].
       */
      constructor(string memory name, string memory version) {
          bytes32 hashedName = keccak256(bytes(name));
          bytes32 hashedVersion = keccak256(bytes(version));
          bytes32 typeHash = keccak256(
              "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
          );
          _HASHED_NAME = hashedName;
          _HASHED_VERSION = hashedVersion;
          _CACHED_CHAIN_ID = block.chainid;
          _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
          _TYPE_HASH = typeHash;
      }
  
      /**
       * @dev Returns the domain separator for the current chain.
       */
      function _domainSeparatorV4() internal view returns (bytes32) {
          if (block.chainid == _CACHED_CHAIN_ID) {
              return _CACHED_DOMAIN_SEPARATOR;
          } else {
              return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
          }
      }
  
      function _buildDomainSeparator(
          bytes32 typeHash,
          bytes32 nameHash,
          bytes32 versionHash
      ) private view returns (bytes32) {
          return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
      }
  
      /**
       * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
       * function returns the hash of the fully encoded EIP712 message for this domain.
       *
       * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
       *
       * ```solidity
       * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
       *     keccak256("Mail(address to,string contents)"),
       *     mailTo,
       *     keccak256(bytes(mailContents))
       * )));
       * address signer = ECDSA.recover(digest, signature);
       * ```
       */
      function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
          return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
      }
  }
  
  
  // File @openzeppelin/contracts/utils/Counters.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @title Counters
   * @author Matt Condon (@shrugs)
   * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
   * of elements in a mapping, issuing ERC721 ids, or counting request ids.
   *
   * Include with `using Counters for Counters.Counter;`
   */
  library Counters {
      struct Counter {
          // This variable should never be directly accessed by users of the library: interactions must be restricted to
          // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
          // this feature: see https://github.com/ethereum/solidity/issues/4637
          uint256 _value; // default: 0
      }
  
      function current(Counter storage counter) internal view returns (uint256) {
          return counter._value;
      }
  
      function increment(Counter storage counter) internal {
          unchecked {
              counter._value += 1;
          }
      }
  
      function decrement(Counter storage counter) internal {
          uint256 value = counter._value;
          require(value > 0, "Counter: decrement overflow");
          unchecked {
              counter._value = value - 1;
          }
      }
  
      function reset(Counter storage counter) internal {
          counter._value = 0;
      }
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol@v4.3.2
  
  pragma solidity ^0.8.25;
  
  
  
  
  
  /**
   * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
   * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
   *
   * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
   * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
   * need to send a transaction, and thus is not required to hold Ether at all.
   *
   * _Available since v3.4._
   */
  abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
      using Counters for Counters.Counter;
  
      mapping(address => Counters.Counter) private _nonces;
  
      // solhint-disable-next-line var-name-mixedcase
      bytes32 private immutable _PERMIT_TYPEHASH =
          keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
  
      /**
       * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
       *
       * It's a good idea to use the same `name` that is defined as the ERC20 token name.
       */
      constructor(string memory name) EIP712(name, "1") {}
  
      /**
       * @dev See {IERC20Permit-permit}.
       */
      function permit(
          address owner,
          address spender,
          uint256 value,
          uint256 deadline,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) public virtual override {
          require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
  
          bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
  
          bytes32 hash = _hashTypedDataV4(structHash);
  
          address signer = ECDSA.recover(hash, v, r, s);
          require(signer == owner, "ERC20Permit: invalid signature");
  
          _approve(owner, spender, value);
      }
  
      /**
       * @dev See {IERC20Permit-nonces}.
       */
      function nonces(address owner) public view virtual override returns (uint256) {
          return _nonces[owner].current();
      }
  
      /**
       * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
       */
      // solhint-disable-next-line func-name-mixedcase
      function DOMAIN_SEPARATOR() external view override returns (bytes32) {
          return _domainSeparatorV4();
      }
  
      /**
       * @dev "Consume a nonce": return the current value and increment.
       *
       * _Available since v4.1._
       */
      function _useNonce(address owner) internal virtual returns (uint256 current) {
          Counters.Counter storage nonce = _nonces[owner];
          current = nonce.current();
          nonce.increment();
      }
  }
  
  
  // File @openzeppelin/contracts/utils/math/SafeCast.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @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 {
      /**
       * @dev Returns the downcasted uint224 from uint256, reverting on
       * overflow (when the input is greater than largest uint224).
       *
       * Counterpart to Solidity's `uint224` operator.
       *
       * Requirements:
       *
       * - input must fit into 224 bits
       */
      function toUint224(uint256 value) internal pure returns (uint224) {
          require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
          return uint224(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) {
          require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
          return uint128(value);
      }
  
      /**
       * @dev Returns the downcasted uint96 from uint256, reverting on
       * overflow (when the input is greater than largest uint96).
       *
       * Counterpart to Solidity's `uint96` operator.
       *
       * Requirements:
       *
       * - input must fit into 96 bits
       */
      function toUint96(uint256 value) internal pure returns (uint96) {
          require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
          return uint96(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) {
          require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
          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) {
          require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
          return uint32(value);
      }
  
      /**
       * @dev Returns the downcasted uint16 from uint256, reverting on
       * overflow (when the input is greater than largest uint16).
       *
       * Counterpart to Solidity's `uint16` operator.
       *
       * Requirements:
       *
       * - input must fit into 16 bits
       */
      function toUint16(uint256 value) internal pure returns (uint16) {
          require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
          return uint16(value);
      }
  
      /**
       * @dev Returns the downcasted uint8 from uint256, reverting on
       * overflow (when the input is greater than largest uint8).
       *
       * Counterpart to Solidity's `uint8` operator.
       *
       * Requirements:
       *
       * - input must fit into 8 bits.
       */
      function toUint8(uint256 value) internal pure returns (uint8) {
          require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
          return uint8(value);
      }
  
      /**
       * @dev Converts a signed int256 into an unsigned uint256.
       *
       * Requirements:
       *
       * - input must be greater than or equal to 0.
       */
      function toUint256(int256 value) internal pure returns (uint256) {
          require(value >= 0, "SafeCast: value must be positive");
          return uint256(value);
      }
  
      /**
       * @dev Returns the downcasted int128 from int256, reverting on
       * overflow (when the input is less than smallest int128 or
       * greater than largest int128).
       *
       * Counterpart to Solidity's `int128` operator.
       *
       * Requirements:
       *
       * - input must fit into 128 bits
       *
       * _Available since v3.1._
       */
      function toInt128(int256 value) internal pure returns (int128) {
          require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
          return int128(value);
      }
  
      /**
       * @dev Returns the downcasted int64 from int256, reverting on
       * overflow (when the input is less than smallest int64 or
       * greater than largest int64).
       *
       * Counterpart to Solidity's `int64` operator.
       *
       * Requirements:
       *
       * - input must fit into 64 bits
       *
       * _Available since v3.1._
       */
      function toInt64(int256 value) internal pure returns (int64) {
          require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
          return int64(value);
      }
  
      /**
       * @dev Returns the downcasted int32 from int256, reverting on
       * overflow (when the input is less than smallest int32 or
       * greater than largest int32).
       *
       * Counterpart to Solidity's `int32` operator.
       *
       * Requirements:
       *
       * - input must fit into 32 bits
       *
       * _Available since v3.1._
       */
      function toInt32(int256 value) internal pure returns (int32) {
          require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
          return int32(value);
      }
  
      /**
       * @dev Returns the downcasted int16 from int256, reverting on
       * overflow (when the input is less than smallest int16 or
       * greater than largest int16).
       *
       * Counterpart to Solidity's `int16` operator.
       *
       * Requirements:
       *
       * - input must fit into 16 bits
       *
       * _Available since v3.1._
       */
      function toInt16(int256 value) internal pure returns (int16) {
          require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
          return int16(value);
      }
  
      /**
       * @dev Returns the downcasted int8 from int256, reverting on
       * overflow (when the input is less than smallest int8 or
       * greater than largest int8).
       *
       * Counterpart to Solidity's `int8` operator.
       *
       * Requirements:
       *
       * - input must fit into 8 bits.
       *
       * _Available since v3.1._
       */
      function toInt8(int256 value) internal pure returns (int8) {
          require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
          return int8(value);
      }
  
      /**
       * @dev Converts an unsigned uint256 into a signed int256.
       *
       * Requirements:
       *
       * - input must be less than or equal to maxInt256.
       */
      function toInt256(uint256 value) internal pure returns (int256) {
          // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
          require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
          return int256(value);
      }
  }
  
  
  // File @openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  
  
  
  /**
   * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's,
   * and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1.
   *
   * NOTE: If exact COMP compatibility is required, use the {ERC20VotesComp} variant of this module.
   *
   * This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either
   * by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting
   * power can be queried through the public accessors {getVotes} and {getPastVotes}.
   *
   * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it
   * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked.
   * Enabling self-delegation can easily be done by overriding the {delegates} function. Keep in mind however that this
   * will significantly increase the base gas cost of transfers.
   *
   * _Available since v4.2._
   */
  abstract contract ERC20Votes is ERC20Permit {
      struct Checkpoint {
          uint32 fromBlock;
          uint224 votes;
      }
  
      bytes32 private constant _DELEGATION_TYPEHASH =
          keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
  
      mapping(address => address) private _delegates;
      mapping(address => Checkpoint[]) private _checkpoints;
      Checkpoint[] private _totalSupplyCheckpoints;
  
      /**
       * @dev Emitted when an account changes their delegate.
       */
      event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
  
      /**
       * @dev Emitted when a token transfer or delegate change results in changes to an account's voting power.
       */
      event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
  
      /**
       * @dev Get the `pos`-th checkpoint for `account`.
       */
      function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) {
          return _checkpoints[account][pos];
      }
  
      /**
       * @dev Get number of checkpoints for `account`.
       */
      function numCheckpoints(address account) public view virtual returns (uint32) {
          return SafeCast.toUint32(_checkpoints[account].length);
      }
  
      /**
       * @dev Get the address `account` is currently delegating to.
       */
      function delegates(address account) public view virtual returns (address) {
          return _delegates[account];
      }
  
      /**
       * @dev Gets the current votes balance for `account`
       */
      function getVotes(address account) public view returns (uint256) {
          uint256 pos = _checkpoints[account].length;
          return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
      }
  
      /**
       * @dev Retrieve the number of votes for `account` at the end of `blockNumber`.
       *
       * Requirements:
       *
       * - `blockNumber` must have been already mined
       */
      function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
          require(blockNumber < block.number, "ERC20Votes: block not yet mined");
          return _checkpointsLookup(_checkpoints[account], blockNumber);
      }
  
      /**
       * @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances.
       * It is but NOT the sum of all the delegated votes!
       *
       * Requirements:
       *
       * - `blockNumber` must have been already mined
       */
      function getPastTotalSupply(uint256 blockNumber) public view returns (uint256) {
          require(blockNumber < block.number, "ERC20Votes: block not yet mined");
          return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
      }
  
      /**
       * @dev Lookup a value in a list of (sorted) checkpoints.
       */
      function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256) {
          // We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
          //
          // During the loop, the index of the wanted checkpoint remains in the range [low-1, high).
          // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant.
          // - If the middle checkpoint is after `blockNumber`, we look in [low, mid)
          // - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high)
          // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not
          // out of bounds (in which case we're looking too far in the past and the result is 0).
          // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is
          // past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out
          // the same.
          uint256 high = ckpts.length;
          uint256 low = 0;
          while (low < high) {
              uint256 mid = Math.average(low, high);
              if (ckpts[mid].fromBlock > blockNumber) {
                  high = mid;
              } else {
                  low = mid + 1;
              }
          }
  
          return high == 0 ? 0 : ckpts[high - 1].votes;
      }
  
      /**
       * @dev Delegate votes from the sender to `delegatee`.
       */
      function delegate(address delegatee) public virtual {
          return _delegate(_msgSender(), delegatee);
      }
  
      /**
       * @dev Delegates votes from signer to `delegatee`
       */
      function delegateBySig(
          address delegatee,
          uint256 nonce,
          uint256 expiry,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) public virtual {
          require(block.timestamp <= expiry, "ERC20Votes: signature expired");
          address signer = ECDSA.recover(
              _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))),
              v,
              r,
              s
          );
          require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
          return _delegate(signer, delegatee);
      }
  
      /**
       * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1).
       */
      function _maxSupply() internal view virtual returns (uint224) {
          return type(uint224).max;
      }
  
      /**
       * @dev Snapshots the totalSupply after it has been increased.
       */
      function _mint(address account, uint256 amount) internal virtual override {
          super._mint(account, amount);
          require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes");
  
          _writeCheckpoint(_totalSupplyCheckpoints, _add, amount);
      }
  
      /**
       * @dev Snapshots the totalSupply after it has been decreased.
       */
      function _burn(address account, uint256 amount) internal virtual override {
          super._burn(account, amount);
  
          _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount);
      }
  
      /**
       * @dev Move voting power when tokens are transferred.
       *
       * Emits a {DelegateVotesChanged} event.
       */
      function _afterTokenTransfer(
          address from,
          address to,
          uint256 amount
      ) internal virtual override {
          super._afterTokenTransfer(from, to, amount);
  
          _moveVotingPower(delegates(from), delegates(to), amount);
      }
  
      /**
       * @dev Change delegation for `delegator` to `delegatee`.
       *
       * Emits events {DelegateChanged} and {DelegateVotesChanged}.
       */
      function _delegate(address delegator, address delegatee) internal virtual {
          address currentDelegate = delegates(delegator);
          uint256 delegatorBalance = balanceOf(delegator);
          _delegates[delegator] = delegatee;
  
          emit DelegateChanged(delegator, currentDelegate, delegatee);
  
          _moveVotingPower(currentDelegate, delegatee, delegatorBalance);
      }
  
      function _moveVotingPower(
          address src,
          address dst,
          uint256 amount
      ) private {
          if (src != dst && amount > 0) {
              if (src != address(0)) {
                  (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount);
                  emit DelegateVotesChanged(src, oldWeight, newWeight);
              }
  
              if (dst != address(0)) {
                  (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], _add, amount);
                  emit DelegateVotesChanged(dst, oldWeight, newWeight);
              }
          }
      }
  
      function _writeCheckpoint(
          Checkpoint[] storage ckpts,
          function(uint256, uint256) view returns (uint256) op,
          uint256 delta
      ) private returns (uint256 oldWeight, uint256 newWeight) {
          uint256 pos = ckpts.length;
          oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
          newWeight = op(oldWeight, delta);
  
          if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
              ckpts[pos - 1].votes = SafeCast.toUint224(newWeight);
          } else {
              ckpts.push(Checkpoint({fromBlock: SafeCast.toUint32(block.number), votes: SafeCast.toUint224(newWeight)}));
          }
      }
  
      function _add(uint256 a, uint256 b) private pure returns (uint256) {
          return a + b;
      }
  
      function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
          return a - b;
      }
  }
  
  
  // File contracts/interfaces/IBasePool.sol
  
  pragma solidity 0.8.25;
  interface IBasePool {
      function distributeRewards(uint256 _amount) external;
  }
  
  
  // File contracts/interfaces/ITimeLockPool.sol
  
  pragma solidity 0.8.25;
  interface ITimeLockPool {
      function deposit(uint256 _amount, uint256 _duration, address _receiver) external;
  }
  
  
  // File contracts/interfaces/IAbstractRewards.sol
  
  pragma solidity 0.8.25;
  
  interface IAbstractRewards {
  	/**
  	 * @dev Returns the total amount of rewards a given address is able to withdraw.
  	 * @param account Address of a reward recipient
  	 * @return A uint256 representing the rewards `account` can withdraw
  	 */
  	function withdrawableRewardsOf(address account) external view returns (uint256);
  
    /**
  	 * @dev View the amount of funds that an address has withdrawn.
  	 * @param account The address of a token holder.
  	 * @return The amount of funds that `account` has withdrawn.
  	 */
  	function withdrawnRewardsOf(address account) external view returns (uint256);
  
  	/**
  	 * @dev View the amount of funds that an address has earned in total.
  	 * accumulativeFundsOf(account) = withdrawableRewardsOf(account) + withdrawnRewardsOf(account)
  	 * = (pointsPerShare * balanceOf(account) + pointsCorrection[account]) / POINTS_MULTIPLIER
  	 * @param account The address of a token holder.
  	 * @return The amount of funds that `account` has earned in total.
  	 */
  	function cumulativeRewardsOf(address account) external view returns (uint256);
  
  	/**
  	 * @dev This event emits when new funds are distributed
  	 * @param by the address of the sender who distributed funds
  	 * @param rewardsDistributed the amount of funds received for distribution
  	 */
  	event RewardsDistributed(address indexed by, uint256 rewardsDistributed);
  
  	/**
  	 * @dev This event emits when distributed funds are withdrawn by a token holder.
  	 * @param by the address of the receiver of funds
  	 * @param fundsWithdrawn the amount of funds that were withdrawn
  	 */
  	event RewardsWithdrawn(address indexed by, uint256 fundsWithdrawn);
  }
  
  
  // File contracts/base/AbstractRewards.sol
  
  pragma solidity 0.8.25;
  
  
  /**
   * @dev Based on: https://github.com/indexed-finance/dividends/blob/master/contracts/base/AbstractDividends.sol
   * Renamed dividends to rewards.
   * @dev (OLD) Many functions in this contract were taken from this repository:
   * https://github.com/atpar/funds-distribution-token/blob/master/contracts/FundsDistributionToken.sol
   * which is an example implementation of ERC 2222, the draft for which can be found at
   * https://github.com/atpar/funds-distribution-token/blob/master/EIP-DRAFT.md
   *
   * This contract has been substantially modified from the original and does not comply with ERC 2222.
   * Many functions were renamed as "rewards" rather than "funds" and the core functionality was separated
   * into this abstract contract which can be inherited by anything tracking ownership of reward shares.
   */
  abstract contract AbstractRewards is IAbstractRewards {
    using SafeCast for uint128;
    using SafeCast for uint256;
    using SafeCast for int256;
  
  /* ========  Constants  ======== */
    uint128 public constant POINTS_MULTIPLIER = type(uint128).max;
  
  /* ========  Internal Function References  ======== */
    function(address) view returns (uint256) private immutable getSharesOf;
    function() view returns (uint256) private immutable getTotalShares;
  
  /* ========  Storage  ======== */
    uint256 public pointsPerShare;
    mapping(address => int256) public pointsCorrection;
    mapping(address => uint256) public withdrawnRewards;
  
    constructor(
      function(address) view returns (uint256) getSharesOf_,
      function() view returns (uint256) getTotalShares_
    ) {
      getSharesOf = getSharesOf_;
      getTotalShares = getTotalShares_;
    }
  
  /* ========  Public View Functions  ======== */
    /**
     * @dev Returns the total amount of rewards a given address is able to withdraw.
     * @param _account Address of a reward recipient
     * @return A uint256 representing the rewards `account` can withdraw
     */
    function withdrawableRewardsOf(address _account) public view override returns (uint256) {
      return cumulativeRewardsOf(_account) - withdrawnRewards[_account];
    }
  
    /**
     * @notice View the amount of rewards that an address has withdrawn.
     * @param _account The address of a token holder.
     * @return The amount of rewards that `account` has withdrawn.
     */
    function withdrawnRewardsOf(address _account) public view override returns (uint256) {
      return withdrawnRewards[_account];
    }
  
    /**
     * @notice View the amount of rewards that an address has earned in total.
     * @dev accumulativeFundsOf(account) = withdrawableRewardsOf(account) + withdrawnRewardsOf(account)
     * = (pointsPerShare * balanceOf(account) + pointsCorrection[account]) / POINTS_MULTIPLIER
     * @param _account The address of a token holder.
     * @return The amount of rewards that `account` has earned in total.
     */
    function cumulativeRewardsOf(address _account) public view override returns (uint256) {
      return ((pointsPerShare * getSharesOf(_account)).toInt256() + pointsCorrection[_account]).toUint256() / POINTS_MULTIPLIER;
    }
  
  /* ========  Dividend Utility Functions  ======== */
  
    /** 
     * @notice Distributes rewards to token holders.
     * @dev It reverts if the total shares is 0.
     * It emits the `RewardsDistributed` event if the amount to distribute is greater than 0.
     * About undistributed rewards:
     *   In each distribution, there is a small amount which does not get distributed,
     *   which is `(amount * POINTS_MULTIPLIER) % totalShares()`.
     *   With a well-chosen `POINTS_MULTIPLIER`, the amount of funds that are not getting
     *   distributed in a distribution can be less than 1 (base unit).
     */
    function _distributeRewards(uint256 _amount) internal {
      uint256 shares = getTotalShares();
      require(shares > 0, "AbstractRewards._distributeRewards: total share supply is zero");
  
      if (_amount > 0) {
        pointsPerShare = pointsPerShare + (_amount * POINTS_MULTIPLIER / shares);
        emit RewardsDistributed(msg.sender, _amount);
      }
    }
  
    /**
     * @notice Prepares collection of owed rewards
     * @dev It emits a `RewardsWithdrawn` event if the amount of withdrawn rewards is
     * greater than 0.
     */
    function _prepareCollect(address _account) internal returns (uint256) {
      uint256 _withdrawableDividend = withdrawableRewardsOf(_account);
      if (_withdrawableDividend > 0) {
        withdrawnRewards[_account] = withdrawnRewards[_account] + _withdrawableDividend;
        emit RewardsWithdrawn(_account, _withdrawableDividend);
      }
      return _withdrawableDividend;
    }
  
    function _correctPointsForTransfer(address _from, address _to, uint256 _shares) internal {
      int256 _magCorrection = (pointsPerShare * _shares).toInt256();
      pointsCorrection[_from] = pointsCorrection[_from] + _magCorrection;
      pointsCorrection[_to] = pointsCorrection[_to] - _magCorrection;
    }
  
    /**
     * @dev Increases or decreases the points correction for `account` by
     * `shares*pointsPerShare`.
     */
    function _correctPoints(address _account, int256 _shares) internal {
      pointsCorrection[_account] = pointsCorrection[_account] + (_shares * (int256(pointsPerShare)));
    }
  }
  
  
  // File @openzeppelin/contracts/access/IAccessControl.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev External interface of AccessControl declared to support ERC165 detection.
   */
  interface IAccessControl {
      /**
       * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
       *
       * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
       * {RoleAdminChanged} not being emitted signaling this.
       *
       * _Available since v3.1._
       */
      event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
  
      /**
       * @dev Emitted when `account` is granted `role`.
       *
       * `sender` is the account that originated the contract call, an admin role
       * bearer except when using {AccessControl-_setupRole}.
       */
      event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
  
      /**
       * @dev Emitted when `account` is revoked `role`.
       *
       * `sender` is the account that originated the contract call:
       *   - if using `revokeRole`, it is the admin role bearer
       *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
       */
      event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
  
      /**
       * @dev Returns `true` if `account` has been granted `role`.
       */
      function hasRole(bytes32 role, address account) external view returns (bool);
  
      /**
       * @dev Returns the admin role that controls `role`. See {grantRole} and
       * {revokeRole}.
       *
       * To change a role's admin, use {AccessControl-_setRoleAdmin}.
       */
      function getRoleAdmin(bytes32 role) external view returns (bytes32);
  
      /**
       * @dev Grants `role` to `account`.
       *
       * If `account` had not been already granted `role`, emits a {RoleGranted}
       * event.
       *
       * Requirements:
       *
       * - the caller must have ``role``'s admin role.
       */
      function grantRole(bytes32 role, address account) external;
  
      /**
       * @dev Revokes `role` from `account`.
       *
       * If `account` had been granted `role`, emits a {RoleRevoked} event.
       *
       * Requirements:
       *
       * - the caller must have ``role``'s admin role.
       */
      function revokeRole(bytes32 role, address account) external;
  
      /**
       * @dev Revokes `role` from the calling account.
       *
       * Roles are often managed via {grantRole} and {revokeRole}: this function's
       * purpose is to provide a mechanism for accounts to lose their privileges
       * if they are compromised (such as when a trusted device is misplaced).
       *
       * If the calling account had been granted `role`, emits a {RoleRevoked}
       * event.
       *
       * Requirements:
       *
       * - the caller must be `account`.
       */
      function renounceRole(bytes32 role, address account) external;
  }
  
  
  // File @openzeppelin/contracts/access/IAccessControlEnumerable.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
   */
  interface IAccessControlEnumerable is IAccessControl {
      /**
       * @dev Returns one of the accounts that have `role`. `index` must be a
       * value between 0 and {getRoleMemberCount}, non-inclusive.
       *
       * Role bearers are not sorted in any particular way, and their ordering may
       * change at any point.
       *
       * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
       * you perform all queries on the same block. See the following
       * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
       * for more information.
       */
      function getRoleMember(bytes32 role, uint256 index) external view returns (address);
  
      /**
       * @dev Returns the number of accounts that have `role`. Can be used
       * together with {getRoleMember} to enumerate all bearers of a role.
       */
      function getRoleMemberCount(bytes32 role) external view returns (uint256);
  }
  
  
  // File @openzeppelin/contracts/utils/Strings.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev String operations.
   */
  library Strings {
      bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
  
      /**
       * @dev Converts a `uint256` to its ASCII `string` decimal representation.
       */
      function toString(uint256 value) internal pure returns (string memory) {
          // Inspired by OraclizeAPI's implementation - MIT licence
          // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
  
          if (value == 0) {
              return "0";
          }
          uint256 temp = value;
          uint256 digits;
          while (temp != 0) {
              digits++;
              temp /= 10;
          }
          bytes memory buffer = new bytes(digits);
          while (value != 0) {
              digits -= 1;
              buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
              value /= 10;
          }
          return string(buffer);
      }
  
      /**
       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
       */
      function toHexString(uint256 value) internal pure returns (string memory) {
          if (value == 0) {
              return "0x00";
          }
          uint256 temp = value;
          uint256 length = 0;
          while (temp != 0) {
              length++;
              temp >>= 8;
          }
          return toHexString(value, length);
      }
  
      /**
       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
       */
      function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
          bytes memory buffer = new bytes(2 * length + 2);
          buffer[0] = "0";
          buffer[1] = "x";
          for (uint256 i = 2 * length + 1; i > 1; --i) {
              buffer[i] = _HEX_SYMBOLS[value & 0xf];
              value >>= 4;
          }
          require(value == 0, "Strings: hex length insufficient");
          return string(buffer);
      }
  }
  
  
  // File @openzeppelin/contracts/utils/introspection/IERC165.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface of the ERC165 standard, as defined in the
   * https://eips.ethereum.org/EIPS/eip-165[EIP].
   *
   * Implementers can declare support of contract interfaces, which can then be
   * queried by others ({ERC165Checker}).
   *
   * For an implementation, see {ERC165}.
   */
  interface IERC165 {
      /**
       * @dev Returns true if this contract implements the interface defined by
       * `interfaceId`. See the corresponding
       * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
       * to learn more about how these ids are created.
       *
       * This function call must use less than 30 000 gas.
       */
      function supportsInterface(bytes4 interfaceId) external view returns (bool);
  }
  
  
  // File @openzeppelin/contracts/utils/introspection/ERC165.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Implementation of the {IERC165} interface.
   *
   * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
   * for the additional interface id that will be supported. For example:
   *
   * ```solidity
   * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
   *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
   * }
   * ```
   *
   * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
   */
  abstract contract ERC165 is IERC165 {
      /**
       * @dev See {IERC165-supportsInterface}.
       */
      function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
          return interfaceId == type(IERC165).interfaceId;
      }
  }
  
  
  // File @openzeppelin/contracts/access/AccessControl.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  
  
  
  /**
   * @dev Contract module that allows children to implement role-based access
   * control mechanisms. This is a lightweight version that doesn't allow enumerating role
   * members except through off-chain means by accessing the contract event logs. Some
   * applications may benefit from on-chain enumerability, for those cases see
   * {AccessControlEnumerable}.
   *
   * Roles are referred to by their `bytes32` identifier. These should be exposed
   * in the external API and be unique. The best way to achieve this is by
   * using `public constant` hash digests:
   *
   * ```
   * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
   * ```
   *
   * Roles can be used to represent a set of permissions. To restrict access to a
   * function call, use {hasRole}:
   *
   * ```
   * function foo() public {
   *     require(hasRole(MY_ROLE, msg.sender));
   *     ...
   * }
   * ```
   *
   * Roles can be granted and revoked dynamically via the {grantRole} and
   * {revokeRole} functions. Each role has an associated admin role, and only
   * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
   *
   * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
   * that only accounts with this role will be able to grant or revoke other
   * roles. More complex role relationships can be created by using
   * {_setRoleAdmin}.
   *
   * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
   * grant and revoke this role. Extra precautions should be taken to secure
   * accounts that have been granted it.
   */
  abstract contract AccessControl is Context, IAccessControl, ERC165 {
      struct RoleData {
          mapping(address => bool) members;
          bytes32 adminRole;
      }
  
      mapping(bytes32 => RoleData) private _roles;
  
      bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
  
      /**
       * @dev Modifier that checks that an account has a specific role. Reverts
       * with a standardized message including the required role.
       *
       * The format of the revert reason is given by the following regular expression:
       *
       *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
       *
       * _Available since v4.1._
       */
      modifier onlyRole(bytes32 role) {
          _checkRole(role, _msgSender());
          _;
      }
  
      /**
       * @dev See {IERC165-supportsInterface}.
       */
      function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
          return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
      }
  
      /**
       * @dev Returns `true` if `account` has been granted `role`.
       */
      function hasRole(bytes32 role, address account) public view override returns (bool) {
          return _roles[role].members[account];
      }
  
      /**
       * @dev Revert with a standard message if `account` is missing `role`.
       *
       * The format of the revert reason is given by the following regular expression:
       *
       *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
       */
      function _checkRole(bytes32 role, address account) internal view {
          if (!hasRole(role, account)) {
              revert(
                  string(
                      abi.encodePacked(
                          "AccessControl: account ",
                          Strings.toHexString(uint160(account), 20),
                          " is missing role ",
                          Strings.toHexString(uint256(role), 32)
                      )
                  )
              );
          }
      }
  
      /**
       * @dev Returns the admin role that controls `role`. See {grantRole} and
       * {revokeRole}.
       *
       * To change a role's admin, use {_setRoleAdmin}.
       */
      function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
          return _roles[role].adminRole;
      }
  
      /**
       * @dev Grants `role` to `account`.
       *
       * If `account` had not been already granted `role`, emits a {RoleGranted}
       * event.
       *
       * Requirements:
       *
       * - the caller must have ``role``'s admin role.
       */
      function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
          _grantRole(role, account);
      }
  
      /**
       * @dev Revokes `role` from `account`.
       *
       * If `account` had been granted `role`, emits a {RoleRevoked} event.
       *
       * Requirements:
       *
       * - the caller must have ``role``'s admin role.
       */
      function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
          _revokeRole(role, account);
      }
  
      /**
       * @dev Revokes `role` from the calling account.
       *
       * Roles are often managed via {grantRole} and {revokeRole}: this function's
       * purpose is to provide a mechanism for accounts to lose their privileges
       * if they are compromised (such as when a trusted device is misplaced).
       *
       * If the calling account had been granted `role`, emits a {RoleRevoked}
       * event.
       *
       * Requirements:
       *
       * - the caller must be `account`.
       */
      function renounceRole(bytes32 role, address account) public virtual override {
          require(account == _msgSender(), "AccessControl: can only renounce roles for self");
  
          _revokeRole(role, account);
      }
  
      /**
       * @dev Grants `role` to `account`.
       *
       * If `account` had not been already granted `role`, emits a {RoleGranted}
       * event. Note that unlike {grantRole}, this function doesn't perform any
       * checks on the calling account.
       *
       * [WARNING]
       * ====
       * This function should only be called from the constructor when setting
       * up the initial roles for the system.
       *
       * Using this function in any other way is effectively circumventing the admin
       * system imposed by {AccessControl}.
       * ====
       */
      function _setupRole(bytes32 role, address account) internal virtual {
          _grantRole(role, account);
      }
  
      /**
       * @dev Sets `adminRole` as ``role``'s admin role.
       *
       * Emits a {RoleAdminChanged} event.
       */
      function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
          bytes32 previousAdminRole = getRoleAdmin(role);
          _roles[role].adminRole = adminRole;
          emit RoleAdminChanged(role, previousAdminRole, adminRole);
      }
  
      function _grantRole(bytes32 role, address account) private {
          if (!hasRole(role, account)) {
              _roles[role].members[account] = true;
              emit RoleGranted(role, account, _msgSender());
          }
      }
  
      function _revokeRole(bytes32 role, address account) private {
          if (hasRole(role, account)) {
              _roles[role].members[account] = false;
              emit RoleRevoked(role, account, _msgSender());
          }
      }
  }
  
  
  // File @openzeppelin/contracts/utils/structs/EnumerableSet.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Library for managing
   * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
   * types.
   *
   * Sets have the following properties:
   *
   * - Elements are added, removed, and checked for existence in constant time
   * (O(1)).
   * - Elements are enumerated in O(n). No guarantees are made on the ordering.
   *
   * ```
   * contract Example {
   *     // Add the library methods
   *     using EnumerableSet for EnumerableSet.AddressSet;
   *
   *     // Declare a set state variable
   *     EnumerableSet.AddressSet private mySet;
   * }
   * ```
   *
   * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
   * and `uint256` (`UintSet`) are supported.
   */
  library EnumerableSet {
      // To implement this library for multiple types with as little code
      // repetition as possible, we write it in terms of a generic Set type with
      // bytes32 values.
      // The Set implementation uses private functions, and user-facing
      // implementations (such as AddressSet) are just wrappers around the
      // underlying Set.
      // This means that we can only create new EnumerableSets for types that fit
      // in bytes32.
  
      struct Set {
          // Storage of set values
          bytes32[] _values;
          // Position of the value in the `values` array, plus 1 because index 0
          // means a value is not in the set.
          mapping(bytes32 => uint256) _indexes;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function _add(Set storage set, bytes32 value) private returns (bool) {
          if (!_contains(set, value)) {
              set._values.push(value);
              // The value is stored at length-1, but we add 1 to all indexes
              // and use 0 as a sentinel value
              set._indexes[value] = set._values.length;
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function _remove(Set storage set, bytes32 value) private returns (bool) {
          // We read and store the value's index to prevent multiple reads from the same storage slot
          uint256 valueIndex = set._indexes[value];
  
          if (valueIndex != 0) {
              // Equivalent to contains(set, value)
              // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
              // the array, and then remove the last element (sometimes called as 'swap and pop').
              // This modifies the order of the array, as noted in {at}.
  
              uint256 toDeleteIndex = valueIndex - 1;
              uint256 lastIndex = set._values.length - 1;
  
              if (lastIndex != toDeleteIndex) {
                  bytes32 lastvalue = set._values[lastIndex];
  
                  // Move the last value to the index where the value to delete is
                  set._values[toDeleteIndex] = lastvalue;
                  // Update the index for the moved value
                  set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
              }
  
              // Delete the slot where the moved value was stored
              set._values.pop();
  
              // Delete the index for the deleted slot
              delete set._indexes[value];
  
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function _contains(Set storage set, bytes32 value) private view returns (bool) {
          return set._indexes[value] != 0;
      }
  
      /**
       * @dev Returns the number of values on the set. O(1).
       */
      function _length(Set storage set) private view returns (uint256) {
          return set._values.length;
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function _at(Set storage set, uint256 index) private view returns (bytes32) {
          return set._values[index];
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function _values(Set storage set) private view returns (bytes32[] memory) {
          return set._values;
      }
  
      // Bytes32Set
  
      struct Bytes32Set {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
          return _add(set._inner, value);
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
          return _remove(set._inner, value);
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
          return _contains(set._inner, value);
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(Bytes32Set storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
          return _at(set._inner, index);
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
          return _values(set._inner);
      }
  
      // AddressSet
  
      struct AddressSet {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(AddressSet storage set, address value) internal returns (bool) {
          return _add(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(AddressSet storage set, address value) internal returns (bool) {
          return _remove(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(AddressSet storage set, address value) internal view returns (bool) {
          return _contains(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(AddressSet storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(AddressSet storage set, uint256 index) internal view returns (address) {
          return address(uint160(uint256(_at(set._inner, index))));
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(AddressSet storage set) internal view returns (address[] memory) {
          bytes32[] memory store = _values(set._inner);
          address[] memory result;
  
          assembly {
              result := store
          }
  
          return result;
      }
  
      // UintSet
  
      struct UintSet {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(UintSet storage set, uint256 value) internal returns (bool) {
          return _add(set._inner, bytes32(value));
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(UintSet storage set, uint256 value) internal returns (bool) {
          return _remove(set._inner, bytes32(value));
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(UintSet storage set, uint256 value) internal view returns (bool) {
          return _contains(set._inner, bytes32(value));
      }
  
      /**
       * @dev Returns the number of values on the set. O(1).
       */
      function length(UintSet storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(UintSet storage set, uint256 index) internal view returns (uint256) {
          return uint256(_at(set._inner, index));
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(UintSet storage set) internal view returns (uint256[] memory) {
          bytes32[] memory store = _values(set._inner);
          uint256[] memory result;
  
          assembly {
              result := store
          }
  
          return result;
      }
  }
  
  
  // File @openzeppelin/contracts/access/AccessControlEnumerable.sol@v4.3.2
  
  pragma solidity ^0.8.0;
  
  
  
  /**
   * @dev Extension of {AccessControl} that allows enumerating the members of each role.
   */
  abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
      using EnumerableSet for EnumerableSet.AddressSet;
  
      mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
  
      /**
       * @dev See {IERC165-supportsInterface}.
       */
      function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
          return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
      }
  
      /**
       * @dev Returns one of the accounts that have `role`. `index` must be a
       * value between 0 and {getRoleMemberCount}, non-inclusive.
       *
       * Role bearers are not sorted in any particular way, and their ordering may
       * change at any point.
       *
       * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
       * you perform all queries on the same block. See the following
       * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
       * for more information.
       */
      function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {
          return _roleMembers[role].at(index);
      }
  
      /**
       * @dev Returns the number of accounts that have `role`. Can be used
       * together with {getRoleMember} to enumerate all bearers of a role.
       */
      function getRoleMemberCount(bytes32 role) public view override returns (uint256) {
          return _roleMembers[role].length();
      }
  
      /**
       * @dev Overload {grantRole} to track enumerable memberships
       */
      function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
          super.grantRole(role, account);
          _roleMembers[role].add(account);
      }
  
      /**
       * @dev Overload {revokeRole} to track enumerable memberships
       */
      function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
          super.revokeRole(role, account);
          _roleMembers[role].remove(account);
      }
  
      /**
       * @dev Overload {renounceRole} to track enumerable memberships
       */
      function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
          super.renounceRole(role, account);
          _roleMembers[role].remove(account);
      }
  
      /**
       * @dev Overload {_setupRole} to track enumerable memberships
       */
      function _setupRole(bytes32 role, address account) internal virtual override {
          super._setupRole(role, account);
          _roleMembers[role].add(account);
      }
  }
  
  
  // File contracts/base/TokenSaver.sol
  
  pragma solidity 0.8.25;
  
  
  
  contract TokenSaver is AccessControlEnumerable {
      using SafeERC20 for IERC20;
  
      bytes32 public constant TOKEN_SAVER_ROLE = keccak256("TOKEN_SAVER_ROLE");
  
      event TokenSaved(address indexed by, address indexed receiver, address indexed token, uint256 amount);
  
      modifier onlyTokenSaver() {
          require(hasRole(TOKEN_SAVER_ROLE, _msgSender()), "TokenSaver.onlyTokenSaver: permission denied");
          _;
      }
  
      constructor() {
          _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
      }
  
      function saveToken(address _token, address _receiver, uint256 _amount) external onlyTokenSaver {
          IERC20(_token).safeTransfer(_receiver, _amount);
          emit TokenSaved(_msgSender(), _receiver, _token, _amount);
      }
  
  }
  
  
  // File contracts/base/BasePool.sol
  
  pragma solidity 0.8.25;
  
  
  
  
  
  
  abstract contract BasePool is ERC20Votes, AbstractRewards, IBasePool, TokenSaver {
      using SafeERC20 for IERC20;
      using SafeCast for uint256;
      using SafeCast for int256;
  
      IERC20 public immutable depositToken;
      IERC20 public immutable rewardToken;
      ITimeLockPool public immutable escrowPool;
      uint256 public immutable escrowPortion; // how much is escrowed 1e18 == 100%
      uint256 public immutable escrowDuration; // escrow duration in seconds
  
      event RewardsClaimed(address indexed _from, address indexed _receiver, uint256 _escrowedAmount, uint256 _nonEscrowedAmount);
  
      constructor(
          string memory _name,
          string memory _symbol,
          address _depositToken,
          address _rewardToken,
          address _escrowPool,
          uint256 _escrowPortion,
          uint256 _escrowDuration
      ) ERC20Permit(_name) ERC20(_name, _symbol) AbstractRewards(balanceOf, totalSupply) {
          require(_escrowPortion <= 1e18, "BasePool.constructor: Cannot escrow more than 100%");
          require(_depositToken != address(0), "BasePool.constructor: Deposit token must be set");
          depositToken = IERC20(_depositToken);
          rewardToken = IERC20(_rewardToken);
          escrowPool = ITimeLockPool(_escrowPool);
          escrowPortion = _escrowPortion;
          escrowDuration = _escrowDuration;
  
          if(_rewardToken != address(0) && _escrowPool != address(0)) {
              IERC20(_rewardToken).safeApprove(_escrowPool, type(uint256).max);
          }
      }
  
      function _mint(address _account, uint256 _amount) internal virtual override {
  		super._mint(_account, _amount);
          _correctPoints(_account, -(_amount.toInt256()));
  	}
  	
  	function _burn(address _account, uint256 _amount) internal virtual override {
  		super._burn(_account, _amount);
          _correctPoints(_account, _amount.toInt256());
  	}
  
      function _transfer(address _from, address _to, uint256 _value) internal virtual override {
  		super._transfer(_from, _to, _value);
          _correctPointsForTransfer(_from, _to, _value);
  	}
  
      function distributeRewards(uint256 _amount) external override {
          rewardToken.safeTransferFrom(_msgSender(), address(this), _amount);
          _distributeRewards(_amount);
      }
  
      function claimRewards(address _receiver) external {
          uint256 rewardAmount = _prepareCollect(_msgSender());
          uint256 escrowedRewardAmount = rewardAmount * escrowPortion / 1e18;
          uint256 nonEscrowedRewardAmount = rewardAmount - escrowedRewardAmount;
  
          if(escrowedRewardAmount != 0 && address(escrowPool) != address(0)) {
              escrowPool.deposit(escrowedRewardAmount, escrowDuration, _receiver);
          }
  
          // ignore dust
          if(nonEscrowedRewardAmount > 1) {
              rewardToken.safeTransfer(_receiver, nonEscrowedRewardAmount);
          }
  
          emit RewardsClaimed(_msgSender(), _receiver, escrowedRewardAmount, nonEscrowedRewardAmount);
      }
  
  }
  
  
  // File contracts/TimeLockPool.sol
  
  pragma solidity 0.8.25;
  
  
  
  
  contract TimeLockPool is BasePool, ITimeLockPool {
      using Math for uint256;
      using SafeERC20 for IERC20;
  
      uint256 public immutable maxBonus;
      uint256 public immutable maxLockDuration;
      uint256 public constant MIN_LOCK_DURATION = 10 minutes;
      
      mapping(address => Deposit[]) public depositsOf;
  
      struct Deposit {
          uint256 amount;
          uint64 start;
          uint64 end;
      }
      constructor(
          string memory _name,
          string memory _symbol,
          address _depositToken,
          address _rewardToken,
          address _escrowPool,
          uint256 _escrowPortion,
          uint256 _escrowDuration,
          uint256 _maxBonus,
          uint256 _maxLockDuration
      ) BasePool(_name, _symbol, _depositToken, _rewardToken, _escrowPool, _escrowPortion, _escrowDuration) {
          require(_maxLockDuration >= MIN_LOCK_DURATION, "TimeLockPool.constructor: max lock duration must be greater or equal to mininmum lock duration");
          maxBonus = _maxBonus;
          maxLockDuration = _maxLockDuration;
      }
  
      event Deposited(uint256 amount, uint256 duration, address indexed receiver, address indexed from);
      event Withdrawn(uint256 indexed depositId, address indexed receiver, address indexed from, uint256 amount);
  
      function deposit(uint256 _amount, uint256 _duration, address _receiver) external override {
          require(_amount > 0, "TimeLockPool.deposit: cannot deposit 0");
          // Don't allow locking > maxLockDuration
          uint256 duration = _duration.min(maxLockDuration);
          // Enforce min lockup duration to prevent flash loan or MEV transaction ordering
          duration = duration.max(MIN_LOCK_DURATION);
  
          depositToken.safeTransferFrom(_msgSender(), address(this), _amount);
  
          depositsOf[_receiver].push(Deposit({
              amount: _amount,
              start: uint64(block.timestamp),
              end: uint64(block.timestamp) + uint64(duration)
          }));
  
          uint256 mintAmount = _amount * getMultiplier(duration) / 1e18;
  
          _mint(_receiver, mintAmount);
          emit Deposited(_amount, duration, _receiver, _msgSender());
      }
  
      function withdraw(uint256 _depositId, address _receiver) external {
          require(_depositId < depositsOf[_msgSender()].length, "TimeLockPool.withdraw: Deposit does not exist");
          Deposit memory userDeposit = depositsOf[_msgSender()][_depositId];
          require(block.timestamp >= userDeposit.end, "TimeLockPool.withdraw: too soon");
  
          //                      No risk of wrapping around on casting to uint256 since deposit end always > deposit start and types are 64 bits
          uint256 shareAmount = userDeposit.amount * getMultiplier(uint256(userDeposit.end - userDeposit.start)) / 1e18;
  
          // remove Deposit
          depositsOf[_msgSender()][_depositId] = depositsOf[_msgSender()][depositsOf[_msgSender()].length - 1];
          depositsOf[_msgSender()].pop();
  
          // burn pool shares
          _burn(_msgSender(), shareAmount);
          
          // return tokens
          depositToken.safeTransfer(_receiver, userDeposit.amount);
          emit Withdrawn(_depositId, _receiver, _msgSender(), userDeposit.amount);
      }
  
      function getMultiplier(uint256 _lockDuration) public view returns(uint256) {
          return 1e18 + (maxBonus * _lockDuration / maxLockDuration);
      }
  
      function getTotalDeposit(address _account) public view returns(uint256) {
          uint256 total;
          for(uint256 i = 0; i < depositsOf[_account].length; i++) {
              total += depositsOf[_account][i].amount;
          }
  
          return total;
      }
  
      function getDepositsOf(address _account) public view returns(Deposit[] memory) {
          return depositsOf[_account];
      }
  
      function getDepositsOfLength(address _account) public view returns(uint256) {
          return depositsOf[_account].length;
      }
  }
  
  
  // File contracts/TimeLockNonTransferablePool.sol
  
  pragma solidity 0.8.25;
  
  contract TimeLockNonTransferablePool is TimeLockPool {
      constructor(
          string memory _name,
          string memory _symbol,
          address _depositToken,
          address _rewardToken,
          address _escrowPool,
          uint256 _escrowPortion,
          uint256 _escrowDuration,
          uint256 _maxBonus,
          uint256 _maxLockDuration
      ) TimeLockPool(_name, _symbol, _depositToken, _rewardToken, _escrowPool, _escrowPortion, _escrowDuration, _maxBonus, _maxLockDuration) {
  
      }
  
      // disable transfers
      function _transfer(address _from, address _to, uint256 _amount) internal override {
          revert("NON_TRANSFERABLE");
      }
  }