// File: @openzeppelin/contracts/access/IAccessControl.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface 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/token/ERC20/extensions/draft-IERC20Permit.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

// File: @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "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");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, 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) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, 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) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or 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 {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // 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
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: @openzeppelin/contracts/utils/introspection/ERC165Checker.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/ERC165Checker.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165Checker {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface.
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
            !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     *
     * _Available since v3.4._
     */
    function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
        internal
        view
        returns (bool[] memory)
    {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     * Interface identification is specified in ERC-165.
     */
    function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
        // prepare call
        bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);

        // perform static call
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly {
            success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0x00)
        }

        return success && returnSize >= 0x20 && returnValue > 0;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface 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: contracts/dapp-game/CflatsDappRequirements.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/access/IAccessControl.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "./errors/CflatsDatabaseErrors.sol";
import "./ICflatsDappRequirements.sol";


abstract contract CflatsDappRequirements is ICflatsDappRequirements, Context
{
    bytes32 private constant _DEFAULT_ADMIN_ROLE = 0x00;
    ICflatsDatabase private immutable _DATABASE;
    constructor(ICflatsDatabase database)
    {
        _DATABASE = database;
    }


    function getDatabase() public view returns (ICflatsDatabase)
    {
        return _DATABASE;
    }



    modifier onlyNotBlacklisted()
    {
        _requireNotBlacklisted(_msgSender());
        _;
    }

    modifier onlyAdmin()
    {
        _requireNotBlacklisted(_msgSender());
        _;
    }

    modifier onlyOperator()
    {
        _requireOperator(_msgSender());
        _;
    }

    modifier onlyDeveloper()
    {
        _requireDeveloper(_msgSender());
        _;
    }



    function _requireAdmin(address user) private view
    {
        _requireNotBlacklisted(user);
        
        if(IAccessControl(address(_DATABASE)).hasRole(_DEFAULT_ADMIN_ROLE, user) != true)
        {
            revert OnlyOperatorCanCallThisFunction();
        }
    }

    function _requireOperator(address user) private view
    {
        _requireNotBlacklisted(user);
        
        if(IAccessControl(address(_DATABASE)).hasRole(_DATABASE.OPERATOR_ROLE(), user) != true)
        {
            revert OnlyOperatorCanCallThisFunction();
        }
    }

    function _requireDeveloper(address user) private view
    {
        _requireNotBlacklisted(user);
        
        if(IAccessControl(address(_DATABASE)).hasRole(_DATABASE.DEVELOPER_ROLE(), user) != true)
        {
            revert OnlyOperatorCanCallThisFunction();
        }
    }

    function _requireNotBlacklisted(address user) private view 
    {
        if(_DATABASE.isBlacklisted(user))
        {
            revert BlacklistedError();
        }
    }
}

// File: contracts/dapp-game/database/ICflatsDatabase.sol



// SPDX-License-Identifier: MIT

/// @author Tient Technologies (Twitter:https://twitter.com/tient_tech | Github:https://github.com/Tient-Technologies | | LinkedIn:https://www.linkedin.com/company/tient-technologies/)
/// @dev NiceArti (https://github.com/NiceArti)
/// To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
/// @title The interface for implementing the CflatsDatabase smart contract 
/// with a full description of each function and their implementation 
/// is presented to your attention.

pragma solidity ^0.8.18;

interface ICflatsDatabase 
{
    /// @dev Emitted when new user is added
    ///`user` is the address of account that should be added
    event UserInserted(address indexed user);


    /// @dev Emitted when user is removed
    /// `user` is the address of account that should be removed
    event UserRemoved(address indexed user);


    /// @dev Emitted when user is added in blacklist
    /// `user` is the address of account that should be added in blacklist
    event UserBlacklisted(address indexed user);


    /// @dev Emitted when user is removed from blacklist
    /// `user` is the address of account that should be removed from blacklist
    event UserRemovedFromBlacklist(address indexed user);


    
    /// @dev Table cell of default user data in database
    ///
    /// `_account` is the address of oae also account address is an id of user
    /// `_role` is the current role for user, by default is USER_ROLE
    ///
    /// There are four types of roles:
    /// `DEFAULT_ADMIN_ROLE` - role granted only for admin of database
    /// `USER_ROLE` - default role for any user added in database
    /// `OPERATOR_ROLE` - role for special mechanisms/bots that should autamatically do
    /// special intermediate calls that are not allowed for USER_ROLE
    /// `DEVELOPER_ROLE` - the same accessibility role as OPERATOR_ROLE the difference is
    /// that this role is intended exclusively for tests on the testnet and 
    /// should not be used in the mainnet network
    struct User
    {
        address _account;
        bytes32 _role;
    }




    //************************* startregion: VARIABLES  *************************//

    /// @dev OAE wallet for team
    ///
    /// This wallet is used as a collection of fees, funds, 
    /// donations, to support the project development team
    /// @return address of team wallet
    function TEAM_WALLET() external view returns (address);


    
    /// @dev This role is given to all users who, for whatever reason,
    /// violate the rules of the project, scammers, fraudsters
    /// @return 0x000000000000000000000000000000000000000000000000000000000000dead
    function BLACKLISTED_ROLE() external view returns (bytes32);


    /// @dev The role is given to bots to automate some processes or 
    /// to be used as intermediaries between the user and the contract
    /// 
    /// NOTE: OPERATOR_ROLE can be granted for other contracts as well as for
    /// intermediate parties as bots
    ///
    /// @return 0x0000000000000000000000000000000000000000000000000000000000000001
    function OPERATOR_ROLE() external view returns (bytes32);


    
    /// @dev The role is intended only for tests on the testnet,
    /// to check the correct operability of the contract
    ///
    /// NOTE: should be only used on testnet
    ///
    /// @return 0x0000000000000000000000000000000000000000000000000000000000000002
    function DEVELOPER_ROLE() external view returns (bytes32);


    
    /// @dev Default role for any user added in database
    /// @return 0x0000000000000000000000000000000000000000000000000000000000000003
    function USER_ROLE() external view returns (bytes32);

    //************************* endregion: VARIABLES  *************************//




    //************************* startregion: CALLABLE FUNCTIONS  *************************//

    /// @dev Returns true for account that is added is blacklist  
    /// @param user - account address of user saved in db
    /// @return false if user not registered in db or not blacklisted
    function isBlacklisted(address user) external view returns (bool);


    /// @dev Returns true if user was added in database and not removed
    /// @param user - account address of user saved in db
    /// @return false if user is not registered in db
    function userExists(address user) external view returns (bool);


    /// @dev Returns table cell of user by it's account address
    /// @param user - account address of user saved in db
    /// @return table cell with user data
    function getUserByAccountAddress(address user) external view returns (User memory);


    /// @dev Returns count of all users that was added in database table
    function getUsersCount() external view returns (uint256);
    
    //************************* startregion: CALLABLE FUNCTIONS  *************************//




    //************************* startregion: SEND FUNCTIONS  *************************//
    
    /// @dev Adds user by account address
    /// @custom:accessibility OPERATOR_ROLE
    /// NOTE: it's highly access function callable only via OPERATOR_ROLE
    /// Also only never added accounts will be added without dubbing
    ///
    /// @return true if transaction is successfull or user 
    /// has never been added in database before
    function addUser(address user) external returns (bool);


    /// @dev Adds many users by account addresses
    /// @custom:accessibility OPERATOR_ROLE
    /// NOTE: it's highly access function callable only via OPERATOR_ROLE
    /// Also only never added accounts will be added without dubbing
    /// 
    /// @return true if transaction is successfull
    function addUsersBatch(address[] calldata users) external returns (bool);


