Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Severity: low
Valid

Incorrect Prize Emitted in GameEnded Event

Root + Impact

Description

  • When the game ends and a winner is declared, the GameEnded event should accurately log the amount of ETH won by the current King. This event is critical for off-chain consumers such as frontends, bots, or subgraphs that rely on on-chain data to track game outcomes.

  • The pot is reset to zero before the event is emitted, causing prizeAmount in the GameEnded event to always be 0, regardless of the actual reward given to the King. This creates inconsistency between the contract’s internal state and emitted logs.

function declareWinner() external gameNotEnded {
require(currentKing != address(0), "Game: No one has claimed the throne yet.");
require(
block.timestamp > lastClaimTime + gracePeriod,
"Game: Grace period has not expired yet."
);
gameEnded = true;
pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
@> pot = 0; // Reset BEFORE emitting the event — causes incorrect prize in logs
emit GameEnded(currentKing, pot, block.timestamp, gameRound);
}

Risk

Likelihood:

  • This will always occur when a player wins the game and someone calls declareWinner().

  • It affects every round and every winner, since the pot is cleared before logging.

Impact:

  • Event GameEnded reports incorrect data: prizeAmount == 0, even if the winner received ETH.

  • Off-chain services, indexers, bots, or explorers that rely on this event will miss or misrepresent the winner's prize, potentially breaking automation, analytics, or payouts.

Proof of Concept

A player claims the throne and builds up the pot. When declareWinner() is called, the pot is set to zero before emitting GameEnded, causing the event to log a prizeAmount of 0 despite a real payout occurring.

// Simulated sequence
game.claimThrone{value: 1 ether}(); // Player becomes King, pot == 1 ether
vm.warp(block.timestamp + game.getRemainingTime() + 1); // Time travel past gracePeriod
game.declareWinner(); // GameEnded emits prizeAmount = 0
// Meanwhile, winner has 1 ether in pendingWinnings
assert(game.pendingWinnings(king) == 1 ether); // ✅ actual state
// But off-chain systems see GameEnded(winner, 0, ...)

Recommended Mitigation

Store the pot value in a local variable before resetting it, and use that variable in the GameEnded event to ensure accurate prize reporting.

function declareWinner() external gameNotEnded {
...
gameEnded = true;
pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
+ uint256 prizeAmount = pot;
+ emit GameEnded(currentKing, prizeAmount, block.timestamp, gameRound);
- pot = 0;
+ pot = 0;
}
Updates

Appeal created

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Game::declareWinner emits GameEnded event with pot = 0 always

Support

FAQs

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

Give us feedback!