Last Man Standing

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

claimThrone() Always Reverts Due to Incorrect Require Check

Root + Impact

Description

  • The normal behavior of claimThrone() should allow a new player (different from the current king) to claim the throne by paying the required fee, thereby updating the currentKing and the game state.

  • However, due to a logic error in the require statement, the function currently only allows the current king to call claimThrone(). Since the initial currentKing is address(0) and no one controls it, no user can successfully call this function. This results in a permanent denial of service for the game.

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:

  • Occurs every time claimThrone() is called by any player, because no one can ever satisfy msg.sender == currentKing unless they are already the king.

  • The initial currentKing is address(0) (nobody), so the first claim will always fail.

Impact:

  • The core feature of the game (claiming the throne) is broken.

  • Players cannot participate, and all deposited ETH (if any) is stuck forever because no claims or game progression can occur.

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();
}
// Main Test
function testClaimThroneFailsForAnyPlayer() public {
vm.prank(player1);
vm.expectRevert(bytes("Game: You are already the king. No need to re-claim."));
game.claimThrone{value: 1 ether}();
}
}

Expected: Player becomes king.
Actual: Reverts every time because msg.sender can never equal address(0).

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