Last Man Standing

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

Wrong Event Data: GameEnded Emits Zero Prize

Root + Impact

Description

  • Normal behavior:
    When the game ends, the GameEnded event should emit the actual prize amount (the pot value before it is reset), so off-chain systems and users can accurately track rewards.

    Specific issue:
    The contract emits the GameEnded event after setting pot = 0, so the event always logs a prize amount of zero, regardless of the actual winnings.

// src/Game.sol
function declareWinner() external gameNotEnded {
// ...
pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
pot = 0;
emit GameEnded(currentKing, pot, block.timestamp, gameRound); // @> Bug: pot is zero here
}

Risk

Likelihood:

  • This will occur every time a winner is declared.

Impact:

  • Off-chain analytics, UIs, and indexers will display a prize of zero.

  • Users may be misled about the actual rewards distributed by the contract.

Proof of Concept

The following test demonstrates the bug. It expects the GameEnded event to emit the actual prize, but the event logs zero instead.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {Game} from "../src/Game.sol";
contract GameEventDataTest is Test {
Game public game;
address public deployer;
address public player1;
uint256 public constant INITIAL_CLAIM_FEE = 0.1 ether;
uint256 public constant GRACE_PERIOD = 1 days;
uint256 public constant FEE_INCREASE_PERCENTAGE = 10;
uint256 public constant PLATFORM_FEE_PERCENTAGE = 5;
event GameEnded(
address indexed winner,
uint256 prizeAmount,
uint256 timestamp,
uint256 round
);
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
function testGameEndedEventEmitsCorrectPrize() public {
// Player1 claims throne
vm.startPrank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
// Fast forward past grace period
vm.warp(block.timestamp + GRACE_PERIOD + 1);
// Expect the GameEnded event with the correct prize (pot before zeroing)
vm.expectEmit(true, false, false, true);
emit GameEnded(player1, game.pot(), block.timestamp, 1);
game.declareWinner();
}
}

Recommended Mitigation

Emit the GameEnded event with the prize value before zeroing the pot:

- pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
- pot = 0;
- emit GameEnded(currentKing, pot, block.timestamp, gameRound);
+ uint256 prize = pot;
+ pendingWinnings[currentKing] += prize;
+ pot = 0;
+ emit GameEnded(currentKing, prize, block.timestamp, gameRound);
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.