Opcode Summary

PropertyValue
Opcodes0xA0 (LOG0), 0xA1 (LOG1), 0xA2 (LOG2), 0xA3 (LOG3), 0xA4 (LOG4)
MnemonicLOG0 through LOG4
Gas375 + 375 * num_topics + 8 * data_size + memory_expansion_cost
Stack Inputoffset, length, [topic0, topic1, ..., topicN] (N = 0..4 depending on opcode)
Stack Output(none)
BehaviorReads length bytes from memory starting at offset and appends a log entry to the transaction receipt. LOG1–LOG4 additionally pop 1–4 topics (each a 32-byte word) from the stack. Topics are indexed in bloom filters and by off-chain indexers; the data payload is not indexed. Logs are not accessible on-chain — no opcode can read previously emitted logs. LOG opcodes are prohibited in a STATICCALL context and will cause the entire sub-call to revert.

Threat Surface

LOG opcodes are the EVM’s only mechanism for emitting structured event data. Events are the backbone of off-chain infrastructure: block explorers, wallets, indexers (The Graph, Goldsky), analytics dashboards, and monitoring systems all consume logs to reconstruct on-chain activity. Despite their ubiquity, logs have a fundamentally deceptive property — they look authoritative but carry zero on-chain enforcement.

The threat surface centers on five properties:

  1. Logs are off-chain only — they cannot be read on-chain. No EVM opcode exists to read a previously emitted log. Events exist in transaction receipts, not in contract storage or the state trie. Any system that treats events as a source of truth for on-chain logic is architecturally broken. Smart contracts cannot verify that a particular event was emitted, and other contracts cannot condition their behavior on log data. This creates a permanent information asymmetry: off-chain systems “see” events, but on-chain code is blind to them.

  2. Any contract can emit any event with arbitrary parameters. The EVM does not validate that a Transfer(address,address,uint256) event was emitted by a contract that actually moved tokens. A malicious contract can emit Transfer events with any from, to, and value parameters — including from addresses it does not control. This enables event spoofing at the protocol level: block explorers, wallets, and bots that index Transfer events cannot distinguish genuine transfers from fabricated ones without verifying the emitting contract’s identity and actual state changes.

  3. Dynamic gas cost enables griefing. LOG gas scales linearly with data size (8 gas/byte) and topic count (375 gas/topic), plus memory expansion cost (quadratic). A contract that allows callers to control log data size can be griefed by forcing excessive gas consumption. In callback or multi-call patterns, an attacker can inflate gas usage by triggering functions that emit large events, potentially causing out-of-gas reverts in the caller.

  4. LOG is prohibited in STATICCALL, creating context-dependent failures. Any execution path that emits an event will revert when called via STATICCALL. This is by design (STATICCALL prevents state changes, and logs are a side effect), but contracts with mixed read/write functions or libraries that emit events for instrumentation will silently fail when called in a static context.

  5. Missing events create invisible audit gaps. If a contract performs a critical state change (ownership transfer, fund withdrawal, parameter update) without emitting an event, off-chain monitoring and incident response systems have no signal. The state change happens, but it’s invisible to everything except direct state inspection. This is not a LOG opcode bug — it’s the consequence of events being optional and unenforced.


Smart Contract Threats

T1: Event Spoofing and Phishing via Fake Transfer Events (High)

Any contract can emit a standard ERC-20 Transfer(address indexed from, address indexed to, uint256 value) event without performing any actual token transfer. The EVM does not enforce correlation between emitted events and state changes. This enables:

  • Address poisoning attacks. An attacker deploys a contract that emits Transfer events with a spoofed from address matching a victim’s frequently-used counterparty, but with a to address that is an attacker-controlled lookalike (matching the first and last few characters). The victim sees this fake transfer in their transaction history, copies the attacker’s address thinking it’s the legitimate recipient, and sends real funds to the attacker. This technique caused a single $68M WBTC loss in May 2024.

  • Fake token activity. An attacker deploys a contract with name = "USD Coin", symbol = "USDC", and decimals = 6, then emits Transfer events mimicking legitimate USDC activity. Block explorers display these as USDC transfers. Users and bots that rely on event data rather than verifying the emitting contract address are deceived.

  • Bot manipulation. MEV bots and automated trading systems that monitor Transfer events via eth_getLogs or WebSocket subscriptions may react to spoofed events. An attacker emitting fake large-value Transfer events from a DEX pair contract address could trick arbitrage bots into executing unprofitable trades based on phantom liquidity movements.

  • Reputation spoofing. Emitting Transfer events with from set to a well-known address (e.g., Vitalik’s address, a Coinbase hot wallet) creates the appearance of endorsement or association. No authorization from the from address is required.

