LooneyLottery by The Looney Farm: a ticket lottery played by sending ETH to the contract, 10 finney per ticket, with the pool paid to one winner per round.
Key Facts
Description
LooneyLottery has no ABI functions for players: sending at least 10 finney to the contract buys tickets, up to 100 per transaction, and any amount above a whole number of tickets is refunded. A round closes once 222 players have entered, or once at least 5 have entered and its 24 hour timer has run out; the next entry after that triggers the draw. The winning ticket is chosen with a Lehmer random number generator whose seeds are mixed with the block coinbase and blockhash, and the winner receives 9.95 finney per ticket in the round, leaving a 50 szabo fee per ticket that the owner can collect with ownerWithdraw(). Player and Winner events log each entry and payout, and the game constants and lifetime totals (transactions, tickets, turnover) are public getters.
Source Verified
Original source by the author: https://github.com/Cryptix23/contracts/blob/9885b2bb6d/src/lottery/lottery.sol (optimizer on). Verified on Etherscan.
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Frontier Era
The initial release of Ethereum. A bare-bones implementation for technical users.
Bytecode Overview
Verified Source Available
Source verified through compiler archaeology and exact bytecode matching.
Show source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
// LooneyLottery that pays out the full pool once a day
//
// git: https://github.com/thelooneyfarm/contracts/tree/master/src/lottery
// url: http://the.looney.farm/game/lottery
contract LooneyLottery {
// modifier for the owner protected functions
modifier owneronly {
// yeap, you need to own this contract to action it
if (msg.sender != owner) {
throw;
}
// function execution inserted here
_
}
// constants for the Lehmer RNG
uint constant private LEHMER_MOD = 4294967291;
uint constant private LEHMER_MUL = 279470273;
uint constant private LEHMER_SDA = 1299709;
uint constant private LEHMER_SDB = 7919;
// various game-related constants, also available in the interface
uint constant public CONFIG_DURATION = 24 hours;
uint constant public CONFIG_MIN_PLAYERS = 5;
uint constant public CONFIG_MAX_PLAYERS = 222;
uint constant public CONFIG_MAX_TICKETS = 100;
uint constant public CONFIG_PRICE = 10 finney;
uint constant public CONFIG_FEES = 50 szabo;
uint constant public CONFIG_RETURN = CONFIG_PRICE - CONFIG_FEES;
uint constant public CONFIG_MIN_VALUE = CONFIG_PRICE;
uint constant public CONFIG_MAX_VALUE = CONFIG_PRICE * CONFIG_MAX_TICKETS;
// our owner, stored for owner-related functions
address private owner = msg.sender;
// basic initialisation for the RNG
uint private random = uint(sha3(block.coinbase, block.blockhash(block.number - 1), now));
uint private seeda = LEHMER_SDA;
uint private seedb = LEHMER_SDB;
// we allow 222 * 100 max tickets, allocate a bit more and store the mapping of entry => address
uint8[25000] private tickets;
mapping (uint => address) private players;
// public game-related values
uint public round = 1;
uint public numplayers = 0;
uint public numtickets = 0;
uint public start = now;
uint public end = start + CONFIG_DURATION;
// lifetime stats
uint public txs = 0;
uint public tktotal = 0;
uint public turnover = 0;
// nothing much to do in the constructor, we have the owner set & init done
function LooneyLottery() {
}
// owner withdrawal of fees
function ownerWithdraw() owneronly public {
// calculate the fees collected previously (excluding current round)
uint fees = this.balance - (numtickets * CONFIG_PRICE);
// return it if we have someting
if (fees > 0) {
owner.call.value(fees)();
}
}
// calculate the next random number with a two-phase Lehmer
function randomize() private {
// calculate the next seed for the first phase
seeda = (seeda * LEHMER_MUL) % LEHMER_MOD;
// adjust the random accordingly, getting extra info from the blockchain together with the seeds
random ^= uint(sha3(block.coinbase, block.blockhash(block.number - 1), seeda, seedb));
// adjust the second phase seed for the next iteration
seedb = (seedb * LEHMER_MUL) % LEHMER_MOD;
}
// pick a random winner when the time is right
function pickWinner() private {
// do we have >222 players or >= 5 tickets and an expired timer
if ((numplayers >= CONFIG_MAX_PLAYERS ) || ((numplayers >= CONFIG_MIN_PLAYERS ) && (now > end))) {
// get the winner based on the number of tickets (each player has multiple tickets)
uint winidx = tickets[random % numtickets];
uint output = numtickets * CONFIG_RETURN;
// send the winnings to the winner and let the world know
players[winidx].call.value(output)();
notifyWinner(players[winidx], output);
// reset the round, and start a new one
numplayers = 0;
numtickets = 0;
start = now;
end = start + CONFIG_DURATION;
round++;
}
}
// allocate tickets to the entry based on the value of the transaction
function allocateTickets(uint number) private {
// the last index of the ticket we will be adding to the pool
uint ticketmax = numtickets + number;
// loop through and allocate a ticket based on the number bought
for (uint idx = numtickets; idx < ticketmax; idx++) {
tickets[idx] = uint8(numplayers);
}
// our new value of total tickets (for this round) is the same as max, store it
numtickets = ticketmax;
// store the actual player info so we can reference it from the tickets
players[numplayers] = msg.sender;
numplayers++;
// let the world know that we have yet another player
notifyPlayer(number);
}
// we only have a default function, send an amount and it gets allocated, no ABI needed
function() public {
// oops, we need at least 10 finney to play :(
if (msg.value < CONFIG_MIN_VALUE) {
throw;
}
// adjust the random value based on the pseudo rndom inputs
randomize();
// pick a winner at the end of a round
pickWinner();
// here we store the number of tickets in this transaction
uint number = 0;
// get either a max number based on the over-the-top entry or calculate based on inputs
if (msg.value >= CONFIG_MAX_VALUE) {
number = CONFIG_MAX_TICKETS;
} else {
number = msg.value / CONFIG_PRICE;
}
// overflow is the value to be returned, >max or not a multiple of min
uint input = number * CONFIG_PRICE;
uint overflow = msg.value - input;
// store the actual turnover, transaction increment and total tickets
turnover += input;
tktotal += number;
txs += 1;
// allocate the actual tickets now
allocateTickets(number);
// send back the overflow where applicable
if (overflow > 0) {
msg.sender.call.value(overflow)();
}
}
// log events
event Player(address addr, uint32 at, uint32 round, uint32 tickets, uint32 numtickets, uint tktotal, uint turnover);
event Winner(address addr, uint32 at, uint32 round, uint32 numtickets, uint output);
// notify that a new player has entered the fray
function notifyPlayer(uint number) private {
Player(msg.sender, uint32(now), uint32(round), uint32(number), uint32(numtickets), tktotal, turnover);
}
// create the Winner event and send it
function notifyWinner(address addr, uint output) private {
Winner(addr, uint32(now), uint32(round), uint32(numtickets), output);
}
}External Links
Related contracts
CharlyLifeLog
Same deployerOn-chain life log and trust fund built by Jaco Greeff for his daughter Charlyn, born April 2016. Holds 16 ETH withdrawable to whitelisted addresses.
0x5a5eff...0953c9June 23, 2016Contract 0x177283...4664aa
Same eraAn early on-chain lottery contract deployed on Frontier by the prolific 0xFD2605 deployer cluster.
0x177283...4664aaAugust 10, 2015Roulette
Same eraAn early Ethereum roulette contract from August 2015, notable for a critical randomness vulnerability: its 'private' seed is hardcoded to 1 in the constructor.
0x5fe5b7...25a4fdAugust 10, 2015Gamble
Same eraA 106-byte coin-flip: send ether and, if the block timestamp is odd, the contract tries to send back double. If it is even, you lose the stake.
0x4b9105...7ea144August 19, 2015Prediction Market
Same eraLMSR prediction market (Aug 2015 – Jun 2016, 662 ETH). 11/25 selectors byte-exact, 14/25 semantic reconstructions.
0x2935aa...7a9085August 23, 2015Gamble
Same eraA 106-byte coin-flip: send ether and, if the block timestamp is odd, the contract tries to send back double. If it is even, you lose the stake.
0xdeed9f...8caea8August 26, 2015