Root + Impact
Description
In Pot.sol::closePot(), claimantCut is calculated by dividing the remaining balance (after manager cut) by i_players.length, but the distribution loop iterates over claimants. When the number of claimants is less than the number of players, each claimant receives a smaller share than intended, and the undistributed portion remains in the contract with no withdrawal mechanism.
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;
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
}
}
Risk
Likelihood:H
Impact:H
Proof of Concept
function test_H1_WrongDivisorLocksTokens() public {
address[] memory players = new address[](2);
players[0] = player1;
players[1] = player2;
uint256[] memory rewards = new uint256[](2);
rewards[0] = 500;
rewards[1] = 500;
vm.startPrank(owner);
address contest = conMan.createContest(players, rewards, IERC20(address(weth)), 1000);
conMan.fundContest(0);
vm.stopPrank();
vm.prank(player1);
Pot(contest).claimCut();
vm.warp(91 days);
vm.prank(owner);
conMan.closeContest(contest);
uint256 lockedBalance = weth.balanceOf(contest);
console.log("[H-1] Locked tokens in Pot after closeContest:", lockedBalance);
assertEq(lockedBalance, 0, "H-1 FAIL: tokens are permanently locked in Pot");
}
Recommended Mitigation
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);
}
}
}