Why it matters: Address poisoning powered by fake Transfer events has caused over 0.65) to seed a fake transaction in a victim’s history.

T2: Relying on Events as Source of Truth for On-Chain Logic (Critical)

Logs cannot be read by smart contracts. Any architecture that depends on events being “available” to on-chain code is fundamentally broken:

  • Bridges and relayers that trust event data. Cross-chain bridges often relay events from a source chain to a destination chain. If the bridge’s validator set trusts raw event data without verifying the emitting contract’s state, an attacker can deploy a contract on the source chain that emits fake events (e.g., Deposit(address,uint256)) to trick the bridge into releasing funds on the destination chain. Proper bridges use Merkle proofs over receipt tries, but lightweight bridges have been exploited through event trust.

  • Off-chain oracles feeding event data back on-chain. If an oracle monitors events and submits their data back to a smart contract as “truth,” the oracle is laundering unverifiable log data into on-chain state. An attacker who can emit the right events can manipulate the oracle’s feed.

  • Governance and multisig UI reliance on events. Governance frontends display proposals, votes, and execution status by indexing events. If an attacker emits fake ProposalExecuted or VoteCast events from a different contract, the UI may display incorrect governance state, leading administrators to take incorrect actions.

Why it matters: Any system that treats events as authoritative without verifying the emitting contract’s identity and corresponding state changes is vulnerable to spoofing. The on-chain/off-chain information gap is permanent and protocol-level.

T3: Gas Griefing via Large Log Data (Medium)

LOG gas cost is 375 + 375 * topics + 8 * data_size + memory_expansion_cost. The linear data cost and quadratic memory expansion cost can be weaponized:

  • Caller-controlled data size. If a contract emits an event containing caller-supplied data (e.g., emit Message(msg.sender, userProvidedBytes)), the caller controls gas consumption. In a pattern where contract A calls contract B and B emits a log, A must provide enough gas for B’s log emission. An attacker can supply large calldata that B logs, consuming A’s gas budget.

  • Multi-call amplification. In batch execution patterns (e.g., Multicall), each subcall may emit events. An attacker crafting a batch of calls that each emit maximum-size events can amplify gas consumption. With 30M block gas limit, a single transaction emitting ~3.5MB of log data would consume the entire block.

  • Memory expansion quadratic cost. If the log data requires reading from a high memory offset, the memory expansion cost grows quadratically: memory_cost = (memory_size_words^2) / 512 + 3 * memory_size_words. An attacker forcing a LOG to read from a large memory offset can cause disproportionate gas consumption even for small data payloads.

Why it matters: Gas griefing via LOG is exploitable in any contract where callers influence event data size. While not directly a fund-loss vector, it can cause denial of service by making critical functions too expensive to execute.

T4: Missing Events for Critical State Changes (Medium)

The absence of events is itself a threat. When contracts perform critical operations without emitting events, the result is an invisible state change:

  • Silent ownership transfers. If a contract’s transferOwnership() function updates the owner state variable without emitting an OwnershipTransferred event, monitoring systems and multisig dashboards have no signal. A compromised admin key can silently take ownership, and the first visible sign of the attack is fund drainage.

  • Unlogged parameter changes. DeFi protocols with configurable parameters (fee rates, collateral ratios, oracle addresses) that can be changed without events allow malicious governance actions to go undetected by off-chain monitoring until the damage is done.

  • Audit and compliance gaps. Regulatory frameworks and security auditors expect critical operations to be logged. Missing events make forensic analysis after an incident significantly harder, as investigators must reconstruct state changes from storage diffs rather than structured log data.

  • Indexer blind spots. The Graph, Dune Analytics, and other indexing services build subgraphs from events. If a contract performs state changes without events, the subgraph’s view of the contract’s state diverges from reality.