    /// @dev Remove user by account address
    /// @custom:accessibility OPERATOR_ROLE
    /// @custom:requires user to be existed in database otherwise it will revert with
    /// error UserDoesNotExistsError(address user)
    ///
    /// NOTE: it's highly access function callable only via OPERATOR_ROLE
    /// Only existed users can be removed from database
    ///
    /// @return true if transaction is successfull or user is unique in database
    function removeUser(address user) external returns (bool);


    /// @dev Remove many users from database
    /// @custom:accessibility DEFAULT_ADMIN_ROLE
    /// @custom:requires user to be existed in database otherwise it will revert with
    /// error UserDoesNotExistsError(address user)
    ///
    /// NOTE: it's highly access function callable only via DEFAULT_ADMIN_ROLE
    /// Only existed users can be removed from database
    ///
    /// @return true if transaction is successfull or user is unique in database
    function removeUsersBatch(address[] memory users) external returns (bool);


    /// @dev Adds the user to the blacklist, thereby blocking and severely 
    /// restricting the user from calling most functions
    /// @custom:accessibility OPERATOR_ROLE
    ///
    /// NOTE: it's highly access function callable only via OPERATOR_ROLE
    ///
    /// @return true if user has never been added in blacklist before
    function addUserInBlacklist(address user) external returns (bool);


    /// @dev Removes user from the blacklist
    /// @custom:accessibility OPERATOR_ROLE
    ///
    /// NOTE: it's highly access function callable only via OPERATOR_ROLE
    ///
    /// @return true if user ever been added in blacklist before
    function removeUserFromBlacklist(address user) external returns (bool);

    //************************* startregion: SEND FUNCTIONS  *************************//
}

// File: contracts/dapp-game/discount/IDiscountable.sol



// SPDX-License-Identifier: MIT

/// @author Tient Technologies (Twitter:https://twitter.com/tient_tech | Github:https://github.com/Tient-Technologies | LinkedIn:https://www.linkedin.com/company/tient-technologies/)
/// @dev NiceArti (https://github.com/NiceArti)
/// To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
/// @title Generalized interface for discounts

pragma solidity ^0.8.18;

interface IDiscountable
{
    /// @dev Interface function for dicounts
    /// @param account address of user
    /// @return tuple of boolean value and uint8
    /// Boolean - returns true if account has discount, false by default
    /// Uint8 - percentage amount of discount (Ex: 20%, 30%), zero by default
    function hasDiscount(address account) external view returns (bool, uint8);
}
// File: contracts/dapp-game/errors/CflatsDatabaseErrors.sol



// SPDX-License-Identifier: MIT

/// @author Tient Technologies (Twitter:https://twitter.com/tient_tech | Github:https://github.com/Tient-Technologies | LinkedIn:https://www.linkedin.com/company/tient-technologies/)
/// @dev NiceArti (https://github.com/NiceArti)
/// To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
/// @title Error constants for CflatsDatabase contract
pragma solidity ^0.8.18;

import "./Errors.sol";

error BlacklistedError();
error OnlyOperatorCanCallThisFunction();
error UserDoesNotExistsError(address user);
error UsersToDeleteExceedAmountOfDatabaseInsertedUsers();
// File: contracts/dapp-game/errors/CflatsStakingErrors.sol



// SPDX-License-Identifier: MIT

/// @author Tient Technologies (Twitter:https://twitter.com/tient_tech | Github:https://github.com/Tient-Technologies | LinkedIn:https://www.linkedin.com/company/tient-technologies/)
/// @dev NiceArti (https://github.com/NiceArti)
/// To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
/// @title Error constants for CflatsStaking contract
pragma solidity ^0.8.18;

import "./Errors.sol";

error TerritoryNotBoughtError();
error NftPassNotBoughtError();
error UnstakePeriodHasntBeenCompletedError();
error MaxGenSupplyOverflow();
error NftPassNotSettedByAdmin();
error ZeroStakerBalanceForCurrentRarity(uint8 rarity);
// File: contracts/dapp-game/errors/Errors.sol



// SPDX-License-Identifier: MIT

/// @author Tient Technologies (Twitter:https://twitter.com/tient_tech | Github:https://github.com/Tient-Technologies | LinkedIn:https://www.linkedin.com/company/tient-technologies/)
/// @dev NiceArti (https://github.com/NiceArti)
/// To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
/// @title Error constants for Cflats project
pragma solidity ^0.8.18;

error ZeroAddress();
error ThisContractAddress();
error NotErc721();
error InvalidCurrentDate();
// File: contracts/dapp-game/errors/StakingErrors.sol



// SPDX-License-Identifier: MIT

/// @author Tient Technologies (Twitter:https://twitter.com/tient_tech | Github:https://github.com/Tient-Technologies | LinkedIn:https://www.linkedin.com/company/tient-technologies/)
/// @dev NiceArti (https://github.com/NiceArti)
/// To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
/// @title Error constants for StakingNFT contract
pragma solidity ^0.8.18;

import "./Errors.sol";

error ZeroStakerBalance();
error StakeTokenAlredyRemovedOrWasNotAdded();
error TokenIsNotAvailableToStake();
error TransferAmountExceedsStakerBalance();
error ClaimAmountExceedBalance();
error StakerIsNotOwnerOf(address stakeToken, uint256 tokenId);
// File: contracts/dapp-game/ICflatsDappRequirements.sol



/**
* @author NiceArti (https://github.com/NiceArti) 
* To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
* @title The interface for implementing the CryptoFlatsNft smart contract 
* with a full description of each function and their implementation 
* is presented to your attention.
*/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "./database/ICflatsDatabase.sol";

interface ICflatsDappRequirements
{
    function getDatabase() external view returns (ICflatsDatabase);
}
// File: contracts/dapp-game/staking/CflatsStaking.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/IAccessControl.sol";
import "../database/ICflatsDatabase.sol";
import "../../interfaces/ICryptoflatsNFT.sol";
import "../../harvest/Harvest.sol";
import "../errors/CflatsStakingErrors.sol";
import "../discount/IDiscountable.sol";
import "../territory/ICflatsTerritory.sol";
import "../CflatsDappRequirements.sol";
import "./ICflatsStaking.sol";
import "./StakingNFT.sol";


