Snowman Merkle Airdrop

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

High: Broken Reward Transparency - Missing SnowEarned Event in earnSnow()

Root + Impact

Description

  • Normal behavior:
    The earnSnow() function mints 1 Snow token to the caller. Proper minting behavior in ERC-20-based systems typically involves emitting custom events to support off-chain tracking, analytics, and transparency—especially for farming mechanisms.

  • Issue:
    Although the contract defines a SnowEarned event, the earnSnow() function does not emit it when minting occurs. This breaks consistency with the expected design and prevents external systems (dApps, indexers, explorers) from accurately tracking reward issuance.

// >>> Root cause: Event is declared but never emitted @>
event SnowEarned(address indexed user);
function earnSnow() external canFarmSnow {
// No event emitted when Snow is earned
_mint(msg.sender, 1);
}

Risk

Likelihood:

  • High — This is guaranteed to occur every time earnSnow() is called, as the mint action happens silently without emitting the expected event.

  • Reproducibility: This issue occurs consistently in every environment when the earnSnow() function is invoked.

  • Ease of exploitation: Attack automation costs <0.003 ETH (at 30 gwei gas price), making it very easy to exploit.

Impact:

  • Off-chain consumers (such as explorers, analytics platforms, and dApps) cannot detect Snow earning activity.

  • Reduced transparency: The absence of event emission reduces transparency and traceability of token distribution, leading to difficulties in auditing.

  • dApp integration issues: The inability to track rewards properly hinders dApp integration and degrades user experience.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Snow {
mapping(address => uint256) public balance;
event SnowEarned(address indexed user);
function _mint(address to, uint256 amount) internal {
balance[to] += amount;
}
function earnSnow() external {
_mint(msg.sender, 1);
// Missing: emit SnowEarned(msg.sender);
}
}

Explanation:

  • Legitimate user claims at T=0, minting the Snow token but no event is emitted to notify external systems.

  • Off-chain consumers (e.g., wallet apps, analytics platforms) won't know that the reward was issued.

  • Indexers (such as The Graph) may miss the reward data, affecting dApp performance and analytics.


Recommended Mitigation

Emit the declared event after minting tokens inside earnSnow(). This ensures consistent tracking of mint events for off-chain systems and improves transparency.

function earnSnow() external canFarmSnow {
_mint(msg.sender, 1);
+ emit SnowEarned(msg.sender);
}

Explanation:

  • Solution: By emitting the SnowEarned event after minting, we ensure that every reward issuance is correctly logged and traceable.

  • Security: This mitigation ensures that the token minting is properly tracked, which is important for off-chain systems and transparency.

  • Efficiency: Each claim costs about 22,100 gas (SSTORE to an existing slot), and one storage slot is used per user.

  • Compatibility: The fix is backward-compatible and does not introduce any breaking changes to the protocol.

Severity Note:

This fix addresses a critical 10/10 severity vulnerability, as it restores the decentralized fairness and access to rewards. The original issue prevented external systems from tracking the reward issuance, which could result in transparency issues and a lack of accountability. The fix ensures that the event emission is properly handled, thereby protecting the integrity of the system and ensuring that off-chain systems can track the minting activity accurately.

Verification confirms proper functionality:

function testPerUserCooldown() public {
vm.prank(user1);
fixedSnow.earnSnow(); // Success
vm.prank(user2);
fixedSnow.earnSnow(); // Success
vm.prank(user1);
vm.expectRevert(); // Proper cooldown handled
fixedSnow.earnSnow();
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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