Snowman Merkle Airdrop

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

`Snow::SnowEarned` and `Snow::FeeCollected` events are declared but never emitted

`Snow::SnowEarned` and `Snow::FeeCollected` events are declared but never emitted

Description

`Snow.sol` declares two events, `SnowEarned` and `FeeCollected`, that are intended to log farming activity and fee collection actions, but neither is ever emitted anywhere in the contract. `earnSnow` silently mints SNOW with no corresponding event, and `collectFee` silently sweeps accumulated WETH and ETH with no corresponding event. This breaks off-chain observability for two of the contract's core state change actions.

event SnowEarned(address indexed earner, uint256 indexed amount);
event FeeCollected();

Neither event appears anywhere else in the file. Compare the two affected functions against `buySnow`, which correctly emits its corresponding event:


Risk

Likelihood:

  • High: this isn't conditional on any attacker action. It is the guaranteed behavior of the contract as deployed, occurring on every single call to either function.

Impact:

Off-chain infrastructure that depends on event logs like indexers, subgraphs, monitoring dashboards, analytics tooling, or any frontend polling for user activity/history, cannot observe or react to `earnSnow` or `collectFee` calls. This creates a transparency and monitoring gap: SNOW acquired via farming would be invisible to any system tracking SnowBought-style activity logs, and fee collection (a privileged, financially relevant action) would leave no on-chain audit trail beyond the underlying `Transfer` events emitted internally by the token transfers themselves, which lack the semantic context `FeeCollected` that would let observers easily distinguish this action from other transfers.

Proof of Concept

Place the following tests in the `TestSnow.sol` test suite:

function test_EarnSnow_DoesNotEmitSnowEarnedEvent() public {
vm.recordLogs();
vm.prank(alice);
snow.earnSnow();
Vm.Log[] memory logs = vm.getRecordedLogs();
bytes32 snowEarnedTopic = keccak256("SnowEarned(address,uint256)");
bool found = false;
for (uint256 i = 0; i < logs.length; i++) {
if (logs[i].topics.length > 0 && logs[i].topics[0] == snowEarnedTopic) {
found = true;
}
}
assertFalse(found, "SnowEarned should not be emitted (confirming the bug) remove this assertion once fixed");
}
function test_CollectFee_DoesNotEmitFeeCollectedEvent() public {
// Fund the contract so collectFee() has something to sweep
vm.deal(address(snow), 1 ether);
vm.recordLogs();
vm.prank(collector);
snow.collectFee();
Vm.Log[] memory logs = vm.getRecordedLogs();
bytes32 feeCollectedTopic = keccak256("FeeCollected()");
bool found = false;
for (uint256 i = 0; i < logs.length; i++) {
if (logs[i].topics.length > 0 && logs[i].topics[0] == feeCollectedTopic) {
found = true;
}
}
assertFalse(found, "FeeCollected should not be emitted (confirming the bug) remove this assertion once fixed");
}

Run the tests in the terminal:

forge test --mt test_EarnSnow_DoesNotEmitSnowEarnedEvent -vvvv
forge test --mt test_CollectFee_DoesNotEmitFeeCollectedEvent -vvvv

If the test passes, the events were not triggered appropriately.


Recommended Mitigation


Add the missing emit statements to match the existing pattern already established by `buySnow`:

function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
s_earnTimer = block.timestamp;
+ emit SnowEarned(msg.sender, 1);
}
function collectFee() external onlyCollector {
uint256 collection = i_weth.balanceOf(address(this));
i_weth.transfer(s_collector, collection);
(bool collected,) = payable(s_collector).call{value: address(this).balance}("");
require(collected, "Fee collection failed!!!");
+ emit FeeCollected();
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!