Skip to main content
This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.

Overview

Opcode: 0x57 Introduced: Frontier (EVM genesis) JUMPI performs a conditional jump to a destination if a condition is non-zero. Like JUMP, the destination MUST be a valid JUMPDEST opcode. If the condition is zero, execution continues sequentially. This enables if-statements, loops, and conditional control flow in EVM bytecode.

Specification

Stack Input:
destination (top)
condition
Stack Output: None Gas Cost: 10 (GasSlowStep) Operation:
1. Pop destination from stack
2. Pop condition from stack
3. If condition != 0:
   - Validate destination is JUMPDEST
   - Set PC = destination
4. Else:
   - PC = PC + 1 (continue)

Behavior

JUMPI conditionally alters program flow:
  1. Consumes 10 gas (GasSlowStep)
  2. Pops destination address from stack (top)
  3. Pops condition value from stack (second)
  4. If condition is non-zero (any value except 0):
    • Validates destination is valid JUMPDEST
    • Updates PC to destination
  5. If condition is zero:
    • Increments PC by 1 (continue sequentially)
Validation (when jumping):
  • Destination must be JUMPDEST opcode (0x5b)
  • Destination must not be inside PUSH data
  • Destination must be within bytecode bounds
  • Failure causes InvalidJump error
Note: Validation only occurs if jump is taken. If condition is zero, destination is not validated.

Examples

Basic Conditional Jump

import { createFrame } from '@tevm/voltaire/evm/Frame';
import { handler_0x57_JUMPI } from '@tevm/voltaire/evm/control';

// Bytecode with JUMPDEST at position 6
const bytecode = new Uint8Array([
  0x60, 0x06,  // PUSH1 6 (destination)
  0x60, 0x01,  // PUSH1 1 (condition: true)
  0x57,        // JUMPI
  0x00,        // STOP (skipped)
  0x5b,        // JUMPDEST (destination)
  0x60, 0x2a,  // PUSH1 42
]);

const frame = createFrame({
  bytecode,
  stack: [1n, 6n],  // condition=1, dest=6
  pc: 4
});

const err = handler_0x57_JUMPI(frame);

console.log(err);       // null (success)
console.log(frame.pc);  // 6 (jumped to JUMPDEST)

Conditional Not Taken

// Same bytecode, but condition is 0
const frame = createFrame({
  bytecode,
  stack: [0n, 6n],  // condition=0, dest=6
  pc: 4
});

const err = handler_0x57_JUMPI(frame);

console.log(err);       // null (success)
console.log(frame.pc);  // 5 (continued, no jump)

Loop Pattern

assembly {
    let i := 0
    let n := 10

    loop:
        jumpdest

        // Loop body
        sstore(i, i)

        // Increment counter
        i := add(i, 1)

        // Continue if i < n
        let continue := lt(i, n)
        jumpi(loop, continue)
}
Bytecode pattern:
PUSH1 0          // i = 0
PUSH1 10         // n = 10

loop:
  JUMPDEST       // Loop start

  DUP2           // Duplicate i
  DUP2           // Duplicate i
  SSTORE         // sstore(i, i)

  DUP2           // Get i
  PUSH1 1
  ADD            // i + 1
  SWAP2
  POP            // Update i

  DUP2           // Get i
  DUP1           // Get n
  LT             // i < n
  PUSH2 loop     // Loop address
  JUMPI          // Jump if i < n

// Exit loop
POP
POP

If-Else Pattern

assembly {
    let value := sload(0)

    // If value == 0
    if iszero(value) {
        sstore(1, 100)
        jump(end)
    }

    // Else
    sstore(1, 200)

    end:
        jumpdest
}
Compiled to:
PUSH1 0
SLOAD              // value = sload(0)

DUP1
ISZERO             // value == 0
PUSH2 then_branch
JUMPI

// Else branch
PUSH1 200
PUSH1 1
SSTORE
PUSH2 end
JUMP

// Then branch
then_branch:
  JUMPDEST
  PUSH1 100
  PUSH1 1
  SSTORE

end:
  JUMPDEST

Non-Zero Condition

// Any non-zero value is truthy
const testConditions = [1n, 42n, 0xffffffffn, -1n & ((1n << 256n) - 1n)];

for (const condition of testConditions) {
  const frame = createFrame({
    bytecode,
    stack: [condition, 6n],
    pc: 4
  });

  handler_0x57_JUMPI(frame);
  console.log(frame.pc); // 6 (all jump - non-zero is true)
}

Gas Cost

Cost: 10 gas (GasSlowStep) Comparison:
  • JUMP: 8 gas (unconditional)
  • JUMPI: 10 gas (conditional)
  • PC: 2 gas
  • JUMPDEST: 1 gas
Total Conditional Jump:
PUSH1 dest:    3 gas
PUSH1 cond:    3 gas
JUMPI:        10 gas
JUMPDEST:      1 gas (if taken)
Total:        17 gas (jump taken)
              16 gas (not taken, no JUMPDEST)
Cost is same whether jump is taken or not:
  • Jump taken: 10 gas + JUMPDEST (1 gas) = 11 gas
  • Jump not taken: 10 gas

