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

Poor ETH Handling in `ThePredicter::withdrawPredictionFees`, the organiser may not be able to withdraw the prediction fees.

Description

The withdrawPredictionFees calculate the amount of ETH to withdraw inside the function:

uint256 fees = address(this).balance - players.length * entranceFee;

This function will work correctly if it is call before others Players call withdraw function after the end of the tournament.

If not, the fee to withdraw may become a negative number and cause arithmetic underflow everytime the Organiser call this function.

Impact

This function always revert when called by the Organiser

Tools Used

Manual review & Foundry

Poc

Place this test into ThePredicter.test.sol

function test_underflowInWithdrawPredictionFees() 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.First);
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.First);
thePredicter.makePrediction{value: 0.0001 ether}(3, ScoreBoard.Result.First);
vm.stopPrank();
vm.startPrank(stranger3);
thePredicter.makePrediction{value: 0.0001 ether}(1, ScoreBoard.Result.First);
thePredicter.makePrediction{value: 0.0001 ether}(2, ScoreBoard.Result.First);
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();
vm.startPrank(organizer);
thePredicter.withdrawPredictionFees();
vm.stopPrank();
assertEq(address(thePredicter).balance, 0 ether);
}

The terminal window will return:

Ran 1 test for test/ThePredicter.test.sol:ThePredicterTest[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)]

Recommendations

Calculate the prediction fee to withdraw inside of makePrediction function instead of withdrawPredictionFees function.

contract ThePredicter {
+ uint256 public predictionFeeToWithdraw;
.
.
.
function makePrediction(uint256 matchNumber, ScoreBoard.Result prediction) public payable {
.
.
.
+ predictionFeeToWithdraw += predictionFee;
}
function withdrawPredictionFees() public {
if (msg.sender != organizer) {
revert ThePredicter__NotEligibleForWithdraw();
}
- uint256 fees = address(this).balance - players.length * entranceFee;
- (bool success,) = msg.sender.call{value: fees}("");
+ (bool success,) = msg.sender.call{value: predictionFeeToWithdraw}("");
require(success, "Failed to withdraw");
}
Updates

Lead Judging Commences

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

Wrong computation in withdrawPredictionFees

withdrawPredictionFees incorrectly computes the value to be transferred to the organizer, which leads to pending players not being able to cancel their registration, approved players not being able to claim their rewards and other errors.

Support

FAQs

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