编译器
0.8.24+commit.e11b9ed9
文件 1 的 34:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
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");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
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");
}
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);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
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);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
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);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
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 {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
文件 2 的 34:BaseCCIPContract.sol
pragma solidity 0.8.24;
contract BaseCCIPContract {
error InvalidRouter(address router);
error UnauthorizedCCIPSender();
address internal immutable CCIP_ROUTER;
mapping(bytes32 => bool) internal _ccipContracts;
constructor(address router) {
CCIP_ROUTER = router;
}
function getCCIPRouter() external view returns (address) {
return CCIP_ROUTER;
}
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
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";
abstract contract BaseCCIPReceiver is BaseCCIPContract, IAny2EVMMessageReceiver, IERC165 {
modifier onlyRouter() {
if (msg.sender != CCIP_ROUTER) revert InvalidRouter(msg.sender);
_;
}
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}
function ccipReceive(Client.Any2EVMMessage calldata message) external virtual override onlyRouter {
_ccipReceive(message);
}
function _ccipReceive(Client.Any2EVMMessage memory message) internal virtual;
}
文件 4 的 34:BaseCCIPSender.sol
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);
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();
}
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
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;
}
function getLinkToken() external view returns (address) {
return LINK_TOKEN;
}
}
文件 6 的 34:Bits.sol
pragma solidity 0.8.24;
library Bits {
function getBool(bytes32 p, uint8 offset) internal pure returns (bool r) {
assembly {
r := and(shr(offset, p), 1)
}
}
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
pragma solidity ^0.8.0;
library Client {
struct EVMTokenAmount {
address token;
uint256 amount;
}
struct Any2EVMMessage {
bytes32 messageId;
uint64 sourceChainSelector;
bytes sender;
bytes data;
EVMTokenAmount[] destTokenAmounts;
}
struct EVM2AnyMessage {
bytes receiver;
bytes data;
EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
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
pragma solidity ^0.8.0;
import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol";
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
文件 9 的 34:ConfirmedOwnerWithProposal.sol
pragma solidity ^0.8.0;
import {IOwnable} from "../interfaces/IOwnable.sol";
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) {
require(newOwner != address(0), "Cannot set owner to zero");
s_owner = newOwner;
if (pendingOwner != address(0)) {
_transferOwnership(pendingOwner);
}
}
function transferOwnership(address to) public override onlyOwner {
_transferOwnership(to);
}
function acceptOwnership() external override {
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);
}
function owner() public view override returns (address) {
return s_owner;
}
function _transferOwnership(address to) private {
require(to != msg.sender, "Cannot transfer to self");
s_pendingOwner = to;
emit OwnershipTransferRequested(s_owner, to);
}
function _validateOwnership() internal view {
require(msg.sender == s_owner, "Only callable by owner");
}
modifier onlyOwner() {
_validateOwnership();
_;
}
}
文件 10 的 34:ECDSA.sol
pragma solidity ^0.8.0;
import "../Strings.sol";
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return;
} 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");
}
}
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
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);
}
}
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
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);
}
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
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;
}
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
文件 11 的 34:IAny2EVMMessageReceiver.sol
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
interface IAny2EVMMessageReceiver {
function ccipReceive(Client.Any2EVMMessage calldata message) external;
}
文件 12 的 34:IERC1155.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC1155 is IERC165 {
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
external
view
returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}
文件 13 的 34:IERC1155MetadataURI.sol
pragma solidity ^0.8.0;
import "../IERC1155.sol";
interface IERC1155MetadataURI is IERC1155 {
function uri(uint256 id) external view returns (string memory);
}
文件 14 的 34:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 15 的 34:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
文件 16 的 34:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
文件 17 的 34:IOwnable.sol
pragma solidity ^0.8.0;
interface IOwnable {
function owner() external returns (address);
function transferOwnership(address recipient) external;
function acceptOwnership() external;
}
文件 18 的 34:IRouterClient.sol
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
interface IRouterClient {
error UnsupportedDestinationChain(uint64 destChainSelector);
error InsufficientFeeTokenAmount();
error InvalidMsgValue();
function isChainSupported(uint64 chainSelector) external view returns (bool supported);
function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens);
function getFee(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external view returns (uint256 fee);
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
}
文件 19 的 34:IVRFCoordinatorV2Plus.sol
pragma solidity ^0.8.0;
import {VRFV2PlusClient} from "../libraries/VRFV2PlusClient.sol";
import {IVRFSubscriptionV2Plus} from "./IVRFSubscriptionV2Plus.sol";
interface IVRFCoordinatorV2Plus is IVRFSubscriptionV2Plus {
function requestRandomWords(VRFV2PlusClient.RandomWordsRequest calldata req) external returns (uint256 requestId);
}
文件 20 的 34:IVRFMigratableConsumerV2Plus.sol
pragma solidity ^0.8.0;
interface IVRFMigratableConsumerV2Plus {
event CoordinatorSet(address vrfCoordinator);
function setCoordinator(address vrfCoordinator) external;
}
文件 21 的 34:IVRFSubscriptionV2Plus.sol
pragma solidity ^0.8.0;
interface IVRFSubscriptionV2Plus {
function addConsumer(uint256 subId, address consumer) external;
function removeConsumer(uint256 subId, address consumer) external;
function cancelSubscription(uint256 subId, address to) external;
function acceptSubscriptionOwnerTransfer(uint256 subId) external;
function requestSubscriptionOwnerTransfer(uint256 subId, address newOwner) external;
function createSubscription() external returns (uint256 subId);
function getSubscription(
uint256 subId
)
external
view
returns (uint96 balance, uint96 nativeBalance, uint64 reqCount, address owner, address[] memory consumers);
function pendingRequestExists(uint256 subId) external view returns (bool);
function getActiveSubscriptionIds(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory);
function fundSubscriptionWithNative(uint256 subId) external payable;
}
文件 22 的 34:IWinnables.sol
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
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
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
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
pragma solidity ^0.8.0;
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
pragma solidity ^0.8.0;
library Math {
enum Rounding {
Down,
Up,
Zero
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a & b) + (a ^ b) / 2;
}
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
return a == 0 ? 0 : (a - 1) / b + 1;
}
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
uint256 prod0;
uint256 prod1;
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
return prod0 / denominator;
}
require(denominator > prod1);
uint256 remainder;
assembly {
remainder := mulmod(x, y, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
uint256 twos = denominator & (~denominator + 1);
assembly {
denominator := div(denominator, twos)
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
uint256 inverse = (3 * denominator) ^ 2;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
result = prod0 * inverse;
return result;
}
}
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;
}
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 result = 1 << (log2(a) >> 1);
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);
}
}
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);
}
}
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;
}
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);
}
}
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;
}
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);
}
}
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;
}
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
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);
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
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
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));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
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");
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 30 的 34:Strings.sol
pragma solidity ^0.8.0;
import "./math/Math.sol";
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
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);
}
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
文件 31 的 34:VRFConsumerBaseV2Plus.sol
pragma solidity ^0.8.4;
import {IVRFCoordinatorV2Plus} from "./interfaces/IVRFCoordinatorV2Plus.sol";
import {IVRFMigratableConsumerV2Plus} from "./interfaces/IVRFMigratableConsumerV2Plus.sol";
import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
abstract contract VRFConsumerBaseV2Plus is IVRFMigratableConsumerV2Plus, ConfirmedOwner {
error OnlyCoordinatorCanFulfill(address have, address want);
error OnlyOwnerOrCoordinator(address have, address owner, address coordinator);
error ZeroAddress();
IVRFCoordinatorV2Plus public s_vrfCoordinator;
constructor(address _vrfCoordinator) ConfirmedOwner(msg.sender) {
if (_vrfCoordinator == address(0)) {
revert ZeroAddress();
}
s_vrfCoordinator = IVRFCoordinatorV2Plus(_vrfCoordinator);
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal virtual;
function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external {
if (msg.sender != address(s_vrfCoordinator)) {
revert OnlyCoordinatorCanFulfill(msg.sender, address(s_vrfCoordinator));
}
fulfillRandomWords(requestId, randomWords);
}
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
pragma solidity ^0.8.4;
library VRFV2PlusClient {
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
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;
bytes32 private immutable KEY_HASH;
uint256 public immutable SUBSCRIPTION_ID;
mapping(uint256 => RequestStatusWithFulfillmentTime) internal _chainlinkRequests;
mapping(uint256 => Raffle) private _raffles;
mapping(address => uint256) private _userNonces;
uint16 private _vrfRequestConfirmations = 3;
mapping(uint256 => RafflePrize) private _rafflePrize;
mapping(uint256 => NFTInfo) private _nftRaffles;
mapping(uint256 => uint256) private _ethRaffles;
mapping(uint256 => TokenInfo) private _tokenRaffles;
uint256 private _ethLocked;
mapping(address => uint256) private _tokensLocked;
mapping(address => mapping(uint256 => bool)) private _nftLocked;
uint32 private _vrfCallbackGas = 100_000;
constructor(
address _vrfCoordinator,
uint256 _subscriptionId,
bytes32 _keyHash,
address _tickets
) VRFConsumerBaseV2Plus(_vrfCoordinator) {
SUBSCRIPTION_ID = _subscriptionId;
KEY_HASH = _keyHash;
TICKETS_CONTRACT = _tickets;
IWinnablesTicket(_tickets).initializeManager();
}
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;
}
function getRafflePrize(uint256 id) external view returns(RafflePrize memory) {
return _rafflePrize[id];
}
function getNFTRaffle(uint256 id) external view returns(NFTInfo memory) {
RaffleType raffleType = _rafflePrize[id].raffleType;
if (raffleType != RaffleType.NFT) {
revert InvalidRaffle();
}
return _nftRaffles[id];
}
function getETHRaffle(uint256 id) external view returns(uint256) {
RaffleType raffleType = _rafflePrize[id].raffleType;
if (raffleType != RaffleType.ETH) {
revert InvalidRaffle();
}
return _ethRaffles[id];
}
function getTokenRaffle(uint256 id) external view returns(TokenInfo memory) {
RaffleType raffleType = _rafflePrize[id].raffleType;
if (raffleType != RaffleType.TOKEN) {
revert InvalidRaffle();
}
return _tokenRaffles[id];
}
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;
}
function getWinner(uint256 raffleId) external view returns(address winner) {
winner = _rafflePrize[raffleId].winner;
if (winner == address(0)) {
revert RaffleNotFulfilled();
}
}
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;
}
function shouldDrawRaffle(uint256 raffleId) external view returns(bool) {
_checkShouldDraw(raffleId);
return true;
}
function shouldCancelRaffle(uint256 raffleId) external view returns(bool) {
_checkShouldCancel(raffleId);
return true;
}
function getNonce(address buyer) external view returns(uint256) {
return _userNonces[buyer];
}
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);
}
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);
}
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);
}
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;
}
}
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);
}
function lockNFT(
uint256 raffleId,
address nft,
uint256 tokenId
) external onlyRole(0) {
_lockNFT(raffleId, nft, tokenId);
}
function lockETH(
uint256 raffleId,
uint256 amount
) external payable onlyRole(0) {
_lockETH(raffleId, amount);
}
function lockTokens(
uint256 raffleId,
address token,
uint256 amount
) external onlyRole(0) {
_lockTokens(raffleId, token, amount);
}
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);
}
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);
}
function createRaffle(
uint256 raffleId,
uint64 startsAt,
uint64 endsAt,
uint32 minTickets,
uint32 maxTickets,
uint32 maxHoldings
) external onlyRole(0) {
_createRaffle(raffleId, startsAt, endsAt, minTickets, maxTickets, maxHoldings);
}
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);
}
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);
}
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);
}
function withdrawTokens(address tokenAddress, uint256 amount) external onlyRole(0) {
IERC20(tokenAddress).safeTransfer(msg.sender, amount);
}
function withdrawETH() external onlyRole(0) {
uint256 balance;
unchecked {
balance = address(this).balance - _ethLocked;
}
_sendETH(balance, msg.sender);
}
function setVRFCallbackGas(uint32 vrfCallbackGas) external onlyRole(0) {
_vrfCallbackGas = vrfCallbackGas;
}
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();
}
unchecked {
_ethLocked += raffle.totalRaised;
}
_cancelRaffle(raffleId);
raffle.status = RaffleStatus.CANCELED;
IWinnablesTicket(TICKETS_CONTRACT).refreshMetadata(raffleId);
}
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();
}
RafflePrize storage rafflePrize = _rafflePrize[raffleId];
address winner = rafflePrize.winner;
rafflePrize.winner = msg.sender;
this.claimPrize(raffleId);
rafflePrize.winner = winner;
}
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);
}
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);
}
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);
}
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);
}
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;
}
}
function _checkRaffleTimings(uint64 startsAt, uint64 endsAt) internal view {
if (startsAt < block.timestamp) {
startsAt = uint64(block.timestamp);
}
if (startsAt + MIN_RAFFLE_DURATION > endsAt) {
revert RaffleClosingTooSoon();
}
}
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];
}
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();
}
}
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);
}
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);
}
function _sendNFTPrize(address nft, uint256 tokenId, address winner) internal {
IERC721(nft).transferFrom(address(this), winner, tokenId);
}
function _sendTokenPrize(address token, uint256 amount, address winner) internal {
IERC20(token).safeTransfer(winner, amount);
}
function _sendETH(uint256 amount, address to) internal {
if (amount == 0) {
revert NothingToSend();
}
(bool success, ) = to.call{ value: amount }("");
if (!success) {
revert ETHTransferFail();
}
}
function setRequestConfirmations(uint16 newRequestConfirmations) external onlyRole(0) {
_vrfRequestConfirmations = newRequestConfirmations;
}
}
文件 34 的 34:draft-IERC20Permit.sol
pragma solidity ^0.8.0;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
{
"compilationTarget": {
"contracts/WinnablesRaffles.sol": "WinnablesRaffles"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1
},
"remappings": []
}
[{"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"}]