contract CflatsStaking is ICflatsStaking, IDiscountable, StakingNFT, CflatsDappRequirements, Harvest
{
    using SafeERC20 for IERC20;

    //***************************** startregion: CONSTANTS *****************************//
    // Profit constants //
    // Gen 1
    uint256 public constant PROFIT_FIRST_GEN_STANDARD = 3000;
    uint256 public constant PROFIT_FIRST_GEN_SILVER = 4000;
    uint256 public constant PROFIT_FIRST_GEN_GOLD = 5000;
    uint256 public constant PROFIT_FIRST_GEN_DIAMOND = 6000;

    // Gen 2
    uint256 public constant PROFIT_SECOND_GEN_STANDARD = 4000;
    uint256 public constant PROFIT_SECOND_GEN_SILVER = 5000;
    uint256 public constant PROFIT_SECOND_GEN_GOLD = 6500;
    uint256 public constant PROFIT_SECOND_GEN_DIAMOND = 8000;
    
    // Gen 3
    uint256 public constant PROFIT_THIRD_GEN_STANDARD = 5000;
    uint256 public constant PROFIT_THIRD_GEN_SILVER = 6000;
    uint256 public constant PROFIT_THIRD_GEN_GOLD = 7000;
    uint256 public constant PROFIT_THIRD_GEN_DIAMOND = 8000;

    // Gen 4
    uint256 public constant PROFIT_FOURTH_GEN_STANDARD = 6000;
    uint256 public constant PROFIT_FOURTH_GEN_SILVER = 8500;
    uint256 public constant PROFIT_FOURTH_GEN_GOLD = 10000;
    uint256 public constant PROFIT_FOURTH_GEN_DIAMOND = 12500;

    // Gen 5
    uint256 public constant PROFIT_FIFTH_GEN = 15000;

    // One day in seconds
    uint256 private constant _ONE_DAY = 86_400;
    
    // Five days in seconds
    uint256 private constant _FIVE_DAYS = _ONE_DAY * 5;
    

    // Get address of token by gen
    address[6] private _tokensByGen;

    //***************************** endregion: CONSTANTS *****************************//


    // address of territory for buying some nft
    ICflatsTerritory private immutable _TERRITORY;


    // mapping for setting up or extract highest rarity staked by owner
    mapping(address staker =>
        mapping(address stakeToken => CflatsRarity rarity)
    ) private _upperStakedRarityOf;


    // mapping for setting up or extract lock time
    mapping(address stakeToken => 
        mapping(uint256 tokenId => uint256 unlockTimestamp)
    ) private _nftLocker;


    // @notice mapping for setting up or extract balance by it's rarity
    mapping(address staker => 
        mapping(address stakeToken => 
        mapping(CflatsRarity rarity => uint256 stakeBalance)
    )) private _stakeBalanceByRarity;


    // @notice mappings for showing statistics of users by staked gen
    mapping(address stakeToken => 
        mapping(CflatsRarity rarity => uint16 stakers)
    ) private _cflatsStatisticsByRarity;





    constructor(
        address rewardToken,
        ICflatsTerritory territory,
        ICflatsDatabase database
    ) 
        StakingNFT(rewardToken)
        CflatsDappRequirements(database)
    {
        _TERRITORY = territory;
    }



    //***************************** startregion: external functions *****************************//

    /// @notice Since in the functionality of the Dapp game there is an opportunity
    /// for some users "fraudsters" to take part of the profits of others for some charge, 
    /// there is a special bot that regulates the events of the function call, and
    /// allows "fraudster" to call the function of transferring reward tokens to the
    /// balance of the fraudster through the CflatsDapp contract. The function also
    /// works in the opposite direction, the "victim" can return his/her tokens with a
    /// certain percentage
    /// @param from: victim
    /// @param to: fraudster
    /// @param amount: uint256
    /// @return True if transaction was successfully done
    function transferRewards(
        address from,
        address to,
        uint256 amount
    ) 
        external
        onlyOperator
        returns (bool)
    {
        _transferRewards(from, to, amount);
        return true;
    }


    /// @notice this function allows the administrator to add authorized 
    /// NFT addresses for staking (gen-0, gen-1, gen-2, gen-3, gen-4, gen-5)
    /// @param newStakeToken: address
    /// @return True if transaction was successfully done
    function addStakeToken(address newStakeToken) 
        external
        onlyOperator
        returns (bool)
    {
        uint256 gen = _getGen(newStakeToken);
        if(_tokensByGen.length < gen)
        {
            revert MaxGenSupplyOverflow();
        }

        _tokensByGen[gen] = newStakeToken;

        _addStakeToken(newStakeToken);

        return true;
    }


    function getAddedStakeTokens() external view returns(address[6] memory){
        return _tokensByGen;
    }


    /// @notice this function allows the administrator to remove 
    /// NFT addresses if it was added by mistake
    /// @param stakeToken: address
    /// @return True if transaction was successfully done
    function removeStakeToken(address stakeToken) 
        external
        onlyOperator
        returns(bool)
    {
        uint256 gen = _getGen(stakeToken);
        if(_tokensByGen.length < gen)
        {
            revert MaxGenSupplyOverflow();
        }

        delete _tokensByGen[gen];

        _removeStakeToken(stakeToken);
        return true;
    }


    function getStatisticsByRarity(address stakeToken) 
        external
        view
        returns(uint16, uint16, uint16, uint16)
    {
        return (
            _cflatsStatisticsByRarity[stakeToken][CflatsRarity.Standart],
            _cflatsStatisticsByRarity[stakeToken][CflatsRarity.Silver],
            _cflatsStatisticsByRarity[stakeToken][CflatsRarity.Gold],
            _cflatsStatisticsByRarity[stakeToken][CflatsRarity.Diamond]
        );
    }

    //***************************** endregion: external functions *****************************//



    //***************************** startregion: public functions *****************************//

    /// @notice shows upper token rarity staked by user
    /// @param staker: address
    /// @param stakeToken: address
    /// @return Highest staked rarity
    function getUpperStakedRarityOf(
        address staker,
        address stakeToken
    ) public view returns (CflatsRarity)
    {
        uint256 diamondBalance = _stakeBalanceByRarity[staker][stakeToken][CflatsRarity.Diamond];
        
        if(diamondBalance > 0)
        {
            return CflatsRarity.Diamond;
        }

        uint256 goldBalance = _stakeBalanceByRarity[staker][stakeToken][CflatsRarity.Gold];
        if(goldBalance > 0)
        {
            return CflatsRarity.Gold;
        }

        uint256 silverBalance = _stakeBalanceByRarity[staker][stakeToken][CflatsRarity.Silver];
        if(silverBalance > 0)
        {
            return CflatsRarity.Silver;
        }

        return CflatsRarity.Standart;
    }




    /// @notice shows rewards that can be received for staking specific gen and rarity
    /// @param stakeToken: address
    /// @param tokenId: address
    /// @return Rewards 
    function getRewardsForStakeToken(
        address stakeToken,
        uint256 tokenId
    ) public view returns(uint256)
    {
        uint256 gen = _getGen(stakeToken);
        uint256 profit = PROFIT_FIFTH_GEN;
        CflatsRarity rarity = ICflatsNftRarity(stakeToken).getNFTType(tokenId);

        if(gen == 0)
        {
            return 0;
        }
        else if(gen == 1)
        {
            if(rarity == CflatsRarity.Standart)
            {
                profit = PROFIT_FIRST_GEN_STANDARD;
            }
            else if(rarity == CflatsRarity.Silver)
            {
                profit = PROFIT_FIRST_GEN_SILVER;
            }
            else if(rarity == CflatsRarity.Gold)
            {
                profit = PROFIT_FIRST_GEN_GOLD;
            }
            else
            {
                profit = PROFIT_FIRST_GEN_DIAMOND;
            }
        }
        else if(gen == 2)
        {
            if(rarity == CflatsRarity.Standart)
            {
                profit = PROFIT_SECOND_GEN_STANDARD;
            }
            else if(rarity == CflatsRarity.Silver)
            {
                profit = PROFIT_SECOND_GEN_SILVER;
            }
            else if(rarity == CflatsRarity.Gold)
            {
                profit = PROFIT_SECOND_GEN_GOLD;
            }
            else
            {
                profit = PROFIT_SECOND_GEN_DIAMOND;
            }
        }
        else if(gen == 3)
        {
            if(rarity == CflatsRarity.Standart)
            {
                profit = PROFIT_THIRD_GEN_STANDARD;
            }
            else if(rarity == CflatsRarity.Silver)
            {
                profit = PROFIT_THIRD_GEN_SILVER;
            }
            else if(rarity == CflatsRarity.Gold)
            {
                profit = PROFIT_THIRD_GEN_GOLD;
            }
            else
            {
                profit = PROFIT_THIRD_GEN_DIAMOND;
            }
        }
        else if(gen == 4)
        {
            if(rarity == CflatsRarity.Standart)
            {
                profit = PROFIT_FOURTH_GEN_STANDARD;
            }
            else if(rarity == CflatsRarity.Silver)
            {
                profit = PROFIT_FOURTH_GEN_SILVER;
            }
            else if(rarity == CflatsRarity.Gold)
            {
                profit = PROFIT_FOURTH_GEN_GOLD;
            }
            else
            {
                profit = PROFIT_FOURTH_GEN_DIAMOND;
            }

        }

        return profit * 10**IERC20Metadata(getRewardToken()).decimals();
    }


    /// @notice shows discount of user if he/she has nft pass staked and diamond token rarity
    /// @param staker: address
    /// @return Discount in boolean and percentage 
    function hasDiscount(
        address staker
    ) public view returns (bool, uint8)
    {
        if(hasNftPassStaked(staker) == false)
        {
            return (false, 0);
        }

        if(getUpperStakedRarityOf(staker, _tokensByGen[0]) == CflatsRarity.Diamond)
        {
            return (true, 20);
        }

        return (true, 10);
    }


    /// @notice shows true if user has nft pass in it's wallet
    /// @param staker: address
    /// @return Boolean
    function hasNftPass(
        address staker
    ) public view returns (bool)
    {
        address nftPass = _tokensByGen[0];
        if(nftPass == address(0))
        {
            revert NftPassNotSettedByAdmin();
        }

        return IERC20Metadata(nftPass).balanceOf(staker) > 0;
    }


    /// @notice shows true if user staked nft pass
    /// @param staker: address
    /// @return Boolean
    function hasNftPassStaked(
        address staker
    ) public view returns (bool)
    {
        address nftPass = _tokensByGen[0];
        if(nftPass == address(0))
        {
            revert NftPassNotSettedByAdmin();
        }

        return stakedBalanceOf(staker, nftPass) > 0;
    }

    //***************************** endregion: public functions *****************************//



    //***************************** startregion: internal overriden functions *****************************//

    function _beforeStake(
        address staker,
        address stakeToken,
        uint256 tokenId
    ) internal onlyNotBlacklisted override 
    {
        _requireStakeToken(stakeToken);

        getDatabase().addUser(staker);

        // check if user has territory
        uint256 gen = _getGen(stakeToken);
        if(gen > 1)
        {
            if(_TERRITORY.hasTerritoryForGen(staker, gen) == false)
            {
                revert TerritoryNotBoughtError();
            }

            // if user wants to stake gen5 it required to
            // has nft pass on wallet balance or staking balance
            if(gen == 5 && hasNftPass(staker) != true  && hasNftPassStaked(staker) != true)
            {
                revert NftPassNotBoughtError();
            }
        }


        CflatsRarity currentTokenRarity = ICflatsNftRarity(stakeToken).getNFTType(tokenId);
        
        // updating upper token rarity 
        _increaseCflatsStatisticsByRarity(staker, stakeToken, currentTokenRarity);
        _increaseUpperStakeTokenRarity(staker, stakeToken, currentTokenRarity);

        // lock tokens for five days
        _lockForFiveDays(stakeToken, tokenId);
    }


    function _afterUnstake(
        address staker,
        address stakeToken,
        uint256 tokenId
    ) internal override 
    {
        if(_isLocked(stakeToken, tokenId) != false)
        {
            revert UnstakePeriodHasntBeenCompletedError();
        }

        CflatsRarity rarity = ICflatsNftRarity(stakeToken).getNFTType(tokenId);
        _decreaseUpperStakeTokenRarity(staker, stakeToken, rarity);
        _decreaseCflatsStatisticsByRarity(staker, stakeToken, rarity);
        
        // claim some rewards staked by user imediately after unstake
        _updateRewards(staker, stakeToken);

        uint256 stakedBalance = stakedBalanceOf(staker, stakeToken);
        if(stakedBalance > 0)
        {
            uint256 stakerRewardsPartion = rewardsOf(staker) / stakedBalance;

            // user can unstake NFT's but can't claim rewards
            if(stakerRewardsPartion > 0 && getDatabase().isBlacklisted(staker) == false)
            {
                _claimRewards(
                    staker,
                    stakeToken,
                    stakerRewardsPartion 
                );
            }
        }
    }


    function _claimRewards(
        address staker,
        address stakeToken,
        uint256 amount
    ) internal onlyNotBlacklisted override
    {
        super._claimRewards(staker, stakeToken, amount);
    }
    


    function _rewardsCalculatedHook(
        address staker,
        address stakeToken
    ) 
        internal
        view
        override
        returns(uint256)
    {
        uint256 balance = stakedBalanceOf(staker, stakeToken);
        uint256[] memory stakeTokenIds = stakedTokensOf(staker, stakeToken);

        // skip all calcualtions if nothing is staked
        if(balance == 0)
        {
            return 0;
        }


        uint256 rewards = 0;
        if(balance == 1)
        {
            rewards = getRewardsForStakeToken(stakeToken, stakeTokenIds[0]);
        }
        else
        {
            uint256 stakeTokensReward = 0;
            for(uint256 i = 0; i < balance;)
            {
                stakeTokensReward += getRewardsForStakeToken(stakeToken, stakeTokenIds[i]);
                unchecked{ ++i; }
            }
            rewards = stakeTokensReward;
        }

        // check if user has nft pass so he/she can get additional prizes
        (bool nftPassStaked, uint8 addPercent) = hasDiscount(staker);
        if(nftPassStaked == true)
        {
            // add 10% if Golden pass or 20% if Diamond pass
            rewards += rewards * addPercent / 100;
        }

        return rewards;
    }


    function _updateRewards(
        address staker,
        address stakeToken
    ) internal override
    {
        if(stakeToken != _tokensByGen[0])
        {
            super._updateRewards(staker, stakeToken);
        }
    }

    //***************************** endregion: internal overriden functions *****************************//



    //***************************** startregion: private functions *****************************//
    function _increaseUpperStakeTokenRarity(
        address staker,
        address stakeToken,
        CflatsRarity rarity
    ) private
    {
        unchecked
        {
            ++_stakeBalanceByRarity[staker][stakeToken][rarity];
        }
    }


    function _decreaseUpperStakeTokenRarity(
        address staker,
        address stakeToken,
        CflatsRarity rarity
    ) private
    {
        uint256 stakeBalanceByRarity = _stakeBalanceByRarity[staker][stakeToken][rarity];
        if(stakeBalanceByRarity == 0)
        {
            revert ZeroStakerBalanceForCurrentRarity(uint8(rarity));
        }
        unchecked
        {
            --_stakeBalanceByRarity[staker][stakeToken][rarity];
        }
    }


    function _increaseCflatsStatisticsByRarity(
        address staker,
        address stakeToken,
        CflatsRarity rarity
    ) private
    {
        if(_stakeBalanceByRarity[staker][stakeToken][rarity] > 0)
        {
            return;
        }

        unchecked
        {
            ++_cflatsStatisticsByRarity[stakeToken][rarity];
        }
    }


    function _decreaseCflatsStatisticsByRarity(
        address staker,
        address stakeToken,
        CflatsRarity rarity
    ) private
    {
        if(_stakeBalanceByRarity[staker][stakeToken][rarity] > 0)
        {
            return;
        }

        unchecked
        {
            --_cflatsStatisticsByRarity[stakeToken][rarity];
        }
    }


    function _lockForFiveDays(
        address stakeToken,
        uint256 tokenId
    ) private 
    {
        _nftLocker[stakeToken][tokenId] = block.timestamp + _FIVE_DAYS;
    }


    function _isLocked(
        address stakeToken,
        uint256 tokenId
    ) private view returns(bool)
    {
        return _nftLocker[stakeToken][tokenId] > block.timestamp;
    }


    function _getGen(address stakeToken) private view returns(uint256)
    {
        (bool success, bytes memory data) = stakeToken.staticcall(
            abi.encodeWithSelector(ICflatsNftGenSelector.gen.selector)
        );
        require(success && data.length >= 32);
        return abi.decode(data, (uint256));
    }


    //***************************** endregion: private functions *****************************//
}
// File: contracts/dapp-game/staking/ICflatsStaking.sol



