A pre-release August 2015 test build of the EtherPot blockhash lottery, deployed three weeks into Frontier and never played.
Historical Significance
EtherPot was one of the earliest decentralized lotteries on Ethereum and became a well-known cautionary tale about using block.blockhash for randomness, since blockhash is only available for the most recent 256 blocks. This contract is a development draft, deployed by the same account that shipped the public EtherPot four days later, that predates any published EtherPot source. It gives a rare look at the design before the public release: a simpler one-entry-per-ticket array and a 10-block test round.
Context
August 2015 was the first month of the Ethereum Frontier mainnet, which launched on July 30, 2015. Tooling was minimal, the Solidity compiler was at v0.1.1, and developers commonly iterated by deploying throwaway builds straight to mainnet because there was no established testnet workflow. EtherPot was part of this first wave of application experiments, alongside early registrars, wallets, and prediction markets.
Key Facts
Description
This is an early development build of EtherPot, the decentralized lottery written by Aakil Fernandes and collaborators in the first weeks of the Ethereum Frontier network. Players buy tickets by sending ether to the contract. Each round lasts a fixed number of blocks, and the winner is chosen from the blockhash of a block mined just after the round closes, an on-chain source of randomness that no single party controls. To limit any one miner's incentive to grind that blockhash, a round's pot is split into subpots no larger than the block reward, so the value of manipulating a result stays below the value of mining honestly. Each subpot is paid out independently by calling cash.
This deployment is not the version EtherPot shipped to the public. It was deployed on August 21, 2015, and it is a draft. blocksPerRound is set to 10 (about two and a half minutes) rather than the 6800 used in production, the kind of short round you choose to watch a full cycle complete while testing. It stores tickets in a single address array with one entry per ticket, and calculateWinner indexes that array directly, where the published contract later refactored this into a deduplicated buyers array plus a ticket-count mapping. The same account deployed the public EtherPot at 0x539f2912831125c9b86451420bc0d37b219587f9 four days later, on August 25, 2015, with blocksPerRound set to 6800. That shared deployer (0xb735bf53abc79525a4f585a004a620d08cc66b27, which sent 34 contract creations in this period) is what ties this draft to the EtherPot author.
The contract was deployed and then abandoned. Its only transaction is the deploy itself, no tickets were ever bought, and its balance has always been zero. It survives as an artifact of EtherPot's design before any source for it was made public. The runtime and creation bytecode were both reproduced byte-for-byte from reconstructed source compiled with soljson v0.1.1 (optimizer off).
Source Verified
Exact bytecode match (runtime + creation). Runtime: 2227 bytes. Creation: 2246 bytes (19 init + 2227 runtime payload, 0 constructor args). Runtime SHA-256 d680a92a9e11813a20b55289b5276095967b7d92eb76930f390365da83ae8b52, creation SHA-256 621530c5eb0e131d202d721df50e7f9bb1d4e9951e2bb705cc73fc49dcca4952.
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)
contract Lotto {
uint constant public blocksPerRound = 10;
uint constant public ticketPrice = 100000000000000000;
uint constant public blockReward = 5000000000000000000;
function getBlocksPerRound() constant returns(uint){ return blocksPerRound; }
function getTicketPrice() constant returns(uint){ return ticketPrice; }
struct Round {
address[] tickets;
uint pot;
mapping(uint=>bool) isCashed;
}
mapping(uint => Round) rounds;
function getRoundIndex() constant returns (uint){
return block.number/blocksPerRound;
}
function getIsCashed(uint roundIndex,uint subpotIndex) constant returns (bool){
return rounds[roundIndex].isCashed[subpotIndex];
}
function calculateWinner(uint roundIndex, uint subpotIndex) constant returns(address){
var decisionBlockNumber = getDecisionBlockNumber(roundIndex,subpotIndex);
if(decisionBlockNumber>block.number)
return;
var decisionBlockHash = getHashOfBlock(decisionBlockNumber);
var winningTicketIndex = decisionBlockHash%rounds[roundIndex].tickets.length;
return rounds[roundIndex].tickets[winningTicketIndex];
}
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;
var winner = calculateWinner(roundIndex,subpotIndex);
var subpot = getSubpot(roundIndex);
winner.send(subpot);
rounds[roundIndex].isCashed[subpotIndex] = true;
}
function getHashOfBlock(uint blockIndex) constant returns(uint){
return uint(block.blockhash(blockIndex));
}
function getTickets(uint roundIndex) constant returns (address[]){
return rounds[roundIndex].tickets;
}
function getPot(uint roundIndex) constant returns(uint){
return rounds[roundIndex].pot;
}
function() {
var roundIndex = getRoundIndex();
var value = msg.value-(msg.value%ticketPrice);
if(value==0) return;
if(value<msg.value){
msg.sender.send(msg.value-value);
}
var ticketsCount = value/ticketPrice;
var ticketIndex = rounds[roundIndex].tickets.length;
rounds[roundIndex].tickets.length = rounds[roundIndex].tickets.length + ticketsCount;
for(var i=0;i<ticketsCount;i++){
rounds[roundIndex].tickets[ticketIndex+i] = msg.sender;
}
rounds[roundIndex].pot+=value;
}
}