Root + Impact
Description
In the declareWinner() function, the GameEnded event emits the pot value after it has already been reset to 0. This leads to incorrect and misleading event logs, where the emitted pot amount appears as zero — despite the winner actually receiving a non-zero reward.
Risk
Likelihood:
Impact:
-
Potential disputes: If players are tracking their rewards based on event data (e.g., UIs, bots, or analytics dashboards), they could wrongly assume rewards weren't issued
-
Incorrect event logs: Off-chain services, indexers, and users relying on event data will believe that the winner received 0 ETH.
Proof of Concept
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {Game} from "../src/Game.sol";
contract GameTest is Test {
Game public game;
address public deployer;
address public player1;
address public player2;
address public player3;
address public maliciousActor;
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;
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
player2 = makeAddr("player2");
player3 = makeAddr("player3");
maliciousActor = makeAddr("maliciousActor");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.deal(player3, 10 ether);
vm.deal(maliciousActor, 10 ether);
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
modifier loadGame() {
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
console.log(game.currentKing());
vm.startPrank(player2);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
_;
}
function testDeclareWinner() public loadGame {
vm.warp(GRACE_PERIOD + 1 hours);
vm.roll(block.number + 3);
vm.prank(player3);
game.declareWinner();
}
}
Result trace
prizeAmount is emitted as 0.
├─ [0] VM::prank(player3: [0xcC37919fDb8E2949328cDB49E8bAcCb870d0c9f3])
│ └─ ← [Return]
├─ [48689] Game::declareWinner()
│ ├─ emit GameEnded(winner: player2: [0xEb0A3b7B96C1883858292F0039161abD287E3324], prizeAmount: 0, timestamp: 90000 [9e4], round: 1)
│ └─ ← [Stop]
└─ ← [Stop]
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;
+ uint256 prizeAmount = pot;
pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
pot = 0; // Reset pot after assigning to winner's pending winnings
// @audit:: emmitted event is wrong
// @audit::cache uint256 prizeAmount = pot;
- emit GameEnded(currentKing, pot, block.timestamp, gameRound);
+ emit GameEnded(currentKing, prizeAmount, block.timestamp, gameRound);
}