Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Unconditional Fee Transfers Cause Failed Transactions and Gas Waste

Unchecked CollectFee Transfer

Description

  • In the collectFee() function, the normal behavior is to transfer any collected fees — both WETH and native ETH — from the contract to the designated s_collector address.

  • However, there is no check to ensure that the contract holds a non-zero WETH or ETH balance before attempting these transfers. This can result in failed transactions or wasted gas due to unnecessary external calls, especially when either balance is zero.

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!!!");
}

Risk

Likelihood:

  • This will occur every time collectFee() is called while either the WETH or ETH balance is zero.

  • It can also happen frequently in a farming environment where fee collection is triggered on a regular interval regardless of balance state.

Impact:

  • Wasted gas due to unnecessary token transfer or ETH call.

  • Potential for user confusion or reversion in tightly integrated scripts depending on the require statement.

Proof of Concept

Scenario: Contract holds 0 ETH, some WETH

Expected: WETH transferred, no error from ETH transfer

Actual: require fails if ETH call fails, even if WETH succeeded

function simulate() public {
snow.collectFee(); // fails if contract has 0 ETH
}

Recommended Mitigation

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!!!");
+ if (collection > 0) {
+ i_weth.transfer(s_collector, collection);
+ }
+ uint256 ethBalance = address(this).balance;
+ if (ethBalance > 0) {
+ (bool collected,) = payable(s_collector).call{value: ethBalance}("");
+ require(collected, "ETH fee collection failed");
+ }
}

This ensures external calls are only made when there’s something to transfer, improving efficiency and avoiding unnecessary failures.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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