账户
0x8d...2cb3
0x8d...2CB3

0x8d...2CB3

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.24+commit.e11b9ed9
语言
Solidity
合同源代码
文件 1 的 34: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);
        }
    }
}
合同源代码
文件 2 的 34:BaseCCIPContract.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;


contract BaseCCIPContract {
    error InvalidRouter(address router);
    error UnauthorizedCCIPSender();

    address internal immutable CCIP_ROUTER;

    /// @dev Linked CCIP contracts
    /// The mapping key is a packed bytes32 with the following bit mapping
    /// [0..159]    address sourceContract
    /// [160..223]  uint64  sourceChainSelector
    mapping(bytes32 => bool) internal _ccipContracts;

    constructor(address router) {
        CCIP_ROUTER = router;
    }

    /// @notice Return the current router
    /// @return Current CCIP Router address
    function getCCIPRouter() external view returns (address) {
        return CCIP_ROUTER;
    }

    /// @notice Manage approved counterpart CCIP contracts
    /// @param contractAddress Address of counterpart contract on the remote chain
    /// @param chainSelector CCIP Chain selector of the remote chain
    /// @param enabled Boolean representing whether this counterpart should be allowed or denied
    function _setCCIPCounterpart(
        address contractAddress,
        uint64 chainSelector,
        bool enabled
    ) internal {
        bytes32 counterpart = _packCCIPContract(contractAddress, chainSelector);
        _ccipContracts[counterpart] = enabled;
    }

    function _packCCIPContract(address contractAddress, uint64 chainSelector) internal pure returns(bytes32) {
        return bytes32(
            uint256(uint160(contractAddress)) |
            uint256(chainSelector) << 160
        );
    }
}
合同源代码
文件 3 的 34:BaseCCIPReceiver.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol";
import "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import "./BaseCCIPContract.sol";

/// @title CCIPReceiver - Base contract for CCIP applications that can receive messages.
abstract contract BaseCCIPReceiver is BaseCCIPContract, IAny2EVMMessageReceiver, IERC165 {

  /// @dev only calls from the set router are accepted.
  modifier onlyRouter() {
    if (msg.sender != CCIP_ROUTER) revert InvalidRouter(msg.sender);
    _;
  }

  /// @notice IERC165 supports an interfaceId
  /// @param interfaceId The interfaceId to check
  /// @return true if the interfaceId is supported
  /// @dev Should indicate whether the contract implements IAny2EVMMessageReceiver
  /// e.g. return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId
  /// This allows CCIP to check if ccipReceive is available before calling it.
  /// If this returns false or reverts, only tokens are transferred to the receiver.
  /// If this returns true, tokens are transferred and ccipReceive is called atomically.
  /// Additionally, if the receiver address does not have code associated with
  /// it at the time of execution (EXTCODESIZE returns 0), only tokens will be transferred.
  function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
    return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
  }

  /// @inheritdoc IAny2EVMMessageReceiver
  function ccipReceive(Client.Any2EVMMessage calldata message) external virtual override onlyRouter {
    _ccipReceive(message);
  }

  /// @notice Override this function in your implementation.
  /// @param message Any2EVMMessage
  function _ccipReceive(Client.Any2EVMMessage memory message) internal virtual;

}
合同源代码
文件 4 的 34:BaseCCIPSender.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
import "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";

import "./BaseCCIPContract.sol";
import "./BaseLinkConsumer.sol";

abstract contract BaseCCIPSender is BaseCCIPContract, BaseLinkConsumer {
    error MissingCCIPParams();
    error InsufficientLinkBalance(uint256 balance, uint256 required);

    /// @dev extraArgs for ccip message
    bytes private _ccipExtraArgs;

    function _sendCCIPMessage(
        bytes32 packedCcipCounterpart,
        bytes memory data
    ) internal returns(bytes32) {
        address ccipDestAddress = address(uint160(uint256(packedCcipCounterpart)));
        uint64 chainSelector = uint64(uint256(packedCcipCounterpart) >> 160);
        return _sendCCIPMessage(ccipDestAddress, chainSelector, data);
    }

    function _sendCCIPMessage(
        address ccipDestAddress,
        uint64 ccipDestChainSelector,
        bytes memory data
    ) internal returns(bytes32 messageId) {
        if (ccipDestAddress == address(0) || ccipDestChainSelector == uint64(0)) {
            revert MissingCCIPParams();
        }

        // Send CCIP message to the desitnation contract
        IRouterClient router = IRouterClient(CCIP_ROUTER);
        LinkTokenInterface linkToken = LinkTokenInterface(LINK_TOKEN);

        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(ccipDestAddress),
            data: data,
            tokenAmounts: new Client.EVMTokenAmount[](0),
            extraArgs: _ccipExtraArgs,
            feeToken: LINK_TOKEN
        });

        uint256 fee = router.getFee(
            ccipDestChainSelector,
            message
        );
        uint256 currentLinkBalance = linkToken.balanceOf(address(this));

        if (fee > currentLinkBalance) {
            revert InsufficientLinkBalance(currentLinkBalance, fee);
        }

        messageId = router.ccipSend(
            ccipDestChainSelector,
            message
        );
    }

    function _setCCIPExtraArgs(bytes calldata extraArgs) internal {
        _ccipExtraArgs = extraArgs;
    }
}
合同源代码
文件 5 的 34:BaseLinkConsumer.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;
import "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";

abstract contract BaseLinkConsumer {
  address internal immutable LINK_TOKEN;

  error LinkApprovalFailed();

  constructor(address token, address approvedSpender) {
    bool approved = LinkTokenInterface(token).approve(approvedSpender, type(uint256).max);
    if (!approved) {
      revert LinkApprovalFailed();
    }
    LINK_TOKEN = token;
  }

  /// @notice Return the LINK Token address
  /// @return Address of the LINK token
  function getLinkToken() external view returns (address) {
    return LINK_TOKEN;
  }
}
合同源代码
文件 6 的 34:Bits.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

library Bits {
    /**
     * @dev get bit at offset [offset]
     */
    function getBool(bytes32 p, uint8 offset) internal pure returns (bool r) {
        assembly {
            r := and(shr(offset, p), 1)
        }
    }

    /**
     * @dev set bit [offset] to {value}
     */
    function setBool(
        bytes32 p,
        uint8 offset,
        bool value
    ) internal pure returns (bytes32 np) {
        assembly {
            np := or(
                and(
                    p,
                    xor(
                        0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
                        shl(offset, 1)
                    )
                ),
                shl(offset, value)
            )
        }
    }
}
合同源代码
文件 7 的 34:Client.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// End consumer library.
library Client {
  /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
  struct EVMTokenAmount {
    address token; // token address on the local chain.
    uint256 amount; // Amount of tokens.
  }

  struct Any2EVMMessage {
    bytes32 messageId; // MessageId corresponding to ccipSend on source.
    uint64 sourceChainSelector; // Source chain selector.
    bytes sender; // abi.decode(sender) if coming from an EVM chain.
    bytes data; // payload sent in original message.
    EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.
  }

  // If extraArgs is empty bytes, the default is 200k gas limit.
  struct EVM2AnyMessage {
    bytes receiver; // abi.encode(receiver address) for dest EVM chains
    bytes data; // Data payload
    EVMTokenAmount[] tokenAmounts; // Token transfers
    address feeToken; // Address of feeToken. address(0) means you will send msg.value.
    bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1)
  }

  // bytes4(keccak256("CCIP EVMExtraArgsV1"));
  bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
  struct EVMExtraArgsV1 {
    uint256 gasLimit;
  }

  function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {
    return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
  }
}
合同源代码
文件 8 的 34:ConfirmedOwner.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol";

/// @title The ConfirmedOwner contract
/// @notice A contract with helpers for basic contract ownership.
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
  constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
合同源代码
文件 9 的 34:ConfirmedOwnerWithProposal.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IOwnable} from "../interfaces/IOwnable.sol";

/// @title The ConfirmedOwner contract
/// @notice A contract with helpers for basic contract ownership.
contract ConfirmedOwnerWithProposal is IOwnable {
  address private s_owner;
  address private s_pendingOwner;

  event OwnershipTransferRequested(address indexed from, address indexed to);
  event OwnershipTransferred(address indexed from, address indexed to);

  constructor(address newOwner, address pendingOwner) {
    // solhint-disable-next-line gas-custom-errors
    require(newOwner != address(0), "Cannot set owner to zero");

    s_owner = newOwner;
    if (pendingOwner != address(0)) {
      _transferOwnership(pendingOwner);
    }
  }

  /// @notice Allows an owner to begin transferring ownership to a new address.
  function transferOwnership(address to) public override onlyOwner {
    _transferOwnership(to);
  }

  /// @notice Allows an ownership transfer to be completed by the recipient.
  function acceptOwnership() external override {
    // solhint-disable-next-line gas-custom-errors
    require(msg.sender == s_pendingOwner, "Must be proposed owner");

    address oldOwner = s_owner;
    s_owner = msg.sender;
    s_pendingOwner = address(0);

    emit OwnershipTransferred(oldOwner, msg.sender);
  }

  /// @notice Get the current owner
  function owner() public view override returns (address) {
    return s_owner;
  }

  /// @notice validate, transfer ownership, and emit relevant events
  function _transferOwnership(address to) private {
    // solhint-disable-next-line gas-custom-errors
    require(to != msg.sender, "Cannot transfer to self");

    s_pendingOwner = to;

    emit OwnershipTransferRequested(s_owner, to);
  }

  /// @notice validate access
  function _validateOwnership() internal view {
    // solhint-disable-next-line gas-custom-errors
    require(msg.sender == s_owner, "Only callable by owner");
  }

  /// @notice Reverts if called by anyone other than the contract owner.
  modifier onlyOwner() {
    _validateOwnership();
    _;
  }
}
合同源代码
文件 10 的 34:ECDSA.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}
合同源代码
文件 11 的 34:IAny2EVMMessageReceiver.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Client} from "../libraries/Client.sol";

