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: 0x14
Introduced: Frontier (EVM genesis)
EQ performs bitwise equality comparison on two 256-bit values. Returns 1 if the values are exactly equal, 0 otherwise. This is a fundamental operation for implementing conditional logic, address validation, and state checks.
Unlike comparison operations (LT/GT), EQ works identically for both signed and unsigned interpretations since it performs exact bitwise equality.
Specification
Stack Input:
Stack Output:
Gas Cost: 3 (GasFastestStep)
Operation:
result = (a == b) ? 1 : 0
Behavior
EQ pops two values from the stack, compares them bitwise, and pushes 1 if they are exactly equal, otherwise 0:
- If
a == b (all 256 bits identical): Result is 1 (true)
- If
a != b (any bit differs): Result is 0 (false)
Comparison is bitwise - no interpretation as signed/unsigned is needed.
Examples
Basic Equality
import { eq } from '@tevm/voltaire/evm/comparison';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// 42 == 42 = 1 (true)
const frame = createFrame({ stack: [42n, 42n] });
const err = eq(frame);
console.log(frame.stack); // [1n]
console.log(frame.gasRemaining); // Original - 3
Inequality
// 10 == 20 = 0 (false)
const frame = createFrame({ stack: [10n, 20n] });
const err = eq(frame);
console.log(frame.stack); // [0n]
Zero Equality
// 0 == 0 = 1 (true)
const frame = createFrame({ stack: [0n, 0n] });
eq(frame);
console.log(frame.stack); // [1n]
// 0 == 1 = 0 (false)
const frame2 = createFrame({ stack: [0n, 1n] });
eq(frame2);
console.log(frame2.stack); // [0n]
Maximum Value
// MAX == MAX = 1 (true)
const MAX = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX, MAX] });
eq(frame);
console.log(frame.stack); // [1n]
Address Comparison
// Common use case: checking addresses
const addr1 = 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3n;
const addr2 = 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3n;
const frame = createFrame({ stack: [addr1, addr2] });
eq(frame);
console.log(frame.stack); // [1n] (addresses match)
Sign-Independent
// EQ doesn't care about signed interpretation
const value1 = (1n << 256n) - 1n; // All bits set
const value2 = (1n << 256n) - 1n;
// As unsigned: 2^256 - 1
// As signed: -1
// EQ: bitwise equal = 1
const frame = createFrame({ stack: [value1, value2] });
eq(frame);
console.log(frame.stack); // [1n]
Gas Cost
Cost: 3 gas (GasFastestStep)
EQ shares the lowest gas tier with all comparison operations:
- LT, GT, SLT, SGT, EQ (comparisons)
- ISZERO, NOT
- ADD, SUB
Comparison:
- EQ: 3 gas
- LT/GT: 3 gas
- MUL/DIV: 5 gas
Edge Cases
Identical Values
// Any value equals itself
eq(createFrame({ stack: [0n, 0n] })); // [1n]
eq(createFrame({ stack: [42n, 42n] })); // [1n]
const MAX = (1n << 256n) - 1n;
eq(createFrame({ stack: [MAX, MAX] })); // [1n]
Different Values
// Different values are never equal
eq(createFrame({ stack: [1n, 2n] })); // [0n]
eq(createFrame({ stack: [0n, 1n] })); // [0n]
const MAX = (1n << 256n) - 1n;
eq(createFrame({ stack: [MAX, MAX - 1n] })); // [0n]
Large Values
// Arbitrary precision equality
const a = 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFn;
const b = 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFn;
const frame = createFrame({ stack: [a, b] });
eq(frame);
console.log(frame.stack); // [1n]
// One bit different
const c = 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEEn;
const frame2 = createFrame({ stack: [a, c] });
eq(frame2);
console.log(frame2.stack); // [0n]
Stack Underflow
// Not enough stack items
const frame = createFrame({ stack: [42n] });
const err = eq(frame);
console.log(err); // { type: "StackUnderflow" }
console.log(frame.stack); // [42n] (unchanged)
Out of Gas
// Insufficient gas
const frame = createFrame({ stack: [42n, 42n], gasRemaining: 2n });
const err = eq(frame);
console.log(err); // { type: "OutOfGas" }
console.log(frame.gasRemaining); // 0n
Common Usage
Address Validation
// require(addr != address(0))
assembly {
if eq(addr, 0) {
revert(0, 0)
}
}
// Check specific address
assembly {
if iszero(eq(caller(), expectedAddr)) {
revert(0, 0)
}
}
State Checks
// Check if storage value matches expected
assembly {
let value := sload(slot)
if iszero(eq(value, expected)) {
revert(0, 0)
}
}
Conditional Execution
// if (a == b)
assembly {
let equal := eq(a, b)
if equal {
// Execute if equal
}
}
Enum Comparison
// Check enum state
enum State { Pending, Active, Closed }
assembly {
let state := sload(stateSlot)
if eq(state, 1) { // State.Active
// Handle active state
}
}
Hash Verification
// Verify hash matches
assembly {
let computed := keccak256(dataPtr, dataLen)
if iszero(eq(computed, expectedHash)) {
revert(0, 0)
}
}
Implementation
/**
* EQ opcode (0x14) - Equality comparison
*/
export function handle(frame: FrameType): EvmError | null {
// Consume gas (GasFastestStep = 3)
const gasErr = consumeGas(frame, FastestStep);
if (gasErr) return gasErr;
// Pop operands (b is top, a is second)
const bResult = popStack(frame);
if (bResult.error) return bResult.error;
const b = bResult.value;
const aResult = popStack(frame);
if (aResult.error) return aResult.error;
const a = aResult.value;
// Compare: a == b (bitwise equality)
const result = a === b ? 1n : 0n;
// Push result
const pushErr = pushStack(frame, result);
if (pushErr) return pushErr;
// Increment PC
frame.pc += 1;
return null;
}
Testing
Test Coverage
import { describe, it, expect } from 'vitest';
import { handle as EQ } from './0x14_EQ.js';
describe('EQ (0x14)', () => {
it('returns 1 when values are equal', () => {
const frame = createFrame([42n, 42n]);
expect(EQ(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
expect(frame.pc).toBe(1);
expect(frame.gasRemaining).toBe(997n);
});
it('returns 0 when values are not equal', () => {
const frame = createFrame([10n, 20n]);
expect(EQ(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('handles zero equality', () => {
const frame = createFrame([0n, 0n]);
expect(EQ(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
});
it('handles zero inequality', () => {
const frame = createFrame([0n, 1n]);
expect(EQ(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('handles max uint256 equality', () => {
const MAX = (1n << 256n) - 1n;
const frame = createFrame([MAX, MAX]);
expect(EQ(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
});
it('handles large value comparison', () => {
const val = 0xDEADBEEFn;
const frame = createFrame([val, val]);
expect(EQ(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
});
it('returns StackUnderflow with insufficient stack', () => {
const frame = createFrame([42n]);
expect(EQ(frame)).toEqual({ type: 'StackUnderflow' });
});
it('returns OutOfGas when insufficient gas', () => {
const frame = createFrame([42n, 42n], 2n);
expect(EQ(frame)).toEqual({ type: 'OutOfGas' });
});
it('preserves stack below compared values', () => {
const frame = createFrame([100n, 200n, 42n, 42n]);
expect(EQ(frame)).toBeNull();
expect(frame.stack).toEqual([100n, 200n, 1n]);
});
});
Edge Cases Tested
- Equal values (42 == 42)
- Unequal values (10 != 20)
- Zero equality (0 == 0)
- Zero inequality (0 != 1)
- Maximum value equality
- Large value comparison
- Stack underflow (< 2 items)
- Out of gas (< 3 gas)
- Stack preservation
Security
Zero Address Checks
CRITICAL: Always validate addresses are not zero:
// VULNERABLE: Missing zero address check
function transfer(address to, uint256 amount) {
balances[to] += amount; // Can burn tokens to 0x0
}
// CORRECT: Explicit zero check
function transfer(address to, uint256 amount) {
require(to != address(0), "zero address");
balances[to] += amount;
}
// Assembly version
assembly {
if eq(to, 0) {
revert(0, 0)
}
}
Access Control
// VULNERABLE: Missing ownership check
function withdraw() {
// Anyone can call!
payable(msg.sender).transfer(address(this).balance);
}
// CORRECT: Owner validation
function withdraw() {
require(msg.sender == owner, "not owner");
payable(msg.sender).transfer(address(this).balance);
}
// Assembly version
assembly {
if iszero(eq(caller(), owner)) {
revert(0, 0)
}
}
State Validation
// VULNERABLE: Missing state check
function execute() {
// Execute regardless of state
doAction();
}
// CORRECT: State validation
enum State { Pending, Active, Closed }
State public state;
function execute() {
require(state == State.Active, "not active");
doAction();
}
Hash Comparison
// VULNERABLE: Using incorrect hash
bytes32 public secretHash;
function reveal(bytes32 secret) {
// Wrong: comparing unhashed value to hash
require(secret == secretHash); // Never matches!
}
// CORRECT: Hash before comparison
function reveal(bytes32 secret) {
require(keccak256(abi.encodePacked(secret)) == secretHash);
}
Optimizations
Commutative Property
// EQ is commutative: a == b is same as b == a
assembly {
let equal := eq(a, b)
// Same as:
let equal := eq(b, a)
}
// Choose order to minimize stack operations
Zero Check Pattern
// Checking equality to zero
assembly {
let isZero := eq(value, 0) // 3 gas
}
// Equivalent using ISZERO (same gas, clearer intent)
assembly {
let isZero := iszero(value) // 3 gas
}
// Prefer ISZERO for zero checks (more readable)
Inverted Logic
// Check inequality: a != b
assembly {
let notEqual := iszero(eq(a, b)) // 6 gas (EQ + ISZERO)
}
// Sometimes more efficient to structure logic around equality
assembly {
if eq(a, b) {
// Equal case
} else {
// Not equal case (no ISZERO needed)
}
}
Multiple Comparisons
// Check if value equals any of multiple options
assembly {
let match := or(
eq(value, option1),
or(eq(value, option2), eq(value, option3))
)
}
// More efficient with structured checks
assembly {
let match := 0
if eq(value, option1) { match := 1 }
if eq(value, option2) { match := 1 }
if eq(value, option3) { match := 1 }
}
Benchmarks
EQ is one of the fastest EVM operations:
Execution time (relative):
- EQ: 1.0x (baseline)
- LT/GT: 1.0x
- ISZERO: 0.95x
- ADD: 1.0x
- MUL: 1.5x
Gas efficiency:
- 3 gas per equality check
- ~333,333 comparisons per million gas
- Highly optimized in all EVM implementations
References
- ISZERO - Zero check (specialized EQ)
- LT - Less than (unsigned)
- GT - Greater than (unsigned)
- SLT - Signed less than
- SGT - Signed greater than