March 24, 2024

EVM Puzzle 5 & 6: Math Operations & Multiple Conditions

12 min readDifficulty: Advanced

Introduction

In this post, we'll explore Puzzles 5 and 6, which focus on mathematical operations and combining multiple conditions in the EVM. Puzzle 5 introduces squaring operations while Puzzle 6 combines two separate conditions that must both be satisfied to solve the puzzle.

Understanding EVM Conditional Logic

The EVM provides several ways to implement conditional logic:

  • JUMPI: Conditional jump that executes if the top stack value is non-zero
  • EQ: Checks if two values are equal, pushing 1 (true) or 0 (false) to the stack
  • LT/GT: Compares values for less-than or greater-than conditions
  • AND/OR: Bitwise operations that can be used for logical conditions

Transaction Properties

EVM puzzles often make use of transaction properties that can be accessed via opcodes:

  • CALLVALUE: The amount of ETH (in wei) sent with the transaction
  • CALLDATASIZE: The size of input data in bytes
  • CALLER: The address that initiated the call
  • GASPRICE: The gas price specified by the transaction

Puzzle 5

JUMPI and EQ

Jumpi: Conditional jump that executes if the top stack value is non-zero

EQ: Checks if two values are equal, pushing 1 (true) or 0 (false) to the stack

Copy and Paste → 34800261010014600C57FDFD5B00FDFD into the EVM Playground. Then click RUN.

# Puzzle 5
# Goal:
# Successfully reach the JUMPDEST at location 0C (12)
# The contract's code is:
#
# 00      34      CALLVALUE
# 01      80      DUP1
# 02      02      MUL
# 03      6101    PUSH2 0x0100
# 06      14      EQ
# 07      600C    PUSH1 0x0C
# 09      57      JUMPI
# 0A      FD      REVERT
# 0B      FD      REVERT
# 0C      5B      JUMPDEST
# 0D      00      STOP
# 0E      FD      REVERT
# 0F      FD      REVERT
#
# Bytecode: 34800261010014600C57FDFD5B00FDFD

Understanding the Puzzle

Let's trace the execution:

  1. CALLVALUE pushes the amount of ETH sent with the transaction onto the stack
  2. DUP1 duplicates this value on the stack
  3. MUL multiplies the top two values (CALLVALUE × CALLVALUE = CALLVALUE²)
  4. PUSH2 0x0100 pushes the value 256 (0x0100 in hex) onto the stack
  5. EQ checks if CALLVALUE² equals 256
  6. PUSH1 0x0C pushes the destination (12) for a potential jump
  7. JUMPI jumps to position 0x0C if the EQ result is true (non-zero)
  8. If the jump occurs, we reach the JUMPDEST and the STOP opcode
  9. If not, we hit a REVERT and fail

Find x:

LocationOpcodeMeaningOperationStack After
00CALLVALUEPushes the amount of ETH sent (x)Push x[x]
01DUP1Duplicates the top stack itemDuplicate x[x, x]
02MULMultiplies the top two stack items to get 256. (We find this out in the next 2 steps)x² = 256[256]
03PUSH2 0x0100Pushes 256 (0x0100) onto the stack. (This is where 256 comes from)Push 256[256, 256]
06EQChecks if top two stack items are equal. (This is how we know x² must = 256)256 == 256 → 1[1]
07PUSH1 0x0CPushes jump destination (12) onto stackPush 12[12, 1]
09JUMPIConditional jump based on conditionJump to 12 if 1[]
0AREVERTReverts the transaction if we reach hereFAIL[]
0BREVERTReverts the transaction if we reach hereFAIL[]
0CJUMPDESTValid jump destination markerSUCCESS![]
0DSTOPHalts execution successfully-[]

Solution:

x To solve this puzzle:

  1. We need CALLVALUE² = 256
  2. Therefore, CALLVALUE = √256 = 16
  3. Send exactly 16 wei with the transaction

This will make the JUMPI condition true, causing the execution to jump to the JUMPDEST at position 0x0C, followed by the STOP opcode which successfully ends the execution.

Puzzle 6

CALLDATALOAD

Puzzle 6 introduces us to the CALLDATALOAD opcode, which loads 32 bytes of calldata starting from a specified position. (This is very important, b/c it means our answer must be 32 bytes long to work.)

Copy and Paste → 60003556FDFDFDFDFDFD5B00 into the EVM Playground. Then click RUN.

# Puzzle 6
# Goal:
# Successfully reach the JUMPDEST at location 0A (10)
#
# The contract's code is:
#
# [00]    PUSH1 00        # Push 0 onto the stack
# [02]    CALLDATALOAD    # Load first 32 bytes of calldata
# [03]    JUMP            # Jump to the loaded value
# [04]    REVERT
# [05]    REVERT
# [06]    REVERT
# [07]    REVERT
# [08]    REVERT
# [09]    REVERT
# [0A]    JUMPDEST        # We need to jump here (to position 10)
# [0B]    STOP
#
# Bytecode: 60003556FDFDFDFDFDFD5B00

Understanding CALLDATALOAD

CALLDATALOAD is an important opcode that reads 32 bytes from the calldata starting at a specified position:

  • Input: Takes a position from the stack (where to start reading)
  • Output: Pushes the 32 bytes read from that position onto the stack
  • Behavior:
    • Always reads exactly 32 bytes
    • Pads with zeros if it reaches the end of calldata
    • The value is interpreted as a big-endian number

Understanding the Puzzle

Let's trace the execution:

LocationOpcodeMeaningOperationStack After
00PUSH1 00Push 0 onto the stackPush starting position[0]
02CALLDATALOADLoad 32 bytes from position 0Read calldata[value_from_calldata]
03JUMPJump to loaded valueJump to position[]
0AJUMPDESTValid jump destinationSuccess![]
0BSTOPHalt execution-[]

Solution:

To solve this puzzle:

  1. We need to provide calldata that, when loaded from position 0, will give us the value 10 (0x0A)
  2. CALLDATALOAD reads 32 bytes, so our calldata needs to be padded appropriately
  3. The value 10 needs to be properly encoded as a 32-byte big-endian number

Example solution calldata:

0x000000000000000000000000000000000000000000000000000000000000000A

This provides exactly the value we need (10) when loaded from position 0, allowing us to jump to the JUMPDEST at position 0x0A.

Key Takeaways

  • Puzzles can combine multiple conditions using JUMPI instructions
  • The CALLDATASIZE opcode can be used with CALLVALUE for more complex challenges
  • Conditional jumps (JUMPI) allow for branching execution paths
  • Transaction properties like value and data size can be used together in conditions