MyCut

AI First Flight #8
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Unsafe ERC20 transfers can silently fail and permanently deny user rewards

Unsafe ERC20 transfers can silently fail and permanently deny user rewards

Summary

The contract uses raw ERC20.transfer() calls without verifying the return value.

Some ERC20 tokens do not revert on failed transfers and instead return false. In such cases, the contract continues execution as if the transfer succeeded, causing users to permanently lose their claimable rewards.

Vulnerability Details

The contract performs token transfers using:

i_token.transfer(player, reward);

inside:

function _transferReward(address player, uint256 reward) internal

However, the return value of transfer() is ignored.

According to the ERC20 standard, transfer() returns a boolean indicating success or failure. Some non-standard or poorly implemented tokens may return false instead of reverting.

In claimCut(), the contract updates critical state before performing the transfer:

playersToRewards[player] = 0;
remainingRewards -= reward;
claimants.push(player);

If transfer() silently fails:

  • the user receives no tokens;

  • the reward is still marked as claimed;

  • the protocol assumes the payout succeeded.

As a result, the user permanently loses access to their rewards.

Impact

Users may irreversibly lose their rewards when interacting with ERC20 tokens that return false instead of reverting on failed transfers.

This breaks the integrity of the reward distribution process.

Impact: Medium

Likelihood

Many real-world ERC20 tokens are non-compliant or behave inconsistently with the standard.

Since the contract does not enforce the use of safe transfer wrappers, this issue is realistically possible depending on the token used.

Likelihood: Medium

Proof of Concept

Assume the contract interacts with a non-standard ERC20 token implementing:

function transfer(address to, uint256 amount) external returns (bool) {
return false;
}

A user calls:

claimCut()

The following state changes execute successfully:

playersToRewards[player] = 0;
remainingRewards -= reward;
claimants.push(player);

Then the contract calls:

i_token.transfer(player, reward);

which returns false but does not revert.

Because the return value is ignored:

  • the transaction succeeds;

  • the user receives no tokens;

  • the reward is permanently lost.

Recommended Mitigation

Use OpenZeppelin’s SafeERC20 library and replace raw transfer() calls with safeTransfer().

Example:

using SafeERC20 for IERC20;
i_token.safeTransfer(player, reward);

safeTransfer() properly handles non-standard ERC20 implementations and reverts when the transfer fails.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!