© Wei-Meng Lee 2019
W.-M. LeeBeginning Ethereum Smart Contracts Programminghttps://doi.org/10.1007/978-1-4842-5086-0_10

10. Project – Online Lottery

Wei-Meng Lee1 
(1)
Ang Mo Kio, Singapore
 

Now that you have seen how Smart Contract works and how to interact with them through the use of the web3.js APIs, it is now a good time to explore an application from end to end – from the Smart Contract to the front end, and perhaps give you some ideas for building your own decentralized applications.

How the Lottery Game Works

For this chapter, we will build a lottery game using a Smart Contract. For this contract, we will allow players to place a bet on a number. Figure 10-1 shows how the game works.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig1_HTML.jpg
Figure 10-1

How the online lottery game works

In the figure, there are a total of five players. Each player will place a bet on a number. For example, player 1 bets on the number 1 using 2 Ethers, and player 2 bets on the number 2 with 4 Ethers, and so on. The contract will randomly draw a number when the maximum number of players is reached. In this example, the contract will draw the winning number after the fifth players have betted.

Suppose the winning number is 2. Based on the example, players 2 and 4 have betted on the winning number. The amount won by each winner will be proportionate to how much they have betted. The calculation is shown in Figure 10-2.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig2_HTML.jpg
Figure 10-2

Calculating the winnings of each player

Our contract will automatically transfer the payout to the winners. If there is no winner for the game, all the Ethers will be transferred to the owner of the contract.

Defining the Smart Contract

For the next few sections, we shall walk through the creation of the contract so that you can see how it is built. For building this contract, we shall use the Remix IDE.

First, define the contract called Betting:
pragma solidity ^0.5;
contract Betting {
}
Let’s declare some variables:
contract Betting {
    address payable owner;
    uint minWager = 1;
    uint totalWager = 0;
    uint numberOfWagers = 0;
    uint constant MAX_NUMBER_OF_WAGERS = 2;
    uint winningNumber = 999;
    uint constant MAX_WINNING_NUMBER = 3;
    address payable [] playerAddresses;
    mapping (address => bool) playerAddressesMapping;
    struct Player {
        uint amountWagered;
        uint numberWagered;
    }
    mapping(address => Player) playerDetails;
}
Here is the use of each variable:
  • owner is used to store the address of the account that deployed the contract.

  • minWager stores the minimum amount of Ether that a player needs to wager.

  • totalWager stores the total amount of Ethers all the players have wagered so far in a game.

  • totalNumberOfWagers stores the total number of players who have played in the game so far.

  • MAX_NUMBER_OF_WAGERS lets you define the maximum number of players allowed in a game before the winning number is drawn. For ease of testing, we have set it to 2. In real life, this could be set to a much larger number.

  • winningNumber stores the winning number for the lottery. When it is set to 999, it indicates that the winning number has not been drawn yet.

  • MAX_WINNING_NUMBER defines the maximum winning number for the lottery game . The winning number is from 1 to MAX_WINNING_NUMBER.

  • playerAddresses is an array storing the account address of each player. You prefix the declaration with the payable keyword to indicate that each player is able to send/receive Ether.

  • Besides storing the player account address in the array, you also store them in a mapping object – playerAddressesMapping. This is mainly for performance reason. When you need to search for a particular player address, it is much more efficient to search through a mapping object rather than iterate through all the elements in an array. This is especially true if you have a large number of players in a game.

  • Player is a structure that contains two members – amountWagered and numberWagered

  • playerDetails is another mapping object that stores the details of each player’s waging details .

Constructor

For the lottery game , you need to set the minimum amount needed for each bet. To make the contract versatile, you can allow this value to be set when the contract is deployed. To do so, you can pass in the value through the contract’s constructor.

The constructor for the Betting contract takes in an integer value, indicating the minimum amount of Ether needed to bet on a number:
    // the constructor for the contract
    constructor(uint _minWager) public {
        owner = msg.sender;
        if (_minWager >0) minWager = _minWager;
    }

