An early Ethereum roulette contract from August 2015, notable for a critical randomness vulnerability: its 'private' seed is hardcoded to 1 in the constructor.
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);
}
}