MyCut

First Flight #23
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Incorrect calculation of `Pot::claimantCut`, resulting in the reduced amount being distributed to the claimants

Description: The calculation of Pot::claimantCut is incorrect. According to the protocol, after a pot is closed, the manager takes a percentage cut from the remaining pool, and the remainder should be distributed equally among those who claimed their rewards in time. The current implementation erroneously divides the remaining amount by the total number of players rather than the number of claimants, severely affecting the core functionality.

Impact: Claimants receive less reward than they are entitled to.

Proof of Concept:

  1. Suppose there are 109 players in a pot, with a total reward of 109 tokens, equating to 1 token per player.

  2. If 9 players claim their reward before the pot is closed, the pot will have 100 tokens remaining.

  3. Upon closure, the manager takes a 10% cut, leaving 90 tokens.

  4. According to the protocol, 90 tokens should be distributed equally among the 9 claimants. However, due to the current implementation, the division (remainingRewards - managerCut) / i_players.length uses the total number of players (109) as the denominator. This results in a claimantCut of 0 because 90 / 109 is less than 1 and thus truncated to 0.
    Consequently, claimants who were supposed to receive 10 additional tokens each will not receive any additional tokens.

Proof of Code:

Code:

Place the folowing into TestMyCut.t.sol

function testClaimantCutIsNotCalculatedCorrectly() public {
uint256 totalRewardsForThePot = 109;
address[] memory potPlayers = new address[](109);
uint256[] memory individualRewards = new uint256[](109);
uint256 usersWhoClaimed = 9;
uint256 awardPerClaimant = 1;
// Create tokens for the pot
console.log("Minting tokens to: ", user);
vm.startPrank(user);
ERC20Mock(weth).mint(user, totalRewardsForThePot);
ERC20Mock(weth).approve(conMan, totalRewardsForThePot);
console.log("Approved tokens to: ", address(conMan));
vm.stopPrank();
// Creating the players array and the rewards array
for (uint8 i = 0; i < potPlayers.length; i++) {
potPlayers[i] = vm.addr(i + 1);
individualRewards[i] = awardPerClaimant;
}
vm.startPrank(user);
contest = ContestManager(conMan).createContest(
potPlayers, individualRewards, IERC20(ERC20Mock(weth)), totalRewardsForThePot
);
// Funding the pot
ContestManager(conMan).fundContest(0);
vm.stopPrank();
Pot pot = Pot(contest);
console.log("Balance of Pot: ", pot.getRemainingRewards());
// Checking balance of first claimant before the claim
uint256 claimantBalanceBeforeClaim = ERC20Mock(weth).balanceOf(potPlayers[1]);
console.log("Balance before the claim: ", claimantBalanceBeforeClaim);
// 9 users claiming their cut
for (uint8 i = 0; i < usersWhoClaimed; i++) {
vm.startPrank(potPlayers[i]);
Pot(contest).claimCut();
vm.stopPrank();
}
// Checking balance of first claimant after the claim
uint256 claimantBalanceAfterClaim = ERC20Mock(weth).balanceOf(potPlayers[1]);
console.log("Balance after the claim: ", claimantBalanceAfterClaim);
// Closing the pot
vm.warp(91 days);
vm.startPrank(user);
ContestManager(conMan).closeContest(contest);
vm.stopPrank();
// Checking balance of first claimant after pot closure
uint256 claimantBalanceAfterPotClosure = ERC20Mock(weth).balanceOf(potPlayers[1]);
console.log("Balance after the pot closure: ", claimantBalanceAfterPotClosure);
// Claimant only has the token(s) that they claimed themselves. Meaning, balance after the claim and the balance after the pot closure is the same
assertEq(claimantBalanceAfterClaim, claimantBalanceAfterPotClosure);
}

Run the test by using:

forge test --mt testClaimantCutIsNotCalculatedCorrectly -vv --force

Recommended Mitigation: Update the calculation of claimantCut in Pot::closePot to use the correct denominator.

function closePot() external onlyOwner {
if (block.timestamp - i_deployedAt < 90 days) {
revert Pot__StillOpenForClaim();
}
console.log("remainingRewards: ", remainingRewards);
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;
console.log("claimantCut: ", claimantCut);
for (uint256 i = 0; i < claimants.length; i++) {
_transferReward(claimants[i], claimantCut);
}
}
}
Updates

Lead Judging Commences

equious Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Incorrect distribution in closePot()

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.