Last Man Standing

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

`Game::claimThrone` indefinitely extends the gracePeriod, preventing the game from ending

Game::claimThrone indefinitely extends the gracePeriod, preventing the game from ending

Description

The claimThrone() function should allow players to claim the throne within a limited period.

However, lastClaimTime is updated every time, causing the gracePeriod to reset every time a player completes claimThrone().

function claimThrone() external payable gameNotEnded nonReentrant {
...
// amountToPot is msg.value - 5%
amountToPot = sentAmount - currentPlatformFee;
pot = pot + amountToPot;
// Update game state
currentKing = msg.sender;
@> lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;
// Increase the claim fee for the next player
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
...
}

Risk

Likelihood: High

  • Each new claim resets the gracePeriod timer.

  • An active player or bot can prevent declareWinner() from ever being executable.

Impact: High

  • declareWinner() can never be executed.

  • The pot cannot be claimed.

  • The game is blocked.

Proof of Concept

This test shows that every time claimThrone() is called, the gracePeriod is completely reset, preventing it from expiring. This can block the end of the game indefinitely.## Proof of Concept

function test_GracePeriodIsUpdatedOnClaimThrone() public {
// Save the time before the first 'claimThrone()'
uint256 graceBefore = game.getRemainingTime();
// Advance time by 12 hours
vm.warp(block.timestamp + 12 hours);
// Check that the remaining time has decreased
uint256 graceMid = game.getRemainingTime();
assertGt(graceBefore, graceMid); // The remaining time should decrease over time
// Claiming the throne resets the gracePeriod
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
// Now the remaining time is back to the initial value
uint256 graceAfter = game.getRemainingTime();
assertEq(graceAfter, graceBefore); // The remaining time has returned to the initial value
}
Ran 1 test for test/Game.t.sol:GameTest
[PASS] test_GracePeriodIsUpdatedOnClaimThrone() (gas: 160984)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 13.86ms (1.12ms CPU time)

Recommended Mitigation

Remove the line that resets lastClaimTime.## Recommended Mitigation

function claimThrone() external payable gameNotEnded nonReentrant {
...ction claimThrone() external payable gameNotEnded nonReentrant {
amountToPot = sentAmount - currentPlatformFee; ...
pot = pot + amountToPot; amountToPot = sentAmount - currentPlatformFee;
// Update game state pot = pot + amountToPot;
currentKing = msg.sender;
- lastClaimTime = block.timestamp; // Update game state
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;nder;
totalClaims = totalClaims + 1;estamp;
layerClaimCount[msg.sender] + 1;
// Increase the claim fee for the next player
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
// Increase the claim fee for the next player
...Percentage) / 100;
}
Updates

Appeal created

inallhonesty Lead Judge 10 days 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.