账户
0xb6...695d
0xb6...695D

0xb6...695D

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.23+commit.f704f362
语言
Solidity
合同源代码
文件 1 的 15:Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [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://consensys.net/diligence/blog/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.8.0/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);
        }
    }
}
合同源代码
文件 2 的 15:Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (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;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
合同源代码
文件 3 的 15:ERC165Checker.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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}.
     *
     * Some precompiled contracts will falsely indicate support for a given interface, so caution
     * should be exercised when using this function.
     *
     * 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;
    }
}
合同源代码
文件 4 的 15: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);
}
合同源代码
文件 5 的 15:IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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);
}
合同源代码
文件 6 的 15:IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/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.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
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].
     *
     * CAUTION: See Security Considerations above.
     */
    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);
}
合同源代码
文件 7 的 15:IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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);
}
合同源代码
文件 8 的 15:IIncentivesController.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.22;

/// @title IIncentivesController
/// @notice Interface for staking contract with multiple token rewards.
interface IIncentivesController {
    struct Reward {
        // Timestamp of the end of current rewards distribution.
        uint periodFinish;
        // Rewards distributed per second.
        uint rewardRate;
        // Timestamp of last update.
        uint lastUpdateTime;
        // Current snapshot of reward tokens per staked token.
        uint rewardPerTokenStored;
        // Current accounted for balance of reward tokens.
        uint balance;
    }

    struct Balances {
        // Staked tokens of the user.
        uint staked;
        // Staked tokens muplitplied by NFT multiplier.
        uint scaled;
        // Staked NFT token id.
        uint nftId;
        // If position is boosted.
        bool boosted;
    }

    struct RewardData {
        // Address of the reward token.
        address token;
        // Amount of the reward token.
        uint amount;
    }

    /**
     * Deposited event.
     * @param user Address of the user.
     * @param amount Amount of tokens deposited.
     * @param scaled Scaled amount of tokens withdrawn (NFT multiplier).
     */
    event Deposited(address indexed user, uint amount, uint scaled);

    /**
     * Withdrawn event.
     * @param user Address of the user.
     * @param amount Amount of tokens withdrawn.
     * @param scaled Scaled amount of tokens withdrawn (NFT multiplier).
     */
    event Withdrawn(address indexed user, uint amount, uint scaled);

    /**
     * RewardPaid event.
     * @param user Address of the user.
     * @param rewardsToken Address of the reward token.
     * @param reward Amount of the reward paid.
     */
    event RewardPaid(address indexed user, address indexed rewardsToken, uint reward);

    /**
     * Deposit staking tokens.
     * @param _amount Amount of staking token to deposit.
     * @param _onBehalfOf Receiver of the staked tokens.
     */
    function deposit(uint _amount, address _onBehalfOf) external;

    /**
     * Withdraw staking tokens while claiming rewards.
     * @param _amount Amount of staking token to withdraw.
     */
    function withdraw(uint _amount) external;

    /**
     * Get rewards accumulated for deposited tokens.
     * @param _rewardTokens Array of reward tokens to get reward for.
     */
    function getReward(address[] calldata _rewardTokens) external;

    /**
     * Get user claimable rewards.
     * @param _account Address of the user.
     * @return claimable Array of rewards claimable by the user.
     */
    function claimableRewards(address _account) external view returns (RewardData[] memory claimable);

    /**
     * Stake NFT to boost deposited staking tokens.
     * @param _tokenId NFT token if to stake.
     */
    function stakeNFT(uint _tokenId) external;

    /**
     * Unstake NFT loosing the boost.
     */
    function unstakeNFT() external;
}
合同源代码
文件 9 的 15:INFTAirdropVesting.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.22;

interface INFTAirdropVesting {
    /**
     * @notice Vesting position.
     * @param amount Amount of tokens vested.
     * @param amountClaimed Amount of tokens claimed.
     * @param startTimestamp Vesting start timestamp.
     */
    struct VestingPosition {
        uint256 amount;
        uint256 amountClaimed;
        uint256 startTimestamp;
    }