Why it matters: Every major smart contract audit framework (OpenZeppelin, Trail of Bits, Consensys Diligence) flags missing events on privileged operations as a finding. Silent state changes undermine the entire off-chain monitoring stack.

T5: LOG in STATICCALL Context Causes Revert (Low)

LOG opcodes are state-modifying side effects. When execution occurs inside a STATICCALL frame, any LOG instruction causes an immediate revert of the sub-call:

  • View functions that accidentally emit events. A function marked as view in Solidity should not emit events, but if it calls an internal function that emits (or if a library function emits for instrumentation), the function will succeed when called via a regular CALL but revert when called via STATICCALL (which is what eth_call and view function invocations use).

  • Proxy patterns with logging. If a proxy contract emits a log in its fallback() for every delegated call (e.g., for access logging), and a caller uses STATICCALL to invoke a read-only function through the proxy, the proxy’s logging causes the entire call to revert.

  • Composability breakage. Other contracts that STATICCALL into a contract expecting a pure read may fail if the target emits events in any code path reachable during the call.

Why it matters: While this is by-design behavior, it creates subtle bugs in contracts that mix event emission with read paths, especially in proxy and library patterns.


Protocol-Level Threats

P1: Bloom Filter Saturation and Deprecation (Low)

Each block header contains a 2048-bit bloom filter indexing the addresses and topics of all logs emitted in that block. Each log entry sets 3 bits per topic/address, meaning ~683 events can saturate a bloom filter entirely.

Security implications:

  • False positive rates are impractically high. At current gas limits, blocks routinely contain 1,000+ log-emitting addresses. The resulting false positive rate (0.5-1.5%) makes bloom filters unreliable for log filtering. Any application relying on bloom filters for security-critical event discovery will miss events or process irrelevant blocks.

  • EIP-7668 proposes removing bloom filters entirely. Authored by Vitalik Buterin (March 2024), this EIP acknowledges that “almost all dapps that access history end up doing so not through RPC calls to an Ethereum node, but through centralized extra-protocol services.” The proposal would empty bloom filters from block headers and receipts.

  • Targeted bloom poisoning. An attacker can deliberately emit events with topics that set the same bloom filter bits as a target contract’s events, causing the target’s bloom filter queries to return excessive false positives. The cost is approximately 515,000 gas per block (~$0.20-0.30 at typical gas prices).

P2: Log Storage and Node Burden (Low)

Logs are stored in transaction receipts, which full nodes must retain. Unlike state data (which can be pruned with state expiry proposals), receipt data grows monotonically:

  • Large event payloads increase receipt sizes. The data field of a log entry has no protocol-enforced maximum (only the block gas limit bounds it). Contracts that emit large events (e.g., storing serialized data in events as a cheap storage alternative) increase the storage burden on archive nodes.

  • “Events as cheap storage” pattern. Some protocols intentionally emit events containing data that would be expensive to store in contract storage (32,000 gas for SSTORE vs. ~8 gas/byte for log data). While this is gas-efficient for the emitter, it externalizes storage costs to node operators who must retain receipt data.

  • Receipt trie growth. Each transaction receipt includes the full log data. At scale, this makes syncing archive nodes increasingly expensive, with receipt data accounting for a significant fraction of total chain data.

P3: Log Data Cannot Be Proven Efficiently On-Chain (Medium)

While logs are included in the receipt trie (and thus covered by the block’s receipt root hash), proving that a specific log was emitted requires a Merkle proof over the receipt trie — which is expensive to verify on-chain:

  • Cross-chain event verification is costly. Bridges that verify events from a source chain must process receipt Merkle proofs, which involve multiple keccak256 operations and RLP decoding. This makes trustless cross-chain event verification gas-intensive (often 200,000+ gas per proof).

  • No native receipt proof verification opcode. Unlike BLOCKHASH (which provides limited historical block hash access), there is no opcode for verifying receipt or log inclusion. All on-chain event verification must be done via custom Merkle proof verification contracts.


