Summary
The protocol states that remaining rewards in the pot after the claim deadline would be distributed equally to claimants who claimed in time.
However when the closePot
function is called, all players including players who have not claimed in time would get an equal share of the remaining rewards.
Vulnerability Details
The vulnerability lies in line 57 of the Pot
contract.
Instead of using claimants.length
which represents the number of claimants who claimed in time, i_players.length
which represents all the registered players of the pot is used instead. This results in an inaccurate representation of the intention stated by the protocol.
Proof Of Concept
Working test case
function testIncorrectClaimantCut() public mintAndApproveTokens {
address player3 = makeAddr("player3");
players = [player1, player2, player3];
rewards = [32, 30, 38];
totalRewards = 100;
uint256 claimantsLength = 0;
vm.startPrank(user);
contest = ContestManager(conMan).createContest(players, rewards, IERC20(ERC20Mock(weth)), totalRewards);
ContestManager(conMan).fundContest(0);
vm.stopPrank();
vm.startPrank(player1);
Pot(contest).claimCut();
claimantsLength++;
assertEq(ERC20Mock(weth).balanceOf(player1), rewards[0]);
vm.stopPrank();
vm.startPrank(player2);
Pot(contest).claimCut();
claimantsLength++;
assertEq(ERC20Mock(weth).balanceOf(player2), rewards[1]);
vm.stopPrank();
vm.warp(91 days);
vm.startPrank(user);
ContestManager(conMan).closeContest(contest);
vm.stopPrank();
uint256 unclaimedRewards = totalRewards - rewards[0] - rewards[1];
uint256 managerCut = unclaimedRewards / 10;
uint256 expectedClaimantCut = (unclaimedRewards - managerCut) / claimantsLength;
assertNotEq(ERC20Mock(weth).balanceOf(player1), rewards[0] + expectedClaimantCut);
assertNotEq(ERC20Mock(weth).balanceOf(player2), rewards[1] + expectedClaimantCut);
}
Impact
The vulnerability leads to claimants who claimed in time to receive lesser rewards than the protocol promised after the pot is closed.
Tools Used
Foundry, manual review
Recommended Mitigation
To mitigate this vulnerability, the closePot
function in the Pot
contract should calculate claimantCut
using the length of claimants
instead of i_players
.
function closePot() external onlyOwner {
...
...
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);
}
}
}