MyCut

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

Unchecked ERC20 `transfer()` return values allow silent reward loss

Description

All three i_token.transfer() calls in Pot.sol ignore the return value. In claimCut(), the player's reward mapping is zeroed and remainingRewards decremented before the transfer. If the token's transfer() returns false without reverting, the player's reward is permanently erased with no tokens received.

Vulnerability Details

// src/Pot.sol, line 37-46
function claimCut() public {
address player = msg.sender;
uint256 reward = playersToRewards[player];
if (reward <= 0) {
revert Pot__RewardNotFound();
}
playersToRewards[player] = 0; // @> state zeroed
remainingRewards -= reward; // @> accounting updated
claimants.push(player);
_transferReward(player, reward); // @> transfer may silently fail
}
// src/Pot.sol, line 64-66
function _transferReward(address player, uint256 reward) internal {
i_token.transfer(player, reward); // @> return value ignored
}

The same pattern appears in closePot() for both the manager cut (line 55) and claimant surplus (line 59). Slither flags all three instances as unchecked-transfer.

Some ERC20 tokens (notably USDT on mainnet) return false on failure instead of reverting. With such tokens, a failed transfer silently passes while the contract's internal state already reflects the deduction.

Risk

Likelihood:

  • The README specifies "Standard ERC20 Tokens Only," and standard ERC20 implementations revert on failure. This reduces the likelihood since non-reverting tokens like USDT are technically out of scope. But any future token integration or fork that uses non-reverting tokens would be immediately vulnerable.

Impact:

  • A player permanently loses their entire reward. The mapping is zeroed, they are added to the claimants array (so they get surplus later), but the actual tokens never arrive. There is no way to re-claim.

PoC

No PoC provided because standard ERC20 tokens (in scope) revert on failure, making this scenario unreachable within the stated scope. The Slither detection confirms the code pattern is present.

Recommendations

Use OpenZeppelin's SafeERC20 wrapper, which handles both reverting and non-reverting tokens. This is a one-line change per call site and is standard practice for any ERC20 interaction.

+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+ using SafeERC20 for IERC20;
function _transferReward(address player, uint256 reward) internal {
- i_token.transfer(player, reward);
+ i_token.safeTransfer(player, reward);
}
// In closePot():
- i_token.transfer(msg.sender, managerCut);
+ i_token.safeTransfer(msg.sender, managerCut);
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 13 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!