/// @notice Application contracts that intend to receive messages from
/// the router should implement this interface.
interface IAny2EVMMessageReceiver {
  /// @notice Called by the Router to deliver a message.
  /// If this reverts, any token transfers also revert. The message
  /// will move to a FAILED state and become available for manual execution.
  /// @param message CCIP Message
  /// @dev Note ensure you check the msg.sender is the OffRampRouter
  function ccipReceive(Client.Any2EVMMessage calldata message) external;
}
合同源代码
文件 12 的 34:IERC1155.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}
合同源代码
文件 13 的 34:IERC1155MetadataURI.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}
合同源代码
文件 14 的 34: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);
}
合同源代码
文件 15 的 34: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);
}
合同源代码
文件 16 的 34: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);
}
合同源代码
文件 17 的 34:IOwnable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IOwnable {
  function owner() external returns (address);

  function transferOwnership(address recipient) external;

  function acceptOwnership() external;
}
合同源代码
文件 18 的 34:IRouterClient.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Client} from "../libraries/Client.sol";

interface IRouterClient {
  error UnsupportedDestinationChain(uint64 destChainSelector);
  error InsufficientFeeTokenAmount();
  error InvalidMsgValue();

  /// @notice Checks if the given chain ID is supported for sending/receiving.
  /// @param chainSelector The chain to check.
  /// @return supported is true if it is supported, false if not.
  function isChainSupported(uint64 chainSelector) external view returns (bool supported);

  /// @notice Gets a list of all supported tokens which can be sent or received
  /// to/from a given chain id.
  /// @param chainSelector The chainSelector.
  /// @return tokens The addresses of all tokens that are supported.
  function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens);

  /// @param destinationChainSelector The destination chainSelector
  /// @param message The cross-chain CCIP message including data and/or tokens
  /// @return fee returns execution fee for the message
  /// delivery to destination chain, denominated in the feeToken specified in the message.
  /// @dev Reverts with appropriate reason upon invalid message.
  function getFee(
    uint64 destinationChainSelector,
    Client.EVM2AnyMessage memory message
  ) external view returns (uint256 fee);

  /// @notice Request a message to be sent to the destination chain
  /// @param destinationChainSelector The destination chain ID
  /// @param message The cross-chain CCIP message including data and/or tokens
  /// @return messageId The message ID
  /// @dev Note if msg.value is larger than the required fee (from getFee) we accept
  /// the overpayment with no refund.
  /// @dev Reverts with appropriate reason upon invalid message.
  function ccipSend(
    uint64 destinationChainSelector,
    Client.EVM2AnyMessage calldata message
  ) external payable returns (bytes32);
}
合同源代码
文件 19 的 34:IVRFCoordinatorV2Plus.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {VRFV2PlusClient} from "../libraries/VRFV2PlusClient.sol";
import {IVRFSubscriptionV2Plus} from "./IVRFSubscriptionV2Plus.sol";

// Interface that enables consumers of VRFCoordinatorV2Plus to be future-proof for upgrades
// This interface is supported by subsequent versions of VRFCoordinatorV2Plus
interface IVRFCoordinatorV2Plus is IVRFSubscriptionV2Plus {
  /**
   * @notice Request a set of random words.
   * @param req - a struct containing following fields for randomness request:
   * keyHash - Corresponds to a particular oracle job which uses
   * that key for generating the VRF proof. Different keyHash's have different gas price
   * ceilings, so you can select a specific one to bound your maximum per request cost.
   * subId  - The ID of the VRF subscription. Must be funded
   * with the minimum subscription balance required for the selected keyHash.
   * requestConfirmations - How many blocks you'd like the
   * oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
   * for why you may want to request more. The acceptable range is
   * [minimumRequestBlockConfirmations, 200].
   * callbackGasLimit - How much gas you'd like to receive in your
   * fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords
   * may be slightly less than this amount because of gas used calling the function
   * (argument decoding etc.), so you may need to request slightly more than you expect
   * to have inside fulfillRandomWords. The acceptable range is
   * [0, maxGasLimit]
   * numWords - The number of uint256 random values you'd like to receive
   * in your fulfillRandomWords callback. Note these numbers are expanded in a
   * secure way by the VRFCoordinator from a single random value supplied by the oracle.
   * extraArgs - abi-encoded extra args
   * @return requestId - A unique identifier of the request. Can be used to match
   * a request to a response in fulfillRandomWords.
   */
  function requestRandomWords(VRFV2PlusClient.RandomWordsRequest calldata req) external returns (uint256 requestId);
}
合同源代码
文件 20 的 34:IVRFMigratableConsumerV2Plus.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice The IVRFMigratableConsumerV2Plus interface defines the
/// @notice method required to be implemented by all V2Plus consumers.
/// @dev This interface is designed to be used in VRFConsumerBaseV2Plus.
interface IVRFMigratableConsumerV2Plus {
  event CoordinatorSet(address vrfCoordinator);

  /// @notice Sets the VRF Coordinator address
  /// @notice This method should only be callable by the coordinator or contract owner
  function setCoordinator(address vrfCoordinator) external;
}
合同源代码
文件 21 的 34:IVRFSubscriptionV2Plus.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice The IVRFSubscriptionV2Plus interface defines the subscription
/// @notice related methods implemented by the V2Plus coordinator.
interface IVRFSubscriptionV2Plus {
  /**
   * @notice Add a consumer to a VRF subscription.
   * @param subId - ID of the subscription
   * @param consumer - New consumer which can use the subscription
   */
  function addConsumer(uint256 subId, address consumer) external;

  /**
   * @notice Remove a consumer from a VRF subscription.
   * @param subId - ID of the subscription
   * @param consumer - Consumer to remove from the subscription
   */
  function removeConsumer(uint256 subId, address consumer) external;

  /**
   * @notice Cancel a subscription
   * @param subId - ID of the subscription
   * @param to - Where to send the remaining LINK to
   */
  function cancelSubscription(uint256 subId, address to) external;

  /**
   * @notice Accept subscription owner transfer.
   * @param subId - ID of the subscription
   * @dev will revert if original owner of subId has
   * not requested that msg.sender become the new owner.
   */
  function acceptSubscriptionOwnerTransfer(uint256 subId) external;

  /**
   * @notice Request subscription owner transfer.
   * @param subId - ID of the subscription
   * @param newOwner - proposed new owner of the subscription
   */
  function requestSubscriptionOwnerTransfer(uint256 subId, address newOwner) external;

  /**
   * @notice Create a VRF subscription.
   * @return subId - A unique subscription id.
   * @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
   * @dev Note to fund the subscription with LINK, use transferAndCall. For example
   * @dev  LINKTOKEN.transferAndCall(
   * @dev    address(COORDINATOR),
   * @dev    amount,
   * @dev    abi.encode(subId));
   * @dev Note to fund the subscription with Native, use fundSubscriptionWithNative. Be sure
   * @dev  to send Native with the call, for example:
   * @dev COORDINATOR.fundSubscriptionWithNative{value: amount}(subId);
   */
  function createSubscription() external returns (uint256 subId);

  /**
   * @notice Get a VRF subscription.
   * @param subId - ID of the subscription
   * @return balance - LINK balance of the subscription in juels.
   * @return nativeBalance - native balance of the subscription in wei.
   * @return reqCount - Requests count of subscription.
   * @return owner - owner of the subscription.
   * @return consumers - list of consumer address which are able to use this subscription.
   */
  function getSubscription(
    uint256 subId
  )
    external
    view
    returns (uint96 balance, uint96 nativeBalance, uint64 reqCount, address owner, address[] memory consumers);

  /*
   * @notice Check to see if there exists a request commitment consumers
   * for all consumers and keyhashes for a given sub.
   * @param subId - ID of the subscription
   * @return true if there exists at least one unfulfilled request for the subscription, false
   * otherwise.
   */
  function pendingRequestExists(uint256 subId) external view returns (bool);

  /**
   * @notice Paginate through all active VRF subscriptions.
   * @param startIndex index of the subscription to start from
   * @param maxCount maximum number of subscriptions to return, 0 to return all
   * @dev the order of IDs in the list is **not guaranteed**, therefore, if making successive calls, one
   * @dev should consider keeping the blockheight constant to ensure a holistic picture of the contract state
   */
  function getActiveSubscriptionIds(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory);

  /**
   * @notice Fund a subscription with native.
   * @param subId - ID of the subscription
   * @notice This method expects msg.value to be greater than or equal to 0.
   */
  function fundSubscriptionWithNative(uint256 subId) external payable;
}
合同源代码
文件 22 的 34:IWinnables.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

interface IWinnables {
    error InvalidPrize();
    error RaffleHasNotStarted();
    error RaffleHasEnded();
    error RaffleIsStillOpen();
    error TooManyTickets();
    error InvalidRaffle();
    error RaffleNotFulfilled();
    error NoParticipants();
    error RequestNotFound(uint256 requestId);
    error ExpiredCoupon();
    error PlayerAlreadyRefunded(address player);
    error NothingToSend();
    error Unauthorized();
    error TargetTicketsNotReached();
    error TargetTicketsReached();
    error RaffleClosingTooSoon();
    error InsufficientBalance();
    error ETHTransferFail();
    error RaffleRequiresTicketSupplyCap();
    error RaffleRequiresMaxHoldings();
    error NotAnNFT();

    event WinnerDrawn(uint256 indexed requestId);
    event RequestSent(uint256 indexed requestId, uint256 indexed raffleId);
    event NewRaffle(uint256 indexed id);
    event PrizeClaimed(uint256 indexed raffleId, address indexed winner);
    event PlayerRefund(uint256 indexed raffleId, address indexed player, bytes32 indexed participation);

    enum RaffleType { NONE, NFT, ETH, TOKEN }
    enum RaffleStatus { NONE, PRIZE_LOCKED, IDLE, REQUESTED, FULFILLED, PROPAGATED, CLAIMED, CANCELED }
    enum CCIPMessageType {
        RAFFLE_CANCELED,
        WINNER_DRAWN
    }

    struct RequestStatus {
        uint256 raffleId;
        uint256 randomWord;
        uint256 blockLastRequested;
    }

