MyCut

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

Remaining funds after the Pot is closed due to rounding

Summary

After the pot is closed there is a potential small remainder of funds in the Pot which occurs in the calculation of claimantCut (rounding error).

Vulnerability Details

When the function Pot::closePot is called by the Owner, the claimantCut is calculated in this Line

uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;

Since solidity doesn't support floating point numbers and (remainingRewards - managerCut) is odd, there will be a remainder of 1 (only valid for two players).

Proof of Concept:
Considering addding this test to TestMyCut.t.sol

function testRemainingFundsInThePot() public mintAndApproveTokens {
vm.startPrank(user);
rewards = [399, 400];
totalRewards = 1000 ;
contest = ContestManager(conMan).createContest(players, rewards, IERC20(ERC20Mock(weth)), totalRewards);
ContestManager(conMan).fundContest(0);
vm.warp(91 days);
vm.stopPrank();
vm.startPrank(player1);
Pot(contest).claimCut();
vm.stopPrank();
vm.startPrank(player2);
Pot(contest).claimCut();
vm.stopPrank();
console.log("Player1 balance:", weth.balanceOf(player1));
console.log("Player1 balance:", weth.balanceOf(player2));
console.log("Balance in the Pot:", weth.balanceOf(contest));
vm.startPrank(user);
ContestManager(conMan).closeContest(contest);
vm.stopPrank();
uint256 remainingBalance = weth.balanceOf(contest);
console.log("Remaining Funds in the Pot:", remainingBalance);
assertEq(remainingBalance, 1);
}

Note: the actual distribution of rewards or totalRewards is not decisive. It rather depends on the number of players which determines the remainder. The more players participate in the contest, the greater can the remainder be (the maximum possible remainder is n-1).

Impact

Remaining funds in the Pot of the contest.

Tools Used

Manual review

Recommendations

Consider adding these Lines to the closePot-function, which transfers the remainder to the owner

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-high reeentrancy the manager could drain the pool here, but the owner is trusted
uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
+ uint256 remainingAfterDistribution = i_token.balanceOf(address(this));
+ if (remainingAfterDistribution > 0) {
+ require(i_token.transfer(msg.sender, remainingAfterDistribution),"Transfer failed!");
+ }
}
}
Updates

Lead Judging Commences

equious Lead Judge about 1 year 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.