## DEV Community

Erhan Tezcan

Posted on • Updated on

# EVM Puzzles - Walkthrough

EVM Puzzles are a collection of puzzles by Franco Victorio. In this Markdown file, I wrote down the walkthrough for each of them.

It is crucial to understand the storage structure of EVM, I refer to this article as a good resource on this. Most importantly, you should be familiar on how the stack works, and how EVM instructions use it. See the original documentation about EVM at https://ethereum.org/en/developers/docs/evm/.

Whenever you see a new instruction, you should immediately go and see what it is and what it does. I have used https://www.evm.codes/ and https://www.ethervm.io/ as my resources. For more information about EVM, you can also see their pages: https://www.evm.codes/about and https://www.ethervm.io/#overview.

Throughout this walkthrough all numbers that start with `0x` are hexadecimal, and all that do not are decimal. Furthermore, bytecodes are given in hexadecimal, and two hexadecimals are equal to a byte, as 4 bits x 2 = 8 bits = 1 byte.

In all puzzles, the objective is to have the code `STOP`, rather than `REVERT`. You basically have to get to the end!

One final remark: please try to solve each of these before looking at the solution! This is a great way to learn about EVM, and solving these is a rewarding feeling. Once you learn an instruction on your own, the rest will be easier; it is the first steps that is hard to take at such a low-level context. Good luck!

## Puzzle 1

``````00      0x34      CALLVALUE
01      0x56      JUMP
02      0xFD      REVERT
03      0xFD      REVERT
04      0xFD      REVERT
05      0xFD      REVERT
06      0xFD      REVERT
07      0xFD      REVERT
08      0x5B      JUMPDEST
09      0x00      STOP
``````

`JUMP` jumps to the destination specified by the top value in the stack. `CALLVALUE` pushes the call value to the stack. Looking at the code, there is a `JUMPDEST` at 8, so our call value must be 8.

## Puzzle 2

``````00      0x34      CALLVALUE
01      0x38      CODESIZE
02      0x03      SUB
03      0x56      JUMP
04      0xFD      REVERT
05      0xFD      REVERT
06      0x5B      JUMPDEST
07      0x00      STOP
08      0xFD      REVERT
09      0xFD      REVERT
``````

Here is a new instruction: `CODESIZE`. It pushes the size of code to the stack. Which code? Well, it is the puzzle code itself, which we can see has `10` bytes (the last line is `09` but remember that it starts from `00`).

The `SUB` instruction takes the top two values, subtracting the second one from the first. So basically, it calculates `CODESIZE - CALLVALUE`. We can see a `JUMPDEST` at `06`. So, we have the equation `CODESIZE - CALLVALUE = 10 - CALLVALUE = 6`.

Our `CALLVALUE` must be 4 Wei.

## Puzzle 3

``````00      0x36      CALLDATASIZE
01      0x56      JUMP
02      0xFD      REVERT
03      0xFD      REVERT
04      0x5B      JUMPDEST
05      0x00      STOP
``````

`CALLDATASIZE` pushes the size of calldata (bytes) to the stack. There is a `JUMPDEST` at 4, so the size should be 4 bytes. Any arbitrary 4-byte calldata would suffice: `0x11223344`.

## Puzzle 4

``````00      0x34      CALLVALUE
01      0x38      CODESIZE
02      0x18      XOR
03      0x56      JUMP
04      0xFD      REVERT
05      0xFD      REVERT
06      0xFD      REVERT
07      0xFD      REVERT
08      0xFD      REVERT
09      0xFD      REVERT
10      0x5B      JUMPDEST
11      0x00      STOP
``````

`CODESIZE` is 12 and `JUMPDEST` is at 10. `XOR` is a bitwise operation that stands for exclusive-or operation. Here is its logic table:

a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0

Denoting `XOR` as `^` (as is the case in many programming languages) we need some value such that `CALLVALUE ^ 12 = 10`. Simple arithmethic yields `CALLVALUE = 10 ^ 12 = 6`.

## Puzzle 5

``````00      0x34          CALLVALUE
01      0x80          DUP1
02      0x02          MUL
03      0x610100      PUSH2 0x0100
06      0x14          EQ
07      0x600C        PUSH1 0x0C
09      0x57          JUMPI
10      0xFD          REVERT
11      0xFD          REVERT
12      0x5B          JUMPDEST
13      0x00          STOP
14      0xFD          REVERT
15      0xFD          REVERT
``````