/**
* @author NiceArti (https://github.com/NiceArti) 
* To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
* @title The interface for implementing the CryptoFlatsNft smart contract 
* with a full description of each function and their implementation 
* is presented to your attention.
*/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "./IStakingNFT.sol";

enum CflatsRarity { Standart, Silver, Gold, Diamond }

interface ICflatsNftRarity 
{
    function getNFTType(uint256 tokenId) external view returns (CflatsRarity);
}

interface ICflatsNftGenSelector
{
    /**
    * @notice current genesis of Cryptoflats NFT
    * @return uint256
    */ 
    function gen()
        external
        view
        returns(uint256);
}


interface ICflatsStaking is IStakingNFT
{
    event RarityChanged(address indexed stakeToken, uint256 tokenId);


    // Infor for gen1 - gen5
    struct CflatsNftInfo 
    {
        uint256 _profitIncome;
        CflatsRarity _rarity;
    }


    function getUpperStakedRarityOf(
        address staker,
        address stakeToken
    ) external view returns (CflatsRarity);

    function getRewardsForStakeToken(
        address stakeToken,
        uint256 tokenId
    ) external view returns(uint256);

    
    function hasNftPass(address staker) external view returns (bool);
    function hasNftPassStaked(address staker) external view returns (bool);

    

    function addStakeToken(address newStakeToken) 
        external
        returns (bool);

