Skip to main content

Overview

Opcode: 0x00 Introduced: Frontier (EVM genesis) STOP halts execution successfully without returning any output data. All state changes are preserved, remaining gas is consumed, and execution terminates with a success status. This is the simplest termination opcode - equivalent to falling off the end of bytecode or explicitly signaling completion without a return value.

Specification

Stack Input: None Stack Output: None Gas Cost: 0 (execution halted before gas consumption) Operation:
frame.stopped = true
return success

Behavior

STOP immediately terminates execution:
  1. Sets execution state to stopped
  2. Preserves all state changes (storage, logs, balance transfers)
  3. Returns no output data (empty return buffer)
  4. Remaining gas is NOT refunded (consumed by transaction)
  5. Control returns to caller with success status
Key Characteristics:
  • No stack items consumed or produced
  • No memory access
  • No output data
  • Cannot be reverted (final state)

Examples

Basic Stop

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

// Stop execution
const frame = createFrame({
  stack: [42n, 123n],  // Stack unchanged
  gasRemaining: 1000n
});

const err = handler_0x00_STOP(frame);

console.log(err);              // null (success)
console.log(frame.stopped);    // true
console.log(frame.output);     // undefined (no output)
console.log(frame.stack);      // [42n, 123n] (unchanged)
console.log(frame.gasRemaining); // 1000n (no gas consumed)

Constructor Pattern

// Contract constructor that stops after initialization
constructor() {
    // Initialize state
    owner = msg.sender;

    // Constructor bytecode ends with STOP
    assembly {
        stop()  // Halt constructor execution
    }
}
Compiled bytecode (simplified):
// Constructor code
CALLER
PUSH1 0x00
SSTORE

// STOP - end constructor
STOP

Function Without Return

// Function that doesn't return a value
function setOwner(address newOwner) external {
    owner = newOwner;
    // Implicit STOP at end (or explicit)
    assembly {
        stop()
    }
}

Explicit Termination

assembly {
    // Perform operations
    sstore(0, 42)

    // Explicitly stop without returning
    stop()

    // Code after STOP is unreachable
    invalid()  // Never executed
}

Gas Cost

Cost: 0 gas STOP is technically free because execution halts immediately. However, the transaction still consumes:
  • Base transaction gas (21000)
  • Gas for executed opcodes before STOP
  • Remaining gas is NOT refunded
Gas Consumption Example:
const frame = createFrame({ gasRemaining: 5000n });

// Execute some operations (consume gas)
frame.gasRemaining -= 100n;  // Some operation cost

// STOP - remaining gas is lost
handler_0x00_STOP(frame);

// frame.gasRemaining = 4900n (not refunded to transaction)
Comparison:
  • STOP: 0 gas (halts execution)
  • RETURN: Memory expansion cost
  • REVERT: Memory expansion cost + refunds remaining gas

Edge Cases

Empty Stack

// STOP works with empty stack (no stack interaction)
const frame = createFrame({ stack: [] });
const err = handler_0x00_STOP(frame);

console.log(err);           // null (success)
console.log(frame.stopped); // true

Already Stopped

// STOP on already-stopped frame (no-op in practice)
const frame = createFrame({ stopped: true });
const err = handler_0x00_STOP(frame);

console.log(frame.stopped); // true
// In real EVM, execution wouldn't reach this point

With Output Buffer

// STOP ignores any existing output
const frame = createFrame({ output: new Uint8Array([1, 2, 3]) });
handler_0x00_STOP(frame);

// Output unchanged but ignored by caller
console.log(frame.output); // Uint8Array([1, 2, 3])
// Caller receives empty return data

Common Usage

Constructor Termination

Every contract constructor ends with STOP (implicit or explicit):
contract Example {
    address public owner;

    constructor() {
        owner = msg.sender;
        // Implicit STOP here
    }
}
Bytecode pattern:
<initialization code>
STOP  // End constructor
<runtime code>

Fallback Without Return

// Fallback function that accepts ETH but doesn't return
fallback() external payable {
    // Log receipt
    emit Received(msg.sender, msg.value);
    // Implicit STOP (no return statement)
}