Here we have a `JUMPI` which is a conditional jump. `PUSH1 0x0C` above it provides the correct destination address, so all we have to care about is that the condition value be non-zero.

Looking at the lines above in order:

1. `CALLVALUE` pushes the value to the stack.
2. `DUP1` duplicates it, so there are two of the same value in the stack.
3. `MUL` multiplies these two, so we basically squared the call value.
4. `PUSH2 0x0100` pushes `0x0100` to the stack, which is `16 ^ 2` in decimals.
5. `EQ` compares the top two items in the stack, which is `16 ^ 2` and the square of our callvalue! Therefore, giving a callvalue of 16 is the winning move.

## Puzzle 6

``````00      0x6000      PUSH1 0x00
03      0x56        JUMP
04      0xFD        REVERT
05      0xFD        REVERT
06      0xFD        REVERT
07      0xFD        REVERT
08      0xFD        REVERT
09      0xFD        REVERT
10      0x5B        JUMPDEST
11      0x00        STOP
``````

`CALLDATALOAD` loads a 32-byte value at the specified byte offset. If 32-bytes go beyond the length of calldata, the overflowing bytes are set to 0.

The offset to be loaded is given by the top value in the stack, which is given by `PUSH1 0x00` above. Basically, the calldata itself should have the destination address for `JUMP`. Our `JUMPDEST` is at `0x0A`, so that is our calldata!

But wait, remember that overflowing bytes are set to 0. So if we just send `0x0A`, the remaining 31 bytes will be `00` and we will have `0x0A00000000000000000000000000000000000000000000000000000000000000` which is a huge number!

Instead, we must do zero-padding to the left, and send `0x000000000000000000000000000000000000000000000000000000000000000A` as our calldata. This way, reading 32-bytes from the zero offset will yield `0x0A`.

## Puzzle 7

``````00      0x36        CALLDATASIZE
01      0x6000      PUSH1 0x00
03      0x80        DUP1
04      0x37        CALLDATACOPY
05      0x36        CALLDATASIZE
06      0x6000      PUSH1 0x00
08      0x6000      PUSH1 0x00
10      0xF0        CREATE
11      0x3B        EXTCODESIZE
12      0x6001      PUSH1 0x01
14      0x14        EQ
15      0x6013      PUSH1 0x13
17      0x57        JUMPI
18      0xFD        REVERT
19      0x5B        JUMPDEST
20      0x00        STOP
``````

The first 4 lines basically copy the entire calldata into memory. The next 4 lines create a contract, where the initialization code is taken from the memory at the position our calldata was just loaded. In other words, the first 10 lines create a contract with our calldata.

Afterwards, the next 3 lines check if the `EXTCODESIZE` is equal 1 byte. The contract that we are checking the code size is the one we just created above with `CREATE`, as `CREATE` pushes the address to the stack. `EXTCODESIZE` is the size of the runtime code of a contract, not the initialization code! The puzzle expects this to be 1 byte (lines `0C` and `0E`). So we just have to write our own initialization code to do all this.

The instruction for this is `CODECOPY`, which works similar to `CALLDATACOPY`. The initialization code will be as follows:

``````PUSH1 0x01 // 1 byte
PUSH1 ;;;; // position in bytecode, we dont know yet
PUSH1 0x00 // write to memory position 0
CODECOPY   // copies the bytecode
PUSH1 0x01 // 1 byte
PUSH1 0x00 // read from memory position 0
RETURN     // returns the code copied above
``````

In terms of bytecode, this results in `0x60 01 60 ;; 60 00 39 60 01 60 00 F3`. This is a total of 12 bytes, so the `;;;;` position will be 12, which is `0x0C`. The final bytecodes are: `0x6001600C60003960016000F3`.

This code copies 1 byte code into memory, and returns it to EVM so that contract creation is completed. The actual runtime code is arbitrary, it just has to be 1 byte. Furthermore, runtime code comes after the initialization code (starting at 12th position in this case), so we just have to append one byte to the end of the bytecodes above.

Let's add `0xEE` for no reason: `0x6001600C60003960016000F3EE`. That should do it!

## Puzzle 8

