Last Man Standing

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

Grace period misconfiguration enables indefinite game extension

Grace period is calculated from the lastClaimTime, allowing players to indefinitely extend the game and block winner declaration.

Description

The declareWinner function uses the condition:

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

This ties the grace period expiration to the lastClaimTime, which resets every time a new player calls claimThrone. As a result, every new throne claim delays the game’s conclusion, effectively allowing the game to continue forever as long as players or a bot keep claiming the throne before `gracePeriod` elapses.

This behavior:

  • Prevents any player from ever being declared the winner unless all others stop participating.

  • Enables denial of service on the declareWinner function indefinitely.

  • Allows the last claimant to strategically time their entry and win unfairly.

  • Violates the intended purpose of a fixed duration competitive game.

Risk

Likelihood:

This happens everytime a user calls claimThrone function.

Impact:

Unbounded game length: The game can never conclude unless all users abstain from calling claimThrone for the full duration of the grace period.

Proof of Concept

This test shows that declaration time is pushed forward with every new claim.

function test_misconfigured_last_claim_time_dos() public {
// Players enter
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.prank(player2);
game.claimThrone{value: 1.1e17}();
// Grace period passes
vm.warp(block.timestamp + 87000 seconds);
// Player3 games claims throne
vm.prank(player3);
game.claimThrone{value: 1.21e17}();
// After every throne claim, declaration is extended by grace period
game.declareWinner();
}

Recommended Mitigation

Set a fixed game end time at round start

uint256 public gameEndTime;
function claimThrone() external payable gameNotEnded nonReentrant {
require(block.timestamp < gameEndTime, "Game has ended.");
...
}
function declareWinner() external {
require(block.timestamp > gameEndTime, "Game not ended yet.");
...
}
Updates

Appeal created

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

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

declareWinner time check is not properly done

Support

FAQs

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

Give us feedback!