In November 2017 a user called an unprotected initialization function on a shared Parity wallet library, became its owner, and then destroyed it. Every multi-signature wallet that depended on that library lost its logic, and the ether inside could no longer be moved. The root cause was a contract left uninitialized and a dangerous self-destruct path.
What happened
According to reporting at the time, affected wallets were standard multi-sig contracts deployed after 20 July 2017. They did not carry their own logic. They forwarded calls to one shared library contract on the chain.
On 6 November 2017 a user known as devops199 called the library's initWallet function. That turned the library itself into a wallet with the caller as owner. The same user then triggered a self-destruct, which deleted the code every dependent wallet was relying on.
Reports put the inaccessible amount between 150 million and 300 million dollars. The estimate varies by source and by the ether price used, so treat any single figure with care. Funds were not moved to an attacker. They were simply stuck.
Why it was possible
Two design choices combined.
First, the library was deployed but never initialized, so the first caller to reach the initializer claimed ownership. Initializers are ordinary public functions unless you lock them.
Second, the library contained a self-destruct that an owner could reach. Contracts that other contracts delegate to are a single point of failure. Destroy the target and every caller breaks.
The same reporting notes that an earlier bug in July 2017 had already cost about 30 million dollars, and that the patched code "still contained another issue". A patch that fixes one path without a review of the whole contract is a common way to ship a second flaw.
Checks that prevent this class of bug
Modern upgrade tooling encodes the lesson. The OpenZeppelin upgrade documentation tells developers to lock the implementation contract by calling _disableInitializers() in its constructor, so nobody can initialize it directly. It also says selfdestruct and delegatecall should not be used in upgradeable contracts, because either could destroy the logic contract and break every proxy that points to it.
A short review list for any shared or proxied contract:
- Can anyone call an initializer on the implementation itself?
- Is there any path to self-destruct, and who can reach it?
- Does the contract hold state that outsiders should never be able to set?
- After a patch, was the whole contract reviewed again, or only the changed lines?
- Is there a test that tries to initialize and destroy the deployed implementation as a stranger?
What teams outside crypto can take from it
The pattern is not specific to blockchains. Shared libraries, base images and common build scripts all create a spot where one unchecked entry point harms every consumer. Treat anything that many systems depend on as a high value target, review its setup path, and test it as an outsider would.
If your code base includes contract dependencies, Safeguard can inventory them and scan the packages they come from. You can try a free scan at gold.safeguard.sh/scan. It does not replace a contract audit.
Quick check
Was the money stolen?
Reporting at the time described the funds as inaccessible rather than taken by an attacker. They stayed in the wallet contracts but could not be moved.
What single step would have blocked the takeover?
Initializing the library at deployment, or locking its initializer, so no outsider could become owner. Removing the self-destruct path would also have avoided the freeze.
Sources
- Threatpost: hundreds of millions in digital currency remains frozen
- OpenZeppelin: writing upgradeable contracts
Last reviewed: 19 October 2026.