In this tutorial we’ll start building a blockchain raffle on Ethereum.

The following content is recommended reading before you dive into this post:

Provably fair games

Provably fair games are games with an element of randomness but with mathematically provable odds. In other words, there must not be a way for the house to change the odds or the outcome between the time the bet was made and the time the bet was realized. You can read more about provably fair games in this post.

Raffle

Apart from a coin toss, the most straightforward example of gambling is probably a raffle. Let’s build one to give away two tickets to Blocksplit, a blockchain developer conference in Split, Croatia.

BlockSplit

Due to the simplicity of this project and the approachable nature of coding in the browser, we’ll be using the Remix tool – an in-browser Ethereum programming environment.

Note: with more serious projects, it’s recommended to run your own blockchain and isolated testing environment. Read this post and this post to find out how to do that.

Smart Contract

A raffle is an example simple enough to be understandable even without an in-depth knowledge of Solidity, and every line we mention will be explained in detail in both this post and its followup.

It’s our recommendation that you follow along with this tutorial by typing in the same code – that’s the best way to learn and remember things and will let you play with your own variations later on.

To open a new file in the Remix editor, click the top left New file icon.

Creating a new file in Remix
Creating a new file in Remix

The file name is arbitrary. Let’s call it Blocksplit.sol. After confirming a new empty file tab will open. The first line of every Solidity project is the pragma declaration.

pragma solidity ^0.4.20;

This means “I’m writing this code with Solidity version 0.4.20 or higher”. If you’re not sure which version is latest when writing your own code, find out here.

Next, we need to open the smart contract. The body of a smart contract is placed between curly braces ({ and }) and the name is preceded by the phrase contract. The name of the contract is commonly the same as the name of the file you’re writing the contract in, so our code now looks like this:

pragma solidity ^0.4.20;

contract Blocksplit {
  
}

Variables and getters

The body of every smart contract (i.e. the part that contains the application logic) must be between curly braces. This includes both variables (also known as properties) and possible functions we might be calling on later.

Let’s declare some variables within those curly braces.

Note: every line of Solidity code which does not end with a parenthesis, brace, or bracket must end with a semicolon (;)

First, we need a way to save all raffle participants. A list is ideal for something like that.

address[] players;

This means “a list of addresses named players”. address is a type of data in Solidity and can hold a literal address of the Ethereum blockchain, whether it’s an individual user’s address, or an address on which a smart contract lives. Brackets ([]) added after a data type look and act like a box. In other words, type[] means “a box into which I can put one or more values of type type“.

Note: some other types in Solidity are uint, string, bool, address, etc.

We also need a way to save the unique players – we don’t want a player to be able to participate in the game multiple times from the same address. Let’s declare a new variable for that.

mapping (address => bool) uniquePlayers;

Mapping is another data type in Solidity. You can imagine a mapping as a two-column table with one being keys and the other values, like so:

keyvalue
x1
y200
z234121

x is 1, y is 200, z is 234121. How can this possibly help us determine uniqueness of players?

mapping (address => bool) uniquePlayers means “The key of uniqueMapping is an address, and its value is a boolean”. A boolean is a special type of data which can only be true or false. Reading from a mapping is done like so: uniquePlayers[address], so name of the mapping, brackets, and the key the value of which we need. So we’ll be able to save addresses into this mapping with the value true, indicating they’ve already participated. The mapping will look like this:

keyvalue
0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9true
0x4da2e85d64bece663ccab06e89b970b6b077f22ftrue

How we’ll use this mapping will be revealed in the next section.

We also need a variable which will save the winners when it’s time to make the draw, and we need a variable into which we’ll save the address to which the funds are going to get sent when the raffle is finished. In this case, that’s going to be the Mining For Charity address because we’re giving all the profits away.

Now our code looks like this:

pragma solidity ^0.4.20;

contract Blocksplit {
    
    address[] players;
    mapping (address => bool) uniquePlayers;
    address[] winners;
    
    address charity = 0xc39eA9DB33F510407D2C77b06157c3Ae57247c2A;
    
}

Although there’s no logic in this contract yet, we can still test it.

JavaScript VM

Remix comes with Testrpc built in. It’s a JavaScript simulation of the blockchain in the browser. It’s not a real functioning blockchain, but just a simulation for testing. It’s recommended you have a real private blockchain for testing proper apps.

This JS simulation, however, lets us test out our contract’s basics.

Click the Run tab in the upper right corner, then select JavaScript VM in the dropdown menu, then click Create next to the contract name as in the animation below:

Deploying a smart contract in Remix
Deploying a smart contract in Remix

Our contract is now deployed on this test blockchain, but it’s useless – we can’t do anything with it. What if, for transparency’s sake, we want to make it possible for outsiders to read the address to which the funds are going to be sent after the raffle is done?

If we append the public visibility modifier to variables, a function which allows external parties to inspect that variable’s value will automatically be generated on deployment. Such a function is called a getter because it serves a single purpose: getting a value. Let’s add public to all our variables, just before their names.

pragma solidity ^0.4.20;

contract Blocksplit {
    
    address[] public players;
    mapping (address => bool) public uniquePlayers;
    address[] public winners;
    
    address public charity = 0xc39eA9DB33F510407D2C77b06157c3Ae57247c2A;
    
}

Now if we redeploy, we’ll have some blue buttons accessible under the contract’s deployed name. Clicking any of them will output the values saved inside them. Currently, only our charity variable has a value because we explicitly defined it in the code.

Testing getter functions in Remix
Testing getter functions in Remix