    struct RequestStatusWithFulfillmentTime {
        uint256 raffleId;
        uint256 randomWord;
        uint256 blockLastRequested;
        uint256 blockFulfilled;
    }

    struct Raffle {
        RaffleStatus status;
        uint64 startsAt;
        uint64 endsAt;
        uint32 minTicketsThreshold;
        uint32 maxTicketSupply;
        uint32 maxHoldings;
        uint256 totalRaised;
        uint256 chainlinkRequestId;
        bytes32 ccipCounterpart;
        mapping(address => bytes32) participations;
    }
}
合同源代码
文件 23 的 34:IWinnablesPrizeManager.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import "./IWinnables.sol";

interface IWinnablesPrizeManager is IWinnables {
    error InvalidRaffleId();
    error AlreadyClaimed();
    error NFTLocked();
    error IllegalRaffleId();
    error UnauthorizedToClaim();
    error InvalidAddress();
    error LINKTokenNotPermitted();

    event NFTPrizeLocked(uint256 indexed raffleId, address indexed contractAddress, uint256 indexed tokenId);
    event TokenPrizeLocked(uint256 indexed raffleId, address indexed contractAddress, uint256 indexed amount);
    event ETHPrizeLocked(uint256 indexed raffleId, uint256 indexed amount);
    event PrizeUnlocked(uint256 indexed raffleId);
    event TokenPrizeUnlocked(uint256 indexed raffleId);
    event ETHPrizeUnlocked(uint256 indexed raffleId);
    event WinnerPropagated(uint256 indexed raffleId, address indexed winner);

    enum RafflePrizeStatus {
        NONE,
        CLAIMED,
        CANCELED
    }

    struct RafflePrize {
        RaffleType raffleType;
        RafflePrizeStatus status;
        bytes32 ccipCounterpart;
        address winner;
    }

    struct NFTInfo {
        address contractAddress;
        uint256 tokenId;
    }

    struct TokenInfo {
        address tokenAddress;
        uint256 amount;
    }
}
合同源代码
文件 24 的 34:IWinnablesTicket.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";

interface IWinnablesTicket is IERC1155MetadataURI {

  error QueryForAddressZero();
  error InconsistentParametersLengths();
  error Unauthorized();
  error TransferToAddressZero();
  error InsufficientBalance();
  error TransferRejected();
  error NoOp();
  error InexistentTicket();
  error CallerNotContractOwner();
  error NotImplemented();

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
  event NewTicket(uint256 indexed id, uint256 indexed startId, uint256 indexed amount);

  function manager() external view returns(address);
  function supplyOf(uint256 raffleId) external view returns(uint256);
  function ownerOf(uint256 raffleId, uint256 ticketNumner) external view returns(address);
  function mint(address to, uint256 raffleId, uint256 amount) external;
  function refreshMetadata(uint256 tokenId) external;
  function initializeManager() external;

  function batchMint(
    address to,
    uint256[] calldata raffleId,
    uint256[] calldata amount
  ) external;
}
合同源代码
文件 25 的 34:IWinnablesTicketManager.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import "./IWinnables.sol";

interface IWinnablesTicketManager is IWinnables {
    error PrizeNotLocked();
    error InvalidRaffleStatus();
    error InvalidTicketCount();
    error RaffleWontDraw();
    error MaxTicketExceed();

    event RafflePrizeLocked(bytes32 messageId, uint64 sourceChainSelector, uint256 raffleId);
    event InvalidVRFRequest(uint256 requestId);
}
合同源代码
文件 26 的 34:LinkTokenInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// solhint-disable-next-line interface-starts-with-i
interface LinkTokenInterface {
  function allowance(address owner, address spender) external view returns (uint256 remaining);

  function approve(address spender, uint256 value) external returns (bool success);

  function balanceOf(address owner) external view returns (uint256 balance);

  function decimals() external view returns (uint8 decimalPlaces);

  function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);

  function increaseApproval(address spender, uint256 subtractedValue) external;

  function name() external view returns (string memory tokenName);

  function symbol() external view returns (string memory tokenSymbol);

  function totalSupply() external view returns (uint256 totalTokensIssued);

  function transfer(address to, uint256 value) external returns (bool success);

  function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);

  function transferFrom(address from, address to, uint256 value) external returns (bool success);
}
合同源代码
文件 27 的 34:Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10**64) {
                value /= 10**64;
                result += 64;
            }
            if (value >= 10**32) {
                value /= 10**32;
                result += 32;
            }
            if (value >= 10**16) {
                value /= 10**16;
                result += 16;
            }
            if (value >= 10**8) {
                value /= 10**8;
                result += 8;
            }
            if (value >= 10**4) {
                value /= 10**4;
                result += 4;
            }
            if (value >= 10**2) {
                value /= 10**2;
                result += 2;
            }
            if (value >= 10**1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
        }
    }
}
合同源代码
文件 28 的 34:Roles.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import "./libraries/Bits.sol";

contract Roles {
    using Bits for bytes32;

    error MissingRole(address user, uint256 role);
    event RoleUpdated(address indexed user, uint256 indexed role, bool indexed status);

    /**
     * @dev There is a maximum of 256 roles: each bit says if the role is on or off
     */
    mapping(address => bytes32) private _addressRoles;

    modifier onlyRole(uint8 role) {
        _checkRole(msg.sender, role);
        _;
    }

    constructor() {
        _setRole(msg.sender, 0, true);
    }

    function _hasRole(address user, uint8 role) internal view returns(bool) {
        return _addressRoles[user].getBool(role);
    }

    function _checkRole(address user, uint8 role) internal virtual view {
        if (!_hasRole(user, role)) {
            revert MissingRole(user, role);
        }
    }

    function _setRole(address user, uint8 role, bool status) internal virtual {
        _addressRoles[user] = _addressRoles[user].setBool(role, status);
        emit RoleUpdated(user, role, status);
    }

    function setRole(address user, uint8 role, bool status) external virtual onlyRole(0) {
        _setRole(user, role, status);
    }

    function getRoles(address user) external view returns(bytes32) {
        return _addressRoles[user];
    }
}
合同源代码
文件 29 的 34: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");
        }
    }
}
合同源代码
文件 30 的 34:Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }
}
合同源代码
文件 31 的 34:VRFConsumerBaseV2Plus.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {IVRFCoordinatorV2Plus} from "./interfaces/IVRFCoordinatorV2Plus.sol";
import {IVRFMigratableConsumerV2Plus} from "./interfaces/IVRFMigratableConsumerV2Plus.sol";
import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";

/** ****************************************************************************
 * @notice Interface for contracts using VRF randomness
 * *****************************************************************************
 * @dev PURPOSE
 *
 * @dev Reggie the Random Oracle (not his real job) wants to provide randomness
 * @dev to Vera the verifier in such a way that Vera can be sure he's not
 * @dev making his output up to suit himself. Reggie provides Vera a public key
 * @dev to which he knows the secret key. Each time Vera provides a seed to
 * @dev Reggie, he gives back a value which is computed completely
 * @dev deterministically from the seed and the secret key.
 *
 * @dev Reggie provides a proof by which Vera can verify that the output was
 * @dev correctly computed once Reggie tells it to her, but without that proof,
 * @dev the output is indistinguishable to her from a uniform random sample
 * @dev from the output space.
 *
 * @dev The purpose of this contract is to make it easy for unrelated contracts
 * @dev to talk to Vera the verifier about the work Reggie is doing, to provide
 * @dev simple access to a verifiable source of randomness. It ensures 2 things:
 * @dev 1. The fulfillment came from the VRFCoordinatorV2Plus.
 * @dev 2. The consumer contract implements fulfillRandomWords.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFConsumerBaseV2Plus, and can
 * @dev initialize VRFConsumerBaseV2Plus's attributes in their constructor as
 * @dev shown:
 *
 * @dev   contract VRFConsumerV2Plus is VRFConsumerBaseV2Plus {
 * @dev     constructor(<other arguments>, address _vrfCoordinator, address _subOwner)
 * @dev       VRFConsumerBaseV2Plus(_vrfCoordinator, _subOwner) public {
 * @dev         <initialization with other arguments goes here>
 * @dev       }
 * @dev   }
 *
 * @dev The oracle will have given you an ID for the VRF keypair they have
 * @dev committed to (let's call it keyHash). Create a subscription, fund it
 * @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface
 * @dev subscription management functions).
 * @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
 * @dev callbackGasLimit, numWords, extraArgs),
 * @dev see (IVRFCoordinatorV2Plus for a description of the arguments).
 *
 * @dev Once the VRFCoordinatorV2Plus has received and validated the oracle's response
 * @dev to your request, it will call your contract's fulfillRandomWords method.
 *
 * @dev The randomness argument to fulfillRandomWords is a set of random words
 * @dev generated from your requestId and the blockHash of the request.
 *
 * @dev If your contract could have concurrent requests open, you can use the
 * @dev requestId returned from requestRandomWords to track which response is associated
 * @dev with which randomness request.
 * @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind,
 * @dev if your contract could have multiple requests in flight simultaneously.
 *
 * @dev Colliding `requestId`s are cryptographically impossible as long as seeds
 * @dev differ.
 *
 * *****************************************************************************
 * @dev SECURITY CONSIDERATIONS
 *
 * @dev A method with the ability to call your fulfillRandomness method directly
 * @dev could spoof a VRF response with any random value, so it's critical that
 * @dev it cannot be directly called by anything other than this base contract
 * @dev (specifically, by the VRFConsumerBaseV2Plus.rawFulfillRandomness method).
 *
 * @dev For your users to trust that your contract's random behavior is free
 * @dev from malicious interference, it's best if you can write it so that all
 * @dev behaviors implied by a VRF response are executed *during* your
 * @dev fulfillRandomness method. If your contract must store the response (or
 * @dev anything derived from it) and use it later, you must ensure that any
 * @dev user-significant behavior which depends on that stored value cannot be
 * @dev manipulated by a subsequent VRF request.
 *
 * @dev Similarly, both miners and the VRF oracle itself have some influence
 * @dev over the order in which VRF responses appear on the blockchain, so if
 * @dev your contract could have multiple VRF requests in flight simultaneously,
 * @dev you must ensure that the order in which the VRF responses arrive cannot
 * @dev be used to manipulate your contract's user-significant behavior.
 *
 * @dev Since the block hash of the block which contains the requestRandomness
 * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
 * @dev miner could, in principle, fork the blockchain to evict the block
 * @dev containing the request, forcing the request to be included in a
 * @dev different block with a different hash, and therefore a different input
 * @dev to the VRF. However, such an attack would incur a substantial economic
 * @dev cost. This cost scales with the number of blocks the VRF oracle waits
 * @dev until it calls responds to a request. It is for this reason that
 * @dev that you can signal to an oracle you'd like them to wait longer before
 * @dev responding to the request (however this is not enforced in the contract
 * @dev and so remains effective only in the case of unmodified oracle software).
 */
