MyCut

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

M-05 Unchecked ERC20 transferFrom Return Value in fundContest()

Root + Impact:

Description:

The fundContest() function uses IERC20.transferFrom() to move tokens from the owner to the Pot contract, but does not check the return value. If the transfer fails silently (returns false), the contest will appear funded but actually has no tokens.

@> function fundContest(uint256 index) public onlyOwner {
Pot pot = Pot(contests[index]);
IERC20 token = pot.getToken();
uint256 totalRewards = contestToTotalRewards[address(pot)];
if (token.balanceOf(msg.sender) < totalRewards) {
revert ContestManager__InsufficientFunds();
}
@> token.transferFrom(msg.sender, address(pot), totalRewards);
}

Risk:

Likelihood: Medium - Depends on the token used; some tokens may fail transfers without reverting.

Impact:

  • Contest appears funded but has no tokens

  • Players will not be able to claim their rewards

  • Owner thinks contest is funded when it's not

  • Economic invariant broken: contest status doesn't match actual token balance

Proof of Concept:

This POC demonstrates that when transferFrom() fails silently, the contest appears funded but actually has no tokens, preventing players from claiming their rewards.

function testExploitVulnerability() public {
// Make transferFrom fail
token.setFailTransferFrom(true);
// Owner tries to fund contest
vm.prank(owner);
contestManager.fundContest(0); // This won't revert even though transfer failed
// Check if pot has tokens
Pot potContract = Pot(pot);
uint256 potBalance = token.balanceOf(pot);
console.log("Pot balance after failed fundContest:", potBalance);
assertEq(potBalance, 0); // Pot received nothing!
console.log("BUG: Contest appears funded but has no tokens!");
}

Recommended Mitigation:

Using OpenZeppelin's SafeERC20 library ensures that failed transferFrom() calls revert the transaction, preventing the contest from appearing funded when it actually has no tokens.

+ import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
contract ContestManager is Ownable {
+ using SafeERC20 for IERC20;
// ... existing code ...
function fundContest(uint256 index) public onlyOwner {
Pot pot = Pot(contests[index]);
IERC20 token = pot.getToken();
uint256 totalRewards = contestToTotalRewards[address(pot)];
if (token.balanceOf(msg.sender) < totalRewards) {
revert ContestManager__InsufficientFunds();
}
- token.transferFrom(msg.sender, address(pot), totalRewards);
+ token.safeTransferFrom(msg.sender, address(pot), totalRewards);
}
}
Updates

Lead Judging Commences

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