In the previous post of this series about programming on the Ethereum blockchain, we started building a provably fair raffle. In this part we’ll finish the code.

To better understand the content in this post, we recommend completing the previous part.

Drawing

Let’s build a function that’ll be responsible for drawing a winner: the draw function. If we know that our aim is to draw a total of two winners, let’s define the function’s responsibilities.

  • the function must become available only after some time has elapsed, so that enough participants can get into the game
  • the function must draw two winners
  • the function must disable the raffle after two winners have been drawn
  • the function must send the collected ether to the address we defined in part 1 once the raffle is done

Let’s define a skeleton first:

function draw() external {

}

We called the function external because it’ll only be called from outside the contract. There will never be a need to call it from within the contract from another function, so it’s better to be explicit.

Time Limit

The first condition is for the function to become available only after the raffle has been active for a while. Let’s add the following line of code into the draw function.

require (now > 1522908000);

The unix epoch is what’s widely considered as the beginning of time in computer science. Or, to be more precise, it’s the moment from when computers started measuring time: January 1st, 1970.

now (or its equivalent block.timestamp) is the timestamp – the number of seconds from the Unix epoch when the current block was mined. Given that Ethereum blocks are mined on average every 15 seconds, the block time won’t always match a unix timestamp on the second – it’ll vary by 15 or so.

The number we’re comparing now to is the number of seconds that we want to have elapsed since 1.1.1970. before we make the function accessible. To get a timestamp from a normal human readable date you can use a converter such as this one.

An alternative is saving the time of contract creation into a variable and then comparing the difference between current time and creation time to a native unit in Solidity, like 2 weeks:

require (now - startTime > 2 weeks);

Both approaches are valid. We’ll keep the current one.

In our original code, we’re aiming to finish the raffle on April 5th.

Picking the Winner

The next code we’ll write is this:

uint256 winningIndex = randomGen();
address winner = players[winningIndex];
winners.push(winner);

Variables we declare inside a function like these inside the draw function, are called local as opposed to global, because they’re available only in that function. Other functions don’t need access to these variables. Global variables are available throughout the whole contract. If some variables have no use outside the function they’re declared in, they’re better kept local because this conserves gas.

The winningIndex variable is of the uint256 type. This is short for unsigned 256bit integer. Unsigned means no prefix sign (-) i.e. only positive numbers can be stored into this variable. 256bit is the size of the number we can store and integer is a whole number (1, 2, 4053, 2384759826738947289). In other words, we’ll save the ordinal number of the winner into the winningIndex variable, and we’ll get that number by calling the randomGen function which we’ll define later.

address winner is the Ethereum address of the winner who’s been picked through the method mentioned above. This address is extracted from the list of players by placing the ordinal number (the winner’s index) into square brackets after the name of the variable which holds the list of players, like so: players[winningIndex]. To clarify on a banal example: if we have a list string a[] with string elements foo, bar, baz, like so:

string a[];
a.push('foo');
a.push('bar');
a.push('baz');

… then a[2] is baz because counting in lists starts from 0. a[0] is foo, and so on. The same approach is used to get a specific address out of the list of players with players[winningIndex] only the winningIndex value will be replaced by an actual number when the program is run and our randomGen function returns a number. For example, if randomGen provides us with the number 3, the program will pick the fourth player like so: players[3].

Finally, we save the winner’s address into the list of winners with winners.push(winner).

But to make sure the winner can’t be drawn a second time, we need to remove that address from the list of players.

players[winningIndex] = players[players.length - 1];
players.length--;

Any element of any list can be deleted like this:

delete players[winningIndex];

But this is not an adequate solution as there will be a hole in the deleted place. If we take our list a as an example and delete one of its elements, e.g. a[1], we get something like this:

a[0] = "foo";
a[1] = null;
a[2] = "baz";

Although the value at a[1] is now a null value and thus invalid, in order to get a valid value from the list we’d have to re-draw until we got a value that isn’t null. This would be very inefficient because several runs would cost more gas. Depending on the number of required attempts, it’s possible our transaction would even be rejected if we provide too little gas.

So instead of doing that, we grab the last element in the list with players[players.length-1] (length is a property that every list has and it shows how many elements there are in it, including holes left in between elements) and copy it over the winning element to overwrite it.

players[winningIndex] = players[players.length - 1];

In our a list example, that comes down to:

a[1] = a[a.length - 1];

//

a[0] = "foo";
a[1] = "baz";
a[2] = "baz";

But now there are two identical elements in the list. We stopped the last winner from winning again, but we made a new address twice as likely to win!

To fix this, we shorten the list with:

players.length--;

Shortening a list immediately deletes the elements that no longer fit. If we apply this to our list a, it looks like this now:

