How to make a lottery project with Ethereum(ETH)(3) ? - The lottery draw
The Lottery Draw
Imagine a lottery where each “ticket” costs a certain amount of ether. When buying a ticket you are allowed to guess one number between 1 and 1,000. Your ticket “id” is simply the wallet address you use to pay the ether with, and it is associated with your guess. You can buy as many “tickets” as you like. Every time you pay and guess, your wallet address is entered again.
At the appointed time, the draw is made (a number is selected randomly) and the pot is divided equally between and paid to any addresses that correctly guess the number (minus a percentage commission for the organiser of the draw). If no one guesses correctly, then the pot (minus the commission to the organiser) gets transferred to the next lottery draw (a rollover) and the process starts again.
The “draw” contract
Each instance of the lottery we will call a “draw”. Our draw contract first declares a bunch of variables (see here for Solidity data types):
contract draw {
address owner;
uint public numTickets;
uint public drawDate;
uint public actualDrawDate;
bool public drawn;
uint public entryFee;
uint public payout;
uint public winningNumber;
address public organiser;
address public nextDraw;
address public previousDrawAddress;
struct Ticket {
uint guess;
address eth_address;
}
mapping(uint => Ticket) public tickets;
address[] public winningaddresses;
}
If you declare them as “public” then you can access them later by calling, for example, draw.numTickets() from the geth console.
The Constructor
After that, you have a constructor function, which gets called once when the contract is “mined”:
function draw(uint _offset, uint _entryFee, address _organiser, address _previousDrawAddress) {
owner = msg.sender;
numTickets = 0;
drawn = false;
winningNumber = 0;
payout = 0;
drawDate = now + _offset;
actualDrawDate = 0;
entryFee = _entryFee;
organiser= _organiser;
previousDrawAddress = _previousDrawAddress;
}
In our contract constructor we initialise all the variables, including setting the owner (who is the only one who can perform certain crucial functions later), the entry fee (i.e. the cost of each ticket), the address of the organiser (who will collect the commission fee) and the address of the previous draw (more on this later, but this is basically so that draws can be daisy-chained together). We also set the drawDate, which is the earliest possible date that the draw can be made.
Buying a ticket
The buyTicket function allows anyone who knows about this contract to buy a ticket:
function buyTicket(address _buyer, uint _guess) returns (uint ticketid) {
if (msg.value != entryFee) throw;
if (_guess > 1000 || _guess < 1) throw;
if (drawn) throw;
ticketid = numTickets++;
tickets[ticketid] = Ticket(_guess, _buyer);
BuyTicket(ticketid);
}
If the value sent in by the ticket buyer is not the exact entry fee, then they will not be allowed to buy a ticket. Also, a ticket cannot be bought if the draw has already taken place! If all conditions are met, the buyer's address and guess are entered in the draw by adding them to our tickets array. The last line, BuyTicket(ticketid); is an Event trigger (Events are outlined here.). I won't go into detail on that because it is incidental to the rest.
Doing the draw
On or after the appointed time (drawDate) the draw can be made by calling this function:
function doDraw() {
if (drawn) throw;
if (now < drawDate) throw;
winningNumber = (now % 1000) +1 ;
actualDrawDate = now;
for (uint i = 0; i < numTickets; ++i) {
if (tickets[i].guess == winningNumber) {
winningaddresses.push(tickets[i].eth_address);
}
}
var commission = numTickets*entryFee / 10;
payout = this.balance - commission;
for (uint j = 0; j < winningaddresses.length; ++j) {
winningaddresses[j].send(payout / winningaddresses.length);
}
organiser.send(commission);
DrawDone(winningNumber);
drawn = true;
}
The main thing to note here is that our "random" selection of a number between 1 and 1,000 is very crude. It is a massive problem with Solidity at the moment that it does not have a Rand() function. Almost anything about a blockchain is deterministic by design, so it is incredibly hard to generate randomness from things available from within the contract. We are going to be doing more work on this later, possibly integration this randomness contract. We will report back on that later. For now, let's assume that we have good randomness to generate a number between 1 and 1,000.
Crucially, any one could trigger the draw, not just the contract owner. So once a draw contract is out there on the blockchain and the earliest draw date has passed, even if he owners disappear the draw can still be held.
Once the winningNumber is obtained, we cycle through all the tickets to figure out if we have any winners, which are pushed into an array of wnningaddresses.
Paying out
The lottery pot is contained in the contract balance (this.balance). A commission is calculated (10% of tickets bought), paid to the organiser. The remaining amount is assigned to a payout variable, divided between the winners and paid out to their wallets. Note that if there are no winners, no more funds will be drained from the contract balance, which leads to....
The rollover
In the event that there are no winners, the pot gets rolled over into the next draw with this function:
function transferPot(address _newContract) {
if (msg.sender != owner) throw;
if (this.balance == 0) throw;
if (!drawn) throw;
_newContract.send(this.balance);
nextDraw = _newContract;
}
This can only be done by the contract owner. It requires that a new draw contract be created that can receive the funds (_newContract). So basically the draws need to be daisy-chained so that one draw can send funds to the next one.
Other helpful functions
Additionally, we have a few functions that allow someone to query the contract to see if they have won:
function getPrizeValue (address _query) constant returns (uint _value) {
if (!drawn) throw;
_value =0;
for (uint i = 0; i < winningaddresses.length; ++i) {
if (winningaddresses[i] == _query) {
_value += payout / winningaddresses.length;
}
}
} ...and another one to obtain the size of the current pot:
function getPot() constant returns (uint) {
return this.balance;
}
i dont understand,too difficult for me
欢迎来到新手村,这确实是个忧伤的故事
Congratulations @coldicelion! You have completed some achievement on Steemit and have been rewarded with new badge(s) :
Award for the number of comments
Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here
If you no longer want to receive notifications, reply to this comment with the word
STOP
欢迎
Congratulations @coldicelion! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!