Last Man Standing

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

Fixed Claim Fee Allows Final-Minute Sniping of Entire Pot

Root + Impact

The contract allows the owner to configure feeIncreasePercentage and platformFeePercentage to 0, resulting in a static claim fee with no platform deductions. This design enables an attacker to exploit the predictable, fixed cost of participation by timing a last-moment claim before the grace period begins. With a fast RPC or MEV access, they can consistently become the final king and drain the entire pot for minimal cost.

Description

  • Normally, the claim fee increases after each successful claim, which deters spam claims and gives early participants an advantage. Additionally, the platform cut reduces the net amount added to the pot, helping balance risk and reward.

  • However, if both fee increase and platform fees are set to zero, the cost of claiming the throne remains fixed, and 100% of each claim gets added to the pot. This creates a high-value honeypot where an attacker can pay a small, known amount (e.g., 0.1 ETH) to win a disproportionately large pot simply by sniping the final claim using faster infrastructure.

constructor(
uint256 _initialClaimFee,
uint256 _gracePeriod,
uint256 _feeIncreasePercentage,
uint256 _platformFeePercentage
) Ownable(msg.sender) { // Set deployer as owner
require(_initialClaimFee > 0, "Game: Initial claim fee must be greater than zero.");
require(_gracePeriod > 0, "Game: Grace period must be greater than zero.");
@> require(_feeIncreasePercentage <= 100, "Game: Fee increase percentage must be 0-100.");
@> require(_platformFeePercentage <= 100, "Game: Platform fee percentage must be 0-100.");
initialClaimFee = _initialClaimFee;
initialGracePeriod = _gracePeriod;
feeIncreasePercentage = _feeIncreasePercentage;
platformFeePercentage = _platformFeePercentage;

Risk

Likelihood:

  • This will occur when the owner sets both feeIncreasePercentage and platformFeePercentage to zero.

  • In this configuration, the pot becomes increasingly valuable with no increase in entry cost, inviting economically motivated exploits.

  • Attackers can monitor mempool or automate via fast RPC to reliably snipe the final throne claim.

Impact:

  • An attacker can win a massive pot by spending a trivial, fixed amount (e.g., 0.1 ETH)

  • All other participants incur real cost while one player dominates the prize repeatedly

  • Completely undermines the game’s fairness and integrity

  • Makes the system vulnerable to MEV bots and automated frontrunning

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "../lib/forge-std/src/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;
address public maliciousActor;
// 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 = 0; // 0%
uint256 public constant PLATFORM_FEE_PERCENTAGE = 0; // 0%
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
player2 = makeAddr("player2");
player3 = makeAddr("player3");
maliciousActor = makeAddr("maliciousActor");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.deal(player3, 10 ether);
vm.deal(maliciousActor, 10 ether);
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
function testZeroFeeScrutiny() public {
// player 1 claims the throne
vm.startPrank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
// player2 claims the throne
vm.startPrank(player2);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
// maliciousActor enters right before the contest ends and becomes king and claims the prize pool
vm.startPrank(maliciousActor);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
// declare winner and then check the balance of maliciousActor
address winner = game.currentKing();
assertEq(winner, maliciousActor);
}
}

Recommended Mitigation

  • Impose a minimum feeIncreasePercentage, or enforce non-zero platform fee

  • Introduce randomized grace period, VRF-based locks, or block-based cooldowns to prevent deterministic sniping

- require(_feeIncreasePercentage <= 100, "Game: Fee increase percentage must be 0-100.");
+ require(_feeIncreasePercentage > 0 && _feeIncreasePercentage <= 100, "Game: Fee increase must be > 0.");
- platformFeePercentage = _platformFeePercentage;
+ require(_platformFeePercentage > 0, "Platform fee cannot be zero");
+ platformFeePercentage = _platformFeePercentage;
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.