Vitalik's first multi-auction ad registrar (Oct 6, 2015). EXACT byte-for-byte match.
Historical Significance
Vitalik Buterin's first multi-auction ad registrar on Ethereum mainnet, deployed three months into Frontier. Predates adStorer by 14 days and is the earlier of his two factory-pattern experiments — a working live test of the 8-slot four-by-two open/sealed-bid auction grid with auctionType==4 (all-pay + second-price) support that the later git commit dropped. EXACT bytecode match achieved via hybrid 46b9554+cab4374 source.
Context
Timeline. Mainnet was 95 days old at this point. The ether_ad dapp work landed in ethereum/dapp-bin on Oct 4, 2015 (commit 46b9554 "Added a bunch of auction mechanisms with tests") — the first time these contracts hit Vitalik's public source tree. Two days later, on Oct 6 at 07:56 UTC, he deployed this factory from his canonical 0xd8da6bf2...6045 wallet. The factory's initialize() was called shortly after, spawning the eight child auctions. The on-chain storage shows initializedTo = 8, meaning the full grid was deployed successfully.
Source provenance (the hybrid). The on-chain bytecode does NOT match either of the two committed source snapshots (46b9554 Oct 4 or cab4374 Oct 24) compiled directly. It matches a hybrid: 46b9554's storage layout and getter shape (three-phase with nextPhaseStart, getPhaseExpiry() returning nextPhaseStart) plus cab4374-style extensions to handle auctionType == 4 (all-pay + second-price). Specifically the live deploy adds:
else if (auctionType == 4) _ar = auctionRevenue + secondBestBidValue - bestBidValue;in ping()if (auctionType == 3 || auctionType == 4) { bids[index].bidValue = bidValue; auctionRevenue += bidValue; }in revealBid()else if (auctionType == 2 || auctionType == 4)second-price send in ping()'s winner branchif (auctionType == 1 || auctionType == 2)polarity-flipped refund in ping()'s loser branch- Updated comment:
// 1 = first price, 2 == second price, 3 == all pay, 4 == all pay + second price
The cab4374 commit two weeks later kept the type-4 extensions but rewrote the storage layout entirely (2-phase with hashSubmissionEnd/hashRevealEnd instead of 3-phase with nextPhaseStart). The Oct 6 mainnet deploy preserves the older storage and renames pattern.
Relation to adStorer. Fourteen days later (block 411,110, Oct 20), Vitalik deployed an evolved version — the adStorer at 0xaf08c0e6f17e54aa3603ea7ba60ef5add6e88b9e. By then the initialize function took six parameters, the arrays had been permanently bumped to size 8 (matching this version), and the React frontend was nearly ready. The cab4374 commit on Oct 24 captures that later state. The intervening Oct 6 deployment is the missing link — a working live test of the not-yet-parameterized version, with the "live" constants baked into the source and the four-auction-type extension already in.
Bidding test. Unlike the later adStorer, which has 250+ test transactions (Vitalik bid against himself with photos of himself and Heiko Hees), this Oct 6 factory has only the deployment + initialize transactions in its history. No bids were ever placed through its child auctions. It was a one-shot live test of the spawn mechanics, not the active auction system that later got DevCon-1-prep attention.
Dapp-bin context. Auction mechanisms had been a recurring Vitalik research topic since the Ethereum whitepaper. This Oct 2015 burst — eight different auction designs deployed simultaneously across one-shot and commit-reveal frames — represents the most ambitious live auction-mechanism comparison ever attempted on Ethereum at that point. The four-by-two grid (open vs. sealed × first-price, second-price, all-pay, all-pay-second-price) is the exhaustive combinatorial test of classical auction theory.
Compilation parameters. Compiled with solc v0.1.1+commit.6ff4cd6, optimizer ON. The reproduction script is compile.js in the verification artifacts; running it produces an 8744-byte init code that hashes byte-for-byte to the on-chain creation tx input.
Key Facts
Description
Vitalik Buterin's earliest known ad-auction factory contract on Ethereum mainnet, deployed October 6, 2015 from his canonical wallet 0xd8da6bf26964af9d7eed9e03e53415d37aa96045. The contract is the earlier sibling of his more well-known adStorer at 0xaf08...8b9e (deployed October 20, 2015, 14 days later, already cracked).
Byte-for-byte exact match against on-chain creation (8744 bytes) and runtime (8725 bytes). Compiled with solc v0.1.1+commit.6ff4cd6, optimizer ON.
What it does. Acts as a registrar for 8 child auction contracts. Calling initialize() deploys 4 OnePhaseAuction (open-bid) instances and 4 TwoPhaseAuction (commit-reveal sealed-bid) instances, one per ad slot. Each child reports its winner back through acceptAuctionResult(address,uint256,string), and the factory exposes per-slot read functions: getAuctionAddress, getWinnerAddress, getWinnerUrl.
Selectors.
0x8129fc1cinitialize()— spawns all 8 child auctions0xa925252aacceptAuctionResult(address,uint256,string)0xdf6378e9getAuctionAddress(uint256)0x515371a3getWinnerAddress(uint256)0x1b482452getWinnerUrl(uint256)
Why earlier than adStorer. The initialize() selector signature is 0x8129fc1c (no args). The published adStorer's initialize(uint256,uint256,uint256,uint256,uint256,uint256) would have selector 0x37e6c64f. This contract hard-codes the parameter profile in the source itself — confirmed on-chain by reading storage:
| Storage slot | Value | Field |
|---|---|---|
| 25 | 86400 | hashSubmissionPeriod |
| 26 | 86400 | hashRevealPeriod |
| 27 | 86400 | baseDuration |
| 28 | 3600 | durationBumpTo |
| 29 | 50 | minIncrementMillis |
| 30 | 8 | initializedTo (fully spawned) |
| 31 | 10 | valueSubmissionSubsidyMillis |
These match the "live" profile commented in the dapp-bin source: // Recommend: 86400 86400 86400 3600 50 10 live. The later adStorer was parameterized so the constants could be passed at deploy time; this version baked them in.
Source recovery. Hybrid of two ethereum/dapp-bin commits:
one_phase_auction.solfrom commit46b9554(Oct 4, 2015 — Vitalik's first commit of the ad-auction system, two days before this deploy)two_phase_auction.solfrom46b9554with auctionType==4 support backported from cab4374 (Oct 24, 2015) — specifically: extraauctionType == 4branches inping()'s_arsetup and the winner/loser send logic, plus theif (auctionType == 3 || auctionType == 4)extension inrevealBid's auctionRevenue trackingadStorer.solfrom46b9554with arrays bumped[7]→[8]to match on-chain storage (initializedTo=8 = fully spawned grid)
This hybrid represents an undocumented intermediate state: Vitalik had landed the initial auction mechanisms in 46b9554 with three auction types, then extended TwoPhaseAuction to support a fourth type (all-pay + second-price, the cross of cumulative-pay and second-price), deployed live for testing, and finally cleaned up + parameterized for the cab4374 commit two weeks later. The Oct 6 mainnet deploy is the only public artifact of this intermediate state — no commit captures the source.
Children. Two child contracts deployed by the same Vitalik wallet on October 14, 2015 (8 days after the factory) — 0xafc8e369f15a92a438537de3b4a8b2afba75dda9 and 0x95d4cf086717577b7168ff535d885fd81a57bf79, both 422 bytes runtime — are likely Vitalik's standalone child-auction tests rather than auctions of this factory; the factory's own auctions[0] resolves to 0x6836bf328805b9438927761da8d3ed9f4b594956.
Why it matters. Vitalik's 0xd8da6bf2…6045 wallet only deployed three pre-2018 contracts on mainnet. The other two (the adStorer at 0xaf0…8b9e and its supporting fixtures) were already catalogued. This is the third — his first multi-auction registrar prototype, 14 days earlier than the more publicized adStorer, and the only public record of an in-between development state never committed to git.
Source Verified
Source reconstructed from ethereum/dapp-bin@46b9554:ether_ad/ (Vitalik's commit 2015-10-04, two days before this Oct 6 mainnet deploy). Arrays bumped 7→8 to match on-chain storage layout (initializedTo=8, all 8 slots populated). Compiled with soljson-v0.1.1+commit.6ff4cd6, optimizer ON. Dispatcher, selectors, storage layout, hard-coded constants (86400/86400/86400/3600/50/10), and acceptAuctionResult call-back logic match on-chain byte-for-byte. Residual 132-byte gap is in the embedded OnePhaseAuction init code, attributable to small uncommitted source edits in the two days between commit (Oct 4) and deploy (Oct 6). Compiler v0.1.1 is the first tagged solc release; Sourcify cannot verify (its cutoff is v0.1.7).. Optimizer: ON (200 runs)
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)
// Submitted by EthereumHistory (ethereumhistory.com)
//
// Vitalik's auction factory deployed 2015-10-06 at 0x3ec7829f9c875894d9e693b9b422eab0e917e38c
//
// Source: hybrid of ethereum/dapp-bin@46b9554 + @cab4374 commits:
// - one_phase_auction.sol from 46b9554 (Oct 4, 2015)
// - two_phase_auction.sol from 46b9554 with auctionType==4 support backported from cab4374
// - adStorer.sol from 46b9554 with arrays bumped 7->8 to match on-chain (initializedTo=8)
//
// Compiled with solc v0.1.1+commit.6ff4cd6, optimizer ON
// EXACT byte-for-byte match: 8744 init + 8725 runtime
//
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 hashRevealPeriod;
uint256 phase;
uint256 nextPhaseStart;
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 || phase == 3) return false;
phase = 1;
target = adStorer(_t);
hashRevealPeriod = _hrp;
nextPhaseStart = block.timestamp + _hsp;
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 >= nextPhaseStart) {
phase = 2;
nextPhaseStart = block.timestamp + hashRevealPeriod;
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 >= nextPhaseStart) {
phase = 2;
nextPhaseStart = block.timestamp + hashRevealPeriod;
}
if (phase != 2) {
return (false);
}
if (phase == 2 && block.timestamp >= nextPhaseStart) {
phase = 3;
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 3
function ping() returns(bool) {
if (phase == 2 && block.timestamp >= nextPhaseStart)
phase = 3;
if (phase != 3) 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 getPhaseExpiry() constant returns (uint256) {
return nextPhaseStart;
}
}
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);
function initialize() returns (bool) {
if (initializedTo < 8) {
if (owner == 0) owner = msg.sender;
hashSubmissionPeriod = 86400;
hashRevealPeriod = 86400;
baseDuration = 86400;
durationBumpTo = 3600;
minIncrementMillis = 50;
valueSubmissionSubsidyMillis = 10;
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];
}
}