ERC20-style 'I owe you a beer' IOU ledger by Nick Johnson (ENS). Each transfer creates a personal beer debt; obligations are tracked and transferable.
Historical Significance
An early example of a peer-to-peer credit/IOU primitive on Ethereum, predating Compound by two years and DeFi lending generally by three. Notable as one of Nick Johnson's pre-ENS contracts: ENS launched in May 2017, and BeerCoin (July 2016) is part of the body of small experimental tokens Nick was building while still at Google before joining the Ethereum Foundation. The novel transferOther function (reassigning someone else's debt to you onto a third party) is functionally a primitive debt-secondary-market mechanism, expressed in 11 KB of Solidity.
Context
Deployed 29 July 2016, nine days after the DAO hard fork (block 1,920,000, 20 July 2016). Solidity 0.3.5 was the current release; OpenZeppelin would not exist until later that year. ERC20 was still being formalized as EIP-20 (draft published 19 November 2015 by Fabian Vogelsteller). BeerCoin matches the EIP-20 ABI but stores balance per-account-pair rather than a single mapping, which is why some wallets (Mist) misinterpret it. Iterable mappings via the Itmap helper library were a common Solidity 0.3 pattern since dynamic-array enumeration of map keys was not built into the language.
Token Information
Key Facts
Description
BeerCoin is an ERC20-shaped IOU token by Nick Johnson (arachnid, later founder of ENS). Unlike a normal ERC20, BeerCoin has no preminted supply and no central issuer: anyone can issue BeerCoin simply by calling transfer(to, value), which creates a debt from the sender to the recipient. The recipient holds an obligation against the sender, redeemable for a beer if the two ever meet.
State is tracked per-account in a UserAccount struct: a debtors map (people who owe you beers), allowances (for the ERC20 transferFrom path), a maxCredit cap (the most beers any single party may owe you, default set at deployment, modifiable per-account up to 655,360), beersOwed (total you owe), and beersOwing (total owed to you). The standard ERC20 balanceOf(owner) returns beersOwing.
Extensions beyond ERC20: balanceOf(owner, debtor) returns just the debt of a specific debtor; transferOther(to, debtor, value) lets you reassign a beer that someone else owes you onto a new creditor; transferOtherFrom is the allowance version; numDebtors / debtor(idx) / debtors() enumerate your debtors via an Itmap iterable mapping; totalDebt(owner) returns beersOwed. The fallback function throws so the contract refuses ETH.
The contract holds 0.003 ETH, sent at construction by Nick himself to seed the deployment. The author wrote in the header comment: 'If you feel obliged to me for creating this, send me a beer at 0x5fC8A61e097c118cE43D200b3c4dcf726Cf783a9.' Compiled with Solidity v0.3.5 (21 July 2016, commit 6610add), CC-BY-SA license.
Source Verified
Author-published source by Nick Johnson, verified on Etherscan. License: CC-BY-SA.
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
DAO Fork Era
The controversial fork to recover funds from The DAO hack.
Bytecode Overview
Verified Source Available
Source verified on Etherscan.
View Verification ProofShow source code (Solidity)
/**
* Author: Nick Johnson (arachnid at notdot.net)
* Copyright 2016; licensed CC-BY-SA.
*
* BeerCoin is a new cryptocurrency intended to encapsulate and record the
* concept of "I owe you a beer". Did someone answer a difficult question you
* had? Send them a BeerCoin. Did they help you carry something heavy? Send
* them a BeerCoin. Someone buy you a beer? Send them a BeerCoin.
*
* Unlike traditional currency, anyone can issue BeerCoin simply by sending it
* to someone else. A person's BeerCoin is only as valuable as the recipient's
* belief that they're good for the beer, should it ever be redeemed; a beer
* owed to you by Vitalik Buterin is probably worth more than a beer owed to you
* by the DAO hacker (but your opinions may differ on that point).
*
* BeerCoin is implemented as an ERC20 compatible token, with a few extensions.
* Regular ERC20 transfers will create or resolve obligations between the two
* parties; they will never transfer third-party BeerCoins. Additional methods
* are provided to allow you transfer beers someone owes you to a third party;
* if Satoshi Nakamoto owes you a beer, you can transfer that obligation to your
* friend who just bought you one down at the pub. Methods are also provided for
* determining the total number of beers a person owes, to help determine if
* they're good for it, and for getting a list of accounts that owe someone a
* beer.
*
* BeerCoin may confuse some wallets, such as Mist, that expect you can only
* send currency up to your current total balance; since BeerCoin operates as
* individual IOUs, that restriction doesn't apply. As a result, you will
* sometimes need to call the 'transfer' function on the contract itself
* instead of using the wallet's built in token support.
*
* If anyone finds a bug in the contract, I'll buy you a beer. If you find a bug
* you can exploit to adjust balances without users' consent, I'll buy you two
* (or more).
*
* If you feel obliged to me for creating this, send me a ? at
* 0x5fC8A61e097c118cE43D200b3c4dcf726Cf783a9. Don't do it unless you mean it;
* if we meet I'll surely redeem it.
*/
contract BeerCoin {
using Itmap for Itmap.AddressUintMap;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
struct UserAccount {
bool exists;
Itmap.AddressUintMap debtors; // People who owe you a beer
mapping(address=>uint) allowances;
uint maxCredit; // Most beers any individual may owe you
uint beersOwed; // Beers owed by this person
uint beersOwing; // Beers owed to this person
}
uint beersOwing;
uint defaultMaxCredit;
function() {
throw;
}
function BeerCoin(uint _defaultMaxCredit) {
defaultMaxCredit = _defaultMaxCredit;
}
mapping(address=>UserAccount) accounts;
function maximumCredit(address owner) constant returns (uint) {
if(accounts[owner].exists) {
return accounts[owner].maxCredit;
} else {
return defaultMaxCredit;
}
}
function setMaximumCredit(uint credit) {
//640k ought to be enough for anyone
if(credit > 655360)
return;
if(!accounts[msg.sender].exists)
accounts[msg.sender].exists = true;
accounts[msg.sender].maxCredit = credit;
}
function numDebtors(address owner) constant returns (uint) {
return accounts[owner].debtors.size();
}
function debtor(address owner, uint idx) constant returns (address) {
return accounts[owner].debtors.index(idx);
}
function debtors(address owner) constant returns (address[]) {
return accounts[owner].debtors.keys;
}
function totalSupply() constant returns (uint256 supply) {
return beersOwing;
}
function balanceOf(address owner) constant returns (uint256 balance) {
return accounts[owner].beersOwing;
}
function balanceOf(address owner, address debtor) constant returns (uint256 balance) {
return accounts[owner].debtors.get(debtor);
}
function totalDebt(address owner) constant returns (uint256 balance) {
return accounts[owner].beersOwed;
}
function transfer(address to, uint256 value) returns (bool success) {
return doTransfer(msg.sender, to, value);
}
function transferFrom(address from, address to, uint256 value) returns (bool) {
if(accounts[from].allowances[msg.sender] >= value && doTransfer(from, to, value)) {
accounts[from].allowances[msg.sender] -= value;
return true;
}
return false;
}
function doTransfer(address from, address to, uint value) internal returns (bool) {
if(from == to)
return false;
if(!accounts[to].exists) {
accounts[to].exists = true;
accounts[to].maxCredit = defaultMaxCredit;
}
// Don't allow transfers that would exceed the recipient's credit limit.
if(value > accounts[to].maxCredit + accounts[from].debtors.get(to))
return false;
Transfer(from, to, value);
value -= reduceDebt(to, from, value);
createDebt(from, to, value);
return true;
}
// Transfers beers owed to you by `debtor` to `to`.
function transferOther(address to, address debtor, uint value) returns (bool) {
return doTransferOther(msg.sender, to, debtor, value);
}
// Allows a third party to transfer debt owed to you by `debtor` to `to`.
function transferOtherFrom(address from, address to, address debtor, uint value) returns (bool) {
if(accounts[from].allowances[msg.sender] >= value && doTransferOther(from, to, debtor, value)) {
accounts[from].allowances[msg.sender] -= value;
return true;
}
return false;
}
function doTransferOther(address from, address to, address debtor, uint value) internal returns (bool) {
if(from == to || to == debtor)
return false;
if(!accounts[to].exists) {
accounts[to].exists = true;
accounts[to].maxCredit = defaultMaxCredit;
}
if(transferDebt(from, to, debtor, value)) {
Transfer(from, to, value);
return true;
} else {
return false;
}
}
// Creates debt owed by `debtor` to `creditor` of amount `value`.
// Returns false without making changes if this would exceed `creditor`'s
// credit limit.
function createDebt(address debtor, address creditor, uint value) internal returns (bool) {
if(value == 0)
return true;
if(value > accounts[creditor].maxCredit)
return false;
accounts[creditor].debtors.set(
debtor, accounts[creditor].debtors.get(debtor) + value);
accounts[debtor].beersOwed += value;
accounts[creditor].beersOwing += value;
beersOwing += value;
return true;
}
// Reduces debt owed by `debtor` to `creditor` by `value` or the total amount,
// whichever is less. Returns the amount of debt erased.
function reduceDebt(address debtor, address creditor, uint value) internal returns (uint) {
var owed = accounts[creditor].debtors.get(debtor);
if(value >= owed) {
value = owed;
accounts[creditor].debtors.remove(debtor);
} else {
accounts[creditor].debtors.set(debtor, owed - value);
}
accounts[debtor].beersOwed -= value;
accounts[creditor].beersOwing -= value;
beersOwing -= value;
return value;
}
// Transfers debt owed by `debtor` from `oldCreditor` to `newCreditor`.
// Returns false without making any changes if `value` exceeds the amount
// owed or if the transfer would exceed `newCreditor`'s credit limit.
function transferDebt(address oldCreditor, address newCreditor, address debtor, uint value) internal returns (bool) {
var owedOld = accounts[oldCreditor].debtors.get(debtor);
if(owedOld < value)
return false;
var owedNew = accounts[newCreditor].debtors.get(debtor);
if(value + owedNew > accounts[newCreditor].maxCredit)
return false;
if(owedOld == value) {
accounts[oldCreditor].debtors.remove(debtor);
} else {
accounts[oldCreditor].debtors.set(debtor, owedOld - value);
}
accounts[oldCreditor].beersOwing -= value;
accounts[newCreditor].debtors.set(debtor, owedNew + value);
accounts[newCreditor].beersOwing += value;
return true;
}
function approve(address spender, uint256 value) returns (bool) {
accounts[msg.sender].allowances[spender] = value;
Approval(msg.sender, spender, value);
return true;
}
function allowance(address owner, address spender) constant returns (uint256) {
return accounts[owner].allowances[spender];
}
}
library Itmap {
struct AddressUintMapEntry {
uint value;
uint idx;
}
struct AddressUintMap {
mapping(address=>AddressUintMapEntry) entries;
address[] keys;
}
function set(AddressUintMap storage self, address k, uint v) internal {
var entry = self.entries[k];
if(entry.idx == 0) {
entry.idx = self.keys.length + 1;
self.keys.push(k);
}
entry.value = v;
}
function get(AddressUintMap storage self, address k) internal returns (uint) {
return self.entries[k].value;
}
function contains(AddressUintMap storage self, address k) internal returns (bool) {
return self.entries[k].idx > 0;
}
function remove(AddressUintMap storage self, address k) internal {
var entry = self.entries[k];
if(entry.idx > 0) {
var otherkey = self.keys[self.keys.length - 1];
self.keys[entry.idx - 1] = otherkey;
self.keys.length -= 1;
self.entries[otherkey].idx = entry.idx;
entry.idx = 0;
entry.value = 0;
}
}
function size(AddressUintMap storage self) internal returns (uint) {
return self.keys.length;
}
function index(AddressUintMap storage self, uint idx) internal returns (address) {
return self.keys[idx];
}
}External Links
Related contracts
shares
Same eraAn early on-chain corporation contract by cryptonomica.net, representing company shares as ERC-20 tokens with embedded voting — deployed hours before the DAO ha
0x684282...9a33b5July 20, 2016ReplayProtection
Same eraAlex Van de Sande's post-DAO-fork ETH/ETC replay-protection splitter. Routes ether or ERC20 tokens to different recipients depending on which chain it runs on.
0x64668c...456541July 27, 2016Xi
Same eraDAO-era ERC-20-shaped token, May 2016. Symbol is the Greek capital letter Xi. Fixed cap 15,000; 1 token per 0.001 ETH via buy().
0xbe3dc7...cab9dfJuly 31, 2016Hash DB Token
Same era2017 ERC-20 token tied to HashDB, an early Ethereum-backed data store; mintable via buy() with refund-on-overpay and frontend-bypass transferFrom.
0x0bf43e...a20147August 7, 2016Blockchain Reddit
Same eraAn Ethereum smart contract that enables on-chain publishing and token-gated upvoting of posts.
0x5793dc...9438c2August 7, 2016Kin
Same eraA 2016 ConsenSys MyAdvancedToken (Japanese 「金」, "gold"), 100 supply, sold via buy() at 1000 wei/token.
0x64f310...95ba99August 21, 2016