Edge Cases

Invalid Destination When Jumped

// Invalid destination but condition is true
const bytecode = new Uint8Array([0x60, 0x00]); // PUSH1 0 (not JUMPDEST)

const frame = createFrame({
  bytecode,
  stack: [1n, 1n],  // condition=1, dest=1 (PUSH data)
  pc: 0
});

const err = handler_0x57_JUMPI(frame);
console.log(err); // { type: "InvalidJump" }

Invalid Destination Not Validated

// Invalid destination but condition is false
const bytecode = new Uint8Array([0x60, 0x00, 0x00]); // PUSH1 0, STOP

const frame = createFrame({
  bytecode,
  stack: [0n, 1n],  // condition=0, dest=1 (invalid but not checked)
  pc: 1
});

const err = handler_0x57_JUMPI(frame);
console.log(err);      // null (success - destination not validated)
console.log(frame.pc); // 2 (continued, no jump)

Zero is False

// Only 0 is falsy - all other values are truthy
const frame = createFrame({
  bytecode,
  stack: [0n, 6n],  // condition=0 is false
  pc: 4
});

handler_0x57_JUMPI(frame);
console.log(frame.pc); // 5 (not jumped)

Stack Underflow

// Need 2 stack items
const frame = createFrame({
  stack: [6n],  // Only 1 item
  pc: 0
});

const err = handler_0x57_JUMPI(frame);
console.log(err); // { type: "StackUnderflow" }

Out of Bounds

// Destination too large for u32
const frame = createFrame({
  stack: [1n, 0x100000000n],  // dest > u32::MAX
  pc: 0
});

const err = handler_0x57_JUMPI(frame);
console.log(err); // { type: "OutOfBounds" }

Common Usage

Require Statements

Solidity require compiles to JUMPI:
function transfer(address to, uint256 amount) external {
    require(balances[msg.sender] >= amount, "Insufficient balance");
    // ...
}
Compiled (simplified):
// Load balance
CALLER
PUSH1 0
SLOAD

// Check balance >= amount
CALLDATA_LOAD 0x04  // amount
DUP2
LT
ISZERO              // balance >= amount

// Jump to continue if true
PUSH2 continue
JUMPI

// Revert if false
REVERT

continue:
  JUMPDEST
  // Continue execution

For Loops

for (uint i = 0; i < n; i++) {
    // body
}
Compiled to:
PUSH1 0           // i = 0

loop_condition:
  JUMPDEST

  // Check i < n
  DUP1            // Get i
  PUSH1 n
  LT              // i < n
  PUSH2 loop_body
  JUMPI

  // Exit loop
  POP
  JUMP exit

loop_body:
  JUMPDEST
  // Loop body

  // Increment i
  PUSH1 1
  ADD

  PUSH2 loop_condition
  JUMP

exit:
  JUMPDEST

While Loops

while (condition) {
    // body
}
Compiled to:
loop_start:
  JUMPDEST

  // Evaluate condition
  <condition code>

  // Continue if true
  PUSH2 loop_body
  JUMPI

  // Exit
  JUMP loop_end

loop_body:
  JUMPDEST
  // Loop body
  PUSH2 loop_start
  JUMP

loop_end:
  JUMPDEST

Switch Statements

assembly {
    switch value
    case 0 { /* ... */ }
    case 1 { /* ... */ }
    default { /* ... */ }
}
Compiled to:
// Check case 0
DUP1
ISZERO
PUSH2 case0
JUMPI

// Check case 1
DUP1
PUSH1 1
EQ
PUSH2 case1
JUMPI

// Default case
JUMP default

case0:
  JUMPDEST
  // Case 0 code
  JUMP end

case1:
  JUMPDEST
  // Case 1 code
  JUMP end

default:
  JUMPDEST
  // Default code

end:
  JUMPDEST

Implementation

import { consumeGas } from "../Frame/consumeGas.js";
import { popStack } from "../Frame/popStack.js";
import { isValidJumpDest } from "../../primitives/Bytecode/isValidJumpDest.js";
import { SlowStep } from "../../primitives/GasConstants/constants.js";

/**
 * JUMPI opcode (0x57) - Conditional jump
 *
 * @param frame - Frame instance
 * @returns Error if operation fails
 */
export function handler_0x57_JUMPI(frame: FrameType): EvmError | null {
  const gasErr = consumeGas(frame, SlowStep);
  if (gasErr) return gasErr;

  const destResult = popStack(frame);
  if (destResult.error) return destResult.error;
  const dest = destResult.value;

  const conditionResult = popStack(frame);
  if (conditionResult.error) return conditionResult.error;
  const condition = conditionResult.value;

  if (condition !== 0n) {
    // Check if destination fits in u32 range
    if (dest > 0xffffffffn) {
      return { type: "OutOfBounds" };
    }

    const destPc = Number(dest);

    // Validate jump destination
    if (!isValidJumpDest(frame.bytecode, destPc)) {
      return { type: "InvalidJump" };
    }

    frame.pc = destPc;
  } else {
    frame.pc += 1;
  }

  return null;
}

