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:
- Consumes 10 gas (GasSlowStep)
- Pops destination address from stack (top)
- Pops condition value from stack (second)
- If condition is non-zero (any value except 0):
- Validates destination is valid JUMPDEST
- Updates PC to destination
- 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