Summary
The Egg minting is based on a random number that should be lower than a certain threshold (default 20)
function searchForEgg() external {
...
uint256 random = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender, eggCounter))
) % 100;
if (random < eggFindThreshold) {
eggCounter++;
eggsFound[msg.sender] += 1;
eggNFT.mintEgg(msg.sender, eggCounter);
emit EggFound(msg.sender, eggCounter, eggsFound[msg.sender]);
}
}
https://ethereum.stackexchange.com/questions/158525/can-prevrandao-be-written-as-a-random-number-on-chain
https://medium.com/@alexbabits/why-block-prevrandao-is-a-useless-dangerous-trap-and-how-to-fix-it-5367ed3c6dfc
Vulnerability Details
All the used elements for generating a random number are known
uint256 random = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender, eggCounter))
) % 100;
I can create a list of future random numbers:
function testWeakRandomness() public {
uint256 random = 0;
uint256 eggCounter = 0;
uint256 eggFindThreshold = 20;
for ( uint256 i = 0 ; i < 100 ; i++ ){
random = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender, eggCounter))) % 100;
if (random <= eggFindThreshold) {
eggCounter++;
}
vm.warp(block.timestamp + 1);
console2.log(random);
}
}
Impact
The Random Number is predictable and I can generate a list of future random numbers and know when the egg will be minted
Tools Used
Solidity, Foundry, Manual Code Review
Recommendations
Switch to an external Randomness Service (ex: Chainlink VRF - https://docs.chain.link/vrf)