Last Man Standing

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

Inverted Require Logic in claimThrone() Prevents all Claims, Causing Permanent DoS

Root + Impact

Description

  • Describe the normal behavior in one or more sentences: As per protocol, any player(non-current king) should successfully claimThrone() by sending >= cliamFee, becoming the new currentKing. This starts with the first claim (from initial address(0) king) to bootstrap the game, allowing subsequent overthrows to build the pot and tirgger grace period mechanic for winner declaration.

  • Explain the specific issue or problem in one or more sentences: Instead, all claims revert due to the inverted logic, preventing the first king from being set and locking the game in an unplayable state indefinitely. This causes a permanent DoS: Pot remains 0, totalClaims unchanged, grace/winner funcs inert—even overpaying or multi-player attempts fail, wasting user gas and eroding trust in the protocol's competitiveness.

// 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."); // Root cause: Inverted logic (should be !=) blocks all valid claims from non-kings
// ... rest of function
}

Risk

Likelihood:

  • Reason 1 : Occurs immediately on deployement for first claim (currentKing=address(0)!=msg.sender always true for non-zero addresses).

  • Reason 2 : Persists post-any hypothetical claim (new players != currentKing revert). No "if" avoidance-100% reproducible.

Impact:

  • Impact 1 : Permanent DoS - pot never accumulates, grace/winner mechanics inert.
    Users waste gas on reverts, trust lost.

  • Impact 2 : Deployer cannot fix mid-game(require migration), eroding ecosystem confidence.
    Real-World: Similar flips in games lead to refunds/backlash, dropping palyer base 50%+.

Proof of Concept

This Foundry test deploys the game and simulates claim attempts from initial state (currentKing=address(0)), using vm.prank/expectRevert to catch the inverted logic failure. It asserts unchanged state post-revert (no king/pot progress), proving permanent DoS.

function testClaimThroneFailsDueToInvertedRequire() public {
// Initial state: No king
assertEq(game.currentKing(), address(0));
// Player1 first claim reverts
vm.prank(player1);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
// State unchanged (DoS proof)
assertEq(game.currentKing(), address(0));
assertEq(game.pot(), 0);
assertEq(game.totalClaims(), 0);
// Overpay edge: Still fails
vm.prank(player1);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE + 0.01 ether}();
// Player2 also fails
vm.prank(player2);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
}
// Test passes, confirming reverts and stuck state (output: [PASS] gas:129920). Proves DoS—game unplayable.

Recommended Mitigation


This flips the logic to match spec (block re-claims by current king, allow others), enabling first/subsequent claims without altering gas (~same opcode).

- require(msg.sender == currentKing, "Game: You are already the king. No need to re-claim.");
+ require(msg.sender != currentKing, "Game: Already the king—wait for overthrow.");
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

kalyan_tr Submitter
about 2 months ago
inallhonesty Lead Judge
about 2 months ago
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.