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.
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 — 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;
}
}
// 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
initWallet() in the library contract was called directly via delegatecall, letting an attacker become the owner and self-destruct the library, freezing $280 million.delegatecall to user input without strict validation.initialize() in implementation contracts using _disableInitializers().