Root + Impact
The GameEnded
event always emits pot
after it has been reset to 0
. This means the emitted event shows no prize, even though the winner actually receives the real pot amount in pendingWinnings
Description
-
Normal Behavior:
When a winner is declared, the full pot
balance should be assigned to the winner’s pendingWinnings
and the GameEnded
event should transparently log the amount won.
-
Specific Issue:
The code assigns the pot
to pendingWinnings
, then resets the pot
to 0
, then emits GameEnded
with pot
— so the event always shows 0
instead of the true pot value.
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);
}
Risk
Likelihood:
Impact:
-
Low impact: Funds are not lost.
-
But it misleads off-chain listeners (frontends, indexers, analytics, or audit logs) who rely on the event for the real prize value.
-
Reduces transparency of game outcomes.
Proof Of Code
.
.
.
event GameEnded(
address indexed winner,
uint256 prizeAmount,
uint256 timestamp,
uint256 round
);
.
.
.
function test_declareWinnerEmitsZeroReward() public {
vm.startPrank(player1);
uint256 claimFee = game.claimFee();
game.claimThrone{value: claimFee}();
vm.stopPrank();
vm.warp(block.timestamp + 2 days);
vm.expectEmit(address(game));
emit GameEnded(address(player1), 0, block.timestamp, game.gameRound());
game.declareWinner();
}
Result:
Traces:
[227572] GameTest::test_declareWinnerEmitsZeroPot()
├─ [0] VM::startPrank(player1: [0x7026B763CBE7d4E72049EA67E89326432a50ef84])
│ └─ ← [Return]
├─ [2514] Game::claimFee() [staticcall]
│ └─ ← [Return] 100000000000000000 [1e17]
├─ [150621] Game::claimThrone{value: 100000000000000000}()
│ ├─ emit ThroneClaimed(newKing: player1: [0x7026B763CBE7d4E72049EA67E89326432a50ef84], claimAmount: 100000000000000000 [1e17], newClaimFee: 110000000000000000 [1.1e17], newPot: 95000000000000000 [9.5e16], timestamp: 1)
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::warp(172801 [1.728e5])
│ └─ ← [Return]
├─ [0] VM::expectEmit(Game: [0x8Ad159a275AEE56fb2334DBb69036E9c7baCEe9b])
│ └─ ← [Return]
├─ [2471] Game::gameRound() [staticcall]
│ └─ ← [Return] 1
├─ emit GameEnded(winner: player1: [0x7026B763CBE7d4E72049EA67E89326432a50ef84], prizeAmount: 0, timestamp: 172801 [1.728e5], round: 1)
├─ [48689] Game::declareWinner()
│ ├─ emit GameEnded(winner: player1: [0x7026B763CBE7d4E72049EA67E89326432a50ef84], prizeAmount: 0, timestamp: 172801 [1.728e5], round: 1)
│ └─ ← [Stop]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 165.35ms (11.35ms CPU time)
Ran 1 test suite in 400.97ms (165.35ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Recommended Mitigation
- emit GameEnded(currentKing, pot, block.timestamp, gameRound);
+ emit GameEnded(currentKing, pendingWinnings[currentKing], block.timestamp, gameRound);