Sitemap

Delegatecall() Vulnerability

3 min readJul 30, 2022

--

What is it?

Delegatecall() is a function used within solidity to activate the code of another contract. It is often used with libraries which are code repositories that are deployed on the blockchain. These libraries serve the purpose of re-using code, which makes the process of creating contracts simpler.

Delegatecall() is a low level function similar to call() which means it works when fallback () receive() is implemented, however, Delegatecall() differs from call() in a few ways. For example, when a contract deploys Delegatecall() it maintains the variables stored in its own despite calling a new contract. Reads and writes to state variables happen to the contract that loads and executes functions with Delegatecall(). Reads and writes never happen to the contract that holds functions that are retrieved. This also means that if I use Delegatecall() to send ETH, I’m using the ETH balance from the sender contract NOT the contract that is called upon.

There are 2 ways to complete a Delegatecall()

1) Abi.encodewithsignature

2) Abi.encodewithselector

The ABI is the standard way to interact with contracts in the Ethereum ecosystem. Both of these encodings create payloads in raw bytes for external contract calls, from outside the blockchain and for contract-to-contract interaction

They can be used interchangeably to obtain the function you’d like to call, e.g) abi.encodewithselector uses the bytes4 function selector of the function you want.

Side note, when making external calls, Solidity adds an extra check using the “extcodesize” opcode. This guarantees that either an exception is raised or that the contract that is about to be called genuinely exists (it contains code).

Low-level calls like call(), delegatecall(), staticcall(),send(), and transfer() that operate on addresses rather than contract instances do not do this check, making them gas-efficient but less secure.

Get Morayo Akran’s stories in your inbox

Join Medium for free to get updates from this writer.

How to use it?

As we mentioned, the contract that is called upon using Delegatecall() maintains the same state variables. But in order for the variables of the caller contract to be updated, the state variables must be inserted diligently in both contracts.

Here is an example of how state variables should be written within the two contracts;

As you can see, Contract B and C both contain the same state variables in their code. It is these state variables that are permanently stored in a blockchain/Ethereum ledger by miners, you can identify them as they are always declared outside of a function.

Additionally, it is important that the state variables are listed in the same order in both contracts. If not ,this may lead to an unintended variable being updated. For example, if contract B had value as its first state variable it would update this value NOT msg.sender as listed in the setVariable() function of Contract C.

Examples of sabotage

Delegatecall() must be used carefully or it may lead to a contract being sabotaged. Here is an example of how this may take place;

The owner of contract Vulnerable can be compromised by triggering the fallback() function and calling the pwn function of Contract Lib. Since we are already aware that delegatecalls only change the state variable of the sender contract, contract Lib is not vulnerable to an attack.

We still need to construct a contract that is able to execute the fallback() function of contract Vulnerable.

Since fallback functions can only be ran when a called function doesn’t exist, contract Attack calls “pwn” as a function to trigger the arguments within fallback() of Vulnerable contract.

When this happens, Lib is called via Delegatecall() and the functions within Lib are prompted which enables the substitution of the owner of contract Vulnerable. From this stage, an attacker can change any variable or execute selfdestruct() which is a function that can be deployed to completely wipe a contract from the blockchain.

This is an illustration of the threats that may surface if one forgets delegatecall() preserves context. When you make a delegatecall() the storage of your contract is at the mercy of whoever is able to call the function, so ensure that only trusted code is executed.

--

--

Morayo Akran
Morayo Akran

Written by Morayo Akran

Blockchain Security Enthusiast