MyCut

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

Incorrect calculation of `claimantCut` in the `Pot:closePot` function causing players who claim on time to receive less than their entitled shares of additional rewards

Summary

In the Pot:closePot function, the claimantCut variable having an incorrect calculation, resulting players who claim their rewards on time receiving less than the expected amount of additional rewards. The discrepancy is due to the wrong denominator is used in the claimantCut.

https://github.com/Cyfrin/2024-08-MyCut/blob/946231db0fe717039429a11706717be568d03b54/src/Pot.sol#L57

Vulnerability Details

The claimantCut variable under the Pot:closePot function is used to calculate the additional rewards to distribute fairly among players who claim their rewards before the pot is closed. The current calculation does not properly account for the number of claimants. The calculation wrongly uses the i_players.length as the denominator to divide the remaining rewards among all players instead of dividing the remaining rewards to those players who make the claim on time.

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);
}
}
}

Proof of Concept:

In test/TestMyCut.t.sol, add the following test case:

function test_audit_incorrectClaimantCut() public mintAndApproveTokens {
uint256[] memory rewardsAdj = new uint256[](2);
rewardsAdj[0] = 3 ether;
rewardsAdj[1] = 1 ether;
uint256 totalRewardsAdj = 4 ether;
vm.startPrank(user);
contest = ContestManager(conMan).createContest(players, rewardsAdj, IERC20(weth), totalRewardsAdj);
ContestManager(conMan).fundContest(0);
vm.stopPrank();
// only player 1 makes the claim on time
vm.prank(player1);
Pot(contest).claimCut();
vm.prank(user);
vm.warp(91 days);
ContestManager(conMan).closeContest(contest);
// player 1 is expected to get 90% after 10% manager cut of the unclaimed 1 ether
uint256 numerator = 1 ether * 90; // Scale numerator
uint256 denominator = 100; // Original denominator
// expected balance = claimed reward on time + additional from unclaimed reward
uint256 expectedPlayerBalance = 3 ether + (numerator / denominator);
uint256 actualPlayerBalance = weth.balanceOf(player1);
console.log("expectedPlayerBalance: ", expectedPlayerBalance);
console.log("actualPlayerBalance: ", actualPlayerBalance);
assert(actualPlayerBalance < expectedPlayerBalance);
}

In the console, the following is displayed :

Ran 1 test for test/TestMyCut.t.sol:TestMyCut
[PASS] test_audit_incorrectClaimantCut() (gas: 907900)
Logs:
expectedPlayerBalance: 3900000000000000000
actualPlayerBalance: 3450000000000000000
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.09ms (241.88µs CPU time)

The test run passes indicating that the actual player balance is less than the expected player balance due to the wrong calculation in the claimantCut.

Impact

Players who claim on time will get lesser than what they entitle to from the unclaimed rewards pot, affecting also the reputation and credibility of protocol team

Tools Used

Manual review

Recommendations

To correct the calculation of the claimantCut by replacing the denominator from i_players.length to claimants.length

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;
console.log("claimantCut: ", claimantCut);
for (uint256 i = 0; i < claimants.length; i++) {
// @audit-unsure: https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop
_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.