Root + Impact
Description
-
In claimthrone function, the player can claim the throne by sending the required claimfee
-
The issue is that in the current logic, the previous king payout is reflecting as 0 and there is no reward for the previous king
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;
@i>uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
if (currentPlatformFee > (sentAmount - previousKingPayout)) {
currentPlatformFee = sentAmount - previousKingPayout;
}
platformFeesBalance = platformFeesBalance + currentPlatformFee;
amountToPot = sentAmount - currentPlatformFee;
pot = pot + amountToPot;
currentKing = msg.sender;
lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}
Risk
Likelihood:
Impact:
Proof of Concept
Add the following test function in the Game.t.sol file
function testDethronedKingReceivesNoPayout() public {
vm.startPrank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
uint256 balanceAfterClaim = player1.balance;
uint256 nextClaimFee = INITIAL_CLAIM_FEE + (INITIAL_CLAIM_FEE * FEE_INCREASE_PERCENTAGE) / 100;
vm.startPrank(player2);
game.claimThrone{value: nextClaimFee}();
vm.stopPrank();
uint256 balanceAfterDethroned = player1.balance;
assertEq(balanceAfterDethroned, balanceAfterClaim, "Dethroned king should not receive a payout (this is the bug).");
}
Recommended Mitigation
I have removed the previousKingpayout variable along with currentplatform fee and amount to pot variable and then refactor logic to reward dethorned king
+ uint256 public dethronedKingRewardPercentage;
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.");
- 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;
// Store current king before updating
+ address payable previousKing = payable(currentKing);
// Calculate shares
+ uint256 dethronedReward = (sentAmount * dethronedKingRewardPercentage) / 100;
+ uint256 platformCut = (sentAmount * platformFeePercentage) / 100;
+ uint256 potShare = sentAmount - platformCut - dethronedReward;
// Update state
currentKing = msg.sender;
lastClaimTime = block.timestamp;
+ playerClaimCount[msg.sender] += 1;
+ totalClaims += 1;
+ platformFeesBalance += platformCut;
+ pot += potShare;
// Increase claim fee
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
// Send dethroned king their reward (last, to avoid reentrancy)
+ if (previousKing != address(0)) {
+ (bool success, ) = previousKing.call{value: dethronedReward}("");
+ require(success, "Game: Failed to send reward to dethroned king");
+ }
emit ThroneClaimed(msg.sender, sentAmount, claimFee, pot, block.timestamp);
}