MyCut

First Flight #23
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: low
Valid

Permanently stuck funds in pot contract after owner calls closePot

Summary

When the closePot function is called, there could still be remaining funds left in the Pot contract that cannot be recovered, leaving it stuck in the contract forever.

Vulnerability Details

The vulnerability lies in line 54 and line 57 of the Pot contract.

Due to integer division precision loss, the code in the mentioned lines would round down to the nearest integer, resulting in remaining funds left in the Pot contract.

Proof of Concept

Working Test Case

function testRemainingFundsAfterClosePot() public mintAndApproveTokens {
address player3 = makeAddr("player3");
players = [player1, player2, player3];
rewards = [32, 30, 38];
totalRewards = 100;
uint256 claimantsLength = 0;
// Create contest and fund pot
vm.startPrank(user);
contest = ContestManager(conMan).createContest(players, rewards, IERC20(ERC20Mock(weth)), totalRewards);
ContestManager(conMan).fundContest(0);
vm.stopPrank();
// Player 1 and player 2 claim their cut
vm.startPrank(player1);
Pot(contest).claimCut();
claimantsLength++;
vm.stopPrank();
vm.startPrank(player2);
Pot(contest).claimCut();
claimantsLength++;
vm.stopPrank();
// Close pot
vm.warp(91 days);
vm.startPrank(user);
ContestManager(conMan).closeContest(contest);
vm.stopPrank();
// Funds remaining in the pot
uint256 unclaimedRewards = totalRewards - rewards[0] - rewards[1];
uint256 managerCut = unclaimedRewards / 10;
// 13
uint256 remainingFunds =
unclaimedRewards - managerCut - ((unclaimedRewards - managerCut) / players.length) * claimantsLength;
assertEq(ERC20Mock(weth).balanceOf(address(Pot(contest))), remainingFunds);
}

Impact

The funds remaining in the contract are permenantly stuck in the contract and there are no ways to recover it.

Tools Used

Foundry, manual review

Recommended Mitigation

To mitigate this vulnerability, all remaining balance of i_token after the closePot function is called should be transferred to an EOA or a contract address where funds can be withdrawn safely. In this example, we treat destination as an EOA set up by the owner of the contest.

function closePot() external onlyOwner {
...
...
if (remainingRewards > 0) {
uint256 managerCut = remainingRewards / managerCutPercent;
i_token.transfer(msg.sender, managerCut);
uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
+ if (i_token.balanceOf(address(this)) != 0) {
+ i_token.transfer(destination, i_token.balanceOf(address(this)));
+ }
+ remainingRewards = 0;
}
}
Updates

Lead Judging Commences

equious Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Dusty Pot

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.