Last Man Standing

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

Critical Logic Error in claimThrone Renders Game Unplayable

Root + Impact

Root Cause

The root cause is a simple logical error in the claimThrone function's validation logic. The code incorrectly uses the equality operator (==) instead of the inequality operator (!=) in the following line:

require(msg.sender == currentKing, ...)

This condition makes it impossible for any new player to enter the game.

Impact

This bug leads to a complete Denial of Service (DoS) for the entire protocol.

Since the claimThrone function is the main entry point to the game and is non-functional, no player can participate. As a result, the protocol is entirely unusable and frozen from the moment of deployment, preventing it from generating any value.


Description

  • The claimThrone function contains a critical logic error in its validation check. It incorrectly requires a new player to already be the king. This fundamental flaw blocks all new players from entering, making the entire game unplayable.


// 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;
// ...
}

Risk

Likelihood:

  • Reason 1: A new user, who is not the current king, calls the claimThrone function to participate in the game.

  • Reason 2: The function's validation logic executes the flawed access control check, which will fail for any new user.

Impact:

  • Impact 1: The core gameplay function, claimThrone, is permanently non-functional for all new players.

  • Impact 2: The entire game protocol is unusable (Denial of Service), as no one can participate after deployment.

Proof of Concept

function test_FailsForNewPlayerBecauseOfBuggyRequire() public {
// 1. Expect the transaction to revert with the specific error from the buggy require statement.
vm.expectRevert("Game: You are already the king. No need to re-claim.");
// 2. Simulate the call coming from a new player, `playerA`.
vm.prank(playerA);
// 3. Call the function with the required fee. This call will fail as expected, passing the test.
game.claimThrone{value: INITIAL_CLAIM_FEE}();
}

Expected Output

git:(main*)$ forge test --match-test test_FailsForNewPlayerBecauseOfBuggyRequire -vvvv
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.28
[⠘] Solc 0.8.28 finished in 569.43ms
Compiler run successful!
Ran 1 test for test/POC.t.sol:POCTest
[PASS] test_FailsForNewPlayerBecauseOfBuggyRequire() (gas: 46959)
Traces:
[46959] POCTest::test_FailsForNewPlayerBecauseOfBuggyRequire()
├─ [0] VM::expectRevert(custom error 0xf28dceb3: 4Game: You are already the king. No need to re-claim.)
│ └─ ← [Return]
├─ [0] VM::prank(playerA: [0x23223AC37AC99a1eC831d3B096dFE9ba061571CF])
│ └─ ← [Return]
├─ [29259] Game::claimThrone{value: 100000000000000000}()
│ └─ ← [Revert] Game: You are already the king. No need to re-claim.
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 17.04ms (1.46ms CPU time)
Ran 1 test suite in 96.90ms (17.04ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)


Recommended Mitigation

The validation logic within the claimThrone function must be corrected to ensure a new player is not the currentKing. This is achieved by changing the equality operator (==) to an inequality operator (!=) as shown below.

- require(msg.sender == currentKing, "Game: You are already the king. No need to re-claim.");
+ require(msg.sender != currentKing, "Game: You cannot reclaim your own throne.");
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.