Build a Verifiably Random Lottery Smart Contract on Ethereum
By: Alex Roan
Tags: Solidity, Smart Contracts, Ethereum, Programming, Blockchain

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:

Figure 1: Submitting numbers

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.

Figure 2: ‘RandomNumberGenerator’ extending ‘VRFConsumer’

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:

Figure 3: Drawing numbers

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.