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

Unexpected error, when a player's score less than zero, while the maxSocre among all players is zero.

Summary

When a player's scores is less than zero, while the maxSocre among all players is zero. Then the player withdraw will come acorss unexpected error.

Vulnerability Details

For the scenaio: maxScore = 0, score <0. accroding to the doc desgin as below, the user shouldn't get profit. but actually encounter unpexcted error.

The prize fund is distributed in proportion to the points collected by all Players with a positive number of points. If all Players have a negative number of points, they will receive back the value of the entry fee.

In this scenario, the totalPositivePoints = 0. which will leads to Reason: panic: division or modulo by zero (0x12)

function withdraw() public {
....
if (maxScore > 0 && score <= 0) {
revert ThePredicter__NotEligibleForWithdraw();
}
// One secnario can continue: maxScore = 0, score <0
uint256 shares = uint8(score);
uint256 totalShares = uint256(totalPositivePoints);
uint256 reward = 0;
reward = maxScore < 0
? entranceFee
: (shares * players.length * entranceFee) / totalShares;
if (reward > 0) {
scoreBoard.clearPredictionsCount(msg.sender);
(bool success, ) = msg.sender.call{value: reward}("");
require(success, "Failed to withdraw");
}
}

Impact

Unexpected error

Tools Used

manual

function test_poc_rewardDistributionPlayerWithNegativePoint() 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
); // -1
thePredicter.makePrediction{value: 0.0001 ether}(
2,
ScoreBoard.Result.Draw
); // -1
thePredicter.makePrediction{value: 0.0001 ether}(
3,
ScoreBoard.Result.Draw
); //-1
vm.stopPrank();
vm.startPrank(stranger2);
thePredicter.makePrediction{value: 0.0001 ether}(
1,
ScoreBoard.Result.Draw
); // -1
thePredicter.makePrediction{value: 0.0001 ether}(
2,
ScoreBoard.Result.Draw
); // -1
thePredicter.makePrediction{value: 0.0001 ether}(
3,
ScoreBoard.Result.Draw
); // -1
vm.stopPrank();
vm.startPrank(stranger3);
thePredicter.makePrediction{value: 0.0001 ether}(
1,
ScoreBoard.Result.Draw
); // -1
thePredicter.makePrediction{value: 0.0001 ether}(
2,
ScoreBoard.Result.Draw
); //-1
thePredicter.makePrediction{value: 0.0001 ether}(
3,
ScoreBoard.Result.First
); // 2
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();
// no return expected error
vm.startPrank(stranger);
thePredicter.withdraw();
vm.stopPrank();
}

Recommendations

maxScore > 0 ==> maxScore >= 0

function withdraw() public {
....
if (maxScore >= 0 && score <= 0) {
revert ThePredicter__NotEligibleForWithdraw();
}
// One secnario can continue: maxScore = 0, score <0
uint256 shares = uint8(score);
uint256 totalShares = uint256(totalPositivePoints);
uint256 reward = 0;
reward = maxScore < 0
? entranceFee
: (shares * players.length * entranceFee) / totalShares;
if (reward > 0) {
scoreBoard.clearPredictionsCount(msg.sender);
(bool success, ) = msg.sender.call{value: reward}("");
require(success, "Failed to withdraw");
}
}
Updates

Lead Judging Commences

NightHawK Lead Judge 12 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.