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: 0x01 Introduced: Frontier (EVM genesis) ADD performs addition on two 256-bit unsigned integers with wrapping overflow semantics. When the result exceeds 2^256 - 1, it wraps around modulo 2^256, matching hardware integer register behavior. This is the most fundamental arithmetic operation in the EVM, used extensively in array indexing, counter increments, and numeric calculations.

Specification

Stack Input:
a (top)
b
Stack Output:
(a + b) mod 2^256
Gas Cost: 3 (GasFastestStep) Operation:
result = (a + b) & ((1 << 256) - 1)

Behavior

ADD pops two values from the stack, adds them, and pushes the result back. Overflow wraps around without throwing exceptions:
  • If a + b < 2^256: Result is the mathematical sum
  • If a + b >= 2^256: Result is (a + b) mod 2^256
No exceptions are thrown for overflow. The result always fits in 256 bits.

Examples

Basic Addition

import { add } from '@tevm/voltaire/evm/arithmetic';
import { createFrame } from '@tevm/voltaire/evm/Frame';

// 5 + 10 = 15
const frame = createFrame({ stack: [5n, 10n] });
const err = add(frame);

console.log(frame.stack); // [15n]
console.log(frame.gasRemaining); // Original - 3

Overflow Wrapping

// Maximum value + 1 wraps to 0
const MAX_U256 = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX_U256, 1n] });
const err = add(frame);

console.log(frame.stack); // [0n]

Large Overflow

// MAX + MAX wraps around
const MAX_U256 = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX_U256, MAX_U256] });
const err = add(frame);

console.log(frame.stack); // [MAX_U256 - 1n]
// Because: (MAX + MAX) mod 2^256 = (2^256 - 2)

Identity Element

// Adding zero
const frame = createFrame({ stack: [42n, 0n] });
const err = add(frame);

console.log(frame.stack); // [42n]

Commutative Property

// a + b = b + a
const frame1 = createFrame({ stack: [5n, 10n] });
add(frame1);

const frame2 = createFrame({ stack: [10n, 5n] });
add(frame2);

console.log(frame1.stack[0] === frame2.stack[0]); // true (both 15n)

Gas Cost

Cost: 3 gas (GasFastestStep) ADD is one of the cheapest operations in the EVM, sharing the lowest gas tier with:
  • SUB (0x03)
  • NOT (0x19)
  • ISZERO (0x15)
  • LT, GT, SLT, SGT, EQ (comparison ops)
Comparison:
  • ADD/SUB: 3 gas
  • MUL/DIV/MOD: 5 gas
  • ADDMOD/MULMOD: 8 gas
  • EXP: 10 + 50 per byte of exponent

Edge Cases

Maximum Overflow

// Largest possible overflow
const MAX = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX, MAX] });
add(frame);

// Result: 0xFFFF...FFFE (2^256 - 2)

Zero Addition

// 0 + 0 = 0
const frame = createFrame({ stack: [0n, 0n] });
add(frame);

console.log(frame.stack); // [0n]

Stack Underflow

// Not enough stack items
const frame = createFrame({ stack: [5n] });
const err = add(frame);

console.log(err); // { type: "StackUnderflow" }

Out of Gas

// Insufficient gas
const frame = createFrame({ stack: [5n, 10n], gasRemaining: 2n });
const err = add(frame);

console.log(err); // { type: "OutOfGas" }
console.log(frame.gasRemaining); // 0n

Common Usage

Array Index Calculation

// Accessing array[i + offset]
assembly {
    let index := add(i, offset)
    let value := sload(add(arraySlot, index))
}

Counter Increments

// Loop counter
for (uint i = 0; i < n; i++) {
    // Compiler generates: ADD i, 1
}

Memory Pointer Arithmetic

assembly {
    let ptr := mload(0x40)  // Free memory pointer
    mstore(ptr, value)
    mstore(0x40, add(ptr, 0x20))  // Update pointer
}

Safe vs Unchecked

Pre-Solidity 0.8.0:
// Manual overflow check
uint256 result = a + b;
require(result >= a, "overflow");
Solidity 0.8.0+:
// Default: checked arithmetic (adds overflow checks)
uint256 result = a + b;  // Reverts on overflow

// Explicit wrapping (uses raw ADD)
unchecked {
    uint256 result = a + b;  // Wraps on overflow
}

Implementation