a.length--;

//

a[0] = "foo";
a[1] = "baz";

The next time we call the draw function, our winner can no longer be drawn and everyone has the same chances.

Finishing the raffle

We mentioned we wanted two winners because we’re giving away two tickets. So we have to call the draw function two times. Seeing as we’re safe from drawing the same person twice now, we have to make sure the raffle deactivates after 2 draws and sends collected ether onto the charity address.

Let’s finish the draw function with the following code:

if (winners.length == 2) {
    charity.transfer(address(this).balance);
}

Every Ethereum address (type address) has a transfer function. This function accepts a single argument – the amount of Wei to transfer to the address on which we are calling the function. The funds are being sent from the current address, i.e. the currently running smart contract – in our case, the raffle. So we need to call transfer on the charity address we previously defined.

The argument we’re passing in is address(this).balance. this applies to “this contract in which the logic is currently being executed”. Every Ethereum address in Solidity has a balance property along with the transfer function, and this property denotes how much ether (in Wei) is in that address. But to read the balance of the address of our contract, we first need to find out which address it’s deployed on, and this is done with address() which accepts this as an argument.

In other words, address(this).balance means “Amount of Wei in THIS contract’s address” and the whole bit of code we wrote means “if the number of winners is 2, transfer all the money from this smart contract onto the charity address”.

We’ve now transferred the money, but how do we stop the draw function from being called again?

We add a new require statement at the top of the function:

require (winners.length < 2);

This prevents the draw function from running again if two or more players have been drawn. For safety, we can add this same condition into the play function as well to stop people from sending money into the raffle when it's already done. Our project's full code now looks like this:

pragma solidity ^0.4.20;

contract Blocksplit {
    
    address[] public players;
    mapping (address => bool) public uniquePlayers;
    address[] public winners;
    
    address public charity = 0xc39eA9DB33F510407D2C77b06157c3Ae57247c2A;
    
    function() external payable {
        play(msg.sender);
    }
    
    function play(address _participant) payable public {
        require (winners.length < 2);        
        require (msg.value >= 1000000000000000 && msg.value <= 100000000000000000);
        require (uniquePlayers[_participant] == false);
        
        players.push(_participant);
        uniquePlayers[_participant] = true;
    }
    
    function draw() external {
        
        require (now > 1522908000);
        require (winners.length < 2);
        
        uint256 winningIndex = randomGen();
        address winner = players[winningIndex];
        winners.push(winner);
        
        players[winningIndex] = players[players.length - 1];
        players.length--;
        
        if (winners.length == 2) {
            charity.transfer(address(this).balance);
        }
    }
    
}

Random Number Generation

There's only one more thing we have to do: define the randomGen function for finding a random winner.

If you've read our Intro to Ethereum, you know we're dealing with a world computer which executes its code on multiple machines (nodes) at once. The system is deterministic which means that there must be no deviations from the results in between those different machines - otherwise we'd have thousands of different blockchains as they would keep diverging in data. It's important that every node produce the same data for the same input so that every calculation is identical. So how do we get a random number?

We have to find a way to get a random number which is unpredictable yet identical on each Ethereum machine. A good candidate for this is the blockhash - a collection of letters and numbers generated by combining and interpreting (hashing) the content of an Ethereum block. Seeing as no one knows which blockhash will come next (if they did, they could use the information to hog all the block rewards and control the blockchain), its value is always an adequate source of randomness which will be identical on every participant's computer. The process we use to pick the number is:

function randomGen() constant internal returns (uint256 randomNumber) {
        uint256 seed = uint256(block.blockhash(block.number - 200));
        return(uint256(keccak256(block.blockhash(block.number-1), seed )) % players.length);
    }

