The refund function is vulnerable to a reentrancy attack.
When the refund function sends ETH to an address, if that address is a contract with a fallback function, that fallback function can be executed. If this fallback function in turn calls the refund function before the initial call to refund completes, the result is a reentrancy attack. In this case, the vulnerable line in the refund function is:
Assume there's a malicious contract called Attacker. This contract's fallback function is designed to call the refund() function again:
solidity
Copy code
contract Attacker {
PuppyRaffle public raffle;
constructor(PuppyRaffle _raffle) {
raffle = _raffle;
}
// This function is used to initiate the attack
function attack() external payable {
raffle.refund(someKnownIndex);
}
// Fallback function
fallback() external payable {
if (address(raffle).balance >= entranceFee) {
raffle.refund(someKnownIndex);
}
}
}
When the attack() function of the Attacker contract is called, it'll trigger the refund() in PuppyRaffle, which in turn will send ETH back to the Attacker's fallback function. The fallback function then immediately calls refund() again, leading to the reentrancy loop.
An attacker can repeatedly call the refund function and drain more ETH than they are entitled to. This can result in significant financial losses for the contract and its users.
Code Review
Recommendations
Use Reentrancy Guard: A common pattern to prevent reentrancy attacks is the use of a reentrancy guard.
Update State Before Transfers: Another common best practice to prevent reentrancy is to update the contract's state before sending out any ETH.
In your function, you'd move the players[playerIndex] = address(0); line before the payable(msg.sender).sendValue(entranceFee); line.
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.