In the constructor, we also saved the address of the account that deployed the account .

Note

For simplicity, we are specifying the minimum wager amount to be in Ether.

Betting a Number

Next, let’s define the bet() function . The bet() function allows a player to bet on a number:
    function bet(uint number) public payable {
        // you check using the mapping for performance reasons
        require(playerAddressesMapping[msg.sender] == false);
        // check the range of numbers allowed
        require(number >=1 && number <= MAX_WINNING_NUMBER);
        // note that msg.value is in wei; need to convert to
        // ether
        require( (msg.value / (1 ether)) >= minWager);
        // record the number and amount wagered by the player
        playerDetails[msg.sender].amountWagered = msg.value;
        playerDetails[msg.sender].numberWagered = number;
        // add the player address to the array of addresses as
        // well as mapping
        playerAddresses.push(msg.sender);
        playerAddressesMapping[msg.sender] = true;
        numberOfWagers++;
        totalWager += msg.value;
        if (numberOfWagers >= MAX_NUMBER_OF_WAGERS) {
            announceWinners();
        }
    }

Note

Observe that the bet() function has the payable keyword. This means that when a player bets on a number, he must also send in Ethers.

In this function, you need to perform a number of checks. First, ensure that each player can only bet once by checking that the player address does not already exist in the playerAddressesMapping object:
        require(playerAddressesMapping[msg.sender] == false);
Next, you need to check that the number betted falls within the range allowed:
        require(number >=1 && number <= MAX_WINNING_NUMBER);
You also need to check that the amount betted is at least the minimum amount. Since msg.value contains the amount in Wei, you need to convert it to Ether:
        require( (msg.value / (1 ether)) >= minWager);
Once all these checks are cleared, you need to record the number and amount wagered by the player (msg.sender is the address of the player):
        playerDetails[msg.sender].amountWagered = msg.value;
        playerDetails[msg.sender].numberWagered = number;
You would also add the player address to the array of addresses as well as the mapping object:
        playerAddresses.push(msg.sender);
        playerAddressesMapping[msg.sender] = true;
You also need to increment the number of wagers as well as sum up all the amount wagered so far:
        numberOfWagers++;
        totalWager += msg.value;
Finally, you check if the required number of players have been met. If it has, you will announce the winner (which you will define in the next section):
        if (numberOfWagers >= MAX_NUMBER_OF_WAGERS) {
            announceWinners();
        }

Drawing the Winning Number and Announcing the Winners

The next function to define is announceWinners(). The announceWinners() function draws a random number and calculates the winning for each player and transfers the winnings to them:
    function announceWinners() private {
        winningNumber =
          uint(keccak256(abi.encodePacked(block.timestamp))) %
          MAX_WINNING_NUMBER + 1;
        address payable[MAX_NUMBER_OF_WAGERS] memory winners;
        uint winnerCount = 0;
        uint totalWinningWager = 0;
        // find out the winners
        for (uint i=0; i < playerAddresses.length; i++) {
            // get the address of each player
            address payable playerAddress =
                playerAddresses[i];
            // if the player betted number is the winning
            // number
            if (playerDetails[playerAddress].numberWagered ==
                winningNumber) {
                // save the player address into the winners
                // array
                winners[winnerCount] = playerAddress;
                // sum up the total wagered amount for the
                // winning numbers
                totalWinningWager +=
                  playerDetails[playerAddress].amountWagered;
                winnerCount++;
            }
        }
        // make payments to each winning player
        for (uint j=0; j<winnerCount; j++) {
            winners[j].transfer(
                (playerDetails[winners[j]].amountWagered /
                 totalWinningWager) * totalWager);
        }
    }
Because Solidity code runs on multiple nodes and must be deterministic, it is not possible to generate truly random numbers. We need a way to generate a random number that can be used by all other nodes running the same code. To solve this, one way is to use the block.timestamp constant, which it is assigned by miner:
        winningNumber =
          uint(keccak256(abi.encodePacked(block.timestamp))) %
          MAX_WINNING_NUMBER + 1;

