EVM Puzzle full solution

As you dive into Solidity, it’s quite important to understand what opcodes are and how to use them. I strongly recommend checking this link :
https://www.evm.codes/?fork=grayGlacier
What are the EVM puzzles?
This is a set of 10 puzzles created by Franco Victorio (https://github.com/fvictorio) to get us more comfortable with opcodes. You can find it in the below link. Follow the instruction to get started: https://github.com/fvictorio/evm-puzzles
What are opcodes?
You can understand “opcode” as low-level human-readable instructions to program. Let’s take a look at the following contract:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;contract Sum { uint256 a = 5; uint256 b = 10; bool isItTrue = true; function addNumbers() public view returns (uint256) { return a+b;}
}
Here would be the resulting opcode:
000 PUSH1 80002 PUSH1 40004 MSTORE005 PUSH1 05007 PUSH1 00009 SSTORE010 PUSH1 0a012 PUSH1 01014 SSTORE015 PUSH1 01017 PUSH1 02019 PUSH1 00021 PUSH2 0100024 EXP025 DUP2026 SLOAD027 DUP2028 PUSH1 ff030 MUL031 NOT032 AND033 SWAP1034 DUP4035 ISZERO036 ISZERO037 MUL038 OR039 SWAP1040 SSTORE041 POP042 CALLVALUE043 DUP1044 ISZERO045 PUSH2 0035048 JUMPI049 PUSH1 00051 DUP1052 REVERT053 JUMPDEST054 POP055 PUSH2 0154058 DUP1059 PUSH2 0045062 PUSH1 00064 CODECOPY065 PUSH1 00067 RETURN068 INVALID069 PUSH1 80071 PUSH1 40073 MSTORE074 CALLVALUE075 DUP1076 ISZERO077 PUSH2 0010080 JUMPI081 PUSH1 00083 DUP1084 REVERT085 JUMPDEST086 POP087 PUSH1 04089 CALLDATASIZE090 LT091 PUSH2 002b094 JUMPI095 PUSH1 00097 CALLDATALOAD098 PUSH1 e0100 SHR101 DUP1102 PUSH4 3a2f9eb7107 EQ108 PUSH2 0030111 JUMPI112 JUMPDEST113 PUSH1 00115 DUP1116 REVERT117 JUMPDEST118 PUSH2 0038121 PUSH2 004e124 JUMP125 JUMPDEST126 PUSH1 40128 MLOAD129 PUSH2 0045132 SWAP2133 SWAP1134 PUSH2 0074137 JUMP138 JUMPDEST139 PUSH1 40141 MLOAD142 DUP1143 SWAP2144 SUB145 SWAP1146 RETURN147 JUMPDESTHow does it work?
Each of these instructions is readable and the full list can be found here: https://www.evm.codes/. The first instructions can be read in the execution order. First, we push a value, then store it … It will be way clearer in the puzzles :-)
Important, these opcodes are compiled into bytecode that looks like this :
60806040526005600055600a6001556001600260006101000a81548160ff02191690831515021790555034801561003557600080fd5b50610154806100456000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80633a2f9eb714610030575b600080fd5b61003861004e565b6040516100459190610074565b60405180910390f35b6000600154600054610060919061008f565b905090565b61006e816100e5565b82525050565b60006020820190506100896000830184610065565b92915050565b600061009a826100e5565b91506100a5836100e5565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100da576100d96100ef565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea26469706673582212203fed942253f5a012c3422013c4601688071a02a565ae8eda725cf0409a454bf364736f6c63430008070033That you can read by decomposing it such as :
6080 => PUSH1 80
6040 => PUSH1 40
…
How to use EVM opcode playground

To use the playground, you can go to this address:https://www.evm.codes/playground
There are a few things to understand here :
- As you as see, I pasted the Bytecode of the above contract, but you can set it to Yul, Solidity, and Mnemonic.
- You can pass it a calldata or a value.
- Use the blue arrow on the top right to go through all opcodes, step by step, see the stack, memory, storage, and return value evolving as you go through the code.
Detailed solution
Let’s dive deeper into all the puzzles. We are presented with a set of 10 puzzles and the goal of each of them is to reach the JUMPDEST.
On https://www.evm.codes/ we can reproduce these puzzles and place them around. The goal is to force us to make some research on the opcodes and understand what’s happening under the hood.

Puzzle 1

Let’s decompose it:
- CALLVALUE opcode that gets the deposited value by the instruction/transaction [value we pass in wei in this case].
- The JUMP opcode will grab the last value of the stack and jump to this destination on the code.
Once this is understood we quickly realize that if we want to reach JUMPDEST, position 08, the CALLVALUE has to be 8.
NB: you can make your experimentation on the playground. The solution is 8 wei.
Puzzle 2

Let’s decompose this second puzzle :
- CALLVALUE opcode again, so we know we will have to pass a value and it will be added on top of the stack.
- CODESIZE this opcode represents the size of the code we have here, so basically 10 ( 00–01–02–03 … 09 ).
- SUB, if we check on the documentation, the SUB opcode takes the value on top of the stack ( CODESIZE ) and subtracts the value right after it ( CALLVALUE).
Knowing that we want to reach JUMPDEST again,
We simply need to solve the following: 10-X = 6
Puzzle 3

New puzzle, new opcodes! :)
Moreover, if we pay attention to the prompt, we need to enter the calldata and not a value in this case!
- CALLDATASIZE opcode takes a value in bytes size.
To reach our JUMPDEST ( 04 )
We need to convert 04 to bytes which are 0xFFFFFFFF
0x — define bytes
4 x FF — represent 4 bytes
Puzzle 4

