Digix GoldTokenLedger, 10 January 2016. The earliest contract deployed carrying all six ERC-20 functions. Source recovered.
Historical Significance
The earliest contract deployed on Ethereum carrying all six functions of the ERC-20 interface: totalSupply, balanceOf, transfer, transferFrom, approve and allowance. It was deployed on 10 January 2016, four days before Digix reused the same design for DGX 1.0 at 0x55b9a11c2e8351b4Ffc7b11561148bfaC9977855, and well before EIP-20 was finalised.
Carrying the interface is not the same as behaving to it, and this contract does not. It declares all three Transfer parameters indexed, so a transfer emits four topics and an empty data field where the standard puts the value in data, and transferFrom names the spender rather than the owner as the sender. Its total supply was zero at deployment and has never changed, and it has never emitted a Transfer.
Context
Digix deployed its gold custody system on Ethereum through January 2016. Around this ledger sit the configuration contract at 0x64e56970dab471498f7624b15cbb397d7dcdc459, which every other component queries for addresses and settings, a Directory library, a shared base contract supplying those lookups, and the gold registry at 0x865376e86f20b23ce060a68fd7c5a3686da38261 that tracks individual bars and the fees owed on them. A ledger with the same code was deployed the same day at 0xb2dd0dc22c7d103928650abd260935ef9ef40cfc.
The system was compiled with solc v0.1.7. The Directory library carries the version stamp of commit c806b9bc, which was never released as a binary, and that stamp was long read as proof the system could not be verified. The stamp binds only the library that carries it. The contracts carry no stamp and reproduce exactly under the released v0.1.7+commit.b4e666cc once the optimizer is run at a low run count, which is what the computed constants in the deployed code reflect.
Token Information
Key Facts
Description
Digix GoldTokenLedger, deployed 10 January 2016 in block 824235, taking the DigixConfiguration at 0x64e56970dab471498f7624b15cbb397d7dcdc459 as its only constructor argument. Runtime 6701 bytes, 44 external functions.
The ledger holds gold token balances and reads every operational parameter from the configuration contract rather than storing it: the accounting and transaction fee wallets, the storage rate, the transaction fee and its ceiling, the billing period, the required confirmations, and the addresses of the gold, vendor, custodian and auditor registries. Balances carry demurrage. actualBalanceOf returns the stored figure, balanceOf returns it net of storage fees accrued since the holder's last payment date, and deductFees settles that difference into the transaction fee wallet before a transfer completes. ledgerMint is restricted to the gold registry, recastCall to the recast contract, and payStorageFee reads the amount owed from the gold registry and pays it out of the caller's balance.
All 44 selectors on the ledger are named. The source published here reproduces the deployed runtime byte for byte under solc v0.1.7+commit.b4e666cc with the optimizer enabled at one run, apart from four bytes at offset 0x1525. Those four bytes are the selector 0x65afd0ed, a function on the Digix gold registry at 0x865376e86f20b23ce060a68fd7c5a3686da38261 taking an address and returning a boolean. Its name is in no public signature database and has not been recovered, so the interface declaration for it carries a placeholder.
Source Verified
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 (near-exact bytecode match).
Show source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
contract DigixConfiguration {
function getConfigEntryAddr(bytes32 _key) public constant returns (address) {}
function getConfigEntryInt(bytes32 _key) public constant returns (uint256) {}
function isAdmin(address _acct) public returns (bool) {}
function getConfigEntryBytes(bytes32 _key) public constant returns (bytes32) {}
}
contract VendorRegistry {
function isVendor(address _vndr) public returns (bool) {}
}
contract CustodianRegistry {
function isCustodian(address _cust) public returns (bool) {}
}
contract AuditorRegistry {
function isAuditor(address _adtr) public returns (bool) {}
}
contract GenericInterface {
address public config;
address public owner;
function getConfigAddress() public returns (address) {
return config;
}
function getOwner() public returns (address) {
return owner;
}
function setOwner(address _newowner) ifowner {
owner = _newowner;
}
function isAdmin(address _acct) public returns (bool) {
return DigixConfiguration(config).isAdmin(_acct);
}
function goldRegistry() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("registry/gold");
}
function vendorRegistry() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("registry/vendor");
}
function custodianRegistry() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("registry/custodian");
}
function auditorRegistry() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("registry/auditor");
}
function goldTokenLedger() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("ledger/token");
}
function recastContract() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("contract/recast");
}
function minterContract() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("contract/minter");
}
function txFeeWallet() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("wallet/txfee");
}
function accountingWallet() public returns (address) {
return DigixConfiguration(config).getConfigEntryAddr("wallet/accounting");
}
function redemptionFee() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/redemptionfee");
}
function recastFee() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/recastfee");
}
function txFee() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/txfee");
}
function txFeeMax() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/txfeemax");
}
function storageRate() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/rate");
}
function getBase() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/base");
}
function billingPeriod() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/billingperiod");
}
function requiredConfirmations() public returns (uint256) {
return DigixConfiguration(config).getConfigEntryInt("settings/confirmations");
}
function nonMintSku() public returns (uint256) {
return uint256(DigixConfiguration(config).getConfigEntryBytes("settings/nonmintsku"));
}
function isGoldRegistry(address _greg) public returns (bool) {
return (goldRegistry() == _greg);
}
function isCustodian(address _cust) public returns (bool) {
return CustodianRegistry(custodianRegistry()).isCustodian(_cust);
}
function isAuditor(address _adtr) public returns (bool) {
return AuditorRegistry(auditorRegistry()).isAuditor(_adtr);
}
function isVendor(address _vndr) public returns (bool) {
return VendorRegistry(vendorRegistry()).isVendor(_vndr);
}
modifier ifvendor { if(isVendor(msg.sender)) _ }
modifier ifowner { if(msg.sender == owner) _ }
modifier ifcustodian { if(isCustodian(msg.sender)) _ }
modifier ifauditor { if(isAuditor(msg.sender)) _ }
modifier ifgoldregistry { if(isGoldRegistry(msg.sender)) _ }
}
contract GoldRegistry {
function getFee(address _gold) public returns (uint256) {}
function unknown65afd0ed(address _gold) public returns (bool) {}
}
contract GoldTokenLedger is GenericInterface {
struct Account {
bool exists;
uint256 balance;
uint256 lastPayDate;
mapping (address => uint256) allowances;
}
uint256 public totalSupply;
mapping (address => Account) accounts;
modifier ifrecast { if (recastContract() == msg.sender) _ }
event Transfer(address indexed _from, address indexed _to, uint256 indexed _value);
event NewAccount(address indexed user);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function GoldTokenLedger(address _conf) {
owner = msg.sender;
config = _conf;
totalSupply = 0;
newAccount(accountingWallet());
newAccount(txFeeWallet());
}
function newAccount(address _address) internal {
if (userExists(_address)) return;
accounts[_address].balance = 0;
accounts[_address].lastPayDate = block.timestamp;
accounts[_address].exists = true;
NewAccount(_address);
}
function userExists(address _address) public returns (bool) {
return accounts[_address].exists;
}
function actualBalanceOf(address _address) public returns (uint256) {
return accounts[_address].balance;
}
function approve(address _addr, uint256 _val) public returns (bool) {
accounts[msg.sender].allowances[_addr] = _val;
Approval(msg.sender, _addr, _val);
return true;
}
function allowance(address _owner, address _spender) public constant returns (uint256) {
return accounts[_owner].allowances[_spender];
}
function transfer(address _to, uint256 _value) public returns (bool) {
if (_to == 0x0) return false;
if (!userExists(_to)) newAccount(_to);
uint256 _fee = calculateTxFee(_value, msg.sender);
uint256 _total;
if (msg.sender == accountingWallet()) _fee = 0;
if (msg.sender == txFeeWallet()) _fee = 0;
_total = _value + _fee;
if (balanceOf(msg.sender) < _total) return false;
if (!deductFees(msg.sender)) return false;
if (!deductFees(_to)) return false;
accounts[msg.sender].balance -= _total;
accounts[_to].balance += _value;
uint256 _timestamp = block.timestamp;
accounts[txFeeWallet()].balance += _fee;
Transfer(msg.sender, _to, _value);
Transfer(msg.sender, txFeeWallet(), _fee);
return true;
}
function calculateTxFee(uint256 _value, address _user) public constant returns (uint256) {
uint256 _fee;
if (_user == accountingWallet()) return 0;
if (_user == txFeeWallet()) return 0;
_fee = ((_value * txFee()) / getBase());
if (_fee > txFeeMax()) return txFeeMax();
return _fee;
}
function calculateDemurrage(address _address) public constant returns (uint256) {
if (actualBalanceOf(_address) == 0) return 0;
if (_address == accountingWallet()) return 0;
if (_address == txFeeWallet()) return 0;
if (getFeeDays(_address) == 0) return 0;
return demurrageCalc(accounts[_address].balance, getFeeDays(_address));
}
function demurrageCalc(uint256 _balance, uint256 _feedays) public returns (uint256) {
uint256 _rate = storageRate() * _feedays;
return ((_balance * _rate) / getBase());
}
function balanceOf(address _address) public constant returns (uint256) {
if (_address == 0x0) return 0;
if (_address == accountingWallet()) return accounts[accountingWallet()].balance;
if (_address == txFeeWallet()) return accounts[txFeeWallet()].balance;
if (accounts[_address].balance == 0) return 0;
if (getFeeDays(_address) <= 0) return accounts[_address].balance;
return (actualBalanceOf(_address) - calculateDemurrage(_address));
}
function deductFees(address _address) public returns (bool) {
if (_address == accountingWallet()) return true;
if (_address == txFeeWallet()) return true;
if (actualBalanceOf(_address) == 0) {
accounts[_address].lastPayDate = block.timestamp;
return true;
}
if (getFeeDays(_address) == 0) return true;
uint256 _fees = calculateDemurrage(_address);
uint256 _balance = actualBalanceOf(_address);
accounts[_address].balance -= _fees;
accounts[txFeeWallet()].balance += _fees;
uint256 _remainder = block.timestamp - (accounts[_address].lastPayDate + (getFeeDays(_address) * billingPeriod()));
accounts[_address].lastPayDate = block.timestamp - _remainder;
Transfer(_address, txFeeWallet(), _fees);
return true;
}
function getFeeDays(address _address) public returns (uint256) {
return ((block.timestamp - accounts[_address].lastPayDate) / billingPeriod());
}
function payStorageFee(address _gold) public {
uint256 _fee = GoldRegistry(goldRegistry()).getFee(_gold);
if (!userExists(tx.origin)) newAccount(tx.origin);
if (!deductFees(tx.origin)) throw;
if (balanceOf(tx.origin) < _fee) throw;
if (!GoldRegistry(goldRegistry()).unknown65afd0ed(_gold)) throw;
accounts[tx.origin].balance -= _fee;
accounts[accountingWallet()].balance += _fee;
Transfer(tx.origin, accountingWallet(), _fee);
}
function ledgerMint(address _gold, address _user, uint256 _tokens, uint256 _fees) public ifgoldregistry returns (bool) {
if (!userExists(_user)) newAccount(_user);
if (!deductFees(_user)) return false;
accounts[accountingWallet()].balance += _fees;
Transfer(tx.origin, accountingWallet(), _fees);
accounts[_user].balance += (_tokens - _fees);
uint256 _total = (_tokens + _fees);
totalSupply += _total;
Transfer(minterContract(), tx.origin, (_tokens - _fees));
return true;
}
function recastCall(address _gold, address _user, uint256 _tokens, uint256 _recastfee) public ifrecast returns (bool) {
if (!userExists(_user)) newAccount(_user);
uint256 _total = _tokens + _recastfee;
if (balanceOf(_user) < _total) return false;
if (!deductFees(_user)) return false;
accounts[_user].balance -= _total;
accounts[accountingWallet()].balance += _recastfee;
totalSupply -= _tokens;
Transfer(tx.origin, accountingWallet(), _recastfee);
Transfer(tx.origin, 0x0, _tokens);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
if (_to == 0x0) return false;
if (!userExists(_to)) newAccount(_to);
uint256 _fee = calculateTxFee(_value, _from);
uint256 _total;
if (_from == accountingWallet()) _fee = 0;
if (_from == txFeeWallet()) _fee = 0;
_total = _value + _fee;
if (accounts[_from].allowances[msg.sender] < _total) return false;
if (balanceOf(_from) < _total) return false;
if (!deductFees(_from)) return false;
if (!deductFees(_to)) return false;
accounts[_from].balance -= _total;
accounts[_to].balance += _value;
uint256 _timestamp = block.timestamp;
accounts[txFeeWallet()].balance += _fee;
Transfer(msg.sender, _to, _value);
Transfer(msg.sender, txFeeWallet(), _fee);
return true;
}
}External Links
Related contracts
Directory
Same deployerDigixDAO typed-collection storage library (Address to bytes/address/bool maps; insert/remove/contains). Anthony Eufemio, Nov 2015. 1 of 9 clones.
0xbce3b5...64bac0January 9, 2016Ledger Storage
Same deployerDigixDAO marketplace ledger storage: maps (uint256 id, address) to records of amount, timestamp and state. Anthony Eufemio, Jan 2016. 7 identical clones.
0x35e1d3...7c2e6dJanuary 9, 2016DigixConfiguration
Same deployerDigixDAO owner + admin-ACL config registry (typed key/value store). CALLCODEs a Directory library clone for its admin set. Anthony Eufemio, Jan 2016.
0xc0a2b0...dfbf56January 9, 2016Directory
Same deployerDigixDAO typed-collection storage library (Address to bytes/address/bool maps; insert/remove/contains). Anthony Eufemio, Nov 2015. 1 of 9 clones.
0x6fb339...164bd9January 9, 2016Ledger Storage
Same deployerDigixDAO marketplace ledger storage: maps (uint256 id, address) to records of amount, timestamp and state. Anthony Eufemio, Jan 2016. 7 identical clones.
0x4c8e1e...92b8d9January 9, 2016DigixConfiguration
Same deployerDigixDAO owner + admin-ACL config registry (typed key/value store). CALLCODEs a Directory library clone for its admin set. Anthony Eufemio, Jan 2016.
0x2f7764...ff9d6cJanuary 9, 2016