Last Man Standing

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

Fee Bypass in Game

Root + Impact

Description

  • The updateClaimFeeParameters() function should only allow parameter updates when the game has ended, ensuring that fee changes don't affect ongoing games and maintain consistent economic conditions throughout a game round.

  • The function is missing the gameEndedOnly modifier, allowing the owner to update initialClaimFee and feeIncreasePercentage during active games. This creates a critical state mismatch where initialClaimFee gets updated but claimFee remains unchanged, enabling players to claim the throne for the old fee amount instead of the new intended fee.

@> function updateClaimFeeParameters(uint256 _newInitialClaimFee, uint256 _newFeeIncreasePercentage)
external
onlyOwner
@>
isValidPercentage(_newFeeIncreasePercentage)
{
require(_newInitialClaimFee > 0, "Game: New initial claim fee must be greater than zero.");
initialClaimFee = _newInitialClaimFee;
feeIncreasePercentage = _newFeeIncreasePercentage;
emit ClaimFeeParametersUpdated(_newInitialClaimFee, _newFeeIncreasePercentage);
}

Risk

Likelihood:

  • Any owner can execute this attack with minimal effort

  • Simple parameter update that can be executed immediately

Impact:

  • Complete breakdown of intended fee structure

  • Revenue loss demonstrated in tests

Proof of Concept

This test proves players can bypass fee increases by claiming after parameter updates, and cause revenue loss.

function testVulnerability_EconomicImpact() public {
// Track expected vs actual revenue
uint256 expectedRevenue = 0;
uint256 actualRevenue = 0;
console2.log("=== ECONOMIC IMPACT ANALYSIS ===");
// Initial claim
uint256 initialFee = game.claimFee();
expectedRevenue += initialFee;
actualRevenue += initialFee;
vm.prank(player1);
game.claimThrone{value: initialFee}();
console2.log("Claim 1 - Expected:", initialFee, "Actual:", initialFee);
// Owner manipulates parameters
uint256 newInitialFee = 5 ether; // 50x increase
vm.prank(owner);
game.updateClaimFeeParameters(newInitialFee, 20);
// Second claim (VULNERABILITY)
uint256 currentFee = game.claimFee();
expectedRevenue += newInitialFee; // Should pay new fee
actualRevenue += currentFee; // Actually pays old fee
vm.prank(player2);
game.claimThrone{value: currentFee}();
console2.log("Claim 2 - Expected:", newInitialFee, "Actual:", currentFee);
console2.log("Revenue loss:", newInitialFee - currentFee);
// Third claim (normal after manipulation)
uint256 thirdFee = game.claimFee();
expectedRevenue += thirdFee;
actualRevenue += thirdFee;
vm.prank(player3);
game.claimThrone{value: thirdFee}();
console2.log("Claim 3 - Expected:", thirdFee, "Actual:", thirdFee);
// Calculate total economic impact
uint256 totalExpectedRevenue = expectedRevenue;
uint256 totalActualRevenue = actualRevenue;
uint256 revenueLoss = totalExpectedRevenue - totalActualRevenue;
uint256 lossPercentage = (revenueLoss * 100) / totalExpectedRevenue;
console2.log("\n=== ECONOMIC IMPACT SUMMARY ===");
console2.log("Total expected revenue:", totalExpectedRevenue);
console2.log("Total actual revenue:", totalActualRevenue);
console2.log("Revenue loss:", revenueLoss);
console2.log("Loss percentage:", lossPercentage, "%");
assertGt(lossPercentage, 50, "Revenue loss should be significant (>50%)");
}

Recommended Mitigation

Simple addition of the gameEndedOnly modifier prevents game manipulation and revenue loss.

function updateClaimFeeParameters(uint256 _newInitialClaimFee, uint256 _newFeeIncreasePercentage)
external
onlyOwner
+ gameEndedOnly
isValidPercentage(_newFeeIncreasePercentage)
{
require(_newInitialClaimFee > 0, "Game: New initial claim fee must be greater than zero.");
initialClaimFee = _newInitialClaimFee;
feeIncreasePercentage = _newFeeIncreasePercentage;
emit ClaimFeeParametersUpdated(_newInitialClaimFee, _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.