Snowman Merkle Airdrop

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

collectFee ignores the WETH transfer return value (unchecked ERC20, inconsistent with SafeERC20 used elsewhere)

Description

collectFee sends the collected WETH using a raw transfer and ignores its return value:

function collectFee() external onlyCollector {
uint256 collection = i_weth.balanceOf(address(this));
i_weth.transfer(s_collector, collection); // @> return value not checked, not SafeERC20
(bool collected,) = payable(s_collector).call{value: address(this).balance}("");
require(collected, "Fee collection failed!!!");
}

The native-ETH leg is checked (require(collected, ...)), but the ERC20 leg is not. i_weth is typed as a generic IERC20, and many ERC20 implementations signal failure by returning false instead of reverting (and some non-standard tokens return no value at all). A raw unchecked transfer would then silently "succeed" at the call site even though no tokens moved.

The project's canonical WETH returns true and reverts on failure, so for that exact token this is latent rather than exploitable today; but the contract takes the token address at construction and uses the unchecked pattern, which is inconsistent with the rest of the codebase (which uses SafeERC20 elsewhere) and unsafe if the configured token is ever non-standard.

Risk

Likelihood: Low — the configured WETH reverts on failure, so the unchecked return is only reachable if a non-standard / misconfigured token is used for i_weth.

Impact: Low — a silently failed transfer would make collectFee appear to succeed while leaving the fees in the contract; no third-party funds are at risk.

Impact

If i_weth is ever a token that returns false on failure (or returns no value), collectFee can report success while the WETH never actually transfers, leaving collected fees stuck and giving the collector a false confirmation.

Proof of Concept

This is a static best-practice defect. The ERC20 leg is unchecked:

i_weth.transfer(s_collector, collection); // return value discarded

while the ETH leg immediately below it is checked:

(bool collected,) = payable(s_collector).call{value: address(this).balance}("");
require(collected, "Fee collection failed!!!");

A token whose transfer returns false on failure would pass the first line without reverting, so collectFee returns successfully even though no WETH moved.

Recommended Mitigation

Use OpenZeppelin SafeERC20 for the token transfer, consistent with the rest of the contract:

+ using SafeERC20 for IERC20;
...
- i_weth.transfer(s_collector, collection);
+ i_weth.safeTransfer(s_collector, collection);
Updates

Lead Judging Commences

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