Snowman Merkle Airdrop

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

[M-01] Snow contract accepts Ether via buySnow but lacks a withdrawal function, locking funds permanently

Root + Impact: The Snow contract accepts native Ether via its payable buySnow function but lacks any public or administrative withdraw function, causing all deposited Ether to be permanently locked in the contract.

Description

  • The Snow contract allows users to purchase tokens by sending Ether through the buySnow function:

// src/Snow.sol
function buySnow(uint256 amount) external payable {
...
_mint(msg.sender, amount);
}

While the contract successfully receives and accumulates Ether from users, there is no function (such as withdrawETH() or sweepFees()) implemented in src/Snow.sol that allows the owner or designated collector (s_collector) to withdraw the contract's Ether balance. As a result, all ETH sent to purchase Snow tokens becomes permanently trapped in the contract bytecode without any recovery mechanism.

Risk

Likelihood: High

  • Reason 1: Any user purchasing tokens via buySnow automatically transfers Ether to the contract balance during normal operation.

  • Reason 2: The issue is built directly into the contract's architecture and triggers on every successful call to buySnow.

Impact: Medium

  • Impact 1: Irrecoverable loss of protocol revenue, as all ETH paid for token purchases cannot be extracted or utilized by the protocol team or collector.

  • Impact 2: Permanent lockup of native assets within the contract address.

Proof of Concept

PoC Explanation: The following Proof of Concept in Foundry demonstrates how Ether sent during buySnow becomes trapped without any mechanism to extract it:

  1. Setup: A user sends 10 ETH to snow.buySnow{value: 10 ether}(10).

  2. Execution: The contract receives the 10 ETH and mints tokens to the user.

  3. Assertion: The balance of address(snow) increases to 10 ETH, but no function exists on the Snow contract interface to transfer or withdraw this balance to the owner or collector.

function test_PoC_Snow_LockedEther() public {
vm.deal(user, 10 ether);
// User buys tokens sending 10 ETH
vm.prank(user);
snow.buySnow{value: 10 ether}(10);
// Ether is now stuck in the Snow contract
assertEq(address(snow).balance, 10 ether);
// No withdraw function exists on Snow to recover the 10 ETH
}

Recommended Mitigation

Mitigation Explanation: Implement an explicit withdrawal function restricted to the contract owner (or the s_collector address) that allows transferring the accumulated Ether balance to an authorized account.

+ error Snow__TransferFailed();
+ function withdrawETH() external onlyOwner {
+ uint256 balance = address(this).balance;
+ (bool success, ) = payable(s_collector).call{value: balance}("");
+ if (!success) {
+ revert Snow__TransferFailed();
+ }
+ }
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!