The randomGen function has been declared as constant because it doesn't change any values in the blockchain - it's a read only function. internal means it can only be called from within this contract, so for internal use only. returns (uint256) means it returns a value to whoever calls it (in our case the draw function's winningIndex variable) and uint256 is the type of the returned value. It has to match the type of the variable storing that value, which it does.

We first define a seed in the function's body. In computer science, whenever you need to get a random number it's generally calculated from a seed of randomness. The seed is an input value for a random number generation function and can be anything from number of drops of rain on the window or number of apples eaten last year, to a combination of all the middle names of family members on the father's side or even the blockhash of the 200th block from the top, like in our case.

uint256 seed = uint256(block.blockhash(block.number - 200));

This means "store the unit256 version of the blockhash of the 200th block from the top into the variable called seed which is also a type uint256". The uint256() part is important because the blockhash is in hexadecimal format by default, like so: 0xab49276f782.... To get a decimal (base 10 number) version, we call the uint256() function with the blockhash as the parameter. This is also known as casting - we cast a value as another type of value. A seed is generally sent into the function for generating random numbers from outside because the seed's predictability means outcome predictability. In this case, that's less important.

Note: for an example of external seed generation, see how generating a Bitcoin address works. The moving of the mouse cursor or entering values into the text field is actually you generating a seed.

Next, we return the value to the caller.

return(uint256(keccak256(block.blockhash(block.number-1), seed )) % players.length);

Despite looking complicated, this is actually quite simple. In order:

  • return() is called when the value needs to be returned to the caller. In this case, the caller is the winningIndex variable from the draw function. The parentheses contain the value we're returning, which can also be an expression which turns into a concrete value when everything is calculated.
  • uint256() turns any value passed into the parens into a whole decimal number.
  • keccak256 is a better version of the sha3 hashing algorithm. Find out more about hashing and these algorithms in our intro to blockchain. The function keccak256() accepts multiple arguments (parameters) separated by a comma, and then calculates a hash out of all of those put together. In this case it combines the blockhash before newest and the seed.
  • block.blockhash() is a function which, if provided with an ordinal number, returns the blockhash of that block. This function only stores the last 255 blockhashes, so if you ask for the 256th you'll get 0, same as 257, 258, etc. Worth keeping in mind, big projects have failed because they didn't account for this.
  • % players.length means modulo which is "smart math talk" for "leftover during division". In other words, "divide the number you get with the number of players, and return the leftover value". We know this will always be less than the number of players, so we're good to go.

Note: The process described above is NOT SAFE for generating random numbers on high stakes because miners can affect it to a certain degree. If the prize is more than 5 ether (the current block reward) it is financially more viable for a miner to cheat the system and forcibly win the prize. This approach is good enough only for trivial gambling. We'll cover high stakes games and better approaches in a future post.

There's one more problem. If our draw depends on the blockhash, then a draw two times in a row (if transactions end up in the same block) will produce the same random number, drawing two adjacent players. We prevent this with a delay trick. First, we define a global variable uint256 drawnBlock = 0;. Then, during the draw, we make sure the block differs from the previously saved drawnBlock: require (block.number != drawnBlock);. After this, the current block is saved into the variable, thereby preventing a draw in the same block but allowing a draw in the very next one.

The whole project's code now looks like this:

pragma solidity ^0.4.20;

contract Blocksplit {
    
    address[] public players;
    mapping (address => bool) public uniquePlayers;
    address[] public winners;
    
    address public charity = 0xc39eA9DB33F510407D2C77b06157c3Ae57247c2A;
    
    uint256 drawnBlock = 0;
    
    function() external payable {
        play(msg.sender);
    }
    
    function play(address _participant) payable public {
        require (winners.length < 2);        
        require (msg.value >= 1000000000000000 && msg.value <= 100000000000000000);
        require (uniquePlayers[_participant] == false);
        
        players.push(_participant);
        uniquePlayers[_participant] = true;
    }
    
    function draw() external {
        require (now > 1522908000);
        require (block.number != drawnBlock);
        require (winners.length < 2);
        
        drawnBlock = block.number;
        
        uint256 winningIndex = randomGen();
        address winner = players[winningIndex];
        winners.push(winner);
        
        players[winningIndex] = players[players.length - 1];
        players.length--;
        
        if (winners.length == 2) {
            charity.transfer(address(this).balance);
        }
    }
    
    function randomGen() constant internal returns (uint256 randomNumber) {
        uint256 seed = uint256(block.blockhash(block.number - 200));
        return(uint256(keccak256(block.blockhash(block.number-1), seed )) % players.length);
    }
    
}

Automatic draw

It's important to note that there's no way to execute a transaction automatically on the blockchain. Since draw requires a transaction, we need to manually execute the draw when ready.

Sending a transaction requires gas and that's a cost that varies, so it's not possible to pay in advance.

There are some solution attempts like the Ethereum Alarm Clock, but we'll discuss those at another time.

Conclusion

Building provably fair gambling on the blockchain is, due to its open nature and mathematical certainty almost trivial, but also very perilous if not careful. We demonstrated this on a simple raffle in this case, but similar principles apply to all gambling games.

You can download the source code for this project on Github.

In a future post we'll cover some alternative solutions to the problems presented in this post. Specifically, we'll talk about safer random number generation and storing private data on the blockchain (i.e. storing the coupon codes for the winners on the blockchain so only they can retrieve them, without knowing the winners in advance).

In a separate article we'll cover the launching of this contract on the live Ethereum mainnet after which you'll be able to launch your own raffles easily after tweaking some minor settings.


If you found this article useful or interesting, please consider donating to keep our operation running.

LEAVE A REPLY

Please enter your comment!
Please enter your name here