Last Man Standing

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

Overpayment Silently Added to Pot Without User Consent

Root + Impact

Description

  • Normal behavior:
    Users should only pay the exact claim fee amount, or any overpayment should be refunded to prevent accidental loss of funds.

    Specific issue:
    The contract only requires msg.value >= claimFee but uses the entire msg.value for calculations. If a user sends more ETH than required, the excess amount is silently added to the pot without any notification or refund, causing unintended financial loss.

// src/Game.sol
function claimThrone() external payable gameNotEnded nonReentrant {
require(msg.value >= claimFee, "Game: Insufficient ETH sent to claim the throne.");
uint256 sentAmount = msg.value; // @> Uses entire amount, not just claimFee
// ...
uint256 currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
uint256 amountToPot = sentAmount - currentPlatformFee;
pot += amountToPot; // @> Overpayment silently added to pot
}

Risk

Likelihood:

  • Users may accidentally send wrong amounts due to wallet interface errors, decimal mistakes, or copy-paste errors.

Impact:

  • Users lose excess funds without notification.

  • Creates poor user experience and potential trust issues.

  • Unintended donations to the pot affect game economics.

Proof of Concept

The following test demonstrates the bug. A player sends 1 ETH instead of the required 0.1 ETH, and the entire overpayment is kept by the contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {Game} from "../src/Game.sol";
contract GameOverpaymentTest is Test {
Game public game;
address public deployer;
address public player1;
uint256 public constant INITIAL_CLAIM_FEE = 0.1 ether;
uint256 public constant GRACE_PERIOD = 1 days;
uint256 public constant FEE_INCREASE_PERCENTAGE = 10;
uint256 public constant PLATFORM_FEE_PERCENTAGE = 5;
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
function testOverpaymentSilentlyAddedToPot() public {
uint256 overpayment = 1 ether; // Player sends 1 ETH instead of 0.1 ETH
uint256 playerBalanceBefore = player1.balance;
uint256 potBefore = game.pot();
// Player accidentally sends 1 ETH instead of 0.1 ETH
vm.startPrank(player1);
game.claimThrone{value: overpayment}();
vm.stopPrank();
uint256 playerBalanceAfter = player1.balance;
uint256 potAfter = game.pot();
// Player lost the entire 1 ETH, not just the required 0.1 ETH
assertEq(playerBalanceBefore - playerBalanceAfter, overpayment, "Player should have lost entire overpayment");
// The excess amount (after platform fee) was added to the pot
uint256 expectedPlatformFee = (overpayment * PLATFORM_FEE_PERCENTAGE) / 100;
uint256 expectedPotIncrease = overpayment - expectedPlatformFee;
assertEq(potAfter - potBefore, expectedPotIncrease, "Overpayment was silently added to pot");
// Verify player got no refund
assertTrue(playerBalanceAfter < playerBalanceBefore - INITIAL_CLAIM_FEE, "Player lost more than the required claim fee");
}
}

Recommended Mitigation

Either require exact payment or refund overpayments:

- 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 4 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.

Give us feedback!