Last Man Standing

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

[H-4] Critical Game Parameters Can Be Updated Mid-Game by Owner, Leading to Centralization Risks and Unfair Gameplay

Root Cause

All owner update functions Game::updateGracePeriod(), Game::updateClaimFeeParameters(), and Game::updatePlatformFeePercentage() lack any restriction to prevent them from being called while a game is active. This allows the contract owner to manipulate core parameters at any time. Any ability to change it during an ongoing game introduces centralization risk and violates the expected fairness for participants.


Description

The following functions can be arbitrarily called by the owner while a game is still ongoing:

Game::updateGracePeriod(uint256 _newGracePeriod)

Game::updateClaimFeeParameters(uint256 _newInitialClaimFee, uint256 _newFeeIncreasePercentage)

Game::updatePlatformFeePercentage(uint256 _newPlatformFeePercentage)

None of these check whether the game is currently active or has ended. As a result, any of these parameters can be modified during an ongoing round to:

  • Favor a specific player.

  • Disrupt timing expectations.

  • Or maximize platform profits at the expense of players.

Such actions violate the trustless and fair competition expectations of on-chain games.

function updateGracePeriod(uint256 _newGracePeriod) external onlyOwner {
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
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
isValidPercentage(_newPlatformFeePercentage)
{
platformFeePercentage = _newPlatformFeePercentage;
emit PlatformFeePercentageUpdated(_newPlatformFeePercentage);
}

Impact

The ability to change gameplay fees, reward timing, or platform fee percentages mid-round breaks player trust and fairness. A malicious or compromised owner can:

  • Extend or reduce grace period based on who is king (Game::updateGracePeriod()).

  • Increase/Decrease the feeIncreasePercentage right before a friend claims the throne (Game::updateClaimFeeParameters()).

  • Increase platform fee after a big deposit to siphon more ETH (Game::updatePlatformFeePercentage()).

This undermines the core assumption of immutability and fairness in game mechanics.


Proof of Concept (PoC)

This test verifies that the contract owner can update key game configuration parameters grace period, claim fee, fee increase percentage, and platform fee while the game is actively running (i.e., mid-game).

The test simulates a real game scenario:

  • Player1 claims the throne, initiating the game

  • The owner then changes game parameters

  • Player2 claims the throne under the new conditions

  • Assertions check that updated parameters are reflected

This test highlights a potential vulnerability:
These updates can be made during an active game, potentially giving unfair advantage or breaking assumptions for current or future participants.

function test_updatingGracePeriodMidGameByOwner() public {
// Player1 starts the game
vm.startPrank(player1);
game.claimThrone{value:game.claimFee()}();
assertEq(player1, game.currentKing());
vm.stopPrank();
// Store old game parameters
uint256 gracePeriodBefore = game.gracePeriod();
uint256 feeIncreasePercentageBefore = game.feeIncreasePercentage();
uint256 platformFeePercentageBefore = game.platformFeePercentage();
// Owner updates parameters during an active game
vm.startPrank(deployer);
game.updateGracePeriod(172800); // from 1 day to 2 days
game.updateClaimFeeParameters(1 ether, 50); // claim fee to 1 ether & feeIncreasePercentage to 50%
game.updatePlatformFeePercentage(50); // platform fee to 50%
vm.stopPrank();
// Assert the values were updated correctly
assertGt(game.gracePeriod(), gracePeriodBefore);
assertGt(game.feeIncreasePercentage(), feeIncreasePercentageBefore);
assertGt(game.platformFeePercentage(), platformFeePercentageBefore);
// Player2 joins under new game conditions
vm.startPrank(player2);
game.claimThrone{value:game.claimFee()}();
assertEq(player2, game.currentKing());
vm.stopPrank();
}

Recommended Mitigation:

Add a Game::gameEndedOnly modifier (or a similar check) to restrict such changes to only after the current game ends:

function updateClaimFeeParameters(...) external onlyOwner gameEndedOnly { ... }
function updateGracePeriod(...) external onlyOwner gameEndedOnly { ... }
function updatePlatformFeePercentage(...) external onlyOwner gameEndedOnly { ... }
Updates

Appeal created

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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