    function removeStakeToken(address newStakeToken) 
        external
        returns (bool);

    function transferRewards(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}
// File: contracts/dapp-game/staking/IStakingNFT.sol



/**
* @author NiceArti (https://github.com/NiceArti) 
* To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
* @title The interface for implementing the CryptoFlatsNft smart contract 
* with a full description of each function and their implementation 
* is presented to your attention.
*/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IStakingNFT
{
    event ReceivedNFT(address indexed operator, address indexed owner, uint256 indexed tokenId, bytes data);
    event StakedNFT(address indexed owner, address indexed stakeToken, uint256 id);
    event UnstakedNFT(address indexed owner, address indexed stakeToken, uint256 id);
    event RewardsClaimed(address indexed owner, uint256 rewards);
    event RewardsTransferred(address indexed from, address indexed to, uint256 amount);


    struct StakedInfoNFT
    {
        // time when current nft was staked
        uint256 timestamp;

        // NFT ids of user
        uint256[] ids;

        // save id positions for gas saving
        mapping(uint256 => uint256) idPos;
    }


    function isStakerOf(
        address staker,
        address stakeToken,
        uint256 tokenId
    ) 
        external
        view 
        returns (bool);

    function isStakeToken(address stakeToken) 
        external
        view
        returns (bool);

    function getRewardToken() external view returns (address);
    function getUpdatePeriod() external pure returns (uint256);

    function stakedBalanceOf(
        address staker,
        address stakeToken
    ) external view returns (uint256);

    function stakedTimestampOf(
        address staker,
        address stakeToken
    ) external view returns (uint256);

    function rewardsOf(address staker)
        external
        view
        returns (uint256);

    function stakedTokensOf(
        address staker,
        address stakeToken
    ) 
        external 
        view 
        returns (uint256[] memory);

    function claimRewards(
        address stakeToken,
        uint256 amount
    ) external returns (bool);

    
    function stake(
        address stakeToken,
        uint256 tokenId
    ) external returns (bool);


    function unstake(
        address stakeToken,
        uint256 tokenId
    ) external returns (bool);

    function updateRewards(
        address staker,
        address stakeToken
    ) external;

    function batchUpdateRewards(
        address staker,
        address[] memory stakeTokens
    ) external;

    
    function batchStake(
        address[] memory stakeTokens,
        uint256[] memory tokenIds,
        uint256[] memory tokenIdsLength
    ) external returns (bool);


    function batchUnstake(
        address[] memory stakeTokens,
        uint256[] memory tokenIds,
        uint256[] memory tokenIdsLength
    ) external returns (bool);
}
// File: contracts/dapp-game/staking/StakingNFT.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "../errors/StakingErrors.sol";
import "./IStakingNFT.sol";

import "hardhat/console.sol";


abstract contract StakingNFT is IStakingNFT, IERC721Receiver, Context
{
    using SafeERC20 for IERC20;

    // Token that will be used as a reward for staking
    // It can be as ERC20 or ERC721 token or other
    address private immutable _REWARD_TOKEN;


    // mapping for setting up or extract boolean for stake token
    mapping(address stakeToken => bool isStakeToken) private _isStakeToken;


    // mapping for setting up or extract rewards of staker
    mapping(address staker => uint256 rewards) private _rewardsOf;


    // mapping for setting up or extract info of staked NFT
    mapping(address staker => 
        mapping(address stakeToken => StakedInfoNFT stakedInfo)
    ) private _stakedInfoOf;


    // mapping for setting up or extract staker of  token
    mapping(address staker => 
        mapping(address stakeToken => 
        mapping(uint256 tokenId => bool isStaker)
    )) private _isStakerOf;




    constructor(address rewardToken_)
    {
        _REWARD_TOKEN = rewardToken_;
    }



    modifier onlyStakeToken(address stakeToken)
    {
        _requireStakeToken(stakeToken);
        _;
    }




    //***************************** startregion: external overrided functions *****************************//

    /// @notice overrided ERC721 receiver
    /// @dev function used to allow this contract to receive ERC721 from any
    /// @param operator: address
    /// @param from: address
    /// @param tokenId: uint256
    /// @param data: bytes
    /// @return bytes4
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) 
        external
        virtual
        override
        returns (bytes4)
    {
        emit ReceivedNFT(operator, from, tokenId, data);
        return IERC721Receiver.onERC721Received.selector;
    }


    /// @notice stake function
    /// @dev takes arguments to stake ERC721 tokens
    /// @param stakeToken: address
    /// @param tokenId: uint256
    /// @return True if transaction is successfull
    function stake(
        address stakeToken,
        uint256 tokenId
    ) external returns (bool)
    {
        _safeStake(_msgSender(), stakeToken, tokenId);
        return true;
    }


    /// @notice unstake function
    /// @dev takes arguments to stake ERC721 tokens
    /// @param stakeToken: address
    /// @param tokenId: uint256
    /// @return True if transaction is successfull
    function unstake(
        address stakeToken,
        uint256 tokenId
    ) external returns (bool)
    {
        _safeUnstake(_msgSender(), stakeToken, tokenId);
        return true;
    }

    //***************************** endregion: external overrided functions *****************************//



    //***************************** startregion: public functions *****************************//

    /// @notice shows address of reward token
    /// @dev reward token cannot be zero address
    /// @custom:require ERC20 token to be added
    /// @return reward token address
    function getRewardToken() public virtual view returns (address)
    {
        return _REWARD_TOKEN;
    }


    /// @notice shows period in which reward balance will be updated
    /// @return Time in seconds when reward balance can be updated
    function getUpdatePeriod() public virtual pure returns (uint256)
    {
        return 86_400;
    }


    /// @notice shows balance of staked tokens
    /// @param staker: address
    /// @param stakeToken: address
    /// @return balance of staked tokens for it's owner
    function stakedBalanceOf(
        address staker,
        address stakeToken
    ) public view returns (uint256)
    {
        return _stakedInfoOf[staker][stakeToken].ids.length;
    }


    /// @notice shows time when first time ERC721 token was staked
    /// @param staker: address
    /// @param stakeToken: address
    /// @return Time in seconds when first time ERC721 token was staked
    function stakedTimestampOf(
        address staker,
        address stakeToken
    ) public view returns (uint256)
    {
        return _stakedInfoOf[staker][stakeToken].timestamp;
    }


    /// @notice shows rewards collected by staker
    /// @param staker: address
    /// @return Collected staker rewards
    function rewardsOf(address staker)
        public
        virtual
        view
        returns (uint256)
    {
        return _rewardsOf[staker];
    }


