Summary
Pot.sol::closePot
Does not correctly calculate claimantCut
leaving a balance in the Pot
contract lost forever. It is also subject to precision loss and an unequal distribution to the claiming players.
Vulnerability Details
In the closePot
function, when claimantCut
is calculated, it uses i_players
to determine how many players will be receiving a cut of the remaining unclaimed reward.
uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
However, this is incorrect, because not every player will be recieving the remaining reward. it should use claimants.length
because it will only be distributing to the claimants.
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
The Calculation of this amount should also be checked for precision loss, in the case where the remaining amount is not evenly divisible by the number of players (or claimants) there will be an unequal amount distirbuted to each player/claimant.
Impact
The test below shows that there is a leftover amount of reward stuck in the Pot
contract
function testUnclaimedRewardDistribution() public mintAndApproveTokens {
vm.startPrank(user);
rewards = [500, 500];
totalRewards = 1000;
contest = ContestManager(conMan).createContest(players, rewards, IERC20(ERC20Mock(weth)), totalRewards);
ContestManager(conMan).fundContest(0);
vm.stopPrank();
vm.startPrank(player1);
Pot(contest).claimCut();
vm.stopPrank();
uint256 claimantBalanceBefore = ERC20Mock(weth).balanceOf(player1);
vm.warp(91 days);
vm.startPrank(user);
ContestManager(conMan).closeContest(contest);
vm.stopPrank();
uint256 claimantBalanceAfter = ERC20Mock(weth).balanceOf(player1);
uint256 balanceAfterContestClose = ERC20Mock(weth).balanceOf(contest);
assert(claimantBalanceAfter > claimantBalanceBefore);
assert(balanceAfterContestClose == 225);
}
Tools Used
--Foundry
Recommendations
It is recommended to change how claimantCut
is calculated.
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);
- uint256 claimantCut = (remainingRewards - managerCut) / i_players.length;
+ uint256 claimantCut = (remainingRewards - managerCut) / claimants.length;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
}
}