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

Denial Of Withdraw Service If MaxScore is Zero

Finding 4: [High] Denial of Withdrawal Service if max_score is Zero

Summary:
No player can withdraw their rewards if the max_score is zero, resulting in all entrance fees being locked in the contract.

Vulnerability Details:
The maximum score can sometimes be zero, leading to the ThePredicter:totalPositivePoints being zero. Consequently, the ThePredicter:totalShares becomes zero. This causes the calculation reward = maxScore < 0 ? entranceFee : (shares * players.length * entranceFee) / totalShares to revert with the error "division or modulo by zero (0x12)". This occurs regardless of who the caller is, resulting in all fees collected at registration being locked in the contract.

Proof of Concept:
A test was conducted to demonstrate the issue:

function test_DenialOfWithDraw() public {
address stranger = makeAddr("stranger");
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.First);
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.Draw);
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);
/**
* stranger Score :- W(2)+2L(-1) = 0
* stranger2 Score :- 3L(-1) = -3
* stranger3 Score :- 3L(-1) = -3
* MaxScore = 0
* TotalShares = 0
* maxScore > -1 && TotalShares == 0 ==> reward = (0 * 3 * 0.04 ether) / 0 -> reverts
*/
vm.stopPrank();
vm.startPrank(organizer);
thePredicter.withdrawPredictionFees();
vm.stopPrank();
vm.startPrank(stranger);
thePredicter.withdraw();
vm.stopPrank();
int8 score = scoreBoard.getPlayerScore(stranger);
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);
}

In this test, the stranger address and others are unable to withdraw their rewards if the max_score is zero, leading to all entrance fees being locked in the contract.

Impact:
High

Tools Used:

  • Manual Review

  • Foundry

Recommendations:
Implement a withdrawal function to handle cases where the max_score is zero.


Updates

Lead Judging Commences

NightHawK Lead Judge about 1 year 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.