    /// @notice shows rewards collected by staker
    /// @param staker: address
    /// @param stakeToken: address
    /// @return Collected staker rewards
    function stakedTokensOf(
        address staker,
        address stakeToken
    ) 
        public 
        virtual 
        view 
        returns (uint256[] memory)
    {
        return _stakedInfoOf[staker][stakeToken].ids;
    }


    /// @notice shows if token is added by owner
    /// @param stakeToken: address
    /// @return True if token is allowed
    function isStakeToken(address stakeToken) 
        public
        virtual
        override
        view
        returns (bool)
    {
        return _isStakeToken[stakeToken];
    }


    /// @notice shows staker of ERC721 token
    /// @param staker: address
    /// @param stakeToken: address
    /// @param tokenId: uint256
    /// @return True if token by it's id is staked by staker
    function isStakerOf(
        address staker,
        address stakeToken,
        uint256 tokenId
    ) public view returns (bool)
    {
        return _isStakerOf[staker][stakeToken][tokenId];
    }


    /// @notice transfers collected rewards to it's owner wallet
    /// @notice caller can claim only it's rewards
    /// @param stakeToken: address
    /// @return True if transaction is successfull
    function claimRewards(address stakeToken, uint256 amount) public virtual returns (bool)
    {
        _claimRewards(_msgSender(), stakeToken, amount);
        return true;
    }


    /// @notice stakes multiple tokens
    /// @param stakeTokens: address[]
    /// @param tokenIds: uint256[]
    /// @param tokenIdsLength: uint256[] - for gas saving
    /// it is used to set length of ids belonging to a specific stake token address
    /// otherwise it will revert 
    /// @return True if transaction is successfull
    function batchStake(
        address[] memory stakeTokens,
        uint256[] memory tokenIds,
        uint256[] memory tokenIdsLength
    ) public virtual returns (bool)
    {
        uint256 currentIndex = 0;
        address currentStakeToken = stakeTokens[0];
        uint256 nextStakeTokenStartsFrom = tokenIdsLength[0] - 1;
        
        for(uint256 i = 0; i < tokenIds.length;)
        {
            if(i > nextStakeTokenStartsFrom)
            {
                unchecked { ++currentIndex; }
                nextStakeTokenStartsFrom = i + tokenIdsLength[currentIndex] - 1;
                currentStakeToken = stakeTokens[currentIndex];
            }

            _safeStake(_msgSender(), currentStakeToken, tokenIds[i]);
            unchecked { ++i; }
        }

        return true;
    }


    /// @notice unstakes multiple tokens
    /// @param stakeTokens: address[]
    /// @param tokenIds: uint256[]
    /// @param tokenIdsLength: uint256[] - for gas saving
    /// it is used to set length of ids belonging to a specific stake token address
    /// otherwise it will revert 
    /// @return True if transaction is successfull
    function batchUnstake(
        address[] memory stakeTokens,
        uint256[] memory tokenIds,
        uint256[] memory tokenIdsLength
    ) public virtual returns (bool)
    {
        uint256 currentIndex = 0;
        address currentStakeToken = stakeTokens[0];
        uint256 nextStakeTokenStartsFrom = tokenIdsLength[0] - 1;
        
        for(uint256 i = 0; i < tokenIds.length;)
        {
            if(i > nextStakeTokenStartsFrom)
            {
                unchecked { ++currentIndex; }
                nextStakeTokenStartsFrom = i + tokenIdsLength[currentIndex] - 1;
                currentStakeToken = stakeTokens[currentIndex];
            }

            _safeUnstake(_msgSender(), currentStakeToken, tokenIds[i]);
            unchecked { ++i; }
        }

        return true;
    }


    /// @notice updates rewards of staker by specific stakeToken
    /// @param staker: address
    /// @param stakeToken: uint256
    function updateRewards(address staker, address stakeToken) public
    {
        _updateRewards(staker, stakeToken);
    }


    /// @notice updates rewards of staker by specific many stake tokens
    /// @param staker: address
    /// @param stakeTokens: uint256[]
    function batchUpdateRewards(address staker, address[] memory stakeTokens) public
    {
        for(uint256 i = 0; i < stakeTokens.length;)
        {
            _updateRewards(staker, stakeTokens[i]);
            unchecked { ++i; }
        }
    }

    //***************************** endregion: public functions *****************************//






    //***************************** startregion: internal centralization functions *****************************//

    function _addStakeToken(address newStakeToken) internal virtual
    {
        if(_isERC721Token(newStakeToken) != true)
        {
            if(newStakeToken == address(0))
            {
                revert ZeroAddress();
            }
            else if(newStakeToken == address(this))
            {
                revert ThisContractAddress();
            }

            revert NotErc721();
        }

        _isStakeToken[newStakeToken] = true;
    }

    function _removeStakeToken(address stakeToken) internal virtual
    {
        if(_isStakeToken[stakeToken] == false)
        {
            revert StakeTokenAlredyRemovedOrWasNotAdded();
        }

        delete _isStakeToken[stakeToken];
    }

    function _transferRewards(address from, address to, uint256 amount) internal 
    {
        if(rewardsOf(from) < amount)
        {
            revert TransferAmountExceedsStakerBalance();
        }

        unchecked
        {
            _rewardsOf[from] -= amount;
            _rewardsOf[to] += amount;
        }

        emit RewardsTransferred(from, to, amount);
    }

    //***************************** endregion: internal centralization functions *****************************//



    //***************************** startregion: internal hooks *****************************//

    function _safeStake(
        address staker,
        address stakeToken,
        uint256 tokenId
    ) internal
    {
        // Stake token should be ERC721 token
        bool isSupportingERC721 = _isERC721Token(stakeToken);

        if(staker == address(0))
        {
            revert ZeroAddress();
        }
        if(isSupportingERC721 != true)
        {
            revert NotErc721();
        }


        // Hook is used for additional checks or actions
        _beforeStake(staker, stakeToken, tokenId);

        _isStakerOf[staker][stakeToken][tokenId] = true;

        // update info about staker
        _incrementUserBalance(_stakedInfoOf[staker][stakeToken], tokenId);

        // Send NFT from owner to this contract
        IERC721(stakeToken).safeTransferFrom(staker, address(this), tokenId);

        // Hook is used for additional checks or actions
        _afterStake(staker, stakeToken, tokenId);


        emit StakedNFT(staker, stakeToken, tokenId);
    }


    function _safeUnstake(
        address staker,
        address stakeToken,
        uint256 tokenId
    ) internal
    {
        StakedInfoNFT storage userStakingInfo = _stakedInfoOf[staker][stakeToken];
        uint256 currentBalance = userStakingInfo.ids.length;

        if(currentBalance == 0)
        {
            revert ZeroStakerBalance();
        }
        if(isStakerOf(staker, stakeToken, tokenId) == false)
        {
            revert StakerIsNotOwnerOf(stakeToken, tokenId);
        }


        // Hook is used for additional checks or actions
        _beforeUnstake(staker, stakeToken, tokenId);

        _isStakerOf[staker][stakeToken][tokenId] = false;

        // update user balance once if balance will be zero
        // so user can release his/her rewards without loosing any
        if(currentBalance - 1 == 0)
        {
            _updateRewards(staker, stakeToken);
        }

        // update info about staker
        _decrementUserBalance(userStakingInfo, tokenId);

        // Send NFT from owner to this contract
        IERC721(stakeToken).safeTransferFrom(address(this), staker, tokenId);
        
        // Hook is used for additional checks or actions
        _afterUnstake(staker, stakeToken, tokenId);

        emit UnstakedNFT(staker, stakeToken, tokenId);
    }

    function _rewardsCalculatedHook(
        address staker,
        address stakeToken
    ) 
        internal
        virtual
        view
        returns(uint256)
    {
        uint256 balance = _stakedInfoOf[staker][stakeToken].ids.length;
        return balance;
    }