Edge Cases

Edge CaseBehaviorSecurity Implication
LOG0 (no topics)Emits an anonymous event with only data payload; no topic bytes in the bloom filterCannot be filtered by topic in eth_getLogs; only discoverable by scanning all logs. Useful for stealth logging but makes off-chain indexing harder.
LOG4 (maximum topics)Emits event with 4 indexed topics (128 bytes of indexed data)Maximum filtering granularity but also maximum gas cost (375 + 4*375 = 1,875 base gas). Topics are each 32 bytes and independently indexable.
Empty data (length = 0)Valid; emits a log entry with topics but zero-length data fieldGas cost is just 375 + 375 * topics (no per-byte data charge). Commonly used for address poisoning (minimal cost to seed a fake Transfer).
Very large data payloadBounded only by block gas limit; at 8 gas/byte, ~3.5MB of log data would consume 30M gasQuadratic memory expansion cost applies if data is read from high memory offsets. A single transaction can emit megabytes of log data.
LOG inside DELEGATECALLEmits log with the calling contract’s address (the proxy), not the implementationEvent address field in the log entry is the contract whose storage context is active, consistent with how DELEGATECALL preserves context. Indexers attribute the event to the proxy address.
LOG inside STATICCALLImmediately reverts the sub-callBy design — LOG is a state-modifying side effect. Any code path reachable via STATICCALL must not emit events, or the call fails.
LOG after REVERT in same callLogs from a reverted sub-call are discarded from the transaction receiptOnly logs from successfully completed execution frames persist. Reverted logs do not appear in eth_getLogs results.
Identical event signatures from different contractsMultiple contracts can emit Transfer(address,address,uint256) — the topic0 hash is identicalIndexers must filter by contract address, not just event signature. Failing to check the emitting contract is the root cause of event spoofing attacks.
LOG in constructorValid; events emitted during contract creation appear in the deployment transaction receiptThe address field in the log entry is the newly created contract’s address (computed via CREATE/CREATE2 before code execution).

Real-World Exploits

Exploit 1: Address Poisoning via Fake Transfer Events — $68M WBTC Theft (May 2024)

Root cause: A victim copied a wallet address from their transaction history that had been planted by an attacker using a spoofed Transfer event from a malicious contract.

Details: On May 3, 2024, an unidentified cryptocurrency whale lost 1,155 WBTC (~$68M) in a single transaction. The attack exploited the LOG opcode’s fundamental property: any contract can emit any event with arbitrary parameters.

The attacker studied the victim’s transaction patterns, identified frequently-used recipient addresses, and used GPU brute-forcing (~2 billion addresses/second) to generate a vanity address matching the first 4 and last 6 characters of a legitimate recipient. The attacker then deployed a contract that called emit Transfer(victim_address, lookalike_address, 0) — a zero-value fake transfer costing only ~$0.65 in gas. This planted the lookalike address in the victim’s transaction history on block explorers. When the victim later needed to send WBTC, they copied the attacker’s address from their history, trusting the visual similarity.

LOG’s role: The entire attack was enabled by the LOG opcode’s permissionless nature. The attacker’s contract emitted a standard ERC-20 Transfer event with the victim’s address as from — despite having no relationship to the victim or their tokens. The EVM validated nothing about the event’s semantic correctness. Block explorers displayed the spoofed event identically to legitimate transfers.

Impact: $68M stolen in a single transaction. The attacker later returned the funds under negotiation pressure, but the attack demonstrated the catastrophic potential of event spoofing at scale.

References:


Exploit 2: Systematic Address Poisoning Campaign — $144M in Losses (Nov 2022 - Feb 2024)

Root cause: Industrialized address poisoning using fake Transfer events at scale, targeting high-value USDC and USDT holders.

