Last Man Standing

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

Game.sol - Backwards Require Statement Prevents All Contract Usage

Description

The claimThrone() function contains a critical logic error where the require statement checks if msg.sender == currentKing instead of msg.sender != currentKing. This backwards condition makes it impossible for anyone to claim the throne for the first time (since currentKing starts as address(0)), and prevents any new user from ever becoming king, effectively rendering the entire game contract completely unusable.

Root Cause

The vulnerability exists in the second require statement of the claimThrone() function where the logic is inverted:

// @audit-issue should be != otherwise no one can use the contract
require(msg.sender == currentKing, "Game: You are already the king. No need to re-claim.");

Key issues:

  1. The condition requires the caller to already be the current king to claim the throne

  2. Since currentKing is initialized as address(0), only the zero address could theoretically claim first

  3. The error message "You are already the king" contradicts the actual check (which requires you to be the king)

  4. This creates an impossible condition that prevents any legitimate user from participating

Risk

Likelihood: High - This bug affects every single call to the core functionality. Any user attempting to play the game will encounter this issue.

Impact: Critical - The entire contract is non-functional. The game cannot be played, making all development and deployment efforts worthless.

Impact

High severity because:

  • All users are locked out from participating in the game

  • Contract must be redeployed with the fix

Proof of Concept

Based on the test results showing the revert, here's what happens when anyone tries to claim the throne:

function test_BackwardsRequireStatementPreventsUsage() public {
// Setup: currentKing starts as address(0)
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
assert(game.currentKing() == address(0));
// User attempts to claim throne with sufficient ETH
address user = address(0x123);
vm.deal(user, 1 ether);
vm.startPrank(user);
// This reverts because user (0x123) != currentKing (0x0)
// But the error message says "You are already the king"!
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
// The game is permanently broken - no one can ever become king
// Even the zero address can't claim due to other contract constraints
}

Recommended Mitigation

Fix the require statement to properly check that the caller is NOT the current king:

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

This simple change will:

This simple change will allow new users to claim the throne when they're not already 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.