One of the eight ether_ad advertising slots, run as a sealed bid auction
Historical Significance
A sealed bid commit and reveal auction running on Ethereum in October 2015, alongside open ascending auctions in the same dapp, which makes the pair an early practical comparison of the two mechanisms.
Context
Deployed during the Frontier era, when dapp-bin was the reference collection of worked Solidity examples.
Key Facts
Description
TwoPhaseAuction sells one advertising slot in ether_ad, the advertising dapp in ethereum/dapp-bin. Bidding runs in two phases: bidders first submit only the hash of a bid, then reveal the value in a second window, so no one can see what anyone else offered while bidding is open. Revealing pays a small subsidy back out of the auction proceeds. When it closes the contract reports the winner and the winning URL to the adStorer that created it and restarts for the next round. This is the revised version, which stores the two deadlines as absolute end times and exposes getHashSubmissionEnd and getHashRevealEnd. It was created by that store rather than by a transaction, so it has no creation bytecode of its own. Recovered from ethereum/dapp-bin. Solc v0.1.1 with the optimizer on reproduces the parent store byte for byte, embedded copy included, and v0.1.4 emits an identical runtime, which is the build recorded on Etherscan.
Frontier Era
The initial release of Ethereum. A bare-bones implementation for technical users.
Bytecode Overview
Verified Source Available
This contract has verified source code on Etherscan.
Show source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
contract OnePhaseAuction {
adStorer target;
address owner;
uint256 phase;
uint256 auctionEnd;
uint256 durationBumpTo;
uint256 minIncrementMillis;
uint256 mostRecentAuctionStart;
struct Bid {
uint256 bidValue;
string metadata;
address bidder;
}
Bid[999999999999999999999] bids;
uint256 bestBidIndex;
uint256 bestBidValue;
uint256 secondBestBidValue;
uint256 nextBidIndex;
uint256 totalRevenue;
// Bitmask: +1 if bids cumulative +0 if independent, +2 if all-pay +0 if lead pays
uint256 auctionType;
event BidSubmitted(uint256 index, uint256 bidValue, string metadata, address indexed bidder);
event BidIncreased(uint256 index, uint256 bidValue, uint256 cumValue, string metadata, address indexed bidder);
event AuctionWinner(uint256 index, uint256 bidValue, string metadata, address indexed bidder);
event AuctionFinalized(uint256 revenue);
event AuctionInitialized();
function OnePhaseAuction() {
owner = msg.sender;
}
// Initialize the auction
function initialize(address _t, uint256 _baseDuration, uint256 _durationBumpTo, uint256 _minIncrementMillis, uint256 _tp) returns (bool) {
if (msg.sender != owner) return false;
if (phase == 1 || phase == 2) return false;
phase = 1;
target = adStorer(_t);
auctionEnd = block.timestamp + _baseDuration;
durationBumpTo = _durationBumpTo;
minIncrementMillis = _minIncrementMillis;
nextBidIndex = 0;
bestBidValue = 0;
bestBidIndex = 0;
auctionType = _tp;
mostRecentAuctionStart = block.number;
AuctionInitialized();
return true;
}
// Place one's bid
function bid(string metadata) returns (int256) {
if (phase != 1) {
msg.sender.send(msg.value);
return (-1);
}
if (phase == 1 && block.timestamp >= auctionEnd) {
phase = 2;
msg.sender.send(msg.value);
return (-1);
}
if (msg.value * 1000 < bestBidValue * (1000 + minIncrementMillis)) {
msg.sender.send(msg.value);
return (-1);
}
if (msg.value > bestBidValue) {
bestBidValue = msg.value;
bestBidIndex = nextBidIndex;
}
bids[nextBidIndex].bidValue = msg.value;
bids[nextBidIndex].metadata = metadata;
bids[nextBidIndex].bidder = msg.sender;
BidSubmitted(nextBidIndex, msg.value, metadata, msg.sender);
nextBidIndex = nextBidIndex + 1;
if (auctionEnd - block.timestamp < durationBumpTo)
auctionEnd = block.timestamp + durationBumpTo;
if ((auctionType & 2) == 2)
totalRevenue += msg.value;
return int256(nextBidIndex) - 1;
}
// Increase one's bid
function increaseBid(uint256 index) returns (bool) {
if ((auctionType & 1) == 0) {
msg.sender.send(msg.value);
return (false);
}
if (phase != 1) {
msg.sender.send(msg.value);
return (false);
}
if (phase == 1 && block.timestamp >= auctionEnd) {
msg.sender.send(msg.value);
phase = 2;
return (false);
}
if ((bids[index].bidValue + msg.value) * 1000 < bestBidValue * (1000 + minIncrementMillis)) {
msg.sender.send(msg.value);
return(false);
}
if (index >= nextBidIndex) {
msg.sender.send(msg.value);
return false;
}
bids[index].bidValue += msg.value;
if (bids[index].bidValue > bestBidValue) {
bestBidValue = bids[index].bidValue;
bestBidIndex = index;
}
if ((auctionType & 2) == 2)
totalRevenue += msg.value;
BidIncreased(index, msg.value, bids[index].bidValue, bids[index].metadata, bids[index].bidder);
if (auctionEnd - block.timestamp < durationBumpTo)
auctionEnd = block.timestamp + durationBumpTo;
return(true);
}
// Clean up during phase 3
function ping() returns(bool) {
if (phase == 1 && block.timestamp >= auctionEnd)
phase = 2;
if (phase != 2) return(false);
uint _nbi = nextBidIndex;
while (msg.gas > 100000 && _nbi > 0) {
_nbi -= 1;
if (_nbi == bestBidIndex) {
}
else {
if ((auctionType & 2) == 0)
bids[_nbi].bidder.send(bids[_nbi].bidValue);
bids[_nbi].bidValue = 0;
}
}
nextBidIndex = _nbi;
if (_nbi == 0) {
phase = 0;
bool success;
if (bestBidValue > 0) {
AuctionWinner(bestBidIndex, bestBidValue, bids[bestBidIndex].metadata, bids[bestBidIndex].bidder);
success = target.acceptAuctionResult(bids[bestBidIndex].bidder, bestBidValue, bids[bestBidIndex].metadata);
}
else {
success = target.acceptAuctionResult(0, 0, "");
}
if ((auctionType & 2) == 2)
AuctionFinalized(totalRevenue);
else
AuctionFinalized(bestBidValue);
if (!success) { while (1 == 1) { _nbi = _nbi; } }
return(true);
}
return(false);
}
function setOwner(address newOwner) {
if (owner == msg.sender) owner = newOwner;
}
function withdraw() {
if (msg.sender == owner) msg.sender.send(this.balance);
}
function getPhase() constant returns (uint256) {
return phase;
}
function getMostRecentAuctionStart() constant returns (uint256) {
return mostRecentAuctionStart;
}
function getPhaseExpiry() constant returns (uint256) {
return auctionEnd;
}
}
contract AuctionResultAcceptor {
function acceptAuctionResult(address winner, uint256 value, string metadata) { }
}
contract TwoPhaseAuction {
adStorer target;
address owner;
uint256 phase;
uint256 hashSubmissionEnd;
uint256 hashRevealEnd;
uint256 mostRecentAuctionStart;
uint256 valueSubmissionSubsidyMillis;
struct Bid {
bytes32 bidValueHash;
uint256 bidValue;
uint256 valueSubmitted;
string metadata;
address bidder;
}
Bid[999999999999999999999] bids;
uint256 bestBidIndex;
uint256 bestBidValue;
uint256 secondBestBidValue;
uint256 nextBidIndex;
uint256 totalValueSubmitted;
uint256 auctionRevenue;
// 1 = first price, 2 == second price, 3 == all pay, 4 == all pay + second price
uint256 auctionType;
event BidCommitted(uint256 index, bytes32 bidValueHash, string metadata, address indexed bidder);
event BidRevealed(uint256 index, uint256 bidValue, string metadata, address indexed bidder);
event AuctionWinner(uint256 index, uint256 bidValue, string metadata, address indexed bidder);
event AuctionFinalized(uint256 revenue);
event AuctionInitialized();
function TwoPhaseAuction() {
owner = msg.sender;
}
// Initialize the auction
function initialize(address _t, uint256 _hsp, uint256 _hrp, uint256 _vssm, uint256 _tp) returns (bool) {
if (msg.sender != owner) return false;
if (phase == 1 || phase == 2) return false;
phase = 1;
target = adStorer(_t);
hashSubmissionEnd = block.timestamp + _hsp;
hashRevealEnd = block.timestamp + _hsp + _hrp;
valueSubmissionSubsidyMillis = _vssm;
nextBidIndex = 0;
bestBidValue = 0;
secondBestBidValue = 0;
totalValueSubmitted = 0;
auctionRevenue = 0;
auctionType = _tp;
mostRecentAuctionStart = block.number;
AuctionInitialized();
return true;
}
// Commit one's bid. This also entails sending an amount of ether at least
// equal to, but potentially more than, one's bid; if you send a greater
// amount than the difference between the submission and your actual bid
// will be refunded to you (even in all-pay auctions). This protects bid
// privacy.
function commitBid(bytes32 bidValueHash, string metadata) returns (int256) {
if (phase != 1) {
msg.sender.send(msg.value);
return (-1);
}
if (phase == 1 && block.timestamp >= hashSubmissionEnd) {
msg.sender.send(msg.value);
return (-1);
}
bids[nextBidIndex].bidValueHash = bidValueHash;
bids[nextBidIndex].valueSubmitted = msg.value;
bids[nextBidIndex].metadata = metadata;
bids[nextBidIndex].bidder = msg.sender;
BidCommitted(nextBidIndex, bidValueHash, metadata, msg.sender);
nextBidIndex = nextBidIndex + 1;
totalValueSubmitted += msg.value;
return int256(nextBidIndex) - 1;
}
// Reveal one's bid
function revealBid(uint256 index, uint256 bidValue, bytes32 nonce) returns (bool) {
if (phase == 1 && block.timestamp >= hashRevealEnd) {
phase = 2;
}
if (phase != 1) {
return (false);
}
if (phase == 1 && block.timestamp < hashSubmissionEnd) {
return (false);
}
if (index >= nextBidIndex)
return false;
if (bidValue > bids[index].valueSubmitted)
return false;
if (sha3(bidValue, nonce) != bids[index].bidValueHash)
return false;
if (bidValue > bestBidValue) {
secondBestBidValue = bestBidValue;
bestBidValue = bidValue;
bestBidIndex = index;
}
else if (bidValue > secondBestBidValue) {
secondBestBidValue = bidValue;
}
// Only need to keep track of bid values for all-pay auctions
if (auctionType == 3 || auctionType == 4) {
bids[index].bidValue = bidValue;
auctionRevenue += bidValue;
}
BidRevealed(index, bidValue, bids[index].metadata, bids[index].bidder);
return true;
}
// Clean up during phase 2
function ping() returns(bool) {
if (phase == 1 && block.timestamp >= hashRevealEnd)
phase = 2;
if (phase != 2) return(false);
uint _nbi = nextBidIndex;
uint _ar;
if (auctionType == 1) _ar = bestBidValue;
else if (auctionType == 2) _ar = secondBestBidValue;
else if (auctionType == 3) _ar = auctionRevenue;
else if (auctionType == 4) _ar = auctionRevenue + secondBestBidValue - bestBidValue;
while (msg.gas > 500000 && _nbi > 0) {
_nbi -= 1;
uint256 subsidy = bids[_nbi].valueSubmitted * _ar * valueSubmissionSubsidyMillis / totalValueSubmitted / 1000;
if (_nbi == bestBidIndex) {
// First price auction or all-pay auction: take winner's bid at its own value
if (auctionType == 1 || auctionType == 3)
bids[_nbi].bidder.send(bids[_nbi].valueSubmitted - bestBidValue + subsidy);
// Second price auction: take winner's bid at second highest value
else if (auctionType == 2 || auctionType == 4)
bids[_nbi].bidder.send(bids[_nbi].valueSubmitted - secondBestBidValue + subsidy);
}
else {
// First price or second price auction: refund everyone else's bids
if (auctionType == 1 || auctionType == 2)
bids[_nbi].bidder.send(bids[_nbi].valueSubmitted + subsidy);
// All-pay auction: don't refund everyone else's bids
else
bids[_nbi].bidder.send(bids[_nbi].valueSubmitted - bids[_nbi].bidValue + subsidy);
bids[_nbi].bidValueHash = 0;
}
}
nextBidIndex = _nbi;
if (_nbi == 0) {
phase = 0;
bool success;
if (bestBidValue > 0) {
AuctionWinner(bestBidIndex, bestBidValue, bids[bestBidIndex].metadata, bids[bestBidIndex].bidder);
success = target.acceptAuctionResult(bids[bestBidIndex].bidder, bestBidValue, bids[bestBidIndex].metadata);
}
else {
success = target.acceptAuctionResult(0, 0, "");
}
AuctionFinalized(_ar);
if (!success) { while (1 == 1) { _nbi = _nbi; } }
return(true);
}
return(false);
}
function setOwner(address newOwner) {
if (owner == msg.sender) owner = newOwner;
}
function withdraw() {
if (msg.sender == owner) msg.sender.send(this.balance);
}
function getPhase() constant returns (uint256) {
return phase;
}
function getMostRecentAuctionStart() constant returns (uint256) {
return mostRecentAuctionStart;
}
function getHashSubmissionEnd() constant returns (uint256) {
return hashSubmissionEnd;
}
function getHashRevealEnd() constant returns (uint256) {
return hashRevealEnd;
}
}
contract adStorer {
string[8] urls;
address[8] winners;
address owner;
address[8] auctions;
uint256 hashSubmissionPeriod;
uint256 hashRevealPeriod;
uint256 baseDuration;
uint256 durationBumpTo;
uint256 minIncrementMillis;
uint256 initializedTo;
uint256 valueSubmissionSubsidyMillis;
event GasRemaining(uint256 g, uint256 i);
// Recommend: 86400 86400 86400 3600 50 10 live
// Recommend: 240 240 240 120 50 10 test
function initialize(uint256 _hsp, uint256 _hrp, uint256 _bdur, uint256 _dbt, uint256 _mim, uint256 _vssm) returns (bool) {
if (initializedTo < 8) {
if (owner == 0) owner = msg.sender;
hashSubmissionPeriod = _hsp;
hashRevealPeriod = _hrp;
baseDuration = _bdur;
durationBumpTo = _dbt;
minIncrementMillis = _mim;
valueSubmissionSubsidyMillis = _vssm;
for (uint256 i = initializedTo; i < 8 && msg.gas > 1100000; i++) {
GasRemaining(msg.gas, i);
if (i < 4) {
auctions[i] = new OnePhaseAuction();
OnePhaseAuction(auctions[i]).initialize(this, baseDuration, durationBumpTo, minIncrementMillis, i);
}
else {
auctions[i] = new TwoPhaseAuction();
TwoPhaseAuction(auctions[i]).initialize(this, hashSubmissionPeriod, hashRevealPeriod, valueSubmissionSubsidyMillis, i - 3);
}
}
initializedTo = i;
if (initializedTo == 8) return true;
else return false;
}
}
function acceptAuctionResult(address winner, uint256 value, string metadata) returns (bool) {
for (uint256 i = 0; i < 8; i++) {
if (msg.sender == auctions[i]) {
if (winner != 0) {
urls[i] = metadata;
winners[i] = winner;
}
if (i < 4) OnePhaseAuction(msg.sender).initialize(this, baseDuration, durationBumpTo, minIncrementMillis, i);
else TwoPhaseAuction(msg.sender).initialize(this, hashSubmissionPeriod, hashRevealPeriod, valueSubmissionSubsidyMillis, i - 3);
return true;
}
}
return false;
}
function getAuctionAddress(uint256 id) constant returns (address) {
return auctions[id];
}
function getWinnerUrl(uint256 id) constant returns (string) {
return urls[id];
}
function getWinnerAddress(uint256 id) constant returns (address) {
return winners[id];
}
}
External Links
Related contracts
OnePhaseAuction
Same deployerOne of the eight ether_ad advertising slots, run as an open ascending auction
0xfe524f...6bee4aOctober 20, 2015Contract 0x616131...c102bb
Same deployerOne of the eight ether_ad advertising slots, run as an open ascending auction
0x616131...c102bbOctober 20, 2015Contract 0xc4b288...fc02c2
Same deployerOne of the eight ether_ad advertising slots, run as an open ascending auction
0xc4b288...fc02c2October 20, 2015Contract 0x68b3b6...3f2c14
Same deployerOne of the eight ether_ad advertising slots, run as an open ascending auction
0x68b3b6...3f2c14October 20, 2015Contract 0x73337d...0d2f95
Same deployerOne of the eight ether_ad advertising slots, run as a sealed bid auction
0x73337d...0d2f95October 20, 2015TwoPhaseAuction
Same deployerOne of the eight ether_ad advertising slots, run as a sealed bid auction
0xcd9244...556a95October 20, 2015