Summary
An incorrect prerequisite check on msg.sender
in the claimThrone
function causes the contract to become unusable after deployment. Since the currentKing is set as address(0) until first claim, attempt by any player to first call claimThrone
reverts with the confusing message "Game: You are already the king. No need to re-claim."
As a result, no other players can join or claim the throne, rendering the game non-functional from the start.
Description
In claimThrone
function, the prerequisite check on msg.sender
to be the currentKing
resulting the contract is unusable after deployment as the currentKing
is set as address(0)
as start.
When any player with non-zero address to first call claimThrone
, it will fail the check at require(msg.sender == currentKing, "Game: You are already the king. No need to re-claim.");
currentKing
is address(0)
contract Game is Ownable {
<@@> address public currentKing;
uint256 public lastClaimTime;
...
constructor(
uint256 _initialClaimFee,
uint256 _gracePeriod,
uint256 _feeIncreasePercentage,
uint256 _platformFeePercentage
) Ownable(msg.sender) {
require(_initialClaimFee > 0, "Game: Initial claim fee must be greater than zero.");
....
gameEnded = false;
<@@>
}
}
Wrong prerequisite check on msg.sender == currentKing
:
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:
Impact:
Proof of Concept
In test/Game.t.sol
, add the following test:
function test_audit_incorrectPrequisiteCheckInClaimThrone() public {
console2.log("currentKing: ", game.currentKing());
vm.prank(player1);
console2.log("msg.sender1: ", player1);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.prank(player2);
console2.log("msg.sender2: ", player2);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.prank(player3);
console2.log("msg.sender3: ", player3);
vm.expectRevert("Game: You are already the king. No need to re-claim.");
game.claimThrone{value: INITIAL_CLAIM_FEE}();
}
In terminal, run forge test --match-test test_audit_incorrectPrequisiteCheckInClaimThrone -vvv
will generate the following results:
$ forge test --match-test test_audit_incorrectPrequisiteCheckInClaimThrone -vvv [18:27:45]
[⠊] Compiling...
[⠔] Compiling 1 files with Solc 0.8.28
[⠒] Solc 0.8.28 finished in 460.41ms
Compiler run successful!
Ran 1 test for test/Game.t.sol:GameTest
[PASS] test_audit_incorrectPrequisiteCheckInClaimThrone() (gas: 130627)
Logs:
currentKing: 0x0000000000000000000000000000000000000000
msg.sender1: 0x7026B763CBE7d4E72049EA67E89326432a50ef84
msg.sender2: 0xEb0A3b7B96C1883858292F0039161abD287E3324
msg.sender3: 0xcC37919fDb8E2949328cDB49E8bAcCb870d0c9f3
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 738.21µs (97.33µs CPU time)
Ran 1 test suite in 117.12ms (738.21µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
The test passed with revert message "Game: You are already the king. No need to re-claim."
for all different players who attempted to first call the claimThrone
function, indicating that no player can successfully claim throne after the contract was deployed.
Recommended Mitigation
To correct the prerequisite condition check on msg.sender
, so it is functional as intented and tally to revert message shall it fail
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.");
+ require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");
uint256 sentAmount = msg.value;
....
}