Ethernaut - Lvl 6: Delegation
Tags: ethereum, security, tutorial, smartcontract
Published: 2023-06-05 17:59:09

###### _Requirements: understanding of delegatecall, fallback special function and methods ID._

## The challenge 🤼‍♀️🤼

Claim ownership of `Delegation`:

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

contract Delegate {

  address public owner;

  constructor(address _owner) {
    owner = _owner;
  }

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

contract Delegation {

  address public owner;
  Delegate delegate;

  constructor(address _delegateAddress) {
    delegate = Delegate(_delegateAddress);
    owner = msg.sender;
  }

  fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}
```

## Inspecting the contracts 🔎🔍

`Delegate.sol` contains:
- `owner`: address of the account that owns the contract.
- `constructor`: receives an address and set it as the owner of the contract at creation time.
- `pwn`: public function to update `owner` as `msg.sender`.

`Delegation.sol` contains:
- `owner`: address of the account that owns the contract.
- `delegate`: instance of `Delegate`, this variable is of type `Delegate`.
- constructor: receives the address of `delegate` and initiliazes an instance of `Delegate`. Set `msg.sender` as `owner`.
- `fallback`: special function that makes a `delegatecall` to `Delegate`.

## Delegatecall and Fallback ☎️▶️

`Delegation` does not have a method that updates `owner` after this variable has been initiliazed inside the `constructor`. But we can see that in `Delegate` there is a method to update the owner in that contract.

The `fallback` function in `Delegation` contains logic to make a `delegatecall` to `Delegate` with some encoded data.

`delegatecall` is a low-level function in Solidity used to make external calls to another contracts. Let's say we call a function in a contract A which delegates the call to a function in a contract B. **`delegatecall` will load and run the logic of the function in contract B in the context of A, using the storage of contract A and for this to work contract A needs to share the same storage layout as B, otherwise we will end up writing to the incorrect slots messing with the storage of contract A**.

Let's review the following example:

```solidity
contract Delegatecall {
    bool public status; // slot 0
    uint256 public num; // slot 1

    function updateNum(uint256 _num) external {
        num = _num;
    }
}

contract BadDelegatecall {
    uint256 public num; // slot 0
    bool public status; // slot 1

    // A `delegatecall` would fail because `BadDelegatecall`
    // and `Delegate` does not share the same storage layout.
}

contract GoodDelegatecall {
    bool public status; // slot 0
    uint256 public num; // slot 1

    // A `delegatecall` would succeed because `GoodDelegatecall`
    // and `Delegate` share the same storage layout. 
}
```

If we make a `delegatecall` from `BadDelegatecall` to update `num`, this would fail because the variable is in different slots. We would end up writing the value of `num` to `status` in `BadDelegatecall`. `delegatecall` will succeed in `GoodDelegatecall` because in this case `num` is in the same slot in both contracts.

`delegatecall` also maintains `msg.sender` between external calls. Let's review the following simple chain call:

```
EOA ---> contract A ---> contract B

`msg.sender` in A and B is the EOA.
```

The `fallback` according to the Solidity [docs](https://docs.soliditylang.org/en/latest/contracts.html#fallback-function):

> The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable.

## The hack 🌌

We need to trigger the `fallback` so that it makes a `delegatecall` to `Delegate` to run the logic of `pwn` in `Delegation` to update `owner` with our address.

We just need to figure out how to target the function `pwn`.

When smart contracts are compiled into bytecode, the functions are encoded with an method id, called as `selector`, that acts as an unique identifier so that the EVM can identify the method to call. The signature of a function consists of its name and the types of its parameters, for example, for `function transfer(address _to, uint _amount)` the function signature is `transfer(address,uint)`, no spaces are used.

The `selector` of `pwn` is what we need to send in our payload for the EVM.

Ok, let's exploit this level. Request a new instance.

Compute the selector of `pwn`:

```solidity
selector = web3.eth.abi.encodeFunctionSignature("pwn()")
```

Send a raw transaction to `Delegation` with the `selector` as `data`:

```solidity
await sendTransaction({from: player, to: contract.address, data: selector})
```

After the transaction in mined, check the owner of `Delegation`:

```solidity
await contract.owner() // should return your address
```

Submit the instance to complete the level.

## Conclusion 💯

`Delegatecall` is a powerful function in Solidity, it allow us to dynamically load code at runtime from a different address, maintaining the `msg.sender` between external calls.

Not properly understanding or usage of `delegatecall` can make a contract vulnerable to exploits because its storage is used by a different contract to execute external logic.

We also need to be careful when calling methods in a contract that implement `delegatecall` because we will be the `msg.sender` in the callee contract authorizing it to execute its logic with our signature.

## Further reading 👀

- [Fallback Function](https://docs.soliditylang.org/en/latest/contracts.html#fallback-function)
- [Delegatecall and Libraries](https://docs.soliditylang.org/en/v0.8.20/introduction-to-smart-contracts.html#delegatecall-and-libraries)
- [Solidity Security: Comprehensive list of known attack vectors and common anti-patterns](https://blog.sigmaprime.io/solidity-security.html#delegatecall)
- [SWC-112: Delegatecall to Untrusted Callee](https://swcregistry.io/docs/SWC-112)