/**
 * ADD opcode (0x01) - Addition with overflow wrapping
 */
export function add(frame: FrameType): EvmError | null {
  // Consume gas (GasFastestStep = 3)
  frame.gasRemaining -= 3n;
  if (frame.gasRemaining < 0n) {
    frame.gasRemaining = 0n;
    return { type: "OutOfGas" };
  }

  // Pop operands
  if (frame.stack.length < 2) return { type: "StackUnderflow" };
  const a = frame.stack.pop();
  const b = frame.stack.pop();

  // Compute result with wrapping (modulo 2^256)
  const result = (a + b) & ((1n << 256n) - 1n);

  // Push result
  if (frame.stack.length >= 1024) return { type: "StackOverflow" };
  frame.stack.push(result);

  // Increment PC
  frame.pc += 1;

  return null;
}

Testing

Test Coverage

import { describe, it, expect } from 'vitest';
import { add } from './0x01_ADD.js';

describe('ADD (0x01)', () => {
  it('adds two numbers', () => {
    const frame = createFrame([5n, 10n]);
    expect(add(frame)).toBeNull();
    expect(frame.stack).toEqual([15n]);
  });

  it('handles overflow wrapping', () => {
    const MAX = (1n << 256n) - 1n;
    const frame = createFrame([MAX, 1n]);
    expect(add(frame)).toBeNull();
    expect(frame.stack).toEqual([0n]);
  });

  it('handles large overflow', () => {
    const MAX = (1n << 256n) - 1n;
    const frame = createFrame([MAX, MAX]);
    expect(add(frame)).toBeNull();
    expect(frame.stack).toEqual([MAX - 1n]);
  });

  it('returns StackUnderflow with insufficient stack', () => {
    const frame = createFrame([5n]);
    expect(add(frame)).toEqual({ type: 'StackUnderflow' });
  });

  it('returns OutOfGas when insufficient gas', () => {
    const frame = createFrame([5n, 10n], 2n);
    expect(add(frame)).toEqual({ type: 'OutOfGas' });
  });
});

Edge Cases Tested

  • Basic addition (5 + 10 = 15)
  • Overflow wrapping (MAX + 1 = 0)
  • Large overflow (MAX + MAX)
  • Zero addition (0 + 0 = 0, 42 + 0 = 42)
  • Stack underflow (< 2 items)
  • Out of gas (< 3 gas)
  • Commutative property (a + b = b + a)

Security

Overflow Vulnerabilities

Before Solidity 0.8.0:
// VULNERABLE: No overflow protection
function transfer(address to, uint256 amount) public {
    balances[msg.sender] -= amount;  // Can underflow!
    balances[to] += amount;          // Can overflow!
}
Attack scenario:
  • User with balance 5 transfers amount = 10
  • balances[msg.sender] -= 10 wraps to MAX_UINT256
  • Attacker now has infinite tokens
Mitigation (pre-0.8.0):
// Use SafeMath library
function transfer(address to, uint256 amount) public {
    balances[msg.sender] = balances[msg.sender].sub(amount);  // Reverts
    balances[to] = balances[to].add(amount);                  // Reverts
}
Modern Solidity (0.8.0+):
// Automatic overflow checks
function transfer(address to, uint256 amount) public {
    balances[msg.sender] -= amount;  // Reverts on underflow
    balances[to] += amount;          // Reverts on overflow
}

// Opt-in to wrapping when needed
function unsafeIncrement(uint256 x) pure returns (uint256) {
    unchecked {
        return x + 1;  // Uses raw ADD, wraps on overflow
    }
}

Safe Patterns

Check-Effects-Interactions:
function withdraw(uint256 amount) public {
    require(balances[msg.sender] >= amount);  // Check
    balances[msg.sender] -= amount;           // Effect
    payable(msg.sender).transfer(amount);     // Interaction
}
Explicit Bounds Checking:
function add(uint256 a, uint256 b) pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a, "addition overflow");
    return c;
}

Benchmarks

ADD is one of the fastest EVM operations: Execution time (relative):
  • ADD: 1.0x (baseline)
  • MUL: 1.2x
  • DIV: 2.5x
  • ADDMOD: 3.0x
  • EXP: 10x+
Gas efficiency:
  • 3 gas per 256-bit addition
  • ~0.33 million additions per million gas
  • Highly optimized in all EVM implementations

References