Rock Paper Scissors

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

Locked Funds and Inflation Risk in _handleTie Token Refund Logic

Summary

The internal _handleTie function incorrectly handles the resolution of tied turns in token-based games. Instead of returning the WinningTokens originally deposited by the players back to them, it mints new tokens for each player, leaving the deposited tokens locked within the contract.


Vulnerability Details

In token-based games (game.bet == 0), Player A and Player B each deposit 1 WinningToken into the RockPaperScissors contract when creating and joining the game, respectively. The contract holds these 2 tokens.

If a turn results in a tie, the _handleTie function is called. The logic within this function for token games is as follows:

// In RockPaperScissors.sol, _handleTie function
} else {
// Token game - return tokens to players
winningToken.mint(game.playerA, 1); // <-- Mints 1 NEW token for Player A
winningToken.mint(game.playerB, 1); // <-- Mints 1 NEW token for Player B
emit GameTied(_gameId);
}

The winningToken.mint(game.playerA, 1); and winningToken.mint(game.playerB, 1); calls create two entirely new WinningTokens (one for each player). This process fails to return the original two tokens that were deposited by the players, which remain held by the RockPaperScissors contract.


Impact

This vulnerability leads to similar negative consequences as Issue #7, but in the context of a tie:

  • Locked Tokens: The 2 WinningTokens deposited by the players for the tied game become permanently locked within the RockPaperScissors contract. There's no function to recover these deposited tokens after they are erroneously left behind by _handleTie.

  • Unnecessary Token Inflation: The WinningToken supply increases by 2 tokens for every tied token game. This inflates the supply without reason and dilutes the value of existing tokens.

  • Incorrect Refund Mechanism: Players expect their original stake (the deposited token) to be returned in a tie. Receiving a newly minted token instead is not the standard or expected behavior for a refund/push scenario.


Tools Used

  • Manual code review.


Recommendations

The _handleTie function needs modification to transfer the existing tokens held by the contract back to the players, rather than minting new ones.

Replace the mint calls with transfer calls:

} else {
// Token game - return tokens to players
- winningToken.mint(game.playerA, 1);
- winningToken.mint(game.playerB, 1);
+ // Transfer the deposited tokens back from this contract to the players
+ // Ensure the contract has sufficient balance (which it should if deposits worked)
+ require(winningToken.balanceOf(address(this)) >= 2, "Contract token balance insufficient for tie refund"); // Safety check
+ winningToken.transfer(game.playerA, 1);
+ winningToken.transfer(game.playerB, 1);
emit GameTied(_gameId);
}
Updates

Appeal created

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

Minting Instead of Transferring Staked Tokens

Mints new tokens upon game completion or cancellation for token-based games

Support

FAQs

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