abstract contract VRFConsumerBaseV2Plus is IVRFMigratableConsumerV2Plus, ConfirmedOwner {
  error OnlyCoordinatorCanFulfill(address have, address want);
  error OnlyOwnerOrCoordinator(address have, address owner, address coordinator);
  error ZeroAddress();

  // s_vrfCoordinator should be used by consumers to make requests to vrfCoordinator
  // so that coordinator reference is updated after migration
  IVRFCoordinatorV2Plus public s_vrfCoordinator;

  /**
   * @param _vrfCoordinator address of VRFCoordinator contract
   */
  constructor(address _vrfCoordinator) ConfirmedOwner(msg.sender) {
    if (_vrfCoordinator == address(0)) {
      revert ZeroAddress();
    }
    s_vrfCoordinator = IVRFCoordinatorV2Plus(_vrfCoordinator);
  }

  /**
   * @notice fulfillRandomness handles the VRF response. Your contract must
   * @notice implement it. See "SECURITY CONSIDERATIONS" above for important
   * @notice principles to keep in mind when implementing your fulfillRandomness
   * @notice method.
   *
   * @dev VRFConsumerBaseV2Plus expects its subcontracts to have a method with this
   * @dev signature, and will call it once it has verified the proof
   * @dev associated with the randomness. (It is triggered via a call to
   * @dev rawFulfillRandomness, below.)
   *
   * @param requestId The Id initially returned by requestRandomness
   * @param randomWords the VRF output expanded to the requested number of words
   */
  // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
  function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal virtual;

  // rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
  // proof. rawFulfillRandomness then calls fulfillRandomness, after validating
  // the origin of the call
  function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external {
    if (msg.sender != address(s_vrfCoordinator)) {
      revert OnlyCoordinatorCanFulfill(msg.sender, address(s_vrfCoordinator));
    }
    fulfillRandomWords(requestId, randomWords);
  }

  /**
   * @inheritdoc IVRFMigratableConsumerV2Plus
   */
  function setCoordinator(address _vrfCoordinator) external override onlyOwnerOrCoordinator {
    if (_vrfCoordinator == address(0)) {
      revert ZeroAddress();
    }
    s_vrfCoordinator = IVRFCoordinatorV2Plus(_vrfCoordinator);

    emit CoordinatorSet(_vrfCoordinator);
  }

  modifier onlyOwnerOrCoordinator() {
    if (msg.sender != owner() && msg.sender != address(s_vrfCoordinator)) {
      revert OnlyOwnerOrCoordinator(msg.sender, owner(), address(s_vrfCoordinator));
    }
    _;
  }
}
合同源代码
文件 32 的 34:VRFV2PlusClient.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

