Last Man Standing

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

Owner Can Manipulate Grace Period to Prematurely End Rounds

Root + Impact

Description

  • In normal operation, the game requires that the owner "cannot declare a winner before the grace period expires," and "cannot reset the game if a round is still active."

  • However, the owner can exploit the updateGracePeriod function during an active round to artificially shorten the grace period (to something like 1 second). This allows the owner to immediately declare a winner and reset the game, bypassing the intended game mechanics.

function declareWinner() external gameNotEnded {
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);
}
@> function updateGracePeriod(uint256 _newGracePeriod) external onlyOwner {
require(_newGracePeriod > 0, "Game: New grace period must be greater than zero.");
gracePeriod = _newGracePeriod;
emit GracePeriodUpdated(_newGracePeriod);
}

Risk

Likelihood:

  • The owner can execute this exploit at any time during an active round

  • The attack requires minimal steps: one transaction to update the grace period and another to declare winner

Impact:

  • Violates game fairness and destroys player trust in the system

  • Enables rug pull scenarios where the owner colludes with the current king (or is the current king themselves)

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console2, console} 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.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
function test_ownerDeclareWinner() public {
vm.prank(player1);
game.claimThrone{value: 0.1 ether}();
vm.prank(player2);
game.claimThrone{value: 0.2 ether}();
vm.prank(player3);
game.claimThrone{value: 0.3 ether}();
// normal behavior: owner can't reset game or declare winner
vm.prank(deployer);
vm.expectRevert("Game: Game has not ended yet.");
game.resetGame();
// exploit
vm.startPrank(deployer);
game.updateGracePeriod(1 seconds);
skip(3 seconds); // wait a few seconds
game.declareWinner();
game.resetGame(); // owner can now reset game and declare winner
vm.stopPrank();
}
}

Recommended Mitigation

Make updateGracePeriod allowed only after the game ends.

- function updateGracePeriod(uint256 _newGracePeriod) external onlyOwner {
+ function updateGracePeriod(uint256 _newGracePeriod) external onlyOwner gameEndedOnly {
Updates

Appeal created

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
kjcao Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
kjcao Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 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.

Give us feedback!