Last Man Standing

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

The `claimThrone` function does not restrict users to claiming the throne only during the grace period.

Description

  • The claimThrone function does not enforce a restriction that users can only claim the throne within the grace period, which contradicts the protocol specification.

Risk

Impact:

  • After the grace period ends, players can still continuously challenge for the throne.

  • Under high player activity, a third-party arbiter (or any user) may never be able to successfully call the declareWinner() function, preventing the game from concluding.

Proof of Concept

  1. This verification assumes the claimThrone function has already fixed the following two issues:

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

    2. Proper handling of the previous king's reward: uint256 previousKingPayout = (sentAmount * previousKngFeePercentage) / 100;

  2. Admin deploys the contract.

  3. Player player1 pays the claim fee and calls claimThrone.

  4. Player player2 pays the claim fee and calls claimThrone.

  5. Wait for the grace period to expire (1 day).

  6. Player player1 pays the claim fee and calls claimThrone.

  7. Player player2 pays the claim fee and calls claimThrone.

  8. Wait for another day.

  9. This cycle can continue indefinitely.

function test_always_claimThrone() public {
uint256 cacheClaimFee = 0;
cacheClaimFee = game.claimFee();
vm.prank(player1);
game.claimThrone{value: cacheClaimFee}();
cacheClaimFee = game.claimFee();
vm.prank(player2);
game.claimThrone{value: cacheClaimFee}();
vm.warp(block.timestamp + GRACE_PERIOD + 1);
cacheClaimFee = game.claimFee();
vm.prank(player1);
game.claimThrone{value: cacheClaimFee}();
cacheClaimFee = game.claimFee();
vm.prank(player2);
game.claimThrone{value: cacheClaimFee}();
vm.warp(block.timestamp + GRACE_PERIOD + 1);
cacheClaimFee = game.claimFee();
vm.prank(player1);
game.claimThrone{value: cacheClaimFee}();
cacheClaimFee = game.claimFee();
vm.prank(player2);
game.claimThrone{value: cacheClaimFee}();
}

Recommended Mitigation

  • Simply add a check at the beginning of the claimThrone function to ensure claiming is only allowed during the grace period:

function claimThrone() external payable gameNotEnded nonReentrant {
+ require(getRemainingTime() > 0, "Game: getRemainingTime > 0");
...
}
Updates

Appeal created

inallhonesty Lead Judge about 1 month 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.