Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

Missing `indexed` on `GameReset` Event Makes Round Lookup Inefficient

Root + Impact

Root Cause: GameReset event’s newRound parameter is not indexed -> Impact: Off-chain services and users cannot efficiently filter or query logs by round number, hampering analytics and UX.

Description

  • The GameReset event is defined as:

@> event GameReset(uint256 newRound, uint256 timestamp);
  • but does not declare newRound as an indexed topic. Since newRound serves as the identifier for each game round, omitting indexed means:

// @info: newRound should be indexed
// @danger: makes filtering by round impossible at the RPC/query layer
@> event GameReset(uint256 newRound, uint256 timestamp);
//...
function resetGame() external onlyOwner gameEndedOnly {
// ... state resets ...
gameRound = gameRound + 1;
@> emit GameReset(gameRound, block.timestamp);
}

Risk

Likelihood: High

  • Off-chain listeners cannot filter logs by newRound at the node level, they must download all GameReset logs and scan each one.

  • This increases bandwidth and processing time for block explorers, analytics dashboards, and players tracking specific rounds.

  • In high-volume chains, unindexed events lead to slower queries and higher costs for data retrieval.

  • Event Topics: Without indexed, newRound is part of the event data payload rather than the topics array.

  • Filtering: RPC calls like eth_getLogs can only filter on topics, not on data fields.

  • Result: To find the log for round 5, clients must retrieve all GameReset logs and inspect each payload.

Impact: Gas & Low(Off-chain logs retrievals)

  • Slow Off-Chain Queries: Analytics and UI must retrieve and scan all GameReset events, leading to high latency.

  • Increased Bandwidth & Costs: More data transferred from nodes, higher indexing costs for services.

  • Poor UX: Players cannot jump directly to a specific round’s log or history without heavy client-side filtering.

Tools Used:

  • Foundry Test Suite

  • Chat-GPT AI Assistance (Report Grammar Check & Improvements)

  • Manual Review

Proof of Concept

You can write a test that inspects the number of topics in the recorded log:
dependencies: forge-std/Vm.sol

//...
import {Vm} from "forge-std/Vm.sol";
//...
function test_GameResetEventIsNotIndexed() public {
vm.startPrank(player1);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
vm.warp(block.timestamp + 1 days + 1);
vm.startPrank(player1);
game.declareWinner();
vm.stopPrank();
// Start logging
vm.recordLogs();
vm.prank(deployer);
game.resetGame();
Vm.Log[] memory entries = vm.getRecordedLogs();
// Find GameReset entry
bytes32 sig = keccak256("GameReset(uint256,uint256)");
bool found = false;
for (uint256 i; i < entries.length; i++) {
// we want to check the topics of the GameReset event only
if (entries[i].topics[0] == sig) {
// Only the signature should exist as a single topic
assertEq(entries[i].topics.length, 1, "newRound should be indexed to increase topics count");
found = true;
break;
}
}
assertTrue(found, "GameReset event not found");
}

Run:

forge test --mt test_GameResetEventIsNotIndexed

Scenario:

A block explorer wants to display the timestamp for round 42. With an indexed newRound, it can call so.
But since newRound is unindexed, it must instead fetch all GameReset logs.

Recommended Mitigation

Index the newRound Parameter

- event GameReset(uint256 newRound, uint256 timestamp);
+ event GameReset(uint256 indexed newRound, uint256 timestamp);
- This adds `newRound` to the topics array, enabling efficient `eth_getLogs` filtering.
Updates

Appeal created

inallhonesty Lead Judge 28 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.