avatarAlex Roan

Summary

This article provides a tutorial on how to build a verifiably random lottery smart contract on Ethereum using a Verifiable Random Function (VRF).

Abstract

The article discusses the challenge of creating true randomness on Ethereum due to the need for transactions to be verified by multiple nodes, which can lead to different results. It then introduces a solution to this problem using a VRF, which allows Ethereum smart contracts to generate random numbers. The article provides a step-by-step guide on building a lottery smart contract using this solution, with three phases: open, closed, and finished. The contract uses an enum to define the phases, and a modifier to ensure the current state of the contract is what is expected. The article also covers submitting numbers, drawing the number, and the full code for the contract.

Bullet points

  • True randomness has been near impossible on Ethereum due to the need for transactions to be verified by multiple nodes.
  • A recent announcement by a major player in the Ethereum ecosystem has introduced a solution to this problem using a Verifiable Random Function (VRF).
  • This solution allows Ethereum smart contracts to generate random numbers, enabling concepts such as a lottery to be brought to life.
  • The article provides a tutorial on building a lottery smart contract with three phases: open, closed, and finished.
  • The contract uses an enum to define the phases and a modifier to ensure the current state of the contract is what is expected.
  • The article covers submitting numbers, drawing the number, and the full code for the contract.

Build a Verifiably Random Lottery Smart Contract on Ethereum

How to create an Ethereum lottery

Photo by dylan nolte on Unsplash

Editor’s note: This article is provided for entertainment and educational purposes only and does not constitute or contain financial advice. Any actions you take from the content of this article are solely your own.

True randomness has been near impossible on Ethereum. This is because transactions need to be verified by multiple nodes on the network to be confirmed. If a smart contract function were truly random, each node that verified a transaction using that function would come to a different result, meaning the transaction would never be confirmed.

A recent announcement by one of the biggest players in the Ethereum ecosystem has caused excitement around this very problem. Using a system called a Verifiable Random Function (VRF), Ethereum smart contracts can now generate random numbers.

This means concepts that seemed a perfect fit with smart contracts but couldn’t be brought to life because they required random numbers now can be.

One such concept is that of a lottery.

Building a Lottery Smart Contract

Our lottery is going to have three phases. The first is open, where new numbers can be submitted by anyone for a small fee. The second is closed, where no new numbers can be submitted and the random number is being generated. The third is finished, where the number has been generated and the winner has been paid.

If no one wins, the lottery contract can be rolled over, increasing the jackpot.

Defining the phases

Phases should restrict actions so only permitted operations can be performed. For example, the only phase that should allow new submissions is the open phase. If the lottery is closed or finished, the contract should forbid new submissions.

Using enum, we can define as many phases as we want. Let’s call it LotteryState. In our state variables, we define the following:

enum LotteryState { Open, Closed, Finished }
LotteryState public state;

Now that the enumeration is defined, we can set rules (require statements) in the functions, ensuring the current state of the contract is what we expect it to be.

Given that these require statements are likely to look similar throughout the contract, let’s minimise it. We can define a modifier that performs the require statement, and we can assign it to any function we wish.

modifier isState(LotteryState _state) {
    require(state == _state, "Wrong state for this action");
    _;
}

Now when we define functions, we can add this modifier to ensure the current state of the lottery is what we expect it to be.

Submitting numbers

Anyone should be allowed to submit a number so long as the minimum entry fee is paid. However, each entrant can’t submit the same number more than once. The only state that should allow new submissions is the open state.

Here’s our submitNumber function:

Line 1 defines the name, the single _number parameter, and the fact it’s public and payable. It also adds the isState modifier to ensure the lottery is open.

Line 2 ensures the correct entry fee has been paid, and line 3 ensures the sender of the message hasn’t already submitted that number and adds it to the entries in the process.

The variable entries refers to a mapping defining the guessed number and a set of addresses that have entered that number. It’s defined like this:

mapping(uint => EnumerableSet.AddressSet) entries;

AddressSet refers to the OpenZeppelin EnumerableSet contract, which provides added functionality for primitive types.

Once the checks are complete, the following four lines add the number to the guesses, pay out a small percentage of the owner cut, and emit a NewEntry event.

Drawing the number

If you’ve read this article on how to use VRF, then you’ll know generating a random number isn’t as simple as calling a single function (like Math.random() in JavaScript).

To generate a random number, you must request randomness from the VRF coordinator and implement a function that VRF can call back to with the response. For this, we need to define a VRF consumer (the details of creating a VRF consumer can be found here), which we call RandomNumberGenerator in Figure 2.

Our lottery will take the address of this contract as an injected parameter upon construction. When drawing the number, it’ll call the request function. This requests randomness from VRF, which, in turn, supplies the response to filfullRandomness on line 18. You can see in Figure 2 calls that this calls back to our Lottery contract with numberDrawn. Let’s define those functions:

drawNumber can only be called by the owner of the lottery in our definition on line 1 and can only be called when the lottery is in the open state.

numberDrawn on line 7 is the function that fulfillRandomness calls back to once the random number has been received by VRF. It ensures the request-id is the ID that was returned from the request, emits an event, pays out the winner, and changes the state of the lottery to Finished.

The Full Code

This is a primitive implementation, but it shows how the advent of verifiable randomness on the blockchain reduces the amount of complexity in contracts like lotteries. Previous gambling contracts needed to use hashing mechanisms, time-based mechanisms, block-based mechanisms, etc — all of which are vulnerable.

Further Reading

If you’re interested in blockchain development, I write tutorials, walk-throughs, hints, and tips on how to get started and build a portfolio.

Programming
Blockchain
Ethereum
Solidity
Smart Contracts
Recommended from ReadMedium
avatarAl Leong, Blockchain Web3 CMO/CEO, Advisor
Top 25 Blockchain and Web3 Events for 2024–2025

1 — METAVSUMMIT

4 min read