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: 0x51
Introduced: Frontier (EVM genesis)
MLOAD reads a 32-byte word from memory at the specified offset. The value is interpreted as a big-endian 256-bit unsigned integer and pushed to the stack. Uninitialized memory reads as zero.
This is the primary mechanism for reading arbitrary data from memory during execution.
Specification
Stack Input:
Stack Output:
value (32 bytes read at offset, as uint256)
Gas Cost: 3 + memory expansion cost
Operation:
value = memory[offset:offset+32] (big-endian)
Behavior
MLOAD pops an offset from the stack, reads 32 bytes starting at that offset, and pushes the result as a 256-bit value.
- Offset is interpreted as unsigned 256-bit integer (max 2^256 - 1)
- Reads exactly 32 bytes (1 word)
- Uninitialized bytes read as 0x00
- Memory automatically expands to accommodate read (quadratic cost)
- Bytes are combined in big-endian order (byte 0 = most significant)
Examples
Basic Load
import { mload } from '@tevm/voltaire/evm/instructions/memory';
import { createFrame } from '@tevm/voltaire/evm/Frame';
const frame = createFrame();
// Write test data at offset 0
frame.memory.set(0, 0x12);
frame.memory.set(1, 0x34);
frame.memory.set(2, 0x56);
frame.memory.set(3, 0x78);
// ... bytes 4-31 are zero
frame.stack.push(0n); // offset
const err = mload(frame);
console.log(frame.stack[0]); // 0x12345678_00000000_...00000000n
console.log(frame.pc); // 1 (incremented)
Load from Uninitialized Memory
const frame = createFrame();
frame.stack.push(0n); // offset
mload(frame);
// Uninitialized memory reads as zero
console.log(frame.stack[0]); // 0n
console.log(frame.memorySize); // 32 (expanded to 1 word)
Load with Non-Zero Offset
const frame = createFrame();
// Write pattern starting at offset 32
for (let i = 0; i < 32; i++) {
frame.memory.set(32 + i, i);
}
frame.stack.push(32n); // offset
mload(frame);
// Result: bytes 0-31 (each byte contains its index)
console.log(frame.stack[0]); // 0x00010203_...1f n
Multiple Reads
const frame = createFrame();
// Initialize memory
for (let i = 0; i < 64; i++) {
frame.memory.set(i, i);
}
// Read word 1 (bytes 0-31)
frame.stack.push(0n);
mload(frame);
const word1 = frame.stack.pop();
// Read word 2 (bytes 32-63)
frame.stack.push(32n);
mload(frame);
const word2 = frame.stack.pop();
console.log(word1 !== word2); // true (different data)
Gas Cost
Base cost: 3 gas (GasFastestStep)
Memory expansion: Quadratic based on access range
Formula:
words_required = ceil((offset + 32) / 32)
expansion_cost = (words_required)² / 512 + 3 * (words_required - words_old)
total_cost = 3 + expansion_cost
Examples:
- Reading bytes 0-31: 1 word, no prior expansion: 3 gas
- Reading bytes 1-32: 2 words (rounds up), 1 word prior: 3 + (4 - 1) = 6 gas
- Reading bytes 0-4095: ~125 words: 3 + (125² / 512 + expansion) ≈ 3 + 30 = 33 gas
Memory is expensive for large accesses due to quadratic expansion formula.
Edge Cases
Byte Alignment
// Reading at non-word-aligned offset
const frame = createFrame();
// Write at offset 1
frame.memory.set(1, 0xff);
frame.stack.push(1n);
mload(frame);
// Reads bytes 1-32, expands memory to 2 words (64 bytes)
console.log(frame.memorySize); // 64
console.log(frame.stack[0]); // 0xff000000_...00000000n
Maximum Offset
const frame = createFrame({ gasRemaining: 1000000000n });
const maxOffset = BigInt(Number.MAX_SAFE_INTEGER);
frame.stack.push(maxOffset);
const err = mload(frame);
console.log(err); // May be OutOfGas if expansion is too large
Out of Bounds
const frame = createFrame({ gasRemaining: 2n });
frame.stack.push(0n);
const err = mload(frame);
console.log(err); // { type: "OutOfGas" }
Stack Underflow
const frame = createFrame();
// No offset on stack
const err = mload(frame);
console.log(err); // { type: "StackUnderflow" }
Common Usage
Loading ABI-Encoded Data
// ABI encoding: function selector (4 bytes) + parameters
assembly {
// calldata is in memory starting at offset 0
let selector := mload(0)
// Selector is in high 4 bytes: selector >> 224
let func := selector
}
Reading Function Parameters
assembly {
// Free memory pointer
let ptr := mload(0x40)
// Load stored value
let value := mload(ptr)
// Load next value
let next := mload(add(ptr, 0x20))
}
Iterating Memory
assembly {
let offset := 0x20
let ptr := mload(offset)
// Chain loading
let first := mload(ptr)
let second := mload(add(ptr, 0x20))
let third := mload(add(ptr, 0x40))
}
Memory Safety
Load safety properties:
- No side effects: Reading memory never modifies state or storage
- Initialization: Uninitialized memory safely reads as zero
- Bounds: Out-of-bounds reads don’t error - they just allocate and charge gas
- Atomicity: 32-byte load is atomic (no tearing)
Applications must ensure offset validity:
// Good: Check bounds before reading
require(offset + 32 <= memorySize, "out of bounds");
let value := mload(offset);
// Bad: Assumes bounds checking
let value := mload(userSuppliedOffset); // Can read beyond allocated
Implementation
/**
* MLOAD opcode (0x51) - Load 32-byte word from memory
*/
export function mload(frame: FrameType): EvmError | null {
// Pop offset from stack
if (frame.stack.length < 1) {
return { type: "StackUnderflow" };
}
const offset = frame.stack.pop();
// Cast offset to u32 (check bounds)
const off = Number(offset);
if (!Number.isSafeInteger(off) || off < 0) {
return { type: "OutOfBounds" };
}
// Calculate memory expansion
const endBytes = BigInt(off + 32);
const expansionCost = calculateMemoryExpansion(endBytes);
// Charge gas
frame.gasRemaining -= 3n + expansionCost;
if (frame.gasRemaining < 0n) {
return { type: "OutOfGas" };
}
// Expand memory
const alignedSize = Math.ceil((off + 32) / 32) * 32;
frame.memorySize = Math.max(frame.memorySize, alignedSize);
// Read 32 bytes in big-endian order
let result = 0n;
for (let i = 0; i < 32; i++) {
const byte = frame.memory.get(off + i) ?? 0;
result = (result << 8n) | BigInt(byte);
}
// Push result to stack
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 { mload } from './0x51_MLOAD.js';
describe('MLOAD (0x51)', () => {
it('loads 32 bytes from memory', () => {
const frame = createFrame();
for (let i = 0; i < 32; i++) {
frame.memory.set(i, i + 1);
}
frame.stack.push(0n);
expect(mload(frame)).toBeNull();
expect(frame.stack[0]).toBe(0x0102030405...n);
expect(frame.pc).toBe(1);
});
it('loads from uninitialized memory as zero', () => {
const frame = createFrame();
frame.stack.push(0n);
mload(frame);
expect(frame.stack[0]).toBe(0n);
expect(frame.memorySize).toBe(32);
});
it('expands memory to word boundary', () => {
const frame = createFrame();
frame.stack.push(1n); // Offset 1 -> bytes 1-32 = 2 words
mload(frame);
expect(frame.memorySize).toBe(64);
});
it('charges correct gas for expansion', () => {
const frame = createFrame({ gasRemaining: 1000n });
frame.stack.push(0n);
mload(frame);
// 3 base + memory expansion
expect(frame.gasRemaining).toBeLessThan(1000n);
});
it('returns StackUnderflow when empty', () => {
const frame = createFrame();
expect(mload(frame)).toEqual({ type: "StackUnderflow" });
});
it('returns OutOfGas when insufficient', () => {
const frame = createFrame({ gasRemaining: 2n });
frame.stack.push(0n);
expect(mload(frame)).toEqual({ type: "OutOfGas" });
});
});
Edge Cases Tested
- Basic load (32 bytes)
- Uninitialized memory (zeros)
- Non-zero offset with word boundary alignment
- Memory expansion costs
- Stack underflow/overflow
- Out of gas conditions
- Endianness verification
Security Considerations
Memory Disclosure
Memory is transaction-scoped and doesn’t persist to state. However, careful handling needed:
// Good: Clear sensitive data
assembly {
mstore(offset, 0) // Clear temporary value
}
// Risky: Sensitive data in memory
assembly {
let privateKey := mload(0x80) // Don't do this
}
Out-of-Bounds Reads
Memory expands automatically - reading beyond allocated areas is safe but expensive:
// Safe but expensive
let value := mload(1000000) // Quadratic gas cost to expand
// Better: Validate before reading
require(offset < endOfData, "invalid offset");
let value := mload(offset);
Benchmarks
MLOAD is among the fastest EVM operations:
Relative performance:
- MLOAD (initialized): 1.0x baseline
- MLOAD (uninitialized): 1.0x baseline
- MSTORE: 1.0x (similar cost)
- SLOAD: 100x slower (storage vs memory)
Gas scaling:
- First word: 3 gas
- Second word: 3 gas (expansion ≈ 3)
- Large memory: Quadratic scaling beyond practical use
References