Summary
The makePrediction
function in the ThePredicter.sol
contract incorrectly requires a prediction fee for resetting predictions, whereas the fee should only be required for setting a new prediction. The function needs correction to handle fee requirements appropriately.
Vulnerability Details
The makePrediction
function currently checks the fee every time a prediction is made, including when updating an existing prediction. This is incorrect if the fee is only meant to be charged for initial predictions and not for updates or resets.
https://github.com/Cyfrin/2024-07-the-predicter/blob/839bfa56fe0066e7f5610197a6b670c26a4c0879/src/ThePredicter.sol#L85-L91
Proof of Concept
This is the test code of updating prediction.
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";
import {ThePredicterTest} from "./ThePredicter.test.sol";
contract CheckPredictionTest is ThePredicterTest {
function testPrediction() public {
vm.startPrank(stranger);
vm.warp(1);
vm.deal(stranger, 1 ether);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
vm.startPrank(organizer);
vm.warp(2);
thePredicter.approvePlayer(stranger);
vm.stopPrank();
vm.startPrank(stranger);
thePredicter.makePrediction{value: 0.0001 ether}(
0,
ScoreBoard.Result.First
);
emit log_named_uint("stranger.balance", address(stranger).balance);
vm.expectRevert(
abi.encodeWithSelector(ThePredicter__IncorrectPredictionFee.selector)
);
thePredicter.makePrediction(
0,
ScoreBoard.Result.Second
);
thePredicter.makePrediction{value: 0.0001 ether}(
0,
ScoreBoard.Result.Second
);
emit log_named_uint("stranger.balance", address(stranger).balance);
vm.stopPrank();
}
}
To test this code:
As you can see, users should repay prediction fee for updating their prediction.
Impact
The requirement to repay the prediction fee for resetting predictions can lead to unintended charges and confusion among users.
Players may be unfairly penalized or discouraged from updating their predictions due to incorrect fee handling.
Tools Used
Manual code review
Recommendations
Modify the makePrediction
function to only require the prediction fee when setting a new prediction. Ensure no fee is required for resetting or updating existing predictions.
function getPrediction(address player, uint256 matchNumber) public view returns (ScoreBoard.Result) {
return playersPredictions[player].predictions[matchNumber];
}
ScoreBoard.sol
function makePrediction(
uint256 matchNumber,
ScoreBoard.Result prediction
) public payable {
bool isNewPrediction = (scoreBoard.getPrediction(msg.sender, matchNumber) == ScoreBoard.Result.Pending);
if (isNewPrediction && msg.value != predictionFee) {
revert ThePredicter__IncorrectPredictionFee();
}
if (block.timestamp > START_TIME + matchNumber * 68400 - 68400) {
revert ThePredicter__PredictionsAreClosed();
}
scoreBoard.confirmPredictionPayment(msg.sender, matchNumber);
scoreBoard.setPrediction(msg.sender, matchNumber, prediction);
}
ThePredicter.sol