Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: low
Invalid

Game.sol - Direct ETH Transfers Become Permanently Stuck

Description

The contract includes a receive() function that accepts direct ETH transfers, but provides no mechanism to recover these funds. Any ETH sent directly to the contract (outside of the claimThrone() function) becomes permanently locked, as there is no withdrawal function that can access these funds.

Root Cause

The contract accepts ETH via the receive() function but has no recovery mechanism:

receive() external payable {}

Key issues:

  1. Contract accepts direct ETH transfers without restriction

  2. No function exists to withdraw accidentally sent ETH

  3. These funds are not tracked by any game state variables

  4. Owner cannot access these stuck funds through existing withdrawal functions

Risk

Likelihood: Medium - Users may accidentally send ETH directly to the contract address, especially if confused about the game mechanics.

Impact: Low - Individual users may lose small amounts, but it doesn't affect game functionality or other users.

Impact

Low severity because:

  • Only affects users who accidentally send ETH directly (not through gameplay)

  • Doesn't impact game mechanics or other players

Proof of Concept

This test demonstrates how direct ETH transfers become permanently stuck in the contract:

function test_DirectETHTransfersPermanentlyStuck() public {
// Setup game
vm.startPrank(deployer);
game = new Game(0.1 ether, 1 hours, 10, 3);
vm.stopPrank();
// Record initial contract balance
uint256 initialBalance = address(game).balance;
// User accidentally sends ETH directly to contract
address user = address(0x123);
vm.deal(user, 1 ether);
vm.prank(user);
(bool success, ) = address(game).call{value: 0.5 ether}("");
assertTrue(success, "Direct transfer should succeed");
// Contract balance increases
assertEq(address(game).balance, initialBalance + 0.5 ether, "ETH should be received");
// But there's NO way to recover this ETH:
// 1. withdrawPlatformFees() only accesses platformFeesBalance (not direct transfers)
// 2. withdrawWinnings() only accesses pendingWinnings mapping
// 3. No general withdrawal function exists
// The 0.5 ETH is permanently stuck
// Owner cannot access it through any existing function
}

Recommended Mitigation

Add an emergency withdrawal function to recover accidentally sent ETH:

+ /**
+ * @dev Emergency function to recover accidentally sent ETH
+ * Can only be called when game is ended and no pending winnings exist
+ */
+ function emergencyWithdraw() external onlyOwner gameEndedOnly {
+ require(pot == 0, "Game pot must be empty");
+ // Additional checks for pending winnings could be added
+
+ uint256 contractBalance = address(this).balance;
+ require(contractBalance > platformFeesBalance, "No excess ETH to withdraw");
+
+ uint256 amount = contractBalance - platformFeesBalance;
+ (bool success, ) = payable(owner()).call{value: amount}("");
+ require(success, "Emergency withdrawal failed");
+ }
Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Direct ETH transfers - User mistake

There is no reason for a user to directly send ETH or anything to this contract. Basic user mistake, info, invalid according to CH Docs.

Support

FAQs

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