Rock Paper Scissors

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

Lack of approve() Call Preventing Token Transfers in createGameWithToken()

Summary

The createGameWithToken() and joinGameWithToken() functions relies on the ERC20 transferFrom() function to transfer tokens from the player's wallet to the contract. However, no prior approve() function call is required or checked. This causes the transferFrom() function to fail, as the contract will not have permission to transfer the player's tokens. As a result, players will not be able to start a game, making the functionality of this feature unusable.

Vulnerability Details

In thecreateGameWithToken() and joinGameWithToken() functions, the following line is used to transfer the token from the player’s wallet to the contract:

// Transfer token to contract
winningToken.transferFrom(msg.sender, address(this), 1);

This operation relies on the player granting the contract permission to transfer the token via the ERC20 approve() function. However, the contract does not include any checks to ensure that the player has called approve() to authorize the transfer and without a prior approve() call, the allowance for the contract is 0, meaning the transferFrom() call will fail because the contract has no permission to transfer the player's tokens.

Proof Of Code

In the RockPapersScissorsTest.t.sol file, the test function does implement the approve(...) function, thus that test case passed successfully. But if we comment out the approve function in the testJoinGameWithToken(as even the actual game does not implement an approve function) then that test case does fail.

// Test joining a game with token
function testJoinGameWithToken() public {
// First create a game with token
vm.startPrank(playerA);
@> //token.approve(address(game), 1);
gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
// Now join the game with token
vm.startPrank(playerB);
@> //token.approve(address(game), 1);
vm.expectEmit(true, true, false, true);
emit PlayerJoined(gameId, playerB);
game.joinGameWithToken(gameId);
vm.stopPrank();
// Verify token transfer
assertEq(token.balanceOf(playerB), 9);
assertEq(token.balanceOf(address(game)), 2);
// Verify game state
(
address storedPlayerA,
address storedPlayerB,
,
,
,
,
,
,
,
,
,
,
,
,
,
RockPaperScissors.GameState state
) = game.games(gameId);
assertEq(storedPlayerA, playerA);
assertEq(storedPlayerB, playerB);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Created));
}

Impact

When the transferFrom() fails, the entire createGameWithToken() function will revert, and the game will not be created. As a result, no player will be able to start a game using this function without manually calling approve() beforehand.

Tools Used

Foundry

Recommendations

  1. Add an Allowance Check: Before calling transferFrom(), check that the user has granted the contract sufficient allowance:

    require(winningToken.allowance(msg.sender, address(this)) >= 1, "Contract not approved to spend token");
  2. Clear Error Messaging: Provide clear and actionable error messages to users so they can understand what went wrong:

    bool success = winningToken.transferFrom(msg.sender, address(this), 1);require(success, "Token transfer failed. Ensure the contract has been approved to spend your token.");
Updates

Appeal created

m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Broken Token Game Creation

createGameWithToken and joinGameWithToken functions will revert because they attempt transferFrom without requiring the user to first approve

m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Broken Token Game Creation

createGameWithToken and joinGameWithToken functions will revert because they attempt transferFrom without requiring the user to first approve

bochi Submitter
4 months ago
m3dython Lead Judge
4 months ago
m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Broken Token Game Creation

createGameWithToken and joinGameWithToken functions will revert because they attempt transferFrom without requiring the user to first approve

Support

FAQs

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