The preceding statement will generate a random number between 1 and MAX_WINNING_NUMBER. Once it is generated and assigned to winningNumber, it will be the same for all other nodes which run the same contract.

Tip

An alternative to determining the winning number using block number is to access an external web service. In real life, you may want to connect to a real lottery feed.

You next create an array in memory to store all the winning players:
        address payable[MAX_NUMBER_OF_WAGERS] memory winners ;
You will iterate through all the players using the playerAddresses array and check if the number they wagered on is the winning number. The winning players are then added to the winners array:
        for (uint i=0; i < playerAddresses.length; i++) {
            address payable playerAddress =
                playerAddresses[i];
            if (playerDetails[playerAddress].numberWagered ==
                winningNumber) {
                winners[winnerCount] = playerAddress;
                totalWinningWager +=
                  playerDetails[playerAddress].amountWagered;
                winnerCount++;
            }
        }
Finally, calculate the winnings for each player and transfer the winnings to them using the transfer() function:
        // make payments to each winning player
        for (uint j=0; j<winnerCount; j++) {
            winners[j].transfer(
                (playerDetails[winners[j]].amountWagered /
                 totalWinningWager) * totalWager);
        }

Getting the Winning Number

To allow the outside world to know the winning number, add the following function named getWinningNumber():
    function getWinningNumber() view public returns (uint) {
        return winningNumber;
    }

Killing the Contract

When there are no winners for the game, the Smart Contract will hold the Ethers that were sent to it. In order to send the Ethers back to the account that deploys the contract, you need to kill (destruct) the contract. This is done via the kill() function :
    function kill() public {
        if (msg.sender == owner) {
            selfdestruct(owner);
        }
    }

Obviously, you need to ensure that only the owner (the one that deploys the contract) of the contract can kill it. When the contract is killed, all Ethers can automatically be transferred back to the owner.

Testing the Contract

With the contract created, it is now time to deploy it and test it using the Remix IDE. For deployment, we shall use Account 1 in MetaMask.

In the Remix IDE, type 1 next to the Deploy button and then click the Deploy button (see Figure 10-3). The 1 indicates that the minimum amount you need to bet for a number is 1 Ether.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig3_HTML.jpg
Figure 10-3

Setting the constructor for the contract

Click CONFIRM to deploy the contract (see Figure 10-4).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig4_HTML.jpg
Figure 10-4

Deploying the contract

Betting on a Number

Once the transaction is confirmed, enter 2 next to the bet button and enter 2 next to the Value label and select the unit as ether (see Figure 10-5). This means that you are now betting on the number 2 using 2 Ethers.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig5_HTML.jpg
Figure 10-5

Using Account 1 to bet on a number with the amount of Ether specified

Tip

Remember to set the Value and the unit to ether

Click the bet button to place the bet. MetaMask will display a pop-up asking you to confirm the transaction. Note the 2 Ethers you are sending to the contract (see Figure 10-6). Click CONFIRM.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig6_HTML.jpg
Figure 10-6

Observe the amount of Ethers you are sending

At this moment, you can record the current balance of Account 1 (see Figure 10-7). For my example, my Account 1 now has a balance of 2.0756 Ethers.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig7_HTML.jpg
Figure 10-7

Take note of the balance of Account 1

In MetaMask, switch to Account 2 now. Back in the Remix IDE, now bet on the number 1 using 3 Ethers (see Figure 10-8). Click the bet button and confirm the transaction.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig8_HTML.jpg
Figure 10-8

Using Account 2 to bet on another number with the amount of Ethers specified

Record the current balance of Account 2. For my example, balance for Account 2 stands at 1.0993 Ethers (see Figure 10-9).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig9_HTML.jpg
Figure 10-9

Take note of the balance of Account 2

After the transaction has been confirmed, the winning number would be drawn.

Note

