An early Ethereum roulette contract from August 2015, notable for a critical randomness vulnerability: its 'private' seed is hardcoded to 1 in the constructor.
Historical Significance
Roulette is one of the earliest documented examples of the blockchain randomness vulnerability on Ethereum — a bug class that would go on to drain millions of dollars from gambling contracts over subsequent years. The contract's author believed that a "private" variable initialized to a hardcoded constant was unpredictable, when in reality all Ethereum state is public. This contract is an artifact of the pre-audit era, when developers were still learning the security implications of programming on a transparent, deterministic blockchain.
Context
In August 2015, Ethereum had been live for less than two weeks. There were no smart contract auditors, no established security best practices, and no public vulnerability databases for Solidity. Developers deploying contracts learned through experimentation. The assumption that private variables are hidden — a reasonable intuition from traditional programming — proved incorrect on a public blockchain. The exploitation of randomness and storage privacy bugs would become one of the defining security themes of early Ethereum.
Key Facts
Description
Roulette is one of the earliest gambling contracts on Ethereum mainnet, deployed at block 66,126 on August 10, 2015 — 11 days after genesis. Created by address 0xa14cf6cec1c6aae4b608458f6e14692863a937aa, it implements a roulette-style betting game where players wager between 1 and 10 ETH on a number.
The contract maintains a Casino struct holding the house address, balance, and betting limits. Players call betOnNumber(uint number) to place a bet; a random number is generated using an internal seed and compared to determine the outcome. Winnings are sent directly to the player's address.
The contract's source code includes a comment claiming that the privSeed used for random number generation is difficult to guess because it is "nowhere visible." However, privSeed is initialized to 1 in the constructor — a hardcoded, publicly known value. Anyone reading the contract source can predict every outcome. This is an early example of the "bad randomness" vulnerability class that would later be systematically exploited across many Ethereum gambling contracts.
The minimum bet is 1 ETH and the maximum is 10 ETH.
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 on Etherscan.
Show source code (Solidity)
contract Roulette {
// Global variables
string sWelcome;
/* Remark:
* Private Seed for generateRand(),
* since this is nowhere visibile,
* it's very hard to guess.
*/
uint privSeed;
struct Casino {
address addr;
uint balance;
uint bettingLimitMin;
uint bettingLimitMax;
}
Casino casino;
// Init Constructor
function Roulette() {
sWelcome = "\n-----------------------------\n Welcome to Roulette \n Got coins? Then come on in! \n-----------------------------\n";
privSeed = 1;
casino.addr = msg.sender;
casino.balance = 0;
casino.bettingLimitMin = 1*10**18;
casino.bettingLimitMax = 10*10**18;
}
function welcome() constant returns (string) {
return sWelcome;
}
function casinoBalance() constant returns (uint) {
return casino.balance;
}
function casinoDeposit() {
if (msg.sender == casino.addr)
casino.balance += msg.value;
else
msg.sender.send(msg.value);
}
function casinoWithdraw(uint amount) {
if (msg.sender == casino.addr && amount <= casino.balance) {
casino.balance -= amount;
casino.addr.send(amount);
}
}
// Bet on Number
function betOnNumber(uint number) public returns (string) {
// Input Handling
address addr = msg.sender;
uint betSize = msg.value;
if (betSize < casino.bettingLimitMin || betSize > casino.bettingLimitMax) {
// Return Funds
if (betSize >= 1*10**18)
addr.send(betSize);
return "Please choose an amount within between 1 and 10 ETH";
}
if (betSize * 36 > casino.balance) {
// Return Funds
addr.send(betSize);
return "Casino has insufficient funds for this bet amount";
}
if (number < 0 || number > 36) {
// Return Funds
addr.send(betSize);
return "Please choose a number between 0 and 36";
}
// Roll the wheel
privSeed += 1;
uint rand = generateRand();
if (number == rand) {
// Winner winner chicken dinner!
uint winAmount = betSize * 36;
casino.balance -= (winAmount - betSize);
addr.send(winAmount);
return "Winner winner chicken dinner!";
}
else {
casino.balance += betSize;
return "Wrong number.";
}
}
// Bet on Color
function betOnColor(uint color) public returns (string) {
// Input Handling
address addr = msg.sender;
uint betSize = msg.value;
if (betSize < casino.bettingLimitMin || betSize > casino.bettingLimitMax) {
// Return Funds
if (betSize >= 1*10**18)
addr.send(betSize);
return "Please choose an amount within between 1 and 10 ETH";
}
if (betSize * 2 > casino.balance) {
// Return Funds
addr.send(betSize);
return "Casino has insufficient funds for this bet amount";
}
if (color != 0 && color != 1) {
// Return Funds
addr.send(betSize);
return "Please choose either '0' = red or '1' = black as a color";
}
// Roll the wheel
privSeed += 1;
uint rand = generateRand();
uint randC = (rand + 1) % 2;
// Win
if (rand != 0 && (randC == color)) {
uint winAmount = betSize * 2;
casino.balance -= (winAmount - betSize);
addr.send(winAmount);
return "Win! Good job.";
}
else {
casino.balance += betSize;
return "Wrong color.";
}
}
// Returns a pseudo Random number.
function generateRand() private returns (uint) {
// Seeds
privSeed = (privSeed*3 + 1) / 2;
privSeed = privSeed % 10**9;
uint number = block.number; // ~ 10**5 ; 60000
uint diff = block.difficulty; // ~ 2 Tera = 2*10**12; 1731430114620
uint time = block.timestamp; // ~ 2 Giga = 2*10**9; 1439147273
uint gas = block.gaslimit; // ~ 3 Mega = 3*10**6
// Rand Number in Percent
uint total = privSeed + number + diff + time + gas;
uint rand = total % 37;
return rand;
}
// Function to recover the funds on the contract
function kill() {
if (msg.sender == casino.addr)
suicide(casino.addr);
}
}