Last Man Standing

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

Incorrect Prize Amount in GameEnded Event Emission

Root + Impact

Description

  • The GameEnded event should emit the actual prize amount that the winner receives when the game ends. According to the event documentation, the prizeAmount parameter represents "The total prize amount won." This should be the accumulated pot value that gets transferred to the winner's pending winnings.

  • The event incorrectly emits pot as the prize amount after it has already been reset to 0, instead of emitting the actual prize amount that was awarded to the winner

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."); // Works fine !!! //@> need to check sequence of events probably should be block.timestamp > (lastClaimTime + gracePeriod)
gameEnded = true;
pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
@> pot = 0; // Reset pot after assigning to winner's pending winnings
@> emit GameEnded(currentKing, pot, block.timestamp, gameRound); //@> pot is 0 ????
}

Risk

Likelihood:

  • This bug occurs every single time declareWinner() is called, making it a 100% reproducible issue.

  • Every game that ends will emit incorrect prize information.

Impact:

  • This is an informational/logging issue that doesn't affect the core protocol functionality or put funds at risk

  • However, external systems, frontends, or analytics tools relying on this event data would receive incorrect information about prize amounts.

Proof of Concept

This test shows that the GameEnded event emits 0 as the prize amount instead of the actual pot value

function testVulnerability_GameEndedEventEmitsZeroPot() public {
// Claim the throne to accumulate pot
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
// Get the pot value before declaring winner
uint256 potBeforeDeclare = game.pot();
uint256 lastClaimTime = game.lastClaimTime();
uint256 gracePeriod = game.gracePeriod();
uint256 gameRound = game.gameRound();
address currentKing = game.currentKing();
// Verify pot has accumulated value (should be 95% of claim fee after platform fee)
uint256 expectedPot = (INITIAL_CLAIM_FEE * 95) / 100; // 95% goes to pot (5% platform fee)
assertEq(potBeforeDeclare, expectedPot, "Pot should contain expected amount before declaring winner");
assertGt(potBeforeDeclare, 0, "Pot should be greater than 0 before declaring winner");
// Warp time past grace period
vm.warp(lastClaimTime + gracePeriod + 1 hours);
// Expect the event to emit 0 as prize amount (demonstrating the vulnerability)
vm.expectEmit(true, false, false, false);
emit GameEnded(currentKing, 0, block.timestamp, gameRound);
// Declare winner - this will emit the event with pot = 0
game.declareWinner();
// Verify the vulnerability: pot is now 0 after declaring winner
uint256 potAfterDeclare = game.pot();
assertEq(potAfterDeclare, 0, "Pot should be 0 after declaring winner");
// Verify the winner received the correct amount in pending winnings
uint256 winnerPendingWinnings = game.pendingWinnings(currentKing);
assertEq(winnerPendingWinnings, expectedPot, "Winner should have correct amount in pending winnings");
// The vulnerability: Event emitted 0 as prize amount instead of the actual prize (expectedPot)
// This demonstrates that external systems listening to this event would receive incorrect information
}

Recommended Mitigation

The code correctly captures prizeAmount before resetting pot. The event emits the correct prize amount.

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;
+ uint256 prizeAmount = pot;
pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
pot = 0; // Reset pot after assigning to winner's pending winnings
- emit GameEnded(currentKing, 0, block.timestamp, gameRound)
+ emit GameEnded(currentKing, prizeAmount, block.timestamp, gameRound)
}
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!