Remember that our contract will draw a winning number after the second player has placed a bet.

Viewing the Winning Number

Click the getWinningNumber button to check the winning number (see Figure 10-10). In my example here, the winning number is 2, which means that Account 1 has won the lottery. Since there is only one winner, Account 1 will take all the Ethers betted, which is 5 (2+3) Ethers.
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig10_HTML.jpg
Figure 10-10

Checking on the winning number

When you now go back to Account 1 in MetaMask, you will see that the balance is now updated with the additional Ethers (see Figure 10-11).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig11_HTML.jpg
Figure 10-11

Account 1 balance is now updated with the winnings

Examining the Contract on Etherscan

If you examine the details of the Smart Contract in Etherscan, you will be able to see that the contract now has a balance of 0 Ether (see Figure 10-12).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig12_HTML.jpg
Figure 10-12

Examining the balance of the contract on Etherscan

If you click the Internal Txns tab, you will see that there is an internal transfer of 2 Ethers to Account 1 (see Figure 10-13).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig13_HTML.jpg
Figure 10-13

The contract recorded an internal transfer of Ethers to the winner of the game

Killing the Contract

In the previous section, you saw that in my example one player won the game and the contract transferred all the winnings to the player. If you try the previous section yourself, you may encounter one of the following scenarios:
  • One player wins the game, just like what was illustrated in the previous section.

  • Both players win the game (both betted on the same winning number). In this case the contract will transfer the winnings to the players based on the number of Ethers they have betted.

  • No player wins the game. In this case the Ethers betted by both players would be held by the contract.

In this section, we shall examine the last scenario where no one wins the game and how to get back the Ethers held by the contract.

