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: 0xa0
Introduced: Frontier (EVM genesis)
LOG0 emits a log entry with no indexed topics. Only the event data (non-indexed parameters) is logged, making it useful for simple event tracking where filtering by topics is not needed.
Specification
Stack Input:
Stack Output:
Gas Cost: 375 + (8 × data_length) + memory_expansion_cost
Operation:
data = memory[offset : offset + length]
log_entry = { address: msg.sender, topics: [], data: data }
append log_entry to logs
Behavior
LOG0 pops offset and length from the stack, reads data from memory, and appends a log entry:
- Offset: Starting position in memory (256-bit value)
- Length: Number of bytes to read from memory (256-bit value)
- Data: Bytes read from memory, padded with zeros if beyond allocated memory
- Topics: Empty array (0 indexed parameters)
Memory Expansion
If the data range extends beyond current memory allocation, memory expands to word boundaries:
new_memory_size = (ceil((offset + length) / 32)) * 32
Static Call Protection
LOG0 cannot execute in static call context (EIP-214):
function badLog() external view {
// Reverts: StaticCallViolation
}
Examples
Empty Log
import { handler_0xa0_LOG0 } from '@tevm/voltaire/evm/log';
const frame = createFrame({
address: "0x1234567890123456789012345678901234567890",
stack: [0n, 0n], // offset=0, length=0
gasRemaining: 1000000n,
});
const err = handler_0xa0_LOG0(frame);
console.log(err); // null (success)
console.log(frame.logs); // [{ address, topics: [], data: Uint8Array(0) }]
console.log(frame.gasRemaining); // 999625n (1000000 - 375)
Log with Data
const frame = createFrame({
address: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
memory: new Map([
[0, 0xde], [1, 0xad], [2, 0xbe], [3, 0xef]
]),
stack: [0n, 4n], // offset=0, length=4
gasRemaining: 1000000n,
});
handler_0xa0_LOG0(frame);
const log = frame.logs[0];
console.log(log.data); // Uint8Array(4) [0xde, 0xad, 0xbe, 0xef]
console.log(frame.gasRemaining); // 999617n (375 base + 32 memory + 32 data)
Solidity Event with No Topics
event SimpleLog(string message);
contract Logger {
function log(string memory msg) public {
emit SimpleLog(msg); // Compiler generates LOG0
}
}
// Usage
Logger logger = new Logger();
logger.log("Hello, world!");
// Transaction receipt includes log with empty topics
Non-Indexed Event Parameters
event Transfer(uint256 indexed id, address from, address to, uint256 value);
// If only 'id' is indexed, Solidity uses LOG1
// But we can emit an event with all non-indexed params using LOG0:
event Data(string text, uint256 amount, bytes payload);
contract DataLogger {
function logData(string memory text, uint256 amount, bytes memory payload) public {
emit Data(text, amount, payload); // LOG0 (no indexed params)
}
}
Gas Cost
Base Cost: 375 gas
Data Cost: 8 gas per byte
Memory Expansion: Proportional to new memory range
Examples:
- Empty: 375 gas
- 1 byte: 375 + 8 = 383 gas
- 32 bytes: 375 + 256 = 631 gas
- 64 bytes: 375 + 512 + 3 (memory expansion) = 890 gas
Edge Cases
Zero-Length Log
const frame = createFrame({ stack: [100n, 0n] });
handler_0xa0_LOG0(frame);
// Valid: logs empty data, no memory expansion
Large Data
const frame = createFrame({
stack: [0n, 10000n],
gasRemaining: 100000n,
});
handler_0xa0_LOG0(frame);
// Gas: 375 + 80000 (data) + memory expansion
// Result: OutOfGas (insufficient gas)
Out of Bounds Memory
const frame = createFrame({
stack: [0n, 1000n],
memory: new Map(), // Empty
gasRemaining: 100000n,
});
handler_0xa0_LOG0(frame);
// Memory fills with zeros from current_size to 1000
// log.data = Uint8Array(1000) filled with zeros
Stack Underflow
const frame = createFrame({ stack: [0n] }); // Only 1 item
const err = handler_0xa0_LOG0(frame);
console.log(err); // { type: "StackUnderflow" }
Out of Gas
const frame = createFrame({
stack: [0n, 0n],
gasRemaining: 374n, // Not enough for base cost
});
const err = handler_0xa0_LOG0(frame);
console.log(err); // { type: "OutOfGas" }
Common Usage
Simple State Changes
event Minted(uint256 amount);
event Burned(uint256 amount);
contract Token {
function mint(uint256 amount) public {
totalSupply += amount;
emit Minted(amount); // LOG0
}
function burn(uint256 amount) public {
totalSupply -= amount;
emit Burned(amount); // LOG0
}
}
Unindexed Data Logging
event ConfigUpdated(string newConfig);
contract Config {
function updateConfig(string memory newConfig) public {
config = newConfig;
emit ConfigUpdated(newConfig); // LOG0 (newConfig is non-indexed)
}
}
Status Events
event StatusChanged(string status);
contract Service {
function shutdown() public {
isActive = false;
emit StatusChanged("OFFLINE"); // LOG0
}
}
Security
Cannot Block On-Chain Filtering
Since LOG0 has no topics, external systems cannot efficiently filter by indexed parameters. This is intentional—use LOG1-LOG4 when filtering capability is needed.
// INEFFICIENT: No topic filtering
event EventWithoutTopics(address user, uint256 amount);
// BETTER: Use indexed parameters
event EventWithTopics(address indexed user, uint256 amount);
Static Call Context Restrictions
LOG0 reverts in view/pure functions or during staticcall operations:
function badView() external view {
emit SomeEvent(); // Reverts: cannot log in view context
}
contract Caller {
function staticCallBad(address target) public {
target.staticcall(abi.encodeCall(Logger.log, ())); // Reverts
}
}
Memory Boundaries
LOG0 reads memory up to offset + length. Uninitialized memory is zero-filled:
// Be aware of what's written to memory
function logUninitialized() public {
// If no data written to memory[0:100], emits 100 zero bytes
emit Data();
}
Implementation
/**
* LOG0 opcode (0xa0) - Emit log with no indexed topics
*/
export function handler_0xa0_LOG0(frame: FrameType): EvmError | null {
// Check static call (EIP-214)
if (frame.isStatic) {
return { type: "WriteProtection" };
}
// Check stack (need offset, length)
if (frame.stack.length < 2) {
return { type: "StackUnderflow" };
}
// Pop offset and length
const offset = frame.stack.pop();
const length = frame.stack.pop();
// Validate bounds (u32 max)
if (offset > Number.MAX_SAFE_INTEGER || length > Number.MAX_SAFE_INTEGER) {
return { type: "OutOfBounds" };
}
const offsetNum = Number(offset);
const lengthNum = Number(length);
// Calculate gas: 375 base + 8 per byte
const logGas = 375n;
const dataGas = BigInt(lengthNum) * 8n;
const totalGas = logGas + dataGas;
// Memory expansion cost
if (lengthNum > 0) {
const endByte = offsetNum + lengthNum;
const newMemWords = Math.ceil(endByte / 32);
const newMemSize = newMemWords * 32;
const memExpansion = calculateMemoryExpansion(frame.memorySize, newMemSize);
frame.memorySize = newMemSize;
frame.gasRemaining -= BigInt(memExpansion);
}
// Consume gas
frame.gasRemaining -= totalGas;
if (frame.gasRemaining < 0n) {
return { type: "OutOfGas" };
}
// Read data from memory
const data = new Uint8Array(lengthNum);
for (let i = 0; i < lengthNum; i++) {
data[i] = frame.memory.get(offsetNum + i) ?? 0;
}
// Create and append log entry
const logEntry = {
address: frame.address,
topics: [],
data,
};
if (!frame.logs) frame.logs = [];
frame.logs.push(logEntry);
// Increment PC
frame.pc += 1;
return null;
}
Testing
Test Cases
import { describe, it, expect } from 'vitest';
import { handler_0xa0_LOG0 } from './0xa0_LOG0.js';
describe('LOG0 (0xa0)', () => {
it('emits log with empty data', () => {
const frame = createFrame({
stack: [0n, 0n],
gasRemaining: 1000000n,
});
const err = handler_0xa0_LOG0(frame);
expect(err).toBeNull();
expect(frame.logs).toHaveLength(1);
expect(frame.logs[0].topics).toEqual([]);
expect(frame.gasRemaining).toBe(999625n);
});
it('reads data from memory', () => {
const frame = createFrame({
memory: new Map([[0, 0xde], [1, 0xad], [2, 0xbe], [3, 0xef]]),
stack: [0n, 4n],
gasRemaining: 1000000n,
});
handler_0xa0_LOG0(frame);
const log = frame.logs[0];
expect(log.data).toEqual(new Uint8Array([0xde, 0xad, 0xbe, 0xef]));
});
it('returns WriteProtection in static context', () => {
const frame = createFrame({ isStatic: true, stack: [0n, 0n] });
const err = handler_0xa0_LOG0(frame);
expect(err).toEqual({ type: "WriteProtection" });
});
it('returns StackUnderflow with insufficient stack', () => {
const frame = createFrame({ stack: [0n] });
const err = handler_0xa0_LOG0(frame);
expect(err).toEqual({ type: "StackUnderflow" });
});
it('returns OutOfGas when insufficient gas', () => {
const frame = createFrame({
stack: [0n, 0n],
gasRemaining: 374n,
});
const err = handler_0xa0_LOG0(frame);
expect(err).toEqual({ type: "OutOfGas" });
});
it('expands memory correctly', () => {
const frame = createFrame({
stack: [0n, 100n],
gasRemaining: 1000000n,
});
handler_0xa0_LOG0(frame);
expect(frame.memorySize).toBe(128); // ceil(100/32)*32
});
});
References