Skip to main content

Overview

Comparison operations provide boolean logic for 256-bit integers. All comparisons return 1 (true) or 0 (false) and consume minimal gas. These operations enable conditional logic, bounds checking, and control flow in smart contracts. 6 opcodes enable:
  • Unsigned comparison: LT, GT
  • Signed comparison: SLT, SGT
  • Equality: EQ
  • Zero check: ISZERO

Opcodes

OpcodeNameGasStack In → OutDescription
0x10LT3a, b → a<bUnsigned less than
0x11GT3a, b → a>bUnsigned greater than
0x12SLT3a, b → a<bSigned less than
0x13SGT3a, b → a>bSigned greater than
0x14EQ3a, b → a==bEquality check
0x15ISZERO3a → a==0Zero check

Signed vs Unsigned Comparison

Unsigned (LT, GT)

Standard unsigned integer comparison treating all 256-bit values as positive:
// Range: 0 to 2^256 - 1
const a = 0x8000000000000000000000000000000000000000000000000000000000000000n;  // 2^255
const b = 1n;

// Unsigned: a > b (2^255 > 1)
LT(a, b)  // = 0 (false)
GT(a, b)  // = 1 (true)

Signed (SLT, SGT)

Two’s complement signed integer comparison:
// Range: -2^255 to 2^255 - 1
// Bit 255 = 1 means negative
const a = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn;  // -1
const b = 1n;

// Signed: a < b (-1 < 1)
SLT(a, b)  // = 1 (true)
SGT(a, b)  // = 0 (false)

Two’s Complement Representation

Signed operations interpret bit 255 as the sign bit:
// Positive: bit 255 = 0
0x0000...0005  // = +5
0x7FFF...FFFF  // = 2^255 - 1 (MAX_INT256)

// Negative: bit 255 = 1
0xFFFF...FFFB  // = -5 (2^256 - 5)
0x8000...0000  // = -2^255 (MIN_INT256)

Gas Costs

All comparison operations cost 3 gas (GasFastestStep), making them the cheapest operations in the EVM.
OperationGasCategory
LT, GT, SLT, SGT3Fastest
EQ, ISZERO3Fastest
ADD, SUB3Fastest (same tier)
MUL, DIV5Fast
ADDMOD, MULMOD8Mid

Common Patterns

Conditional Logic

// if (a < b)
assembly {
    let lessThan := lt(a, b)
    if lessThan {
        // Execute if true
    }
}

Bounds Checking

// require(index < length)
assembly {
    if iszero(lt(index, length)) {
        revert(0, 0)
    }
}

Range Validation

// Check if value in range [min, max]
assembly {
    let inRange := and(
        iszero(lt(value, min)),  // value >= min
        iszero(gt(value, max))   // value <= max
    )
}

Zero Address Check

// require(addr != address(0))
assembly {
    if iszero(addr) {
        revert(0, 0)
    }
}

Signed Integer Logic

// Check if signed value is negative
assembly {
    let isNegative := slt(value, 0)
}

// Absolute value
assembly {
    let abs := value
    if slt(value, 0) {
        abs := sub(0, value)  // Negate
    }
}

Boolean Operations

Comparison results (0 or 1) compose with bitwise operations for complex logic:
// AND: (a < b) && (c < d)
assembly {
    let result := and(lt(a, b), lt(c, d))
}

// OR: (a < b) || (c < d)
assembly {
    let result := or(lt(a, b), lt(c, d))
}

// NOT: !(a < b)
assembly {
    let result := iszero(lt(a, b))
}

Edge Cases

Maximum Values

const MAX_UINT256 = (1n << 256n) - 1n;
const MIN_INT256 = 1n << 255n;  // -2^255 in signed
const MAX_INT256 = (1n << 255n) - 1n;  // 2^255 - 1 in signed

// Unsigned edge cases
LT(MAX_UINT256, 0)  // = 0 (max not less than 0)
GT(MAX_UINT256, 0)  // = 1 (max greater than 0)

// Signed edge cases
SLT(MIN_INT256, MAX_INT256)  // = 1 (-2^255 < 2^255-1)
SGT(MAX_INT256, MIN_INT256)  // = 1 (2^255-1 > -2^255)

// Equality
EQ(MAX_UINT256, MAX_UINT256)  // = 1
EQ(0, 0)  // = 1