- CALLVALUE — we know we will have to pass a value here
- CODESIZE — so here we have numeric until 9 then it goes 0A 0B so the total code size is 0C <-> 12 bytes
- XOR — This one is a bit tricky, so keep in mind that in the stack we need have “x” ( To be found).
The Stack is a LIFO queue, so when the XOR will be applied it would be like this: XOR(CODESIZE, CALLVALUE)
We know that the result of XOR(12, CALLVALUE ) must be equal to 10 — JUMPDEST
Therefore, the result is 6 :-)
Puzzle 5

- Again we have to pass a CALLVALUE
- DUP1 — Duplicate the first item in the stack ( here, CALLVALUE )
- MUL — multiply the last 2 items on the stack → CALLVALUE * CALLVALUE
- PUSH2 0100 — Place 2 bytes item on the stack
- EQ — will check the equality of the last 2 items in the stack (the result of MUL == 100 ? ) return 1 if true, 0 if false
- PUSH1 0C — Place 1byte item on the stack which is 0C in this case
Therefore, we need to find a value * value == 0x0100
! 0x0100 in decimal == 256
So 16 * 16 = 256 :-)
Puzzle 6

In this puzzle, the first instruction is a PUSH1 with a 0 value.
Then we have a new opcode: CALLDATALOAD which will take the last value in the stack ( 0 ) as an index. So we are reading the calldata with an offset of 0, so an entire 32 bytes of data.
So we know the offset index is a value of 0 that we need inside of calldata that will jump us a position 0A ( JUMPDEST)
The solution is: 0x000000000000000000000000000000000000000000000000000000000000000A
Puzzle 7