// End consumer library.
library VRFV2PlusClient {
  // extraArgs will evolve to support new features
  bytes4 public constant EXTRA_ARGS_V1_TAG = bytes4(keccak256("VRF ExtraArgsV1"));
  struct ExtraArgsV1 {
    bool nativePayment;
  }

  struct RandomWordsRequest {
    bytes32 keyHash;
    uint256 subId;
    uint16 requestConfirmations;
    uint32 callbackGasLimit;
    uint32 numWords;
    bytes extraArgs;
  }

  function _argsToBytes(ExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {
    return abi.encodeWithSelector(EXTRA_ARGS_V1_TAG, extraArgs);
  }
}
合同源代码
文件 33 的 34:WinnablesRaffles.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";

import "./Roles.sol";
import "./interfaces/IWinnablesTicketManager.sol";
import "./interfaces/IWinnablesPrizeManager.sol";
import "./interfaces/IWinnablesTicket.sol";
import "./BaseCCIPSender.sol";
import "./BaseCCIPReceiver.sol";

contract WinnablesRaffles is
    Roles,
    VRFConsumerBaseV2Plus,
    IWinnablesTicketManager,
    IWinnablesPrizeManager
{
    using SafeERC20 for IERC20;

    uint256 constant internal MIN_RAFFLE_DURATION = 60;
    uint256 constant internal MAX_TICKET_PURCHASABLE = 3_000;
    uint256 constant internal VRF_REQUEST_TIMEOUT = 200;

    address immutable private TICKETS_CONTRACT;

    /// @dev The key hash of the Chainlink VRF
    bytes32 private immutable KEY_HASH;

    /// @dev The subscription ID of the Chainlink VRF
    uint256 public immutable SUBSCRIPTION_ID;

    /// @dev Mapping from Chainlink request id to struct RequestStatusWithFulfillmentTime
    mapping(uint256 => RequestStatusWithFulfillmentTime) internal _chainlinkRequests;

    /// @dev Mapping from raffle ID to struct Raffle
    mapping(uint256 => Raffle) private _raffles;

    /// @dev Nonces used in the signature that allows ticket sales to avoid signature reuse
    mapping(address => uint256) private _userNonces;

    /// @dev VRF request confirmations to pass when requesting randomness
    uint16 private _vrfRequestConfirmations = 3;

    /// @dev Mapping from raffleId to raffleType
    mapping(uint256 => RafflePrize) private _rafflePrize;

    /// @dev Mapping from raffle ID to struct NFTInfo (only set when an NFT is locked in for a raffle)
    mapping(uint256 => NFTInfo) private _nftRaffles;

    /// @dev Mapping from raffle ID to prize amount (only set when ETH is locked in for a raffle)
    mapping(uint256 => uint256) private _ethRaffles;

    /// @dev Mapping from raffle ID to struct TokenInfo (only set for Tokens are locked in for a raffle)
    mapping(uint256 => TokenInfo) private _tokenRaffles;

    /// @dev Amount of ETH currently locked in an ETH Raffle or from active raffles (that may need to be refunded)
    uint256 private _ethLocked;

    /// @dev Mapping from token address to amount of tokens currently locked in a Token Raffle
    mapping(address => uint256) private _tokensLocked;

    /// @dev Mapping from NFT address to a mapping from tokenId to boolean
    ///      (true if locked in an NFT Raffle)
    mapping(address => mapping(uint256 => bool)) private _nftLocked;

    uint32 private _vrfCallbackGas = 100_000;

    /// @dev Contract constructor
    /// @param _vrfCoordinator Address of the Chainlink VRFCoordinator contract on the chain you are deploying to
    /// @param _subscriptionId ID of the Chainlink VRF subscription that will fund Random Number request
    /// @param _keyHash The key hash of the Chainlink VRF
    /// @param _tickets Address of the ERC1155 collection of the tickets
    constructor(
        address _vrfCoordinator,
        uint256 _subscriptionId,
        bytes32 _keyHash,
        address _tickets
    ) VRFConsumerBaseV2Plus(_vrfCoordinator) {
        SUBSCRIPTION_ID = _subscriptionId;
        KEY_HASH = _keyHash;
        TICKETS_CONTRACT = _tickets;
        IWinnablesTicket(_tickets).initializeManager();
    }

    // =============================================================
    // -- Public Views
    // =============================================================

    /// @notice (Public) Get general information about the state of a raffle
    /// @param id ID of the Raffle
    /// @return raffleType Raffle type
    /// @return startsAt timestamp of when the raffle starts
    /// @return endsAt timestamp of when the raffle ends
    /// @return minTicketsThreshold minimum Minimum number of tickets that needs to be sold before drawing a winner
    /// @return maxTicketSupply maximum number of tickets that can be sold for this raffle
    /// @return maxHoldings maximum number of tickets that one address may hold
    /// @return totalRaised amount of ETH raised by this raffle
    /// @return status status of the raffle
    /// @return chainlinkRequestId ID of the Chainlink VRF request
    function getRaffle(uint256 id) external view returns(
        RaffleType raffleType,
        uint64 startsAt,
        uint64 endsAt,
        uint32 minTicketsThreshold,
        uint32 maxTicketSupply,
        uint32 maxHoldings,
        uint256 totalRaised,
        RaffleStatus status,
        uint256 chainlinkRequestId
    ) {
        Raffle storage raffle = _raffles[id];
        raffleType = _rafflePrize[id].raffleType;
        startsAt = raffle.startsAt;
        endsAt = raffle.endsAt;
        minTicketsThreshold = raffle.minTicketsThreshold;
        maxTicketSupply = raffle.maxTicketSupply;
        maxHoldings = raffle.maxHoldings;
        totalRaised = raffle.totalRaised;
        status = raffle.status;
        chainlinkRequestId = raffle.chainlinkRequestId;
    }

    /// @notice (Public) Get general information about a raffle prize
    ///         (type, status, winner)
    /// @param id ID of the Raffle
    /// @return Information about the raffle prize
    function getRafflePrize(uint256 id) external view returns(RafflePrize memory) {
        return _rafflePrize[id];
    }

    /// @notice (Public) Get information about the prize of an NFT raffle
    /// @param id ID of the Raffle
    /// @return Information about the prize of an NFT raffle
    function getNFTRaffle(uint256 id) external view returns(NFTInfo memory) {
        RaffleType raffleType = _rafflePrize[id].raffleType;
        if (raffleType != RaffleType.NFT) {
            revert InvalidRaffle();
        }
        return _nftRaffles[id];
    }

    /// @notice (Public) Get the prize amount of an ETH raffle
    /// @param id ID of the Raffle
    /// @return Prize amount of an ETH raffle
    function getETHRaffle(uint256 id) external view returns(uint256) {
        RaffleType raffleType = _rafflePrize[id].raffleType;
        if (raffleType != RaffleType.ETH) {
            revert InvalidRaffle();
        }
        return _ethRaffles[id];
    }

    /// @notice (Public) Get information about the prize of a Token raffle
    /// @param id ID of the Raffle
    /// @return Information about the prize of a Token raffle
    function getTokenRaffle(uint256 id) external view returns(TokenInfo memory) {
        RaffleType raffleType = _rafflePrize[id].raffleType;
        if (raffleType != RaffleType.TOKEN) {
            revert InvalidRaffle();
        }
        return _tokenRaffles[id];
    }

    /// @notice (Public) Shows the participation details of a participant to a raffle
    /// @param raffleId ID of the raffle
    /// @param participant Address of the participant
    /// @return totalSpent Total spent by address participant for raffle raffleId
    /// @return totalPurchased Total number of tickets purchased
    /// @return withdrawn Whether this player has been refunded for this raffle or not
    function getParticipation(uint256 raffleId, address participant) external view returns(
        uint128 totalSpent,
        uint32 totalPurchased,
        bool withdrawn
    ) {
        bytes32 participation = _raffles[raffleId].participations[participant];
        totalSpent = uint128(uint256(participation));
        totalPurchased = uint32(uint256(participation) >> 128);
        withdrawn = uint8((uint256(participation) >> 160) & 1) == 1;
    }

    /// @notice (Public) Shows the address of the winner of a raffle
    /// @param raffleId ID of the raffle
    /// @return winner Address of the winner
    function getWinner(uint256 raffleId) external view returns(address winner) {
        winner = _rafflePrize[raffleId].winner;

        if (winner == address(0)) {
            revert RaffleNotFulfilled();
        }
    }

    /// @notice (Public) Get the status of a Chainlink request
    /// @param requestId ID of the Chainlink request
    /// @return fulfilled The fulfillment status
    /// @return randomWord the Random number if the request is fulfilled
    /// @return raffleId the ID of the associated raffle
    /// @return blockLastRequested block at which the last VRF request was sent
    /// @return blockFulfilled block at which the VRF request was fulfilled
    function getRequestStatus(uint256 requestId) external view returns (
        bool fulfilled,
        uint256 randomWord,
        uint256 raffleId,
        uint256 blockLastRequested,
        uint256 blockFulfilled
    ) {
        RequestStatusWithFulfillmentTime storage request = _chainlinkRequests[requestId];
        Raffle storage raffle = _raffles[request.raffleId];
        if (raffle.status == RaffleStatus.NONE) {
            revert RequestNotFound(requestId);
        }
        fulfilled = raffle.status == RaffleStatus.FULFILLED;
        randomWord = request.randomWord;
        raffleId = request.raffleId;
        blockLastRequested = request.blockLastRequested;
        blockFulfilled = request.blockFulfilled;
    }

    /// @notice (Public) Check if a raffle should draw a winner
    /// @param raffleId Raffle ID
    /// @return true if the winner should be drawn, revert otherwise
    function shouldDrawRaffle(uint256 raffleId) external view returns(bool) {
        _checkShouldDraw(raffleId);
        return true;
    }

    /// @notice (Public) Check if a raffle should be canceled
    /// @param raffleId Raffle ID
    /// @return true if the raffle should be canceled, revert otherwise
    function shouldCancelRaffle(uint256 raffleId) external view returns(bool) {
        _checkShouldCancel(raffleId);
        return true;
    }

    /// @notice (Public) Get the nonce of a given address to use for a ticket purchase approval signature
    /// @param buyer Address of the account that wants to purchase a ticket
    /// @return nonce for this account
    function getNonce(address buyer) external view returns(uint256) {
        return _userNonces[buyer];
    }

    // =============================================================
    // -- Public functions
    // =============================================================

    /// @notice (Public) Participate in a raffle
    /// @param raffleId ID of the Raffle
    /// @param ticketCount Number of tickets purchased
    /// @param blockNumber Number of the block when the signature expires
    /// @param signature Signature provided by the API to authorize this ticket sale at given price
    function buyTickets(
        uint256 raffleId,
        uint16 ticketCount,
        uint256 blockNumber,
        bytes calldata signature
    ) external payable {
        if (ticketCount == 0) {
            revert InvalidTicketCount();
        }
        _checkTicketPurchaseable(raffleId, ticketCount);
        _checkPurchaseSig(raffleId, ticketCount, blockNumber, signature);

        Raffle storage raffle = _raffles[raffleId];
        uint256 participation = uint256(raffle.participations[msg.sender]);
        uint128 totalPaid = uint128(participation) + uint128(msg.value);
        uint32 totalPurchased = uint32(participation >> 128) + uint32(ticketCount);
        raffle.participations[msg.sender] = bytes32(
            (participation & type(uint256).max << 160)
            | totalPaid |
            uint256(totalPurchased) << 128
        );
        unchecked {
            raffle.totalRaised += msg.value;
            _userNonces[msg.sender]++;
            _ethLocked += msg.value;
        }
        IWinnablesTicket(TICKETS_CONTRACT).mint(msg.sender, raffleId, ticketCount);
        IWinnablesTicket(TICKETS_CONTRACT).refreshMetadata(raffleId);
    }

    /// @notice (Public) Send a request for random number from Chainlink VRF
    /// @param raffleId ID of the Raffle we wish to draw a winner for
    function drawWinner(uint256 raffleId) external {
        Raffle storage raffle = _raffles[raffleId];

        if (raffle.status == RaffleStatus.REQUESTED) {
            _checkVRFTimeout(raffle);
        } else {
            _checkShouldDraw(raffleId);
        }
        raffle.status = RaffleStatus.REQUESTED;

        uint256 requestId = s_vrfCoordinator.requestRandomWords(
            VRFV2PlusClient.RandomWordsRequest({
                keyHash: KEY_HASH,
                subId: SUBSCRIPTION_ID,
                requestConfirmations: _vrfRequestConfirmations,
                callbackGasLimit: _vrfCallbackGas,
                numWords: 1,
                extraArgs: VRFV2PlusClient._argsToBytes(
                    VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
                )
            })
        );
        RequestStatusWithFulfillmentTime storage request = _chainlinkRequests[requestId];

        request.raffleId = raffleId;
        request.blockLastRequested = block.number;
        raffle.chainlinkRequestId = requestId;
        emit RequestSent(requestId, raffleId);
        IWinnablesTicket(TICKETS_CONTRACT).refreshMetadata(raffleId);
    }

    /// @notice (Public) Cancel a raffle if it can be canceled
    /// @param raffleId ID of the raffle to cancel
    function cancelRaffle(uint256 raffleId) external {
        Raffle storage raffle = _raffles[raffleId];
        if (raffle.status == RaffleStatus.REQUESTED) {
            _checkVRFTimeout(raffle);
        } else {
            _checkShouldCancel(raffleId);
        }

        _cancelRaffle(raffleId);

        raffle.status = RaffleStatus.CANCELED;
        IWinnablesTicket(TICKETS_CONTRACT).refreshMetadata(raffleId);
    }

    /// @notice (Public) Refund their participation to a list of players for a canceled Raffle ID
    /// @param raffleId ID of the canceled Raffle
    /// @param players List of players to refund
    function refundPlayers(uint256 raffleId, address[] calldata players) external {
        Raffle storage raffle = _raffles[raffleId];
        if (raffle.status != RaffleStatus.CANCELED) {
            revert InvalidRaffle();
        }
        uint256 totalRefunded = 0;
        for (uint256 i = 0; i < players.length; ) {
            address player = players[i];
            uint256 participation = uint256(raffle.participations[player]);
            if (((participation >> 160) & 1) == 1) {
                revert PlayerAlreadyRefunded(player);
            }
            raffle.participations[player] = bytes32(participation | (1 << 160));
            uint256 amountToSend = (participation & type(uint128).max);
            _sendETH(amountToSend, player);
            emit PlayerRefund(raffleId, player, bytes32(participation));
            unchecked {
                totalRefunded += amountToSend;
                ++i;
            }
        }
        unchecked {
            _ethLocked -= totalRefunded;
        }
    }

    /// @notice (Public) Send the prize for a Raffle to its rightful winner
    /// @param raffleId ID of the raffle
    function claimPrize(uint256 raffleId) external {
        RafflePrize storage rafflePrize = _rafflePrize[raffleId];
        RaffleType raffleType = rafflePrize.raffleType;
        if (raffleType == RaffleType.NFT) {
            NFTInfo storage raffle = _nftRaffles[raffleId];
            _nftLocked[raffle.contractAddress][raffle.tokenId] = false;
            _sendNFTPrize(raffle.contractAddress, raffle.tokenId, msg.sender);
        } else if (raffleType == RaffleType.TOKEN) {
            TokenInfo storage raffle = _tokenRaffles[raffleId];
            unchecked { _tokensLocked[raffle.tokenAddress] -= raffle.amount; }
            _sendTokenPrize(raffle.tokenAddress, raffle.amount, msg.sender);
        } else if (raffleType == RaffleType.ETH) {
            unchecked { _ethLocked -= _ethRaffles[raffleId]; }
            _sendETH(_ethRaffles[raffleId], msg.sender);
        } else {
            revert InvalidRaffle();
        }
        if (msg.sender != rafflePrize.winner) {
            revert UnauthorizedToClaim();
        }
        if (rafflePrize.status == RafflePrizeStatus.CLAIMED) {
            revert AlreadyClaimed();
        }
        rafflePrize.status = RafflePrizeStatus.CLAIMED;
        emit PrizeClaimed(raffleId, msg.sender);
    }

    // =============================================================
    // -- Admin functions
    // =============================================================

    /// @notice (Admin) Send the prize for a Raffle to its rightful winner
    /// @param raffleId ID of the Raffle that will be associated
    /// @param nft NFT contract address
    /// @param tokenId NFT token id
    function lockNFT(
        uint256 raffleId,
        address nft,
        uint256 tokenId
    ) external onlyRole(0) {
        _lockNFT(raffleId, nft, tokenId);
    }

    /// @notice (Admin) Send the prize for a Raffle to its rightful winner
    /// @param raffleId ID of the Raffle that will be associated
    /// @param amount Amount of ETH to lock as a prize
    function lockETH(
        uint256 raffleId,
        uint256 amount
    ) external payable onlyRole(0) {
        _lockETH(raffleId, amount);
    }

    /// @notice (Admin) Send the prize for a Raffle to its rightful winner
    /// @param raffleId ID of the Raffle that will be associated
    /// @param token Token contract address
    /// @param amount Amount of tokens to lock as a prize
    function lockTokens(
        uint256 raffleId,
        address token,
        uint256 amount
    ) external onlyRole(0) {
        _lockTokens(raffleId, token, amount);
    }

    /// @notice (Admin) Use this to withdraw any ERC20 from the contract that
    ///         is not locked in a raffle, or withdraw LINK
    /// @param token ERC20 address
    function withdrawToken(address token, uint256 amount) external onlyRole(0) {
        uint256 tokenBalance = IERC20(token).balanceOf(address(this));
        uint256 availableBalance;
        unchecked { availableBalance = tokenBalance - _tokensLocked[token]; }
        if (availableBalance < amount) {
            revert InsufficientBalance();
        }
        IERC20(token).safeTransfer(msg.sender, amount);
    }

    /// @notice (Admin) Use this to withdraw any NFT from the contract that is
    ///         not locked in a raffle
    /// @param nft Address of the NFT contract
    /// @param tokenId ID of the NFT
    function withdrawNFT(address nft, uint256 tokenId) external onlyRole(0) {
        if (_nftLocked[nft][tokenId]) {
            revert NFTLocked();
        }

        try IERC721(nft).ownerOf(tokenId) returns (address) {} catch {
            revert NotAnNFT();
        }
        IERC721(nft).transferFrom(address(this), msg.sender, tokenId);
    }

    /// @notice (Admin) Create Raffle for a prize previously sent to this contract
    /// @param raffleId ID Of the raffle shared with the remote chain
    /// @param startsAt Epoch timestamp in seconds of the raffle start time
    /// @param endsAt Epoch timestamp in seconds of the raffle end time
    /// @param minTickets Minimum number of tickets required to be sold for this raffle
    /// @param maxTickets Maximum number of tickets that can be sold for this raffle
    /// @param maxHoldings Maximum number of tickets one player can hold
    function createRaffle(
        uint256 raffleId,
        uint64 startsAt,
        uint64 endsAt,
        uint32 minTickets,
        uint32 maxTickets,
        uint32 maxHoldings
    ) external onlyRole(0) {
        _createRaffle(raffleId, startsAt, endsAt, minTickets, maxTickets, maxHoldings);
    }

    /// @notice (Admin) Lock an NFT prize and create a raffle for it
    /// @param raffleId ID Of the raffle shared with the remote chain
    /// @param nft NFT contract address
    /// @param tokenId NFT token id
    /// @param startsAt Epoch timestamp in seconds of the raffle start time
    /// @param endsAt Epoch timestamp in seconds of the raffle end time
    /// @param minTickets Minimum number of tickets required to be sold for this raffle
    /// @param maxTickets Maximum number of tickets that can be sold for this raffle
    /// @param maxHoldings Maximum number of tickets one player can hold
    function createNFTRaffle(
        uint256 raffleId,
        address nft,
        uint256 tokenId,
        uint64 startsAt,
        uint64 endsAt,
        uint32 minTickets,
        uint32 maxTickets,
        uint32 maxHoldings
    ) external onlyRole(0) {
        _lockNFT(raffleId, nft, tokenId);
        _createRaffle(raffleId, startsAt, endsAt, minTickets, maxTickets, maxHoldings);
    }

    /// @notice (Admin) Lock an ETH prize and create a raffle for it
    /// @param raffleId ID Of the raffle shared with the remote chain
    /// @param amount Amount of ETH to lock
    /// @param startsAt Epoch timestamp in seconds of the raffle start time
    /// @param endsAt Epoch timestamp in seconds of the raffle end time
    /// @param minTickets Minimum number of tickets required to be sold for this raffle
    /// @param maxTickets Maximum number of tickets that can be sold for this raffle
    /// @param maxHoldings Maximum number of tickets one player can hold
    function createETHRaffle(
        uint256 raffleId,
        uint256 amount,
        uint64 startsAt,
        uint64 endsAt,
        uint32 minTickets,
        uint32 maxTickets,
        uint32 maxHoldings
    ) external payable onlyRole(0) {
        _lockETH(raffleId, amount);
        _createRaffle(raffleId, startsAt, endsAt, minTickets, maxTickets, maxHoldings);
    }

    /// @notice (Admin) Lock an NFT prize and create a raffle for it
    /// @param raffleId ID Of the raffle shared with the remote chain
    /// @param token ERC20 contract address
    /// @param amount Amount of tokens for the raffle
    /// @param startsAt Epoch timestamp in seconds of the raffle start time
    /// @param endsAt Epoch timestamp in seconds of the raffle end time
    /// @param minTickets Minimum number of tickets required to be sold for this raffle
    /// @param maxTickets Maximum number of tickets that can be sold for this raffle
    /// @param maxHoldings Maximum number of tickets one player can hold
    function createTokenRaffle(
        uint256 raffleId,
        address token,
        uint256 amount,
        uint64 startsAt,
        uint64 endsAt,
        uint32 minTickets,
        uint32 maxTickets,
        uint32 maxHoldings
    ) external onlyRole(0) {
        _lockTokens(raffleId, token, amount);
        _createRaffle(raffleId, startsAt, endsAt, minTickets, maxTickets, maxHoldings);
    }

    /// @notice (Admin) Withdraw Link or any ERC20 tokens accidentally sent here
    /// @param tokenAddress Address of the token contract
    function withdrawTokens(address tokenAddress, uint256 amount) external onlyRole(0) {
        IERC20(tokenAddress).safeTransfer(msg.sender, amount);
    }

    /// @notice (Admin) Withdraw ETH from a canceled raffle or ticket sales
    function withdrawETH() external onlyRole(0) {
        uint256 balance;
        unchecked {
            balance = address(this).balance - _ethLocked;
        }
        _sendETH(balance, msg.sender);
    }

    /// @notice (Admin) Set VRF Callback gas
    /// @param vrfCallbackGas new VRF Callback gas
    function setVRFCallbackGas(uint32 vrfCallbackGas) external onlyRole(0) {
        _vrfCallbackGas = vrfCallbackGas;
    }

    /// @notice (Admin) Cancel a raffle if it has been fulfilled and the end time is passed 2 weeks
    /// @param raffleId ID of the raffle
    function cancelNotClaimedRaffle(uint256 raffleId) external onlyRole(0) {
        Raffle storage raffle = _raffles[raffleId];
        if (raffle.status != RaffleStatus.FULFILLED) {
            revert InvalidRaffle();
        }
        if (block.timestamp < raffle.endsAt + 2 weeks) {
            revert RaffleIsStillOpen();
        }

        // lock funds for refund as they are not locked after a raffle is fulfilled
        unchecked {
            _ethLocked += raffle.totalRaised;
        }
        _cancelRaffle(raffleId);
        raffle.status = RaffleStatus.CANCELED;
        IWinnablesTicket(TICKETS_CONTRACT).refreshMetadata(raffleId);
    }

    /// @notice (Admin) withdraw a raffle if it has been fulfilled and the end time is passed 4 weeks
    /// @param raffleId ID of the raffle
    function withdrawNotClaimedRaffle(uint256 raffleId) external onlyRole(0) {
        Raffle storage raffle = _raffles[raffleId];
        if (raffle.status != RaffleStatus.FULFILLED) {
            revert InvalidRaffle();
        }
        if (block.timestamp < raffle.endsAt + 4 weeks) {
            revert RaffleIsStillOpen();
        }
        
        // send the prize to the admin
        RafflePrize storage rafflePrize = _rafflePrize[raffleId];
        address winner = rafflePrize.winner;
        rafflePrize.winner = msg.sender;
        this.claimPrize(raffleId);
        rafflePrize.winner = winner;
    }

    // =============================================================
    // -- Internal functions
    // =============================================================

    /// @dev Send the prize for a Raffle to its rightful winner
    /// @param raffleId ID of the Raffle that will be associated
    /// @param nft NFT contract address
    /// @param tokenId NFT token id
    function _lockNFT(
        uint256 raffleId,
        address nft,
        uint256 tokenId
    ) internal {
        RafflePrize storage rafflePrize = _checkValidRaffle(raffleId);
        if (IERC721(nft).ownerOf(tokenId) != address(this)) {
            revert InvalidPrize();
        }
        if (_nftLocked[nft][tokenId]) {
            revert InvalidPrize();
        }
        rafflePrize.raffleType = RaffleType.NFT;
        _nftLocked[nft][tokenId] = true;
        _nftRaffles[raffleId].contractAddress = nft;
        _nftRaffles[raffleId].tokenId = tokenId;
        _raffles[raffleId].status = RaffleStatus.PRIZE_LOCKED;

        emit NFTPrizeLocked(raffleId, nft, tokenId);
    }

    /// @dev Send the prize for a Raffle to its rightful winner
    /// @param raffleId ID of the Raffle that will be associated
    /// @param amount Amount of ETH to lock as a prize
    function _lockETH(
        uint256 raffleId,
        uint256 amount
    ) internal {
        RafflePrize storage rafflePrize = _checkValidRaffle(raffleId);
        uint256 ethBalance = address(this).balance;

        if (ethBalance < amount + _ethLocked) {
            revert InvalidPrize();
        }
        rafflePrize.raffleType = RaffleType.ETH;
        _ethLocked += amount;
        _ethRaffles[raffleId] = amount;

        _raffles[raffleId].status = RaffleStatus.PRIZE_LOCKED;
        emit ETHPrizeLocked(raffleId, amount);
    }

    /// @dev Send the prize for a Raffle to its rightful winner
    /// @param raffleId ID of the Raffle that will be associated
    /// @param token Token contract address
    /// @param amount Amount of tokens to lock as a prize
    function _lockTokens(
        uint256 raffleId,
        address token,
        uint256 amount
    ) internal {
        RafflePrize storage rafflePrize = _checkValidRaffle(raffleId);
        uint256 tokenBalance = IERC20(token).balanceOf(address(this));
        if (tokenBalance < amount + _tokensLocked[token]) {
            revert InvalidPrize();
        }
        rafflePrize.raffleType = RaffleType.TOKEN;
        unchecked { _tokensLocked[token] += amount; }
        _tokenRaffles[raffleId].tokenAddress = token;
        _tokenRaffles[raffleId].amount = amount;

        _raffles[raffleId].status = RaffleStatus.PRIZE_LOCKED;
        emit TokenPrizeLocked(raffleId, token, amount);
    }

    /// @dev Create a Raffle for a prize previously locked to this contract
    /// @param raffleId ID Of the raffle shared with the remote chain
    /// @param startsAt Epoch timestamp in seconds of the raffle start time
    /// @param endsAt Epoch timestamp in seconds of the raffle end time
    /// @param minTickets Minimum number of tickets required to be sold for this raffle
    /// @param maxTickets Maximum number of tickets that can be sold for this raffle
    /// @param maxHoldings Maximum number of tickets one player can hold
    function _createRaffle(
        uint256 raffleId,
        uint64 startsAt,
        uint64 endsAt,
        uint32 minTickets,
        uint32 maxTickets,
        uint32 maxHoldings
    ) internal {
        _checkRaffleTimings(startsAt, endsAt);
        if (maxTickets == 0) {
            revert RaffleRequiresTicketSupplyCap();
        }
        if (maxHoldings == 0) {
            revert RaffleRequiresMaxHoldings();
        }
        if (minTickets > maxTickets) {
            revert RaffleWontDraw();
        }
        Raffle storage raffle = _raffles[raffleId];
        if (raffle.status != RaffleStatus.PRIZE_LOCKED) {
            revert PrizeNotLocked();
        }

        raffle.status = RaffleStatus.IDLE;
        raffle.startsAt = startsAt;
        raffle.endsAt = endsAt;
        raffle.minTicketsThreshold = minTickets;
        raffle.maxTicketSupply = maxTickets;
        raffle.maxHoldings = maxHoldings;

        emit NewRaffle(raffleId);
    }

    /// @notice (Chainlink VRF Coordinator) Use given random number as a result to determine the winner of a Raffle
    /// @param requestId ID of the VRF request to fulfill
    /// @param randomWords Array of 32 bytes integers sent back from the oracle
    function fulfillRandomWords(
        uint256 requestId,
        uint256[] calldata randomWords
    ) internal override {
        RequestStatusWithFulfillmentTime storage request = _chainlinkRequests[requestId];
        uint256 raffleId = request.raffleId;
        Raffle storage raffle = _raffles[raffleId];
        RafflePrize storage rafflePrize = _rafflePrize[raffleId];
        if (raffle.chainlinkRequestId != requestId || raffle.status != RaffleStatus.REQUESTED) {
            emit InvalidVRFRequest(requestId);
            return;
        }
        uint256 word = randomWords[0];
        request.randomWord = word;
        request.blockFulfilled = block.number;
        raffle.status = RaffleStatus.FULFILLED;
        rafflePrize.winner = _computeWinner(raffleId, word);
        emit WinnerDrawn(requestId);
        IWinnablesTicket(TICKETS_CONTRACT).refreshMetadata(request.raffleId);
        unchecked {
            _ethLocked -= raffle.totalRaised;
        }
    }

    /// @dev Checks that a raffle's start time and end time are consistent with the rules:
    ///      - If startsAt is less than block.timestamp use block.timestamp as the reference
    ///      - Raffle duration should be at least MIN_RAFFLE_DURATION
    /// @param startsAt Raffle scheduled starting time
    /// @param endsAt Raffle scheduled ending time
    function _checkRaffleTimings(uint64 startsAt, uint64 endsAt) internal view {
        if (startsAt < block.timestamp) {
            startsAt = uint64(block.timestamp);
        }
        if (startsAt + MIN_RAFFLE_DURATION > endsAt) {
            revert RaffleClosingTooSoon();
        }
    }

    /// @dev Checks that all the necessary conditions are met to purchase a ticket
    /// @param raffleId ID of the raffle for which the tickets are being sold
    /// @param ticketCount Number of tickets to be sold
    function _checkTicketPurchaseable(uint256 raffleId, uint256 ticketCount) internal view {
        Raffle storage raffle = _raffles[raffleId];
        if (ticketCount > MAX_TICKET_PURCHASABLE) {
            revert MaxTicketExceed();
        }
        if (raffle.startsAt > block.timestamp) {
            revert RaffleHasNotStarted();
        }
        if (raffle.status != RaffleStatus.IDLE) {
            revert RaffleHasEnded();
        }
        if (block.timestamp > raffle.endsAt) {
            revert RaffleHasEnded();
        }
        uint256 ticketPurchased = uint256(uint32(uint256(raffle.participations[msg.sender]) >> 128));
        unchecked {
            if (ticketPurchased + ticketCount > raffle.maxHoldings) {
                revert TooManyTickets();
            }
        }
        uint256 supply = IWinnablesTicket(TICKETS_CONTRACT).supplyOf(raffleId);
        unchecked {
            if (supply + ticketCount > raffle.maxTicketSupply) {
                revert TooManyTickets();
            }
        }
    }

    function _checkShouldDraw(uint256 raffleId) internal view {
        Raffle storage raffle = _raffles[raffleId];
        if (raffle.status != RaffleStatus.IDLE) {
            revert InvalidRaffle();
        }
        uint256 currentTicketSold = IWinnablesTicket(TICKETS_CONTRACT).supplyOf(raffleId);
        if (currentTicketSold == 0) {
            revert NoParticipants();
        }
        if (block.timestamp <= raffle.endsAt) {
            if (currentTicketSold < raffle.maxTicketSupply) {
                revert RaffleIsStillOpen();
            }
        }
        if (currentTicketSold < raffle.minTicketsThreshold) {
            revert TargetTicketsNotReached();
        }
    }

    function _checkShouldCancel(uint256 raffleId) internal view {
        Raffle storage raffle = _raffles[raffleId];
        if (raffle.status == RaffleStatus.PRIZE_LOCKED) {
            _checkRole(msg.sender, 0);
            return;
        }
        if (raffle.status != RaffleStatus.IDLE) {
            revert InvalidRaffle();
        }
        if (block.timestamp <= raffle.endsAt) {
            revert RaffleIsStillOpen();
        }
        uint256 supply = IWinnablesTicket(TICKETS_CONTRACT).supplyOf(raffleId);
        if (supply == 0) {
            return;
        }
        if (supply >= raffle.minTicketsThreshold) {
            revert TargetTicketsReached();
        }
    }

    function _checkVRFTimeout(Raffle storage raffle) internal {
        uint256 requestId = raffle.chainlinkRequestId;
        RequestStatusWithFulfillmentTime storage request = _chainlinkRequests[requestId];
        if (request.blockFulfilled != 0) {
            revert InvalidRaffle();
        }
        uint256 blockLastRequested = request.blockLastRequested;
        uint256 blocksSinceLastRequest;
        unchecked {
            blocksSinceLastRequest = block.number - blockLastRequested;
        }
        if (blocksSinceLastRequest < VRF_REQUEST_TIMEOUT) {
            revert InvalidRaffle();
        }
        _checkRole(msg.sender, 0);
        delete _chainlinkRequests[requestId];
    }

    /// @dev Checks the validity of a signature to allow the purchase of tickets at a given price
    /// @param raffleId ID of the Raffle
    /// @param ticketCount Number of tickets purchased
    /// @param blockNumber Number of the block when the signature expires
    /// @param signature Signature to check
    function _checkPurchaseSig(
        uint256 raffleId,
        uint16 ticketCount,
        uint256 blockNumber,
        bytes calldata signature
    ) internal view {
        if (blockNumber < block.number) {
            revert ExpiredCoupon();
        }
        address signer = _getSigner(
            keccak256(
                abi.encodePacked(
                    msg.sender, _userNonces[msg.sender], raffleId, ticketCount, blockNumber, msg.value
                )
            ), signature
        );
        if (!_hasRole(signer, 1)) {
            revert Unauthorized();
        }
    }

    /// @dev Extracts the address of the signer from a signed message
    /// @param message SHA-3 Hash of the signed message
    /// @param signature Signature
    /// @return Address of the signer
    function _getSigner(bytes32 message, bytes calldata signature) internal pure returns(address) {
        bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message));
        return ECDSA.recover(hash, signature);
    }

    /// @dev Get the address of the winner of a given raffle for a given randomWord
    /// @param raffleId ID of the raffle
    /// @param randomWord randomWord returned by Chainlink VRF
    /// @return Address of the winner of the raffle
    function _computeWinner(uint256 raffleId, uint256 randomWord) internal view returns(address) {
        uint256 supply = IWinnablesTicket(TICKETS_CONTRACT).supplyOf(raffleId);
        uint256 winningTicketNumber = randomWord % supply;
        return IWinnablesTicket(TICKETS_CONTRACT).ownerOf(raffleId, winningTicketNumber);
    }

    function _checkValidRaffle(uint256 raffleId) internal view returns(RafflePrize storage) {
        if (raffleId == 0) {
            revert IllegalRaffleId();
        }
        RafflePrize storage rafflePrize = _rafflePrize[raffleId];
        Raffle storage raffle = _raffles[raffleId];
        if (rafflePrize.raffleType != RaffleType.NONE || raffle.status != RaffleStatus.NONE) {
            revert InvalidRaffleId();
        }
        return rafflePrize;
    }

    function _cancelRaffle(uint256 raffleId) internal {
        RaffleType raffleType = _rafflePrize[raffleId].raffleType;
        if (_rafflePrize[raffleId].status == RafflePrizeStatus.CANCELED) {
            revert InvalidRaffle();
        }
        if (raffleType == RaffleType.NFT) {
            NFTInfo storage nftInfo = _nftRaffles[raffleId];
            _nftLocked[nftInfo.contractAddress][nftInfo.tokenId] = false;
        } else if (raffleType == RaffleType.TOKEN) {
            TokenInfo storage tokenInfo = _tokenRaffles[raffleId];
            unchecked { _tokensLocked[tokenInfo.tokenAddress] -= tokenInfo.amount; }
        } else {
            unchecked { _ethLocked -= _ethRaffles[raffleId]; }
        }
        _rafflePrize[raffleId].status = RafflePrizeStatus.CANCELED;
        emit PrizeUnlocked(raffleId);
    }

    /// @dev Transfers the NFT prize to the winner
    /// @param nft NFT address
    /// @param tokenId NFT token id
    /// @param winner Address of the winner
    function _sendNFTPrize(address nft, uint256 tokenId, address winner) internal {
        IERC721(nft).transferFrom(address(this), winner, tokenId);
    }

    /// @dev Transfers the NFT prize to the winner
    /// @param token Token address
    /// @param amount Amount of tokens to send
    /// @param winner Address of the winner
    function _sendTokenPrize(address token, uint256 amount, address winner) internal {
        IERC20(token).safeTransfer(winner, amount);
    }
    /// @dev Sends ETH to an account and handles error cases
    /// @param amount The amount to send
    /// @param to The recipient
    function _sendETH(uint256 amount, address to) internal {
        if (amount == 0) {
            revert NothingToSend();
        }
        (bool success, ) = to.call{ value: amount }("");
        if (!success) {
            revert ETHTransferFail();
        }
    }

    /// @dev Setter for VRF Request Confirmations
    /// @param newRequestConfirmations New value for VRF Request Confirmations
    function setRequestConfirmations(uint16 newRequestConfirmations) external onlyRole(0) {
        _vrfRequestConfirmations = newRequestConfirmations;
    }
}
合同源代码
文件 34 的 34: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);
}
设置
{
  "compilationTarget": {
    "contracts/WinnablesRaffles.sol": "WinnablesRaffles"
  },
  "evmVersion": "shanghai",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"address","name":"_vrfCoordinator","type":"address"},{"internalType":"uint256","name":"_subscriptionId","type":"uint256"},{"internalType":"bytes32","name":"_keyHash","type":"bytes32"},{"internalType":"address","name":"_tickets","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyClaimed","type":"error"},{"inputs":[],"name":"ETHTransferFail","type":"error"},{"inputs":[],"name":"ExpiredCoupon","type":"error"},{"inputs":[],"name":"IllegalRaffleId","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidPrize","type":"error"},{"inputs":[],"name":"InvalidRaffle","type":"error"},{"inputs":[],"name":"InvalidRaffleId","type":"error"},{"inputs":[],"name":"InvalidRaffleStatus","type":"error"},{"inputs":[],"name":"InvalidTicketCount","type":"error"},{"inputs":[],"name":"LINKTokenNotPermitted","type":"error"},{"inputs":[],"name":"MaxTicketExceed","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"role","type":"uint256"}],"name":"MissingRole","type":"error"},{"inputs":[],"name":"NFTLocked","type":"error"},{"inputs":[],"name":"NoParticipants","type":"error"},{"inputs":[],"name":"NotAnNFT","type":"error"},{"inputs":[],"name":"NothingToSend","type":"error"},{"inputs":[{"internalType":"address","name":"have","type":"address"},{"internalType":"address","name":"want","type":"address"}],"name":"OnlyCoordinatorCanFulfill","type":"error"},{"inputs":[{"internalType":"address","name":"have","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"coordinator","type":"address"}],"name":"OnlyOwnerOrCoordinator","type":"error"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"PlayerAlreadyRefunded","type":"error"},{"inputs":[],"name":"PrizeNotLocked","type":"error"},{"inputs":[],"name":"RaffleClosingTooSoon","type":"error"},{"inputs":[],"name":"RaffleHasEnded","type":"error"},{"inputs":[],"name":"RaffleHasNotStarted","type":"error"},{"inputs":[],"name":"RaffleIsStillOpen","type":"error"},{"inputs":[],"name":"RaffleNotFulfilled","type":"error"},{"inputs":[],"name":"RaffleRequiresMaxHoldings","type":"error"},{"inputs":[],"name":"RaffleRequiresTicketSupplyCap","type":"error"},{"inputs":[],"name":"RaffleWontDraw","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"RequestNotFound","type":"error"},{"inputs":[],"name":"TargetTicketsNotReached","type":"error"},{"inputs":[],"name":"TargetTicketsReached","type":"error"},{"inputs":[],"name":"TooManyTickets","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnauthorizedToClaim","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"vrfCoordinator","type":"address"}],"name":"CoordinatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ETHPrizeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"ETHPrizeUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"InvalidVRFRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"contractAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"NFTPrizeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"NewRaffle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":true,"internalType":"bytes32","name":"participation","type":"bytes32"}],"name":"PlayerRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"winner","type":"address"}],"name":"PrizeClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"PrizeUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"messageId","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"sourceChainSelector","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"RafflePrizeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"RequestSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"role","type":"uint256"},{"indexed":true,"internalType":"bool","name":"status","type":"bool"}],"name":"RoleUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"contractAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokenPrizeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"TokenPrizeUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"WinnerDrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"winner","type":"address"}],"name":"WinnerPropagated","type":"event"},{"inputs":[],"name":"SUBSCRIPTION_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"uint16","name":"ticketCount","type":"uint16"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"buyTickets","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"cancelNotClaimedRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"cancelRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"claimPrize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"startsAt","type":"uint64"},{"internalType":"uint64","name":"endsAt","type":"uint64"},{"internalType":"uint32","name":"minTickets","type":"uint32"},{"internalType":"uint32","name":"maxTickets","type":"uint32"},{"internalType":"uint32","name":"maxHoldings","type":"uint32"}],"name":"createETHRaffle","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"address","name":"nft","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"startsAt","type":"uint64"},{"internalType":"uint64","name":"endsAt","type":"uint64"},{"internalType":"uint32","name":"minTickets","type":"uint32"},{"internalType":"uint32","name":"maxTickets","type":"uint32"},{"internalType":"uint32","name":"maxHoldings","type":"uint32"}],"name":"createNFTRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"uint64","name":"startsAt","type":"uint64"},{"internalType":"uint64","name":"endsAt","type":"uint64"},{"internalType":"uint32","name":"minTickets","type":"uint32"},{"internalType":"uint32","name":"maxTickets","type":"uint32"},{"internalType":"uint32","name":"maxHoldings","type":"uint32"}],"name":"createRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"startsAt","type":"uint64"},{"internalType":"uint64","name":"endsAt","type":"uint64"},{"internalType":"uint32","name":"minTickets","type":"uint32"},{"internalType":"uint32","name":"maxTickets","type":"uint32"},{"internalType":"uint32","name":"maxHoldings","type":"uint32"}],"name":"createTokenRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"drawWinner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getETHRaffle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getNFTRaffle","outputs":[{"components":[{"internalType":"address","name":"contractAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"internalType":"struct IWinnablesPrizeManager.NFTInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"buyer","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"address","name":"participant","type":"address"}],"name":"getParticipation","outputs":[{"internalType":"uint128","name":"totalSpent","type":"uint128"},{"internalType":"uint32","name":"totalPurchased","type":"uint32"},{"internalType":"bool","name":"withdrawn","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getRaffle","outputs":[{"internalType":"enum IWinnables.RaffleType","name":"raffleType","type":"uint8"},{"internalType":"uint64","name":"startsAt","type":"uint64"},{"internalType":"uint64","name":"endsAt","type":"uint64"},{"internalType":"uint32","name":"minTicketsThreshold","type":"uint32"},{"internalType":"uint32","name":"maxTicketSupply","type":"uint32"},{"internalType":"uint32","name":"maxHoldings","type":"uint32"},{"internalType":"uint256","name":"totalRaised","type":"uint256"},{"internalType":"enum IWinnables.RaffleStatus","name":"status","type":"uint8"},{"internalType":"uint256","name":"chainlinkRequestId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getRafflePrize","outputs":[{"components":[{"internalType":"enum IWinnables.RaffleType","name":"raffleType","type":"uint8"},{"internalType":"enum IWinnablesPrizeManager.RafflePrizeStatus","name":"status","type":"uint8"},{"internalType":"bytes32","name":"ccipCounterpart","type":"bytes32"},{"internalType":"address","name":"winner","type":"address"}],"internalType":"struct IWinnablesPrizeManager.RafflePrize","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"getRequestStatus","outputs":[{"internalType":"bool","name":"fulfilled","type":"bool"},{"internalType":"uint256","name":"randomWord","type":"uint256"},{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"uint256","name":"blockLastRequested","type":"uint256"},{"internalType":"uint256","name":"blockFulfilled","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getRoles","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getTokenRaffle","outputs":[{"components":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct IWinnablesPrizeManager.TokenInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"getWinner","outputs":[{"internalType":"address","name":"winner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lockETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"address","name":"nft","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"lockNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lockTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"rawFulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"address[]","name":"players","type":"address[]"}],"name":"refundPlayers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"s_vrfCoordinator","outputs":[{"internalType":"contract IVRFCoordinatorV2Plus","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vrfCoordinator","type":"address"}],"name":"setCoordinator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"newRequestConfirmations","type":"uint16"}],"name":"setRequestConfirmations","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint8","name":"role","type":"uint8"},{"internalType":"bool","name":"status","type":"bool"}],"name":"setRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"vrfCallbackGas","type":"uint32"}],"name":"setVRFCallbackGas","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"shouldCancelRaffle","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"shouldDrawRaffle","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nft","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"withdrawNotClaimedRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]