One of the earliest provably-fair dice gambling contracts on Ethereum, deployed August 12, 2015 (day 13 of Frontier). Commit-reveal dice game with 2% house edge...
Key Facts
Description
HonestDice was a commit-reveal dice game deployed just 13 days after Ethereum's Frontier launch. Players submitted a bet with a secret hash; the server provided a random seed; the player then revealed their secret to determine the outcome. The game offered configurable odds (1-255) with a 2% house edge and a maximum payout of 5% of the contract's bankroll per roll.
The contract enforced a 10 ETH minimum bet (the published GitHub source shows 1 ETH, but the as-deployed version used 10 ETH). A server-provided seed within 20 blocks prevented front-running.
Notably, the contract contains a shadowing bug: both lockBetsForWithdraw() and unlockBets() declare a local uint betsLocked variable instead of modifying the state variable. Both functions are no-ops on-chain, meaning the owner could never actually lock or unlock bets.
The contract was mislabeled as 'EtherDice' in several academic smart contract analysis datasets. The actual contract name in the source code is HonestDice.
122 ETH remains locked in the contract. The deployer's key appears to be lost.
Source Verified
Exact creation (1,992 bytes) + runtime (1,927 bytes) match. soljson v0.1.1, optimizer enabled.
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 HonestDice {
event Bet(address indexed user, uint blocknum, uint256 amount, uint chance);
event Won(address indexed user, uint256 amount, uint chance);
struct Roll {
uint256 value;
uint chance;
uint blocknum;
bytes32 secretHash;
bytes32 serverSeed;
}
uint betsLocked;
address owner;
address feed;
uint256 minimumBet = 10 * 1000000000000000000; // 10 Ether
uint256 constant maxPayout = 5; // 5% of bankroll
uint constant seedCost = 100000000000000000; // This is the cost of supplyin the server seed, deduct it;
mapping (address => Roll) rolls;
uint constant timeout = 20; // 5 Minutes
function HonestDice() {
owner = msg.sender;
feed = msg.sender;
}
function roll(uint chance, bytes32 secretHash) {
if (chance < 1 || chance > 255 || msg.value < minimumBet || calcWinnings(msg.value, chance) > getMaxPayout() || betsLocked != 0) {
msg.sender.send(msg.value); // Refund
return;
}
rolls[msg.sender] = Roll(msg.value, chance, block.number, secretHash, 0);
Bet(msg.sender, block.number, msg.value, chance);
}
function serverSeed(address user, bytes32 seed) {
// The server calls this with a random seed
if (msg.sender != feed) return;
if (rolls[user].serverSeed != 0) return;
rolls[user].serverSeed = seed;
}
function hashTo256(bytes32 hash) constant returns (uint _r) {
// Returns a number between 0 - 255 from a hash
return uint(hash) & 0xff;
}
function hash(bytes32 input) constant returns (uint _r) {
// Simple sha3 hash. Not to be called via the blockchain
return uint(sha3(input));
}
function isReady() constant returns (bool _r) {
return isReadyFor(msg.sender);
}
function isReadyFor(address _user) constant returns (bool _r) {
Roll r = rolls[_user];
if (r.serverSeed == 0) return false;
return true;
}
function getResult(bytes32 secret) constant returns (uint _r) {
// Get the result number of the roll
Roll r = rolls[msg.sender];
if (r.serverSeed == 0) return;
if (sha3(secret) != r.secretHash) return;
return hashTo256(sha3(secret, r.serverSeed));
}
function didWin(bytes32 secret) constant returns (bool _r) {
// Returns if the player won or not
Roll r = rolls[msg.sender];
if (r.serverSeed == 0) return;
if (sha3(secret) != r.secretHash) return;
if (hashTo256(sha3(secret, r.serverSeed)) < r.chance) { // Winner
return true;
}
return false;
}
function calcWinnings(uint256 value, uint chance) constant returns (uint256 _r) {
// 1% house edge
return (value * 98 / 100) * 256 / chance;
}
function getMaxPayout() constant returns (uint256 _r) {
return this.balance * maxPayout / 100;
}
function claim(bytes32 secret) {
Roll r = rolls[msg.sender];
if (r.serverSeed == 0) return;
if (sha3(secret) != r.secretHash) return;
if (hashTo256(sha3(secret, r.serverSeed)) < r.chance) { // Winner
msg.sender.send(calcWinnings(r.value, r.chance));
Won(msg.sender, r.value, r.chance);
}
delete rolls[msg.sender];
}
function canClaimTimeout() constant returns (bool _r) {
Roll r = rolls[msg.sender];
if (r.serverSeed != 0) return false;
if (r.value <= 0) return false;
if (block.number < r.blocknum + timeout) return false;
return true;
}
function claimTimeout() {
// Get your monies back if the server isn't responding with a seed
if (!canClaimTimeout()) return;
Roll r = rolls[msg.sender];
msg.sender.send(r.value);
delete rolls[msg.sender];
}
function getMinimumBet() constant returns (uint _r) {
return minimumBet;
}
function getBankroll() constant returns (uint256 _r) {
return this.balance;
}
function getBetsLocked() constant returns (uint _r) {
return betsLocked;
}
function setMinimumBet(uint256 _minimumBet) {
if (msg.sender != owner) return;
minimumBet = _minimumBet;
}
function setFeed(address newFeed) {
if (msg.sender != owner) return;
feed = newFeed;
}
function lockBetsForWithdraw() {
if (msg.sender != owner) return;
uint betsLocked = block.number;
}
function unlockBets() {
if (msg.sender != owner) return;
uint betsLocked = 0;
}
function withdraw(uint amount) {
if (msg.sender != owner) return;
if (betsLocked == 0 || block.number < betsLocked + 5760) return;
owner.send(amount);
}
}