March 23, 2024

EVM Puzzle 3 & 4: Conditional Jumps

10 min readDifficulty: Intermediate

Puzzle 3:

CALLDATASIZE

Puzzle 3 introduces us to the CALLDATASIZE opcode, which gets the size of the input data and demonstrates how we can use it to control program flow with JUMP operations.

Copy and Paste → 3656FDFD5B00 into the EVM Playground. Then click RUN.

# Puzzle 3
# Goal:
# Find the right amount of calldata that will make execution reach JUMPDEST (04)

00      37      CALLDATASIZE
01      56      JUMP
02      FD      REVERT
03      FD      REVERT
04      5B      JUMPDEST
05      00      STOP

# Bytecode: 0x3656FDFD5B00

Understanding CALLDATASIZE

CALLDATASIZE is used to return the size (in bytes) of the data sent along with a transaction. It determines how much data was included in a transaction's calldata field.

Common uses:
  • Validating input data length before processing
  • Determining if any function arguments were provided
  • Detecting whether a contract was called directly or via another contract
  • Used with CALLDATACOPY to read transaction input
Transaction data: When you send data with a transaction (like function arguments or raw bytes), CALLDATASIZE tells you exactly how many bytes you sent

** In hexadecimal notation (starting with 0x), each byte is represented by 2 hex digits.

For Example:

0x34 → 1 byte (CALLDATASIZE = 1)

0x3456 → 2 bytes (CALLDATASIZE = 2)

0x345678 → 3 bytes (CALLDATASIZE = 3)

0x12345678 → 4 bytes (CALLDATASIZE = 4) ← Solution for this puzzle!

0x1234567890 → 5 bytes (CALLDATASIZE = 5)

Understanding the Puzzle

In this puzzle, since our CALLVALUE = 4, we need to make our CALLVALUESIZE exactly 4 bytes of calldata. This will push 4 onto the stack and direct our JUMP instruction to our destination (04).

LocationOpcodeMeaningOperationStack After
0CALLDATASIZEPushes the size of calldata onto the stackPush 4[4]
1JUMPJumps to the position specified by the top stack valueJump to 4[]
2REVERTReverts the transaction if we jump hereFAIL[]
3REVERTReverts the transaction if we jump hereFAIL[]
4JUMPDESTValid jump destination markerSUCCESS![]
5STOPHalts execution successfully-[]

Solution:

To solve this puzzle, we need to:

  1. Send exactly 4 bytes of calldata (this will make CALLDATASIZE push 4 onto the stack)
  2. This will work with any 4 byte number (0x11223344)

  3. This will make the JUMP instruction jump to position 4, where the JUMPDEST is located

This puzzle teaches us that CALLDATASIZE can be used to control program flow just like CALLVALUE, but it's based on the size of the input data rather than the amount of ETH sent.

Puzzle 4:

XOR and CODESIZE

Puzzle 4 introduces the XOR operation, which performs a bitwise exclusive OR on stack values.

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

# Puzzle 4
# Goal:
# Make it to JUMPDEST at 0x0a

00      34      CALLVALUE
01      38      CODESIZE
02      18      XOR
03      56      JUMP
04      FD      REVERT
05      FD      REVERT
06      FD      REVERT
07      FD      REVERT
08      FD      REVERT
09      FD      REVERT
0A      5B      JUMPDEST
0B      00      STOP

# Bytecode: 0x34381856FDFDFDFDFDFD5B00

Understanding XOR

XOR (exclusive OR) is a bitwise operation that returns 1 only when exactly one of the compared bits is 1. It returns 0 if both bits are the same (both 0 or both 1).

XOR Truth Table:

0 XOR 0 = 0

0 XOR 1 = 1

1 XOR 0 = 1

1 XOR 1 = 0

Understanding CODESIZE

CODESIZE is an opcode that returns the size (in bytes) of the currently executing contract's bytecode. Here's what you need to know:

  • Purpose: Determines the total size of the contract's bytecode
  • How it's calculated: Each opcode is 1 byte (2 digits), and some opcodes (like PUSH1, PUSH2, etc.) have additional data bytes that count toward the total size
  • Common uses:
    • Contract size optimization (Ethereum has a maximum contract size limit)
    • Security checks (contract size can help identify certain vulnerabilities)
    • Creating mathematical relationships in code (as in this puzzle)

In this puzzle, the bytecode is 12 bytes long:

0x34 38 18 56 FD FD FD FD FD FD 5B 00

1. 0x34 - CALLVALUE

2. 0x38 - CODESIZE

3. 0x18 - XOR

4. 0x56 - JUMP

5-10. 0xFD - REVERT (6 times)

11. 0x5B - JUMPDEST

12. 0x00 - STOP

When the CODESIZE opcode executes at position 1, it counts all these bytes (including itself) and pushes 12 onto the stack.

Visual XOR Calculation

Let's visually walk through how we calculate 12 XOR x = 10:

Step 1: Convert to binary

CODESIZE (12):
00001100
Unknown (x):
????????
Target (10):
00001010

Step 2: Solve for x using XOR properties

CODESIZE (12):
00001100
Target (10):
00001010
Unknown (x):
00000110

Step 3: Convert back to decimal

Binary:
00000110
Position value:
2726252423222120
1286432168421

IF: 12 XOR x = 10

Then: 12 XOR 10 = 6

Therefore: x = 6

So we need to send 6 wei as CALLVALUE to make the JUMP go to position 10.

Understanding the Puzzle

Find X:

LocationOpcodeMeaningOperationStack After
0CALLVALUEPushes the amount of ETH sent. We're going to call this 'x' b/c we don't know it yet.x[x]
1CODESIZEPushes the size of the contract codePush 12[12, x]
2XORPerforms bitwise XOR on top two stack items. (We need this to = 10 b/c our JUMPDEST is 10)12 XOR x = 10[10]
3JUMPJumps to the position specified by the top stack valueJump to 10 (0x0A)[10]
4REVERTReverts the transaction if we jump hereFAIL[]
AJUMPDESTOur destinationSUCCESS![]
BSTOPHalts execution successfully-[]

Solution:

To solve this puzzle:

  1. We need CODESIZE XOR CALLVALUE = 10 (0x0A)
  2. CODESIZE is 12 bytes (0x0C)
  3. So we need: 12 XOR CALLVALUE = 10
  4. Using the properties of XOR: CALLVALUE = 12 XOR 10 = 6
  5. Send exactly 6 wei with the transaction

The key insight: For XOR operations, if A XOR B = C, then A XOR C = B. This property of XOR allows us to solve for CALLVALUE.