编译器
0.8.24+commit.e11b9ed9
文件 1 的 20: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 的 20: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 的 20: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 的 20: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 的 20: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 的 20: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 的 20: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 的 20:IAny2EVMMessageReceiver.sol
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
interface IAny2EVMMessageReceiver {
function ccipReceive(Client.Any2EVMMessage calldata message) external;
}
文件 9 的 20:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 10 的 20: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);
}
文件 11 的 20: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);
}
文件 12 的 20:IERC721Receiver.sol
pragma solidity ^0.8.0;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
文件 13 的 20: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);
}
文件 14 的 20: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 }
struct RequestStatus {
uint256 raffleId;
uint256 randomWord;
uint256 blockLastRequested;
}
struct Raffle {
RaffleStatus status;
uint64 startsAt;
uint64 endsAt;
uint32 minTicketsThreshold;
uint32 maxTicketSupply;
uint32 maxHoldings;
uint256 totalRaised;
uint256 chainlinkRequestId;
bytes32 ccipCounterpart;
mapping(address => bytes32) participations;
}
}
文件 15 的 20: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 CCIPMessageType {
RAFFLE_CANCELED,
WINNER_DRAWN
}
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;
}
}
文件 16 的 20: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);
}
文件 17 的 20: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];
}
}
文件 18 的 20: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");
}
}
}
文件 19 的 20:WinnablesPrizeManager.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/token/ERC721/IERC721Receiver.sol";
import "./Roles.sol";
import "./BaseCCIPSender.sol";
import "./BaseCCIPReceiver.sol";
import "./interfaces/IWinnablesPrizeManager.sol";
contract WinnablesPrizeManager is Roles, BaseCCIPSender, BaseCCIPReceiver, IWinnablesPrizeManager, IERC721Receiver {
using SafeERC20 for IERC20;
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;
constructor(
address _linkToken,
address _ccipRouter
) BaseCCIPContract(_ccipRouter) BaseLinkConsumer(_linkToken, _ccipRouter) {}
function getRaffle(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 getWinner(uint256 id) external view returns(address) {
return _rafflePrize[id].winner;
}
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]; }
_sendETHPrize(_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(
address ticketManager,
uint64 chainSelector,
uint256 raffleId,
address nft,
uint256 tokenId
) external onlyRole(0) {
RafflePrize storage rafflePrize = _checkValidRaffle(raffleId);
rafflePrize.ccipCounterpart = _packCCIPContract(ticketManager, chainSelector);
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;
_sendCCIPMessage(ticketManager, chainSelector, abi.encodePacked(raffleId));
emit NFTPrizeLocked(raffleId, nft, tokenId);
}
function lockETH(
address ticketManager,
uint64 chainSelector,
uint256 raffleId,
uint256 amount
) external payable onlyRole(0) {
RafflePrize storage rafflePrize = _checkValidRaffle(raffleId);
rafflePrize.ccipCounterpart = _packCCIPContract(ticketManager, chainSelector);
uint256 ethBalance = address(this).balance;
if (ethBalance < amount + _ethLocked) {
revert InvalidPrize();
}
rafflePrize.raffleType = RaffleType.ETH;
_ethLocked += amount;
_ethRaffles[raffleId] = amount;
_sendCCIPMessage(ticketManager, chainSelector, abi.encodePacked(raffleId));
emit ETHPrizeLocked(raffleId, amount);
}
function lockTokens(
address ticketManager,
uint64 chainSelector,
uint256 raffleId,
address token,
uint256 amount
) external onlyRole(0) {
if (token == LINK_TOKEN) {
revert LINKTokenNotPermitted();
}
RafflePrize storage rafflePrize = _checkValidRaffle(raffleId);
rafflePrize.ccipCounterpart = _packCCIPContract(ticketManager, chainSelector);
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;
_sendCCIPMessage(ticketManager, chainSelector, abi.encodePacked(raffleId));
emit TokenPrizeLocked(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 withdrawETH(uint256 amount) external onlyRole(0) {
uint256 balance = address(this).balance;
uint256 availableBalance;
unchecked { availableBalance = balance - _ethLocked; }
if (availableBalance < amount) {
revert InsufficientBalance();
}
(bool success,) = msg.sender.call{ value: amount }("");
if (!success) {
revert ETHTransferFail();
}
}
function setCCIPExtraArgs(bytes calldata extraArgs) external onlyRole(0) {
_setCCIPExtraArgs(extraArgs);
}
function _checkValidRaffle(uint256 raffleId) internal view returns(RafflePrize storage) {
if (raffleId == 0) {
revert IllegalRaffleId();
}
RafflePrize storage rafflePrize = _rafflePrize[raffleId];
if (rafflePrize.raffleType != RaffleType.NONE) {
revert InvalidRaffleId();
}
return rafflePrize;
}
function _ccipReceive(
Client.Any2EVMMessage memory message
) internal override {
(address _senderAddress) = abi.decode(message.sender, (address));
bytes32 counterpart = _packCCIPContract(_senderAddress, message.sourceChainSelector);
CCIPMessageType messageType = CCIPMessageType(uint8(message.data[0]));
uint256 raffleId;
address winner;
if (messageType == CCIPMessageType.RAFFLE_CANCELED) {
raffleId = _decodeRaffleCanceledMessage(message.data);
if (_rafflePrize[raffleId].ccipCounterpart != counterpart) {
revert UnauthorizedCCIPSender();
}
_cancelRaffle(raffleId);
return;
}
(raffleId, winner) = _decodeWinnerDrawnMessage(message.data);
if (_rafflePrize[raffleId].ccipCounterpart != counterpart) {
revert UnauthorizedCCIPSender();
}
_rafflePrize[raffleId].winner = winner;
emit WinnerPropagated(raffleId, winner);
}
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 _sendETHPrize(uint256 amount, address winner) internal {
(bool success, ) = winner.call{ value: amount }("");
if (!success) {
revert ETHTransferFail();
}
}
function _decodeRaffleCanceledMessage(bytes memory b) internal pure returns(uint256 raffleId) {
assembly { raffleId := mload(add(b, 0x21)) }
}
function _decodeWinnerDrawnMessage(bytes memory b) internal pure returns(uint256 raffleId, address winner) {
assembly {
raffleId := mload(add(b, 0x21))
winner := and(
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
mload(add(b, 0x35))
)
}
}
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
文件 20 的 20: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/WinnablesPrizeManager.sol": "WinnablesPrizeManager"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_linkToken","type":"address"},{"internalType":"address","name":"_ccipRouter","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":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"InsufficientLinkBalance","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidPrize","type":"error"},{"inputs":[],"name":"InvalidRaffle","type":"error"},{"inputs":[],"name":"InvalidRaffleId","type":"error"},{"inputs":[{"internalType":"address","name":"router","type":"address"}],"name":"InvalidRouter","type":"error"},{"inputs":[],"name":"LINKTokenNotPermitted","type":"error"},{"inputs":[],"name":"LinkApprovalFailed","type":"error"},{"inputs":[],"name":"MissingCCIPParams","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":"player","type":"address"}],"name":"PlayerAlreadyRefunded","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":[{"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":"UnauthorizedCCIPSender","type":"error"},{"inputs":[],"name":"UnauthorizedToClaim","type":"error"},{"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":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":"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":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":[{"components":[{"internalType":"bytes32","name":"messageId","type":"bytes32"},{"internalType":"uint64","name":"sourceChainSelector","type":"uint64"},{"internalType":"bytes","name":"sender","type":"bytes"},{"internalType":"bytes","name":"data","type":"bytes"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct Client.EVMTokenAmount[]","name":"destTokenAmounts","type":"tuple[]"}],"internalType":"struct Client.Any2EVMMessage","name":"message","type":"tuple"}],"name":"ccipReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"claimPrize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCCIPRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getETHRaffle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLinkToken","outputs":[{"internalType":"address","name":"","type":"address"}],"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":"uint256","name":"id","type":"uint256"}],"name":"getRaffle","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":"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":"id","type":"uint256"}],"name":"getWinner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ticketManager","type":"address"},{"internalType":"uint64","name":"chainSelector","type":"uint64"},{"internalType":"uint256","name":"raffleId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lockETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"ticketManager","type":"address"},{"internalType":"uint64","name":"chainSelector","type":"uint64"},{"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":"address","name":"ticketManager","type":"address"},{"internalType":"uint64","name":"chainSelector","type":"uint64"},{"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":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"name":"setCCIPExtraArgs","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":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"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":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"}]