    error ArgumentIsIndexOutOfBound();

    /**
     * @notice Vest tokens.
     * @param _amount Amount of tokens to vest.
     * @param _tokenId Address to vest tokens to.
     */
    function vestTokens(uint256 _amount, address _identity, uint256 _tokenId) external;

    /**
     * @notice Claim tokens.
     * @param _positionIndexes Array of position indexes to claim tokens from.
     */
    function claimTokens(uint256 _tokenId, uint256[] calldata _positionIndexes) external;

    /**
     * @notice Get amount of tokens available to claim at given timestamp.
     * @param _tokenId Address to check.
     * @param _positionIndex Position index to check.
     * @param _timestamp Timestamp to check.
     * @return Amount of tokens available to claim.
     */
    function availableToClaim(uint256 _tokenId, uint256 _positionIndex, uint256 _timestamp) external view returns (uint256);

    /**
     * @notice Get vesting positions for the given address.
     * @param _tokenId Address to get vesting positions for.
     * @return Array of vesting positions.
     */
    function getVestingPositions(uint256 _tokenId) external view returns (VestingPosition[] memory);

    /**
     * @notice Get vesting schedule for the given address and position index.
     * @param _tokenId Address to get vesting schedule for.
     * @param _positionIndex Position index to get vesting schedule for.
     * @return timestamps Array of timestamps of token unlock times.
     * @return amounts Array of amounts at token unlocks.
     */
    function getVestingSchedule(
        uint256 _tokenId,
        uint256 _positionIndex
    ) external view returns (uint256[12] memory timestamps, uint256[12] memory amounts);

    error WrongTokenIdOwner();
    error AmountTooSmall();
    error AmountExceedsAllocation();
    error IncorrectEtherValueSent();
    error ArgumentIsZero();
    error ArgumentIsAddressZero();
    error TgeTimestampInPast();
    error TokensNotUnlocked();

    event TokensVested(uint256 indexed _tokenId, uint256 indexed positionIndex, uint256 amount_);
    event TokensClaimed(address indexed _buyer, uint256 indexed _tokenId, uint256 indexed positionIndex, uint256 amount_);
}
合同源代码
文件 10 的 15:INFTWithLevel.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

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

interface INFTWithLevel is IERC721 {
    /**
     * @notice Returns level of the specified token.
     * @param _tokenId Token id.
     * @return level_ Level of the token.
     */
    function getLevelOfTokenById(uint256 _tokenId) external view returns (uint256 level_);
}
合同源代码
文件 11 的 15:IncentivesController.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import '@openzeppelin/contracts/utils/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import './interfaces/IIncentivesController.sol';
import './interfaces/INFTWithLevel.sol';

