MyCut

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

`Pot` ignores the return value of `i_token.transfer`, so a claim can be marked successful while no tokens move

Description

Pot moves tokens with the raw ERC20 transfer and never checks the returned boolean:

function _transferReward(address player, uint256 reward) internal {
i_token.transfer(player, reward); // return value ignored
}

The ERC20 standard allows a token's transfer to return false on failure instead of
reverting. If such a token is used, claimCut still sets playersToRewards[player] = 0
and decrements remainingRewards before/around this call, so the claim is recorded as
successful even though no tokens were actually sent. The player permanently loses their
reward. (Non-standard tokens like USDT that do not return a bool also break this call.)

Risk

Likelihood: Low — depends on the specific token configured for the pot; it bites with
non-reverting / non-standard ERC20s.

Impact: Medium — when it occurs, a player's reward is marked as claimed while they
receive nothing: a direct, unrecoverable loss of funds for that player.

Proof of Concept

Using a token whose transfer returns false without moving funds, claimCut "succeeds"
but the player gets nothing and their reward is zeroed:

contract FalseToken is ERC20 {
constructor() ERC20("False","FALSE") {}
function mint(address to, uint256 a) external { _mint(to, a); }
function transfer(address, uint256) public pure override returns (bool) { return false; }
}
​
function test_claimSilentlyFailsWithFalseReturningToken() public {
address[] memory players = new address[](1); players[0] = p1;
uint256[] memory rewards = new uint256[](1); rewards[0] = 25 ether;
​
FalseToken token = new FalseToken();
Pot pot = new Pot(players, rewards, IERC20(address(token)), 25 ether);
token.mint(address(pot), 25 ether);
​
vm.prank(p1);
pot.claimCut();
​
assertEq(token.balanceOf(p1), 0); // received nothing
assertEq(pot.checkCut(p1), 0); // but reward is marked claimed -> lost
}

Recommended Mitigation

Use OpenZeppelin's SafeERC20 and safeTransfer, which reverts if the transfer fails or
the token returns false:

using SafeERC20 for IERC20;
​
function _transferReward(address player, uint256 reward) internal {
i_token.safeTransfer(player, reward);
Updates

Lead Judging Commences

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