Rock Paper Scissors

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

Incorrect Token Accounting leads to token inflation and locked Tokens

Summary

In the RockPaperScissors contract, when a token-based game finishes, the contract mints new tokens for the winner instead of transferring the tokens that were collected during game creation/joining. This leads to token accumulation in the contract and effective double-spending of tokens, as the original deposited tokens remain locked in the contract forever.

Vulnerability Details

During game creation and joining with tokens:

function createGameWithToken(...) {
// ...
winningToken.transferFrom(msg.sender, address(this), 1); // Token gets transferred to contract
}
function joinGameWithToken(...) {
// ...
winningToken.transferFrom(msg.sender, address(this), 1); // Token gets transferred to contract
}

But in the finish function:

function _finishGame(uint256 _gameId, address _winner) internal {
if (game.bet == 0) { // Token game
winningToken.mint(_winner, 2); // Mints new tokens instead of transferring existing ones
}
}

The issue occurs because:

  1. Players deposit tokens to the contract during game creation/joining (2 tokens total)

  2. Upon game completion, instead of transferring these tokens, new tokens are minted

  3. The original deposited tokens remain locked in the contract

  4. This creates an unlimited supply of new tokens while old ones accumulate

Impact

  1. Token Supply Inflation

    • Each completed token game increases total supply by 2 tokens

    • Original tokens remain locked forever

    • Uncontrolled token supply expansion

  2. Asset Lock

    • Deposited tokens are permanently locked in the contract

    • No mechanism to recover these tokens

    • Value is effectively lost

  3. Economic Impact

    • Token value may decrease due to supply inflation

    • Original token deposits become dead capital

    • Protocol accumulates worthless token reserves

Tools Used

Manual review

Recommendations

Replace minting with transfer of existing tokens:

function _finishGame(uint256 _gameId, address _winner) internal {
Game storage game = games[_gameId];
if (game.bet == 0) { // Token game
// Transfer existing tokens instead of minting
winningToken.transfer(_winner, 2); // Transfer both tokens to winner
} else { // ETH game
winningToken.mint(_winner, 1); // Still mint for ETH games
}
}
Updates

Appeal created

m3dython Lead Judge about 1 month 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.