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

Entrance fees funds get locked if maximum score is 0

Summary

If the maximum score for all players results in 0 all of the funds collected from the entrance fees get locked because the function ThePredicter.withdraw will revert due to a division by zero error.

Vulnerability Details

If the maximum score for all players results in 0 all of the funds collected from the entrance fees get locked because the function ThePredicter.withdraw will revert due to a division by zero error.

This happens because the code handles correctly the cases where the maximum score is positive or negative, but does not handle the case where it is 0.

In the following lines of code where the reward is computed, the variable totalShares value is 0 if the maximum score is 0, which causes a division by zero error for all players that want to withdraw their rewards.

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

The PoC below shows how this scenario might happen, and how no player will be able to withdraw rewards:

pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ThePredicter} from "../src/ThePredicter.sol";
import {ScoreBoard} from "../src/ScoreBoard.sol";
contract MaxScore0LocksFunds is Test {
ThePredicter public thePredicter;
ScoreBoard public scoreBoard;
address public organizer = makeAddr("organizer");
address public stranger = makeAddr("stranger");
function setUp() public {
vm.startPrank(organizer);
scoreBoard = new ScoreBoard();
thePredicter = new ThePredicter(
address(scoreBoard),
0.04 ether,
0.0001 ether
);
scoreBoard.setThePredicter(address(thePredicter));
vm.stopPrank();
}
function test_maxScore0LocksFunds() public {
address player = makeAddr("player");
vm.deal(player, 1 ether);
vm.deal(stranger, 1 ether);
vm.startPrank(player);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
vm.startPrank(stranger);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
vm.startPrank(organizer);
thePredicter.approvePlayer(player);
thePredicter.approvePlayer(stranger);
vm.stopPrank();
// make predictions
// player makes 9 incorrect predictions (score -9)
vm.startPrank(player);
for (uint256 m = 0; m < 9; ++m) {
thePredicter.makePrediction{value: 0.0001 ether}(
m,
ScoreBoard.Result.Second
);
}
vm.stopPrank();
// stranger makes 2 correct predictions and 4 incorrect predictions
// (score 0)
vm.startPrank(stranger);
for (uint256 m = 0; m < 4; ++m) {
thePredicter.makePrediction{value: 0.0001 ether}(
m,
ScoreBoard.Result.Second
);
}
thePredicter.makePrediction{value: 0.0001 ether}(
4,
ScoreBoard.Result.First
);
thePredicter.makePrediction{value: 0.0001 ether}(
5,
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();
// the stranger does not have a negative number of points (score 0),
// so they should receive a reward, but the call reverts
// due to a division by 0
vm.startPrank(stranger);
vm.expectRevert();
thePredicter.withdraw();
vm.stopPrank();
// the player is not able to retrieve the entrance fee either
// due to a division by 0
vm.startPrank(player);
vm.expectRevert();
thePredicter.withdraw();
vm.stopPrank();
// so the funds collected from the entrance fees are locked
}
}

Impact

  • Funds collected from entrance fees get locked

Tools Used

Foundry

Recommendations

Make it possible for players to withdraw the entrance fee if the maximum score was 0. The reward computation should be as follows:

reward = maxScore <= 0
? entranceFee
: (shares * players.length * entranceFee) / totalShares;
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.