Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Lack of Automation or Incentive in declareWinner() May Stall Game Progress

Lack of Automation or Incentive in declareWinner() May Stall Game Progress

Description

  • Normally, after a player claims the throne, the game enters a "grace period" during which no other player can claim. Once this period expires, the game is supposed to end, and the current king is declared the winner. This logic is implemented in the declareWinner() function.

  • However, the contract does not have any automated trigger for ending the game. The function declareWinner() can only be called by an external address. If no one calls this function after the grace period expires, the game remains stuck in a limbo state — the pot is locked, and no new rounds can be started.

function declareWinner() external gameNotEnded {
require(currentKing != address(0), "Game: No one has claimed the throne yet.");
require(
block.timestamp > lastClaimTime + gracePeriod,
@> "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 issue is likely to occur in cases where no users are actively monitoring the game or incentivized to call declareWinner().

  • On low-traffic contracts or newly deployed games, this condition may arise early in the contract lifecycle.

Impact:

  • The game can stall indefinitely.

  • The pot remains locked in the contract.

  • resetGame() cannot be called since gameEnded never becomes true.

  • This affects the trust, usability, and functionality of the entire game system.

Proof of Concept

// Scenario:
1. Player claims throne.
2. Grace period expires (e.g., 5 minutes).
3. No one calls declareWinner().
Expected: The currentKing should win, pot should be transferred.
Actual: Game remains active, pot locked, no winner, no reset possible.

Recommended Mitigation

- function declareWinner() external gameNotEnded {
+ function declareWinner() external gameNotEnded {
+ // Incentivize the caller to end the game
+ uint256 reward = pot / 100; // 1% fee
+ pendingWinnings[msg.sender] += reward;
+ pendingWinnings[currentKing] += (pot - reward);
+ gameEnded = true;
+ pot = 0;
+ emit GameEnded(currentKing, pot, block.timestamp, gameRound);
}
Alternative Mitigations:
Automation with Chainlink Keeper
Integrate Chainlink Automation (formerly Keepers) to automatically check for game end conditions and call declareWinner() when block.timestamp > lastClaimTime + gracePeriod.
Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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