MyCut

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

fundContest has no fee-on-transfer protection causing Pot insolvency

Root + Impact

Description

  • When fundContest() transfers tokens to the Pot, it records totalRewards as the funded amount. However, with fee-on-transfer tokens, the Pot receives fewer tokens than totalRewards due to the transfer fee deducted during transferFrom().

  • The Pot's remainingRewards is set to totalRewards in its constructor, creating a mismatch between the actual token balance and the internal accounting. Later claims will revert when the balance runs out before all players have claimed.

// ContestManager.sol::fundContest()
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);
// Pot receives (totalRewards - fee), but remainingRewards == totalRewards
}

Risk

Likelihood:

  • Any contest created with a fee-on-transfer token (PAXG, STA, SAFEMOON, or USDT with fee activated) triggers this insolvency.

  • The owner may not be aware that a token charges transfer fees, especially since USDT has a fee mechanism that is currently set to 0 but can be activated at any time.

Impact:

  • The Pot becomes insolvent: it holds fewer tokens than remainingRewards indicates.

  • Later players who call claimCut() will have their transactions revert because the Pot's token balance is insufficient.

  • The last claimants are effectively robbed of their rewards while early claimants get paid in full — first-come-first-served drain.

Proof of Concept

The following scenario demonstrates that when a fee-on-transfer token is used, the Pot receives fewer tokens than its internal remainingRewards tracking expects, causing the last claimants to have their transactions revert due to insufficient token balance.

function testFeeOnTransferInsolvency() public {
// Token charges 5% transfer fee
// totalRewards = 100 tokens
// After transferFrom: Pot receives 95 tokens
// remainingRewards = 100 (set in constructor)
// Players 1-9 claim 10 tokens each = 90 tokens total
// Player 10 tries to claim 10 tokens
// Pot only has 95 - 90 = 5 tokens left
// transfer(player10, 10) REVERTS — insufficient balance
// Player 10 loses their reward
}

Recommended Mitigation

Add a balance-before-after check in fundContest() to verify the Pot actually received the full totalRewards amount. If the actual received amount is less (due to a transfer fee), the transaction reverts, preventing the creation of an insolvent Pot.

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();
}
+ uint256 balanceBefore = token.balanceOf(address(pot));
token.transferFrom(msg.sender, address(pot), totalRewards);
+ uint256 balanceAfter = token.balanceOf(address(pot));
+ uint256 actualReceived = balanceAfter - balanceBefore;
+ require(actualReceived >= totalRewards, "Fee-on-transfer not supported");
}
Updates

Lead Judging Commences

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