EVM Puzzles
The EVM puzzles test your understanding of EVM bytecode. I went down the rabbit hole to understand contract bytecode and here I present to you my solutions. A helpful source for understanding how opcodes interact with the stack can be found here.
Puzzle 1
The lines
CALLVALUE
JUMP
To jump to the destination we need CALLVALUE = 0x08
.
Puzzle 2
We want to choose a call value and code size such that
CODESIZE - CALLVALUE == 0x06
. The codesize is 10 bytes
so we need the call value to be 4.
Puzzle 3
To jump to line 4 the call data size must be 4 bytes. Any
4 byte payload will do, like 0x00000000
.
Puzzle 4
For this challenge we want the XOR of two arguments to jump to the
desired line. It will be helpful to look at CALLVALUE
and CODESIZE
,
and the line’s binary representations.
The jump destination is line 0A
, or 1010
(base 2). The code size is 12
bytes, or 1100
(base 2). We want to choose a value x
such that x ^ 1100 == 1010
.
The answer is 6
.
Puzzle 5
For this challenge I found it helpful to visualize the stack after each instruction.
CALLVALUE => stack = [CALLVALUE]
DUP1 => stack = [CALLVALUE, CALLVALUE]
MUL => stack = [CALLVALUE**2]
PUSH2 0100 => stack = [0x0100, CALLVALUE**2]
EQ => ?
What do we have on the stack after EQ
? Well, that depends on CALLVALUE
.
Recall the EQ
opcode consumes two items from the stack and produces one.
If the two items are equal it will push 0x01
onto the stack, otherwise it
will push 0x00
onto the stack. In our case we want it to push 0x01
so we
must set CALLVALUE = sqrt(0x0100) = 16 (base 10)
Puzzle 6
The CALLDATALOAD
opcode takes element x
off the stack and reads the
32 byte word in calldata starting at offset x
. In this challenge we’re
reading from offset 0x00
. We want the first 32 bytes of call data to be
the jump destination: 0x000000000000000000000000000000000000000000000000000000000000000a
Puzzle 7
This challenge uses the calldata to deploy a contract with the CREATE
opcode. We essentially need to write creation + runtime contract bytecode
where the deployment bytecode is only a single byte in length.
Contract creation bytecode
A quick primer on contract creation bytecode: the bytecode loads the runtime bytecode into memory and returns it. We could write something like this
#################
Creation bytecode
#################
00 PUSH1 0x01 (0x6001)
02 PUSH1 0x0c (0x600c)
04 PUSH1 0x00 (0x6000)
06 CODECOPY (0x39)
07 PUSH1 0x01 (0x6001)
09 PUSH1 0x00 (0x6000)
0b RETURN (0xF3)
################
Runtime bytecode
################
0c INVALID (0xFE)
This code copies 1 byte of code to memory starting at line 0c
and returns that byte. If we sent this bytecode with a contract
creation transaction then it would create a contract whose
bytecode is just the single byte 0xFE
. This is exactly what
we need to solve the puzzle.
As a bytecode string the answer is: 0x6001601160003960016000F3FE
.
Puzzle 8
This is another creation bytecode puzzle. The code calls our
newly created contract and jumps only if the CALL
opcode
returns 0. Let’s look at our opcode reference to figure out how
to make CALL
return 0: if the call is successful it returns 1,
otherwise it returns 0. Therefore our newly created contract should
revert. All we have to do is change the runtime bytecode from our
previous solutions to be REVERT
.
#################
Creation bytecode
#################
00 PUSH1 0x01 (0x6001)
02 PUSH1 0x0c (0x600c)
04 PUSH1 0x00 (0x6000)
06 CODECOPY (0x39)
07 PUSH1 0x01 (0x6001)
09 PUSH1 0x00 (0x6000)
0b RETURN (0xF3)
################
Runtime bytecode
################
0c REVERT (0xFD)
As a bytecode string the answer is: 0x6001600c60003960016000F3FD
.
Puzzle 9
For the first jump we need 0x03 < CALLDATASIZE
and for the second
jump we need CALLVALUE * CALLDATASIZE = 0x08
. To satisfy the
constraints we’ll choose a 4 byte long calldata, e.g. 0x00000000
and a value of 2.
Puzzle 10
For the first jump we need CODESIZE > CALLVALUE
. For the second
jump we need CALLDATASIZE % 3 == 0
and CALLVALUE + 0x0a = 0x1b
.
A call value of 15 and calldata 0x000000 will satisfy the constraints.