Last Man Standing

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

Players Can Claim Throne After Grace Period Expiration, Preventing Winner Declaration

Root + Impact

The claimThrone() function lacks a check to ensure the grace period has not expired. This allows a player to claim the throne even after the current king's time has run out, effectively stealing the win from the rightful king. This vulnerability breaks the game's core rule, which is that the last king standing after the grace period wins the pot.

Description

  • The king at the end of the grace period is supposed to be the winner.

  • However, the claimThrone() function does not verify if the grace period has already expired.

  • This allows any player to become the new king even after the previous king should have been declared the winner, as long as declareWinner() has not yet been called.

function claimThrone() external payable gameNotEnded nonReentrant {
require(msg.value >= claimFee, "Game: Insufficient ETH sent to claim the throne.");
require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");
@> // @audit did not check if graceperiod is expired
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// ...rest of code
}

Risk

Likelihood:

  • A malicious player can intentionally wait for the grace period to expire to steal the throne from the current king just before declareWinner() is called. This is a clear incentive.

Impact:

  • This vulnerability undermines the entire point of the game. The rightful winner can be denied their prize, which destroys fairness and trust in the contract.

Proof of Concept

The following test demonstrates that a player can still claim the throne after the grace period has expired.

function test_GracePeriodExpireCanStillClaimThrone() public {
vm.startPrank(player1);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
vm.warp(block.timestamp + GRACE_PERIOD + 1);
assertFalse(game.gameEnded()); // grace period is expired but game has not ended
vm.startPrank(player2);
game.claimThrone{value: game.claimFee()}(); // player 2 can still claim the throne
vm.stopPrank();
assertEq(player2, game.currentKing());
}

Recommended Mitigation

Add a require statement to the claimThrone() function to ensure that the grace period has not expired. This prevents new claims after the time limit has passed, ensuring the rightful winner can be declared.

function claimThrone() external payable gameNotEnded nonReentrant {
+ require(block.timestamp <= lastClaimTime + gracePeriod, "Game: Grace period has expired. Winner should be declared.");
require(msg.value >= claimFee, "Game: Insufficient ETH sent to claim the throne.");
require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// ...rest of code
}
Updates

Appeal created

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

Game::claimThrone can still be called regardless of the grace period

Support

FAQs

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