Delegatecall injection allows attackers to execute arbitrary code in a contract's context by controlling the target address passed to delegatecall(). This enables complete contract takeover, state manipulation, and fund theft. A single vulnerable delegatecall can compromise an entire protocol. Firepan's HOUND AI detects delegatecall injection vulnerabilities automatically during development and post-deployment monitoring, covering continuous analysis across the full contract lifecycle.
Delegatecall is an EVM primitive that executes code from another contract while preserving the calling contract's storage, msg.sender, and msg.value. Unlike call(), delegatecall preserves context—perfect for proxies and libraries.
However, delegatecall with untrusted target addresses enables arbitrary code execution:
The vulnerability is critical because:
Delegatecall injection unfolds in simple steps:
// VULNERABLE — example only
// Demonstrates: Delegatecall Injection
// Do NOT use in production
pragma solidity ^0.8.0;
contract VulnerableProxy {
address public implementation;
address public owner;
mapping(address => uint256) public balances;
constructor(address _impl) {
implementation = _impl;
owner = msg.sender;
}
// VULNERABLE: No validation on implementation address
function setImplementation(address _impl) public {
require(msg.sender == owner);
implementation = _impl; // Attacker can set to malicious contract
}
// VULNERABLE: Delegatecall to user-controlled target
fallback() external payable {
(bool success, ) = implementation.delegatecall(msg.data);
require(success);
}
// Alternative vulnerability: library call to user-supplied address
function callLibrary(address lib, bytes memory data) public {
(bool success, ) = lib.delegatecall(data);
require(success);
}
}
contract VulnerableToken {
address public operator; // Can be hijacked
mapping(address => uint256) public balances;
// VULNERABLE: delegatecall to external address
function executeLogic(address target, bytes memory data) public {
require(msg.sender == operator);
(bool success, ) = target.delegatecall(data);
require(success);
}
}
contract MaliciousImplementation {
// Matches function signature expected by victim contract
function transfer(address to, uint256 amount) public {
// This executes with VulnerableProxy's storage context
// Attacker can:
// 1. Modify storage directly
// 2. Call other functions as the proxy
// 3. Send all funds to attacker
}
function stealOwnership() public {
// Directly modify owner variable (storage slot 1)
// assembly { sstore(1, caller()) }
}
function drainFunds() public {
// Transfer all balances to attacker
// VulnerableProxy's balances are now accessible to attacker's contract
}
}
contract Attacker {
VulnerableProxy public proxy;
MaliciousImplementation public malicious;
constructor(address payable _proxy) {
proxy = VulnerableProxy(_proxy);
malicious = new MaliciousImplementation();
}
function exploit() public {
// Step 1: Set implementation to malicious contract
proxy.setImplementation(address(malicious));
// Step 2: Call any function via fallback
// This delegatecalls into malicious contract
// Any function call to proxy now executes attacker's code
// with proxy's storage context
// Step 3: Call specific attack function
(bool success, ) = address(proxy).call(
abi.encodeWithSignature("stealOwnership()")
);
// Now attacker owns proxy
// Can call executeLogic to further manipulate state
}
}
contract DirectDelegatecallInjection {
// VULNERABLE: delegatecall with untrusted target
function executeWithLibrary(address lib, bytes calldata data) public {
(bool success, ) = lib.delegatecall(data);
require(success);
}
// Attacker calls: executeWithLibrary(attackerContractAddress, craftedData)
// Attacker's contract code executes with DirectDelegatecallInjection's storage
}
| Protocol | Date | Loss | Root Cause | |----------|------|------|-----------| | Parity Multisig | 2017 | $30M+ | Delegatecall in wallet initialization | | Geth Wallet | 2017 | $15M+ | delegatecall vulnerability in proxy | | Multiple Proxies | 2020-2023 | $200M+ | Unvalidated implementation addresses | | BeamSwap | 2021 | $6.5M | Delegatecall to user-supplied library |
Manual detection focuses on delegatecall usage patterns:
Red flags:
Firepan's HOUND AI performs comprehensive delegatecall analysis:
Firepan's HOUND AI engine identifies delegatecall injection paths across all monitored contracts.
1. Hardcode Implementation Addresses
For proxies, immutably bind implementation address:
// SECURE: Implementation address hardcoded, immutable
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract SecureProxy is ERC1967Proxy {
constructor(address logic, bytes memory data)
ERC1967Proxy(logic, data)
{}
}
2. Use Transparent Proxy Pattern
OpenZeppelin's TransparentProxy separates admin logic from user calls:
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract SecureDeployment {
address implementation;
address admin;
constructor(address _implementation, address _admin) {
implementation = _implementation;
admin = _admin;
// Only admin can upgrade
}
}
3. Validate Implementation Contracts
If upgrades necessary, whitelist approved implementations:
contract UpgradeableProxy {
address public implementation;
mapping(address => bool) public approvedImplementations;
address public admin;
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
function upgradeImplementation(address newImpl) public onlyAdmin {
require(approvedImplementations[newImpl], "Not approved");
require(newImpl.code.length > 0, "Not contract");
implementation = newImpl;
}
fallback() external payable {
(bool success, ) = implementation.delegatecall(msg.data);
require(success);
}
}
4. Never delegatecall User-Supplied Addresses
Never allow users to specify delegatecall targets:
// VULNERABLE
function userDelegatecall(address target, bytes memory data) public {
(bool success, ) = target.delegatecall(data);
require(success);
}
// SECURE: delegatecall only to trusted, immutable addresses
function libraryOperation(bytes memory data) internal {
(bool success, ) = TRUSTED_LIBRARY.delegatecall(data);
require(success);
}
5. Use UUPS (Universal Upgradeable Proxy Standard)
UUPS pattern puts upgrade logic in implementation contract:
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
contract UUPS Implementation is UUPSUpgradeable {
function _authorizeUpgrade(address newImplementation)
internal
override
onlyRole(UPGRADER_ROLE)
{}
}
Q: What is delegatecall injection in smart contracts?
A: Delegatecall injection exploits contracts that execute delegatecall to untrusted or user-controlled addresses. The attacker supplies a malicious contract address, which then executes with the victim contract's storage context, enabling complete state manipulation and fund theft.
Q: Which protocols have been exploited via delegatecall injection?
A: Parity Multisig (2017, $30M+) and Geth Wallet (2017, $15M+) suffered major delegatecall attacks. BeamSwap (2021, $6.5M) was compromised via delegatecall to user-supplied library. Generic proxy vulnerability affected 200M+ across DeFi since 2020.
Q: How does Firepan detect delegatecall injection?
A: Firepan enumerates all delegatecall instructions, traces target address sources, performs taint analysis to confirm targets don't depend on user input, validates proxy storage layout compatibility, and verifies implementation updates have proper access control.
Q: Can delegatecall injection be exploited after deployment?
A: Yes, delegatecall injection is immediately exploitable post-deployment. If target address is user-controlled, attacker instantly supplies malicious contract. If implementation address is changeable, attacker sets it to malicious implementation and gains complete control.
Q: How do I prevent delegatecall injection?
A: Hardcode implementation addresses and make them immutable. Use OpenZeppelin's TransparentProxy or UUPS patterns. Never allow user-supplied delegatecall targets. Whitelist approved implementation contracts. Validate that delegatecall targets are actual contract addresses.
Delegatecall injection enables complete contract takeover with a single malicious delegatecall. Parity ($30M) and Geth ($15M) demonstrated the severity in 2017. Modern proxy patterns (TransparentProxy, UUPS) with hardcoded/immutable implementation addresses eliminate delegatecall injection risk. Firepan's HOUND AI detects user-controlled delegatecall targets across all monitored contracts.
Start securing your smart contracts at https://app.firepan.com/
Firepan
12,453 contracts secured. 2,851 vulnerabilities blocked. 236 exploits prevented. Run a free surface scan — results in minutes, no credit card required.
Run Free Scan →