Details: Research published at ACM CCS 2024 using the “Poison-Hunter” detection system revealed a massive, sustained address poisoning campaign across Ethereum. Attackers automated the process: (1) monitor mempool and historical transactions for high-value addresses, (2) generate lookalike vanity addresses matching the first and last characters of each target’s counterparties, (3) deploy minimal contracts that emit Transfer events from the target’s address to the lookalike address, (4) seed the target’s transaction history on block explorers.

The campaign targeted primarily USDC and USDT holders, exploiting the fact that stablecoin transfers are routine, high-frequency operations where users are most likely to copy-paste from history. Over 1,800 victim addresses were confirmed, with approximately 144M in potential losses.

LOG’s role: Each poisoning transaction consisted almost entirely of LOG operations — the contracts had no real token logic, only emit Transfer(...) calls. The cost per poisoning attempt was negligible (a few cents), while the potential payoff was millions. The LOG opcode’s lack of access control or semantic validation is the protocol-level enabler of the entire attack class.

Impact: $90-144M in cumulative losses across 1,800+ victims. Address poisoning toolkits became available on darknet marketplaces, commoditizing the attack.

References:


Exploit 3: Event Spoofing for Bot Manipulation and Analytics Pollution (Ongoing, 2023-Present)

Root cause: Automated systems (MEV bots, analytics platforms, portfolio trackers) consuming raw event data without verifying the emitting contract’s legitimacy.

Details: Security researcher Ian documented in April 2025 how trivial it is to deploy contracts on Base and other EVM chains that emit standard Transfer events from any address — including spoofing transfers “from” the USDC contract address or “from” Vitalik Buterin’s address. The deployed proof-of-concept contracts contained zero token logic (no storage, no balances, no actual transfer mechanism) but emitted events that block explorers displayed as legitimate transfers.

The implications extend beyond phishing: (1) MEV bots monitoring Transfer events via eth_subscribe may execute trades based on phantom token movements, (2) analytics platforms (Dune, Nansen) that aggregate Transfer events without contract verification show inflated or fabricated activity metrics, (3) portfolio trackers display fake token balances, which attackers leverage to trick users into signing malicious approval transactions (“you received 10,000 USDC — click here to claim”).

LOG’s role: The proof-of-concept is a direct demonstration of the LOG opcode’s design: LOG3 with the Transfer event signature as topic0, from as topic1, to as topic2, and amount encoded in data. The EVM executes this identically whether the contract holds real tokens or not.

Impact: Ongoing; contributes to the broader address poisoning and phishing ecosystem. Exact losses from bot manipulation are difficult to quantify but are estimated in the millions annually.

References:


Attack Scenarios

Scenario A: Address Poisoning via Fake Transfer Event

contract AddressPoisoner {
    // Standard ERC-20 Transfer signature -- identical topic0 hash
    event Transfer(address indexed from, address indexed to, uint256 value);
 
    // Attacker generates a vanity address matching the victim's real
    // counterparty (same first 4 and last 6 hex characters).
    // Cost: ~72 seconds of GPU time at 2B addresses/second.
    function poison(address victim, address lookalike) external {
        // Emits a Transfer event "from" the victim to the lookalike address.
        // The victim's wallet/explorer shows this in their history.
        // No tokens move. No authorization required.
        // Gas cost: ~375 + 375*2 + 8*32 = 1,381 gas (~$0.01)
        emit Transfer(victim, lookalike, 0);
    }
 
    // When the victim copies the address from their transaction history,
    // they send real funds to the attacker's lookalike address.
}

Scenario B: Fake Event Emission to Manipulate Off-Chain Systems

contract FakeUSDC {
    string public name = "USD Coin";
    string public symbol = "USDC";
    uint8 public decimals = 6;
 
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
 
    // No balances, no real token logic.
    // Block explorers display this contract's events as "USDC" transfers.
    function spoofTransfer(address from, address to, uint256 amount) external {
        emit Transfer(from, to, amount);
    }
 
    // Attack: An off-chain oracle or bridge relayer monitors Transfer events
    // and processes them without verifying the emitting contract is the real
    // USDC contract (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).
    // The attacker spoofs a large deposit event, and the relayer credits
    // the attacker's account on the destination chain.
}

