Summary
The Parity multisig wallet (version 1.5+) suffered two distinct incidents rooted in the same flaw. On 19 July 2017 an attacker stole 153,037 ETH (~$30M) from several wallets, and on 6 November 2017 the user devops199 accidentally froze 513,774 ETH (~$150M+ across 587 wallets) permanently. Each thin wallet contract held no logic and used delegatecall to forward unmatched calls to a single shared WalletLibrary, which executed in the caller's storage context. The library's initWallet (calling initMultiowned) was a public function with no initialized guard, so in July the attacker called initWallet on a deployed wallet to overwrite m_owners with only their own address and m_required to 1, then called execute() to drain it. In November devops199 called the unprotected initWallet directly on the shared WalletLibrary itself (whose own storage was still uninitialized, bypassing the post-July fix that only checked the caller's m_numOwners), became its owner, then called the library's kill() which ran selfdestruct, deleting the shared code and bricking every wallet that delegatecalled into it.
How to avoid it in your code
- Protect every initializer with an initialized guard or OpenZeppelin Initializable; never leave init() publicly re-callable
- Treat any library reached via delegatecall as part of your trust boundary; deploy it initialized and locked
- Do not use delegatecall as a catch-all fallback; explicitly whitelist which library functions are externally reachable
- Gate selfdestruct and ownership-changing paths behind explicit access control, and prefer removing selfdestruct entirely
- After deploying a singleton implementation, call its initializer immediately so no one else can claim it
References
Related vulnerabilities
All Web3 →- CRITICALWEB3-HEDGEY-2024
On April 19, 2024, Hedgey Finance was drained of about $44.7 million (notional) across Arbitrum (~$42.6 million, mostly BONUS tokens) and Ethereum (~$2.1 million in USDC, ETH and other tokens). The root cause was an unvalidated attacker-controlled address combined with a stale token allowance in the ClaimCampaigns contract. createLockedCampaign granted an ERC-20 allowance via SafeERC20.safeIncreaseAllowance(IERC20(campaign.token), claimLockup.tokenLocker, campaign.amount) without validating that the caller-supplied tokenLocker was a legitimate Hedgey vesting contract, so the attacker passed their own address and obtained spend approval. cancelCampaign then refunded the deposited tokens but never called safeDecreaseAllowance, leaving the dangling allowance live after capital was returned. Funding the deposit with a Balancer flash loan, the attacker looped create-then-cancel to accumulate approvals, then called the token's transferFrom directly to drain funds belonging to other campaigns out of the contract.
- CRITICALWEB3-NOMAD-2022
On August 1, 2022, the Nomad token bridge was drained of about $190 million. Nomad messages require two steps, prove (record the message hash under a proven Merkle root) then process (execute), and process() gated execution on acceptableRoot(messages[hash]) being valid. During a routine upgrade, initialize() set confirmAt[_committedRoot] = 1 with _committedRoot equal to bytes32(0) (the empty-tree root), so confirmAt[0x00] became non-zero. For any never-proven message, messages[hash] returns the Solidity default bytes32(0), and acceptableRoot(0x00) then read confirmAt[0x00] = 1 and passed the timestamp check, so every unproven message was treated as valid. Attackers skipped prove() entirely and called process() directly with crafted calldata to release funds, submitting no Merkle proof at all. After the first demonstration, hundreds of opportunistic users copy-pasted the transaction with their own addresses, turning it into a chaotic crowdsourced free-for-all; only about $22 million was recovered shortly after.
- CRITICALWEB3-PROXY-COLLISION-2022
On July 23, 2022 the Audius governance, staking, and delegation contracts on Ethereum mainnet were drained of 18,564,497 AUDIO (~$6.1M) because their upgradeable delegatecall proxy had a storage-layout collision with a re-callable initializer. A delegatecall proxy runs the logic contract's bytecode against the proxy's own storage, so the two contracts must agree on every slot index; Audius added a variable to the proxy that occupied the same low slot the implementation used for the OpenZeppelin Initializable initialized flag. Writing the proxy-side value reset the implementation's initialized boolean to a non-true state, removing the one-time guard, so the attacker re-invoked initialize() against an already-deployed contract. Re-initialization let the attacker register themselves as governance guardian and submit a malicious proposal that delegated enormous voting weight and executed an immediate treasury transfer. The contracts had been audited by OpenZeppelin and Kudelski but the collision was introduced later and missed.
- CRITICALWEB3-LIFI-2022
On 20 March 2022 the LI.FI swap/bridge router was exploited for about $596,000 from 29 wallets that had granted token approvals to its CBridgeFacet contract. The swapAndStartBridgeTokensViaCBridge path let callers supply an array of swaps each carrying an arbitrary destination address and arbitrary calldata, which the contract executed with a low-level call() under its own context and with no target allowlist or selector check. The attacker passed a tiny legitimate swap followed by calls whose target was an ERC-20 token and whose calldata was transferFrom(victim, attacker, amount). Because victims had given infinite approval to CBridgeFacet, those transferFrom calls succeeded, draining their wallets directly. This is the arbitrary-external-call / untrusted call-target router bug that weaponizes user approvals.
- HIGHWEB3-FRONTEND-DNS-HIJACK-2022
A frontend hijack leaves the on-chain contracts untouched but replaces the Web2 surface serving the dApp UI with a wallet-drainer clone, so no Solidity audit can catch it. The recurring pattern: attackers take over the domain registrar or DNS provider account (or a CDN/tag-manager account), repoint the domain to a cloned site, and prompt visitors to sign malicious token approvals, EIP-2612 permit signatures, or transfers. Curve Finance was hit twice: on August 9-10, 2022 its curve.fi domain was DNS-hijacked via a compromised nameserver and drained ~$570K in USDC/DAI; and again around May 12, 2025 at the registrar level, after which Curve permanently migrated to curve.finance and announced an ENS move (Convex Finance and Resupply, which depend on Curve's data feeds, suffered dependency-driven outages but were not themselves compromised). In July 2024 a mass wave hit DeFi domains registered through Squarespace, whose forced migration off Google Domains stripped 2FA: Compound's frontend redirected to an Inferno Drainer clone and 100+ protocols were exposed (Celer blocked its takeover via domain monitoring). Ambient Finance's domain was hijacked through stolen registrar credentials on October 17, 2024. Most recently, on April 14, 2026 attackers used forged identity documents to social-engineer the registrar into handing over DNS control of CoW Swap's swap.cow.fi and cow.fi domains, redirecting users to a pixel-perfect drainer clone for about 90 minutes; over $1M was taken in roughly three hours, including 219 ETH (~$750K) from a single wallet, while CoW's contracts, backend APIs, and solver network were untouched. The same bucket includes CDN-account injections (KyberSwap's September 2022 Cloudflare/Google Tag Manager compromise, ~$265K) and BGP route hijacks that swap signed bundles for drainer code.
- CRITICALWEB3-BUNNI-2025
On September 2, 2025 Bunni, a liquidity manager built on Uniswap v4, was drained of roughly $8.4 million across Ethereum and Unichain (USDC, USDT, and weETH/ETH) through a rounding error in its withdrawal accounting amplified by flash loans. Bunni's Liquidity Distribution Function (LDF) tracks an 'idle balance' that is rebalanced on every swap, and the withdraw path rounded that balance in the wrong direction under specific conditions. The attacker flash-borrowed millions in USDT and executed a precisely sized sequence of swaps that pushed the pool's spot price back and forth across tick boundaries, triggering the faulty rounding repeatedly; each cycle let them withdraw more tokens than they burned in liquidity (in the USDC/USDT pool the idle balance fell 85.7% while liquidity fell only 84.4%, and that gap was the leak). The bug was application-specific accounting math, not an oracle or price-feed flaw. Unable to fund a secure relaunch, the Bunni team announced on October 23, 2025 that it was permanently shutting down, leaving withdrawals open and relicensing v2 from BUSL to MIT.