To ensure that no one wins the game, let’s hard code the winning number in the contract:
    function announceWinners() private {
        /*
        winningNumber =
          uint(keccak256(abi.encodePacked(block.timestamp))) %
          MAX_WINNING_NUMBER + 1;
        */
        winningNumber = 1;

So now the winning number is always 1, and as long as no player bets on 1, there will be no winners.

As in the previous section, in the Remix IDE, deploy the contract using Account 1. After the contract is deployed, observe the balance of Account 1 (see Figure 10-14).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig14_HTML.jpg
Figure 10-14

Take note of the balance of Account 1

Using Account 2, bet the number 2 with 1 Ether. After that, using Account 3, bet the number 3 with 1 Ether. After the winning number has been drawn (which is always 1), check the details of the contract on Etherscan (see Figure 10-15).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig15_HTML.jpg
Figure 10-15

Examining the balance of the contract on Etherscan

Observe that the contract has a balance of 2 Ethers (since no player wins). So how do you get the 2 Ethers back? Turns out that if you kill the contract, the Ethers would be refunded back to the account that deploys it.

So let’s now switch back to Account 1 in the MetaMask. In the Remix IDE, click the kill button (see Figure 10-16).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig16_HTML.jpg
Figure 10-16

Killing the contract in the Remix IDE

Caution

Only the account that deployed the contract can kill it. Hence you need to switch to Account 1 (in MetaMask) in order to kill the contract.

Once the transaction is confirmed, you should see the 2 Ethers credited into Account 1 (see Figure 10-17).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig17_HTML.jpg
Figure 10-17

The Ethers would be credited back into Account 1 after the contract has been killed

In the Etherscan entry for the contract, you will an internal transfer from the contract to the original account that deployed the contract (see Figure 10-18).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig18_HTML.jpg
Figure 10-18

Verifying that the balance Ether was transferred back to the owner of the contract

Adding Events to the Contract

Up to till point, our contract has all its functions defined. But to be truly usable, you need to define a couple of events so that you can notify the users when certain events happen:
  • When the winning number is announced

  • When there is a change in the game status (e.g., the number of players played so far)

Add the following statements in bold to the Betting contract in the Remix IDE:
pragma solidity ^0.5;
contract Betting {
    address payable owner;
    uint minWager = 1;
    uint totalWager = 0;
    uint numberOfWagers = 0;
    uint constant MAX_NUMBER_OF_WAGERS = 2;
    uint winningNumber = 999;
    uint constant MAX_WINNING_NUMBER = 3;
    address payable [] playerAddresses;
    mapping (address => bool) playerAddressesMapping;
    struct Player {
        uint amountWagered;
        uint numberWagered;
    }
    mapping(address => Player) playerDetails;
    // the event to announce the winning number
    event WinningNumber(
        uint number
    );
    // the event to display the status of the game
    event Status (
        uint players,
        uint maxPlayers
    );
    // the constructor for the contract
    constructor(uint _minWager) public {
        owner = msg.sender;
        // set the minimum amount wager amount allowed
        // note that this number is in ether
        if (_minWager >0) minWager = _minWager;
    }
    function bet(uint number) public payable {
        // ensure that each player can play once
        // you check using the mapping for performance reasons
        require(playerAddressesMapping[msg.sender] == false);
        // check the range of numbers allowed
        require(number >=1 && number <= MAX_WINNING_NUMBER);
        // note that msg.value is in wei; need to convert to
        // ether
        require( (msg.value / (1 ether)) >= minWager);
        // record the number and amount wagered by the player
        playerDetails[msg.sender].amountWagered = msg.value;
        playerDetails[msg.sender].numberWagered = number;
        // add the player address to the array of addresses as
        // well as mapping
        playerAddresses.push(msg.sender);
        playerAddressesMapping[msg.sender] = true;
        numberOfWagers++;
        totalWager += msg.value;
        if (numberOfWagers >= MAX_NUMBER_OF_WAGERS) {
            announceWinners();
        }
        // call the event to inform the client about the
        // status of the game
        emit Status(numberOfWagers, MAX_NUMBER_OF_WAGERS);
    }
    function announceWinners() private {
        //winningNumber =
        //uint(keccak256(abi.encodePacked(block.timestamp))) %
        //   MAX_WINNING_NUMBER + 1;
        // hard code the winning number to see what happens
        // when the contract is killed
        winningNumber = 1;
        // call the event to announce the winning number
        emit WinningNumber(winningNumber);
        address payable[MAX_NUMBER_OF_WAGERS] memory winners ;
        uint winnerCount = 0;
        uint totalWinningWager = 0;
        // find out the winners
        ...
    }
    function getWinningNumber() view public returns (uint) {
        return winningNumber;
    }
    function kill() public {
        if (msg.sender == owner) {
            selfdestruct(owner);
        }
    }
    function getStatus() view public returns (uint,uint) {
        return (numberOfWagers, MAX_NUMBER_OF_WAGERS);
    }
}
Note that you added two events and fire them when
  • Someone has betted a number

  • Announcing the winning number

Also observe that we added a function named getStatus() so that when a client connects to the Smart Contract for the first time, it can query the contract to find out the current status of the game.

You can now redeploy the contract (with the constructor value 1). Once the contract is deployed, take note of its address and ABI. You will need it in the next section.

Creating the Web Front End

Let’s now create the web front end for the lottery game. Create a new text file and name it as OnlineBetting.html. Save it in the web3projects folder.

Populate the OnlineBetting.html file as follows:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="main.css">
    <script src="./node_modules/web3/dist/web3.min.js">
    </script>
</head>
<body>
    <div class="container">
        <h1>Ethereum Betting</h1>
        <center>
            <label for="numberToWager"
                class="col-lg-2 control-label">
                Number to wager
            </label>
            <input id="numberToWager" type="text">
            <label for="etherToWager"
                class="col-lg-2 control-label">
                Number of ethers to wager
            </label>
            <input id="etherToWager" type="text">
            <button id="btnBet">Bet</button>
            <hr/>
            <h2 id="result"></h2>
            <h2 id="status"></h2>
        </center>
    </div>
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js">
    </script>
    <script>
        if (typeof web3 !== 'undefined') {
            // this statement is executed if you are using
            // MetaMask
            async function enableAccounts() {
                await ethereum.enable();
            }
            enableAccounts();
        } else {
            // set the provider you want from Web3.providers
            web3 = new Web3(
                new Web3.providers.HttpProvider(
                "http://localhost:8545"));
        }
        var abi = [ { "constant": false, "inputs": [], "name": "kill", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getStatus", "outputs": [ { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "number", "type": "uint256" } ], "name": "bet", "outputs": [], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": true, "inputs": [], "name": "getWinningNumber", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "inputs": [ { "name": "_minWager", "type": "uint256" } ], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "number", "type": "uint256" } ], "name": "WinningNumber", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "players", "type": "uint256" }, { "indexed": false, "name": "maxPlayers", "type": "uint256" } ], "name": "Status", "type": "event" } ]
        var bettingContract = web3.eth.contract(abi)
        //---change the address below to that of your own---
        var contractAddress =
            '0x5491ecc1c403315cddd402b57990a3c5c6c50c63';
        var contract = bettingContract.at(contractAddress);
        var _minWager = 1;
        //---event---
        var winnningNumberEvent = contract.WinningNumber();
        winnningNumberEvent.watch((error, result) => {
            if (!error){
                $("#result").html("Winning Number is: " +
                    result.args.number);
            }
        });
        //---event---
        var statusEvent = contract.Status();
        statusEvent.watch((error, result) => {
            if (!error){
                $("#status").html("Status: " +
                    result.args.players +
                    " of " + result.args.maxPlayers);
            }
        });
        //---get the status of the game---
        contract.getStatus(
            (error, result) => {
                if (!error){
                    $("#status").html("Status: " +
                        result[0].c[0] +
                        " of " + result[1].c[0]);
                }
        });
        $("#btnBet").click(function() {
            var numberToWager = $("#numberToWager").val();
            var etherToWager = $("#etherToWager").val();
            contract.bet(numberToWager, {
                gas: 300000,
                from: web3.eth.defaultAccount,
                value: web3.toWei(etherToWager, 'ether')
            }, (err, result) => {
                if (err){
                    $("#result").html(err);
                } else {
                    $("#result").html(
                    "Number has been submitted for betting.");
                }
            });
        });
    </script>
