Rock Paper Scissors

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

Unchecked transferFrom Return Value in Token-Based Game Functions

Summary

The transferFrom calls in RockPaperScissors::createGameWithToken and RockPaperScissors::joinGameWithToken interact with the external WinningToken contract without verifying the returned boolean value. This is a violation of the ERC20 standard introduced in EIP-20, where transferFrom should return true on success. Ignoring this return value opens the contract to logic inconsistencies, where the function continues execution even if the token transfer fails.


Vulnerability Details

function createGameWithToken(...) external returns (uint256) {
...
// @audit-issue Unchecked return value from transferFrom
@> winningToken.transferFrom(msg.sender, address(this), 1);
...
}
function joinGameWithToken(...) external {
...
// @audit-issue Unchecked return value from transferFrom
@> winningToken.transferFrom(msg.sender, address(this), 1);
...
}

Issue Explanation

  1. ERC20 transferFrom Returns a Boolean
    Per the standard, transferFrom returns a bool indicating success or failure. However, both functions above fail to check this return value.

  2. Token Transfer Can Fail Silently
    If transferFrom fails—due to insufficient allowance, paused token, or transfer restrictions—the function proceeds as if the transfer succeeded. This leads to:

    • Invalid game state where a user appears to have paid but hasn’t

    • Game logic continuing with mismatched stakes

    • Potential denial of service in future rounds

  3. Violates Checks-Effects-Interactions Pattern
    External calls should not be trusted blindly. Not checking the return value makes the function vulnerable to subtle failures or malicious token behavior.


Impact

  • Logic Inconsistency: Games can be created or joined without actual token transfer, breaking fairness.

  • Silent Failure: Users may believe they’ve successfully entered a game when they haven’t.

  • Denial of Service: Malicious tokens that always return false can disrupt the platform’s flow.

  • Potential Exploit Path: If any game flow depends on token stake but doesn’t confirm its presence, the system becomes exploitable.


Tools Used

  • Slither

  • Manual Review


Recommendations

Always check the return value of transferFrom. Either use a require statement or import SafeERC20 from OpenZeppelin.

Option 1: Manual Check

require(
winningToken.transferFrom(msg.sender, address(this), 1),
"Token transfer failed"
);

Option 2: SafeERC20 Library

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;
// Then call:
winningToken.safeTransferFrom(msg.sender, address(this), 1);

This ensures that if the token transfer fails for any reason, the function will revert and prevent any further incorrect state changes.


Updates

Appeal created

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

Missing Check on External Call Return Value

ERC20 implementation typically reverts on transfer failures

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

Missing Check on External Call Return Value

ERC20 implementation typically reverts on transfer failures

Support

FAQs

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