This is the final part of the Lottery Smart Contract series that I started 2 weeks ago. You are read part 1 here and part 2 here. In this final part I will be walking through the codes of the Lottery Smart Contract to explain what some of the more important lines of codes do.
The Codes
It helps if you have a wide screen monitor, or dual screens so that you can open my Lottery.sol codes in Github while you read this side-by-side. When I refer to line numbers here, I am referring to them in my Lottery.sol codes in Github.
Line 3 states that this contract should be compiled in solidity 0.4.19. Line 4 imports the Oraclize library.
pragma solidity ^0.4.19;
import "github.com/oraclize/ethereum-api/oraclizeAPI.sol";
Line 4 says that the Lottery contract uses the Oraclize library. Lines 8 to 11 declare the variables that I use for the contract. betNumber will store the number that the punter bets on and result will store the number that was randomly generated. Both variables are declared public because of transparency – both the punter and the house needs to see them.
Lines 10 and 11 stores the wallet addresses for the house and better, again, public for transparency. Line 12 declares the states where the contract will go through.
- Created: When the house first created the contract.
- Betted: When the better has decided on a number and placed his bet.
- Paidout: When the random number has been generated and the winner has been paid.
- Inactive: An inactive lottery contract is in its final dormant state.
Line 13 declares State as a public state variable that both house and punter can see. This is important because as a punter, you wouldn’t want to be betting on a contract that has already paid out its prizes.
contract Lottery is usingOraclize {
string public betNumber;
string public result;
address public house;
address public better;
enum State { Created, Betted, Paidout, Inactive }
State public state;
Events in an Ethereum Smart Contract are events that can be fired by parties participating in the Smart Contract. It can be triggered in the future, say, 100 blocks later. In this case, we have 4 events declared between lines 15 and 18.
The Oraclize event in line 15 is triggered when the release() function on line 72 is activated by the house. More on this later.
event newOraclizeQuery(string description);
event Aborted();
event Betted();
event Released();
The function Lottery() is a constructor that executes when the Lottery contract is deployed. It is payable because it requires ETH to execute. Generally, you can expect any functions that requires writing to the blockchain to be payable. Anything that reads values from a contract is free to execute.
On line 21, we pick up the address of the sender (the house) and put it into the variable house. Then we set the state of the Lottery Smart Contract to ‘Created’.
function Lottery() public payable {
house = msg.sender;
state = State.Created;
}
Some functions such as “release” should only be run by the house. Others like “bet” can only be run by the punter. Here, we set the modifiers to check that only the correct parties can run the function.
There are 2 such modifiers here. onlyBetter() will check if the party who’s running the function is the better. onlyHouse() will check if the party who’s running the function is the house.
modifier onlyBetter() {
require(msg.sender == better);
_;
}
modifier onlyHouse() {
require(msg.sender == house);
_;
}
I found a function that compares 2 strings on Stackoverflow and decided to use it to compare the winning number with what the punter bet on. I had decided to store these numbers as strings rather than integer because in the lottery system that I am familiar with, people bet on numbers that could have a preceding 0, e.g. 0405 and since this is a tutorial, I am too lazy to write codes to strip 0s for comparisons of integers. Do feel free to fork my codes to make this better!
//source: https://ethereum.stackexchange.com/questions/30912/how-to-compare-strings-in-solidity
function compareStrings (string a, string b) view returns (bool){
return keccak256(a) == keccak256(b);
}
The bet function checks a few things. Line 52 makes sure that it runs only if the Lottery contract is in the “Created” state. Line 53 checks that the punter bet only 1 ETH (that’s my logic, you can only bet with 1 ETH). But hey, shouldn’t you check that only the better runs this? You can’t because the punter hasn’t started to interact with the contract at this point – you don’t have his address, so you wouldn’t know if he’s the better. What you can do to improve this is to make sure that the house don’t get to run the bet function.
Line 57 saves the address of the better, line 58 saves the number he bet on and line 59 changes the state of the contract to “Betted”.
function bet(string _betNumber)
public
inState(State.Created)
condition(msg.value == (1 ether))
payable
{
Betted();
better = msg.sender;
betNumber = _betNumber;
state = State.Betted;
}
The abort() function can only be run by the house. An abort can only occur if the contract is in the “Created” state. During an abort, the state of the contract is changed to “Inactive” and the balance held by the contract is returned to the house.
function abort()
public
onlyHouse
inState(State.Created)
{
Aborted();
state = State.Inactive;
house.transfer(address(this).balance);
}
The release() function is where the action is. Only the house can run it, and it can only run if the state of the contract is “Betted”. Oraclize charges some ETH for helping the contract make the external web service call. So line 78 checks if the contract contains some ETH. It does, since both the house and punter would have added their ETH to the contract. Line 82 makes the web service call to my external random number generator.
The callback function on line 86 takes over when Oraclize comes back with the result after helping to make the external web service call.
Line 90 performs a comparison between the random number generated by the external web service call with the number that the punter bet on. If the number is the same, line 91 transfers all the ETH in the contract to the better. Or else, all the ETH is transferred to the house.
Finally, the state of the smart contract is changed to “Paidout”.
function release()
payable
public
onlyHouse
inState(State.Betted)
{
if (oraclize_getPrice("URL") > this.balance) {
newOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
} else {
newOraclizeQuery("Oraclize query was sent, standing by for the answer..");
oraclize_query("URL", "json(https://jacksonng.org/codetest/random.php/).random");
}
}
function __callback(bytes32 myid, string res) {
if (msg.sender != oraclize_cbAddress()) throw;
result = res;
if (compareStrings(result,betNumber)) {
better.transfer(address(this).balance);
}
else {
house.transfer(address(this).balance);
}
Released();
state = State.Paidout;
}
}
What’s Next
Writing the Lottery Smart Contract is a fun way to learn how about states and the Oraclize library. There are many ways this contract can be improved. Here are some:
- Allow the house to reuse the contract by running a function to switch the contract back to the “Created” state.
- Allow more than 1 punter to bet.
- Do a payout ratio – bet 1ETH to get 1 ETH. Bet 10 to get 10.
- Use the Ethereum alarm clock to do an auto release some time into the future so that the house need not trigger release to see results.
Have fun!
Photo by steve sawusch on Unsplash