Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

Players cannot withdraw the funds they are entitled to in a scenario where no one has a positive score, but at least one player has a score of 0!

Summary

The protocol is designed such that if none of the players have a positive score, all of them are allowed to withdraw the entrance fee. The problem arises in a scenario where almost all players have a negative score, and at least one player has a score of 0. In this case, in the function for withdrawing funds ThePredicter::withdraw, the variable totalPositivePoints used to set the variable totalRewards will be 0. Additionally, the variable maxScore will also be set to 0. In the part of the code where the reward is calculated (variable reward):

reward = maxScore < 0
? entranceFee
@> : (shares * players.length * entranceFee) / totalShares;

and in the given scenario, it will divide by totalShares which has a value of 0, causing a revert.

Impact

This will prevent users who are entitled to withdraw certain funds from actually withdrawing those funds.

PoC

Add the following code to the test/ThePredicter.Test.sol file:

function test_rewardDistributionWithNoPositiveScoreIfThereIsAtLeastOneZeroScore() public {
address stranger2 = makeAddr("stranger2");
address stranger3 = makeAddr("stranger3");
vm.startPrank(stranger);
vm.deal(stranger, 1 ether);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
vm.startPrank(stranger2);
vm.deal(stranger2, 1 ether);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
vm.startPrank(stranger3);
vm.deal(stranger3, 1 ether);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
vm.startPrank(organizer);
thePredicter.approvePlayer(stranger);
thePredicter.approvePlayer(stranger2);
thePredicter.approvePlayer(stranger3);
vm.stopPrank();
vm.startPrank(stranger);
thePredicter.makePrediction{value: 0.0001 ether}(
1,
ScoreBoard.Result.Draw
);
thePredicter.makePrediction{value: 0.0001 ether}(
2,
ScoreBoard.Result.Draw
);
thePredicter.makePrediction{value: 0.0001 ether}(
3,
ScoreBoard.Result.Draw
);
vm.stopPrank();
vm.startPrank(stranger2);
thePredicter.makePrediction{value: 0.0001 ether}(
1,
ScoreBoard.Result.Draw
);
thePredicter.makePrediction{value: 0.0001 ether}(
2,
ScoreBoard.Result.Draw
);
thePredicter.makePrediction{value: 0.0001 ether}(
3,
ScoreBoard.Result.Draw
);
vm.stopPrank();
vm.startPrank(stranger3);
thePredicter.makePrediction{value: 0.0001 ether}(
1,
ScoreBoard.Result.Draw
);
thePredicter.makePrediction{value: 0.0001 ether}(
2,
ScoreBoard.Result.Draw
);
thePredicter.makePrediction{value: 0.0001 ether}(
3,
ScoreBoard.Result.First
);
vm.stopPrank();
vm.startPrank(organizer);
scoreBoard.setResult(0, ScoreBoard.Result.First);
scoreBoard.setResult(1, ScoreBoard.Result.First);
scoreBoard.setResult(2, ScoreBoard.Result.First);
scoreBoard.setResult(3, ScoreBoard.Result.First);
scoreBoard.setResult(4, ScoreBoard.Result.First);
scoreBoard.setResult(5, ScoreBoard.Result.First);
scoreBoard.setResult(6, ScoreBoard.Result.First);
scoreBoard.setResult(7, ScoreBoard.Result.First);
scoreBoard.setResult(8, ScoreBoard.Result.First);
vm.stopPrank();
vm.startPrank(organizer);
thePredicter.withdrawPredictionFees();
vm.stopPrank();
vm.startPrank(stranger);
thePredicter.withdraw();
vm.stopPrank();
assertEq(stranger.balance, 0.9997 ether);
vm.startPrank(stranger2);
thePredicter.withdraw();
vm.stopPrank();
assertEq(stranger2.balance, 0.9997 ether);
vm.startPrank(stranger3);
thePredicter.withdraw();
vm.stopPrank();
assertEq(stranger3.balance, 0.9997 ether);
assertEq(address(thePredicter).balance, 0 ether);
}

Recommendations

In the given part of the code in the ThePredicter::withdraw function, it is necessary to consider the possibility that maxScore can be 0:

@> reward = maxScore < 0
? entranceFee
: (shares * players.length * entranceFee) / totalShares;

and thereby avoid the possibility of division by zero.

Updates

Lead Judging Commences

NightHawK Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Possible maxScore of zero is not accounted

The checks related to maxScore do not account possible maxScore of zero leading to stuck funds or a division by zero error.

Support

FAQs

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