MyCut

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

Unchecked ERC20 transfer in closePot causes silent failure of manager payout

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • The normal behavior is that when a pot closes after 90 days, the manager receives 10% of remaining rewards.

  • Explain the specific issue or problem in one or more sentences

  • The Pot.closePot() function transfers the manager's cut using i_token.transfer() without checking the return value. For tokens that return false on failure (like USDT)

// Root cause in the codebase with @> marks to highlight the relevant section
// Pot.sol - Lines 49-62
function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
if (remainingRewards > 0) {
uint256 managerCut = remainingRewards / managerCutPercent;
i_token.transfer(msg.sender, managerCut); // @audit Line 55 - return value ignored
uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
}
}

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Medium - This occurs when:

    • Token returns false instead of reverting (USDT, BNB)

    • Manager address is blacklisted on token contract


  • Reason 2

Impact:

  • Impact 1

  • High Impact:

    1. Manager loses their rightful 10% cut

    2. Tokens remain locked in Pot contract

  • Impact 2

Proof of Concept

// Scenario with USDT
// 1. Contest has 10,000 USDT remaining after 90 days
// 2. Manager calls closePot()
// 3. Manager should receive 1,000 USDT (10%)
// 4. Manager is blacklisted on USDT contract
// 5. transfer returns false but doesn't revert
// 6. Function continues execution
// 7. Claimants receive their share
// 8. Manager's 1,000 USDT is stuck in contract forever

Recommended Mitigation

- remove this code
+ add this code// Pot.sol
pragma solidity ^0.8.20;
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
+ import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
contract Pot is Ownable(msg.sender) {
+ using SafeERC20 for IERC20;
function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
if (remainingRewards > 0) {
uint256 managerCut = remainingRewards / managerCutPercent;
- i_token.transfer(msg.sender, managerCut);
+ i_token.safeTransfer(msg.sender, managerCut);
uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
}
}
}
Updates

Lead Judging Commences

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