Last Man Standing

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

loss of users Funds due to no refund mechanism

Description

The Game contract allows players to claim the throne by paying the claimFee, If a user mistakenly sends excess ETH (e.g., 10 ether), the entire amount is consumed without refunding the excess. leading to loss of users funds.

function claimThrone() external payable gameNotEnded nonReentrant {
//user mistakenly sends 10ether
@> 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;
// 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 + amountToPot;
currentKing = msg.sender;
lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;

Risk

Likelihood:

Players may send excess ETH due to misunderstanding, front-end errors, or manual transaction mistakes, as the contract only checks msg.value >= claimFee.

the loss of excess ETH discourages participation.

Impact:

Players lose excess ETH without refund, leading to financial loss and distrust in the game.

Proof of Concept

Before running this test, Kindly change the clamThrone require to
require(msg.sender != currentKing);

function test_ClaimThrone_ExcessEtherConsumed() public {
vm.startPrank(player1);
uint256 initialBalance = player1.balance;
// Player1 mistakenly sends 10 ether (excess)
game.claimThrone{value: 10 ether}();
// Verify excess ETH is consumed and no refund is given
assertEq(player1.balance, initialBalance - 10 ether, "Player1 loses all sent ETH");
assertEq(game.currentKing(), player1, "Player1 should be current king");
vm.stopPrank();
}

Tool Used

Manual review

Code Snippet
https://github.com/CodeHawks-Contests/2025-07-last-man-standing/blob/47d9d19a78acb52270269f4bff1568b87eb81a96/src/Game.sol#L186

Recommended Mitigation

consider changing the require check to be the exact claimfee. Alternatively Add a mechanism for refund when a player sends excess ETH

function claimThrone() external payable gameNotEnded nonReentrant {
- require(msg.value >= claimFee, "Game: Insufficient ETH sent to claim the throne.");
+ require(msg.value == claimFee, "Game: Must send exact claim fee amount.");
Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Overpayment not refunded, included in pot, but not in claim fee

Support

FAQs

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