Testing

Test Coverage

import { describe, it, expect } from 'vitest';
import { handler_0x57_JUMPI } from './0x57_JUMPI.js';

describe('JUMPI (0x57)', () => {
  it('jumps when condition is non-zero', () => {
    const bytecode = new Uint8Array([
      0x60, 0x05, // PUSH1 5
      0x60, 0x01, // PUSH1 1
      0x57,       // JUMPI
      0x00,       // STOP
      0x5b,       // JUMPDEST
    ]);

    const frame = createFrame({
      bytecode,
      stack: [1n, 5n],
      pc: 4,
    });

    expect(handler_0x57_JUMPI(frame)).toBeNull();
    expect(frame.pc).toBe(5);
  });

  it('continues when condition is zero', () => {
    const bytecode = new Uint8Array([0x57, 0x00]);

    const frame = createFrame({
      bytecode,
      stack: [0n, 5n],
      pc: 0,
    });

    expect(handler_0x57_JUMPI(frame)).toBeNull();
    expect(frame.pc).toBe(1);
  });

  it('rejects invalid destination when jumped', () => {
    const bytecode = new Uint8Array([0x60, 0x00]);

    const frame = createFrame({
      bytecode,
      stack: [1n, 1n],
      pc: 0,
    });

    expect(handler_0x57_JUMPI(frame)).toEqual({ type: 'InvalidJump' });
  });

  it('does not validate destination when not jumped', () => {
    const bytecode = new Uint8Array([0x57, 0x00]);

    const frame = createFrame({
      bytecode,
      stack: [0n, 999n], // Invalid dest but not validated
      pc: 0,
    });

    expect(handler_0x57_JUMPI(frame)).toBeNull();
    expect(frame.pc).toBe(1);
  });

  it('treats any non-zero as truthy', () => {
    const conditions = [1n, 42n, 0xffffffffn];

    for (const condition of conditions) {
      const bytecode = new Uint8Array([0x57, 0x5b]);
      const frame = createFrame({
        bytecode,
        stack: [condition, 1n],
        pc: 0,
      });

      handler_0x57_JUMPI(frame);
      expect(frame.pc).toBe(1);
    }
  });
});

Security

Conditional Validation Bypass

Pattern to avoid:
// VULNERABLE: Condition can bypass validation
function unsafeConditional(uint256 condition, uint256 dest) external {
    assembly {
        jumpi(dest, condition)
    }
}
Attack:
  • Set condition = 0
  • Invalid destination not validated
  • If destination is later used, could cause issues
Not exploitable in practice:
  • Invalid destinations only checked when jumped
  • No jump means no execution at destination
  • Still poor practice - validate explicitly

Infinite Loops

JUMPI enables infinite loops that exhaust gas:
assembly {
    loop:
        jumpdest
        push1 1        // Always true
        push2 loop
        jumpi          // Jump back forever
}
Gas protection:
  • Out-of-gas halts execution
  • Only affects transaction sender
  • Transaction reverts, state unchanged

Condition Manipulation

VULNERABLE:
// User controls condition
function unsafeJump(uint256 userCondition) external {
    assembly {
        jumpi(privileged_code, userCondition)

        // Normal code path
        jump(end)

        privileged_code:
            jumpdest
            // Privileged operations
            // Should not be accessible

        end:
            jumpdest
    }
}
Attack:
  • User sets userCondition = 1
  • Jumps to privileged code
  • Bypasses access control
SAFE:
// Validate condition internally
function safeJump() external {
    bool isAuthorized = (msg.sender == owner);

    assembly {
        jumpi(privileged_code, isAuthorized)

        revert(0, 0)

        privileged_code:
            jumpdest
            // Safe - condition validated
    }
}

Short-Circuit Evaluation

Solidity’s && and || compile to JUMPI for short-circuiting:
if (condition1 && condition2) {
    // code
}
Compiled to:
// Evaluate condition1
<condition1 code>
ISZERO
PUSH2 skip      // Skip if false
JUMPI

// Evaluate condition2 (only if condition1 true)
<condition2 code>
ISZERO
PUSH2 skip
JUMPI

// Execute code
<code>

skip:
  JUMPDEST
This prevents evaluating condition2 if condition1 is false, saving gas and avoiding side effects.

Compiler Behavior

Require Statements

require(balance >= amount, "Insufficient");
Compiles to:
// Check condition
PUSH balance
PUSH amount
LT
ISZERO

// Jump to continue if true
PUSH2 continue
JUMPI

// Revert with error message
PUSH error_data_offset
PUSH error_data_size
REVERT

continue:
  JUMPDEST

If Statements

if (value > 0) {
    result = 1;
} else {
    result = 2;
}
Compiles to:
// Evaluate condition
PUSH value
PUSH1 0
GT

// Jump to then if true
PUSH2 then_branch
JUMPI

// Else branch
PUSH1 2
PUSH1 result_slot
SSTORE
PUSH2 end
JUMP

// Then branch
then_branch:
  JUMPDEST
  PUSH1 1
  PUSH1 result_slot
  SSTORE

end:
  JUMPDEST

References