/// @title IncentivesController
/// @notice Staking contract with multiple token rewards.
contract IncentivesController is IIncentivesController, Ownable {
    using SafeMath for uint;
    using SafeERC20 for IERC20;

    /// @notice Period during which new rewards are distributed.
    uint public rewardsDuration = 86400 * 45;

    /// @notice Amounts of user's accumulated rewards.
    mapping(address => mapping(address => uint)) public rewards;

    /// @notice Info about reward tokens distribution.
    mapping(address => Reward) public rewardData;

    /// @dev User balances.
    mapping(address => Balances) public balances;

    /// @notice Staking token for depositing.
    IERC20 public immutable stakingToken;

    /// @notice NFT collection for boosting rewards.
    INFTWithLevel public immutable boosterNFT;

    /// @notice Array of registered reward tokens.
    address[] public rewardTokens;

    /// @notice Total scaled balances of deposited staking token.
    uint public totalScaled;

    /// @notice Snapshot of user's accounted reward tokens.
    mapping(address => mapping(address => uint)) private userRewardPerTokenPaid;

    constructor(IERC20 _stakingToken, INFTWithLevel _boosterNFT) Ownable() {
        stakingToken = _stakingToken;
        boosterNFT = _boosterNFT;
    }

    /** EXTERNAL FUNCTIONS **/

    /**
     * @inheritdoc IIncentivesController
     */
    function deposit(uint _amount, address _onBehalfOf) external {
        require(_amount > 0, 'Amount is zero');
        _updateReward(_onBehalfOf, rewardTokens);
        Balances storage bal = balances[_onBehalfOf];
        uint scaled = _amount;
        if (bal.boosted) {
            uint multiplier = _getMultiplier(bal.nftId);
            scaled = _amount.mul(multiplier).div(10);
        }
        totalScaled = totalScaled.add(scaled);
        bal.staked = bal.staked.add(_amount);
        bal.scaled = bal.scaled.add(scaled);
        stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
        emit Deposited(_onBehalfOf, _amount, scaled);
    }

    /**
     * @inheritdoc IIncentivesController
     */
    function withdraw(uint _amount) external {
        Balances storage bal = balances[msg.sender];
        require(_amount <= bal.staked, 'Amount greater than staked');
        _updateReward(msg.sender, rewardTokens);
        _getReward(rewardTokens);
        uint scaled = _amount;
        if (bal.boosted) {
            uint multiplier = _getMultiplier(bal.nftId);
            scaled = _amount.mul(multiplier).div(10);
        }
        if (_amount == bal.staked) {
            scaled = bal.scaled;
        }
        bal.staked = bal.staked.sub(_amount);
        bal.scaled = bal.scaled.sub(scaled);
        totalScaled = totalScaled.sub(scaled);
        stakingToken.safeTransfer(msg.sender, _amount);
        emit Withdrawn(msg.sender, _amount, scaled);
    }

    /**
     * @inheritdoc IIncentivesController
     */
    function stakeNFT(uint _tokenId) external {
        require(msg.sender == boosterNFT.ownerOf(_tokenId), 'Must be NFT owner');
        _updateReward(msg.sender, rewardTokens);
        Balances storage bal = balances[msg.sender];
        require(!bal.boosted, 'Balance already boosted');

        uint multiplier = _getMultiplier(_tokenId);
        uint scaled = bal.staked.mul(multiplier).div(10);
        uint newScaled = scaled - bal.staked;

        totalScaled = totalScaled.add(newScaled);
        bal.scaled = bal.scaled.add(newScaled);
        bal.nftId = _tokenId;
        bal.boosted = true;

        boosterNFT.transferFrom(msg.sender, address(this), _tokenId);
        emit Deposited(msg.sender, 0, newScaled);
    }

    /**
     * @inheritdoc IIncentivesController
     */
    function unstakeNFT() external {
        _updateReward(msg.sender, rewardTokens);
        _getReward(rewardTokens);
        Balances storage bal = balances[msg.sender];
        require(bal.boosted, 'NFT not staked');

        uint takenOut = bal.scaled - bal.staked;

        totalScaled = totalScaled.sub(takenOut);
        bal.scaled = bal.staked;
        bal.boosted = false;

        boosterNFT.transferFrom(address(this), msg.sender, bal.nftId);
        emit Withdrawn(msg.sender, 0, takenOut);
    }

    /**
     * @inheritdoc IIncentivesController
     */
    function getReward(address[] calldata _rewardTokens) external {
        _updateReward(msg.sender, _rewardTokens);
        _getReward(_rewardTokens);
    }

    /** VIEW FUNCTIONS **/

    /**
     * Calcualte last valid timestamp for reward token dsitribution.
     * @param _rewardsToken Address of the reward token.
     * @return Timestamp of last valid reward distribution.
     */
    function lastTimeRewardApplicable(address _rewardsToken) public view returns (uint) {
        uint periodFinish = rewardData[_rewardsToken].periodFinish;
        return block.timestamp < periodFinish ? block.timestamp : periodFinish;
    }

    /**
     * @inheritdoc IIncentivesController
     */
    function claimableRewards(address _account) external view returns (RewardData[] memory claimable) {
        claimable = new RewardData[](rewardTokens.length);
        for (uint i = 0; i < claimable.length; i++) {
            claimable[i].token = rewardTokens[i];
            claimable[i].amount = _earned(
                _account,
                claimable[i].token,
                balances[_account].scaled,
                _rewardPerToken(rewardTokens[i], totalScaled)
            ).div(1e12);
        }
        return claimable;
    }

    /** OWNER FUNCTIONS **/

    /**
     * Register token as a reward.
     * @param _rewardToken Address of the reward token.
     */
    function addReward(address _rewardToken) external onlyOwner {
        require(_rewardToken != address(stakingToken), 'Staking token is not reward');
        require(rewardData[_rewardToken].lastUpdateTime == 0);
        rewardTokens.push(_rewardToken);
        rewardData[_rewardToken].lastUpdateTime = block.timestamp;
        rewardData[_rewardToken].periodFinish = block.timestamp;
    }

    /**
     * Add reward tokens for distribution over next 7 days.
     * @param _rewardTokens Array of addresses of the reward tokens.
     * @param _amounts Array of amounts of rewards tokens.
     */
    function notifyReward(address[] calldata _rewardTokens, uint[] calldata _amounts) external onlyOwner {
        _updateReward(address(this), _rewardTokens);
        uint length = _rewardTokens.length;
        for (uint i; i < length; i++) {
            address token = _rewardTokens[i];
            Reward storage r = rewardData[token];
            require(r.periodFinish > 0, 'Unknown reward token');
            IERC20(token).safeTransferFrom(msg.sender, address(this), _amounts[i]);
            uint unseen = IERC20(token).balanceOf(address(this)).sub(r.balance);
            _notifyReward(token, unseen);
            r.balance = r.balance.add(unseen);
        }
    }

    /**
     * Set duration of rewards distribution for new rewards.
     * @param _newRewardsDuration Rewards duration in seconds.
     */
    function setRewardsDuration(uint256 _newRewardsDuration) external onlyOwner {
        require(_newRewardsDuration > 0, 'Duration is zero');
        rewardsDuration = _newRewardsDuration;
    }

    /** INTERNAL FUNCTIONS **/

    function _getReward(address[] memory _rewardTokens) internal {
        uint length = _rewardTokens.length;
        for (uint i; i < length; i++) {
            address token = _rewardTokens[i];
            uint reward = rewards[msg.sender][token].div(1e12);
            Reward storage r = rewardData[token];
            // This is only called after _updateReward() which check below require():
            // require(r.periodFinish > 0, 'Unknown reward token');
            r.balance = r.balance.sub(reward);
            if (reward == 0) continue;
            rewards[msg.sender][token] = 0;
            IERC20(token).safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, token, reward);
        }
    }

    function _rewardPerToken(address _rewardsToken, uint _supply) internal view returns (uint) {
        if (_supply == 0) {
            return rewardData[_rewardsToken].rewardPerTokenStored;
        }
        return
            rewardData[_rewardsToken].rewardPerTokenStored.add(
                lastTimeRewardApplicable(_rewardsToken)
                    .sub(rewardData[_rewardsToken].lastUpdateTime)
                    .mul(rewardData[_rewardsToken].rewardRate)
                    .mul(1e18)
                    .div(_supply)
            );
    }

    function _earned(
        address _user,
        address _rewardsToken,
        uint _balance,
        uint _currentRewardPerToken
    ) internal view returns (uint) {
        return
            _balance.mul(_currentRewardPerToken.sub(userRewardPerTokenPaid[_user][_rewardsToken])).div(1e18).add(
                rewards[_user][_rewardsToken]
            );
    }

    function _notifyReward(address _rewardsToken, uint _reward) internal {
        Reward storage r = rewardData[_rewardsToken];
        if (block.timestamp >= r.periodFinish) {
            r.rewardRate = _reward.mul(1e12).div(rewardsDuration);
        } else {
            uint remaining = r.periodFinish.sub(block.timestamp);
            uint leftover = remaining.mul(r.rewardRate).div(1e12);
            r.rewardRate = _reward.add(leftover).mul(1e12).div(rewardsDuration);
        }
        r.lastUpdateTime = block.timestamp;
        r.periodFinish = block.timestamp.add(rewardsDuration);
    }

    function _updateReward(address _account, address[] memory _rewardTokens) internal {
        uint length = _rewardTokens.length;
        for (uint i = 0; i < length; i++) {
            address token = _rewardTokens[i];
            Reward storage r = rewardData[token];
            require(r.periodFinish > 0, 'Unknown reward token');
            uint rpt = _rewardPerToken(token, totalScaled);
            r.rewardPerTokenStored = rpt;
            r.lastUpdateTime = lastTimeRewardApplicable(token);
            if (_account != address(this)) {
                rewards[_account][token] = _earned(_account, token, balances[_account].scaled, rpt);
                userRewardPerTokenPaid[_account][token] = rpt;
            }
        }
    }

    /**
     * @notice Get multiplier for the given NFT token.
     * @param _tokenId NFT token id.
     */
    function _getMultiplier(uint256 _tokenId) internal view returns (uint256) {
        uint256 level = boosterNFT.getLevelOfTokenById(_tokenId);

        if (level == 0) {
            // Diamond
            return 50; // 5 * SCALING_FACTOR
        } else if (level == 1) {
            // Platinum
            return 40; // 4 * SCALING_FACTOR
        } else if (level == 2) {
            // Gold
            return 30; // 3 * SCALING_FACTOR
        } else if (level == 3) {
            // Silver
            return 20; // 2 * SCALING_FACTOR
        } else if (level == 4) {
            // Bronze
            return 15; // 1.5 * SCALING_FACTOR
        } else {
            revert('Invalid token level');
        }
    }
}
合同源代码
文件 12 的 15:NFTAirdropVesting.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.22;

