Hacker Gold (HKG), the token of ether.camp's 2017 virtual accelerator, redeployed with every balance restored after a one character bug in its predecessor.
Historical Significance
This is one of the earliest well documented token bugs on Ethereum, and it became a standard teaching example. The lesson it taught was not about clever attacks: the code was readable, the logic was correct as written, and the flaw was a typographical error that compiles silently and does not look wrong. It is a large part of why later token work leaned on audited base implementations rather than hand written balance arithmetic.
The response is as instructive as the bug. The supply was recomputed off chain, hardcoded into a new constructor, and the token continued at a new address with holders made whole. There was no proxy to upgrade and no admin switch to flip, so a redeployment carrying a snapshot in its constructor was the only repair available. That constraint shaped how the whole ecosystem thought about upgradeability afterwards.
Context
Deployed on 9 January 2017 at block 2,963,564, during a run of HackerGold deployments that week as the problem was found and worked through. An almost identical contract had gone out the day before at 0xa62bdee2f277c2e2c0f46cba96879b263796ee1c, already carrying the same recovery balance.
The one difference between the two is visible on chain and is the reason this address is the one that stuck. The earlier contract kept its supply in a plain state variable with no ERC-20 getter, so calling totalSupply() on it reverts with an invalid jump to this day, and only its own getTotalSupply() answers. This version renames the variable and adds the standard totalSupply() function, which is what exchanges and wallets expect to find. Byte for byte identical code was deployed once more on 18 January.
A separate contract carrying the original uncorrected source was deployed by an unrelated address on 9 January and survives at 0xd7ac7486f7e756df65422752b76060ae0808f6a2, where the flawed transferFrom can still be read.
Token Information
Key Facts
Description
HKG funded ether.camp's virtual accelerator, a hackathon where holders backed teams with tokens rather than votes. It sold at three tiers, 200 then 150 then 100 HKG per ether, and carries three decimals rather than the usual eighteen.
What makes this particular deployment worth reading is its constructor. It does not start empty. It assigns a supply of 16,110,893,000 units and credits the whole of it to a single recovery address, alongside a recorded total of 85,362 ether raised. The comment in the source calls it exactly that, a recovery balance. This contract was not selling anything new: it existed to carry the state of a broken predecessor across to working code.
Read on chain today, totalSupply() still returns 16,110,893,000, unchanged from the constant compiled into the constructor, and getPrice() returns zero because the sale is long closed. The recovery address has since paid out almost all of its holding and retains 43,813,419 units.
The defect being recovered from was a single character. In the predecessor's transferFrom the recipient's balance was updated with '=+' rather than '+=', which Solidity reads as assignment of a positive number rather than addition. The recipient's balance was therefore overwritten with the transferred amount instead of increased by it, so anyone could approve themselves a token and use transferFrom to reset another account's balance to a number of their choosing. No ether was ever at risk, because the token contract held none.
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Spurious Dragon Era
Continued DoS protection. State trie clearing.
Bytecode Overview
Verified Source Available
This contract has verified source code on Etherscan.
Show source code (Solidity)
pragma solidity ^ 0.4 .0;
/*
* Token - is a smart contract interface
* for managing common functionality of
* a token.
*
* ERC.20 Token standard: https://github.com/eth ereum/EIPs/issues/20
*/
contract TokenInterface {
// total amount of tokens
uint totalSupplyVar;
/**
*
* balanceOf() - constant function check concrete tokens balance
*
* @param owner - account owner
*
* @return the value of balance
*/
function balanceOf(address owner) constant returns(uint256 balance);
function transfer(address to, uint256 value) returns(bool success);
function transferFrom(address from, address to, uint256 value) returns(bool success);
/**
*
* approve() - function approves to a person to spend some tokens from
* owner balance.
*
* @param spender - person whom this right been granted.
* @param value - value to spend.
*
* @return true in case of succes, otherwise failure
*
*/
function approve(address spender, uint256 value) returns(bool success);
/**
*
* allowance() - constant function to check how much is
* permitted to spend to 3rd person from owner balance
*
* @param owner - owner of the balance
* @param spender - permitted to spend from this balance person
*
* @return - remaining right to spend
*
*/
function allowance(address owner, address spender) constant returns(uint256 remaining);
function totalSupply() constant returns(uint256 totalSupply) {
return totalSupplyVar;
}
// events notifications
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
pragma solidity ^ 0.4 .2;
/*
* StandardToken - is a smart contract
* for managing common functionality of
* a token.
*
* ERC.20 Token standard:
* https://github.com/eth ereum/EIPs/issues/20
*/
contract StandardToken is TokenInterface {
// token ownership
mapping(address => uint256) balances;
// spending permision management
mapping(address => mapping(address => uint256)) allowed;
function StandardToken() {}
/**
* transfer() - transfer tokens from msg.sender balance
* to requested account
*
* @param to - target address to transfer tokens
* @param value - ammount of tokens to transfer
*
* @return - success / failure of the transaction
*/
function transfer(address to, uint256 value) returns(bool success) {
if (balances[msg.sender] >= value && value > 0) {
// do actual tokens transfer
balances[msg.sender] -= value;
balances[to] += value;
// rise the Transfer event
Transfer(msg.sender, to, value);
return true;
} else {
return false;
}
}
/**
* transferFrom() - used to move allowed funds from other owner
* account
*
* @param from - move funds from account
* @param to - move funds to account
* @param value - move the value
*
* @return - return true on success false otherwise
*/
function transferFrom(address from, address to, uint256 value) returns(bool success) {
if (balances[from] >= value &&
allowed[from][msg.sender] >= value &&
value > 0) {
// do the actual transfer
balances[from] -= value;
balances[to] += value;
// addjust the permision, after part of
// permited to spend value was used
allowed[from][msg.sender] -= value;
// rise the Transfer event
Transfer(from, to, value);
return true;
} else {
return false;
}
}
/**
*
* balanceOf() - constant function check concrete tokens balance
*
* @param owner - account owner
*
* @return the value of balance
*/
function balanceOf(address owner) constant returns(uint256 balance) {
return balances[owner];
}
/**
*
* approve() - function approves to a person to spend some tokens from
* owner balance.
*
* @param spender - person whom this right been granted.
* @param value - value to spend.
*
* @return true in case of succes, otherwise failure
*
*/
function approve(address spender, uint256 value) returns(bool success) {
// now spender can use balance in
// ammount of value from owner balance
allowed[msg.sender][spender] = value;
// rise event about the transaction
Approval(msg.sender, spender, value);
return true;
}
/**
*
* allowance() - constant function to check how mouch is
* permited to spend to 3rd person from owner balance
*
* @param owner - owner of the balance
* @param spender - permited to spend from this balance person
*
* @return - remaining right to spend
*
*/
function allowance(address owner, address spender) constant returns(uint256 remaining) {
return allowed[owner][spender];
}
}
pragma solidity ^ 0.4 .0;
/**
*
* @title Hacker Gold
*
* The official token powering the hack.ether.camp virtual accelerator.
* This is the only way to acquire tokens from startups during the event.
*
* Whitepaper https://hack.ether.camp/whitepaper
*
*/
contract HackerGold is StandardToken {
// Name of the token
string public name = "HackerGold";
// Decimal places
uint8 public decimals = 3;
// Token abbreviation
string public symbol = "HKG";
// 1 ether = 200 hkg
uint BASE_PRICE = 200;
// 1 ether = 150 hkg
uint MID_PRICE = 150;
// 1 ether = 100 hkg
uint FIN_PRICE = 100;
// Safety cap
uint SAFETY_LIMIT = 4000000 ether;
// Zeros after the point
uint DECIMAL_ZEROS = 1000;
// Total value in wei
uint totalValue;
// Address of multisig wallet holding ether from sale
address wallet;
// Structure of sale increase milestones
struct milestones_struct {
uint p1;
uint p2;
uint p3;
uint p4;
uint p5;
uint p6;
}
// Milestones instance
milestones_struct milestones;
/**
* Constructor of the contract.
*
* Passes address of the account holding the value.
* HackerGold contract itself does not hold any value
*
* @param multisig address of MultiSig wallet which will hold the value
*/
function HackerGold(address multisig) {
wallet = multisig;
// set time periods for sale
milestones = milestones_struct(
1476972000, // P1: GMT: 20-Oct-2016 14:00 => The Sale Starts
1478181600, // P2: GMT: 03-Nov-2016 14:00 => 1st Price Ladder
1479391200, // P3: GMT: 17-Nov-2016 14:00 => Price Stable,
// Hackathon Starts
1480600800, // P4: GMT: 01-Dec-2016 14:00 => 2nd Price Ladder
1481810400, // P5: GMT: 15-Dec-2016 14:00 => Price Stable
1482415200 // P6: GMT: 22-Dec-2016 14:00 => Sale Ends, Hackathon Ends
);
// assign recovery balance
totalSupplyVar = 16110893000;
balances[0x342e62732b76875da9305083ea8ae63125a4e667] = 16110893000;
totalValue = 85362 ether;
}
/**
* Fallback function: called on ether sent.
*
* It calls to createHKG function with msg.sender
* as a value for holder argument
*/
function() payable {
createHKG(msg.sender);
}
/**
* Creates HKG tokens.
*
* Runs sanity checks including safety cap
* Then calculates current price by getPrice() function, creates HKG tokens
* Finally sends a value of transaction to the wallet
*
* Note: due to lack of floating point types in Solidity,
* contract assumes that last 3 digits in tokens amount are stood after the point.
* It means that if stored HKG balance is 100000, then its real value is 100 HKG
*
* @param holder token holder
*/
function createHKG(address holder) payable {
if (now < milestones.p1) throw;
if (now >= milestones.p6) throw;
if (msg.value == 0) throw;
// safety cap
if (getTotalValue() + msg.value > SAFETY_LIMIT) throw;
uint tokens = msg.value * getPrice() * DECIMAL_ZEROS / 1 ether;
totalSupplyVar += tokens;
balances[holder] += tokens;
totalValue += msg.value;
if (!wallet.send(msg.value)) throw;
}
/**
* Denotes complete price structure during the sale.
*
* @return HKG amount per 1 ETH for the current moment in time
*/
function getPrice() constant returns(uint result) {
if (now < milestones.p1) return 0;
if (now >= milestones.p1 && now < milestones.p2) {
return BASE_PRICE;
}
if (now >= milestones.p2 && now < milestones.p3) {
uint days_in = 1 + (now - milestones.p2) / 1 days;
return BASE_PRICE - days_in * 25 / 7; // daily decrease 3.5
}
if (now >= milestones.p3 && now < milestones.p4) {
return MID_PRICE;
}
if (now >= milestones.p4 && now < milestones.p5) {
days_in = 1 + (now - milestones.p4) / 1 days;
return MID_PRICE - days_in * 25 / 7; // daily decrease 3.5
}
if (now >= milestones.p5 && now < milestones.p6) {
return FIN_PRICE;
}
if (now >= milestones.p6) {
return 0;
}
}
/**
* Returns total stored HKG amount.
*
* Contract assumes that last 3 digits of this value are behind the decimal place. i.e. 10001 is 10.001
* Thus, result of this function should be divided by 1000 to get HKG value
*
* @return result stored HKG amount
*/
function getTotalSupply() constant returns(uint result) {
return totalSupplyVar;
}
/**
* It is used for test purposes.
*
* Returns the result of 'now' statement of Solidity language
*
* @return unix timestamp for current moment in time
*/
function getNow() constant returns(uint result) {
return now;
}
/**
* Returns total value passed through the contract
*
* @return result total value in wei
*/
function getTotalValue() constant returns(uint result) {
return totalValue;
}
}External Links
Related contracts
GavCoin
Same deployerThe first deployment of GavCoin, potentially by Gavin Wood himself.
0xb4abc1...4b8aa4April 26, 2016Pass Dao
Same eraShare ledger and proposal system for a funded working group, priced on a decaying curve.
0x09bc33...9a4acdNovember 24, 2016DoriToken
Same eraA fixed-supply token with transfers and allowance spending, deployed November 2016.
0xdbd90d...c4cb16November 25, 2016"ITT Demo 0.3.6 - Live Chain"
Same eraA fixed-supply token with transfers and third-party allowances, deployed November 2016.
0xb9a357...c293e3November 28, 2016HumanStandartTokenTC
Same eraA fixed-supply token with transfers and third-party allowances, deployed November 2016.
0x7dd73b...715250November 28, 2016INRF Test Tokens
Same eraOwner can mint and freeze, and the version label was renamed from the tutorial default.
0x74f074...abb8beNovember 30, 2016