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