Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

Missing Previous King Payouts Break Game Economics

Description

  • A significant economic flaw has been identified in the claimThrone () function of the Game contract.

  • The previousKingPayout variable is declared and initialized to zero but never used in calculations:

function claimThrone() external payable gameNotEnded nonReentrant {
// ...
uint256 previousKingPayout = 0; // ← Declared but never used
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// Calculate platform fee
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// Defensive check to ensure platformFee doesn't exceed available amount after previousKingPayout
if (currentPlatformFee > (sentAmount - previousKingPayout)) { // ← previousKingPayout is always 0
currentPlatformFee = sentAmount - previousKingPayout;
}
platformFeesBalance = platformFeesBalance + currentPlatformFee;
// Remaining amount goes to the pot
amountToPot = sentAmount - currentPlatformFee; // ← previousKingPayout not subtracted
pot = pot + amountToPot;
// ...
}

Risk

Likelihood:

  • Previous king receives no payout when dethroned

  • Entire amount (minus platform fee) goes to the pot

Impact:

  • Player Expectation Violation: Players expect compensation when losing the throne

  • Reduced Motivation: No incentive to become king without intermediate rewards

Proof of Concept

// Player1 becomes first king
vm.prank(player1);
// Assuming logic error is fixed and this works
// game.claimThrone{value: INITIAL_CLAIM_FEE}();
// Record player1's initial balance
uint256 player1InitialBalance = player1.balance;
console2.log("Player1 initial balance:", player1InitialBalance);
// Get player1's balance after first claim
uint256 player1BalanceAfterClaim = player1.balance;
console2.log("Player1 balance after becoming king:", player1BalanceAfterClaim);
// Player2 claims throne (becomes new king)
uint256 newClaimFee = game.claimFee();
uint256 player1BalanceBeforeDethroned = player1.balance;
vm.prank(player2);
// game.claimThrone{value: newClaimFee}();
// Check player1's balance after losing throne
uint256 player1BalanceAfterDethroned = player1.balance;
console2.log("Player1 balance after being dethroned:", player1BalanceAfterDethroned);
// VULNERABILITY: Player1 received no payout
assertEq(player1BalanceBeforeDethroned, player1BalanceAfterDethroned);
console2.log("VULNERABILITY: Previous king received NO payout!");
// Check that player1 has no pending winnings
uint256 player1PendingWinnings = game.pendingWinnings(player1);
assertEq(player1PendingWinnings, 0);

Recommended Mitigation

function claimThrone() external payable gameNotEnded nonReentrant {
require(msg.value >= claimFee, "Game: Insufficient ETH sent to claim the throne.");
require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
+ // Give payout to previous king if one exists
+ if (currentKing != address(0)) {
+ previousKingPayout = (sentAmount * 10) / 100; // 10% to previous king
+ pendingWinnings[currentKing] += previousKingPayout;
+ }
- // Calculate platform fee
- currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
+ // Calculate platform fee from remaining amount
+ uint256 remainingAmount = sentAmount - previousKingPayout;
+ currentPlatformFee = (remainingAmount * platformFeePercentage) / 100;
- // Defensive check to ensure platformFee doesn't exceed available amount after previousKingPayout
- if (currentPlatformFee > (sentAmount - previousKingPayout)) {
- currentPlatformFee = sentAmount - previousKingPayout;
- }
platformFeesBalance = platformFeesBalance + currentPlatformFee;
// Remaining amount goes to the pot
- amountToPot = sentAmount - currentPlatformFee;
+ amountToPot = remainingAmount - currentPlatformFee;
pot = pot + amountToPot;
// Update game state
currentKing = msg.sender;
lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;
// Increase the claim fee for the next player
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}
Updates

Appeal created

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

Missing Previous King Payout Functionality

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.