Description
The contract defines a gameEndedOnly
modifier intended to restrict certain administrative actions to be callable only after the game has ended. According to the documentation, functions like updateGracePeriod
, updateClaimFeeParameters
, and updatePlatformFeePercentage
are meant to be restricted during an active game to preserve fairness and prevent disruption,
However, these functions do not apply the gameEndedOnly
modifier, allowing the owner
to invoke them at any time including while a game round is still active. This results in a direct violation of the documented game rules and introduces a serious business logic flaw.
function updateGracePeriod(uint256 _newGracePeriod) external onlyOwner {
require(_newGracePeriod > 0, "Game: New grace period must be greater than zero.");
gracePeriod = _newGracePeriod;
emit GracePeriodUpdated(_newGracePeriod);
}
Impact:
Although the owner is assumed to be trusted, allowing them to change core game parameters during an active round breaks the game's intended rules. This can lead to unfair gameplay, reduced player trust, and undermines the protocol’s credibility.
Proof of Concept
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;
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 = 51;
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
function testOwnerCanUpdateSettingsWhileGameIsActive() public {
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
console2.log("Grace Perido Before :",game.gracePeriod());
vm.prank(deployer);
game.updateGracePeriod(999);
console2.log("Grace Perido After :",game.gracePeriod());
uint256 updatedGracePeriod = game.gracePeriod();
assertEq(updatedGracePeriod, 999, "Owner was able to modify settings during active game");
}
}
salah@fedora:~/Desktop/2025-07-last-man-standing$ forge test --mt testOwnerCanUpdateSettingsWhileGameIsActive -vvv
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/Game.t.sol:GameTest
[PASS] testOwnerCanUpdateSettingsWhileGameIsActive() (gas: 170305)
Logs:
Grace Perido Before : 86400
Grace Perido After : 999
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.15ms (553.17µs CPU time)
Ran 1 test suite in 16.11ms (3.15ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Recommended Mitigation
//we add gameEndedOnly modifier
+ function updateGracePeriod(uint256 _newGracePeriod) external onlyOwner gameEndedOnly {
require(_newGracePeriod > 0, "Game: New grace period must be greater than zero.");
gracePeriod = _newGracePeriod;
emit GracePeriodUpdated(_newGracePeriod);
}
function updateClaimFeeParameters(
uint256 _newInitialClaimFee,
uint256 _newFeeIncreasePercentage
+ ) external onlyOwner gameEndedOnly isValidPercentage(_newFeeIncreasePercentage) {
require(_newInitialClaimFee > 0, "Game: New initial claim fee must be greater than zero.");
initialClaimFee = _newInitialClaimFee;
feeIncreasePercentage = _newFeeIncreasePercentage;
emit ClaimFeeParametersUpdated(_newInitialClaimFee, _newFeeIncreasePercentage);
}
function updatePlatformFeePercentage(uint256 _newPlatformFeePercentage)
external
onlyOwner
+ gameEndedOnly
isValidPercentage(_newPlatformFeePercentage)
{
platformFeePercentage = _newPlatformFeePercentage;
emit PlatformFeePercentageUpdated(_newPlatformFeePercentage);
}