Logo

dev-resources.site

for different kinds of informations.

The delegatecall Function in Solidity

Published at
11/18/2024
Categories
evm
web3
solidity
programming
Author
ayoashy
Categories
4 categories in total
evm
open
web3
open
solidity
open
programming
open
Author
7 person written this
ayoashy
open
The delegatecall Function in Solidity

The delegatecall method is a powerful low-level function that enables one contract (the "Caller") to execute code in another contract (the "Callee") while preserving the storage and context of the Caller. Unlike the call method, which executes code in a different contract but does not retain the Caller’s state, delegatecall maintains the context of the Caller’s storage, allowing for more flexible contract design. However, as with any low-level operation, improper use of delegatecall can introduce significant security risks. For a deeper understanding of the call method, be sure to check out my previous article, The call Function in Solidity.

What is delegatecall?

The delegatecall function in Solidity enables a contract to call another contract’s function but retain its own storage, sender, and msg.value. This is useful when you want to separate logic from storage, allowing the Caller to use the Callee’s logic while maintaining its own state.

Common Use Cases:

  • Upgradeable Contracts: The Proxy pattern uses delegatecall to upgrade contract logic without affecting stored data.

  • Modular Contracts: Break down complex functionality into separate contracts to improve code reusability and maintainability.

Basic Example of delegatecall

In this example, the Caller contract calls setNum in the Callee contract to set a number. The state change happens in the Caller’s storage rather than the Callee’s.

Callee Contract

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

contract Callee {
    uint public num;

    function setNum(uint _num) public {
        num = _num;
    }
}
Enter fullscreen mode Exit fullscreen mode

Caller Contract

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

contract Caller {
    uint public num;
    address public calleeAddress;

    uint public num; // State variable that MUST be in storage slot 0 to matche the storage layout of the Callee contract

    function setCalleeAddress(address _calleeAddress) public {
        calleeAddress = _calleeAddress;
    }

        /**
     * @dev Calls the setNum function of the Callee contract using delegatecall.
     * The logic of the Callee's setNum function is executed in the context of the Caller contract.
     * This updates the `num` variable in the Caller contract's storage, NOT the Callee's.
     * 
     * NOTE: The `num` variable in both the Caller and Callee contracts MUST occupy the same storage slot.
     * In this case, `num` is the first declared state variable in both contracts, which means it is in slot 0.
     * 
     * @param _num The value to set the Caller contract's `num` variable to.
     */
    function delegateSetNum(uint _num) public {
        (bool success, ) = calleeAddress.delegatecall(
            abi.encodeWithSignature("setNum(uint256)", _num)
        );
        require(success, "delegatecall failed");
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. The setCalleeAddress function stores the address of the Callee contract.

  2. delegateSetNum uses delegatecall to execute setNum in Callee, but any state changes are applied in Caller’s storage.

Risks and Best Practices of Using delegatecall

While delegatecall is powerful, improper use can introduce serious security risks. Here’s what to consider:

  1. Storage Layout Consistency

    • The storage layout between the Caller and Callee must match exactly. Any mismatch can cause unexpected behavior and data corruption.
  2. Reentrancy Attacks

    • Using delegatecall with untrusted contracts can expose vulnerabilities, such as reentrancy attacks. Apply the checks-effects-interactions pattern to minimize risk.
  3. Avoid Unauthorized Access

    • Ensure delegatecall does not inadvertently expose sensitive functions. Use access control to limit function calls as needed.

Example of a Security Vulnerability

In the following example, HackMe calls Lib's pwn function with delegatecall, allowing anyone to take over the HackMe contract by changing the owner.

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

contract Lib {
    address public owner;

    function pwn() public {
        owner = msg.sender;
    }
}

contract HackMe {
    address public owner;
    Lib public lib;

    constructor(Lib _lib) {
        owner = msg.sender;
        lib = Lib(_lib);
    }

    // Delegate all external calls to Lib contract, including unauthorized access to pwn
    fallback() external payable {
        address(lib).delegatecall(msg.data);
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Anyone calling pwn can change the owner of HackMe because HackMe uses Lib’s storage layout, unintentionally exposing its owner variable.

Practical Tips for Using delegatecall Safely

To maximize the benefits of delegatecall while minimizing risks, follow these tips:

  1. Check Storage Compatibility: Ensure that the Caller and Callee contracts have matching storage layouts to avoid corruption.

  2. Validate delegatecall Targets: Only use trusted contracts as targets for delegatecall.

  3. Use Access Control: Restrict access to functions that use delegatecall to avoid unauthorized actions.

Conclusion

The delegatecall function in Solidity provides flexibility for modular and upgradeable smart contracts. However, due to its powerful nature, it’s essential to use delegatecall with caution, carefully managing storage layouts and implementing security checks. Proper use of delegatecall can open up many possibilities for flexible contract development, but with poor implementation, it can introduce serious vulnerabilities.

evm Article's
30 articles in total
Favicon
Ever wonder what happens when you send a transaction on Ethereum? 👀
Favicon
TEEs: The Secret Sauce Making Ethereum Rollups Faster and Simpler
Favicon
How to List Held Tokens by an Address Using the Moralis API
Favicon
Ethereum Transaction Calls and State Changes
Favicon
Creating a Toy Solidity compiler and running it in a Toy EVM
Favicon
The delegatecall Function in Solidity
Favicon
The delegatecall Function in Solidity
Favicon
ERC-4337 Shared Mempool: Official Launch on Ethereum Mainnet, Arbitrum and Optimism
Favicon
Understanding Fallback and Receive Functions in Solidity
Favicon
vyper挺好玩的
Favicon
Understanding EVM(Ethereum Virtual Machine)
Favicon
designing the skeleton
Favicon
building free speech forever
Favicon
I've made Huff docs Simple Storage page simpler for newcomers
Favicon
Polygon Nodes: Types and Usage
Favicon
BasilicaEVM: A modern dApp Stack
Favicon
EVM Reverse Engineering Challenge 0x02
Favicon
EVM Reverse Engineering Challenge 0x03
Favicon
Implementing Earned Value Management EVM in Government Projects
Favicon
EVM Reverse Engineering Challenge 0x01
Favicon
EVM Reverse Engineering Challenge 0x00
Favicon
Monad: Diving Deep into the L1 Choice
Favicon
Confidential Smart Contracts & Building w/Oasis Sapphire
Favicon
How To Setup A Berachain React Native Expo dApp With WalletConnect
Favicon
Chainsight Hands-on: Subscribe to On-Chain Events & Collect
Favicon
Creating a multi-chain voting in 30 minutes with Chainsight
Favicon
Oráculos Descentralizados em Blockchain: Conectando o Mundo Real à Blockchain.
Favicon
Swisstronick's Innovative Use Cases
Favicon
How Binary Heaps Are Utilized In A Leveraged Trading Protocol On EVM
Favicon
Deploy a Smart Contract on Polygon (MATIC)

Featured ones: