Feeling Lucky Smart Contract

Feeling Lucky Smart Contract

The Feeling Lucky system utilizes smart contracts to operate a transparent, fair, and decentralized game directly on the Base blockchain, en suring the establishment of trust and maintenance of integrity. This system is structured around a sequence of 'rounds', with each round representing a distinct game iteration.

Participants have the opportunity to enter these rounds by placing deposits (ETH, ERC-721 or ERC-20 tokens). The probability of a participant winning is directly proportional to the portion of the total pool they have deposited. Each deposit value is calculated, and each participant is allocated a number of entry spots proportional to the value deposited. Winners are selected at the end of each round.

The Feeling Lucky contract depends on several external oracles: Chainlink for ensuring fair randomness, Reservoir for ERC-721 pricing information, and Uniswap for ERC-20 pricing data.

Please note: The valuation of all NFTs is the floor price of the collection; rarity is not taken into consideration.

Functions

The contract encompasses a range of features for engaging with the Feeling Lucky system. These functionalities encompass actions like depositing into the current round (deposit), drawing the winners (drawWinner), claiming the prizes (claimPrizes), cancelling a round if less than two addresses deposited (cancel), withdrawing the deposit if cancelled (withdrawDeposits) and cancelling a round (if less than two addresses entered).

Events

Events are generated to capture different actions within the system, including actions like making deposits, withdrawing deposits, updating round statuses, claiming prizes, and sending randomness requests, among others.

Deployed contracts addresses

Base Mainnet

Contract Name
Contract Address

FeelingLucky

0xFbCe94A2686B8315208b39045E77B3a1Cf56D1De

TransferManager

0xF6A78b7e55dE9c5503848FbCB483B84a8824aDd5

PriceOracle

0x1d67FA31AbfB60b368004b4B038AE7845D1BcCC4

Table of content

This page includes the following sections.

Feeling Lucky System Overview

The contract is initialised with the rounds configurations: These values define things like the duration of each round (roundDuration), the limits on participants per round and deposits (maximumNumberOfDepositsPerRound/maximumNumberOfParticipantsPerRound) and the various oracles addresses and details.

The contract owner can adjust these values.

  1. The round is started: The status is set to Open. At this point, people can deposit assets and currencies and receive entries equivalent to the total value of the assets deposited. The Reservoir and Uniswap oracles are used to calculate the assets' value. The price is fetched only with the first deposit, and it will stay the same for the whole round.

  2. The round is successfully completed: The status is set to Drawing. The randomness request is sent to Chainlink; this can take a few minutes to complete based on the network status.

    1. The randomness request is completed: The status is set to Drawn. The value returned by Chainlink is used to select the winner of the last round.

  3. The round does not reach two participants: The status is set to Cancelled. The deposits can be withdrawn by calling withdrawDepositsor rolled over to the next round when cancelling a round the current by calling cancelCurrentRoundAndDepositToTheNextRound.

After a round is completed or cancelled, a new one is started.

Feeling Lucky configuration

When the contract is first deployed, there are a set of parameters that are required within the constructor and govern the logic of each round. Most of these values can be updated by the contract owner.

Here is the ConstructorCalldata struct:

Copy

struct ConstructorCalldata {
    // The following values can be updated after the contract has been deployed
    address owner;
    address operator;
    uint40 maximumNumberOfDepositsPerRound;
    uint40 maximumNumberOfParticipantsPerRound;
    uint40 roundDuration;
    uint256 valuePerEntry;
    address protocolFeeRecipient;
    uint16 protocolFeeBp;
    address reservoirOracle;
    address erc20Oracle;
    uint40 signatureValidityPeriod;

    // The following values cannot be updated after the contract has been deployed
    bytes32 keyHash;
    uint64 subscriptionId;
    address vrfCoordinator;
    address weth;
    address transferManager;
}

Definitions

  • owner: The contract’s owner can update all the values outlined above except the currencies allowlist.

  • operator: The contract’s operator can update the allowed currencies.

  • maximumNumberOfDepositsPerRound: The maximum number of deposits per round.

  • maximumNumberOfParticipantsPerRound: The maximum number of participants per round.

  • roundDuration: The duration of each round in seconds. It must be at most 1 hour.

  • valuePerEntry: The value in ETH of one entry.

  • protocolFeeRecipient: The address which will receive the protocol fee.

  • protocolFeeBp: The protocol fee in bps, taken from the total value of a round. At most 2500 (25%).

  • reservoirOracle: The address of Reservoir’s price oracle. erc20Oracle: The address of Uniswap’s price oracle.

  • signatureValidityPeriod: The time in seconds that a given Reservoir signature will be valid for.

  • keyHash/subscriptionId/vrfCoordinator: Chainlinks configuration variables, see Random Numbers: Using Chainlink VRF for more details.

  • weth: The WETH address. Used for the protocol fee transfer function in case the ETH transfer fails.

  • transferManager: The Transfer Manager contract’s address. Used to transfer the assets. For more details, see the transfer manager contract interface. Approvals must be granted to this contract by the participants.

As soon as the contract is deployed, a round will start.

Events emitted

  • ERC20OracleUpdated(address erc20Oracle)

  • MaximumNumberOfDepositsPerRoundUpdated(uint40 maximumNumberOfDepositsPerRound)

  • MaximumNumberOfParticipantsPerRoundUpdated(uint40 maximumNumberOfParticipantsPerRound)

  • ProtocolFeeBpUpdated(uint16 protocolFeeBp)

  • ProtocolFeeRecipientUpdated(address protocolFeeRecipient)

  • ReservoirOracleUpdated(address reservoirOracle)

  • RoundDurationUpdated(uint40 roundDuration)

  • SignatureValidityPeriodUpdated(uint40 signatureValidityPeriod)

  • ValuePerEntryUpdated(uint256 valuePerEntry)

  • RoundStatusUpdated(roundId, RoundStatus.Open)

