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

Users may not be able to predict due to the calculation of the deadline

Summary

Users may not be able to predict on matches because of the deadline wrongly set to far from the start time of the match

Vulnerability Details

Based on the design of this protocol players should be able to predict until an hour before the start of a match which is 20:00 each day. on the first day the makePrediction() function in ThePredicter.sol prevents users from making prediction 19 hours before the start of the match, on the second day users can only predict 24 hours before the start of the match, on the 3rd day users can only predict 29 hours before the start time like that onward, on the last day users can only predict 59 hours (which is about 2 days and 11 hours) before the match contrary to the 1 hour intended design

Add this file to test and run forge t --mc AuditTest --mt test_makePredictions -vvvvv

// 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";
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; // Thu Aug 15 2024 20:00:00 GMT+0000
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_makePredictions() public{
vm.deal(player, 1 ether);
//player registers with fee
vm.prank(player);
thePredicter.register{value: 0.04 ether}();
vm.startPrank(organizer);
thePredicter.approvePlayer(player);
vm.stopPrank();
//player makes prediction with fee an hour before the match this function will fail
vm.expectRevert();
vm.warp(START_TIME - 3600);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(0, ScoreBoard.Result.First);
vm.stopPrank();
//if we try to predict 4 hours before the match which is 14400 seconds before the match
vm.expectRevert();
vm.warp(START_TIME - 14400);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(0, ScoreBoard.Result.First);
vm.stopPrank();
//if we try to predict 18 hours before the match which is 64800 seconds before the match
vm.expectRevert();
vm.warp(START_TIME - 64800);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(0, ScoreBoard.Result.First);
vm.stopPrank();
//if we try to predict 19hours before the match which is 68400 seconds before the match now this should pass
vm.warp(START_TIME - 68400);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(0, ScoreBoard.Result.First);
vm.stopPrank();
//on the second day users can only
//if we try to predict 1 hour before the match (START_TIME + 86,400(24HRS) - 3600) this should fail
vm.expectRevert();
vm.warp(START_TIME + 86400 - 3600);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(1, ScoreBoard.Result.First);
vm.stopPrank();
//If we predict 23 hours before the start of the match (START_TIME + 86,400(24HRS) - 82,800) this should fail
vm.expectRevert();
vm.warp(START_TIME + 86400 - 82800);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(1, ScoreBoard.Result.First);
vm.stopPrank();
//if we predict 24 hours before the start of the match (START_TIME + 86,400(24HRS) - 86,400) this should pass
vm.warp(START_TIME + 86400 - 86400);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(1, ScoreBoard.Result.First);
vm.stopPrank();
// on the 9th day
//if we predict an hour before the start of the match (START_TIME + 86,400(24HRS) * 9 - 3600) this should fail
vm.expectRevert();
vm.warp(START_TIME + 86400 * 8 - 3600);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(8, ScoreBoard.Result.First);
vm.stopPrank();
//if we predict 58 hours before the start of the match (START_TIME + 86,400(24HRS) * 9 - 208,800(58 hours)) this should fail
vm.expectRevert();
vm.warp(START_TIME + 86400 * 8 - 208800);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(8, ScoreBoard.Result.First);
vm.stopPrank();
//if we predict 59 hours before the start of the match (START_TIME + 86,400(24HRS) * 9 - 212,400(59 hours)) this should pass
vm.warp(START_TIME + 86400 * 8 - 212400);
vm.startPrank(player);
thePredicter.makePrediction{value: 0.0001 ether}(8, ScoreBoard.Result.First);
vm.stopPrank();
}
}

Impact

This could lead to all the users not being able to predict and therefore the organizers not getting the money from the prediction fees

Tools Used

Manual code review

Recommendations

The calculation for deadline should be fixed on makePrediction() in ThePredicter.sol and setPrediction() in Scoreboard.sol

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 (block.timestamp > START_TIME + matchNumber * 86400 - 3600) {
revert ThePredicter__PredictionsAreClosed();
}
scoreBoard.confirmPredictionPayment(msg.sender, matchNumber);
scoreBoard.setPrediction(msg.sender, matchNumber, prediction);
}
function setPrediction(
address player,
uint256 matchNumber,
Result result
) public {
- if (block.timestamp <= START_TIME + matchNumber * 68400 - 68400)
+ if (block.timestamp <= START_TIME + matchNumber * 86400 - 3600)
playersPredictions[player].predictions[matchNumber] = result;
playersPredictions[player].predictionsCount = 0;
for (uint256 i = 0; i < NUM_MATCHES; ++i) {
if (
playersPredictions[player].predictions[i] != Result.Pending &&
playersPredictions[player].isPaid[i]
) ++playersPredictions[player].predictionsCount;
}
}
Updates

Lead Judging Commences

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

Match timestamps are incorrect

In both contracts there is a similar error in the computation of the timestamps of the matches.

Support

FAQs

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