Last Man Standing

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

Logic error prevents new players from claiming throne

Root + Impact

Description

  • The claimThrone() function should allow any player (except the current king) to claim the throne by sending the required ETH fee, enabling the core game mechanic where players compete to become and remain king.

  • The function contains inverted logic that only allows the current king to claim their own throne while preventing all other players from participating, completely breaking the fundamental game mechanism.

// Root cause in the codebase with @> marks to highlight the relevant section
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.");
// ... rest of function
}

Risk

Likelihood:

  • Any player attempting to claim the throne (except the current king) will immediately trigger this require statement and have their transaction reverted.

Impact:

  • No one can dethrone the current king, effectively locking the game.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Game.sol";
contract SimpleBugTest is Test {
Game public game;
address alice = address(0x1);
address bob = address(0x2);
function setUp() public {
game = new Game(1 ether, 24 hours, 10, 5);
vm.deal(alice, 10 ether);
vm.deal(bob, 10 ether);
}
function testClaimThroneBug() public {
// Alice claims throne first (works because currentKing is address(0))
vm.prank(alice);
game.claimThrone{value: 1 ether}();
// Bob (who is NOT the king) tries to claim but gets rejected
// The bug: only the current king can call claimThrone()
vm.prank(bob);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: 1.1 ether}();
// Only Alice (current king) can call claimThrone() - which is wrong!
vm.prank(alice);
game.claimThrone{value: 1.1 ether}(); // This works but defeats the purpose
}
}

This example simulates a real interaction sequence to demonstrate the bug:

  • Alice claims the throne first and becomes currentKing. This part works as expected.

  • Bob then attempts to claim the throne by sending the required ETH. Under normal game rules, this should be allowed — he's not the current king and is trying to dethrone Alice.

  • However, the transaction reverts with an error stating that Bob is already the king, which makes no logical sense. This happens because of the incorrect condition which allows only the current king to proceed.

  • As a result, no other player can ever reclaim the throne after the first claim — proving that the contract’s behavior is broken after the initial move.

Recommended Mitigation

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.");
+ require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");
// ... rest of function
}

This fix corrects the logical condition used to validate throne claims.

  • The original line incorrectly allowed only the current king to claim the throne again, while rejecting all other players — which defeats the entire game purpose.

  • The corrected line now prevents the current king from re-claiming, while allowing new participants to challenge for the throne by paying the required fee.

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.