Delegatecall Vulnerability

delegatecall is a low-level EVM opcode that executes code from a target contract but uses the caller's storage, balance, and context. It's the foundation of upgradeable proxy patterns — but it's also a source of catastrophic bugs when used carelessly.

How it works

When contract A delegatecalls contract B, B's code runs as if it were A's code. Any storage writes made by B's code go into A's storage, at the same slot positions. If B writes to slot 0, it overwrites slot 0 in A — regardless of what A stores there.

Vulnerable pattern

// VULNERABLE — any caller can delegatecall arbitrary code
contract Proxy {
    address public implementation;
    
    fallback() external payable {
        // No access check!
        (bool ok,) = implementation.delegatecall(msg.data);
        require(ok);
    }
    
    // No protection on upgrades either
    function upgrade(address newImpl) public {
        implementation = newImpl;
    }
}

Safe pattern

// SAFER — only owner can upgrade, use OpenZeppelin's TransparentUpgradeableProxy
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
// The proxy admin is separated from the user-facing proxy to prevent selector clashes
// All upgrades go through the ProxyAdmin contract

Real-world exploits

  • Parity Multisig Library (2017) — a public initWallet() in the library contract was called directly via delegatecall, letting an attacker become the owner and self-destruct the library, freezing $280 million.
  • Wormhole (2022) — a delegatecall-related vulnerability in the Solana bridge (not EVM, but same pattern) led to a $320 million exploit.

How to prevent it

  • Use OpenZeppelin's battle-tested proxy patterns (Transparent, UUPS) instead of rolling your own.
  • Never expose raw delegatecall to user input without strict validation.
  • Protect upgrade functions with multi-sig or timelock controls.
  • Disable initialize() in implementation contracts using _disableInitializers().
← Back to Glossary