Last Man Standing

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

Logic Flaw in claimThrone() Allows First King to Remain Indefinitely

Root + Impact

Description

  • The bug is isolated to the claimThrone() function in the Game contract.

  • Specifically, it involves the access control logic that restricts who can call claimThrone().

  • No other functions or features appear affected by this issue based on the current code.

  • The bug affects all game rounds and all players, preventing any new player from claiming the throne once it is held by someone.

  • It effectively breaks the core gameplay mechanic and flow, halting all intended competitive interactions.

// 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.");
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// ... rest of the function ...
}

Risk

Likelihood:

  • This bug will occur whenever any player other than the current king attempts to claim the throne, which is the normal expected gameplay flow.

  • Since the contract enforces that only the current king can call claimThrone(), all attempts by new players to take the throne will revert, making the bug triggered on every claim attempt after the first king.

Impact:

  • New players cannot claim the throne, effectively freezing the game and preventing any change in the current king.

  • The game’s core mechanic—“king of the hill” competition—is broken, making the initial king unbeatable and the pot unreachable by others.

Proof of Concept

Explanation:

This proof of concept demonstrates the bug in the claimThrone function where the contract incorrectly blocks other players from claiming the throne once the first player is king. The test shows:

Player 1 claims the throne successfully.

Player 1 attempting to claim again correctly reverts.

However, Player 2's attempt to claim the throne fails in the buggy version but succeeds after the fix.

This confirms the bug: the contract mistakenly requires the caller to be the current king, preventing legitimate throne claims by other players.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Game.sol";
contract GameTest is Test {
Game game;
address player1 = address(0x1);
address player2 = address(0x2);
function setUp() public {
// claimFee = 1 ether, grace = 60s, feeIncrease=10%, platformFee=5%
game = new Game(1 ether, 60, 10, 5);
}
function test_ClaimThrone_Bug_CurrentLogic() public {
vm.deal(player1, 10 ether);
vm.deal(player2, 20 ether);
// Player 1 claims the throne first
vm.startPrank(player1);
game.claimThrone{value: game.claimFee()}();
// Player 1 tries to claim again - should revert
uint256 currentFee = game.claimFee(); // Get fee first
vm.expectRevert(bytes("Game: You are already the king.")); // Expect revert right before the call
game.claimThrone{value: currentFee}(); // This call should revert
vm.stopPrank();
// Player 2 claims - should succeed
vm.startPrank(player2);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
}
}

Recommended Mitigation

Explanation:

The mitigation involves correcting the require statement in claimThrone from:

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

Appeal created

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

Game::claimThrone `msg.sender == currentKing` check is busted

Support

FAQs

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