Ana içeriğe geç

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)

ChainRaffle v2Transfer Manager
Mainnet0x0000000000aDEaD599C11A0C9a7475B67852c1D00x00000000000ea4af05656C17b90f4d64AdD29e1d
Sepolia0xae7cf912ab5d9f6aa53126ea7aabd007f844ceec0x8B43b6C4601FaCF70Fe17D057b3912Bde0206CFB

Table of content

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:

  1. 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.
  2. Raffle Entry: Participants can enter the raffle by purchasing entries. The number of tickets purchased determines the participant's chances of winning.
  3. 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.
  4. Randomness Received: Once Chainlink fulfils the randomness request, the raffle’s status is set to RandomnessFulfilled and the winners can be selected.
  5. Select Winners: The winners are selected using the randomness previously received by calling the selectWinners function. The raffle's status is set to Drawn.
  6. 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.
  7. 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 total protocolFeeRecipientClaimableFees.

Raffle Lifecycle

A raffle goes through several stages in its lifecycle:

  1. Open: The raffle is initialized with the specified parameters, the prizes get deposited, and the raffle is open for participants to buy entries.
  2. Drawing: The last entry has been sold, and the randomness request has been sent to Chainlink.
  3. RandomnessFulfilled: Chainlink has fulfilled the randomness request.
  4. Drawn: The winners of the raffle have been determined.
  5. 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).
bilgi

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 the minimumEntries 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 of Prize structs representing the prizes to be distributed.
  • pricingOptions: An array of PricingOption 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:

  1. The first pricing option's entriesCount (pricingOption[0].entriesCount), must divide the minimumEntries number evenly and be greater than zero.
  2. The entriesCount of each pricingOption must be evenly divisible by pricingOption[0].entriesCount.
  3. The price of all pricing options must be divisible by the number of entries it provides.
  4. The entries given must increase as pricing options are added; they can't be less or equal to the previous pricingOption entries.
  5. The price of the pricingOption must be greater than the previous pricingOption.
  6. 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.

  1. We initiate the process by generating a random number using Chainlink's VRF.

  2. This random number is then utilized to select the first winner, who receives the tier 0 prize.

  3. To maintain randomness for subsequent selections, we then hash the initial random number using the keccak256 function, producing a new random number.

  4. The second winner is then chosen using this new random number, and they are awarded the tier 1 prize.

  5. In an event where a selected entry has already won, then the seed is rehashed instead to find the next winning entry.

  6. 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) if claimPrizes was used.
  • PrizeClaimed(raffleId, winnerIndex) if claimPrize 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

FunctionInputOutputDescription
rafflesCountN/Auint256The number of raffles created.
rafflesraffleIdRaffleThe raffle object, see Raffle Object Data Types.
rafflesParticipantsStatsraffleId, addressParticipantStatsThe participants stats of the raffles, see Other Data Types.
isCurrencyAllowedaddressboolIt checks whether the currency is allowed.
randomnessRequestsrequestIdRandomnessRequestThe randomness requests, see Other Data Types.
protocolFeeRecipientN/AaddressThe protocol fee recipient.
protocolFeeBpN/Auint16The protocol fee in basis points.
getWinnersraffleIdWinner[]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.
getPrizesraffleIdPrize[]It returns the raffle’s prizes for a specific raffleId.
getEntriesraffleIdEntry[]It returns the raffle’s entries for a specific raffleId.
getPricingOptionsraffleIdPricingOption[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:

  1. The first pricing option's entriesCount (pricingOption[0].entriesCount), must divide the minimumEntries number evenly and be greater than zero.
  2. The entriesCount of each pricingOption must be evenly divisible by pricingOption[0].entriesCount.
  3. The price of all pricing options must be divisible by the number of entries it provides.
  4. The entries given must increase as pricing options are added; they can't be less or equal to the previous pricingOption entries.
  5. The price of the pricingOption must be greater than the previous pricingOption.
  6. 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;
}