Last Man Standing

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

Missing Upper Bound Checks on Critical Game Parameters

Root + Impact

Root Cause: No maximum limits on fees or periods, Impact: Game becomes unplayable or stalls indefinitely - a vector for human error and griefing

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 currently enforces only lower bounds (> 0) on two key configuration parameters-initialClaimFee and initialGracePeriod—both in its constructor and in the owner-only update functions.

  • By omitting upper-limit checks, the deployer or owner can inadvertently (or maliciously) set:

  • Entry fees so large (e.g. 100_000_000_000 ether) that no participant can afford to play.

  • Grace periods so long (e.g. 36000 seconds instead of 3600) that no one can ever call declareWinner() within a reasonable timeframe.

This oversight not only frustrates users but also opens a griefing vector: a bad actor could freeze the game at will or make participation cost-prohibitive. Furthermore, analogous real-world DeFi exploits have occurred when protocols lacked upper bounds on critical parameters-resulting in multimillion-dollar losses.

  1. Constructor

constructor(
uint256 _initialClaimFee,
uint256 _gracePeriod,
/* … */
) {
require(_initialClaimFee > 0, "Game: Initial claim fee must be greater than zero.");
require(_gracePeriod > 0, "Game: Grace period must be greater than zero.");
@> // ❌ Missing: require(_initialClaimFee ≤ MAX_FEE)
@> // ❌ Missing: require(_gracePeriod ≤ MAX_PERIOD)
}
  1. updateClaimFeeParameters()

function updateClaimFeeParameters(uint256 _newInitialClaimFee, …) external onlyOwner {
require(_newInitialClaimFee > 0, "Game: New initial claim fee must be greater than zero.");
// ❌ Missing: require(_newInitialClaimFee ≤ MAX_FEE)
}
  1. updateGracePeriod()

function updateGracePeriod(uint256 _newGracePeriod) external onlyOwner {
require(_newGracePeriod > 0, "Game: New grace period must be greater than zero.");
// ❌ Missing: require(_newGracePeriod ≤ MAX_PERIOD)
}

Risk

Likelihood: Low

Real-World DeFi Exploits Due to Missing Bounds

  • Nomad Bridge Exploit (Aug 2022)
    A flawed upgrade removed a check that limited which message IDs could be processed. Attackers replayed a single valid cross-chain transfer call to drain ~$190 million by repeatedly withdrawing without bound.

  • bZx Flash Loan Attack (Sep 2020)
    The protocol lacked bounds on price manipulation impact when sourcing rates from Uniswap. An attacker used a flash loan to skew the price oracle beyond expected limits, then borrowed under-collateralized amounts—netting $8 million before fix.

Both incidents illustrate how missing upper-limit validations on critical parameters can lead to catastrophic, multi-million-dollar losses.

Impact: Low

  • Game Freeze: No one can join or progress.

  • User Frustration: Legitimate players blocked by absurd settings.

  • Griefing Vector: Malicious owner can sabotage gameplay.

  • Audit Complexity: Every parameter change requires manual review.

  • Reputation & Revenue Loss: Platform appears unreliable, deterring participation.

Refs & Resources:

Tools Used

  • Foundry Test Suite

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

  • Manual Review

Proof of Concept

function test_ClaimFeeMissingUpperBoundProneToHumanError() public {
// 100 billion ethers as new initialGameFee
// Note: human error, deployer and/or dev wanna push wei but unconciously they put ether, due to typing tendency.
uint256 newInitialGameFee = 100_000_000_000 ether;
uint256 newFeeIncreasePercentage = 100;
// deployer deploys The Game
vm.startPrank(deployer);
Game newGame = new Game(newInitialGameFee, GRACE_PERIOD, newFeeIncreasePercentage, PLATFORM_FEE_PERCENTAGE);
vm.stopPrank();
// same oversight in updateClaimFeeParameters
vm.startPrank(deployer);
newGame.updateClaimFeeParameters(newInitialGameFee, newFeeIncreasePercentage);
vm.stopPrank();
// Hence: nobody is willing to play the game with exhaustive entry or game fee
}
function test_gracePeriodMissingUpperBoundProneToHumanError() public {
// 10 hours as new gracePeriod
// Note: human error, deployer or/and dev wanna push 1 hours,
// but mistakenly they put 36000s == 10 hours instead of 3600s == 1 hour
uint256 newGracePeriod = 36000;
// deployer deploys The Game
vm.startPrank(deployer);
Game newGame = new Game(INITIAL_CLAIM_FEE, newGracePeriod, FEE_INCREASE_PERCENTAGE, PLATFORM_FEE_PERCENTAGE);
vm.stopPrank();
// please mutate or get rid off an another bug resides in claimThrone function.
// bug: require(msg.sender == currentKing, "Game: You are already the king. No need to re-claim.");
// fix: require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");
// player1 entered into the game to vie the throne...
vm.startPrank(player1);
newGame.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
vm.roll(block.timestamp + 3600 + 1);
// After one hour, let's say someone or current king or owner itself executes the
// declareWinner function
// however, they have to wait ~10 hours to be able to declare the game result.
vm.startPrank(player1);
vm.expectRevert("Game: Grace period has not expired yet.");
newGame.declareWinner();
vm.stopPrank();
// same oversight exist in updateClaimFeeParameters
vm.startPrank(deployer);
newGame.updateGracePeriod(newGracePeriod);
vm.stopPrank();
}

Scenario

  • A developer mistypes 100_000_000_000 ether for _initialClaimFee, expecting 0.01 ether.

  • Another confuses seconds, passing 36000 instead of 3600.

  • The contract deploys successfully, but players are unable to join (fee too high) or declare a winner (period too long).

Recommended Mitigation

Define & Enforce Sensible Upper Bounds

+ int256 constant MAX_INITIAL_CLAIM_FEE = 1 ether;
+ uint256 constant MAX_GRACE_PERIOD = 1 hours;
...
// In constructor & updates:
+ require(_initialClaimFee <= MAX_INITIAL_CLAIM_FEE, "Game: Initial claim fee too high.");
+ require(_gracePeriod <= MAX_GRACE_PERIOD, "Game: Grace period too long.");

Use Custom Errors

+ error FeeTooHigh(uint256 provided, uint256 maxAllowed);
+ error PeriodTooLong(uint256 provided, uint256 maxAllowed);
+ if (_initialClaimFee > MAX_INITIAL_CLAIM_FEE) {
+ revert FeeTooHigh(_initialClaimFee, MAX_INITIAL_CLAIM_FEE);
+ }
Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
thesandf Auditor
about 2 months ago
inallhonesty Lead Judge about 2 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.