Rock Paper Scissors

First Flight #38
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

Unnecessary receive() Function Allows Untracked ETH Transfers

Summary

The RockPaperScissors contract includes a receive() function that accepts arbitrary ETH transfers. However, the contract does not track, associate, or handle these ETH payments in any way when received outside of the official game creation or join flows. This could lead to ETH being stuck in the contract with no attribution or way to recover it. To maintain transparency and safety, the contract should reject direct ETH transfers that do not follow expected entry points.

Vulnerability Details

The current receive() function is defined as:

receive() external payable {
// Allow contract to receive ETH
}

This means anyone can send ETH to the contract without interacting with a specific function like createGameWithEth or joinGameWithEth. However:

  • These ETH transfers are not recorded in any game state.

  • There's no way to withdraw them unless they're part of a structured flow.

  • It creates an illusion that ETH was used for gameplay, which isn't true.

  • If users accidentally send ETH to the contract directly (e.g., from wallet), they may lose funds permanently.

Impact

  • Loss of funds: ETH sent directly to the contract is untraceable and cannot be refunded.

  • Unclear accounting: ETH balance of the contract may exceed the expected amount based on active games and fees.

  • User confusion: Users might mistakenly believe that ETH sent directly is valid for joining/creating games.

  • Audit complexity: ETH inflows not linked to events or state changes make security audits more difficult.

Tools Used

  • Code inspection

  • Understanding of Solidity receive() and fallback behavior

  • Common wallet/UX usage patterns

Recommendations

Replace the permissive receive() with a defensive version that rejects all unsolicited ETH:

receive() external payable {
revert("Direct ETH transfers not allowed");
}

Alternatively, remove the receive() function entirely. This ensures all ETH inflows must come through officially defined functions like createGameWithEth() or joinGameWithEth() which are trackable and verifiable.

Updates

Appeal created

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

Orphaned ETH due to Unrestricted receive() or Canceled Game

ETH sent directly to the contract via the receive function or after a canceled game becomes permanently locked

Support

FAQs

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