Last Man Standing

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

DoS: declareWinner() Can Be Permanently Blocked Due to Grace Period Reset

Root + Impact

Description

  • After a player claims the throne, there should be a grace period. If no one else claims within this time, the current king can call declareWinner() and win the pot.

  • Every new claim resets the grace period timer. This means someone can keep claiming the throne just before the grace period ends, over and over again, making it impossible for anyone to ever declare a winner.


function declareWinner() external gameNotEnded {
require(currentKing != address(0), "Game: No one has claimed the throne yet.");
require(
block.timestamp > lastClaimTime + gracePeriod, @> // Grace period is based on last claim
"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:

  • This happens every time players continue to claim the throne before the grace period timer ends.

  • It's easy to exploit, even with just two players alternating throne claims.

Impact:

  • The game can never end naturally.

  • Funds may be permanently stuck in the contract.

Proof of Concept

Every time someone claims the throne, the grace period resets.

Because of this, declareWinner() can never be called and the game never ends since the timer keeps extending with each new claim.

function test__NobodyCanEverDeclareWinner() public {
// Alice claims the throne
vm.prank(alice);
game.claimThrone{value: 1 ether}();
// Fast forward close to the end of the grace period
vm.warp(GRACE_PERIOD - 1 hours);
// Bob claims the throne, resetting the grace period
vm.prank(bob);
game.claimThrone{value: 2 ether}();
// Fast forward well beyond original grace period
vm.warp(23 hours + 6 hours);
// Alice tries to declare winner, but it still reverts
vm.expectRevert();
vm.prank(alice);
game.declareWinner();
}

Recommended Mitigation

Track a fixed gameStartTime when the game starts or resets.

Use this timestamp to check grace period expiry, so players can’t keep resetting the timer by claiming.

- require(block.timestamp > lastClaimTime + gracePeriod, "Game: Grace period has not expired yet.");
+ require(block.timestamp > gameStartTime + gracePeriod, "Game: Grace period has not expired yet.");
Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

declareWinner time check is not properly done

Support

FAQs

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