paint-brush
Building a Raffle Smart Contract Using Oraclizeby@pabloruiz55
4,052 reads
4,052 reads

Building a Raffle Smart Contract Using Oraclize

by Pablo RuizOctober 23rd, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Generating random numbers in <a href="https://hackernoon.com/tagged/solidity" target="_blank">Solidity</a> is not simple. For starters, Solidity doesn’t come with a native random function because the deterministic nature of the system.

Coin Mentioned

Mention Thumbnail
featured image - Building a Raffle Smart Contract Using Oraclize
Pablo Ruiz HackerNoon profile picture

Generating random numbers in Solidity is not simple. For starters, Solidity doesn’t come with a native random function because the deterministic nature of the system.

In mathematics and physics, a deterministic system is a system in which no randomness is involved in the development of future states of the system. A deterministic model will thus always produce the same output from a given starting condition or initial state. — https://en.wikipedia.org/wiki/Deterministic_system

There are a number of services and APIs that allow us to generate random numbers, but the problem is that they are not decentralized and you have to trust someone else that the generated number is truly random.

Additionally, you can’t trust seemingly random values available to the contract, such as blockhash, timestamp, or other miner-defined value. Miners can manipulate these values to such extent, and event choose not to publish a block. For a casino app you can’t just trust miners, but there are a lot of other use cases where you could rely on blockhash for generating a random outcome, as long as the total amount of value resting on the quality of that randomness is lower than what a miner earns by mining a single block. More info on this: https://ethereum.stackexchange.com/questions/419/when-can-blockhash-be-safely-used-for-a-random-number-when-would-it-be-unsafe

So, how can we generate a random number securely inside a Solidity smart contract?

There are a few solutions out there that can help us achieve this, such as Randao (Randao: https://github.com/randao/randao.) and Oraclize (http://www.oraclize.it/#services). In this article I’m going to focus on Oraclize, mainly because it’s extremely easy to get started with it and secondly, because it also can be used to do many more things other than generating a random number, such as accessing any other API from inside a Solidity smart contract.

Building the Raffle contract



In this tutorial we’ll build a very simple raffle smart contract to show how to use Oraclize to generate a random number. The contract will work as follows:We’ll deploy the contract and set a minimum and maximum number of participants the raffle will accept. Once there are enough participants, the raffle organizer (or anyone for that matter) can execute the function that will have Oraclize generate a random number. With that random number we’ll just select the winner out of the participants’ array.*For this first version we won’t include all the payments logic, so anyone is free to participate but they won’t receive any ETH prize, of course.

Project Setup

For this tutorial, we’ll just deploy everything on Remix, using Metamask to connect to Ropsten, as Oraclize won’t work locally or on the JavaScript VM. I’m going to assume you have some basic understanding on how to work with Remix and Metamask. Also, make sure you have at least 3 or 4 accounts with some ether balance as we’ll need it to pay for the gas cost of joining the raffle as well as paying Oraclize to generate the random number (more on this later).

Coding the contract

In Remix, create a new file called Raffle.sol, this will be our contract containing all the logic for the raffle.

pragma solidity ^0.4.4;

contract Raffle {






uint private chosenNumber;address private winnerParticipant;uint8 maxParticipants;uint8 minParticipants;uint8 joinedParticipants;address organizer;

bool raffleFinished = false;


address[] participants;mapping (address => bool) participantsMapping;


event ChooseWinner(uint _chosenNumber,address winner);event RandomNumberGenerated(uint);




function Raffle(){address _org = msg.sender;uint8 _min = 2;uint8 _max = 10;

require(\_min < \_max && \_min >=2 && \_max <=50);

organizer = \_org;  
chosenNumber = 999;  
maxParticipants = \_max;  
minParticipants = \_min;  

}

function() payable {}





function joinraffle(){require(!raffleFinished);require(msg.sender != organizer);require(joinedParticipants + 1 < maxParticipants);require(!participantsMapping[msg.sender]);

participants.push(msg.sender);  
participantsMapping\[msg.sender\] = true;

joinedParticipants ++;  

}





function chooseWinner(uint _chosenNum) internal{chosenNumber = _chosenNum;winnerParticipant = participants[chosenNumber];ChooseWinner(chosenNumber,participants[chosenNumber]);}



function generateRandomNum(){require(!raffleFinished);require(joinedParticipants >=minParticipants && joinedParticipants<=maxParticipants);

raffleFinished=true;  
  
chooseWinner(0); //We'll replace this with a call to Oraclize service later on.

}



function getChosenNumber() constant returns (uint) {return chosenNumber;}



function getWinnerAddress() constant returns (address) {return winnerParticipant;}



function getParticipants() constant returns (address[]) {return participants;}

}

I’ll go over the most important parts of the code:

  • function Raffle(): The constructor of the contract sets the configuration state variables of the contract, such as the required minimum and maximum participants and who the organizer of the raffle is (for now, the creator of the contract). Notice we are hardcoding these variables, just to make the example simpler, but you could have the constructor receive these parameters. Also, we require there are at least 2 participants and no more than 50. This maximum could also be changed if you wanted.
  • fallback function: I added the fallback function as we’ll need to send Ether to the contract so it can pay for Oraclize’s services. Did you think they would provide their services for free? :) — More on this later.
  • function joinRaffle(): This function allows the caller to join the raffle. First, we make a few checks to make sure we are not adding a new participant to a raffle that has already finished or that has reached its maximum amount of participants. We also check that the organizer is not the one trying to join and that this person hasn’t already joined. Then we add the new participant to the participants array (so we can later select one of them at random) and we also add it to a participants mapping (which allows us to easy check if that participant had already joined). Having 2 variables that hold the same information feels a bit hacky to me, but given the example is rather simple, I felt we could get away with it.
  • function generateRandomNumber(): This function will be called to have Oraclize generate the random number. Notice we made it public and doesn’t require the organizer to be the one that calls it. This is on purpose, so that if the organizer, for some reason, disappears, the participants can select a winner anyways. This could be changed as well, for example, to allow this mechanism but only after some time has passed. For now, all this function does is set the raffleFinished flag to true (so this function can only be called once) and call the chooseWinner with a 0 as parameter. When we implement Oraclize, instead of sending a hardcoded 0 (which automatically makes the first person who joined the raffle the winner) we’ll generate the random number.
  • function chooseWinner(): This is an internal function that receives an uint which indicates the position of the winner in the participants array. With this number, we also set the address of the winner and fire an event that logs who the winner was so we can later retrieve it and display it.
  • The remaining functions are just used later in the front-end to display the winners.

