Last Man Standing

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

Critical Logic Error in claimThrone() Prevents Game from Starting - No Player Can Ever Claim the Throne

Root + Impact

Description

Normal behavior: Players should be able to claim the throne by paying the required fee. The first player to claim becomes the king, and subsequent players can dethrone the current king by paying an increased fee.

Issue: The claimThrone() function contains a backwards logic check that prevents any player from claiming the throne, making the entire game unplayable.

@> // Root cause in Game.sol line 234
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."); // @> This should be != not ==

Risk

Likelihood: High

  • This bug triggers on EVERY attempt to claim the throne

  • The game starts with 'current = address(0)'

  • No real address equals 'address(0)', so all claims will revert

  • The game cannot start and will remain stuck forever

Impact: High

  • The entire game contract is non-functional

  • No player can participate in the game

  • Any ETH sent to the contract through other means could be locked forever

  • The contract deployment gas fees are wasted

  • Complete loss of protocol functionality

Proof of Concept

/*
This test demonstrates that no player can claim the throne due to the backwards logic check.
The test attempts to claim the throne with three different player addresses, and all attempts
fail with the same error message. The game remains stuck with `currentKing = address(0)` forever,
proving the contract is completely non-functional.
*/
function testGameIsCompletelyBroken() public {
// The game starts with currentKing = address(0)
assertEq(game.currentKing(), address(0));
// No real address can claim because they're not address(0)
address[] memory players = new address[](3);
players[0] = player1;
players[1] = player2;
players[2] = player3;
for(uint i = 0; i < players.length; i++) {
vm.startPrank(players[i]);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
}
// The game is stuck forever with no king
assertEq(game.currentKing(), address(0));
}

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.");
/*
The require statement currently checks if the caller IS the current king (==) and reverts if true.
This prevents anyone except the current king from claiming the throne. Since the game starts with
currentKing = address(0), and no real address can equal address(0), no one can ever claim the throne.
By changing the operator to != (not equal), the logic correctly prevents the current king from reclaiming
their own throne while allowing other players to claim it, which is the intended game behavior.
*/
Updates

Appeal created

inallhonesty Lead Judge about 1 month 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.