EtherPot, a blockhash lottery that runs in rounds and pays out in subpots
Context
Deployed in August 2015, in the first month of the Frontier chain, and compiled with solc v0.1.1. The source is lotto.sol from the etherpot contract repository, which has since been deleted from GitHub.
Key Facts
Description
The EtherPot lottery. Rounds are defined by block height rather than by any state: getRoundIndex is simply the block number divided by blocksPerRound, set here to 5760 blocks, roughly a day. Sending ether to the fallback function buys tickets at a tenth of an ether each, with any remainder refunded, and records the buyer in that round's list. Once a round is over anyone may call cash, which splits the pot into subpots of five ether and pays each one to the holder whose ticket index matches the hash of the round's decision block, modulo the ticket count. A subpot can only be cashed once. The design leans on block.blockhash for randomness, which a miner producing the decision block can influence.
Source Verified
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.
View Verification ProofShow source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
contract Lotto {
uint constant public blocksPerRound = 5760;
// there are an infinite number of rounds (just like a real lottery that takes place every week). `blocksPerRound` decides how many blocks each round will last. 6800 is around a day.
uint constant public ticketPrice = 100000000000000000;
// the cost of each ticket is .1 ether.
uint constant public blockReward = 5000000000000000000;
function getBlocksPerRound() constant returns(uint){ return blocksPerRound; }
function getTicketPrice() constant returns(uint){ return ticketPrice; }
//accessors for constants
struct Round {
address[] buyers;
uint pot;
uint ticketsCount;
mapping(uint=>bool) isCashed;
mapping(address=>uint) ticketsCountByBuyer;
}
mapping(uint => Round) rounds;
//the contract maintains a mapping of rounds. Each round maintains a list of tickets, the total amount of the pot, and whether or not the round was "cashed". "Cashing" is the act of paying out the pot to the winner.
function getRoundIndex() constant returns (uint){
//The round index tells us which round we're on. For example if we're on block 24, we're on round 2. Division in Solidity automatically rounds down, so we don't need to worry about decimals.
return block.number/blocksPerRound;
}
function getIsCashed(uint roundIndex,uint subpotIndex) constant returns (bool){
//Determine if a given.
return rounds[roundIndex].isCashed[subpotIndex];
}
function calculateWinner(uint roundIndex, uint subpotIndex) constant returns(address){
//note this function only calculates the winners. It does not do any state changes and therefore does not include various validitiy checks
var decisionBlockNumber = getDecisionBlockNumber(roundIndex,subpotIndex);
if(decisionBlockNumber>block.number)
return;
//We can't decided the winner if the round isn't over yet
var decisionBlockHash = getHashOfBlock(decisionBlockNumber);
var winningTicketIndex = decisionBlockHash%rounds[roundIndex].ticketsCount;
//We perform a modulus of the blockhash to determine the winner
var ticketIndex = uint256(0);
for(var buyerIndex = 0; buyerIndex<rounds[roundIndex].buyers.length; buyerIndex++){
var buyer = rounds[roundIndex].buyers[buyerIndex];
ticketIndex+=rounds[roundIndex].ticketsCountByBuyer[buyer];
if(ticketIndex>winningTicketIndex){
return buyer;
}
}
}
function getDecisionBlockNumber(uint roundIndex,uint subpotIndex) constant returns (uint){
return ((roundIndex+1)*blocksPerRound)+subpotIndex;
}
function getSubpotsCount(uint roundIndex) constant returns(uint){
var subpotsCount = rounds[roundIndex].pot/blockReward;
if(rounds[roundIndex].pot%blockReward>0)
subpotsCount++;
return subpotsCount;
}
function getSubpot(uint roundIndex) constant returns(uint){
return rounds[roundIndex].pot/getSubpotsCount(roundIndex);
}
function cash(uint roundIndex, uint subpotIndex){
var subpotsCount = getSubpotsCount(roundIndex);
if(subpotIndex>=subpotsCount)
return;
var decisionBlockNumber = getDecisionBlockNumber(roundIndex,subpotIndex);
if(decisionBlockNumber>block.number)
return;
if(rounds[roundIndex].isCashed[subpotIndex])
return;
//Subpots can only be cashed once. This is to prevent double payouts
var winner = calculateWinner(roundIndex,subpotIndex);
var subpot = getSubpot(roundIndex);
var success = winner.send(subpot);
if(!success)
return;
rounds[roundIndex].isCashed[subpotIndex] = true;
//Mark the round as cashed
}
function getHashOfBlock(uint blockIndex) constant returns(uint){
var hash = block.blockhash(blockIndex);
return uint(hash);
}
function getBuyers(uint roundIndex,address buyer) constant returns (address[]){
return rounds[roundIndex].buyers;
}
function getTicketsCountByBuyer(uint roundIndex,address buyer) constant returns (uint){
return rounds[roundIndex].ticketsCountByBuyer[buyer];
}
function getPot(uint roundIndex) constant returns(uint){
return rounds[roundIndex].pot;
}
function() {
//this is the function that gets called when people send money to the contract.
var roundIndex = getRoundIndex();
var value = msg.value-(msg.value%ticketPrice);
if(value==0) return;
if(value<msg.value){
msg.sender.send(msg.value-value);
}
//no partial tickets, send a partial refund
var ticketsCount = value/ticketPrice;
if(rounds[roundIndex].ticketsCountByBuyer[msg.sender]==0){
var buyersLength = rounds[roundIndex].buyers.length++;
rounds[roundIndex].buyers[buyersLength] = msg.sender;
}
rounds[roundIndex].ticketsCountByBuyer[msg.sender]+=ticketsCount;
rounds[roundIndex].ticketsCount+=ticketsCount;
//keep track of the total tickets
rounds[roundIndex].pot+=value;
//keep track of the total pot
}
}External Links
Related contracts
EtherPot (draft build)
Same deployerA pre-release August 2015 test build of the EtherPot blockhash lottery, deployed three weeks into Frontier and never played.
0x47f53b...416adfAugust 21, 2015EtherPot
Same deployerThe public EtherPot lottery, an August 2015 Frontier contract whose blockhash-based winner selection became an early cautionary tale about on-chain randomness.
0x539f29...9587f9August 25, 2015Lotto
Same deployerEtherPot with rounds ten blocks long, deployed to exercise the payout path
0x396864...d0edb7August 27, 2015Lotto
Same deployerEtherPot, a blockhash lottery that runs in rounds and pays out in subpots
0x4bf42d...d2e7e3August 28, 2015MessageStore
Same eraA 6-line contract storing a single public string, deployed Day 9 of Ethereum by a prolific early experimenter who shipped 18 contracts in one week.
0xd2eccd...ff3d6bAugust 8, 2015Contract 0xa85146...9bbf06
Same erago-ethereum's built-in hash registrar, mapping a key to a content hash
0xa85146...9bbf06August 10, 2015