Go ahead and try the contract in Remix. Just copy and paste the code above and deploy the contract. You should be able to join the raffle with different accounts and then, after there are enough participant, you can call generateRandomNumber() to select a winner (It will always be the address at position 0 in the participants array).

Now that we have made sure the logic of the raffle is ok, it’s time to use Oraclize to generate a random number for us.

Using Oraclize to generate a random number

Integrating Oraclize in our smart contract is pretty easy and straight-forward. The fist step is to import the Oraclize API in our contract. Add the following line before the Raffle contract declaration.

import "github.com/oraclize/ethereum-api/oraclizeAPI.sol";



contract Raffle is usingOraclize{...}

Notice that we also changed Raffle to inherit from usingOraclize.

Now that we imported Oraclize, we have access to its functions. One thing you should be aware of is that importing Oraclize this way will fail, for example, if you are using Truffle to compile your contracts. If you are using Truffle, you will have to make a local copy of the Oraclize file and rename it to “usingOraclize.sol” as Truffle needs the file have the same name as the contract.

The first thing we will do is add the Oraclize callback function. The way Oraclize works is by first making a query — we could be querying an external API, Wolfram’s Mathematica, or, like in our case, the built-in random number generator — which will fire a transaction to an Oraclize contract, and after that gets processed, it fires the callback function we define inside our contract with the corresponding result.

This is how the callback function will look like:



// the callback function is called by Oraclize when the result is ready// the oraclize_randomDS_proofVerify modifier prevents an invalid proof to execute this function code:// the proof validity is fully verified on-chain






