LooksRare Raffles Smart Contract
TL;DR: The raffle contract allows the creation of a new raffle with parameters such as cutoff time, minimum entries, maximum entries per participant, fees, and prizes. The prizes are deposited into the raffle at creation, and participants can enter the raffles by purchasing entries. Once the raffle is concluded, the winners are selected with the help of randomness provided by Chainlink VRF, after which the winners can claim their prizes. The contract also provides functionalities for the raffle owner to claim the collected fees and for the participants to withdraw or rollover their entry fees to any currently open raffles with the same fee currency in case the raffle is cancelled.
Functions
The contract includes various functions for interacting with the raffle system, such as creating a raffle (createRaffle
), depositing prizes (depositPrizes
), entering a raffle (enterRaffles
), selecting winners (selectWinners
), claiming prizes (claimPrize(s)
), claiming fees (claimFees
), cancelling a raffle (cancel
), claiming a refund (claimRefund
), rollover entries (rollover
), setting the protocol fee (setProtocolFeeBp
), setting the protocol fee recipient (setProtocolFeeRecipient
), updating the status of currencies (updateCurrenciesStatus
), toggling the contract's paused status (togglePaused
), and various getter functions to retrieve information about raffles, such as the winners, pricing options, prizes, and entries.
Events
Events are emitted for various actions, such as buying an entry, refunding an entry, updating the raffle status, claiming prizes, claiming fees, sending a randomness request etc.
Subgraph
Our Raffle Subgraph indexes the events emitted by this contract.
More details about this subgraph can also be found in the Raffle Subgraph documentation.
Deployed contracts addresses (v2)
Chain | Raffle v2 | Transfer Manager |
---|---|---|
Mainnet | 0x0000000000aDEaD599C11A0C9a7475B67852c1D0 | 0x00000000000ea4af05656C17b90f4d64AdD29e1d |
Sepolia | 0xae7cf912ab5d9f6aa53126ea7aabd007f844ceec | 0x8B43b6C4601FaCF70Fe17D057b3912Bde0206CFB |
Table of content
This page includes the following sections.
- Raffle System Overview
- Raffle Lifecycle
- Creating a raffle
- Entering a raffle
- Completing a raffle
- Selecting the winners
- Claiming the prizes
- Claiming the fees
- Cancelling a raffle
- Withdrawing the prizes
- Claiming a refund
- Rollover entries
- Collecting the protocol fees
- Read-only functions
- Raffle Object Data Types
- Other Data Types
Raffle System Overview
The Raffle system is designed to create and manage raffles on-chain and makes use of Chainlink's Verifiable Random Function (VRF) to provide a secure and provably fair mechanism for drawing winners.
The system allows users to create their own raffles (currently only at the contract level). The system is also designed to handle a wide range of tokens as prizes: ETH, ERC20, ERC721 and ERC1155. A raffle can have at most 200 winners and a max of 200 prizes.
Here is a brief description of how it works:
- Raffle Creation: A user (the raffle owner) creates a raffle by specifying the raffle parameters, such as the cut-off time, the minimum number of entries required to draw the raffle, the prize(s), the protocol fee, the pricing options for tickets, and the number of winners. The prizes are automatically deposited at this stage (ensure the Transfer Manager approvals are sorted). The raffle’s status is set to
Open
. - Raffle Entry: Participants can enter the raffle by purchasing entries. The number of tickets purchased determines the participant's chances of winning.
- Raffle Completion: Once a raffle’s total purchased entries reach the minimum number of entries required, the raffle’s status is set to
Drawing
, and the randomness request is sent. The raffle’s owner has also the option to draw the winners if the minimum number of entries is not met after the cutoff time. - Randomness Received: Once Chainlink fulfils the randomness request, the raffle’s status is set to
RandomnessFulfilled
and the winners can be selected. - Select Winners: The winners are selected using the randomness previously received by calling the
selectWinners
function. The raffle's status is set toDrawn
. - Claiming Prizes: Once the status is set to
Drawn
, the winners can claim their prizes. Prizes are transferred securely from the contract to the winner's address. - Claim Fees: The raffle’s owner can claim their fees once the winners have been selected. The raffle’s status is set to
Complete
(winners can continue to claim their prizes). At this stage, the protocol fee is also calculated and added to the totalprotocolFeeRecipientClaimableFees
.
Raffle Lifecycle
A raffle goes through several stages in its lifecycle:
- Open: The raffle is initialized with the specified parameters, the prizes get deposited, and the raffle is open for participants to buy entries.
- Drawing: The last entry has been sold, and the randomness request has been sent to Chainlink.
- RandomnessFulfilled: Chainlink has fulfilled the randomness request.
- Drawn: The winners of the raffle have been determined.
- Complete: The raffle is set to
Complete
once the fees have been withdrawn.
- Refundable: The raffle was cancelled because it didn’t reach the minimum number of entries before the
cutoffTime
, or if Chainlink did not fulfil the randomness request within the limit (one day). In this case, the owner can withdraw the prizes, and participants can request a refund for their entries. - Cancelled: The raffle’s owner withdrawn the prizes after canceling the raffle (based on the criteria outlined above). In this case, the participants can still request a refund for their entries (if applicable).
As long as there are more entries than prizes, the raffle's owner can draw the winners even tho the minimum entries are not met by calling drawWinners(uint256 raffleId)
.
Creating a raffle
Before creating a raffle, ensure to sort all the approvals needed to deposit the prizes, by approving ERC tokens to be transferred by the Transfer Manager. It's also required to grant approval to the Raffle V2 contract to request the transfer via the Transfer Manager. That can be done by calling grantApprovals()
with the Raffle V2 contract address as the parameter.
Raffles are created using the createRaffle(CreateRaffleCalldata calldata params)
function. This function accepts a CreateRaffleCalldata
struct as a parameter, which includes the following properties:
cutoffTime
: The time after which no more entries can be accepted for the raffle. Must be at least 1 day in the future and within 1 week.isMinimumEntriesFixed
: Specifies whether the minimum number of entries also acts as a maximum number of entries or allows for an additional batch of entries to be purchased even if the total would exceed theminimumEntries
amount.minimumEntries
: The minimum number of entries required to draw the raffle.maximumEntriesPerParticipant
: The maximum number of entries that a single participant can buy.protocolFeeBp
: The protocol fee in basis points.feeTokenAddress
: The address of the token to be used as a fee.prizes
: An array ofPrize
structs representing the prizes to be distributed.pricingOptions
: An array ofPricingOption
structs representing the pricing options for the raffle.
PricingOption
Each raffle can have between 1 to 5 pricing options. The PricingOption
must respect the following rules:
- The first pricing option's
entriesCount
(pricingOption[0].entriesCount
), must divide theminimumEntries
number evenly and be greater than zero. - The
entriesCount
of eachpricingOption
must be evenly divisible bypricingOption[0].entriesCount
. - The price of all pricing options must be divisible by the number of entries it provides.
- The entries given must increase as pricing options are added; they can't be less or equal to the previous
pricingOption
entries. - The price of the
pricingOption
must be greater than the previouspricingOption
. - The price per entry must be lower than the previous
pricingOption
.
If the raffle is successfully created, the function will return the raffleId
and the prizes defined will be transferred automatically.
Data Types
CreateRaffleCalldata
is a struct used when creating a raffle, including the cutoff time, whether the minimum number of entries is a strict limit, the minimum entries, the maximum entries per participant, the protocol fee, the fee token address, the prizes, and the pricing options.
struct CreateRaffleCalldata {
uint40 cutoffTime;
bool isMinimumEntriesFixed;
uint40 minimumEntries;
uint40 maximumEntriesPerParticipant;
uint16 protocolFeeBp;
address feeTokenAddress;
Prize[] prizes;
PricingOption[] pricingOptions;
}
Events Emitted
RaffleStatusUpdated(raffleId, RaffleStatus.Open)
Entering a raffle
Participants can enter a raffle using the enterRaffles(EntryCalldata[] calldata entries)
function. This function accepts an array of EntryCalldata
structs, each containing the raffleId
and the pricing option index/id that the participant wishes to enter, alongside the count/amount being bought and the recipient address (optional, defaults to the msg.sender
). The function checks if the raffle is still open, if the participant has not exceeded the maximum entries, and if they have enough balance to pay for the entries. If all checks pass, the entries are added to the raffle.
Data Types
EntryCalldata
is a struct used when making entries into a raffle, containing the raffle ID and the index of the pricing option.
struct EntryCalldata {
uint256 raffleId;
uint256 pricingOptionIndex;
uint40 count;
address recipient;
}
Events Emitted
EntrySold(raffleId, msg.sender, recipient, entriesCount, price)
Completing a raffle
When the last entry is sold (as defined in raffle.minimumEntries
), the raffle status will be automatically set to Drawing
, and a randomness request will be sent to Chainlink VRF. Once Chainlink has fulfilled the randomness request, the raffle’s status will be set to RandomnessFulfilled
.
Events Emitted
Once the randomness request is sent, the following events are emitted:
RaffleStatusUpdated(raffleId, RaffleStatus.Drawing)
RandomnessRequested(raffleId, requestId)
Once the randomness request has been fulfilled, the following events are emitted:
RaffleStatusUpdated(raffleId, RaffleStatus.RandomnessFulfilled)
Selecting the winners
The winners are drawn using the selectWinners(uint256 raffleId)
function, which accepts the raffleId
as a parameter. It checks if the raffle is in the Drawing
status and if Chainlink VRF has fulfilled the randomness request. If all checks pass, the function draws the winners for the raffle.
Methodology for Selecting Winners
Let's take an example where we have a total of 106 winners - one tier 0 prize, five tier 1 prizes, and one hundred tier 2 prizes.
We initiate the process by generating a random number using Chainlink's VRF.
This random number is then utilized to select the first winner, who receives the tier 0 prize.
To maintain randomness for subsequent selections, we then hash the initial random number using the
keccak256
function, producing a new random number.The second winner is then chosen using this new random number, and they are awarded the tier 1 prize.
In an event where a selected entry has already won, then the seed is rehashed instead to find the next winning entry.
This process is repeated for each remaining prize, moving from lower to higher tiers (with lower tiers being more valuable), until all the prizes have been distributed.
Events Emitted
RaffleStatusUpdated(raffleId, RaffleStatus.Drawn)
Claiming the prizes
The winners will be able to claim their prizes once the raffle’s status is set to Drawn
by calling either the claimPrizes(ClaimPrizesCalldata[] calldata claimPrizesCalldata)
function or the claimPrize(uint256 raffleId, uint256 winnerIndex)
function. The functions accepts an array of ClaimPrizesCalldata
structs, each containing a raffleId
and the index(es) of the prize(s) won (based upon the raffle.winners
array) for the claimPrizes
case or just the raffleId
and winnerIndex
in case of a single claim. If all the checks pass, the function caller will receive the prizes won. A winner can claim multiple prizes from multiple raffles in a single transaction.
Data Types
ClaimPrizesCalldata
is a struct used when claiming prizes, including the raffleId
and the indices of the winners (based on the Winner[]
array defined in Raffle
object, see Raffle Object Data Types).
struct ClaimPrizesCalldata {
uint256 raffleId;
uint256[] winnerIndices;
}
Events Emitted
PrizesClaimed(raffleId, winnerIndices)
ifclaimPrizes
was used.PrizeClaimed(raffleId, winnerIndex)
ifclaimPrize
was used.
Claiming the fees
Once the raffle’s status is set to Drawn
either to raffle's owner or the protocol owner can call the claimFees(uint256 raffleId)
function, which accepts the raffleId
as a parameter. This function will transfer the raffle profits to the raffle's owner and calculate the protocol fee. The protocol fees won't be transferred at this time, but instead, the value will be added to the protocolFeeRecipientClaimableFees[currency]
object. The protocol owner can then call the claimProtocolFees(address currency)
at any time to collect the fees accrued so far.
Events Emitted
RaffleStatusUpdated(raffleId, RaffleStatus.Complete)
FeesClaimed(raffleId, claimableFees)
Cancelling a raffle
The raffle’s owner can cancel the raffle if the minimum amount of entries has not been reached by the cutoff time (status Open
and timestamp > cutoffTime
). This can be done by calling the cancel(uint256 raffleId)
function, which accepts the raffleId
as a parameter. The contract’s owner can also call cancelAfterRandomnessRequest(uint256 raffleId)
if Chainlink does not fulfil the randomness request within the limit (one day).
Once one or more raffles are cancelled, the participants can get a refund of their entries, by calling claimRefund(uint256[] calldata raffleIds)
.
After a raffle is cancelled, the status will be set to Refundable
meaning the raffle’s owner can withdraw the prizes by calling withdrawPrizes(uint256 raffleId)
. Once the prizes have been withdrawn the status is ultimately set to Cancelled
. The participants will be able to claim a refund or rollover the entries with either of those raffle statuses.
Events Emitted
RaffleStatusUpdated(raffleId, RaffleStatus.Refundable)
(cancelled, but prizes not yet withdrawn)RaffleStatusUpdated(raffleId, RaffleStatus.Cancelled)
(cancelled and prizes withdrawn)
Withdrawing the prizes
Once the raffle has been cancelled and the status is set to Refundable
the raffle’s owner can withdraw the prizes deposited by calling withdrawPrizes(uint256 raffleId)
, which accepts the raffleId
as a parameter. This function will transfer all the deposited prizes back to the owner and set the raffle’s status to Cancelled
.
Events Emitted
RaffleStatusUpdated(raffleId, RaffleStatus.Cancelled)
Claiming a refund
Once a raffle has been cancelled, the participants can request a refund for their entries by calling the claimRefund(uint256[] calldata raffleIds)
function, which accepts an array of raffleId
. It’s possible to request refunds for multiple raffles in one call.
Events Emitted
EntryRefunded(raffleId, msg.sender, amountPaid)
Rollover entries
Once a raffle has been cancelled, the participants can rollover their entries by calling rollover(uint256[] calldata refundableRaffleIds, EntryCalldata[] calldata entries)
, which accepts an array of raffles ids (refundableRaffleIds
) to be refunded and an array of EntryCalldata
which are the entries to be bought in the new raffle. The fee token address for all the raffles involved must be the same.
Events Emitted
EntrySold(raffleId, msg.sender, recipient, entriesCount, price)
Collecting the protocol fees
Protocol fees are collected on each raffle creation and are set as a percentage of the total value of the raffle. The protocol fee is set in basis points (bps), where 1 bps is equal to 0.01%. The protocol fee is collected in the token specified by feeTokenAddress
during raffle creation.
The collected fees are stored in the contract and can be withdrawn by the contract owner to a specified address using the claimProtocolFees(address currency)
function. The function checks if the caller is the contract owner and if there are any fees to withdraw. If all checks pass, the fees are transferred to the specified address.
Read-only functions
Function | Input | Output | Description |
---|---|---|---|
rafflesCount | N/A | uint256 | The number of raffles created. |
raffles | raffleId | Raffle | The raffle object, see Raffle Object Data Types. |
rafflesParticipantsStats | raffleId, address | ParticipantStats | The participants stats of the raffles, see Other Data Types. |
isCurrencyAllowed | address | bool | It checks whether the currency is allowed. |
randomnessRequests | requestId | RandomnessRequest | The randomness requests, see Other Data Types. |
protocolFeeRecipient | N/A | address | The protocol fee recipient. |
protocolFeeBp | N/A | uint16 | The protocol fee in basis points. |
getWinners | raffleId | Winner[] | It calculates and returns the winners for a specific raffleId once the randomness request has been satisfied even if the function selectWinners hasn’t been executed yet. |
getPrizes | raffleId | Prize[] | It returns the raffle’s prizes for a specific raffleId . |
getEntries | raffleId | Entry[] | It returns the raffle’s entries for a specific raffleId . |
getPricingOptions | raffleId | PricingOption[5] | It returns the raffle’s pricing options for a specific raffleId . |
Raffle Object Data Types
Raffle
is a struct representing a raffle, including the owner's address, the status of the raffle, the cutoff time, the drawn time, the minimum entries, the maximum entries per participant, the protocol fee, the claimable fees, the pricing options, the prizes, the entries, and the winners.
struct Raffle {
address owner;
RaffleStatus status;
bool isMinimumEntriesFixed;
uint40 cutoffTime;
uint40 drawnAt;
uint40 minimumEntries;
uint40 maximumEntriesPerParticipant;
address feeTokenAddress;
uint16 protocolFeeBp;
uint208 claimableFees;
PricingOption[] pricingOptions;
Prize[] prizes;
Entry[] entries;
Winner[] winners;
}
RaffleStatus
is an enum with the different possible statuses of a raffle
enum RaffleStatus {
None,
Open,
Drawing,
RandomnessFulfilled,
Drawn,
Complete,
Refundable,
Cancelled,
}
PricingOption
is a struct that defines a pricing option, including the number of entries that can be purchased for the given price and the price itself.
struct PricingOption {
uint40 entriesCount;
uint208 price;
}
PricingOption
Each raffle can have between 1 to 5 pricing options. The PricingOption
must respect the following rules:
- The first pricing option's
entriesCount
(pricingOption[0].entriesCount
), must divide theminimumEntries
number evenly and be greater than zero. - The
entriesCount
of eachpricingOption
must be evenly divisible bypricingOption[0].entriesCount
. - The price of all pricing options must be divisible by the number of entries it provides.
- The entries given must increase as pricing options are added; they can't be less or equal to the previous
pricingOption
entries. - The price of the
pricingOption
must be greater than the previouspricingOption
. - The price per entry must be lower than the previous
pricingOption
.
Prize
is a struct defining a prize, including the number of winners, the type of the prize, the tier of the prize, the address of the prize, the prizeId
, and the amount of the prize.
struct Prize {
uint40 winnersCount;
uint40 cumulativeWinnersCount;
TokenType prizeType;
uint8 prizeTier;
address prizeAddress;
uint256 prizeId;
uint256 prizeAmount;
}
Entry
is a struct representing an entry in a raffle, including the participant's address and the cumulative number of entries in the raffle.
struct Entry {
uint40 currentEntryIndex;
address participant;
}
Winner
is a struct representing a raffle winner, including the participant's address, whether the prize has been claimed, the index of the prize, and the index of the winning entry.
struct Winner {
address participant;
bool claimed;
uint8 prizeIndex;
uint40 entryIndex;
}
Other Data Types
ParticipantStats
is a struct representing the statistics of a participant, including the amount paid, the number of entries, and whether the participant has been refunded.
struct ParticipantStats {
uint208 amountPaid;
uint40 entriesCount;
bool refunded;
}
RandomnessRequest
is a struct representing a randomness request, including whether the request exists, the raffleId, and the random words returned by Chainlink VRF.
struct RandomnessRequest {
bool exists;
uint80 raffleId;
uint256 randomWord;
}