Summary
The contract relies on on-chain data for randomness, which can be predictable and subject to manipulation, raising concerns about the fairness of winner selection
Vulnerability Details
The contract employs the keccak256
function for generating randomness to select a winner, which, in an on-chain context, can be predictable and potentially manipulated by malicious actors. This predictability undermines the integrity of the selection process, potentially allowing unfair advantages or gaming of the system
Impact
The predictable and manipulable randomness mechanism in the contract can lead to several adverse consequences. It may allow malicious users to influence the selection of winners, thereby compromising the fairness and integrity of the raffle. This impact could result in a loss of trust and reputation for the contract and its operators, as well as potential financial losses for participants.
Tools Used
Recommendations
Consider implementing a more secure source of randomness like external oracles or Chainlink VRF for improved raffle fairness and security.
You can implement Chainlink VRF in the selectWinner
function to replace the current randomness generation. Here's an example of how you can modify the selectWinner function to use Chainlink VRF for generating random numbers:
Steps:
1 - Import VRFConsumerBase import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
2 - Import VRFConsumerBase to PuppyRaffle contract
-contract PuppyRaffle is ERC721, Ownable {
+contract PuppyRaffle is ERC721, Ownable, VRFConsumerBase { // Import VRFConsumerBase
3 - Declare these vars and add the below to constructor
uint256 public randomResult;
constructor(
uint256 _entranceFee,
address _feeAddress,
uint256 _raffleDuration,
address _vrfCoordinator,
address _link,
bytes32 _keyHash,
uint256 _fee
) ERC721("Puppy Raffle", "PR") VRFConsumerBase(_vrfCoordinator, _link) {
entranceFee = _entranceFee;
feeAddress = _feeAddress;
raffleDuration = _raffleDuration;
raffleStartTime = block.timestamp;
keyHash = _keyHash;
fee = _fee;
}
4 - And now add the below function for fair randomness
function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
require(players.length >= 4, "PuppyRaffle: Need at least 4 players");
requestRandomNumber();
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
require(msg.sender == vrfCoordinator, "Fulfillment only allowed by Coordinator");
randomResult = randomness;
uint256 winnerIndex = randomResult % players.length;
address winner = players[winnerIndex];
uint256 totalAmountCollected = players.length * entranceFee;
uint256 prizePool = (totalAmountCollected * 80) / 100;
uint256 fee = (totalAmountCollected * 20) / 100;
totalFees = totalFees + uint64(fee);
uint256 tokenId = totalSupply();
uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100;
if (rarity <= COMMON_RARITY) tokenIdToRarity[tokenId] = COMMON_RARITY;
else if (rarity <= COMMON_RARITY + RARE_RARITY) tokenIdToRarity[tokenId] = RARE_RARITY;
else tokenIdToRarity[tokenId] = LEGENDARY_RARITY;
delete players;
raffleStartTime = block.timestamp;
previousWinner = winner;
(bool success,) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId);
}
function requestRandomNumber() private {
require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK to fulfill request");
bytes32 requestId = requestRandomness(keyHash, fee);
}
Make sure to add test LINK