One hundred numbered pet rocks sold from a rising price curve in December 2017, offering ownership and nothing else.
Historical Significance
EtherRock is the clearest early statement that a collectible on Ethereum needs no utility whatsoever. The project said outright that the rocks do nothing beyond letting you own a rock, and it turned out that this was sufficient. Years after the supply sold out the collection was rediscovered and revalued dramatically, which made it a recurring reference point in arguments about whether digital scarcity alone can carry a price. Because the contract predates ERC721 and implements no standard interface, none of that trading could happen on ordinary marketplaces without a wrapper, and one now holds 23 of the 100 rocks.
Context
Deployed 26 December 2017 at block 4,797,962 by 0xa7ab2ea319948818b62e84f0947f33651c25c25c, compiled with solc 0.4.17 and verified on Sourcify as EtherRock. It belongs to the 2017 wave of collectibles that arrived before ERC721 was finished, alongside Curio Cards, CryptoPunks, MoonCatRescue and CryptoKitties, all of which shipped earlier that same year. Ownership is readable only through the contract's own getRockInfo and rocks accessors, which is why the archive reads it directly rather than through any token standard.
Key Facts
Description
EtherRock keeps a rocks mapping of its own, holding an owner, a sale flag, a price and a sold count for each of the 100 rocks, ids 0 through 99. Trading happens entirely inside the contract through buyRock, sellRock, dontSellRock and giftRock, with buyRock requiring the exact asking price and forwarding it to the previous owner. New rocks were released one at a time along a rising curve, priced at 0.001 ether plus the square of the rock number times 0.001 ether, so each successive rock cost more than the last until the hundredth closed the supply for good. The constructor is worth reading: it seeds the first eleven rocks with owners and sale histories already attached, which means this contract inherited the state of an earlier deployment rather than starting empty.
Byzantium Era
First Metropolis hard fork. Added zk-SNARK precompiles, REVERT opcode, and staticcall.
Bytecode Overview
Verified Source Available
This contract has verified source code on Etherscan.
Show source code (Solidity)
pragma solidity ^0.4.2;
// This is a revised version of the original EtherRock contract 0x37504ae0282f5f334ed29b4548646f887977b7cc with all the rock owners and rock properties the same at the time this new contract is being deployed.
// The original contract at 0x37504ae0282f5f334ed29b4548646f887977b7cc had a simple mistake in the buyRock() function. The line:
// require(rocks[rockNumber].currentlyForSale = true);
// Had to have double equals, as follows:
// require(rocks[rockNumber].currentlyForSale == true);
// Therefore in the original contract, anyone could buy anyone elses rock for the same price the owner purchased it for (regardless of whether the owner chose to sell it or not)
contract EtherRock {
struct Rock {
address owner;
bool currentlyForSale;
uint price;
uint timesSold;
}
mapping (uint => Rock) public rocks;
mapping (address => uint[]) public rockOwners;
uint public latestNewRockForSale;
address owner;
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function EtherRock() {
latestNewRockForSale = 11;
rocks[0].owner = 0x789c778b340f17eb046a5a8633e362468aceeff6;
rocks[0].currentlyForSale = true;
rocks[0].price = 10000000000000000000;
rocks[0].timesSold = 2;
rockOwners[0x789c778b340f17eb046a5a8633e362468aceeff6].push(0);
rocks[1].owner = 0x9a643a42748243f80243a65666146a2e1bd5c6aa;
rocks[1].currentlyForSale = false;
rocks[1].price = 2000000000000000;
rocks[1].timesSold = 3;
rockOwners[0x9a643a42748243f80243a65666146a2e1bd5c6aa].push(1);
rocks[2].owner = 0x5d5d6543d73066e69424ce2756cc34cbfe4c368c;
rocks[2].currentlyForSale = false;
rocks[2].price = 5000000000000000;
rocks[2].timesSold = 1;
rockOwners[0x5d5d6543d73066e69424ce2756cc34cbfe4c368c].push(2);
rocks[3].owner = 0xe34501580dc9591211afc7c13f16ddf591c87cde;
rocks[3].currentlyForSale = true;
rocks[3].price = 1000000000000000000;
rocks[3].timesSold = 1;
rockOwners[0xe34501580dc9591211afc7c13f16ddf591c87cde].push(3);
rocks[4].owner = 0x93cdb0a93fc36f6a53ed21ecf6305ab80d06beca;
rocks[4].currentlyForSale = true;
rocks[4].price = 1000000000000000000;
rocks[4].timesSold = 1;
rockOwners[0x93cdb0a93fc36f6a53ed21ecf6305ab80d06beca].push(4);
rocks[5].owner = 0x9467d05ee1c90010a657e244f626194168596583;
rocks[5].currentlyForSale = true;
rocks[5].price = 42000000000000000000;
rocks[5].timesSold = 1;
rockOwners[0x9467d05ee1c90010a657e244f626194168596583].push(5);
rocks[6].owner = 0xb6e2e5e06397dc522db58faa064f74c95322b58e;
rocks[6].currentlyForSale = true;
rocks[6].price = 60000000000000000;
rocks[6].timesSold = 1;
rockOwners[0xb6e2e5e06397dc522db58faa064f74c95322b58e].push(6);
rocks[7].owner = 0xbcddcf35880443b6a1f32f07009097e95c327716;
rocks[7].currentlyForSale = true;
rocks[7].price = 100000000000000000;
rocks[7].timesSold = 1;
rockOwners[0xbcddcf35880443b6a1f32f07009097e95c327716].push(7);
rocks[8].owner = 0xf7007f39a41d87c669bd9beadc3d5cc2ef5a32be;
rocks[8].currentlyForSale = false;
rocks[8].price = 65000000000000000;
rocks[8].timesSold = 1;
rockOwners[0xf7007f39a41d87c669bd9beadc3d5cc2ef5a32be].push(8);
rocks[9].owner = 0xf7007f39a41d87c669bd9beadc3d5cc2ef5a32be;
rocks[9].currentlyForSale = true;
rocks[9].price = 10000000000000000000;
rocks[9].timesSold = 1;
rockOwners[0xf7007f39a41d87c669bd9beadc3d5cc2ef5a32be].push(9);
rocks[10].owner = 0xd17e2bfe196470a9fefb567e8f5992214eb42f24;
rocks[10].currentlyForSale = true;
rocks[10].price = 200000000000000000;
rocks[10].timesSold = 1;
rockOwners[0xd17e2bfe196470a9fefb567e8f5992214eb42f24].push(10);
rocks[11].currentlyForSale = true;
rocks[11].price = 122000000000000000;
owner = msg.sender;
}
function getRockInfo (uint rockNumber) returns (address, bool, uint, uint) {
return (rocks[rockNumber].owner, rocks[rockNumber].currentlyForSale, rocks[rockNumber].price, rocks[rockNumber].timesSold);
}
function rockOwningHistory (address _address) returns (uint[]) {
return rockOwners[_address];
}
function buyRock (uint rockNumber) payable {
require(rocks[rockNumber].currentlyForSale == true);
require(msg.value == rocks[rockNumber].price);
rocks[rockNumber].currentlyForSale = false;
rocks[rockNumber].timesSold++;
if (rockNumber != latestNewRockForSale) {
rocks[rockNumber].owner.transfer(rocks[rockNumber].price);
}
rocks[rockNumber].owner = msg.sender;
rockOwners[msg.sender].push(rockNumber);
if (rockNumber == latestNewRockForSale) {
if (rockNumber != 99) {
latestNewRockForSale++;
rocks[latestNewRockForSale].price = 10**15 + (latestNewRockForSale**2 * 10**15);
rocks[latestNewRockForSale].currentlyForSale = true;
}
}
}
function sellRock (uint rockNumber, uint price) {
require(msg.sender == rocks[rockNumber].owner);
require(price > 0);
rocks[rockNumber].price = price;
rocks[rockNumber].currentlyForSale = true;
}
function dontSellRock (uint rockNumber) {
require(msg.sender == rocks[rockNumber].owner);
rocks[rockNumber].currentlyForSale = false;
}
function giftRock (uint rockNumber, address receiver) {
require(msg.sender == rocks[rockNumber].owner);
rocks[rockNumber].owner = receiver;
rockOwners[receiver].push(rockNumber);
}
function() payable {
}
function withdraw() onlyOwner {
owner.transfer(this.balance);
}
}External Links
Wrapped as
Later contracts that represent this one. Holding one of these counts as holding the original on a collector card.