Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

malicious actor can claim all free `Snow`

Description

  • The logic for collecting free Snow in earnSnow function allows first caller to receive Snow in a specific predictable time.

Risk

Likelihood: High

  • A malicious actor can claim instantly every single time, locking others from fair chance to collect free token.

Impact: High

  • MEV & Flashbots can be used to automate collecting

  • DoS - all participants can be locked from receiving free Snow

  • Poor UX - Participants must

Proof of Concept

With no restriction the same user can claim every time in the correct time, we need to adjust or remove the first to call logic

function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) { // @> the first caller will receive the snow in a specific time each time
revert S__Timer();
}
_mint(msg.sender, 1);
s_earnTimer = block.timestamp;
}

Recommended Mitigation

  1. Adding a track for the last receiver of Snow and preventing him from claiming every reward
    This will stop the automated claiming from a single actor.

+ error S_ClaimedPrevReward();
+ address prevClaimer;
function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) { // @> the first caller will receive the snow in a specific time each time
revert S__Timer();
}
+ if (prevClaimer == msg.sender) {
+ revert S_ClaimedPrevReward();
+ }
_mint(msg.sender, 1);
s_earnTimer = block.timestamp;
+ prevClaimer = msg.sender;
}

Another way, and more fair can be using lottery principle.

This can be achieved with adding 2 functions:
enterSnowFight - function for entering the lottery for Snow
drawWinner - function that can be called only by collector

+ uint256 public participantCount;
+ mapping(uint256 => address) public participants;
+ mapping(address => bool) public registered;
+ function enterSnowFight() external canFarmSnow {
+ require(!registered[msg.sender], "Already in!");
+
+ participants[participantCount] = msg.sender;
+ participantCount++;
+ registered[msg.sender] = true;
+ }
+ function drawWinner() external onlyCollector {
+ if (block.timestamp < s_earnTimer + 1 weeks) revert("Too early");
+ if (participantCount == 0) revert("No participants");
+ uint256 winnerIndex = uint256(
+ keccak256(abi.encodePacked(block.timestamp, block.prevrandao, participantCount))
+ ) % participantCount;
// @> Will be good to integrate Chainlink VRF for randomness
+ address winner = participants[winnerIndex];
+ _mint(winner, 1);
+ for (uint256 i = 0; i < participantCount; i++) {
+ delete registered[participants[i]];
+ delete participants[i];
+ }
+ participantCount = 0;
+ s_earnTimer = block.timestamp;
+ }
Updates

Lead Judging Commences

yeahchibyke Lead Judge 27 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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