Rock Paper Scissors

First Flight #38
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: low
Invalid

Excess ETH accepted without refund

Summary

In createGameWithEth, the contract accepts any msg.value >= minBet like 0.01 ETH as the bet amount and in joinGameWithEth, player B must match this exact amount (msg.value == game.bet). If player A sends more ETH than intended like 0.02 ETH the excess is not refunded and becomes part of the bet, increasing the stake without explicit user consent.

Vulnerability Details

The vulnerable code can be found here:

function createGameWithEth(uint256 _totalTurns, uint256 _timeoutInterval) external payable returns (uint256) {
require(msg.value >= minBet, "Bet amount too small");
require(_totalTurns > 0, "Must have at least one turn");
require(_totalTurns % 2 == 1, "Total turns must be odd");
require(_timeoutInterval >= 5 minutes, "Timeout must be at least 5 minutes");
uint256 gameId = gameCounter++;
Game storage game = games[gameId];
game.playerA = msg.sender;
game.bet = msg.value;
game.timeoutInterval = _timeoutInterval;
game.creationTime = block.timestamp;
game.joinDeadline = block.timestamp + joinTimeout;
game.totalTurns = _totalTurns;
game.currentTurn = 1;
game.state = GameState.Created;
emit GameCreated(gameId, msg.sender, msg.value, _totalTurns);
return gameId;
}

If a user accidentally sends 1 ETH instead of 0.01 ETH, the entire amount is locked as the bet and player B must match it, which deters participation or escalating the stake unintentionally. The excess is not lost it’s paid out in _finishGame or refunded in _cancelGame but it could confuse users and lead to unintended exposure.

Impact

Depends on user error, which is mitigated by front end interfaces but not enforced onchain. No funds are stolen, but it’s a usability flaw that could frustrate players or lead to disputes.

Recommendations

To solve this I would cap the bet at a reasonable maximum or refund excess ETH, like so:

uint256 constant MAX_BET = 100 ether; // Example cap
function createGameWithEth(uint256 _totalTurns, uint256 _timeoutInterval) external payable returns (uint256) {
require(msg.value >= minBet && msg.value <= MAX_BET, "Bet amount out of range");
game.bet = msg.value;
if (msg.value > minBet) {
uint256 excess = msg.value - minBet;
(bool success,) = msg.sender.call{value: excess}("");
require(success, "Excess refund failed");
game.bet = minBet; // Or keep as msg.value with a cap
}
...
}
Updates

Appeal created

m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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