Scenario C: Gas Griefing via Log Data Inflation

contract VulnerableMessageBoard {
    event MessagePosted(address indexed author, string content);
 
    // VULNERABLE: Caller controls the size of the emitted log data.
    function postMessage(string calldata content) external {
        // No length check on content.
        // An attacker passes a 100KB string: 8 * 100,000 = 800,000 gas
        // just for the log data, plus memory expansion costs.
        emit MessagePosted(msg.sender, content);
    }
}
 
contract GriefingAttacker {
    function attack(VulnerableMessageBoard board) external {
        // Generate 100KB of junk data
        bytes memory junk = new bytes(100_000);
        board.postMessage(string(junk));
        // Consumes ~800K+ gas in LOG costs alone,
        // potentially causing out-of-gas in the calling context
        // if gas was budgeted for a normal-size message.
    }
}

Scenario D: Missing Events Enable Silent Fund Drainage

// VULNERABLE: No events on critical operations.
contract UnsafeVault {
    address public owner;
    mapping(address => uint256) public balances;
 
    function transferOwnership(address newOwner) external {
        require(msg.sender == owner);
        owner = newOwner;
        // No OwnershipTransferred event!
        // Off-chain monitoring sees nothing.
    }
 
    function emergencyWithdraw(address to) external {
        require(msg.sender == owner);
        uint256 amount = address(this).balance;
        payable(to).transfer(amount);
        // No Withdrawal event!
        // Attacker silently takes ownership, drains funds.
        // The first visible signal is the ETH balance dropping,
        // which requires active balance monitoring rather than event subscriptions.
    }
}
 
// Safe alternative:
contract SafeVault {
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event EmergencyWithdrawal(address indexed to, uint256 amount);
 
    address public owner;
 
    function transferOwnership(address newOwner) external {
        require(msg.sender == owner);
        address prev = owner;
        owner = newOwner;
        emit OwnershipTransferred(prev, newOwner);
    }
 
    function emergencyWithdraw(address to) external {
        require(msg.sender == owner);
        uint256 amount = address(this).balance;
        payable(to).transfer(amount);
        emit EmergencyWithdrawal(to, amount);
    }
}

Scenario E: LOG Reverts Inside STATICCALL

contract InstrumentedLibrary {
    event AccessLog(address indexed caller, bytes4 indexed selector);
 
    // Emits an event for every call -- useful for monitoring,
    // but breaks STATICCALL compatibility.
    function getPrice(address token) external returns (uint256) {
        emit AccessLog(msg.sender, msg.sig);
        // ... price lookup logic ...
        return 1e18;
    }
}
 
contract PriceConsumer {
    InstrumentedLibrary lib;
 
    function checkPrice(address token) external view returns (uint256) {
        // STATICCALL is used because this is a view function.
        // But lib.getPrice() emits an event, which is prohibited
        // in a STATICCALL context => the call reverts silently.
        return lib.getPrice(token);
    }
 
    // The Solidity compiler may not catch this if getPrice() is not
    // marked as view/pure. The revert only manifests at runtime
    // when called via eth_call or from another view function.
}

Mitigations

