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

`ThePredicter.sol::withdraw` Will fail if all participants have a score of zero and nobody can withdraw anything

Summary

ThePredicter.sol::withdraw Will fail if all participants have a score of zero and nobody can withdraw anything.

Vulnerability Details

As per the documentation if the total score is positive then the participants will withdraw a reward depending on how much score they have.

If the total score is negative then participants should be able to withdraw their entranceFee.

However, if the total score is zero, then nobody can withdraw a reward or their entranceFee, essentially locking the funds in the contract.

Impact

The test below fails with a panic: division or modulo by zero error showing that nobody can withdraw if the total score is equal to zero.

function test_everyoneHasZeroPoints() public {
address stranger2 = makeAddr("stranger2");
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(organizer);
thePredicter.approvePlayer(stranger);
thePredicter.approvePlayer(stranger2);
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.First);
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.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(stranger);
thePredicter.withdraw();
vm.stopPrank();
assertEq(address(thePredicter).balance, 0 ether);
}

Tools Used

--Foundry

Recommendations

It is recommended to also allow participants to withdraw their entranceFee if the total score is <= to zero

function withdraw() public {
if (!scoreBoard.isEligibleForReward(msg.sender)) {
revert ThePredicter__NotEligibleForWithdraw();
}
int8 score = scoreBoard.getPlayerScore(msg.sender);
int8 maxScore = -1;
int256 totalPositivePoints = 0;
for (uint256 i = 0; i < players.length; ++i) {
int8 cScore = scoreBoard.getPlayerScore(players[i]);
if (cScore > maxScore) maxScore = cScore;
if (cScore > 0) totalPositivePoints += cScore;
}
if (maxScore > 0 && score <= 0) {
revert ThePredicter__NotEligibleForWithdraw();
}
uint256 shares = uint8(score);
uint256 totalShares = uint256(totalPositivePoints);
uint256 reward = 0;
- reward = maxScore < 0 ? entranceFee : (shares * players.length * entranceFee) / totalShares;\
+ 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 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.