function __callback(bytes32 _queryId, string _result, bytes _proof){// If we already generated a random number, we can't generate a new one.require(!raffleFinished);// if we reach this point successfully, it means that the attached authenticity proof has passed!require (msg.sender == oraclize_cbAddress());





if (oraclize_randomDS_proofVerify__returnCode(_queryId, _result, _proof) != 0) {// the proof verification has failed, do we need to take any action here? (depends on the use case)} else {// the proof verification has passedraffleFinished=true;

// for simplicity of use, let's also convert the random bytes to uint if we need  
uint maxRange = joinedParticipants; // this is the highest uint we want to get. It should never be greater than 2^(8\*N), where N is the number of random bytes we had asked the datasource to return  
uint randomNumber = uint(sha3(\_result)) % maxRange; // this is an efficient way to get the uint out in the \[0, maxRange\] range  
  
chooseWinner(randomNumber);

RandomNumberGenerated(randomNumber); // this is the resulting random number (uint)  


}}

First, we will make sure that raffleFinished is false so we don’t allow this function to be called more than once.

Then we are checking that the data we got has not been tampered with while being delivered to the smart contract. You can read more about that here: https://blog.oraclize.it/the-random-datasource-chapter-2-779946e54f49


If the verification has passed, meaning the data has not been tampered with, then we proceed to use the random number. We will be calling the chooseWinner() function that we previously used, but instead of passing a hardcoded 0 as parameter, we’ll pass the random number we generated.As you can see, the random number that gets generated uses the joinedPaticipants state variable to determine its max range. We are also firing an event to log the random number we generated.

The last thing we have to do is modify our generateRandomNum() function so when someone executes it, it makes the query to Oraclize.



function generateRandomNum(){require(!raffleFinished);require(joinedParticipants >=minParticipants && joinedParticipants<=maxParticipants);

oraclize\_setProof(proofType\_Ledger); // sets the Ledger authenticity proof  
uint N = 4; // number of random bytes we want the datasource to return  
uint delay = 0; // number of seconds to wait before the execution takes place  
uint callbackGas = 200000; // amount of gas we want Oraclize to set for the callback function  
bytes32 queryId = oraclize\_newRandomDSQuery(delay, N, callbackGas); // this function internally generates the correct oraclize\_query and returns its queryId

}

By calling oraclize_newRandomDSQuery(delay, N, callbackGas) we’ll have Oraclize generate a random number for us, and when it’s ready, the callback function we defined earlier will be fired. oraclize_newRandomDSQuery receives 3 parameters.

  • delay: which is the number of seconds to wait before the execution takes place. We set it to 0 so it executes the callback as soon as we have results.
  • N: which is how many bytes we want the datasource to return. In our case, since we made the raffle to accept up to 50 participants if the organizer wants so, 4 bytes is more than enough.
  • callbackGas: How much gas we will forward Oraclize so it can execute the callback function. We are setting it up to 200.000 which is enough to execute this function. (In my tests, it has consumed around 125.000 gas).

And that’s it! You can give it a try in Remix and see how it works. A few considerations and words of advice:

  • Oraclize’s pricing model: As I mentioned before, Oraclize isn’t free. You can check their pricing here: http://docs.oraclize.it/#pricing. Notice that the first query your contract makes is free, then you will have to pay for the transaction. How? The contract executing the query pays for it from it’s balance. So, before you call the generateRandomNum() function, make sure you send some ether to the contract. — That’s why we included the fallback function.
  • Testing in Remix: Testing your contract can be quite tedious. First, since we are using Oraclize, you can’t test this locally, we need to connect to a testnet (I’m using Ropsten). You should remember that when you call generateRandomNum() it will take 30–50 seconds to get mined and then, it will take another minute to actually process the callback function. Also, switching accounts in Metamask + Remix takes some work. Arm yourself with patience.
  • How much gas is needed to execute this? I’m still playing around with the necessary gas required to execute this contract. On Remix, I had to up the limit to 5.000.000 when deploying the contract. Make sure you are also sending enough gas when generating the random number, or else the callback function will fail.

The contract we built today allows us to run a simple Raffle that people can join and then a winner is selected by using Oraclize to generate a random number we can be certain hasn’t been tampered with. There’s a lot of room for improvements and new features, such as allowing people to enter the Raffle with ether and win a prize.

Let me know in the comments section below if you have any suggestions or if you encounter any problems with the code.