Parity's wallets were thin proxies: any call they did not recognise was forwarded to one shared library with delegatecall, meaning the library's code ran against your wallet's storage. The library's initWallet had no guard against being called twice, so anyone could re-initialize any wallet and make themselves the sole owner. In July, 153,037 ETH left three ICO wallets. The replacement library shipped the next day still had no guard — and in November a user called initWallet on the library itself, became its owner, and called kill(). Every wallet built on it now delegates into an empty address. 513,774 ETH have been unreachable ever since.
// Wallet.sol — anything unmatched runs in OUR storage function() payable { if (msg.value > 0) Deposit(msg.sender, msg.value); else if (msg.data.length > 0) _walletLibrary.delegatecall(msg.data); } // WalletLibrary.sol function initWallet(address[] _owners, uint _required, uint _daylimit) { initDaylimit(_daylimit); initMultiowned(_owners, _required); // ❌ no modifier. no "already initialised" flag. public to the world. } // July 2017 — against a WALLET: wallet.initWallet([attacker], 1, ...) → sole owner → execute() → 153,037 ETH // November 2017 — against the LIBRARY: library.initWallet([anon], 1, ...) → owner of the library library.kill(anon) → SELFDESTRUCT → 513,774 ETH frozen forever
initializer modifier, and every logic/library contract needs to be initialized at deployment so nobody else can claim it. A delegatecall fallback turns every public function in the library into a public function of your wallet — including the ones you forgot about.Entries in the SAFE database that describe this failure. The first ones name this incident directly.
Every figure on this page comes from the post-mortems above, not from us. Losses are US dollars at the time of the incident.
Access control is one of the 203 classes the SaferICO scanner checks for. It will not review your signing process — but it will read your Solidity.