Last Man Standing

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

Throne Cannot Be Claimed

Logical Flaw in claimThrone Prevents Any Player from Becoming King, Making the Game Unplayable

Description

  • Normal Behavior: A new player (msg.sender) who is not the currentKing should be able to call claimThrone() with the required claimFee to become the new currentKing. The game is designed for players to usurp the throne from one another.

  • The Issue: The claimThrone function contains a require statement that is logically inverted. The line require(msg.sender == currentKing, "Game: You are already the king. No need to re-claim."); requires the caller to already be the king. This makes it impossible for any new player to ever claim the throne. The game cannot progress past its initial state where currentKing is address(0).

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;
// Calculate platform fee
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// Defensive check to ensure platformFee doesn't exceed available amount after previousKingPayout
if (currentPlatformFee > (sentAmount - previousKingPayout)) {
currentPlatformFee = sentAmount - previousKingPayout;
}
platformFeesBalance = platformFeesBalance + currentPlatformFee;
// Remaining amount goes to the pot
amountToPot = sentAmount - currentPlatformFee;
pot = pot + amountToPot;
// Update game state
currentKing = msg.sender;
lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;
// Increase the claim fee for the next player
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}

Risk

Likelihood: HIGH

  • Certain. This logic error will block 100% of legitimate attempts by new players to claim the throne.

Impact: HIGH

  • The core function of the game is fundamentally broken.

  • No one can ever become king, no funds can enter the pot (beyond a failed transaction), and the game cannot be played in any capacity. The contract fails to execute its primary purpose.

Proof of Concept

  • Deploy the Game contract. The currentKing is initialized to address(0).

  • As any user account (e.g., account_A), attempt to call claimThrone() by sending the initialClaimFee.

  • Observe: The transaction will always revert with the error message "Game: You are already the king. No need to re-claim."

  • Reason: The check require(msg.sender == currentKing) fails because account_A (msg.sender) is not address(0) (currentKing).

Add this code to the test file:

function testClaimThroneRevert() public {
vm.prank(player1);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
}

Recommended Mitigation

The logical check must be inverted to ensure the caller is not the current king.

Change this line:

function claimThrone() external payable gameNotEnded nonReentrant {
require(
msg.value >= claimFee,
"Game: Insufficient ETH sent to claim the throne."
);
require(
- msg.sender == currentKing,
+ 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;
// Calculate platform fee
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// Defensive check to ensure platformFee doesn't exceed available amount after previousKingPayout
if (currentPlatformFee > (sentAmount - previousKingPayout)) {
currentPlatformFee = sentAmount - previousKingPayout;
}
platformFeesBalance = platformFeesBalance + currentPlatformFee;
// Remaining amount goes to the pot
amountToPot = sentAmount - currentPlatformFee;
pot = pot + amountToPot;
// Update game state
currentKing = msg.sender;
lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;
// Increase the claim fee for the next player
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}
Updates

Appeal created

inallhonesty Lead Judge 4 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.

Give us feedback!