Last Man Standing

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

Event Bug in declareWinner — Prize Amount Always Zero

Root + Impact

Description

  • The normal behavior is that when a winner is declared via declareWinner(), the contract emits a GameEnded event showing the winner’s address, the prize amount won (the pot), the timestamp, and the round number.


  • The issue is that the contract resets the pot to zero before emitting the GameEnded event, causing the event to always log a prize amount of zero instead of the actual accumulated pot.

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; // @> pot reset before event emit
emit GameEnded(currentKing, pot, block.timestamp, gameRound); // @> pot is zero here
}

Risk

Likelihood:


  • This will occur every time declareWinner() is called to end a game round.

  • Since the event is emitted after the pot is cleared, the issue is consistent and reproducible on every winner declaration.

Impact:

  • Off-chain event listeners, UIs, and analytics relying on the GameEnded event will always see zero as the prize amount, misrepresenting the actual winnings.

  • This may cause confusion or incorrect reward tracking in frontends or third-party tools, reducing trust and usability.

Proof of Concept

pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Game.sol";
contract GameEventBugTest is Test {
Game game;
address player = address(0xBEEF);
function setUp() public {
game = new Game(1 ether, 60, 10, 5);
vm.deal(player, 10 ether);
}
function test_EventPrizeIsZeroDueToPotReset() public {
vm.prank(player);
game.claimThrone{value: 1 ether}();
vm.warp(block.timestamp + 61);
vm.expectEmit(true, true, true, true);
emit Game.GameEnded(player, 0, block.timestamp, 1); // Expect pot to be zero in emitted event
game.declareWinner();
assertEq(game.pot(), 0);
}
}

Recommended Mitigation

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;
-
- emit GameEnded(currentKing, pot, block.timestamp, gameRound); // <-- event emitted after pot reset
+ emit GameEnded(currentKing, pot, block.timestamp, gameRound); // <-- emit event BEFORE pot reset
+
+ pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
+ pot = 0;
}
Updates

Appeal created

inallhonesty Lead Judge about 1 month 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.