Last Man Standing

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

[H-1] Incorrect King Validation Prevents Anyone from Claiming the Throne

Description

The claimThrone() function contains a critical logic error in the king validation check. The function requires that msg.sender == currentKing, which prevents anyone except the current king from claiming the throne. This error ensures no address can claim the king. This is the opposite of the Protocol functionalities.

Root + Impact

The root impact is caused by a logical error in the require statement, which is shown below.

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:

  • This issue occurs 100% of the time

  • The bug is deterministic and will always prevent claims

  • Any attempt by a new player to claim the throne will fail with the error message "Game: You are already the king. No need to re-claim."

Impact:

  • This vulnerability completely breaks the core game mechanics

  • Nobody can claim the king

  • Players lose the ability to participate in the game after the initial claim

  • The entire "last man standing" concept is defeated, as no person can ever be king

Proof of Concept

When currentKing is address(0) (initial state), only address(0) can claim (impossible)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console2} from "forge-std/Test.sol";
import {Game} from "../src/Game.sol";
/**
* @title KingValidationVulnerabilityTest
* @dev Test contract to demonstrate the king validation vulnerability in Game.sol
* This test shows how the incorrect require statement prevents anyone from claiming the throne
*/
contract KingValidationVulnerabilityTest is Test {
Game public game;
address public deployer;
address public player1;
address public player2;
address public player3;
// Initial game parameters for testing
uint256 public constant INITIAL_CLAIM_FEE = 0.1 ether;
uint256 public constant GRACE_PERIOD = 1 days;
uint256 public constant FEE_INCREASE_PERCENTAGE = 10;
uint256 public constant PLATFORM_FEE_PERCENTAGE = 5;
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
player2 = makeAddr("player2");
player3 = makeAddr("player3");
// Give players enough ETH to participate
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.deal(player3, 10 ether);
// Deploy the game contract
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
/**
* @dev Test that demonstrates the vulnerability where no one can claim the throne initially
* Since currentKing starts as address(0), and the require checks msg.sender == currentKing,
* no real address can ever equal address(0), so the first claim will always fail
*/
function test_NoOneCanClaimThroneInitially() public {
// Verify initial state
assertEq(
game.currentKing(),
address(0),
"Initial king should be address(0)"
);
// Try to claim throne with player1 - this should fail due to the bug
vm.startPrank(player1);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
// Verify that the state hasn't changed
assertEq(
game.currentKing(),
address(0),
"King should still be address(0)"
);
assertEq(game.pot(), 0, "Pot should still be 0");
assertEq(game.totalClaims(), 0, "Total claims should still be 0");
}
}

Recommended Mitigation

Change the equality operator from == to != in the require statement:

function claimThrone() external payable gameNotEnded nonReentrant {
- 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 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.