Last Man Standing

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

Incorrect Logic in `require` Statement Prevents Legitimate Claims in `claimThrone` Function .

Root Cause

The logic mistakenly inverts the intended check.

Expected Behavior Actual Behavior
Prevent current king from re-claiming Prevents everyone except current king
Allow new players to claim the throne Allows only the current king to proceed

Thus, new users attempting to claim kingship with a valid fee will always revert, breaking the competitive flow of the game.


Description

The claimThrone function contains a require statement intended to prevent the current king (0x00) from reclaiming the throne. However, the logic is implemented incorrectly and unintentionally blocks all other users from participating:

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.");
// ...
emit ThroneClaimed(msg.sender, sentAmount, claimFee, pot, block.timestamp);
}

erroneously permits only the current king (0x00) to proceed, while reverting all new participants. This defeats the core purpose of the "king-of-the-hill" gameplay mechanic.


Risk

Likelihood: High

  • The bug is in a commonly used function (claimThrone()), meaning it will be hit early during contract use.

  • Any player not already the king will instantly encounter this bug.

Impact:High

  • The throne can never be claimed by any address other than the initial currentKing.

  • The game becomes locked in a static state, halting all further interaction.

  • Platform revenue from claim fees is frozen.

  • Critical game functionality is entirely disabled.


Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console2} from "forge-std/Test.sol";
import {Game} from "../src/Game.sol";
contract GameTest is Test {
Game public game;
address public deployer;
address public player1;
address public player2;
address public player3;
address public maliciousActor;
// Initial game parameters for testing
uint256 public constant INITIAL_CLAIM_FEE = 0.1 ether; // 0.1 ETH
uint256 public constant GRACE_PERIOD = 1 days; // 1 day in seconds
uint256 public constant FEE_INCREASE_PERCENTAGE = 10; // 10%
uint256 public constant PLATFORM_FEE_PERCENTAGE = 5; // 5%
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
player2 = makeAddr("player2");
player3 = makeAddr("player3");
maliciousActor = makeAddr("maliciousActor");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.deal(player3, 10 ether);
vm.deal(maliciousActor, 10 ether);
vm.startPrank(deployer);
game = new Game(INITIAL_CLAIM_FEE, GRACE_PERIOD, FEE_INCREASE_PERCENTAGE, PLATFORM_FEE_PERCENTAGE);
vm.stopPrank();
}
function test_ClaimThrone_RevertsForPlayer() public {
vm.prank(player1);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE + 1 ether}(); // Should revert
}
}

Even though player1 is not the current king, the transaction reverts due to faulty logic.


Tools Used - Manual Review ,Foundry

Recommended Mitigation

Update the require condition to correctly reject only the current king from reclaiming, and allow new participants:

Corrected logic:

- 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-clai

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

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.