Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Business Logic Flaw: Owner Can Modify Game Settings While Game Is Active, Violating Intended Restrictions

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.

//should add gameEndedOnly modifier
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

// 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;
// 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 = 51; // 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 {
// 1. Start the game by having a player claim the throne
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
console2.log("Grace Perido Before :",game.gracePeriod());
// 2. Immediately attempt to change a setting as the owner
vm.prank(deployer);
game.updateGracePeriod(999); // This should NOT be allowed if game is active
console2.log("Grace Perido After :",game.gracePeriod());
// 3. Check that the change was applied
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);
}
Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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