State Update Only

// Function that only updates state
function incrementCounter() external {
    counter++;
    // Implicit STOP
}
Compiles to:
PUSH1 0x00
SLOAD
PUSH1 0x01
ADD
PUSH1 0x00
SSTORE
STOP

Unreachable Code Guard

assembly {
    // Switch statement
    switch value
    case 0 { /* ... */ }
    case 1 { /* ... */ }
    default {
        // This should never execute
        invalid()  // Or stop() for graceful fail
    }
}

Implementation

/**
 * STOP opcode (0x00) - Halt execution
 *
 * @param frame - Frame instance
 * @returns Error if operation fails
 */
export function handler_0x00_STOP(frame: FrameType): EvmError | null {
  frame.stopped = true;
  return null;
}

Testing

Test Coverage

import { describe, it, expect } from 'vitest';
import { handler_0x00_STOP } from './0x00_STOP.js';
import { createFrame } from '../Frame/index.js';

describe('STOP (0x00)', () => {
  it('halts execution', () => {
    const frame = createFrame({});
    const err = handler_0x00_STOP(frame);

    expect(err).toBeNull();
    expect(frame.stopped).toBe(true);
  });

  it('does not consume gas', () => {
    const frame = createFrame({ gasRemaining: 1000n });
    handler_0x00_STOP(frame);

    expect(frame.gasRemaining).toBe(1000n);
  });

  it('does not modify stack', () => {
    const frame = createFrame({ stack: [42n, 123n] });
    handler_0x00_STOP(frame);

    expect(frame.stack).toEqual([42n, 123n]);
  });

  it('works with empty stack', () => {
    const frame = createFrame({ stack: [] });
    const err = handler_0x00_STOP(frame);

    expect(err).toBeNull();
    expect(frame.stopped).toBe(true);
  });

  it('does not produce output', () => {
    const frame = createFrame({});
    handler_0x00_STOP(frame);

    expect(frame.output).toBeUndefined();
  });
});

Security

State Finality

STOP makes all state changes final - they cannot be reverted:
// VULNERABLE: No revert on invalid state
function unsafeUpdate(uint256 value) external {
    require(value > 0, "Value must be positive");

    balance = value;  // State changed

    // STOP makes this final - no further validation
    assembly { stop() }
}
Better approach:
// SAFE: All validation before state changes
function safeUpdate(uint256 value) external {
    require(value > 0, "Value must be positive");
    require(value < MAX_VALUE, "Value too large");

    balance = value;  // State changed after all checks
}

Gas Griefing

STOP doesn’t refund remaining gas - can be used in gas griefing:
// VULNERABLE: Gas griefing attack
function griefGas() external {
    // Caller provides lots of gas
    // Execute minimal code
    assembly {
        stop()  // Remaining gas consumed, not refunded
    }
    // Caller loses unused gas
}
Not a vulnerability in practice:
  • Gas stipend for external calls (2300) prevents this
  • Caller controls gas limit
  • Only affects caller, not contract state

STOP vs RETURN

STOP:
  • No output data
  • Simpler (no memory access)
  • Slightly cheaper (no memory expansion)
  • Use for: void functions, constructors, state-only operations
RETURN:
  • Returns output data
  • Requires memory operations
  • Dynamic gas cost
  • Use for: view functions, getter methods, function return values

Compiler Behavior

Solidity Implicit STOP

Solidity adds STOP at the end of constructor code:
contract Example {
    uint256 public value;

    constructor(uint256 _value) {
        value = _value;
        // Implicit STOP here (separates constructor from runtime)
    }
}
Bytecode structure:
<constructor code>
CODECOPY  // Copy runtime code to memory
RETURN    // Return runtime code for deployment

// Deployed bytecode
<runtime code>

Function Without Return

function voidFunction() external {
    // Do something
}
Compiles to:
<function body>
STOP  // Implicit at end

Unreachable Code Elimination

Compilers eliminate code after STOP:
assembly {
    stop()
    sstore(0, 1)  // ELIMINATED: unreachable
}
Optimized bytecode:
STOP
// Code after STOP removed

References