delegatecall

Understanding delegatecall in Solidity: What It Is, How It Works, and When to Use It

delegatecall

|
5 min read

delegatecall is one of the most important low-level features in Solidity.

It is powerful, but it can also be dangerous if used carelessly.

For a beginner, the easiest way to understand it is this:

delegatecall lets one contract run the code of another contract, but keep using its own storage, its own address, and its own balance.

That one sentence is the core idea.

What does delegatecall do?

Normally, if Contract A calls Contract B, then Contract B runs its own code in its own context.

But with delegatecall, Contract A can run Contract B’s code as if that code belonged to Contract A.

This means:

  • the code comes from Contract B
  • the storage used is from Contract A
  • msg.sender stays the original caller
  • msg.value also stays the same

So delegatecall is mainly about borrowing code while keeping your own data.

Why is this useful?

The main reason is upgradeability.

A smart contract normally cannot be changed after deployment. But developers often want to improve logic later. One common solution is:

  • deploy a proxy contract
  • deploy a separate implementation contract
  • let the proxy use delegatecall to run the implementation’s code

This way, users keep interacting with the proxy, but the logic behind it can be changed by pointing to a new implementation contract.

It is also sometimes used in:

  • contract modular design
  • reusable shared logic
  • plugin-like systems

But the most common real-world use is still proxy upgrade patterns.

How does delegatecall behave?

Here is what makes it special:

When contract code is executed through delegatecall:

  • address(this) is the caller contract’s address
  • storage writes go to the caller contract
  • events are emitted from the caller contract
  • the external caller remains msg.sender

This is different from a normal call.

A normal call says:

“Go to that contract and let it do the work.”

A delegatecall says:

“Take that contract’s code and run it here.”

A simple example

Below is a small example.

Logic contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Logic {
    uint256 public number;

    function setNumber(uint256 _number) external {
        number = _number;
    }

    function addOne() external {
        number += 1;
    }
}

Proxy-like contract using delegatecall

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleProxy {
    address public implementation;
    uint256 public number;

    constructor(address _implementation) {
        implementation = _implementation;
    }

    function setImplementation(address _implementation) external {
        implementation = _implementation;
    }

    function delegateSetNumber(uint256 _number) external {
        (bool success, ) = implementation.delegatecall(
            abi.encodeWithSignature("setNumber(uint256)", _number)
        );
        require(success, "delegatecall failed");
    }

    function delegateAddOne() external {
        (bool success, ) = implementation.delegatecall(
            abi.encodeWithSignature("addOne()")
        );
        require(success, "delegatecall failed");
    }
}

What happens in this example?

Suppose:

  • SimpleProxy is deployed
  • Logic is deployed
  • SimpleProxy.implementation points to Logic

Now if a user calls:

delegateSetNumber(10)

the code of Logic.setNumber() runs, but the value is written into the storage of SimpleProxy, not Logic.

So after the call:

  • SimpleProxy.number becomes 10
  • Logic.number does not change

That is the key point.

The logic comes from Logic, but the data lives in SimpleProxy.

Storage layout is very important

This is the part beginners must remember carefully:

With delegatecall, the storage layout must match correctly.

Look at this proxy:

address public implementation; // slot 0
uint256 public number; // slot 1

If the logic contract has:

uint256 public number; // slot 0

then there is a problem.

Why? Because when the logic writes to its number, it writes to slot 0. But in the proxy, slot 0 is implementation, not number.

That means the logic could accidentally overwrite the implementation address.

That is a serious bug.

So the rule is:

When using delegatecall, storage layout compatibility matters more than variable names.

What must match is the storage position and type.

A safer matching example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract MatchingLogic {
    address public implementation; // slot 0
    uint256 public number; // slot 1

    function setNumber(uint256 _number) external {
        number = _number;
    }
}

Now the layout matches this proxy:

address public implementation; // slot 0
uint256 public number; // slot 1

In this case, when setNumber() runs through delegatecall, it writes to the correct slot.

Main use cases of delegatecall

Upgradeable proxy contracts

This is the most important use.

Users interact with the proxy address, while the logic can be replaced later.

Shared logic

Multiple contracts can reuse the same logic contract, while each keeps its own storage.

Splitting large systems

A project can separate logic into parts to keep contracts more organized.

Risks of delegatecall

delegatecall is powerful, but it has clear risks:

  • storage collision
  • accidental overwrite of important variables
  • calling malicious implementation contracts
  • hard-to-find bugs from wrong layout design

Because of this, developers usually use tested patterns and libraries, such as OpenZeppelin proxy contracts, instead of writing everything from scratch.

Final summary

delegatecall allows a contract to execute another contract’s code while keeping:

  • its own storage
  • its own address
  • the original caller
  • the original ETH value

Its main real-world use is upgradeable smart contracts.

The biggest thing to understand is this:

delegatecall changes code source, but not data location.

That is why it is useful, and that is also why it is risky.

For beginners, if you remember only one thing, remember this:

With delegatecall, code runs from another contract, but storage is still your own.

Tagsdelegatecall

© 2026 Tony Ye. All rights reserved.