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: 0x06
Introduced: Frontier (EVM genesis)
MOD computes the remainder of unsigned integer division (modulo operation) on two 256-bit values. Like DIV, modulo by zero returns 0 instead of throwing an exception.
This operation is essential for cyclic calculations, hash table indexing, and constraint checking in smart contracts.
Specification
Stack Input:
a (top - dividend)
b (modulus)
Stack Output:
a % b (if b ≠ 0)
0 (if b = 0)
Gas Cost: 5 (GasFastStep)
Operation:
result = (b == 0) ? 0 : (a % b)
Behavior
MOD pops two values from the stack and computes the remainder:
- If
b ≠ 0: Result is a - (a / b) * b where division is integer division
- If
b = 0: Result is 0 (no exception)
The result satisfies: a = (a / b) * b + (a % b) for all b ≠ 0.
Examples
Basic Modulo
import { mod } from '@tevm/voltaire/evm/arithmetic';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// 10 % 3 = 1
const frame = createFrame({ stack: [10n, 3n] });
const err = mod(frame);
console.log(frame.stack); // [1n]
console.log(frame.gasRemaining); // Original - 5
Even/Odd Check
// Check if number is even (n % 2 == 0)
const frame = createFrame({ stack: [42n, 2n] });
mod(frame);
console.log(frame.stack); // [0n] - even
const frame2 = createFrame({ stack: [43n, 2n] });
mod(frame2);
console.log(frame2.stack); // [1n] - odd
Modulo by Zero
// Modulo by zero returns 0 (no exception)
const frame = createFrame({ stack: [42n, 0n] });
const err = mod(frame);
console.log(frame.stack); // [0n]
console.log(err); // null (no error!)
Modulo by Power of Two
// n % 2^k extracts lower k bits
const frame = createFrame({ stack: [0x12345678n, 0x100n] }); // % 256
mod(frame);
console.log(frame.stack); // [0x78n] - lower 8 bits
Identity Cases
// n % n = 0
const frame1 = createFrame({ stack: [42n, 42n] });
mod(frame1);
console.log(frame1.stack); // [0n]
// n % 1 = 0
const frame2 = createFrame({ stack: [42n, 1n] });
mod(frame2);
console.log(frame2.stack); // [0n]
// n % (n+1) = n (when n < n+1)
const frame3 = createFrame({ stack: [42n, 43n] });
mod(frame3);
console.log(frame3.stack); // [42n]
Gas Cost
Cost: 5 gas (GasFastStep)
MOD has the same cost as DIV and MUL:
Comparison:
- ADD/SUB: 3 gas
- MUL/DIV/MOD/SDIV/SMOD/SIGNEXTEND: 5 gas
- ADDMOD/MULMOD: 8 gas
- EXP: 10 + 50 per byte
MOD and DIV are typically implemented together in hardware, hence identical cost.
Edge Cases
Zero Modulo
// 0 % 0 = 0 (special case)
const frame = createFrame({ stack: [0n, 0n] });
mod(frame);
console.log(frame.stack); // [0n]
// 0 % n = 0 (for any n)
const frame2 = createFrame({ stack: [0n, 42n] });
mod(frame2);
console.log(frame2.stack); // [0n]
Modulo Greater Than Dividend
// a % b = a when a < b
const frame = createFrame({ stack: [5n, 10n] });
mod(frame);
console.log(frame.stack); // [5n]
Large Modulus
// Large number modulo
const MAX = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX, 100n] });
mod(frame);
// MAX % 100 = 99 (since MAX = 100k + 99 for some k)
console.log(frame.stack); // [99n]
Power of Two Modulus
// Efficient bit masking
const cases = [
[0xFFn, 0x10n], // 255 % 16 = 15
[0x123n, 0x100n], // 291 % 256 = 35
[0x1234n, 0x1000n], // 4660 % 4096 = 564
];
for (const [a, b] of cases) {
const frame = createFrame({ stack: [a, b] });
mod(frame);
console.log(frame.stack[0]);
}
Common Usage
Cyclic Indexing
// Wrap index to array bounds
function cyclicIndex(uint256 index, uint256 arrayLength)
pure returns (uint256) {
return index % arrayLength;
}
// Circular buffer implementation
function circularBufferIndex(uint256 counter, uint256 bufferSize)
pure returns (uint256) {
return counter % bufferSize;
}
Range Constraints
// Ensure value is within range [0, max)
function constrain(uint256 value, uint256 max)
pure returns (uint256) {
return value % max;
}
// Hash to slot mapping
function hashToSlot(bytes32 hash, uint256 numSlots)
pure returns (uint256) {
return uint256(hash) % numSlots;
}
Even/Odd Checks
// Check parity
function isEven(uint256 n) pure returns (bool) {
return n % 2 == 0;
}
function isOdd(uint256 n) pure returns (bool) {
return n % 2 == 1;
}
Divisibility Testing
// Check if divisible
function isDivisibleBy(uint256 n, uint256 divisor)
pure returns (bool) {
require(divisor != 0, "division by zero");
return n % divisor == 0;
}
// Check if multiple of
function isMultipleOf(uint256 n, uint256 factor)
pure returns (bool) {
return factor != 0 && n % factor == 0;
}
// Extract lower k bits (equivalent to n % 2^k)
function extractLowerBits(uint256 n, uint8 k)
pure returns (uint256) {
return n % (1 << k);
}
// Extract byte at position
function extractByte(uint256 n, uint8 position)
pure returns (uint8) {
return uint8((n / (256 ** position)) % 256);
}
Implementation
/**
* MOD opcode (0x06) - Modulo operation (mod by zero returns 0)
*/
export function mod(frame: FrameType): EvmError | null {
// Consume gas (GasFastStep = 5)
frame.gasRemaining -= 5n;
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();
// Modulo by zero returns 0 (no exception)
const result = b === 0n ? 0n : a % b;
// 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 { mod } from './0x06_MOD.js';
describe('MOD (0x06)', () => {
it('computes modulo', () => {
const frame = createFrame([10n, 3n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
});
it('handles modulo by zero', () => {
const frame = createFrame([42n, 0n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('handles zero modulo zero', () => {
const frame = createFrame([0n, 0n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('checks even number', () => {
const frame = createFrame([42n, 2n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('checks odd number', () => {
const frame = createFrame([43n, 2n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
});
it('handles n % n = 0', () => {
const frame = createFrame([42n, 42n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('handles n % 1 = 0', () => {
const frame = createFrame([42n, 1n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('handles a < b case', () => {
const frame = createFrame([5n, 10n]);
expect(mod(frame)).toBeNull();
expect(frame.stack).toEqual([5n]);
});
it('consumes correct gas (5)', () => {
const frame = createFrame([10n, 3n], 100n);
expect(mod(frame)).toBeNull();
expect(frame.gasRemaining).toBe(95n);
});
});
Security
Modulo by Zero
// MOD returns 0 for division by zero (no revert)
// Contracts MUST check modulus
// WRONG: No check
function hash(uint256 value, uint256 buckets) pure returns (uint256) {
return value % buckets; // Returns 0 if buckets = 0!
}
// RIGHT: Explicit check
function hash(uint256 value, uint256 buckets) pure returns (uint256) {
require(buckets > 0, "buckets must be positive");
return value % buckets;
}
Bias in Random Selection
// WRONG: Biased modulo
function randomIndex(uint256 seed, uint256 arrayLength)
pure returns (uint256) {
return seed % arrayLength;
}
// If seed is random 0-255 and arrayLength = 100:
// Values 0-55 appear more often (3 times each)
// Values 56-99 appear less often (2 times each)
// BETTER: Reject and retry (in real implementation)
function fairRandomIndex(uint256 seed, uint256 arrayLength)
pure returns (uint256) {
uint256 max = type(uint256).max;
uint256 threshold = max - (max % arrayLength);
require(seed < threshold, "retry"); // Reject biased values
return seed % arrayLength;
}
Off-by-One Errors
// Common mistake in range calculations
function wrongRange(uint256 value) pure returns (bool) {
// WRONG: Allows 100 (should be 0-99)
return value % 100 <= 100;
}
function correctRange(uint256 value) pure returns (bool) {
// RIGHT: Constrains to 0-99
uint256 normalized = value % 100;
return normalized < 100; // Always true, but shows intent
}
Negative Results in Assembly
// MOD is unsigned only
// For signed modulo, use SMOD
// WRONG: Unexpected behavior with "negative" values
function wrongSignedMod(int256 a, int256 b) pure returns (int256) {
int256 result;
assembly {
result := mod(a, b) // Treats as unsigned!
}
return result;
}
// RIGHT: Use SMOD for signed modulo
function correctSignedMod(int256 a, int256 b) pure returns (int256) {
int256 result;
assembly {
result := smod(a, b) // Signed modulo
}
return result;
}
Benchmarks
MOD performance characteristics:
Execution time:
- ADD: 1.0x
- MUL: 1.2x
- MOD: 2.5x (same as DIV)
Gas efficiency:
- 5 gas per modulo operation
- ~200,000 modulo operations per million gas
- Often computed with DIV in single hardware instruction
Optimization for powers of 2:
// MOD by power of 2: use AND
uint256 result = x % 256; // 5 gas (MOD)
uint256 result = x & 0xFF; // 3 gas (AND) - 40% cheaper!
uint256 result = x % 1024; // 5 gas (MOD)
uint256 result = x & 0x3FF; // 3 gas (AND)
// Compiler often optimizes this automatically
References