AirDropper

AI First Flight #5
Beginner FriendlyDeFiFoundry
EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

No ERC20 recovery path: unclaimed or misconfigured airdrop tokens are locked in MerkleAirdrop forever

Root + Impact

Description

MerkleAirdrop holds the airdrop tokens, but it has no owner function to recover ERC20 tokens: i_airdropToken and i_merkleRoot are immutable, and the only owner function, claimFees, withdraws ETH.

// @> the only owner-only function moves ETH; there is no way to move ERC20 out except claim()
function claimFees() external onlyOwner {
(bool succ,) = payable(owner()).call{ value: address(this).balance }("");
if (!succ) {
revert MerkleAirdrop__TransferFailed();
}
}

Any tokens that cannot be claimed stay in the contract forever. That includes allocations that are never claimed, tokens sent by mistake, and the full balance whenever the root or token configuration is wrong.

Risk

Likelihood:

  • Some recipients of an airdrop commonly never claim. Configuration mistakes also happen; this codebase's own deploy script has two of them (root amount decimals and token address).

Impact:

  • Unclaimed or misconfigured funds are permanently locked, and the owner has no way to remediate without redeploying and re-funding.

Proof of Concept

Code inspection: MerkleAirdrop exposes only claim, claimFees, and view functions. No path transfers i_airdropToken, or any other ERC20, except claim, which requires a valid proof for the exact leaf amount.

Recommended Mitigation

Add an owner-only recovery that can only be used after a claim deadline, so it cannot be used to rug users during the claim window:

+ uint256 private immutable i_claimDeadline;
+
+ function recoverTokens(IERC20 token, address to) external onlyOwner {
+ require(block.timestamp > i_claimDeadline, "claim window open");
+ token.safeTransfer(to, token.balanceOf(address(this)));
+ }
Updates

Lead Judging Commences

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