Snowman Merkle Airdrop

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

Snow.collectFee() calls bare .transfer() on the WETH token interface, bypassing the SafeERC20 wrapper already imported and used elsewhere

Root + Impact

Description

Snow.sol correctly imports and uses SafeERC20 for all token interactions except one:

using SafeERC20 for IERC20;
// All other WETH calls use safe wrappers:
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount)); // ✓ safe
function collectFee() external onlyCollector {
uint256 collection = i_weth.balanceOf(address(this));
// @> Unsafe: calls IERC20.transfer() directly, no return value check
i_weth.transfer(s_collector, collection);
(bool collected,) = payable(s_collector).call{value: address(this).balance}("");
require(collected, "Fee collection failed!!!");
}

Risk

Likelihood:

  • With the canonical WETH implementation (which does return a bool), this does not cause issues in practice.

  • The risk materialises if the WETH address is ever pointed at a non-standard token, or if the contract is deployed with a custom WETH that follows the "no return value" pattern.

Impact:

  • A failed WETH transfer goes undetected — the collector believes fees were collected but the WETH stays in the Snow contract.

  • Accumulated protocol fees can be permanently locked if the transfer silently fails.

Proof of Concept

// Illustrative — shows the inconsistency in the code
// Safe usage (everywhere else in Snow.sol):
i_weth.safeTransferFrom(msg.sender, address(this), fee); // uses SafeERC20
// Unsafe usage (collectFee):
i_weth.transfer(s_collector, collection); // raw IERC20 call
// If transfer() returns false or no-return token is used, this silently succeeds
// without actually moving tokens

Recommended Mitigation

Use safeTransfer consistently, and emit the declared FeeCollected event:

function collectFee() external onlyCollector {
uint256 wethCollection = i_weth.balanceOf(address(this));
uint256 ethCollection = address(this).balance;
// @> Use SafeERC20 wrapper for consistency and safety
if (wethCollection > 0) {
i_weth.safeTransfer(s_collector, wethCollection);
}
if (ethCollection > 0) {
(bool collected,) = payable(s_collector).call{value: ethCollection}("");
require(collected, "Fee collection failed!!!");
}
// @> Emit the declared but unused event
emit FeeCollected();
}
Updates

Lead Judging Commences

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