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!");
+ }
}
}