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

The owner could manipulate the results for all 9 matches to potentially secure all the rewards.

Summary

The ScoreBoard contract allows the organizer (owner) to set the results for all 9 matches. This presents a vulnerability where the organizer can manipulate the results to ensure that their predictions are always correct, potentially claiming all the funds in the ThePredicter contract.

Vulnerability Details

By setting the results for all matches to a predetermined value and making predictions that match these results, the organizer can ensure they win all rewards.

POC
this ensures the results for all 9 matches are set to guarantee that the organizer wins and can claim all funds:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {ThePredicter} from "./ThePredicter.sol";
import {ScoreBoard} from "./ScoreBoard.sol";
contract ExploitTest {
ThePredicter thePredicter;
ScoreBoard scoreBoard;
constructor(address _thePredicter, address _scoreBoard) {
thePredicter = ThePredicter(_thePredicter);
scoreBoard = ScoreBoard(_scoreBoard);
}
function exploit() public {
// Register and approve the organizer
thePredicter.register{value: 1 ether}(); // Assuming 1 ether is the entrance fee
thePredicter.approvePlayer(address(this));
// Set results to ensure the organizer's predictions will win
for (uint256 i = 0; i < 9; i++) {
scoreBoard.setResult(i, ScoreBoard.Result.First); // Setting results to "First"
}
// Make predictions that match the set results
for (uint256 i = 0; i < 9; i++) {
thePredicter.makePrediction(i, ScoreBoard.Result.First); // Matching predictions
}
// Withdraw all funds
thePredicter.withdraw();
}
}

Impact

The ScoreBoard contract is not properly validated and there are no checks to prevent manipulation, the owner can exploit it to claim all the rewards. This is especially concerning if the prize pool contains significant funds.

Tools Used

Manual review and foundry

Recommendation

  1. Implement multi-signature approval for setting match results.

  2. Emit events for result proposals and approvals.

  3. Consider using decentralized oracles or third-party verifiers to validate match results.


    Here's an updated implementation of the ScoreBoard contract incorporating multi-signature approval for result setting

    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.20;
    contract MultiSigScoreBoard {
    uint256 private constant NUM_MATCHES = 9;
    enum Result {
    Pending,
    First,
    Draw,
    Second
    }
    struct ResultProposal {
    Result result;
    bool executed;
    uint256 approvalCount;
    mapping(address => bool) approvals;
    }
    address[] public approvers;
    uint256 public numApprovalsRequired;
    mapping(address => bool) public isApprover;
    mapping(uint256 => ResultProposal) public resultProposals;
    event ResultProposed(uint256 matchNumber, Result result);
    event ResultApproved(uint256 matchNumber, address approver);
    event ResultFinalized(uint256 matchNumber, Result result);
    modifier onlyApprover() {
    require(isApprover[msg.sender], "Not an approver");
    _;
    }
    constructor(address[] memory _approvers, uint256 _numApprovalsRequired) {
    require(_approvers.length >= _numApprovalsRequired, "Invalid setup");
    for (uint256 i = 0; i < _approvers.length; i++) {
    isApprover[_approvers[i]] = true;
    }
    approvers = _approvers;
    numApprovalsRequired = _numApprovalsRequired;
    }
    function proposeResult(uint256 matchNumber, Result result) public onlyApprover {
    ResultProposal storage proposal = resultProposals[matchNumber];
    require(!proposal.executed, "Result already executed");
    proposal.result = result;
    proposal.approvalCount = 0;
    emit ResultProposed(matchNumber, result);
    }
    function approveResult(uint256 matchNumber) public onlyApprover {
    ResultProposal storage proposal = resultProposals[matchNumber];
    require(!proposal.executed, "Result already executed");
    require(!proposal.approvals[msg.sender], "Already approved");
    proposal.approvals[msg.sender] = true;
    proposal.approvalCount += 1;
    emit ResultApproved(matchNumber, msg.sender);
    if (proposal.approvalCount >= numApprovalsRequired) {
    proposal.executed = true;
    emit ResultFinalized(matchNumber, proposal.result);
    }
    }
    function getResult(uint256 matchNumber) public view returns (Result) {
    ResultProposal storage proposal = resultProposals[matchNumber];
    require(proposal.executed, "Result not finalized");
    return proposal.result;
    }
    }
Updates

Lead Judging Commences

NightHawK Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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