// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../lib/Constants.sol";
import "./MinimalBase.sol";
/// @title Raffles manager (lean, delegate.cash, single winner, nft gated with an nft from each whitelisted collection and a fixed amount of ETH as prize)
/// @author Camden Grieh
/// @author Nathan Goodwin - revertCloseRequested function
/// @notice It consumes QRNG from API3. It has the role
/// "operator" that is the one used by a backend app to make some calls
/// @dev It saves in an ordered array the player wallet and the current
/// entries count. So buying entries has a complexity of O(1)
/// For calculating the winner, from the huge random number generated by Api3
/// a normalized random is generated by using the module method, adding 1 to have
/// a random from 1 to entriesCount.
/// So next step is to perform a binary search on the ordered array to get the
/// player O(log n)
/// Example:
/// 0 -> { 1, player1} as player1 buys 1 entry
/// 1 -> {51, player2} as player2 buys 50 entries
/// 2 -> {52, player3} as player3 buys 1 entry
/// 3 -> {53, player4} as player4 buys 1 entry
/// 4 -> {153, player5} as player5 buys 100 entries
/// So the setWinner method performs a binary search on that sorted array to get the upper bound.
/// If the random number generated is 150, the winner is player5. If the random number is 20, winner is player2
abstract contract BaseCompetition is Constants, MinimalBase {
event RaffleStatusUpdated(
uint256 indexed raffleId, STATUS newStatus
);
constructor(
address _airnodeRrp)
payable MinimalBase(_airnodeRrp) {}
/// @notice Called by the Airnode through the AirnodeRrp contract to
/// fulfill the request
function fulfillUint256(bytes32 requestId, bytes calldata data)
external override
onlyAirnodeRrp
{
require(
expectingRequestWithIdToBeFulfilled[requestId],
"Request ID not known"
);
expectingRequestWithIdToBeFulfilled[requestId] = false;
uint256 qrngUint256 = abi.decode(data, (uint256));
_qrngUint256 = qrngUint256;
// Do what you want with `qrngUint256` here...
emit ReceivedUint256(requestId, qrngUint256);
// randomness is the actual random number. Now extract from the aux map the original param id of the call
RaffleInfo memory raffleInfo = api3RaffleInfo[requestId];
// save the random number on the map with the original id as key
uint256 normalizedRandomNumber = (qrngUint256 % raffleInfo.size) + 1;
RandomResult memory result = RandomResult({
randomNumber: qrngUint256,
nomalizedRandomNumber: normalizedRandomNumber
});
requests[raffleInfo.id] = result;
// send the event with the original id and the random number
emit RandomNumberCreated(
raffleInfo.id,
qrngUint256,
normalizedRandomNumber
);
_transferPrizesAndFunds(raffleInfo.id, normalizedRandomNumber);
}
function _transferPrizesAndFunds(
uint256 idRaffle,
uint256 normalizedRandomNumber
) internal virtual;
function _saveEntryInfo() internal {
EntryInfoStruct memory entryInfo = EntryInfoStruct({
status: STATUS.CREATED,
amountRaised: 0,
entriesLength: 0,
walletsCap: 0,
requireWhitelisting: false
});
rafflesEntryInfo.push(entryInfo);
}
function _saveEntryInfo(
address[] calldata _whitelist,
uint48 _walletsCap,
uint256 _raffleId
) internal virtual {}
function _buyEntryActions(
uint256 _raffleId,
uint256 _id
) internal returns (EntryInfoStruct memory) {
EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
if (entryInfo.status != STATUS.ACCEPTED)
revert EntryNotAllowed("Not in ACCEPTED");
// Price checks
PriceStructure memory priceStruct = pricesList[_id];
if (priceStruct.id != _raffleId)
revert EntryNotAllowed("Id not in raffleId");
uint48 numEntries = priceStruct.numEntries;
if (msg.value != priceStruct.price)
revert EntryNotAllowed("msg.value not the price");
if (priceStruct.price == 0) {
if (tx.origin != msg.sender)
revert EntryNotAllowed("tx.origin != msg.sender");
bytes32 hash = keccak256(abi.encode(msg.sender, _raffleId));
if (freeEntriesPerWallet[hash] == true)
revert EntryNotAllowed("Player already got free entry");
freeEntriesPerWallet[hash] = true;
}
// save the entries onchain
uint48 entriesLength = entryInfo.entriesLength;
EntriesBought memory entryBought = EntriesBought({
player: msg.sender,
currentEntriesLength: uint48(entriesLength + numEntries)
});
entriesList[_raffleId].push(entryBought);
delete entriesList[_raffleId][0];
// update raffle variables
entryInfo.amountRaised += uint128(msg.value);
entryInfo.entriesLength = entriesLength + numEntries;
emit EntrySold(_raffleId, msg.sender, entryInfo.entriesLength, _id);
return entryInfo;
}
function _buyEntriesRequiredNFTCheck(
address col,
uint256 id,
uint256 _raffleId,
address sender
) internal {
bytes32 collectionHash = keccak256(abi.encode(col, _raffleId));
require(
whitelistCollections[collectionHash] == true,
"Not in required collection"
);
IERC721 requiredNFT = IERC721(col);
require(requiredNFT.ownerOf(id) == sender, "Not the owner of tokenId");
bytes32 hashRequiredNFT = keccak256(abi.encode(col, _raffleId, id));
// check the tokenId has not been using yet in the raffle, to avoid abuse
if (requiredNFTWallets[hashRequiredNFT] == address(0)) {
requiredNFTWallets[hashRequiredNFT] = sender;
} else
require(
requiredNFTWallets[hashRequiredNFT] == sender,
"tokenId used"
);
}
function _checkWalletsCap(
EntryInfoStruct memory entryInfo,
uint256 _raffleId,
uint256 _id
) internal {
// 31.05.23 wallet cap
if (entryInfo.walletsCap > 0) {
PriceStructure memory priceStruct = pricesList[_id];
bytes32 entriesCountHash = keccak256(
abi.encode(_raffleId, msg.sender)
);
uint48 entriesCount = walletsCap[entriesCountHash];
if (entriesCount + priceStruct.numEntries > entryInfo.walletsCap)
revert EntryNotAllowed("Wallet already used");
// update wallet cap of current user
walletsCap[entriesCountHash] =
walletsCap[entriesCountHash] +
priceStruct.numEntries;
}
}
// The operator can add free entries to the raffle
/// @param _raffleId Id of the raffle
/// @param _freePlayers array of addresses corresponding to the wallet of the users that won a free entrie
/// @dev only operator can make this call. Assigns a single entry per user, except if that user already reached the max limit of entries per user
function giveBatchEntriesForFree(
uint256 _raffleId,
address[] memory _freePlayers
) external onlyRole(OPERATOR_ROLE) {
EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
require(
entryInfo.status == STATUS.ACCEPTED,
"Raffle is not in accepted"
);
uint256 freePlayersLength = _freePlayers.length;
uint48 validPlayersCount;
for (uint256 i; i < freePlayersLength; ++i) {
address entry = _freePlayers[i];
{
// add a new element to the entriesBought array.
// as this method only adds 1 entry per call, the amountbought is always 1
EntriesBought memory entryBought = EntriesBought({
player: entry,
currentEntriesLength: uint48(
entryInfo.entriesLength + i + 1
)
});
entriesList[_raffleId].push(entryBought);
unchecked {
++validPlayersCount;
}
}
}
entryInfo.entriesLength = entryInfo.entriesLength + validPlayersCount;
emit FreeEntry(
_raffleId,
_freePlayers,
freePlayersLength,
entryInfo.entriesLength
);
}
function _setWinnerActions(
uint256 _raffleId
) internal virtual returns (EntryInfoStruct memory) {
EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
// RaffleStruct storage raffle = raffles[_raffleId];
FundingStructure storage funding = fundingList[_raffleId];
// Check if the raffle is already accepted or is called again because early cashout failed
require(entryInfo.status == STATUS.ACCEPTED, "Raffle in wrong status");
require(
entryInfo.amountRaised >= funding.minimumFundsInWeis,
"Not enough funds raised"
);
require(
funding.desiredFundsInWeis <= entryInfo.amountRaised,
"Desired funds not raised"
);
entryInfo.status = STATUS.CLOSING_REQUESTED;
emit SetWinnerTriggered(_raffleId, entryInfo.amountRaised);
return entryInfo;
}
/// @param _raffleId Id of the raffle
/// @notice the operator finish the raffle, if the desired funds has been reached
/// @dev it triggers Api3 QRNG consumer, and generates a random number that is normalized and checked that corresponds to a MW player
function setWinner(uint256 _raffleId) external onlyRole(OPERATOR_ROLE) {
EntryInfoStruct memory raffle = _setWinnerActions(_raffleId);
// this call triggers the QRNG from Api3
bytes32 requestId = _callQRNGAndGetRequestId();
_getRandomNumber(_raffleId, raffle.entriesLength, requestId);
}
/// @param _newAddress new address of the platform
/// @dev Change the wallet of the platform. The one that will receive the platform fee when the raffle is closed.
/// Only the admin can change this
function setDestinationAddress(
address payable _newAddress
) external onlyRole(DEFAULT_ADMIN_ROLE) {
destinationWallet = _newAddress;
}
/// @param _raffleId Id of the raffle
/// @dev The operator can cancel the raffle. The NFT is sent back to the seller
/// The raised funds are send to the destination wallet. The buyers will
/// be refunded offchain in the metawin wallet
function cancelRaffle(
uint256 _raffleId
) external virtual onlyRole(OPERATOR_ROLE) {}
/// @param _raffleId Id of the raffle
function transferRemainingFunds(
uint256 _raffleId
) external onlyRole(OPERATOR_ROLE) {
EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
require(
entryInfo.status == STATUS.CANCEL_REQUESTED ||
entryInfo.status == STATUS.CANCELLED,
"Wrong status"
);
entryInfo.status = STATUS.CANCELLED;
(bool sent, ) = destinationWallet.call{value: entryInfo.amountRaised}(
""
);
require(sent, "Fail send Eth to MW");
emit RemainingFundsTransferred(_raffleId, entryInfo.amountRaised);
entryInfo.amountRaised = 0;
}
/// @param _raffleId Id of the raffle
/// @return array of entries bougth of that particular raffle
function getEntriesBought(
uint256 _raffleId
) external view returns (EntriesBought[] memory) {
return entriesList[_raffleId];
}
function getRafflesEntryInfo(
uint256 _raffleId
) public view returns (EntryInfoStruct memory) {
return rafflesEntryInfo[_raffleId];
}
function _transferNFTTo(
address prizeAddress,
uint256 prizeNumber,
address receptor
) internal {
IERC721 _asset = IERC721(prizeAddress);
_asset.transferFrom(address(this), receptor, prizeNumber);
}
function _transferETHTo(uint256 prizeNumber, address receptor) internal {
(bool sentPrize, ) = receptor.call{value: prizeNumber}("");
require(sentPrize, "Failed to send Ether");
}
function _transferERC20To(
address prizeAddress,
uint256 prizeNumber,
address receptor
) internal {
IERC20 _asset = IERC20(prizeAddress);
_asset.transfer(receptor, prizeNumber);
}
/// @param _raffleId Id of the raffle
/// @dev The operator can cancel the raffle. The NFT is sent back to the seller
/// The raised funds are send to the destination wallet. The buyers will
/// be refunded offchain in the metawin wallet
function _cancelRaffleActions(uint256 _raffleId, uint48 _type) internal {
RaffleStruct storage raffle = raffles[_raffleId];
EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
// Dont cancel twice, or cancel an already ended raffle
//TODO: To save gas, check that it is in the correct status instead of checking all the wrong ones
require(
entryInfo.status == STATUS.ACCEPTED ||
entryInfo.status == STATUS.CREATED,
"Wrong status"
);
// only if the raffle is in accepted status the NFT is staked and could have entries sold
if (entryInfo.status == STATUS.ACCEPTED) {
if (_type == 0)
// transfer nft to the owner
_transferNFTTo(
raffle.prizeAddress,
raffle.prizeNumber,
raffle.seller
);
if (_type == 1)
// transfer ETH to the owner
_transferETHTo(raffle.prizeNumber, raffle.seller);
if (_type == 2)
// transfer ETH to the owner
_transferERC20To(
raffle.prizeAddress,
raffle.prizeNumber,
raffle.seller
);
}
entryInfo.status = STATUS.CANCEL_REQUESTED;
emit RaffleCancelled(_raffleId, entryInfo.amountRaised);
}
// The operator can call this method once they receive the event "RandomNumberCreated"
// triggered by the QRNG consumer contract (RandomNumber.sol)
/// @param _raffleId Id of the raffle
/// @param _normalizedRandomNumber index of the array that contains the winner of the raffle. Generated by api3
/// @notice it is the method that sets the winner and transfers funds and nft
/// @dev called by Api3 callback
function _transferPrizesAndFundsActions(
uint256 _raffleId,
uint256 _normalizedRandomNumber
) internal returns (RaffleStruct memory) {
RaffleStruct storage raffle = raffles[_raffleId];
EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
// Only when the raffle has been asked to be closed and the platform
require(
entryInfo.status == STATUS.EARLY_CASHOUT ||
entryInfo.status == STATUS.CLOSING_REQUESTED,
"Raffle in wrong status"
);
raffle.randomNumber = _normalizedRandomNumber;
raffle.winner = getWinnerAddressFromRandom(
_raffleId,
_normalizedRandomNumber
);
entryInfo.status = STATUS.ENDED;
uint256 amountForPlatform = (entryInfo.amountRaised *
raffle.platformPercentage) / 10000;
uint256 amountForSeller = entryInfo.amountRaised - amountForPlatform;
// transfer amount (75%) to the seller.
(bool sent, ) = raffle.seller.call{value: amountForSeller}("");
require(sent, "Failed to send Ether");
// transfer the amount to the platform
(bool sent2, ) = destinationWallet.call{value: amountForPlatform}("");
require(sent2, "Failed send Eth to MW");
emit FeeTransferredToPlatform(_raffleId, amountForPlatform);
emit RaffleEnded(
_raffleId,
raffle.winner,
entryInfo.amountRaised,
_normalizedRandomNumber
);
return raffle;
}
function _saveEntryInfo(
uint48 _walletsCapPerUser,
ENTRY_TYPE _entryType,
address[] calldata _whitelist,
uint256 _raffleId
) internal virtual {}
/// @notice Creates the raffle in the data structures of the contract
/// @dev Saves the raffle struct. Creates the packages with the price and amount of entries in the priceStructure struct.
/// Saves the desired and required funds, and to avoid the first player to pay extra gas, the entries data structure is created and deleted.
/// It is an internal method that will be called by the createRaffle method of the correspondent competition contract
/// @param _desiredFundsInWeis Funds in wais the seller wants
/// @param _prizeAddress if the prize is an NFT or an ERC20, the address of the prize. If the prize is ETH, will be address(0)
/// @param _prizeNumber if the prize is an NFT, the token Id of the NFT. If the prize is an ERC20 the number of tokens of the prize. If the prize
/// is ETH, the amount in weis of the prize.
/// @param _minimumFundsInWeis The mininum amount required for the raffle to set a winner
/// @param _prices array of elements of type PriceStructure that contains the packages with prize (in weis) and amount of entries used in the raffle
/// @param _commissionInBasicPoints percentage in basic points the platform will make.
/// @param _entryType Obsolete param (used for the hamburger system from Valerio), used only for compatibility porpouses.
function _createRaffleActions(
uint128 _desiredFundsInWeis,
address _prizeAddress,
uint256 _prizeNumber,
uint128 _minimumFundsInWeis,
PriceStructure[] calldata _prices,
uint48 _commissionInBasicPoints,
ENTRY_TYPE _entryType
) internal {
RaffleStruct memory raffle = RaffleStruct({
prizeAddress: _prizeAddress,
prizeNumber: _prizeNumber,
winner: address(0),
randomNumber: 0,
seller: address(0),
platformPercentage: _commissionInBasicPoints
});
raffles.push(raffle);
uint256 idRaffle = raffles.length - 1;
if (_prices.length == 0) revert CreateRaffleError("No prices");
for (uint256 i; i < _prices.length; ++i) {
if (_prices[i].numEntries == 0)
revert CreateRaffleError("numEntries is 0");
PriceStructure memory p = PriceStructure({
id: uint48(idRaffle),
numEntries: _prices[i].numEntries,
price: _prices[i].price
});
pricesList[_prices[i].id] = p;
}
fundingList[raffles.length - 1] = FundingStructure({
minimumFundsInWeis: _minimumFundsInWeis,
desiredFundsInWeis: _desiredFundsInWeis
});
emit RaffleCreated(raffles.length - 1, _prizeAddress, _prizeNumber);
// Initialize the entries list array, by adding a player and removing it
EntriesBought memory entryBought = EntriesBought({
player: msg.sender,
currentEntriesLength: uint48(1)
});
entriesList[idRaffle].push(entryBought);
delete entriesList[idRaffle][0];
}
function _saveEntryInfoForMulti(
address[] calldata _whitelist,
uint48 _walletsCap,
uint256 _raffleId
) internal {
bool requireWhitelisting = false;
uint256 whitelistLength = _whitelist.length;
if (whitelistLength > 0) {
requireWhitelisting = true;
for (uint256 i; i <= whitelistLength - 1; ++i) {
bytes32 key = keccak256(abi.encode(_whitelist[i], _raffleId));
whitelistCollections[key] = true;
}
}
EntryInfoStruct memory entryInfo = EntryInfoStruct({
status: STATUS.CREATED,
amountRaised: 0,
entriesLength: 0,
requireWhitelisting: requireWhitelisting,
walletsCap: _walletsCap
});
rafflesEntryInfo.push(entryInfo);
}
/**
* @dev Reverts the status of a raffle from CLOSING_REQUESTED back to ACCEPTED.
* This allows the raffle to remain open for further actions such as a redraw.
* Useful in avoiding funds becoming stuck in the event of a failed draw.
* @param raffleId The identifier of the raffle to revert the closing status for.
* @notice Can only be called by users with the OPERATOR_ROLE.
* Emits a RaffleStatusUpdated event upon successful status update.
* @notice Ensures the raffle is currently in CLOSING_REQUESTED status before reverting.
*/
function revertCloseRequested(uint256 raffleId) public onlyRole(OPERATOR_ROLE) {
require(rafflesEntryInfo[raffleId].status == STATUS.CLOSING_REQUESTED, "Raffle is not in close requested state");
rafflesEntryInfo[raffleId].status = STATUS.ACCEPTED;
emit RaffleStatusUpdated(raffleId, STATUS.ACCEPTED);
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "../interfaces/IDelegationRegistry.sol";
import "./MinimalConstants.sol";
abstract contract Constants is AccessControl, MinimalConstants {
////////// DELEGATE:CASH ///////////////
IDelegationRegistry reg;
// Main raffle data struct
struct RaffleStruct {
uint256 prizeNumber; // number (can be a percentage, an id, an amount, etc. depending on the competition)
uint48 platformPercentage; // percentage of the funds raised that goes to the platform
address prizeAddress; // address of the prize
address winner; // address of thed winner of the raffle. Address(0) if no winner yet
address seller; // address of the seller of the NFT
uint256 randomNumber; // normalized (0-Entries array size) random number generated by the VRF
}
// The main structure is an array of raffles
RaffleStruct[] public raffles;
struct EntryInfoStruct {
bool requireWhitelisting;
STATUS status; // status of the raffle. Can be created, accepted, ended, etc
uint48 walletsCap;
uint48 entriesLength; // to easy frontend, the length of the entries array is saved here
uint128 amountRaised; // funds raised so far in wei
}
// The main structure is an array of raffles
EntryInfoStruct[] public rafflesEntryInfo;
// All the different status a rafVRFCoordinatorfle can have
enum STATUS {
CREATED, // the operator creates the raffle
ACCEPTED, // the seller stakes the nft for the raffle
EARLY_CASHOUT, // the seller wants to cashout early
CANCELLED, // the operator cancels the raffle and transfer the remaining funds after 30 days passes
CLOSING_REQUESTED, // the operator requests to close the raffle and start the VRF
ENDED, // the raffle is finished, and NFT and funds were transferred
CANCEL_REQUESTED, // operator asks to cancel the raffle. Players has 30 days to ask for a refund
WINNER_DETERMINED // the vrf sets a winner
}
// Event sent when the raffle is created by the operator
event RaffleCreated(
uint256 indexed raffleId,
address indexed nftAddress,
uint256 indexed nftId
);
// Event sent when one or more entries are sold (info from the price structure)
event EntrySold(
uint256 indexed raffleId,
address indexed buyer,
uint256 currentSize,
uint256 priceStructureId
);
constructor() {
_grantRole(OPERATOR_ROLE, 0x13503B622abC0bD30A7e9687057DF6E8c42Fb928);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
// the delegation registry addess is the same in all networks:
// https://github.com/delegatecash/delegate-registry
reg = IDelegationRegistry(0x00000000000076A84feF008CDAbe6409d2FE638B);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
import "../lib/BaseCompetition.sol";
/// @title Raffles manager (lean, single winner and a fixed amount of ETH as prize)
/// @author Camden Grieh
/// @notice It consumes QRNG from API3. It has the role
/// "operator" that is the one used by a backend app to make some calls
/// @dev It saves in an ordered array the player wallet and the current
/// entries count. So buying entries has a complexity of O(1)
/// For calculating the winner, from the huge random number generated by Api3
/// a normalized random is generated by using the module method, adding 1 to have
/// a random from 1 to entriesCount.
/// So next step is to perform a binary search on the ordered array to get the
/// player O(log n)
/// Example:
/// 0 -> { 1, player1} as player1 buys 1 entry
/// 1 -> {51, player2} as player2 buys 50 entries
/// 2 -> {52, player3} as player3 buys 1 entry
/// 3 -> {53, player4} as player4 buys 1 entry
/// 4 -> {153, player5} as player5 buys 100 entries
/// So the setWinner method performs a binary search on that sorted array to get the upper bound.
/// If the random number generated is 150, the winner is player5. If the random number is 20, winner is player2
contract ETHComp is BaseCompetition {
constructor(address _airnodeRrp) BaseCompetition(_airnodeRrp) {}
//////////////////////////////////////////////
/// @param _desiredFundsInWeis the amount the seller would like to get from the raffle
/// @param _collateralAddress The address of the NFT of the raffle
/// @param _collateralId The id of the NFT (ERC721)
/// @param _minimumFundsInWeis The mininum amount required for the raffle to set a winner
/// @param _prices Array of prices and amount of entries the customer could purchase
// /// @param _commissionInBasicPoints commission for the platform, in basic points
/// @notice Creates a raffle
/// @dev creates a raffle struct and push it to the raffles array. Some data is stored in the funding data structure
/// sends an event when finished
/// @return raffleId
function createRaffle(
uint128 _desiredFundsInWeis,
uint256 _prizeNumber,
uint128 _minimumFundsInWeis,
PriceStructure[] calldata _prices,
uint48 _commissionInBasicPoints,
ENTRY_TYPE _entryType
) external onlyRole(OPERATOR_ROLE) returns (uint256) {
if (_commissionInBasicPoints > 5000)
revert CreateRaffleError("commission too high");
_createRaffleActions(
_desiredFundsInWeis,
address(0),
_prizeNumber,
_minimumFundsInWeis,
_prices,
_commissionInBasicPoints,
_entryType
);
_saveEntryInfo();
uint256 idRaffle = raffles.length - 1;
return idRaffle;
}
/// @param _raffleId Id of the raffle
function stakeETH(uint256 _raffleId) external payable {
EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
RaffleStruct storage raffle = raffles[_raffleId];
// Check if the raffle is already created
require(entryInfo.status == STATUS.CREATED, "Raffle not CREATED");
// check the amount staked is the correct
require(raffle.prizeNumber == msg.value, "Prize not staked");
entryInfo.status = STATUS.ACCEPTED;
raffle.seller = msg.sender;
emit RaffleStarted(_raffleId, msg.sender);
}
// The operator can call this method once they receive the event "RandomNumberCreated"
// triggered by the QRNG v1 consumer contract (RandomNumber.sol)
/// @param _raffleId Id of the raffle
/// @param _normalizedRandomNumber index of the array that contains the winner of the raffle. Generated by api3
/// @notice it is the method that sets the winner and transfers funds and nft
/// @dev called by Api3 callback
function _transferPrizesAndFunds(
uint256 _raffleId,
uint256 _normalizedRandomNumber
) internal override {
RaffleStruct memory raffle = _transferPrizesAndFundsActions(_raffleId, _normalizedRandomNumber);
_transferETHTo(raffle.prizeNumber, raffle.winner);
}
/// @param _raffleId Id of the raffle
/// @dev The operator can cancel the raffle. The NFT is sent back to the seller
/// The raised funds are send to the destination wallet. The buyers will
/// be refunded offchain in the metawin wallet
function cancelRaffle(uint256 _raffleId) external override onlyRole(OPERATOR_ROLE) {
_cancelRaffleActions(_raffleId, 1);
}
/// @dev callable by players. Depending on the number of entries assigned to the price structure the player buys (_id parameter)
/// one or more entries will be assigned to the player.
/// Also it is checked the maximum number of entries per user is not reached
/// As the method is payable, in msg.value there will be the amount paid by the user
/// @notice If the operator set requiredNFTs when creating the raffle, only the owners of nft on that collection can make a call to this method. This will be
/// used for special raffles
/// @param _raffleId: id of the raffle
/// @param _id: id of the price structure
function buyEntry(uint256 _raffleId, uint256 _id) virtual external payable {
EntryInfoStruct memory entryInfo = _buyEntryActions(_raffleId, _id);
_checkWalletsCap(entryInfo, _raffleId, /*msg.sender,*/ _id);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IAuthorizationUtilsV0.sol";
import "./ITemplateUtilsV0.sol";
import "./IWithdrawalUtilsV0.sol";
interface IAirnodeRrpV0 is
IAuthorizationUtilsV0,
ITemplateUtilsV0,
IWithdrawalUtilsV0
{
event SetSponsorshipStatus(
address indexed sponsor,
address indexed requester,
bool sponsorshipStatus
);
event MadeTemplateRequest(
address indexed airnode,
bytes32 indexed requestId,
uint256 requesterRequestCount,
uint256 chainId,
address requester,
bytes32 templateId,
address sponsor,
address sponsorWallet,
address fulfillAddress,
bytes4 fulfillFunctionId,
bytes parameters
);
event MadeFullRequest(
address indexed airnode,
bytes32 indexed requestId,
uint256 requesterRequestCount,
uint256 chainId,
address requester,
bytes32 endpointId,
address sponsor,
address sponsorWallet,
address fulfillAddress,
bytes4 fulfillFunctionId,
bytes parameters
);
event FulfilledRequest(
address indexed airnode,
bytes32 indexed requestId,
bytes data
);
event FailedRequest(
address indexed airnode,
bytes32 indexed requestId,
string errorMessage
);
function setSponsorshipStatus(address requester, bool sponsorshipStatus)
external;
function makeTemplateRequest(
bytes32 templateId,
address sponsor,
address sponsorWallet,
address fulfillAddress,
bytes4 fulfillFunctionId,
bytes calldata parameters
) external returns (bytes32 requestId);
function makeFullRequest(
address airnode,
bytes32 endpointId,
address sponsor,
address sponsorWallet,
address fulfillAddress,
bytes4 fulfillFunctionId,
bytes calldata parameters
) external returns (bytes32 requestId);
function fulfill(
bytes32 requestId,
address airnode,
address fulfillAddress,
bytes4 fulfillFunctionId,
bytes calldata data,
bytes calldata signature
) external returns (bool callSuccess, bytes memory callData);
function fail(
bytes32 requestId,
address airnode,
address fulfillAddress,
bytes4 fulfillFunctionId,
string calldata errorMessage
) external;
function sponsorToRequesterToSponsorshipStatus(
address sponsor,
address requester
) external view returns (bool sponsorshipStatus);
function requesterToRequestCountPlusOne(address requester)
external
view
returns (uint256 requestCountPlusOne);
function requestIsAwaitingFulfillment(bytes32 requestId)
external
view
returns (bool isAwaitingFulfillment);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IAuthorizationUtilsV0 {
function checkAuthorizationStatus(
address[] calldata authorizers,
address airnode,
bytes32 requestId,
bytes32 endpointId,
address sponsor,
address requester
) external view returns (bool status);
function checkAuthorizationStatuses(
address[] calldata authorizers,
address airnode,
bytes32[] calldata requestIds,
bytes32[] calldata endpointIds,
address[] calldata sponsors,
address[] calldata requesters
) external view returns (bool[] memory statuses);
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.17;
/**
* @title An immutable registry contract to be deployed as a standalone primitive
* @dev See EIP-5639, new project launches can read previous cold wallet -> hot wallet delegations
* from here and integrate those permissions into their flow
*/
interface IDelegationRegistry {
/// @notice Delegation type
enum DelegationType {
NONE,
ALL,
CONTRACT,
TOKEN
}
/// @notice Info about a single delegation, used for onchain enumeration
struct DelegationInfo {
DelegationType type_;
address vault;
address delegate;
address contract_;
uint256 tokenId;
}
/// @notice Info about a single contract-level delegation
struct ContractDelegation {
address contract_;
address delegate;
}
/// @notice Info about a single token-level delegation
struct TokenDelegation {
address contract_;
uint256 tokenId;
address delegate;
}
/// @notice Emitted when a user delegates their entire wallet
event DelegateForAll(address vault, address delegate, bool value);
/// @notice Emitted when a user delegates a specific contract
event DelegateForContract(address vault, address delegate, address contract_, bool value);
/// @notice Emitted when a user delegates a specific token
event DelegateForToken(address vault, address delegate, address contract_, uint256 tokenId, bool value);
/// @notice Emitted when a user revokes all delegations
event RevokeAllDelegates(address vault);
/// @notice Emitted when a user revoes all delegations for a given delegate
event RevokeDelegate(address vault, address delegate);
/**
* ----------- WRITE -----------
*/
/**
* @notice Allow the delegate to act on your behalf for all contracts
* @param delegate The hotwallet to act on your behalf
* @param value Whether to enable or disable delegation for this address, true for setting and false for revoking
*/
function delegateForAll(address delegate, bool value) external;
/**
* @notice Allow the delegate to act on your behalf for a specific contract
* @param delegate The hotwallet to act on your behalf
* @param contract_ The address for the contract you're delegating
* @param value Whether to enable or disable delegation for this address, true for setting and false for revoking
*/
function delegateForContract(address delegate, address contract_, bool value) external;
/**
* @notice Allow the delegate to act on your behalf for a specific token
* @param delegate The hotwallet to act on your behalf
* @param contract_ The address for the contract you're delegating
* @param tokenId The token id for the token you're delegating
* @param value Whether to enable or disable delegation for this address, true for setting and false for revoking
*/
function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external;
/**
* @notice Revoke all delegates
*/
function revokeAllDelegates() external;
/**
* @notice Revoke a specific delegate for all their permissions
* @param delegate The hotwallet to revoke
*/
function revokeDelegate(address delegate) external;
/**
* @notice Remove yourself as a delegate for a specific vault
* @param vault The vault which delegated to the msg.sender, and should be removed
*/
function revokeSelf(address vault) external;
/**
* ----------- READ -----------
*/
/**
* @notice Returns all active delegations a given delegate is able to claim on behalf of
* @param delegate The delegate that you would like to retrieve delegations for
* @return info Array of DelegationInfo structs
*/
function getDelegationsByDelegate(address delegate) external view returns (DelegationInfo[] memory);
/**
* @notice Returns an array of wallet-level delegates for a given vault
* @param vault The cold wallet who issued the delegation
* @return addresses Array of wallet-level delegates for a given vault
*/
function getDelegatesForAll(address vault) external view returns (address[] memory);
/**
* @notice Returns an array of contract-level delegates for a given vault and contract
* @param vault The cold wallet who issued the delegation
* @param contract_ The address for the contract you're delegating
* @return addresses Array of contract-level delegates for a given vault and contract
*/
function getDelegatesForContract(address vault, address contract_) external view returns (address[] memory);
/**
* @notice Returns an array of contract-level delegates for a given vault's token
* @param vault The cold wallet who issued the delegation
* @param contract_ The address for the contract holding the token
* @param tokenId The token id for the token you're delegating
* @return addresses Array of contract-level delegates for a given vault's token
*/
function getDelegatesForToken(address vault, address contract_, uint256 tokenId)
external
view
returns (address[] memory);
/**
* @notice Returns all contract-level delegations for a given vault
* @param vault The cold wallet who issued the delegations
* @return delegations Array of ContractDelegation structs
*/
function getContractLevelDelegations(address vault)
external
view
returns (ContractDelegation[] memory delegations);
/**
* @notice Returns all token-level delegations for a given vault
* @param vault The cold wallet who issued the delegations
* @return delegations Array of TokenDelegation structs
*/
function getTokenLevelDelegations(address vault) external view returns (TokenDelegation[] memory delegations);
/**
* @notice Returns true if the address is delegated to act on the entire vault
* @param delegate The hotwallet to act on your behalf
* @param vault The cold wallet who issued the delegation
*/
function checkDelegateForAll(address delegate, address vault) external view returns (bool);
/**
* @notice Returns true if the address is delegated to act on your behalf for a token contract or an entire vault
* @param delegate The hotwallet to act on your behalf
* @param contract_ The address for the contract you're delegating
* @param vault The cold wallet who issued the delegation
*/
function checkDelegateForContract(address delegate, address vault, address contract_)
external
view
returns (bool);
/**
* @notice Returns true if the address is delegated to act on your behalf for a specific token, the token's contract or an entire vault
* @param delegate The hotwallet to act on your behalf
* @param contract_ The address for the contract you're delegating
* @param tokenId The token id for the token you're delegating
* @param vault The cold wallet who issued the delegation
*/
function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId)
external
view
returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ITemplateUtilsV0 {
event CreatedTemplate(
bytes32 indexed templateId,
address airnode,
bytes32 endpointId,
bytes parameters
);
function createTemplate(
address airnode,
bytes32 endpointId,
bytes calldata parameters
) external returns (bytes32 templateId);
function getTemplates(bytes32[] calldata templateIds)
external
view
returns (
address[] memory airnodes,
bytes32[] memory endpointIds,
bytes[] memory parameters
);
function templates(bytes32 templateId)
external
view
returns (
address airnode,
bytes32 endpointId,
bytes memory parameters
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IWithdrawalUtilsV0 {
event RequestedWithdrawal(
address indexed airnode,
address indexed sponsor,
bytes32 indexed withdrawalRequestId,
address sponsorWallet
);
event FulfilledWithdrawal(
address indexed airnode,
address indexed sponsor,
bytes32 indexed withdrawalRequestId,
address sponsorWallet,
uint256 amount
);
function requestWithdrawal(address airnode, address sponsorWallet) external;
function fulfillWithdrawal(
bytes32 withdrawalRequestId,
address airnode,
address sponsor
) external payable;
function sponsorToWithdrawalRequestCount(address sponsor)
external
view
returns (uint256 withdrawalRequestCount);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
import "../lib/MinimalConstants.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol";
/// @title Raffles manager (lean, delegate.cash, single winner, nft gated with an nft from each whitelisted collection and a fixed amount of ETH as prize)
/// @author Camden Grieh
/// @notice It consumes QRNG from API3. It has the role
/// "operator" that is the one used by a backend app to make some calls
/// @dev It saves in an ordered array the player wallet and the current
/// entries count. So buying entries has a complexity of O(1)
/// For calculating the winner, from the huge random number generated by Api3
/// a normalized random is generated by using the module method, adding 1 to have
/// a random from 1 to entriesCount.
/// So next step is to perform a binary search on the ordered array to get the
/// player O(log n)
/// Example:
/// 0 -> { 1, player1} as player1 buys 1 entry
/// 1 -> {51, player2} as player2 buys 50 entries
/// 2 -> {52, player3} as player3 buys 1 entry
/// 3 -> {53, player4} as player4 buys 1 entry
/// 4 -> {153, player5} as player5 buys 100 entries
/// So the setWinner method performs a binary search on that sorted array to get the upper bound.
/// If the random number generated is 150, the winner is player5. If the random number is 20, winner is player2
abstract contract MinimalBase is
AccessControl,
MinimalConstants,
RrpRequesterV0
{
event RequestedUint256(bytes32 indexed requestId);
event ReceivedUint256(bytes32 indexed requestId, uint256 response);
event RequestedUint256Array(bytes32 indexed requestId, uint256 size);
event ReceivedUint256Array(bytes32 indexed requestId, uint256[] response);
event WithdrawalRequested(
address indexed airnode,
address indexed sponsorWallet
);
address public airnode; // The address of the QRNG Airnode
bytes32 public endpointIdUint256; // The endpoint ID for requesting a single random number
bytes32 public endpointIdUint256Array; // The endpoint ID for requesting an array of random numbers
address public sponsorWallet; // The wallet that will cover the gas costs of the request
uint256 public _qrngUint256; // The random number returned by the QRNG Airnode
uint256[] public _qrngUint256Array; // The array of random numbers returned by the QRNG Airnode
mapping(bytes32 => bool) public expectingRequestWithIdToBeFulfilled;
constructor(address _airnodeRrp) RrpRequesterV0(_airnodeRrp) {}
/// @notice Sets the parameters for making requests
function setRequestParameters(
address _airnode,
bytes32 _endpointIdUint256,
bytes32 _endpointIdUint256Array,
address _sponsorWallet
) external onlyRole(DEFAULT_ADMIN_ROLE) {
airnode = _airnode;
endpointIdUint256 = _endpointIdUint256;
endpointIdUint256Array = _endpointIdUint256Array;
sponsorWallet = _sponsorWallet;
}
function fulfillUint256(
bytes32 requestId,
bytes calldata data
) external virtual onlyAirnodeRrp {}
function _callQRNGAndGetRequestId() internal returns (bytes32) {
bytes32 _requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256.selector,
""
);
expectingRequestWithIdToBeFulfilled[_requestId] = true;
emit RequestedUint256(_requestId);
return _requestId;
}
/// @dev this is the method that will be called by the smart contract to get a random number
/// @param _id Id of the raffle
/// @param _entriesSize length of the entries array of that raffle
/// @param _requestId id generated by Api3 QRNG
function _getRandomNumber(
uint256 _id,
uint256 _entriesSize,
bytes32 _requestId
) internal {
api3RaffleInfo[_requestId] = RaffleInfo({id: _id, size: _entriesSize});
}
// helper method to get the winner address of a raffle
/// @param _raffleId Id of the raffle
/// @param _normalizedRandomNumber Generated by api3
/// @return the wallet that won the raffle
/// @dev Uses a binary search on the sorted array to retreive the winner
/// but if the winner candidate is blacklisted, loop through the left looking for
/// a candidate not blacklisted
function getWinnerAddressFromRandom(
uint256 _raffleId,
uint256 _normalizedRandomNumber
) public view virtual returns (address) {
uint256 position = _findUpperBound(
entriesList[_raffleId],
_normalizedRandomNumber
);
address candidate = entriesList[_raffleId][position].player;
// general case
if (candidate != address(0)) return candidate;
// special case. The user is blacklisted, so try next on the left until find a non-blacklisted
else {
bool ended = false;
uint256 i = position;
while (
ended == false && entriesList[_raffleId][i].player == address(0)
) {
if (i == 0) i = entriesList[_raffleId].length - 1;
else i = i - 1;
// we came to the beginning without finding a non blacklisted player
if (i == position) ended == true;
}
require(!ended, "All users blacklisted");
return entriesList[_raffleId][i].player;
}
}
/// @param array sorted array of EntriesBought. CurrentEntriesLength is the numeric field used to sort
/// @param element uint256 to find. Goes from 1 to entriesLength
/// @dev based on openzeppelin code (v4.0), modified to use an array of EntriesBought
/// Searches a sorted array and returns the first index that contains a value greater or equal to element.
/// If no such index exists (i.e. all values in the array are strictly less than element), the array length is returned. Time complexity O(log n).
/// array is expected to be sorted in ascending order, and to contain no repeated elements.
/// https://docs.openzeppelin.com/contracts/3.x/api/utils#Arrays-_findUpperBound-uint256---uint256-
function _findUpperBound(
EntriesBought[] storage array,
uint256 element
) internal view returns (uint256) {
if (array.length == 0) {
return 0;
}
uint256 low = 0;
uint256 high = array.length;
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds down (it does integer division with truncation).
if (array[mid].currentEntriesLength > element) {
high = mid;
} else {
low = mid + 1;
}
}
// At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && array[low - 1].currentEntriesLength == element) {
return low - 1;
} else {
return low;
}
}
/// @notice To withdraw funds from the sponsor wallet to the contract.
function requestWithdraw() external onlyRole(DEFAULT_ADMIN_ROLE) {
airnodeRrp.requestWithdrawal(
airnode,
sponsorWallet
);
}
/// @notice To withdraw funds from the contract to the owner.
function withdraw() external onlyRole(DEFAULT_ADMIN_ROLE) {
payable(destinationWallet).transfer(address(this).balance);
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "../lib/interfaces/VRFCoordinatorV2Interface.sol";
abstract contract MinimalConstants {
////////// API3 QRNG /////////////////
struct RandomResult {
uint256 randomNumber; // random number generated by api3.
uint256 nomalizedRandomNumber; // random number % entriesLength + 1. So between 1 and entries.length
}
// event sent when the random number is generated by the VRF
event RandomNumberCreated(
uint256 indexed idFromMetawin,
uint256 randomNumber,
uint256 normalizedRandomNumber
);
struct RaffleInfo {
uint256 id; // raffleId
uint256 size; // length of the entries array of that raffle
}
mapping(uint256 => RandomResult) public requests;
// map the requestId created by api3 with the raffle info passed as param when calling _getRandomNumber()
mapping(bytes32 => RaffleInfo) public api3RaffleInfo;
/////////////// END API3 QRNG //////////////
error EntryNotAllowed(string errorType);
error CreateRaffleError(string errorType);
// Event sent when the owner of the nft stakes it for the raffle
event RaffleStarted(uint256 indexed raffleId, address indexed seller);
// Event sent when the raffle is finished (either early cashout or successful completion)
event RaffleEnded(
uint256 indexed raffleId,
address indexed winner,
uint256 amountRaised,
uint256 randomNumber
);
// Event sent when a free entry is added by the operator
event FreeEntry(
uint256 indexed raffleId,
address[] buyer,
uint256 amount,
uint256 currentSize
);
// Event sent when a raffle is asked to cancel by the operator
event RaffleCancelled(uint256 indexed raffleId, uint256 amountRaised);
// The raffle is closed successfully and the platform receives the fee
event FeeTransferredToPlatform(
uint256 indexed raffleId,
uint256 amountTransferred
);
// When the raffle is asked to be cancelled and 30 days have passed, the operator can call a method
// to transfer the remaining funds and this event is emitted
event RemainingFundsTransferred(
uint256 indexed raffleId,
uint256 amountInWeis
);
// When the raffle is asked to be cancelled and 30 days have not passed yet, the players can call a
// method to refund the amount spent on the raffle and this event is emitted
event Refund(
uint256 indexed raffleId,
uint256 amountInWeis,
address indexed player
);
event EarlyCashoutTriggered(uint256 indexed raffleId, uint256 amountRaised);
event SetWinnerTriggered(uint256 indexed raffleId, uint256 amountRaised);
// Emitted when an entry is cancelled
event EntryCancelled(
uint256 indexed raffleId,
uint256 amountOfEntriesCanceled,
address player
);
struct PriceStructure {
uint48 id;
uint48 numEntries;
uint168 price;
}
mapping(uint256 => PriceStructure) public pricesList;
// Every raffle has a funding structure.
struct FundingStructure {
uint128 minimumFundsInWeis;
uint128 desiredFundsInWeis;
}
mapping(uint256 => FundingStructure) public fundingList;
// In order to calculate the winner, in this struct is saved for each bought the data
struct EntriesBought {
uint48 currentEntriesLength; // current amount of entries bought in the raffle
address player; // wallet address of the player
}
// every raffle has a sorted array of EntriesBought. Each element is created when calling
// either buyEntry or giveBatchEntriesForFree
mapping(uint256 => EntriesBought[]) public entriesList;
// Map with the player wallets linked to a particular raffle + nft
mapping(bytes32 => address) public requiredNFTWallets;
mapping(bytes32 => uint48) public walletsCap;
// key = collection address + raffleID
mapping(bytes32 => bool) public whitelistCollections;
mapping(bytes32 => bool) public freeEntriesPerWallet;
struct NFTIdUsed {
address collection;
uint256 tokenId;
}
enum ENTRY_TYPE {
ONLY_DIRECTLY,
ONLY_EXTERNAL_CONTRACT,
MIXED
}
// The operator role is operated by a backend application
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR");
// requested by Hamburger. Role for the buy method of the hamburger (only that contract)
bytes32 public constant MINTERCONTRACT_ROLE = keccak256("MINTERCONTRACT");
// address of the wallet controlled by the platform that will receive the platform fee
address payable public destinationWallet =
payable(0x52a032cF59eA274f9D745f29b6D514fe95Ba192D);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/IAirnodeRrpV0.sol";
/// @title The contract to be inherited to make Airnode RRP requests
contract RrpRequesterV0 {
IAirnodeRrpV0 public immutable airnodeRrp;
/// @dev Reverts if the caller is not the Airnode RRP contract.
/// Use it as a modifier for fulfill and error callback methods, but also
/// check `requestId`.
modifier onlyAirnodeRrp() {
require(msg.sender == address(airnodeRrp), "Caller not Airnode RRP");
_;
}
/// @dev Airnode RRP address is set at deployment and is immutable.
/// RrpRequester is made its own sponsor by default. RrpRequester can also
/// be sponsored by others and use these sponsorships while making
/// requests, i.e., using this default sponsorship is optional.
/// @param _airnodeRrp Airnode RRP contract address
constructor(address _airnodeRrp) {
airnodeRrp = IAirnodeRrpV0(_airnodeRrp);
IAirnodeRrpV0(_airnodeRrp).setSponsorshipStatus(address(this), true);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface VRFCoordinatorV2Interface {
/**
* @notice Get configuration relevant for making requests
* @return minimumRequestConfirmations global min for request confirmations
* @return maxGasLimit global max for request gas limit
* @return s_provingKeyHashes list of registered key hashes
*/
function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory);
/**
* @notice Request a set of random words.
* @param keyHash - Corresponds to a particular oracle job which uses
* that key for generating the VRF proof. Different keyHash's have different gas price
* ceilings, so you can select a specific one to bound your maximum per request cost.
* @param subId - The ID of the VRF subscription. Must be funded
* with the minimum subscription balance required for the selected keyHash.
* @param minimumRequestConfirmations - How many blocks you'd like the
* oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
* for why you may want to request more. The acceptable range is
* [minimumRequestBlockConfirmations, 200].
* @param callbackGasLimit - How much gas you'd like to receive in your
* _fulfillRandomWords callback. Note that gasleft() inside _fulfillRandomWords
* may be slightly less than this amount because of gas used calling the function
* (argument decoding etc.), so you may need to request slightly more than you expect
* to have inside _fulfillRandomWords. The acceptable range is
* [0, maxGasLimit]
* @param numWords - The number of uint256 random values you'd like to receive
* in your _fulfillRandomWords callback. Note these numbers are expanded in a
* secure way by the VRFCoordinator from a single random value supplied by the oracle.
* @return requestId - A unique identifier of the request. Can be used to match
* a request to a response in _fulfillRandomWords.
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external returns (uint256 requestId);
/**
* @notice Create a VRF subscription.
* @return subId - A unique subscription id.
* @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
* @dev Note to fund the subscription, use transferAndCall. For example
* @dev LINKTOKEN.transferAndCall(
* @dev address(COORDINATOR),
* @dev amount,
* @dev abi.encode(subId));
*/
function createSubscription() external returns (uint64 subId);
/**
* @notice Get a VRF subscription.
* @param subId - ID of the subscription
* @return balance - LINK balance of the subscription in juels.
* @return reqCount - number of requests for this subscription, determines fee tier.
* @return owner - owner of the subscription.
* @return consumers - list of consumer address which are able to use this subscription.
*/
function getSubscription(
uint64 subId
) external view returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers);
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @param newOwner - proposed new owner of the subscription
*/
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @dev will revert if original owner of subId has
* not requested that msg.sender become the new owner.
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external;
/**
* @notice Add a consumer to a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - New consumer which can use the subscription
*/
function addConsumer(uint64 subId, address consumer) external;
/**
* @notice Remove a consumer from a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - Consumer to remove from the subscription
*/
function removeConsumer(uint64 subId, address consumer) external;
/**
* @notice Cancel a subscription
* @param subId - ID of the subscription
* @param to - Where to send the remaining LINK to
*/
function cancelSubscription(uint64 subId, address to) external;
/*
* @notice Check to see if there exists a request commitment consumers
* for all consumers and keyhashes for a given sub.
* @param subId - ID of the subscription
* @return true if there exists at least one unfulfilled request for the subscription, false
* otherwise.
*/
function pendingRequestExists(uint64 subId) external view returns (bool);
}
{
"compilationTarget": {
"contracts/core/ETHComp.sol": "ETHComp"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_airnodeRrp","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"string","name":"errorType","type":"string"}],"name":"CreateRaffleError","type":"error"},{"inputs":[{"internalType":"string","name":"errorType","type":"string"}],"name":"EntryNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountRaised","type":"uint256"}],"name":"EarlyCashoutTriggered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOfEntriesCanceled","type":"uint256"},{"indexed":false,"internalType":"address","name":"player","type":"address"}],"name":"EntryCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"currentSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"priceStructureId","type":"uint256"}],"name":"EntrySold","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountTransferred","type":"uint256"}],"name":"FeeTransferredToPlatform","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"buyer","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentSize","type":"uint256"}],"name":"FreeEntry","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountRaised","type":"uint256"}],"name":"RaffleCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"nftAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"nftId","type":"uint256"}],"name":"RaffleCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"winner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountRaised","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"randomNumber","type":"uint256"}],"name":"RaffleEnded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller","type":"address"}],"name":"RaffleStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"enum Constants.STATUS","name":"newStatus","type":"uint8"}],"name":"RaffleStatusUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"idFromMetawin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"randomNumber","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"normalizedRandomNumber","type":"uint256"}],"name":"RandomNumberCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"response","type":"uint256"}],"name":"ReceivedUint256","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":false,"internalType":"uint256[]","name":"response","type":"uint256[]"}],"name":"ReceivedUint256Array","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountInWeis","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"}],"name":"Refund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountInWeis","type":"uint256"}],"name":"RemainingFundsTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"}],"name":"RequestedUint256","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"size","type":"uint256"}],"name":"RequestedUint256Array","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"raffleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountRaised","type":"uint256"}],"name":"SetWinnerTriggered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"airnode","type":"address"},{"indexed":true,"internalType":"address","name":"sponsorWallet","type":"address"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTERCONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_qrngUint256","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"_qrngUint256Array","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"airnode","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"airnodeRrp","outputs":[{"internalType":"contract IAirnodeRrpV0","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"api3RaffleInfo","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"size","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"},{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"buyEntry","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"cancelRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"_desiredFundsInWeis","type":"uint128"},{"internalType":"uint256","name":"_prizeNumber","type":"uint256"},{"internalType":"uint128","name":"_minimumFundsInWeis","type":"uint128"},{"components":[{"internalType":"uint48","name":"id","type":"uint48"},{"internalType":"uint48","name":"numEntries","type":"uint48"},{"internalType":"uint168","name":"price","type":"uint168"}],"internalType":"struct MinimalConstants.PriceStructure[]","name":"_prices","type":"tuple[]"},{"internalType":"uint48","name":"_commissionInBasicPoints","type":"uint48"},{"internalType":"enum MinimalConstants.ENTRY_TYPE","name":"_entryType","type":"uint8"}],"name":"createRaffle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"destinationWallet","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endpointIdUint256","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endpointIdUint256Array","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"entriesList","outputs":[{"internalType":"uint48","name":"currentEntriesLength","type":"uint48"},{"internalType":"address","name":"player","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"expectingRequestWithIdToBeFulfilled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"freeEntriesPerWallet","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"fulfillUint256","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"fundingList","outputs":[{"internalType":"uint128","name":"minimumFundsInWeis","type":"uint128"},{"internalType":"uint128","name":"desiredFundsInWeis","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"getEntriesBought","outputs":[{"components":[{"internalType":"uint48","name":"currentEntriesLength","type":"uint48"},{"internalType":"address","name":"player","type":"address"}],"internalType":"struct MinimalConstants.EntriesBought[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"getRafflesEntryInfo","outputs":[{"components":[{"internalType":"bool","name":"requireWhitelisting","type":"bool"},{"internalType":"enum Constants.STATUS","name":"status","type":"uint8"},{"internalType":"uint48","name":"walletsCap","type":"uint48"},{"internalType":"uint48","name":"entriesLength","type":"uint48"},{"internalType":"uint128","name":"amountRaised","type":"uint128"}],"internalType":"struct Constants.EntryInfoStruct","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"},{"internalType":"uint256","name":"_normalizedRandomNumber","type":"uint256"}],"name":"getWinnerAddressFromRandom","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"},{"internalType":"address[]","name":"_freePlayers","type":"address[]"}],"name":"giveBatchEntriesForFree","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pricesList","outputs":[{"internalType":"uint48","name":"id","type":"uint48"},{"internalType":"uint48","name":"numEntries","type":"uint48"},{"internalType":"uint168","name":"price","type":"uint168"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"raffles","outputs":[{"internalType":"uint256","name":"prizeNumber","type":"uint256"},{"internalType":"uint48","name":"platformPercentage","type":"uint48"},{"internalType":"address","name":"prizeAddress","type":"address"},{"internalType":"address","name":"winner","type":"address"},{"internalType":"address","name":"seller","type":"address"},{"internalType":"uint256","name":"randomNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rafflesEntryInfo","outputs":[{"internalType":"bool","name":"requireWhitelisting","type":"bool"},{"internalType":"enum Constants.STATUS","name":"status","type":"uint8"},{"internalType":"uint48","name":"walletsCap","type":"uint48"},{"internalType":"uint48","name":"entriesLength","type":"uint48"},{"internalType":"uint128","name":"amountRaised","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requestWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"requests","outputs":[{"internalType":"uint256","name":"randomNumber","type":"uint256"},{"internalType":"uint256","name":"nomalizedRandomNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"requiredNFTWallets","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"raffleId","type":"uint256"}],"name":"revertCloseRequested","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_newAddress","type":"address"}],"name":"setDestinationAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_airnode","type":"address"},{"internalType":"bytes32","name":"_endpointIdUint256","type":"bytes32"},{"internalType":"bytes32","name":"_endpointIdUint256Array","type":"bytes32"},{"internalType":"address","name":"_sponsorWallet","type":"address"}],"name":"setRequestParameters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"setWinner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sponsorWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"stakeETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"transferRemainingFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"walletsCap","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"whitelistCollections","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]