Delegate Call Return Value: How it works?

by ImmuneBytes
delegate call

We are back with the next part of our series All About Smart Contract Bugs & Security A cakewalk series, discussing with you a bug that might not look harmful while deploying a smart contract but drained out $280M, Delegate Call Return Value. 

Make sure to check out the other parts in the series wherein we talk about Stack Size Limit, Transaction Ordering Dependency, and many other bugs that can be fatal to your smart contracts.

Often, while developing Ethereum Smart Contracts, there are scenarios where we need to interact with other contracts. Solidity provides several ways to achieve this particular goal. It’s simpler when one is aware of the target contract’s ABI (description of the contract’s interface). But what happens when the ABI is not known?

For this very purpose, the DelegateCall function is used.

Precisely, DelegateCall, as the name suggests, is the calling mechanism of how caller contract calls target contract function but when target contract executes its logic, the context is not on the user who executes caller contract but on caller contract.

To define it more simply, let’s assume there is a Proxy contract and a Business contract. Proxy contract delegateCall to Business contract function. If the user calls Proxy contract, it will then delegateCall to Business contract and the function would be executed successfully. All state changes, however, will be reflected in Proxy contract storage, not the Business contract.

How does Delegatecall harm smart contracts?

The low-level delegatecall() does not return the execution outcome but converts the value returned by the function called to a boolean instead. 

At the lowest level, the delegate call function takes the call data of another function and returns a Boolean value to determine whether or not the call was successful. The return value of the delegate call is simply the data interpreted as a Boolean. If the parameter function returns at least 32 zero bytes, the delegate call will always return false even if the call did not throw an exception.

In simpler terms, if another function’s call data contains 32 zero bytes when the delegate call function is executed, it will always return false regardless of whether an exception is thrown.

Internally, booleans are stored as integer values in the memory and since the EVM does not check if a function returned a correct value which is still “false” as a Boolean, it interprets the function as failed. Let’s take a look at the implementation of this vulnerability in a smart contract.

contract TestContract{

unit value;

function set(uint_value) external{

value =- value;

}

function getdelegated() external constant returns (bools)

{

return this.delegatecall(bytes4(sha3(“get()”)));

}

 

}

In the above smart contract code, the function getdelegated() contained in the contract would return a false Boolean despite it being of perfectly legal syntax and only being passed an SHA-3 hash of the value variable.

This bug would cause a smart contract to fail, that was otherwise written as it should. The function is extensively used in Solidity libraries and was discovered in an Ethereum client.

A real-life example: The $280M Ethereum’s Parity bug

On July 22, 2017, Parity had informed its users and developers about a bug that got “accidentally” triggered which resulted in freezing more than $280M worth of ETH, including $90M belonging to Parity’s Founder & Ethereum former core developer: Gavin Woods.

Here’s a simpler version of the Parity smart contract code that was exploited by the hacker.

contract Wallet {

    address _walletLibrary;

    address owner;

 

    function Wallet(address _owner) {

        _walletLibrary = <address of pre-deployed WalletLibrary>;

        _walletLibrary.delegatecall(bytes4(sha3(“initWallet(address)”)), _owner);

    }

 

    function withdraw(uint amount) returns (bool success) {

        return _walletLibrary.delegatecall(bytes4(sha3(“withdraw(uint)”)), amount);

    }

 

    function () payable {

        _walletLibrary.delegatecall(msg.data);

    }

}

Notice the use of delegatecall() throughout the contract. Any storage writes inside the delegatecall will be made to the storage of the client, not the storage of the library. delegatecall() allows a client contract to delegate the responsibility of handling a call to another contract. This was the vulnerability that was exploited, consequently freezing the funds.

This is ImmuneBytes’ take on the Delegate Call Return Value bug along with a real-life example to show you how it can weaken your smart contracts. Connect with our team to get your smart contract free of any vulnerabilities and loopholes such as this one.

Tune in next Thursday for Part-VIII of this series wherein we discuss Race conditions in smart contracts.

Spread the love

You may also like