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:
Stack Output:
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