ThreatMitigationImplementation
T1: Event spoofing / address poisoningAlways verify the emitting contract address, not just event dataFilter eth_getLogs results by contract address; maintain allowlists of canonical token contract addresses; display emitting contract prominently in UIs
T1: Fake token eventsUse token registries and verified contract listsCross-reference events against Etherscan verified contracts, CoinGecko/CoinMarketCap token lists, or on-chain registries
T1: Address poisoning in walletsFull address display and address book featuresNever truncate addresses in transaction history; implement address books with user-confirmed labels; warn on first-time recipients
T2: Relying on events as source of truthVerify state changes, not eventsFor bridges: use receipt Merkle proofs with emitting contract verification. For oracles: read state directly via SLOAD/STATICCALL rather than indexing events
T2: Off-chain oracle manipulationVerify emitting contract identity in oracle logicCheck log.address matches the expected canonical contract before processing any event data
T3: Gas griefing via log data sizeCap caller-controlled data in emitted eventsrequire(bytes(content).length <= MAX_LENGTH) before any emit; use pagination for large data
T3: Memory expansion cost attackBound memory usage before LOGValidate offset and length parameters; avoid emitting arbitrarily large memory ranges
T4: Missing events on critical operationsEmit events for all privileged state changesFollow OpenZeppelin patterns: OwnershipTransferred, Paused, Unpaused, RoleGranted, etc. for every admin function
T4: Monitoring blind spotsMandate events in code reviews and auditsAdd “missing event” checks to CI/audit checklists; use static analysis tools (Slither missing-events-access-control detector)
T5: LOG in STATICCALLSeparate read and write code pathsMark functions that emit events as non-view; provide separate view accessors that do not emit events; avoid logging in library functions that may be called via STATICCALL

Compiler/EIP-Based Protections

  • Solidity view/pure enforcement: The Solidity compiler prevents emit statements in functions marked view or pure, catching most STATICCALL incompatibilities at compile time. However, this does not protect against cross-contract calls to non-view functions from a view context.
  • EIP-7668 (Remove Bloom Filters, Proposed 2024): Authored by Vitalik Buterin, this proposal removes bloom filters from block headers and receipts, acknowledging their impracticality at current scale. Does not change LOG behavior but eliminates the false sense of security from bloom-filter-based event discovery.
  • EIP-7745 (Trustless Log Index, Proposed): Proposes replacing bloom filters with a more efficient trustless log index using Merkle trees and dimensional sparse bitmaps, improving event discoverability without centralized indexers.
  • Slither static analysis: The missing-events-access-control and missing-events-arithmetic detectors flag state-changing functions that lack corresponding events, catching T4 at development time.

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractHighHigh144M cumulative losses (2022-2024)
T2Smart ContractCriticalMediumBridge exploits via event trust; off-chain oracle manipulation
T3Smart ContractMediumMediumGas griefing in callback/multicall patterns
T4Smart ContractMediumHighAudit finding in most smart contract reviews; silent exploits are underreported
T5Smart ContractLowLowSubtle runtime failures in proxy/library patterns
P1ProtocolLowN/ABloom filter saturation (EIP-7668 proposes removal)
P2ProtocolLowLowArchive node storage growth; events-as-cheap-storage pattern
P3ProtocolMediumMediumCostly cross-chain receipt proof verification

OpcodeRelationship
MLOAD (0x51)LOG reads the data payload from memory at the specified offset and length. MLOAD (and MSTORE) are used to prepare the data that LOG will emit. Memory expansion costs from LOG are calculated the same way as for MLOAD.
KECCAK256 (0x20)The first topic (topic0) in LOG1-LOG4 is conventionally the keccak256 hash of the event signature (e.g., keccak256("Transfer(address,address,uint256)")). This convention is how indexers match events to their ABI definitions, but it is not enforced by the EVM — a contract can put any 32-byte value as topic0.
STATICCALL (0xFA)LOG is prohibited inside STATICCALL execution frames. Any LOG instruction in a STATICCALL context causes the sub-call to revert. This is the mechanism that prevents view/pure functions from emitting events.
SSTORE (0x55)SSTORE modifies contract storage (the state trie); LOG writes to transaction receipts (the receipt trie). Events are sometimes used as a cheaper alternative to storage for data that only needs to be accessible off-chain (8 gas/byte for LOG vs. 20,000 gas minimum for SSTORE).
CALL (0xF1)The address field in a log entry is the contract executing the LOG instruction. In a CALL, this is the called contract. In a DELEGATECALL, this is the delegating contract (proxy). Understanding which address appears in the log is critical for verifying event authenticity.
DELEGATECALL (0xF4)When LOG executes inside a DELEGATECALL frame, the log entry’s address field is the delegating contract’s (proxy’s) address, not the implementation’s. This means indexers attribute events to the proxy, which is correct for state context but can confuse event verification if the implementation contract address is expected.