Deposit assets

Once a round has started, users can deposit one or more assets/currencies (ETH/ERC-20/ERC-721) by calling the function deposit(uint256 roundId, DepositCalldata[] calldata deposits). This function accepts a roundId and an array of DepositCalldata. Not needed for ETH deposits.

The DepositCalldata struct includes the following properties:

  • tokenType: Either 1 for ERC-20 or 2 for ERC-721 (ETH does not need a DepositCalldata to be provided, the value sent alongside the transaction will be automatically deposited as ETH).

  • tokenAddress: The asset’s address.

  • tokenIdsOrAmounts: The asset’s ids for ERC-721 or the amount for ERC-20.

    • If the type is ERC-20 the tokenIdsOrAmounts length must be 1.

  • reservoirOracleFloorPrice: An object of type ReservoirOracleFloorPrice that can be retrieved via the Reservoir API (see the section below for more details) with the following properties:

    • id: The response’s id.

    • payload: The response’s payload.

    • timestamp: The response’s timestamp.

    • signature: The response’s signature.

How to retrieve the floor price data via the Reservoir API

The signed collection’s floor price data can be retrieved via the Reservoir API.

  • Use the Collection floor endpoint.

  • Set the kind to TWAP.

  • Set the twapSeconds to 86400.

  • Set the useNonFlaggedFloorAsk to false.

DepositCalldata struct:

struct ReservoirOracleFloorPrice {
    bytes32 id;
    bytes payload;
    uint256 timestamp;
    bytes signature;
}

ReservoirOracleFloorPrice struct:

struct DepositCalldata {
    TokenType tokenType;
    address tokenAddress;
    uint256[] tokenIdsOrAmounts;
    ReservoirOracleFloorPrice reservoirOracleFloorPrice;
}

Please note: All NFTs in a collection will be valued at the floor price of the collection; rarity is not accounted for.

Events emitted

  • Deposited(msg.sender, roundId, totalEntriesCount)

  • RandomnessRequested(roundId, requested) if the deposit reached the limit per round

  • RoundStatusUpdated(roundId, RoundStatus.Drawing) if the deposit reached the limit per round

Draw winner

Anyone can trigger for the winner to be drawn if the round’s cutoffTime has been reached but not the maximum participants/deposits threshold, as long as there are at least 2 participants. This can be achieved by calling the function drawWinner().

Events emitted

  • RandomnessRequested(roundId, requested)

  • RoundStatusUpdated(roundId, RoundStatus.Drawing)

Claim prizes

Once a round has been completed successfully, the winner can claim the prizes by calling the function claimPrizes(ClaimPrizesCalldata[] calldata claimPrizesCalldata); it can be used to claim multiple rounds at once. The function accepts an array of ClaimPrizesCalldata.

The ClaimPrizesCalldata struct includes the following properties:

  • roundId: The id of the round won.

  • prizeIndices: An array of indices of prizes to claim.

ClaimPrizesCalldata struct:

struct ClaimPrizesCalldata {
    uint256 roundId;
    uint256[] prizeIndices;
}

Events emitted

PrizesClaimed(perRoundClaimPrizesCalldata.roundId, msg.sender, prizeIndices)

Cancel a round

Anyone can cancel a round if one of the following two conditions are met:

  • The round’s cutoffTime has been reached, and there are less than 2 participants.

  • The randomness request to Chainlink has not been fulfilled within 12 minutes.

There are 3 different cancel functions:

  • cancel()

  • cancelCurrentRoundAndDepositToTheNextRound(DepositCalldata[] calldata deposits)

  • cancelAfterRandomnessRequest()

The cancelCurrentRoundAndDepositToTheNextRound function requires a DepositCalldata array.

For more details on DepositCalldata, see the deposit assets section.

Events emitted

  • RoundStatusUpdated(roundId, RoundStatus.Cancelled) current round cancelled

  • RoundStatusUpdated(roundId, RoundStatus.Open) new round started

  • Deposited(msg.sender, roundId, totalEntriesCount) if the deposit has been rolled over.

Withdraw deposits

After a round has been cancelled, the participants can withdraw their deposits by calling withdrawDeposits(uint256 roundId, uint256[] calldata depositIndices).

The function accepts a roundId and an array of deposit indices.

Events emitted

  • DepositsWithdrawn(roundId, msg.sender, depositIndices)

Variables update

The contract has a group of functions which are used to update some of the variables initially set at deployment. See Feeling Lucky configuration for more details.

Update Functions

Function Name
Function Input
Access Control

updateRoundDuration

uint40 _roundDuration

owner

updateSignatureValidityPeriod

uint40 _signatureValidityPeriod

owner

updateValuePerEntry

uint256 _valuePerEntry

owner

updateProtocolFeeRecipient

address _protocolFeeRecipient

owner

updateProtocolFeeBp

uint16 _protocolFeeBp

owner

updateMaximumNumberOfDepositsPerRound

uint40 _maximumNumberOfDepositsPerRound

owner

updateMaximumNumberOfParticipantsPerRound

uint40 _maximumNumberOfParticipantsPerRound

owner

updateReservoirOracle

address _reservoirOracle

owner

updateERC20Oracle

address _erc20Oracle

owner

updateTokensStatus

address[] tokens, uint tokenType, bool isAllowed

operator

Last updated