Snowman Merkle Airdrop

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

Reentrancy Vulnerability

Root + Impact

Description

Technical Root Cause
The collectFee() function violates the fundamental Checks-Effects-Interactions pattern. It performs external calls that transfer value (ETH and WETH) before updating any internal state variables.

Why this is broken:

The function does not update any state variable (e.g., a collected flag, balance tracking, or a nonce) before making the external calls.

The contract sends ETH/WETH to s_collector, but does not mark the collection as completed or lock the function.

Because the contract's state is unchanged, an attacker can repeatedly re-enter the collectFee() function before the first execution finishes, draining the entire balance.


Likelihood of Exploitation: VERY HIGH (9/10)
Factor Analysis
Attack Complexity Low. Attackers do not need advanced math or complex flash loans. A standard Solidity fallback/receive function with a recursive call is sufficient.
Prerequisite Knowledge Basic. Any intermediate Solidity developer or auditor can write the attacking contract in under 30 lines of code.
External Dependencies None. The attack does not rely on oracles, DEX liquidity, or external market conditions.
Accessibility High. If collectFee() is public or external without access controls, anyone can trigger it. If restricted to s_collector, an attacker only needs to become the collector (or front-run a legitimate transaction) to execute it.
Gas Costs Cheap. Executing 10–20 reentrancy loops costs minimal gas compared to the potential ETH/WETH drain.
Detection Evasion High. The entire attack occurs in a single transaction. Standard monitoring tools often flag multiple transactions, but a single blast transaction is harder to stop in time.

Impact:

Impact Description
Financial Loss Total loss of all ETH and WETH held in the contract balance.
Irreversible Blockchain transactions are final; drained funds cannot be recovered.
No Traceability The attack is performed in a single transaction; standard monitoring may not flag it in time.
Affected Assets All native ETH and any WETH tokens stored in the contract.

Proof of Concept

An attacker can deploy a malicious contract (let's call it Attacker) that acts as the s_collector.

Step-by-step Exploit:

The attacker ensures their Attacker contract is set as the s_collector (if they have privileges) or finds a way to call collectFee().

The attacker calls collectFee() from their Attacker contract.

The Snow contract executes Line 103 or Line 105 and sends ETH/WETH to the Attacker contract.

Upon receiving the funds (via the receive() or fallback() function), the Attacker contract synchronously calls collectFee() again before the first invocation finishes.

Since the Snow contract's state has not changed (no flag set, no balance deduction tracked), the contract mistakenly thinks the full balance is still available to send.

The Snow contract sends the funds again.

The attacker repeats steps 3–6 in a loop, draining the contract balance completely in a single transaction.

// Line 103, 105
i_weth.transfer(s_collector, collection);
address(s_collector).call{value: address(this).balance}("");

Recommended Mitigation

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Snow is ReentrancyGuard {
function collectFee() external nonReentrant {
// 1. CHECK - Validate conditions
// 2. EFFECTS - Update all state variables
// 3. INTERACTIONS - External calls last
uint256 balance = address(this).balance;
(bool success,) = s_collector.call{value: balance}("");
require(success, "Transfer failed");
}
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour 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!