</body>
</html>
To test the web front end, type the following commands in Terminal:
$ cd ~/webprojects
$ serve
Using two instances of the Chrome browser, load each browser with the following URL: http://localhost:5000/OnlineBetting.html. You should see both display the same status (see Figure 10-19).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig19_HTML.jpg
Figure 10-19

Two instances of Chrome displaying the same page and game status

Using Account 1, on the left browser, place a bet on the number 1 using 2 Ethers, and click the Bet button. Observe that MetaMask will pop up a window showing the amount to be sent to the contract. Click CONFIRM. Once you click the CONFIRM button, the first browser will show the message “Number has been submitted for betting” (see Figure 10-20).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig20_HTML.jpg
Figure 10-20

The browser on the left has just betted on a number

When the transaction has been confirmed, both browsers will update the status of the game (see Figure 10-21).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig21_HTML.jpg
Figure 10-21

Both browsers now show the updated status of the game

Now switch to Account 2 on the second web browser and bet on the number 3 with 1 Ether. Click Confirm when the pop-up from MetaMask shows.

Once the transaction is confirmed, you should see the winning number and status of the game on both browsers (see Figure 10-22).
../images/471881_1_En_10_Chapter/471881_1_En_10_Fig22_HTML.jpg
Figure 10-22

Both browsers showing the winning number

Returning Ethers Back to the Owner at the End of the Game

One issue we observed with the contract earlier in this chapter is that at the end of the game, we need to kill the contract in order to get back the Ethers held by the contract (when no one wins the game). Wouldn’t it be better if you could transfer the remaining Ethers back to the owners automatically at the end of the game instead of having to kill it?

