Last Man Standing

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

Strict inequality prevents immediate winner declaration at grace period expiration

Description:

The declareWinner() function uses a strict inequality (>) instead of greater-than-or-equal (>=) when checking if the grace period has expired:

// Current implementation
require(block.timestamp > lastClaimTime + gracePeriod, "Game: Grace period has not expired yet.");

This creates a timing issue where the winner cannot be declared at the exact moment the grace period expires, but only after at least one additional second has passed.

At the same time, the getRemainingTime function uses the correct comparison

function getRemainingTime() public view returns (uint256) {
// ...
uint256 endTime = lastClaimTime + gracePeriod;
if (block.timestamp >= endTime) { // @audit >= is correct
return 0; // Grace period has expired
}
// ...
}

Example scenario:

  • lastClaimTime = 1000 (timestamp)

  • gracePeriod = 3600 (1 hour)

  • Grace period should end at timestamp 4600

  • Current behavior: Winner can only be declared at timestamp 4601 or later

  • Expected behavior: Winner should be declarable at timestamp 4600 (exactly when grace period ends)

This creates a 1-second window where the grace period has technically expired according to the game rules, but the contract still prevents winner declaration.

Impact:

Creates unnecessary 1-second delay after grace period expiration

Game logic doesn't match expected timing semantics

The 1-second delay could allow last-second throne claims when they shouldn't be possible

PoC:

Change this line in claimThore() for sucessful test run

- require(msg.sender == currentKing, "Game: You are already the king. No need to re-claim.");
+ require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");

Put this into Game.t.sol file and run forge test --mt testDeclareWinnerTheMomentGracePeriodIsOver -vvv

function testDeclareWinnerTheMomentGracePeriodIsOver() public {
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.warp(block.timestamp + GRACE_PERIOD);
vm.expectRevert();
game.declareWinner();
}

Recommended Mitigation:

Change the strict inequality to greater-than-or-equal in the declareWinner() function:

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.");
+ 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);
}

This ensures that the winner can be declared immediately when block.timestamp equals lastClaimTime + gracePeriod, which aligns with the natural expectation that the grace period ends (inclusively) at that exact timestamp.

Updates

Appeal created

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

Discrepancy between getRemainingTime and declareWinner, one includes equality the other one doesn't

wolf_kalp Auditor
about 1 month ago
inallhonesty Lead Judge
28 days ago
inallhonesty Lead Judge 27 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Discrepancy between getRemainingTime and declareWinner, one includes equality the other one doesn't

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.