// Zero check
ISZERO(0)  // = 1
ISZERO(MAX_UINT256)  // = 0

Sign Bit Boundary

// 2^255 - 1 (largest positive signed)
const maxPos = (1n << 255n) - 1n;
// 2^255 (smallest negative signed = -2^255)
const minNeg = 1n << 255n;

// Unsigned: minNeg > maxPos
LT(minNeg, maxPos)  // = 0
GT(minNeg, maxPos)  // = 1

// Signed: minNeg < maxPos
SLT(minNeg, maxPos)  // = 1
SGT(minNeg, maxPos)  // = 0

Implementation

TypeScript

import * as Comparison from '@tevm/voltaire/evm/instructions/comparison';

// Execute comparison operations
Comparison.lt(frame);      // 0x10
Comparison.gt(frame);      // 0x11
Comparison.slt(frame);     // 0x12
Comparison.sgt(frame);     // 0x13
Comparison.eq(frame);      // 0x14
Comparison.iszero(frame);  // 0x15

Zig

const evm = @import("evm");
const ComparisonHandlers = evm.instructions.comparison.Handlers(FrameType);

// Execute operations
try ComparisonHandlers.lt(frame);
try ComparisonHandlers.gt(frame);
try ComparisonHandlers.slt(frame);
try ComparisonHandlers.sgt(frame);
try ComparisonHandlers.eq(frame);
try ComparisonHandlers.iszero(frame);

Security Considerations

Signed Integer Confusion

Mixing signed and unsigned comparisons can cause vulnerabilities:
// VULNERABLE: Using unsigned comparison for signed values
function withdraw(int256 amount) {
    // LT instead of SLT - negative amounts bypass check!
    assembly {
        if lt(balance, amount) {  // Wrong! Treats -1 as huge positive
            revert(0, 0)
        }
    }
}

// CORRECT: Use signed comparison
function withdraw(int256 amount) {
    assembly {
        if slt(balance, amount) {  // Correct: -1 < balance
            revert(0, 0)
        }
    }
}

Integer Overflow in Comparisons

Comparisons happen after arithmetic wrapping:
// VULNERABLE: Overflow before comparison
uint256 total = a + b;  // May wrap to small value
require(total > a, "overflow");  // Check may pass incorrectly

// BETTER: Check before operation
require(a <= type(uint256).max - b, "overflow");
uint256 total = a + b;

Off-by-One Errors

// VULNERABLE: Should use <= not <
require(index < array.length);  // Allows array.length (out of bounds!)

// CORRECT: Strict less than for 0-indexed arrays
require(index < array.length);  // Max valid index is length - 1

Zero Address Checks

Always validate addresses:
// VULNERABLE: Missing zero check
function transfer(address to, uint256 amount) {
    balances[to] += amount;  // Can send to 0x0, burning tokens
}

// CORRECT: Explicit validation
function transfer(address to, uint256 amount) {
    require(to != address(0), "zero address");
    balances[to] += amount;
}

Optimizations

Gas-Efficient Patterns

// Cheaper: ISZERO + ISZERO instead of EQ for zero check
assembly {
    // Cost: 3 + 3 = 6 gas
    let isZero := iszero(value)

    // Same as: eq(value, 0)
    // Cost: 3 gas (but ISZERO + ISZERO is 6)
    // Prefer EQ when comparing to zero
}

// Multiple conditions: short-circuit with branches
assembly {
    // Evaluate cheapest condition first
    if iszero(lt(a, b)) {
        // Skip expensive checks if first fails
        if condition2 {
            // ...
        }
    }
}

Comparison Inversion

// These are equivalent:
// a < b  ===  !(a >= b)  ===  iszero(or(gt(a, b), eq(a, b)))
// Sometimes inversions save gas or simplify logic

assembly {
    // Direct
    let less := lt(a, b)

    // Inverted (NOT greater-or-equal)
    let less := iszero(or(gt(a, b), eq(a, b)))
}

Benchmarks

Comparison operations are among the fastest EVM operations:
OperationGasExecution Time (relative)
LT/GT/SLT/SGT31.0x (baseline)
EQ31.0x
ISZERO30.9x (slightly faster)
ADD/SUB31.0x
MUL51.5x
See BENCHMARKING.md for detailed benchmarks.

References