    function _timeLeft(
        uint256 currentTimestamp,
        uint256 lastTimestamp
    ) internal pure returns(uint256)
    {
        if(lastTimestamp > currentTimestamp)
        {
            revert InvalidCurrentDate();
        }

        return (currentTimestamp - lastTimestamp) / getUpdatePeriod();
    }

    function _requireStakeToken(address stakeToken) internal view 
    {
        if(_isStakeToken[stakeToken] == false)
        {
            revert TokenIsNotAvailableToStake();
        }
    }

    
    function _updateRewards(address staker, address stakeToken) internal virtual
    {
        StakedInfoNFT storage userStakingInfo = _stakedInfoOf[staker][stakeToken];
        
        uint256 currentTimestamp = block.timestamp;

        // skip if timestamp less than next period
        if(currentTimestamp <= userStakingInfo.timestamp + getUpdatePeriod())
        {
            return;
        }

        uint256 balance = userStakingInfo.ids.length;

        // skip update if balance is 0
        if(balance == 0)
        {
            return;
        }

        _rewardsOf[staker] += _rewardsCalculatedHook(staker, stakeToken) * _timeLeft(currentTimestamp, userStakingInfo.timestamp);
        
        userStakingInfo.timestamp = currentTimestamp;
    }


    function _claimRewards(
        address staker,
        address stakeToken,
        uint256 amount
    ) internal virtual
    {
        // Update staker balance to last actual
        _updateRewards(staker, stakeToken);

        uint256 stakerRewards = rewardsOf(staker);

        // Anulate user stake balance greather then zero and 
        // transfer tokens to staker or do nothing otherwise
        if(stakerRewards < amount)
        {
            revert ClaimAmountExceedBalance();
        }
        
        _rewardsOf[staker] -= amount;
        IERC20(_REWARD_TOKEN).safeTransfer(staker, amount);
        
        // emit event only when rewards can be claimed
        emit RewardsClaimed(staker, stakerRewards);
    }


    function _beforeStake(address staker, address stakeToken, uint256 tokenId) internal virtual {}
    

    function _afterStake(address staker, address stakeToken, uint256 tokenId) internal virtual {}


    function _beforeUnstake(address staker, address stakeToken, uint256 tokenId) internal virtual {}
    

    function _afterUnstake(address staker, address stakeToken, uint256 tokenId) internal virtual {}

    //***************************** endregion: internal hooks *****************************//



    //***************************** startregion: private functions *****************************//

    function _incrementUserBalance(
        StakedInfoNFT storage userStakingInfo,
        uint256 tokenId
    ) private 
    {
        if(userStakingInfo.ids.length == 0)
        {
            userStakingInfo.timestamp = block.timestamp;
        }

        userStakingInfo.idPos[tokenId] = userStakingInfo.ids.length;
        userStakingInfo.ids.push(tokenId);
    }


    function _decrementUserBalance(
        StakedInfoNFT storage userStakingInfo,
        uint256 tokenId
    ) private
    {
        uint256 lastIdPos = userStakingInfo.ids.length - 1;
        
        if(userStakingInfo.ids[lastIdPos] == tokenId)
        {
            userStakingInfo.ids.pop();
        }
        
        // as we don't know where exactly our elements are in array
        // so as we save all arr positions in mapping we can use it here
        // to exctract exact pos and save gas skipping for each search
        else
        {
            uint256 currentElemPos = userStakingInfo.idPos[tokenId];

            // do swap between current elements and last to pop from array
            uint256 tmp = userStakingInfo.ids[currentElemPos];
            userStakingInfo.ids[currentElemPos] = userStakingInfo.ids[lastIdPos];
            userStakingInfo.ids[lastIdPos] = tmp;
            
            // delete current id from array
            userStakingInfo.ids.pop();
        }


        // if current user balance is zero anulate timestamp
        if(userStakingInfo.ids.length == 0)
        {
            delete userStakingInfo.timestamp;
        }

        delete userStakingInfo.idPos[tokenId];
    }


    function _isERC721Token(address stakeToken) private view returns (bool)
    {
        bytes4 IERC721Id = type(IERC721).interfaceId;
        ERC165Checker.supportsInterface(stakeToken, IERC721Id);
        return ERC165Checker.supportsInterface(stakeToken, IERC721Id);
    }


    //***************************** endregion: private functions *****************************//
}

// File: contracts/dapp-game/territory/ICflatsTerritory.sol



// SPDX-License-Identifier: MIT

/// @author Tient Technologies (Twitter:https://twitter.com/tient_tech | Github:https://github.com/Tient-Technologies | | LinkedIn:https://www.linkedin.com/company/tient-technologies/)
/// @dev NiceArti (https://github.com/NiceArti)
/// To maintain developer you can also donate to this address - 0xDc3d3fA1aEbd13fF247E5F5D84A08A495b3215FB
/// @title The interface for implementing the CflatsTerritory smart contract 
/// with a full description of each function and their implementation 
/// is presented to your attention.

pragma solidity ^0.8.18;

interface ICflatsTerritory 
{
    /// @dev Emitted when user buys territory
    /// `from` is the address that transfers terrotory to recepient
    /// `to` is the address of account that bought territory
    /// `gen` is the gen number of territory that relates to Cflats NFT
    event TerritoryTransfer(address indexed from, address indexed to, uint256 gen);


    /// @dev Shows address of utility token
    function utilityToken() external view returns(address);


    /// @dev Shows the amount of territories that users owns
    function balanceOf(address owner, uint256 gen) external view returns (uint256);


    /// @dev Shows if `owner` has territory for staking NFT gen
    /// @param owner is an account address of user
    /// @param gen is a number of gen that is allowed to stake token
    /// @return true if `owner` has territory for special `gen`
    function hasTerritoryForGen(
        address owner,
        uint256 gen
    ) external view returns (bool);


    /// @dev Shows the price for buying territory for special `gen`
    /// @param gen is a number of gen that is allowed to stake token
    /// @return true if `owner` has territory for special `gen`
    function getPriceForGen(uint256 gen) external view returns (uint256);


    /// @dev Allows user to buy territory using $CFLAT tokens  
    /// @param gen is a number of NFT gen
    /// @param amount of $CFLAT for buing territory
    /// 
    /// @custom:requires amount to be greather than or equal to price of territory
    ///
    /// @return true if `owner` has territory for special `gen`
    function buy(uint256 gen, uint256 amount) external returns (bool);
    
    
    /// @dev Allows user to transfer territory to anyone  
    /// @param recipient the address who will receive territory
    /// @param gen is a number of NFT gen
    /// 
    /// @custom:requires `owner` balance to be at least two
    ///
    /// @return true if `owner` has territory for special `gen`
    function transfer(
        address recipient,
        uint256 gen
    ) external returns (bool);
}

// File: contracts/harvest/Harvest.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./IHarvest.sol";


abstract contract Harvest is IHarvest
{
    using SafeERC20 for IERC20;

    // TEAM_WALLET
    address private constant _HARVEST_ADDRESS = 0x1d6B3E373B947319a4B76A851bb17C1dEcCADb1D;


    receive() external payable 
    {
        emit Thanks(msg.sender, msg.value);
    }


    /// the function of collecting tokens that got accidentally
    /// or specifically into a smart contract and were not recorded
    /// in storage
    /// NOTE: this function does not take user's funds from the contract,
    /// but will withdraw those funds that are considered to be
    /// donation and receiving funds!
    function collect(address token) public virtual returns (bool)
    {
        uint256 harvestAmount = _getHarvest(token);

        if(harvestAmount == 0)
        {
            return false;
        }

        if(token == address(0))
        {
            (bool sent, ) = payable(_HARVEST_ADDRESS).call{value: harvestAmount}("");
            require(sent, "Failed to send Ether!");
        }
        else
        {
            IERC20(token).safeTransfer(_HARVEST_ADDRESS, harvestAmount);
        }

        emit HarvestCollected();
        return true;
    }


    /// The main idea is to collect all funds that were sent in contract
    /// accidently or as donation, not to stole a whole contract balance
    /// NOTE: any funds received for a smart contract and not recorded
    /// in the storage are regarded as a donation that the team can take
    function _getHarvest(address token) internal view returns (uint256)
    {
        if(token == address(0))
        {
            return address(this).balance;
        }

        return IERC20Metadata(token).balanceOf(address(this));
    }


    function _extractDonationPercentage(uint256 fullAmount, uint16 percentage) internal pure returns (uint256)
    {
        // accuracy up to two characters
        // 10000 = 100.00
        return fullAmount * percentage / 10000;
    }
}