import {ERC165Checker} from '@openzeppelin/contracts/utils/introspection/ERC165Checker.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import './interfaces/INFTAirdropVesting.sol';
import './IncentivesController.sol';

// TODO change events
contract NFTAirdropVesting is INFTAirdropVesting, Ownable {
    using SafeERC20 for IERC20;

    /// @notice Mapping of account to vesting positions.
    mapping(uint256 => VestingPosition[]) public vestingPositions;

    /// @dev Array of Incentives controllers to check if the NFT is staked.
    IncentivesController[] public incentivesControllers;

    /// @notice Token Generation Event timestamp.
    uint256 public immutable tgeTimestamp;

    /// @notice FMBC token.
    IERC20 public immutable fmbcToken;

    /// @dev ERC721 collection to verify against.
    IERC721 public immutable collection;

    /// @dev Percent of tokens unlocked at TGE.
    uint256 constant UNLOCKED_AT_TGE = 833;
    /// @dev Percent of tokens unlocked monthly.
    uint256 constant UNLOCKED_MONTHLY = 833;
    /// @dev Precision for percentage calculations.
    uint256 constant PRECISION = 10000;
    /// @dev Number of monthly unlocks.
    uint256 constant NO_MONTHLY_UNLOCKS = 11;

    /**
     * @notice Constructor.
     * @param _tgeTimestamp Token Generation Event timestamp.
     * @param _fmbcTokenAddress FMBC token address.
     */
    constructor(uint256 _tgeTimestamp, IERC20 _fmbcTokenAddress, address _collection) {
        if (_tgeTimestamp < block.timestamp) revert TgeTimestampInPast();
        if (address(_fmbcTokenAddress) == address(0)) revert ArgumentIsAddressZero();
        require(ERC165Checker.supportsInterface(_collection, type(IERC721).interfaceId));

        collection = IERC721(_collection);
        fmbcToken = _fmbcTokenAddress;
        tgeTimestamp = _tgeTimestamp;
    }

    /**
     * @inheritdoc INFTAirdropVesting
     */
    function vestTokens(uint256 _amount, address _identity, uint256 _tokenId) external {
        if (_amount == 0) revert ArgumentIsZero();

        fmbcToken.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 positionIndex = vestingPositions[_tokenId].length;

        // We allow anyone to create positions for NFT token id.
        // There is no harm in that, and AirdropNFTHolders.sol already verifies users.

        if (positionIndex == 1 && block.timestamp < tgeTimestamp) {
            // Update vesting position
            VestingPosition storage vestingPosition = vestingPositions[_tokenId][0];
            vestingPosition.amount += _amount;
            positionIndex = 0;
        } else {
            // Add new vesting position
            uint256 startTimestamp = tgeTimestamp;
            uint256 unlockedAmount = 0;
            if (block.timestamp >= tgeTimestamp) {
                startTimestamp = block.timestamp;
                unlockedAmount = (_amount * UNLOCKED_AT_TGE) / PRECISION;
                fmbcToken.safeTransfer(_identity, unlockedAmount);
                emit TokensClaimed(_identity, _tokenId, positionIndex, unlockedAmount);
            }
            vestingPositions[_tokenId].push(VestingPosition(_amount, unlockedAmount, startTimestamp));
        }
        emit TokensVested(_tokenId, positionIndex, _amount);
    }

    /**
     * @inheritdoc INFTAirdropVesting
     */
    function claimTokens(uint256 _tokenId, uint256[] calldata _positionIndexes) external {
        address tokenOwner = collection.ownerOf(_tokenId);
        require(msg.sender == tokenOwner || _isNftStaked(msg.sender, _tokenId), 'Only token owner can claim tokens');
        uint256 length = _positionIndexes.length;
        for (uint256 i = 0; i < length; ) {
            if (block.timestamp < tgeTimestamp) revert TokensNotUnlocked();
            VestingPosition storage vestingPosition = vestingPositions[_tokenId][_positionIndexes[i]];
            uint256 amountToClaim = availableToClaim(_tokenId, _positionIndexes[i], block.timestamp);
            vestingPosition.amountClaimed += amountToClaim;
            fmbcToken.safeTransfer(msg.sender, amountToClaim);
            emit TokensClaimed(msg.sender, _tokenId, _positionIndexes[i], amountToClaim);
            unchecked {
                ++i;
            }
        }
    }

    /**
     * @inheritdoc INFTAirdropVesting
     */
    function availableToClaim(uint256 _tokenId, uint256 _positionIndex, uint256 _timestamp) public view returns (uint256) {
        if (_timestamp < tgeTimestamp) return 0;
        VestingPosition storage vestingPosition = vestingPositions[_tokenId][_positionIndex];
        uint256 timeSinceStart = _timestamp - vestingPosition.startTimestamp;
        uint256 numberOfUnlocks = timeSinceStart / 30 days;
        uint256 amountUnlocked = numberOfUnlocks >= NO_MONTHLY_UNLOCKS
            ? vestingPosition.amount
            : (vestingPosition.amount * (UNLOCKED_AT_TGE + (numberOfUnlocks * UNLOCKED_MONTHLY))) / PRECISION;
        uint256 amountToClaim = amountUnlocked - vestingPosition.amountClaimed;
        return amountToClaim;
    }

    /**
     * @inheritdoc INFTAirdropVesting
     */
    function getVestingPositions(uint256 _tokenId) external view returns (VestingPosition[] memory) {
        return vestingPositions[_tokenId];
    }

    /**
     * @inheritdoc INFTAirdropVesting
     */
    function getVestingSchedule(
        uint256 _tokenId,
        uint256 _positionIndex
    ) external view returns (uint256[NO_MONTHLY_UNLOCKS + 1] memory timestamps, uint256[NO_MONTHLY_UNLOCKS + 1] memory amounts) {
        VestingPosition storage vestingPosition = vestingPositions[_tokenId][_positionIndex];
        uint256 amountUnlocked = 0;
        timestamps[0] = vestingPosition.startTimestamp;
        amounts[0] = (vestingPosition.amount * UNLOCKED_AT_TGE) / PRECISION;
        for (uint256 i = 1; i < NO_MONTHLY_UNLOCKS + 1; ) {
            amountUnlocked += amounts[i - 1];
            timestamps[i] = timestamps[i - 1] + 30 days;
            uint256 tmp = ((vestingPosition.amount * (UNLOCKED_AT_TGE + (i * UNLOCKED_MONTHLY))) / PRECISION);
            amounts[i] = i == NO_MONTHLY_UNLOCKS ? vestingPosition.amount - amountUnlocked : tmp - amountUnlocked;
            unchecked {
                ++i;
            }
        }
    }

    /**
     * @dev Add an IncentivesController to the array
     *
     * @param _controller The address of the IncentivesController to add
     */
    function addIncentivesController(address _controller) external onlyOwner {
        if (_controller == address(0)) revert ArgumentIsAddressZero();
        incentivesControllers.push(IncentivesController(_controller));
    }

    /**
     * @dev Remove an IncentivesController from the array
     *
     * @param index The index of the IncentivesController to remove
     */
    function removeIncentivesController(uint index) external onlyOwner {
        if (index >= incentivesControllers.length) revert ArgumentIsIndexOutOfBound();
        incentivesControllers[index] = incentivesControllers[incentivesControllers.length - 1];
        incentivesControllers.pop();
    }

    /**
     * @dev Get the count of IncentivesControllers
     */
    function getIncentivesControllersCount() external view returns (uint256) {
        return incentivesControllers.length;
    }

    /**
     * @dev Check if the NFT is staked in any of the IncentivesControllers
     *
     * @param identity        The identity to check
     * @param tokenId         The NFT to check
     */
    function _isNftStaked(address identity, uint256 tokenId) internal view returns (bool) {
        for (uint i = 0; i < incentivesControllers.length; i++) {
            (, , uint nftId, bool boosted) = incentivesControllers[i].balances(identity);
            if (boosted && nftId == tokenId) {
                return true;
            }
        }
        return false;
    }
}
合同源代码
文件 13 的 15:Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
合同源代码
文件 14 的 15:SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/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;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    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));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    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");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    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");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation 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).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // 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 cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}
合同源代码
文件 15 的 15:SafeMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}
设置
{
  "compilationTarget": {
    "contracts/NFTAirdropVesting.sol": "NFTAirdropVesting"
  },
  "evmVersion": "paris",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"uint256","name":"_tgeTimestamp","type":"uint256"},{"internalType":"contract IERC20","name":"_fmbcTokenAddress","type":"address"},{"internalType":"address","name":"_collection","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AmountExceedsAllocation","type":"error"},{"inputs":[],"name":"AmountTooSmall","type":"error"},{"inputs":[],"name":"ArgumentIsAddressZero","type":"error"},{"inputs":[],"name":"ArgumentIsIndexOutOfBound","type":"error"},{"inputs":[],"name":"ArgumentIsZero","type":"error"},{"inputs":[],"name":"IncorrectEtherValueSent","type":"error"},{"inputs":[],"name":"TgeTimestampInPast","type":"error"},{"inputs":[],"name":"TokensNotUnlocked","type":"error"},{"inputs":[],"name":"WrongTokenIdOwner","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_buyer","type":"address"},{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"TokensClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"positionIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"TokensVested","type":"event"},{"inputs":[{"internalType":"address","name":"_controller","type":"address"}],"name":"addIncentivesController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_positionIndex","type":"uint256"},{"internalType":"uint256","name":"_timestamp","type":"uint256"}],"name":"availableToClaim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256[]","name":"_positionIndexes","type":"uint256[]"}],"name":"claimTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collection","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fmbcToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getIncentivesControllersCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getVestingPositions","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"amountClaimed","type":"uint256"},{"internalType":"uint256","name":"startTimestamp","type":"uint256"}],"internalType":"struct INFTAirdropVesting.VestingPosition[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_positionIndex","type":"uint256"}],"name":"getVestingSchedule","outputs":[{"internalType":"uint256[12]","name":"timestamps","type":"uint256[12]"},{"internalType":"uint256[12]","name":"amounts","type":"uint256[12]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"incentivesControllers","outputs":[{"internalType":"contract IncentivesController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"removeIncentivesController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tgeTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_identity","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"vestTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"vestingPositions","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"amountClaimed","type":"uint256"},{"internalType":"uint256","name":"startTimestamp","type":"uint256"}],"stateMutability":"view","type":"function"}]