Last Man Standing

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

Incorrect claimThrone condition prevents new players from claiming the throne

Root + Impact

A logic error in the claimThrone function incorrectly prevents any user other than the current king from claiming the throne, breaking the core gameplay mechanic and halting the entire game flow

Description

  • Normally, a player who is not the current king should be able to send the claimFee and take the throne, becoming the new king

  • However, the claimThrone() function currently checks that the caller must be the current king, which is backwards, so only the current king could “claim” again (which makes no sense). This blocks all new players from claiming the throne and breaks the main game loop

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

Risk

Likelihood:

  • Any time a player tries to claim the throne while not being the current king, the transaction will revert with Game: You are already the king — which makes no sense and halts the game for everyone except the current king

  • This condition will always occur for the first claim too, since currentKing is address(0) — so the deployer can never claim either if they aren’t the currentKing

Impact:

  • The main claimThrone mechanic becomes impossible to use

  • The pot never grows, no one can compete, and the game is permanently stuck

Proof of Concept

  1. Deployer deploys contract with initialClaimFee = 1 ether

  2. player1 calls claimThrone() with 1 ether:

    • currentKing = address(0)

    • require(msg.sender == currentKing) fails

    • Transaction reverts

The Test:

function test_claimThrone() public {
vm.startPrank(player1);
uint256 claimFee = game.claimFee() + 0.01 ether; // Ensure player1 sends enough ETH to cover the claim fee
// Player 1 claims the throne
vm.expectRevert();
game.claimThrone{value: claimFee}();
// assertEq(game.currentKing(), player1, "Player 1 should be the current king");
vm.stopPrank();
}

The Output:

\[⠢] Compiling...
\[⠒] Compiling 1 files with Solc 0.8.28
\[⠑] Solc 0.8.28 finished in 6.34s
Compiler run successful!
Ran 1 test for test/Game.t.sol:GameTest
\[PASS] test\_claimThrone() (gas: 48367)
Logs:
currentKing: 0x0000000000000000000000000000000000000000
Traces:
\[48367] GameTest::test\_claimThrone()
├─ \[0] VM::startPrank(player1: \[0x7026B763CBE7d4E72049EA67E89326432a50ef84])
│ └─ ← \[Return]
├─ \[2514] Game::claimFee() \[staticcall]
│ └─ ← \[Return] 100000000000000000 \[1e17]
├─ \[0] VM::expectRevert(custom error 0xf4844814)
│ └─ ← \[Return]
├─ \[27259] Game::claimThrone{value: 110000000000000000}()
│ └─ ← \[Revert] Game: You are already the king. No need to re-claim.
├─ \[0] VM::stopPrank()
│ └─ ← \[Return]
└─ ← \[Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 54.69ms (6.11ms CPU time)
Ran 1 test suite in 250.25ms (54.69ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Recommended Mitigation

- 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 9 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.