Root + Impact
Description
In Game::claimThrone()
, the access control require statement:
@> require(
msg.sender == currentKing,
"Game: You are already the king. No need to re-claim."
);
is logically inverted in that it only allows the Game::currentKing
to claim throne and reverts if anyone else tries.
According to the game rules, part of the Game::currentKing
limitations is that they "Cannot claim if they are already the current king.". However the above statement does the opposite and only allow the currentKing to claim throne.
Additionally when the game is initialized, Game::currentKing
is address(0)
, further preventing anyone from participating, which severely breaks the game's protocol.
Risk
Likelihood:
Impact:
-
On deployment, the initial currentKing is set to address(0).
-
Any user trying to claim the throne (e.g., msg.sender != address(0)) will fail the require statement, and the transaction reverts.
-
This makes it impossible for anyone to ever claim the throne, rendering the game non-functional.
Proof of Concept
pragma solidity ^0.8.20;
import {Test, console} 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;
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");
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 testAnyoneCanPlay() public {
vm.prank(player1);
vm.expectRevert();
game.claimThrone{value: INITIAL_CLAIM_FEE}();
}
}
Recommended Mitigation
in Game::claimThrone()
, replace 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."
);
.
.
.
}