This is what we will do in this section. Make the following additions to the contract in the Remix IDE:
    function announceWinners() private {
        //winningNumber =
        //uint(keccak256(abi.encodePacked(block.timestamp))) %
        //  MAX_WINNING_NUMBER + 1;
        // hard code the winning number to see what happens
        // when the contract is killed
        winningNumber = 1;
        // call the event to announce the winning number
        emit WinningNumber(winningNumber);
        address payable[MAX_NUMBER_OF_WAGERS] memory winners ;
        uint winnerCount = 0;
        uint totalWinningWager = 0;
        for (uint i=0; i < playerAddresses.length; i++) {
            address payable playerAddress =
                playerAddresses[i];
            if (playerDetails[playerAddress].numberWagered ==
                winningNumber) {
                winners[winnerCount] = playerAddress;
                totalWinningWager +=
                   playerDetails[playerAddress].amountWagered;
                winnerCount++;
            }
        }
        for (uint j=0; j<winnerCount; j++) {
            winners[j].transfer(
                (playerDetails[winners[j]].amountWagered /
                 totalWinningWager) * totalWager);
        }
        // if there is no winner, transfer all
        // the remaining ethers back to owner
        if (winnerCount==0) {
            owner.transfer(address(this).balance);
        }
    }

To get the balance of Ethers held by the contract, you can use the balance property of an account. The address(this) statement returns the address of the current contract.

To test the modified contract, redeploy the contract using Account 1 and use Account 2 and 3 to play the game:
  • Deploy the contract and update the contract address in the OnlineBetting.html file.

  • Observe balance of Account 1.

  • Reload the OnlineBetting.html page using the Chrome browser.

  • Using Account 2, bet on number 2 using 1 Ether.

  • Using Account 3, bet on number 3 using 1 Ether

When the winning number is announced (which is still a 1 since we have hardcoded the contract to return 1 as the winning number), check the balance of Account 1. It should have 2 additional Ethers, which is transferred by the contract since no one wins the game).

Making the Game Run Indefinitely

So far our game stops when two players have played. And in order to get back all the Ethers, you have to kill the contract. A much better option is to make the game run indefinitely. That is, after the winning number is drawn, the contract automatically pays out either to the players or the owner and the game starts all over again.

To do this, add the following statements in bold to the contract:
...
    function bet(uint number) public payable {
        require(playerAddressesMapping[msg.sender] == false);
        require(number >=1 && number <= MAX_WINNING_NUMBER);
        require( (msg.value / (1 ether)) >= minWager);
        // record the number and amount wagered by the player
        playerDetails[msg.sender].amountWagered = msg.value;
        playerDetails[msg.sender].numberWagered = number;
        playerAddresses.push(msg.sender);
        playerAddressesMapping[msg.sender] = true;
        numberOfWagers++;
        totalWager += msg.value;
        if (numberOfWagers >= MAX_NUMBER_OF_WAGERS) {
            announceWinners();
            //---start a new game---
            // remove all the player addresses mappings
            removeAllPlayersFromMapping();
            // remove all the addresses in the array
            delete playerAddresses;
            // reset the variables
            totalWager = 0;
            numberOfWagers = 0;
            winningNumber = 999;
        }
        emit Status(numberOfWagers, MAX_NUMBER_OF_WAGERS);
    }
    function removeAllPlayersFromMapping() private {
        for (uint i=0; i < playerAddresses.length; i++) {
            delete playerAddressesMapping[playerAddresses[i]];
        }
    }
    ...

To start a new game, you remove all the players’ address and reset the necessary variables. When you now redeploy the contract, the game can be played indefinitely.

Summary

In this chapter, you learned how to build an online lottery game. Apart from using the knowledge that you have learned from the previous few chapters, you have learned quite a number of new things, such as:
  • How to get the Ethers held by a contract to refund to the owner by killing it

  • How to transfer Ethers programmatically to another account

  • How to use Etherscan to view the internal transfers made in a contract

In the next chapter, you will learn about tokens and how you can use them in your Smart Contracts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.77.131