Note: in Remix, blue buttons mean reading from the blockchain, an action which is always free. Red buttons mean writing to the blockchain, an action which will always cost gas because it requires a transaction.

Fallback function

To let a smart contract accept payments, the function which accepts them needs to be declared as payable. But what if we want someone to be able to participate in the raffle by just sending ether to the address of the raffle, without complicating their life with custom functions?

That’s exactly what the fallback function is there for:

    function() external payable {
        play(msg.sender);
    }

A fallback function is a nameless function, so its declaration starts with function(). The word external means that it can only be called from outside the contract, not inside by other functions. The word payable is important here because it makes it possible for the function to process ether payments.

The body of the function calls another function called play and passes it a parameter: msg.sender. What this means will be revealed in the next section.

Note: functions are called by typing their name, then parenthesis, and then optionally one or more arguments (parameters) they might require to run properly – it depends on how the function was declared. Example: myfunction(argument1, argument2)

Entering the raffle

Our participants will be able to enter the raffle by either sending money directly to the raffle’s address, or by calling the function play and providing an address which they wish to enter. That means, yes, they can also enter other people into the game through a function argument. Let’s define it.

function play(address _participant) payable public {
  
}

Our function is called play and it needs one argument of type address we’ve arbitrarily called _participant. Putting _ at the front of argument names is just convention in order to differentiate them from the function’s own arguments. The function is payable which means it can accept transactions with ether attached, and it’s public which means it can be called from both inside the contract like the fallback function does, and outside it – from another contract or a wallet interface like MyEtherWallet.

Next up we need to do some sanity checks before we let the player enter the game. The first check is making sure the amount of ether is sufficient – between 0.001 ether and 0.1 ether.

require (msg.value >= 1000000000000000 && msg.value <= 100000000000000000);

In Solidity, the amount of ether is represented as its lowest possible unit: Wei. To get the ether amount from wei, we divide the number by 1018, or use a converter like this one. msg.value is the amount of ether that was sent with the transaction, and msg is the transaction itself - a global variable accessible throughout the whole contract. It will have the sender property, as seen in the fallback function above, and the value property, indicating how much ether was sent along with it.

require means "If this requirement is not met, exit the program and reject the transaction". In other words: "Unless the transaction contains an amount of ether bigger than or equal to 0.001 ether, or less than or equal to 0.1 ether, cancel this action". && means and, so both conditions need to be true.

Note: named units of ether and time are also available in Solidity. 1 ether is equivalent to 1000000000000000000, etc. The same goes for time: 1 minutes is 60, 2 hours is 7200, etc.

The second check makes sure the player isn't already in the game.

require (uniquePlayers[_participant] == false);

Every mapping is infinitely generated in advance. You can check every address' value in this mapping even if it has never been added to the contract because the default is assumed to be false. The == sign is used to check equality. = is used for assignment, so it's no good here - it would mean "make the left value equal to the right", which is not what we want.

After these checks, we do the following:

players.push(_participant);
uniquePlayers[_participant] = true;

Every list (remember - that's everything that has [] appended after the type) has a function push built in. push accepts a single argument and lets us inject an element into the list. So here, if the checks we previously defined have been passed, we can add the player into the list of players. We also set his uniquePlayers value to true so the check will fail if he tried to participate again (notice the single = for assignment here!)

Why didn't we check for existing participation with a loop through all added players, exiting if the player is found?

Loops are expensive. We could have written a for loop that loops through all the players added so far and exits the program if a duplicate is found, but as we explained in our gas guide, that's expensive and better done outside the smart contract or not at all. The mapping + list combination is perfectly sufficient in this case and at the same time helps wallets like MyEtherWallet estimate the gas cost for the transaction.

Let's test this. Our code should now look 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 (msg.value >= 1000000000000000 && msg.value <= 100000000000000000);
        require (uniquePlayers[_participant] == false);
        
        players.push(_participant);
        uniquePlayers[_participant] = true;
    }
    
}

Following the previously explained process of deploying a contract with JavaScript VM, we end up with a different set of buttons.

Testing deployed smart contract buttons in Remix
Testing deployed smart contract buttons in Remix

We have two red functions - fallback and play. Those are writing functions (they change the blockchain's state) and need a transaction, which means they cost money to execute. Let's use fallback to send money directly to the contract. First, in the menu above under Value we need to put a number between 0.001 and 0.1. Then, the value unit (menu to the right of Value) needs to be put to Ether. Finally, we click the fallback red button. If all went well, clicking the blue players button should display the address from which we just executed the transaction.

Listing players in Remix
Listing players in Remix

If we try to execute this same procedure again, the grey part of the screen will throw out a warning. The transaction will fail because this player is already in the game.

Remix reports a bug after running the procedure again
Remix reports a bug after running the procedure again

Add the other 4 addresses JavaScript VM generated for you into the game following the same procedure - just switch the addresses in the top menu and execute fallback from each one. Then, walk through the players list by putting in the ordinal numbers of added players from 0 to 4 (0 is first).

Inspecting all added players
Inspecting all added players

Once we reach number 5, the program will start to output errors because there's only 5 players in the game (0, 1, 2, 3, 4, 5).

Conclusion

In this part, we started building a raffle for the Blocksplit conference.

We went through the basics of smart contract structure, through variables, and some basic functions. We also learned how to accept payments in a smart contract and how to simulate transactions in the browser.

In the next part we'll complete the raffle by adding a draw function to draw the winners, by polishing the code, and by deploying the contract to the live Ethereum network.


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

2 COMMENTS

  1. Thanks! Just getting into Solidity development, and this was one of the use-cases I was interested in.

LEAVE A REPLY

Please enter your comment!
Please enter your name here