``````00      0x36        CALLDATASIZE
01      0x6000      PUSH1 0x00
03      0x80        DUP1
04      0x37        CALLDATACOPY
05      0x36        CALLDATASIZE
06      0x6000      PUSH1 0x00
08      0x6000      PUSH1 0x00
10      0xF0        CREATE
11      0x6000      PUSH1 0x00
13      0x80        DUP1
14      0x80        DUP1
15      0x80        DUP1
16      0x80        DUP1
17      0x94        SWAP5
18      0x5A        GAS
19      0xF1        CALL
20      0x6000      PUSH1 0x00
22      0x14        EQ
23      0x601B      PUSH1 0x1B
25      0x57        JUMPI
26      0xFD        REVERT
27      0x5B        JUMPDEST
28      0x00        STOP
``````

Similar to the previous puzzle, the first 4 lines copy the entire calldata into memory. The next 4 lines create a contract, the initialization code is taken from the memory at the position that calldata was just loaded.

Afterwards, 5 of `0x00` are pushed to the stack. `SWAP5` will exchange the 1st and 6th stack items, and the 6th item at that point is the address yielded from `CREATE`. Next, the remaining gas amount is pushed to stack with `GAS`. All of this was done for the sake of `CALL` instruction:

``````CALL
gas       // given by GAS the previous line
value     // 0
argOffset // 0
argSize   // 0
retOffset // 0
retSize   // 0
``````

After `CALL`, a boolean result is pushed to the stack indicating its success. Looking at the following lines we see that this is expected to be 0 (`PUSH1 00` and `EQ` with `JUMPI` afterwards). So we can create a contract with a `REVERT` instruction.

``````PUSH1 0x00
PUSH1 0x00
REVERT
``````

This shall be our runtime code, which in bytecode is `0x60006000FD` at 5 bytes total. We will write the initialization code ourselves too, similar to what we did in the previous puzzle.

``````PUSH1 0x05 // 5 bytes
PUSH1 0x0C // position of runtime code in bytecode
PUSH1 0x00 // write to memory position 0
CODECOPY   // copies the bytecode
PUSH1 0x05 // 5 bytes
PUSH1 0x00 // read from memory position 0
RETURN     // returns the code copied above
``````

Again the position is `0x0C` because the initialization code is 12 bytes. So our initialization bytecodes are `0x6005600C60003960056000F3` and the runtime bytecodes are `0x60006000FD`. The calldata will be these concatenated: `0x6005600C60003960056000F360006000FD`.

## Puzzle 9

``````00      0x36        CALLDATASIZE
01      0x6003      PUSH1 0x03
03      0x10        LT
04      0x6009      PUSH1 0x09
06      0x57        JUMPI
07      0xFD        REVERT
08      0xFD        REVERT
09      0x5B        JUMPDEST
10      0x34        CALLVALUE
11      0x36        CALLDATASIZE
12      0x02        MUL
13      0x6008      PUSH1 0x08
15      0x14        EQ
16      0x6014      PUSH1 0x14
18      0x57        JUMPI
19      0xFD        REVERT
20      0x5B        JUMPDEST
21      0x00        STOP
``````

We start with a small `JUMPI` that requires `3 < CALLDATASIZE` so our calldata is larger than 3 bytes. Afterwards, we multiply our `CALLVALUE` and `CALLDATASIZE`, which is expected to be 8. Simply, we will send a 4 byte calldata with 2 Wei call value.

## Puzzle 10

``````00      0x38          CODESIZE
01      0x34          CALLVALUE
02      0x90          SWAP1
03      0x11          GT
04      0x6008        PUSH1 0x08
06      0x57          JUMPI
07      0xFD          REVERT
08      0x5B          JUMPDEST
09      0x36          CALLDATASIZE
10      0x610003      PUSH2 0x0003
13      0x90          SWAP1
14      0x06          MOD
15      0x15          ISZERO
16      0x34          CALLVALUE
17      0x600A        PUSH1 0x0A
20      0x57          JUMPI
21      0xFD          REVERT
22      0xFD          REVERT
23      0xFD          REVERT
24      0xFD          REVERT
25      0x5B          JUMPDEST
26      0x00          STOP
``````

The first `CODESIZE` is the size of this puzzle itself, which is `1B` (28 bytes). Next it swaps the `CALLVALUE` with it, and runs `GT`. In effect, this checks if `CODESIZE > CALLVALUE`.

After the successfull `JUMPI`, we are doing a `CALLDATASIZE MOD 0x003 == 0` operation. We want this to be true for the next `JUMPI` to work, so our calldata size must be a multiple of 3.

The destination of `JUMPI` is defined by `CALLVALUE ADD 0x0A`, which should add up to `0x19`. In decimals, `0x0A` is 10 and `0x19` is 25, so our `CALLVALUE` should be 15.