critical

Delegatecall Injection: How It Works, Real Exploits & Automated Detection

April 1, 2026
Chainsethereumarbitrumbaseoptimismpolygon
Detected byslithermythrilechidnahound-ai

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.

What Is Delegatecall Injection?

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:

  • Proxy pattern abuse: Attacker supplies malicious implementation address
  • Library hijacking: User-controlled library call targets attacker's contract
  • Function routing: Contract routes function calls via delegatecall to untrusted address
  • Initialization bypass: delegatecall in constructor can initialize proxy to attacker's code

The vulnerability is critical because:

  1. Full storage access: Attacker modifies all state variables (balances, ownership, etc.)
  2. Arbitrary code: Attacker's contract executes with victim contract's permissions
  3. Context preservation: Attacker receives msg.sender and msg.value as if they were the victim contract
  4. Immediate exploitation: No time window—instant complete takeover

How Delegatecall Injection Works

Delegatecall injection unfolds in simple steps:

  1. Identify delegatecall: Find delegatecall(target, ...) with user-controlled target
  2. Craft malicious contract: Create contract with function matching expected signature
  3. Execute exploit: Call vulnerable function, supplying attacker's contract address
  4. Hijack state: Attacker's delegatecall executes, modifying victim contract state
  5. Extract value: Transfer funds, mint tokens, or change ownership to attacker
// 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
}

Real-World Delegatecall Injection Exploits

| 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 |

How to Detect Delegatecall Injection

Manual detection focuses on delegatecall usage patterns:

  • Delegatecall calls: Identify all delegatecall instructions
  • Target validation: Verify target address is hardcoded or strictly validated
  • User control: Flag delegatecall targets derived from user input
  • Proxy patterns: Confirm implementation addresses are immutable or tightly governed
  • Library calls: Ensure library addresses come from trusted sources only
  • Storage layout: Verify proxy and implementation share compatible storage layout

Red flags:

  • delegatecall(target, ...) with user-supplied target
  • Implementation address changeable by non-admin
  • No whitelist of allowed implementation addresses
  • delegatecall in fallback without validation
  • Library addresses passed as function parameters

How Firepan Detects Delegatecall Injection Automatically

Firepan's HOUND AI performs comprehensive delegatecall analysis:

  1. Delegatecall enumeration: Identifies all delegatecall instructions
  2. Target source analysis: Traces where delegatecall target addresses originate
  3. Control flow verification: Confirms targets don't depend on user input
  4. Taint analysis: Flags targets influenced by external data
  5. Proxy pattern validation: Confirms storage layout compatibility
  6. Access control check: Verifies implementation updates have proper authorization

Firepan's HOUND AI engine identifies delegatecall injection paths across all monitored contracts.

Prevention Best Practices

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)
    {}
}

Frequently Asked Questions

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.

Conclusion

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

Scan Your Contracts Now

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 →