# Ethernaut Lvl 13 Gatekeeper 1 Walkthrough: How to calculate smart contract gas consumption (and byte masking)

## This is a in-depth series around Zeppelin team’s smart contract security puzzles. We learn key Solidity concepts to solve the puzzles 100% on your own.

Published in
5 min readSep 4, 2018

--

In this level, you estimate gas and mask your bytes to pass three different gates. The concepts behind Gate 1 is explained here in detail.

# How to count gas

In Ethereum, computations cost money. This is calculated by `gas * gas price`, where `gas` is a unit of computation and `gas price` scales with the load on Ethereum network. The transaction sender needs to pay the resulting ethers for every transaction she/it invokes.

Complex transactions (like contract creation) costs more than easier transactions (like sending someone some Ethers). Storing data to the blockchain costs more than reading the data, and reading constant variables costs less than reading storage values.

## Stepping through Solidity assembly (Gate 2)

Specifically, `gas` is assigned at the assembly level, i.e. each time an operation happens on the call stack. For example, these are arithmetic operations and their current gas costs, from the Ethereum Yellow Paper (Appendix H):

Let’s use Remix IDE to step through the following simple contract:

`pragma solidity ^0.4.24;contract SimpleContract {    function add() public pure returns (uint) {        uint a = 1;         uint b = 2;         return (a+b);    }}`

In Javascript VM, deploy this contract, invoke `add()`, and click the on the `debug` enabler in Remix console to bring up debugger UI:

Enable both Instructions and Step detail drop downs as you step into each assembly opcode.

Notice how the gas values in Step detail correspond to the opcodes chart in the yellow paper. In this case, the opcode `ADD` costs 3 gas, as predicted.

## Important to know

Different Solidity compiler versions will calculate gas differently. And whether or not optimization is enabled will also affect gas usage. Try changing the compiler defaults in `Settings` tab to see how remaining gas will change.

Before starting this level, make sure you have configured Remix to the correct compiler version.

# Datatype conversions

The second piece of knowledge you need to solve this level is around data conversions. Whenever you convert a datapoint with larger storage space into a smaller one, you will lose and corrupt your data.

## Byte masking (Gate 3)

Conversely, if you want to intentionally achieve the above result, you can perform byte masking. Solidity allows such bitwise operations for bytes and ints as follows:

`bytes4 a = 0xffffffff;bytes4 mask = 0xf0f0f0f0;bytes4 result = a & mask ;   // 0xf0f0f0f0`

You are now ready to solve this level!

# Detailed Walkthrough

## Pass Gate 1

1. Similar to Ethernaut level 4, you can pass Gate 1 by simply letting your contract be the middleman. Create a contract called `Hack.sol` which accesses your level instance:
`contract Hack {    GatekeeperOne gate = GatekeeperOne(//YOUR ADDR);    ...}`

## Pass Gate 3

2. Gate 3 takes in an 8 byte key, and has the following requirements:

`require(uint32(_gateKey) == uint16(_gateKey));require(uint32(_gateKey) != uint64(_gateKey));require(uint32(_gateKey) == uint16(tx.origin));`

This means that the integer key, when converted into various byte sizes, need to fulfil the following properties:

• `0x11111111 == 0x1111`, which is only possible if the value is masked by `0x0000FFFF`
• `0x1111111100001111 != 0x00001111`, which is only possible if you keep the preceding values, with the mask `0xFFFFFFFF0000FFFF`

3. Calculate the key using the`0xFFFFFFFF0000FFFF` mask:

`bytes8 key = bytes8(tx.origin) & 0xFFFFFFFF0000FFFF;`

## Pass Gate 2

Finally, to pass Gate 2’s `require(msg.gas % 8191 == 0)`, you have to ensure that your remaining gas is an integer multiple of `8191`, at the particular moment when `msg.gas % 8191` is executed in the call stack.

4. Since you’ll be calculating gas, first figure out your contract instance’s compiler version & settings. In Etherscan, look up your contract instance by its address.

5. Notice GatekeeperOne was compiled with `version v0.4.18` with `no optimization` enabled. Update your Remix settings accordingly.

6. Create a function which will call `enter()` and allocate a specified amount of gas. You should invoke enter() with the lower level `call` function, which gives you more control over gas usage. Allocate some arbitrary amount of gas:

`function hackGate() public {    gate.call.gas(99999)(bytes4(keccak256('enter(bytes8)')), key);}`

7. Step through the Remix debugger in JavaScript VM until you reach the correct opcode (when the Remix IDE highlights over `msg.gas % 8191`).

8. Count the remaining gas and work backwards to arrive at the correct original gas allocation. And replace the call gas allocation with your new value.

Note: You may have to do step 8 for 1–2 iterations before your remaining `gas % 8191` becomes 0.

6. Toggle Remix back to Injected Web3 and you should now be able to pass GatekeeperOne!

# Key Security Takeaways

• Abstain from asserting gas consumption in your smart contracts, as different compiler settings will yield different results.
• Be careful about data corruption when converting data types into different sizes.
• Save gas by not storing unnecessary values. Pushing a value to state `MSTORE`, `MLOAD` is always less gas intensive than store values to the blockchain with `SSTORE`, `SLOAD`
• Save gas by using appropriate modifiers to get functions calls for free, i.e. `external pure` or `external view` function calls are free!
• Save gas by masking values (less operations), rather than typecasting

# More Levels

Get Best Software Deals Directly In Your Inbox

--

--