Parity Multi-Sig Wallet Freeze
An uninitialized library contract was claimed by a user who then accidentally destroyed it, permanently freezing 513,000 ETH held by ~587 wallets.
Background
Parity Technologies built a popular multi-signature wallet for Ethereum. Their architecture used a delegatecall pattern: a shared library contract held all the logic, and individual wallet contracts delegatecall'd into it to execute. This was elegant — one codebase, many wallets.
The library contract was deployed on-chain but never properly initialized.
The Vulnerability
The library had an initWallet() function that anyone could call — there was no check that it had already been initialized. This function set the contract's owner list. Since it was never called during deployment, it was open for anyone to claim.
// No initialization check — anyone can call this
function initWallet(
address[] _owners,
uint _required,
uint _daylimit
) {
initDaylimit(_daylimit);
// Sets owners array — no check if already initialized!
initMultiowned(_owners, _required);
}
// Only "owner" can call this
function kill(address _to) onlyowner {
selfdestruct(_to); // Destroys the library forever
}The Attack — Step by Step
- A GitHub user noticed the uninitialized library contract
- They called
initWallet(["their_address"], 1, 0)— making themselves the sole owner - Then called
kill(their_address)— which executedselfdestructon the library - The library contract ceased to exist on-chain
- All ~587 wallet contracts that delegatecall'd into the library now pointed to a non-existent address
- Every transaction to those wallets failed. ~513,000 ETH ($150M) was permanently frozen
The user claimed it was accidental. Whether intentional or not, the result was the same: irreversible loss.
Impact
Unlike the DAO hack, there was no hard fork to recover these funds. The ETH is still frozen in those wallets to this day. This remains one of the largest permanent losses in Ethereum history from a smart contract bug.
Lessons Learned
Always initialize contracts immediately after deployment. Use initializer modifiers (as in OpenZeppelin's Initializable pattern) to prevent double-initialization and unauthorized initialization.
- Initialize immediately: Call the initialization function in the same deployment transaction
- Guard initialization: Use a
bool initializedflag or OpenZeppelin'sInitializable - Think twice about selfdestruct: Does your contract really need it? The risk often outweighs the benefit
- Audit library contracts: Shared logic contracts deserve extra scrutiny — one bug affects every dependent contract