Snowman Merkle Airdrop

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

`collectFee` ignores WETH transfer return value

collectFee ignores WETH transfer return value

Impact

Low

Likelihood

Low

Description

Snow.collectFee() transfers WETH to the collector using i_weth.transfer() and ignores the returned boolean:

uint256 collection = i_weth.balanceOf(address(this));
i_weth.transfer(s_collector, collection);

Some ERC20 tokens return false instead of reverting on failed transfers. The contract already imports SafeERC20 and declares:

using SafeERC20 for IERC20;

but does not use safeTransfer() in collectFee().

Affected code:

  • src/Snow.sol:101

  • src/Snow.sol:103

Risk

If the configured WETH-like token returns false, collectFee() may continue as if the WETH transfer succeeded. This can cause fee collection to appear successful even though WETH was not transferred.

Impact:

  • failed WETH collection may be missed,

  • accounting and operational assumptions can become incorrect,

  • collector may only receive native ETH while WETH remains in the contract.

Proof of Concept

The following PoC shows that collectFee() can complete successfully even when the WETH transfer reports failure.

Add this test to test/AuditFuzz.t.sol:

contract FalseReturnWETH {
mapping(address => uint256) public balanceOf;
function mint(address receiver, uint256 amount) external {
balanceOf[receiver] += amount;
}
function transfer(address, uint256) external pure returns (bool) {
return false;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
balanceOf[from] -= amount;
balanceOf[to] += amount;
return true;
}
}
function test_CollectFeeIgnoresFailedWethTransfer() public {
FalseReturnWETH weth = new FalseReturnWETH();
address collector = makeAddr("collector");
Snow snow = new Snow(address(weth), 5, collector);
weth.mint(address(snow), 5 ether);
vm.prank(collector);
snow.collectFee();
assertEq(weth.balanceOf(address(snow)), 5 ether);
assertEq(weth.balanceOf(collector), 0);
}

Run:

forge test --match-test test_CollectFeeIgnoresFailedWethTransfer -vvv

Result:

[PASS] test_CollectFeeIgnoresFailedWethTransfer()

The test proves that the collector can call collectFee() without a revert, while the full WETH balance remains in the Snow contract and the collector receives no WETH.

Mitigation

Use SafeERC20.safeTransfer() instead of raw transfer().

Example fix:

function collectFee() external onlyCollector {
uint256 collection = i_weth.balanceOf(address(this));
i_weth.safeTransfer(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 about 4 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!