Smart-contract security & auditing

delegatecall injection

Delegatecall injection is a family of bugs that arise from misusing delegatecall, the opcode that runs another contract's code inside the caller's own storage and identity. Because delegatecall executes the target's bytecode but reads and writes the caller's storage, and keeps the original msg.sender and msg.value, it is enormously powerful, and enormously dangerous: whoever controls the called code, or the layout it assumes, effectively controls the caller's state. This is the engine behind upgradeable proxies and also behind some of the most expensive failures in Ethereum history.

Two main hazards recur. The first is storage collision: a proxy and its implementation must agree on what each storage slot means, because delegatecall writes by slot number, not by variable name. If the proxy stores its admin address in slot 0 and the implementation happens to put a user-controlled variable in slot 0, an ordinary user write can overwrite the admin. The second is an uninitialized or unprotected implementation that can be delegatecalled or self-destructed. The 2017 Parity multisig freeze was exactly this: the shared library was left uninitialized, an attacker called its initialization to become owner, then triggered selfdestruct, permanently bricking roughly 513,000 ETH across every wallet that delegatecalled into it.

Defenses are now standardized. EIP-1967 places critical proxy variables (implementation, admin) at fixed, pseudo-random storage slots derived from a hash, so they cannot collide with ordinary sequential variables. Implementation contracts should disable their own initializers (OpenZeppelin's _disableInitializers in the constructor) so no one can initialize and hijack the logic contract directly. Never delegatecall into untrusted or arbitrary addresses, and treat every byte of a delegatecall target as if it were your own code, because to the EVM, it is.

A proxy stores its owner in slot 0; the implementation, reached by delegatecall, treats slot 0 as a plain user value. A user calling the implementation's setter writes slot 0 and silently becomes the proxy's owner. Fixed-slot schemes (EIP-1967) prevent the overlap.

The Parity multisig freeze of November 2017 (about 513,000 ETH) was a delegatecall bug: the shared library was left uninitialized, an attacker claimed ownership and self-destructed it, bricking every wallet whose code lived inside that one library.

Also called
storage collision儲存碰撞