Last Man Standing

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

Owner-Controlled Platform Fee Update Can prevent players from Claiming the Throne.

Root + Impact

Description

  • The function updatePlatformFeePercentage function allows the owner to change the PlatformFee even while a game round is active. This can be exploited to prevent the other players from entering the game , impacting gameplay fairness and user trust.

function updatePlatformFeePercentage(uint256 _newPlatformFeePercentage) external onlyOwner
isValidPercentage(_newPlatformFeePercentage)

Risk

Likelihood:

  • The function is callable by the owner at any time. No guard (e.g., gameEndedOnly check) is in place to prevent misuse during active rounds.

Impact:

  • Changing the PlatformFee mid-game can:

    1. Prevent the new player from claiming the throne because of increased fee. The fee to be updated after the game has ended.

    2. Enable malicious manipulation by the owner.

    3. Allows the currentKing to win.

    4. Break user trust and violate expectations of fairness and finality.

Proof of Concept

This POC shows how the pot value decreases after the platformfees are updated in the middle of the game.

// 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;
address public player2;
address public player3;
// 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 = 5; // 5%
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
player2 = makeAddr("player2");
player3 = makeAddr("player3");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.deal(player3, 10 ether);
vm.prank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
}
function test_claimmthrone() external{
vm.prank(player1);
game.claimThrone{value: 1 ether}();
vm.prank(deployer);
game.updatePlatformFeePercentage(99);
vm.prank(player2);
game.claimThrone{value:1.02 ether}();
vm.prank(player3);
game.claimThrone{value: 1.1 ether}();
}
}

Output:

GameTest::test_claimmthrone()
├─ [0] VM::prank(player1: [0x7026B763CBE7d4E72049EA67E89326432a50ef84])
│ └─ ← [Return]
├─ [152621] Game::claimThrone{value: 1000000000000000000}()
│ ├─ emit ThroneClaimed(newKing: player1: [0x7026B763CBE7d4E72049EA67E89326432a50ef84], claimAmount: 1000000000000000000 [1e18], newClaimFee: 110000000000000000 [1.1e17], newPot: 950000000000000000 [9.5e17], timestamp: 1)
│ └─ ← [Stop]
├─ [0] VM::prank(deployer: [0xaE0bDc4eEAC5E950B67C6819B118761CaAF61946])
│ └─ ← [Return]
├─ [6838] Game::updatePlatformFeePercentage(99)
│ ├─ emit PlatformFeePercentageUpdated(newPlatformFeePercentage: 99)
│ └─ ← [Stop]
├─ [0] VM::prank(player2: [0xEb0A3b7B96C1883858292F0039161abD287E3324])
│ └─ ← [Return]
├─ [50121] Game::claimThrone{value: 1020000000000000000}()
│ ├─ emit ThroneClaimed(newKing: player2: [0xEb0A3b7B96C1883858292F0039161abD287E3324], claimAmount: 1020000000000000000 [1.02e18], newClaimFee: 121000000000000000 [1.21e17], newPot: 960200000000000000 [9.602e17], timestamp: 1)
│ └─ ← [Stop]
├─ [0] VM::prank(player3: [0xcC37919fDb8E2949328cDB49E8bAcCb870d0c9f3])
│ └─ ← [Return]
│ └─ ← [Stop]
├─ [0] VM::prank(deployer: [0xaE0bDc4eEAC5E950B67C6819B118761CaAF61946])
│ └─ ← [Return]
├─ [6838] Game::updatePlatformFeePercentage(99)
│ ├─ emit PlatformFeePercentageUpdated(newPlatformFeePercentage: 99)
│ └─ ← [Stop]
├─ [0] VM::prank(player2: [0xEb0A3b7B96C1883858292F0039161abD287E3324])
│ └─ ← [Return]
├─ [50121] Game::claimThrone{value: 1020000000000000000}()
│ ├─ emit ThroneClaimed(newKing: player2: [0xEb0A3b7B96C1883858292F0039161abD287E3324], claimAmount: 1020000000000000000 [1.02e18], newClaimFee: 121000000000000000 [1.21e17], newPot: 960200000000000000 [9.602e17], timestamp: 1)
│ └─ ← [Stop]
├─ [0] VM::prank(player3: [0xcC37919fDb8E2949328cDB49E8bAcCb870d0c9f3])
│ └─ ← [Return]
│ ├─ emit PlatformFeePercentageUpdated(newPlatformFeePercentage: 99)
│ └─ ← [Stop]
├─ [0] VM::prank(player2: [0xEb0A3b7B96C1883858292F0039161abD287E3324])
│ └─ ← [Return]

Recommended Mitigation:

By adding the modifier gameEndedOnly will mitigate the vunerability.

- function updateClaimFeeParameters(
- uint256 _newInitialClaimFee,
- uint256 _newFeeIncreasePercentage
- ) external onlyOwner isValidPercentage(_newFeeIncreasePercentage)
+ function updateClaimFeeParameters(
+ uint256 _newInitialClaimFee,
+ uint256 _newFeeIncreasePercentage
+ ) external ganeEndedOnly onlyOwner isValidPercentage(_newFeeIncreasePercentage)
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.