Last Man Standing

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

[M-1]Missing Payout to Previous King Contradicts Game Rules

[M-1] Missing Payout to Previous King Contradicts Game Rules

Description

The game's rules, as described in the function comments, state that when a player claims the throne, a portion of their fee should be paid out to the previous king. This creates a key incentive for players to participate and become the king.

The claimThrone function fails to implement this payout mechanic. The previousKingPayout variable is initialized to 0 and is never updated. As a result, the overthrown king receives nothing, which is contrary to the documented behavior.

// src/Game.sol
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; // Payout is initialized to 0...
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// ... and is never updated before being used in calculations.
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
if (currentPlatformFee > (sentAmount - previousKingPayout)) {
currentPlatformFee = sentAmount - previousKingPayout;
}
// ...

Risk

Likelihood: High

  • This bug occurs every time a king is overthrown (assuming the first critical bug is fixed).

  • The payout logic is completely absent, so it will fail in 100% of eligible scenarios.

Impact: Medium

  • The game's incentive structure is broken. Players are not rewarded as promised by the rules, which can discourage participation.

  • This discrepancy between documented rules and actual contract behavior erodes user trust and the perceived fairness of the game.

Proof of Concept

The following test first fixes the initial claimThrone bug, then proves that player1 (the overthrown king) receives no payout after player2 claims the throne. player1's balance remains unchanged throughout the entire process.

// test/Game.t.sol
function testOverthrownKingReceivesNoPayoutThroughoutGameLifecycle() public {
// This test requires the contract to be fixed so a new player can claim the throne.
// For this PoC, we assume `require(msg.sender != currentKing)` is in place.
// Step 1: Player 1 becomes the first king.
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
assertEq(game.currentKing(), player1, "Player 1 should be the king.");
// Step 2: Record player1's balance before being overthrown.
uint256 player1BalanceBeforeOverthrow = player1.balance;
// Step 3: Player 2 overthrows player1.
uint256 nextClaimFee = game.claimFee();
vm.prank(player2);
game.claimThrone{value: nextClaimFee}();
assertEq(game.currentKing(), player2, "Player 2 should now be the king.");
// Step 4: Check player1's balance immediately after being overthrown.
// It should be unchanged, proving no immediate payout was sent.
assertEq(
player1.balance,
player1BalanceBeforeOverthrow,
"FAIL: Player 1's balance changed immediately after being overthrown."
);
}

Recommended Mitigation

Implement the payout logic for the previous king. This involves adding a new state variable, previousKingPayoutPercentage, to define the payout amount and modifying the claimThrone function to calculate and transfer the funds.

// src/Game.sol
contract Game is Ownable {
// ...
uint256 public feeIncreasePercentage;
uint256 public platformFeePercentage;
+ uint256 public previousKingPayoutPercentage; // Percentage of claimFee for the previous king
// ...
constructor(
uint256 _initialClaimFee,
uint256 _gracePeriod,
uint256 _feeIncreasePercentage,
- uint256 _platformFeePercentage
+ uint256 _platformFeePercentage,
+ uint256 _previousKingPayoutPercentage
) Ownable(msg.sender) {
// ...
require(_platformFeePercentage <= 100, "Game: Platform fee percentage must be 0-100.");
+ require(_previousKingPayoutPercentage < 100, "Game: Payout percentage must be less than 100.");
+ require((_platformFeePercentage + _previousKingPayoutPercentage) < 100, "Game: Combined fees must be less than 100.");
// ...
platformFeePercentage = _platformFeePercentage;
+ previousKingPayoutPercentage = _previousKingPayoutPercentage;
// ...
}
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;
+ address previousKing = currentKing;
+
+ // Calculate payouts
+ uint256 currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
+ uint256 previousKingPayout = (sentAmount * previousKingPayoutPercentage) / 100;
+
+ // Update platform fee balance
+ platformFeesBalance += currentPlatformFee;
+
+ // Pay the previous king if one exists
+ if (previousKing != address(0) && previousKingPayout > 0) {
+ (bool success, ) = payable(previousKing).call{value: previousKingPayout}("");
+ require(success, "Game: Failed to pay previous king.");
+ }
+
+ // Remaining amount goes to the pot
+ uint256 amountToPot = sentAmount - currentPlatformFee - previousKingPayout;
+ pot += amountToPot;
- // Calculate platform fee
- currentPlatformFee = (sentAmount * 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;
- pot = pot + amountToP
// Update game state
currentKing = msg.sender;
lastClaimTime = 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.