See the breakdown:
- CALLDATASIZE: Get the size of input data in the current environment
- PUSH1 00: Push 00 in the first position of the stack
- DUP1: will duplicate the 1st stack item ( 00 )
- CALLDATACOPY: pops 3 values from the stack and copies the calldata value from the transaction data to memory:
destOffset ( 0 ) offset ( 0 ) size (CALLDATASIZE)
Will return bytes in memory
- CALLDATASIZE: same as above
- PUSH1 00
- PUSH1 00
- CREATE: pops 3 values from the stack. This will actually deploy a new contract and returns the address of the deployed contract that is pushed to the stack.
- value — value in wei to send to the new contract address
2. offset — byte offset from where you want to start to copy the new contract’s code from the memory
3. size — byte size to copy ( size of the initialization code )
Returns the address of the deployed contract, if 0, failed
- EXTCODESIZE: get the size in. bytes of the deployed contract
Here we need to contract size code to be 1.
- EQ: equality comparison, return 1 if true, 0 if false.
- PUSH1 13
- JUMPI
Note on Create:
The create opcode gets executed in the transaction, returning a copy of the runtime code.
⚠️ constructor is part of the creation code, but NOT the runtime code
The runtime byte code is the part of the code saved in the blockchain as the contract runtime code.
When the CREATE opcode is executed, only the code returned by the RETURN opcode will be the runtime code that will be executed in the future once the contract is called.
Here is what we want 600060005360016000F3
Will return 1-byte code.
Puzzle 8

We can see that the first chunk of code is the same as puzzle 7 so I won’t go through it.

Let’s look a the second big chunk:

- CALL opcode creates a new sub-context and executes the code present in the external account. It will also pop 7 elements from the stack.
- Gas: the amount of gas to send to the sub-context to execute
- Address: an account in which context to execute
- Value: value in wei to send
- ArgsOffset: byte offset in the memory in bytes [Call data of the sub context]
- ArgSize: byte size to copy [Byte size of the call data]
- RetOffset: byte offset in the memory in bytes, where to store the return data of the subcontext.
- RetSize: byte size to copy
- SWAP5: this opcode will swap the opcode in position 0 with the one in position 5
At this moment here is where we stand:
CALL(ALL_THE_GAS_AVAILABLE,ADDRESS_FROM_CREATE,0,0,0,0,0)
Will return 0 if the call is reverted, otherwise 1.
Then there is the last chunk:

So basically, we need to make the call revert to having 0.
Then PUSH1 00
EQ 0 == 0 => 1Here is an example that would revert: 0x60FD60005360016000F3
Puzzle 9

So for this puzzle, the interesting point is that we will have to pass a value in wei and a calldata for this level.
Here, we have a new opcode: LT it pops 2 values (a,b) from the stack and pushes the results.
a < b => 1 a > b => 0
In the first chunk of code, we need to make the CALLDATASIZE greater or equal to 3.

Then here, we have the MUL(CALLDATASIZE, CALLVALUE) = 8
So that could be either:
CALLDATASIZE = 4 ( 0xFFFFFFFF )CALLVALUE = 2OR
CALLDATASIZE = 8 ( 0xFFFFFFFFFFFFFFFF )CALLVALUE=1 Puzzle 10

Here once again, we will have to pass a value && calldata.
Let’s break it down:

There is a new opcode here, GT, pop 2 values from the stack and push the result of a > b.
The code size in our case is 0x1b ( 27 in decimal )
a > b => 1a < b => 0So:
GT(CODESIZE,CALLVALUE) GT(27,CALLVALUE)CALLVALUE must be ≤ 27 ( 1b in hex )
Here we have some new opcodes as well:
- MOD: pop2 values from the stack and push back to the stack the result of a % b
- ISZERO: pop a value A from the stack and push the result of a === 0 to the stack
ISZERO( CALLDATA SIZE % 3 ) ( 3 from PUSH2 0003 )
ADD(CALLVALUE, 0A) = 0x19 ( 25 in decimal) the destination
Based on this, ISZERO results must be equal to 1
This is a possible answer:
CALLVALUE: 15
CALLDATA: 0xFFFFFFFFFFFF
NB: You can find my solutions here: https://github.com/Simon-Busch/evm-puzzles
References:
https://www.evm.codes/playground
https://blog.openzeppelin.com/deconstructing-a-solidity-contract-part-i-introduction-832efd2d7737/