// File: contracts/harvest/IHarvest.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IHarvest
{
    event Thanks(address indexed donator, uint256 donation);
    event HarvestCollected();

    /// the function of collecting tokens that got accidentally
    /// or specifically into a smart contract and were not recorded
    /// in storage
    /// NOTE: this function does not take user's funds from the contract,
    /// but will withdraw those funds that are considered to be
    /// donation funds!
    function collect(address token) external returns (bool);
}

// File: contracts/interfaces/ICryptoflatsNFT.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

enum Type {
    Standart,
    Silver,
    Gold,
    Diamond
}

interface ICryptoflatsNFT is IERC721 
{
    function getNFTType(uint256 _tokenId) external view returns (Type);
}

// File: hardhat/console.sol



// SPDX-License-Identifier: MIT
pragma solidity >= 0.4.22 <0.9.0;

library console {
	address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);

	function _sendLogPayload(bytes memory payload) private view {
		uint256 payloadLength = payload.length;
		address consoleAddress = CONSOLE_ADDRESS;
		assembly {
			let payloadStart := add(payload, 32)
			let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0)
		}
	}

	function log() internal view {
		_sendLogPayload(abi.encodeWithSignature("log()"));
	}

	function logInt(int256 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(int256)", p0));
	}

	function logUint(uint256 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256)", p0));
	}

	function logString(string memory p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string)", p0));
	}

	function logBool(bool p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
	}

	function logAddress(address p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address)", p0));
	}

	function logBytes(bytes memory p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes)", p0));
	}

	function logBytes1(bytes1 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0));
	}

	function logBytes2(bytes2 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0));
	}

	function logBytes3(bytes3 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0));
	}

	function logBytes4(bytes4 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0));
	}

	function logBytes5(bytes5 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0));
	}

	function logBytes6(bytes6 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0));
	}

	function logBytes7(bytes7 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0));
	}

	function logBytes8(bytes8 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0));
	}

	function logBytes9(bytes9 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0));
	}

	function logBytes10(bytes10 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0));
	}

	function logBytes11(bytes11 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0));
	}

	function logBytes12(bytes12 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0));
	}

	function logBytes13(bytes13 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0));
	}

	function logBytes14(bytes14 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0));
	}

	function logBytes15(bytes15 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0));
	}

	function logBytes16(bytes16 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0));
	}

	function logBytes17(bytes17 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0));
	}

	function logBytes18(bytes18 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0));
	}

	function logBytes19(bytes19 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0));
	}

	function logBytes20(bytes20 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0));
	}

	function logBytes21(bytes21 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0));
	}

	function logBytes22(bytes22 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0));
	}

	function logBytes23(bytes23 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0));
	}

	function logBytes24(bytes24 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0));
	}

	function logBytes25(bytes25 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0));
	}

	function logBytes26(bytes26 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0));
	}

	function logBytes27(bytes27 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0));
	}

	function logBytes28(bytes28 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0));
	}

	function logBytes29(bytes29 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0));
	}

	function logBytes30(bytes30 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0));
	}

	function logBytes31(bytes31 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0));
	}

	function logBytes32(bytes32 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0));
	}

	function log(uint256 p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256)", p0));
	}

	function log(string memory p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string)", p0));
	}

	function log(bool p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
	}

	function log(address p0) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address)", p0));
	}

	function log(uint256 p0, uint256 p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1));
	}

	function log(uint256 p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1));
	}

	function log(uint256 p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1));
	}

	function log(uint256 p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1));
	}

	function log(string memory p0, uint256 p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1));
	}

	function log(string memory p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1));
	}

	function log(string memory p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1));
	}

	function log(string memory p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1));
	}

	function log(bool p0, uint256 p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1));
	}

	function log(bool p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1));
	}

	function log(bool p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1));
	}

	function log(bool p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1));
	}

	function log(address p0, uint256 p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1));
	}

	function log(address p0, string memory p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1));
	}

	function log(address p0, bool p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1));
	}

	function log(address p0, address p1) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1));
	}

	function log(uint256 p0, uint256 p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2));
	}

	function log(uint256 p0, uint256 p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2));
	}

	function log(uint256 p0, uint256 p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2));
	}

	function log(uint256 p0, uint256 p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2));
	}

	function log(uint256 p0, string memory p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2));
	}

	function log(uint256 p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2));
	}

	function log(uint256 p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2));
	}

	function log(uint256 p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2));
	}

	function log(uint256 p0, bool p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2));
	}

	function log(uint256 p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2));
	}

	function log(uint256 p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2));
	}

	function log(uint256 p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2));
	}

	function log(uint256 p0, address p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2));
	}

	function log(uint256 p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2));
	}

	function log(uint256 p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2));
	}

	function log(uint256 p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2));
	}

	function log(string memory p0, uint256 p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2));
	}

	function log(string memory p0, uint256 p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2));
	}

	function log(string memory p0, uint256 p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2));
	}

	function log(string memory p0, uint256 p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2));
	}

	function log(string memory p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2));
	}

	function log(string memory p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2));
	}

	function log(string memory p0, address p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2));
	}

	function log(string memory p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2));
	}

	function log(string memory p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2));
	}

	function log(string memory p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2));
	}

	function log(bool p0, uint256 p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2));
	}

	function log(bool p0, uint256 p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2));
	}

	function log(bool p0, uint256 p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2));
	}

	function log(bool p0, uint256 p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2));
	}

	function log(bool p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2));
	}

	function log(bool p0, bool p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2));
	}

	function log(bool p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2));
	}

	function log(bool p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2));
	}

	function log(bool p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2));
	}

	function log(bool p0, address p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2));
	}

	function log(bool p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2));
	}

	function log(bool p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2));
	}

	function log(bool p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2));
	}

	function log(address p0, uint256 p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2));
	}

	function log(address p0, uint256 p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2));
	}

	function log(address p0, uint256 p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2));
	}

	function log(address p0, uint256 p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2));
	}

	function log(address p0, string memory p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2));
	}

	function log(address p0, string memory p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2));
	}

	function log(address p0, string memory p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2));
	}

	function log(address p0, string memory p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2));
	}

	function log(address p0, bool p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2));
	}

	function log(address p0, bool p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2));
	}

	function log(address p0, bool p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2));
	}

	function log(address p0, bool p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2));
	}

	function log(address p0, address p1, uint256 p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2));
	}

	function log(address p0, address p1, string memory p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2));
	}

	function log(address p0, address p1, bool p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2));
	}

	function log(address p0, address p1, address p2) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2));
	}

	function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, uint256 p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3));
	}

	function log(uint256 p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, uint256 p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3));
	}

	function log(string memory p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, uint256 p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3));
	}

	function log(bool p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, uint256 p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, string memory p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, bool p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint256 p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint256 p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint256 p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, uint256 p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, string memory p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, bool p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, uint256 p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, string memory p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, bool p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3));
	}

	function log(address p0, address p1, address p2, address p3) internal view {
		_sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3));
	}

}