/lottery-smart-contract

lottery game for solidity-ethereum smart contract

Primary LanguageJavaScript

Lottery

Overview

We implemented a simple lottery(Milli Piyango) game in Solidity. Users buy ticket with a pre-generated hash in the submission stage. This pre-generated hash validates if the correct user requested correct ticket number with a random number(like a password). If they can provide this valid random number they're able to join the lottery with their valid tickets. Users can buy different ticket numbers as they like, but ticket numbers are unique from 0 to 99999 (defined as constants in the contract). Contract owner can start the lottery generation in the reveal stage. Generation step generates winning tickets and awards the winners. If enough money collected in the submission stage, then rewards are distributed to each winner. If there is not enough money to pay all prizes, then a refund mechanism works to pay ticket costs to players. If any money leftover after the reward distribution, then leftover amount transferred to the pre-defined charity address.

Structs

SenderHash:

Struct defines a hash and ticket number tuple.

Variables and Constants

There are many variables and constants to track users, winners, tickets. Since Solidity has no iterable mapping structure, we had to store keys for maps in seperate array to be able to iterate over them. Constants define prizes, ticket range and stage durations.

Modifiers

We wanted to keep everything in a easy readable form, so we took advantage of Solidity modifiers. There are 3 main modifiers:

purchaseOngoing:

It ensures the lottery in purchase state. Checks current time against when lottery started and how long purchase duration is.

revealOngoing:

It ensures the lottery in reveal state. Checks current time against when lottery started and how long reveal duration is.

ownerOnly:

Ensures the message sender is contract owner.

Constructor:

A simple constructor where the charity address and contract owner is defined. It calls resetLottery so that ensures lottery is able to be started.

Runnable Functions:

resetLottery:

It resets the lottery variables and sets lottery start time. Called many places in different functions to end the current on going lottery.

ticketAvailable:

A public function to help if requested ticket is available. Users can check if their ticketNumber is purchased before they submit their hashes.

buyTicket:

A external function issues SenderHash struct to message senders. It accepts a byte32 hash which should be keccak256(sha3) hash generated by user first. Hash format should be: keccak256(randomNumber, ticketNumber, msg.sender)

If the sent value is enough to buy a ticket and the request ticket is available, this function accepts hash and ticket number. Accepted values will be used in enterLottery function. This functions is callable only in the purchase step.

enterLottery:

A external function which actually let the user join the actual lottery game. buyTicket must be called before this step with valid hash. This function accepts a random number and ticket number and checks if the sender sent a valid hash in the buyTicket function. If checks are satisfied, then enterLottery lets the user to join the lottery. Without a verified hash users cannot be awarded even if they picked a winning ticket. Also they cannot verify the same ticketNumber more than 1. This functions is callable only in the purchase step.

generateWinners:

External function only callable by contract owner. Generates winning tickets for main prizes and last digit prizes. Also tries to send the rewards to winners if there is enough money. Leftover money after distribution will be sent to the charity address. If there is not enough money, ticket costs will be refunded to every buyer. Note: User will be able to get refund money even if they could not enter lottery with a valid random number.

Generation step first generates the winning tickets, then it tries to find if there is any address wins prize. It will sum up the winner amounts and then checks if the contract can pay this amount. So there is actually a "temporary award" step in the generation. If there is enough money to reward the winners, then temporary awards are distributed to actual user balances.

getWinnerTickets:

A external function lets the user see the winning tickets. Without this function user would not trust the contract if they are really won a reward or not. Last 3 tickets are digit prizes.

withdrawLotteryMoney:

Lets the user withdraw the money from the contract. This money can stay there as long as user withdraws it. Also if a user wins multiple rewards, in the same lottery or in different lotteries through time, contract will increase the amount in the user balance map.

Random Tickets

We followed the random generation idea in the: https://medium.com/@promentol/lottery-smart-contract-can-we-generate-random-numbers-in-solidity-4f586a152b27 But inheriting the same random generation in the link would create a vulnerability in our lottery game. In the lottery game ticket numbers are pre defined. So using only ticket numbers to generate a random ticket number could be breakable by brute force attacks. So we had to use an external random number from users. With provided random numbers, the system will be able to generate much more random like numbers with a simple sha3 hash generation. We hardened the random number generation with supplying tickets bought to the sha3 functions. We thought that it would decrease the probability of a coordinated player attacks.

Sha3 generates a very big integer, so we needed to take small portions of this big number. We were able to generate a random number in ticket number range with taking modulos of big random number. Also using only 1 random ticket would not enough for different prizes. We also added a seed to the random number generation and for each prize we supplied a different seed to get different random tickets. (See generateDigitWinner functions). The most challenging part of this project was generating random tickets.

Testing

In our smart contract unit testing script, we wrote some test cases that checks some boundary conditions and validity of the functions. In our script, firsly we tested if the ticket number is between 0-99999. If not, test returns false. After that, we checked if the paid amount of ethers are exacly 2 or not. If payement amount is under 2 ethers, test returns “less payment reverts”. If payment is above 2 ethers. Test returns “more payement reverts”. In our test cases, we also tried that if same ticket can be bought or not. When we run the corresponding test, if the ticket can’t be bought by two person it returns "tickets cannot be bought by two person". We also checked if the random numbers works correctly. In order to do so, we run RandomNumberIsCorrect test case. If test is passed, it says "Random number correctly verified". We also checked if the total amount of the collected money is enough for the price payement. In our test case, if the test is passed it returns "Enough Money is Collected". During our testing, we faced remix compiler errors and we had to deploy the testContract in order to test. When we add lottery_modifierless to test stage 2 functions ( because modifiers sets time rules), we faced some compiler errors that we couldn’t figure out.