Last Man Standing

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

Centralization Risk: Owner-Only Controls Allow Malicious Owner to Hijack or Freeze the Game

Root + Impact

Root Cause: All critical administration and game-flow functions are restricted to a single owner() account → Impact: Owner can unilaterally manipulate game parameters, reset or block progress, or drain fees—no checks or multi-party governance.

WHY WOULD ANYONE TRUST THE OWNER: It's a Game something similar to a gamble. So we can't expect the owner to be fair. The owner can change the rules at any time, so we can't expect the game to be fair.

Description

  • The Game contract places complete authority over game progression, configuration, and fund withdrawals in the hands of a single owner(). Functions such as:

  • resetGame()

  • updateGracePeriod(...)

  • updateClaimFeeParameters(...)

  • updatePlatformFeePercentage(...)

  • withdrawPlatformFees()

are all guarded by onlyOwner. While ownership-based access is common, granting unchecked, unilateral control of both game logic and funds to one key creates a single point of failure and opens the door to:

  • Game Freezing: Owner can set gracePeriod to an excessively high value, or call resetGame() prematurely—blocking players permanently.

  • Fee Griefing: Owner can drive fees to astronomical levels, or repeatedly withdraw platform fees to starve the pot.

  • Malicious Drains: Owner can repeatedly update parameters or withdraw funds without accountability.

@> function resetGame() external onlyOwner gameEndedOnly { … }
@> function updateGracePeriod(uint256) external onlyOwner { … }
@> function updateClaimFeeParameters(uint256,uint256) external onlyOwner { … }
@> function updatePlatformFeePercentage(uint256) external onlyOwner { … }
@> function withdrawPlatformFees() external onlyOwner { … }

Risk

Likelihood: High

  • In real-world DeFi, overly centralized admin keys (e.g., unrevoked multisig keys) have resulted in protocol hijacks, emergency halts, and irreversible fund loss.

  • All critical control is vested in one EOA.

  • No limits on how often or what values can be set.

  • No multi-party checks, timelocks, or on-chain governance required.

Impact: High

  • Single-Point Control: Owner can unilaterally halt or hijack the game without recourse.

  • User Trust Erosion: Players have no defense against malicious parameter changes.

  • Financial Risk: Owner can empty the fee pool at will, potentially leaving no pot for players.

  • No Recovery Mechanism: No timelock, no multisig, no emergency override—game remains frozen until owner chooses otherwise.

In DeFi history, protocols with unrevoked admin keys (e.g., Yam Finance v1, various forks of Compound) suffered governance takeovers, emergency halts, and community loss when keyholders exercised unchecked power.

Tools Used:

  • Foundry Test Suite

  • Chat-GPT AI Assistance (Report Grammar Check & Improvements)

  • Manual Review

Proof of Concept

Add the following to test/Game.t.sol to simulate malicious owner behavior:

function test_owner_can_freeze_and_drain_game() public {
vm.startPrank(deployer);
// Freeze the game by setting infinite grace period
game.updateGracePeriod(10000 days);
vm.expectRevert();
game.declareWinner(); // will never succeed
// Block all new claims by inflating the fee
game.updateClaimFeeParameters(1000 ether, 100);
game.claimThrone{value: 1 ether}();
vm.warp(block.timestamp + 10000 days + 1);
game.declareWinner();
game.resetGame();
vm.expectRevert();
game.claimThrone{value: 1 ether}();
vm.stopPrank();
}

Run:

forge test --mt test_owner_can_freeze_and_drain_game

Scenario

  1. Owner Freezes Game

    game.updateGracePeriod(type(uint256).max); // Grace period effectively infinite
    // Now, even if a king exists, declareWinner() will always revert:
    // “Grace period has not expired yet.”
  2. Owner Blocks Participation

    game.updateClaimFeeParameters(1000 ether, 100); // Entry fee made prohibitively large
    // No one else can afford to claim the throne.
  3. Owner Drains Protocol Fees

    while (game.platformFeesBalance() > 0) {
    game.withdrawPlatformFees();
    // Repeats until owner has emptied all reserves
    }
  4. Owner Resets at Will

    game.resetGame(); // Can revert or reorganize the game state arbitrarily

Recommended Mitigation

- function declareWinner() external gameNotEnded {
+ function declareWinner() external gameNotEnded ONLY_ROLE(GAME_MANAGER) {
require(currentKing != address(0), "Game: No one has claimed the throne yet.");
require(block.timestamp > lastClaimTime + gracePeriod, "Game: Grace period has not expired yet.");
gameEnded = true;
pendingWinnings[currentKing] = pendingWinnings[currentKing] + pot;
pot = 0; // Reset pot after assigning to winner's pending winnings
emit GameEnded(currentKing, pot, block.timestamp, gameRound);
}
  1. Introduce Multi-Party Governance or Timelock

    • Require a timelock delay on critical parameter updates (e.g., 24 hours) so users can react.

    • Or use a multisig or DAO vote to approve changes, distributing authority.

  2. Implement Role-Based Access

    • Replace onlyOwner with granular roles (CONFIG_ADMIN, FEE_COLLECTOR, GAME_MANAGER) via OpenZeppelin AccessControl.

    • Assign roles to distinct addresses or contracts to separate powers.

  3. Add Immutable Limits

    • Enforce absolute max/min bounds on parameters (e.g., gracePeriod ≤ 7 days, claimFee ≤ 10 ether).

    • Prevent extreme values even if called by admin.

  4. Emergency Pause/Exit

    • Add a pause() feature allowing the community or a timelocked guardian to halt the game if malicious activity is detected.

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.