Last Man Standing

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

Broken access control in claimThrone prevents any participation in the game

Root + Impact

Faulty access control condition in Game::claimThrone , causing a Functional Denial of Service to the Contract.

Description

  • The claimThrone function is designed to allow any new player to claim the throne by sending an amount of ETH that meets or exceeds the required claimFee. When a player successfully calls this function, they become the new currentKing, the pot increases by the sent amount.

  • The claimThrone function contains a faulty access control condition that incorrectly requires msg.sender to already be the currentKing in order to claim the throne. This prevents any new player from participating, effectively locking the game and causing a functional denial of service.

// 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."
);
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
if (currentPlatformFee > (sentAmount - previousKingPayout)) {
currentPlatformFee = sentAmount - previousKingPayout;
}
platformFeesBalance = platformFeesBalance + currentPlatformFee;
amountToPot = sentAmount - currentPlatformFee;
pot = pot + amountToPot;
currentKing = msg.sender;
lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}

Risk

Likelihood:

  • This issue will occur every time a new player attempts to call claimThrone, since the require(msg.sender == currentKing) condition blocks anyone who is not already the current king. This includes the very first player after deployment.

  • Because the currentKing is initially set to a zero address (or unset), no user can ever meet the condition unless manually set, which causes the game to remain unusable from the beginning and indefinitely.

Impact:

  • The claimThrone function becomes completely inaccessible to all users, including the first intended player, rendering the core game logic unusable.

  • This causes a functional Denial of Service (DoS) to the entire contract, as no game round can ever be initiated, leading to a total loss of utility for both players and the contract owner.

Proof of Concept

  1. First Player Tries to claim throne and pay the initial claim fee

  2. Gets an error that he is already king and doesn't need to reclaim


    Place the following into the Game.t.sol


    function test_noplayerallowedtoclaim() 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

Replace the incorrect access control check which will allow new players to claim the throne, while preventing the current king from reclaiming their own position unnecessarily.

- 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."
+ );
Updates

Appeal created

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