Executive Overview
- Solidity events with
indexedparameters of complex types (structs, arrays, strings, bytes) require additional compiler-generated code to compute a Keccak256 hash of the data
- This additional code consumes stack slots and can unexpectedly trigger "stack too deep" errors, even when the original code compiled successfully
- At the moment of writing this article, the compiler error message does not indicate that the issue originates from event indexing, making it difficult to debug
- Understanding this behavior is important when designing events for complex data types or when recommending indexed parameters during code reviews
Understanding Events
Events are a fundamental part of the Ethereum ecosystem, serving two primary purposes:
- A cheaper alternative to storage, provided that the contract itself does not need access to the emitted data
- A mechanism to trigger off-chain applications listening for specific on-chain activity (events)
Events are triggered (emitted) on-chain by using the emit keyword on a previously declared event. Events can contain indexed parameters, which are stored as separate "topics" in the log entry, enabling efficient filtering and searching within blocks.
Explaining events themselves is beyond the scope of this post. For readers unfamiliar with events, the following resources provide comprehensive explanations:
Solidity Compiler Behavior for Complex Indexed Event Emissions
Solidity documentation defines the following behavior for indexed event parameters:
Indexed event parameters that are not value types, i.e. arrays and structs are not stored directly but instead a Keccak256 hash of an encoding is stored.
In practice, this means that when an event includes an indexed parameter of a complex type such as a struct, array, string, or bytes, the compiler will perform the following steps at runtime:
- ABI-encode the complex type value
- Compute the Keccak256 hash of the encoded data
- Store the resulting hash as a log topic
Crucially, these steps are implemented via compiler-generated code.
This injected logic consumes additional stack slots, and, in functions that already operate near the EVM's 16-slot stack limit, due to local variables, return values, or nested expressions, it can be enough to trigger a stack too deep error.
Hidden Stack Too Deep Errors
When a "stack too deep" error is caused by indexed event parameters, the compiler error message provides no indication that event indexing is the source. The error typically appears without a specific line number or with a misleading location, making debugging difficult.
Observing the Compiler-Added Indexing Code
Foundry's inspect command lets you view the intermediate Yul code that the Solidity compiler generates before producing the final bytecode.
Consider a simple contract emitting a struct-based event:
pragma solidity ^0.8.13;
contract Emitter {
struct BigStruct {
uint256 slot1;
uint256 slot2;
uint256 slot3;
uint256 slot4;
uint256 slot5;
uint256 slot6;
uint256 slot7;
uint256 slot8;
uint256 slot9;
uint256 slot10;
}
event EmitInfo(BigStruct indexed data);
function doWork(BigStruct memory data) public {
emit EmitInfo(data);
}
}By running the forge inspect Emitter ir command, once with an indexed struct parameter and once without, we can see exactly what additional code the compiler injects to handle the indexed hashing.
Comparing the intermediate representation (IR) of indexed versus non-indexed versions reveals the following:
We can observe that:
- The indexed version includes an additional function to compute the Keccak256 hash
- The
log1opcode is replaced withlog2to accommodate the extra topic - The hash computation function adds multiple stack operations
Discovered in the Wild
We encountered this issue during the remediation phase of our iLayer LayerZero Integration audit. After applying fixes, the codebase suddenly failed to compile with a stack too deep error that had no obvious source.
To isolate the cause, we resorted to a binary search approach where we systematically commented out sections of the codebase until compilation succeeded, continuing until we narrowed down the responsible code. This eventually pointed to an event emission, which was unexpected since events are not typically associated with stack depth issues.
The only change that had been made to that specific event was the addition of indexed to one of its emitted parameters. Specifically on a parameter of the following Order structure type:
struct Token {
Type tokenType;
bytes32 tokenAddress;
uint256 tokenId;
uint256 amount;
}
struct Order {
bytes32 user;
bytes32 recipient;
bytes32 filler;
Token[] inputs;
Token[] outputs;
uint32 sourceChainEid;
uint32 destinationChainEid;
bool sponsored;
uint64 primaryFillerDeadline;
uint64 deadline;
bytes32 callRecipient;
bytes callData;
uint256 callValue;
}This prompted a deeper investigation into how the Solidity compiler handles indexed complex types, which led to the findings documented in this article.
To summarize, in our case, adding the indexed keyword to an Order parameter in an event caused the compiler to generate substantial hashing overhead code, which then triggered a significantly-hard-to-debug stack too deep error.
Implications for Developers and Auditors
| Scenario | Consideration |
|---|---|
| Designing events | Avoid indexing complex types unless filtering by that parameter is genuinely required |
| Code reviews | Recommendations to add indexing should consider the struct complexity and existing function stack usage |
| Debugging stack errors | When encountering unexplained stack too deep errors, check recent changes to event definitions |
| Large structs | Events emitting large structs may need to emit individual fields rather than the entire struct |
Bonus: Brief Showcase of Event Issues in Web3
Events can be the source of various security and functionality issues beyond stack depth problems. Notably, event-related vulnerabilities appear more frequently in off-chain processing systems, such as bridges or blockchain node software, than in smart contracts themselves. These issues typically require in-depth knowledge to identify and often stem from inadequate validation of log data.
The purpose of this article is not to showcase an exhaustive list of event-related issues, however, the following is selection of notable examples organized by category to illustrate the variety of event-related vulnerabilities that have been discovered over the years.
Insufficient Off-Chain Log Validation
16/09/2025Ethereum Log Confusion in Polygon's Heimdall19/09/2021pNetwork Post Mortem: pBTC-on-BSC Exploit26/04/2022Aurora Inflation Spend Bugfix Review: $6m Payout
Event Processing in Blockchain Infrastructure
The Zeta blockchain audit revealed multiple event processing vulnerabilities:
18/12/2023Fake ZetaReceived events cause outbound cctx to remain pending resulting in blocked outbound EVM transaction queue18/12/2023A malicious inbound transaction can prevent subsequent events from being processed by observers18/12/2023Inbound transactions with multiple ZetaSent and Deposited events are not processed correctly by observers resulting in loss of funds
Incorrect Event Processing in Off-Chain Systems
Cross-Chain Event Handling
Further Reading
For those interested in further investigating blockchain event-related vulnerabilities:
- 0xcuriousapple's x/twitter thread provides an interesting discussion on event-related vulnerabilities
- Solodit offers a searchable database of audit findings, including event-related issues
Conclusion
The Solidity compiler's handling of indexed event parameters for complex types is a subtle but important behavior to understand. When a struct, array, or other non-primitive value type is indexed, the compiler generates additional code to hash the data, which consumes stack space.
Key takeaways:
- Indexed complex types require compiler-generated hashing code that uses stack slots
- Stack too deep errors from event indexing are not clearly indicated in compiler output
- Consider the trade-off between indexing convenience and compilation constraints
- When debugging unexplained stack errors, review recent event definition changes
While this behavior is documented, it is not widely known, making it a common source of confusion when it manifests as compilation errors in otherwise straightforward code changes.
