AirDropper

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

No Contract Existence Check on Token Address — Silent Failure on Wrong Deployment Chain

[MEDIUM-3] No Contract Existence Check on Token Address — Silent Failure on Wrong Deployment Chain

File: src/MerkleAirdrop.sol constructor (line 25–28), script/Deploy.s.sol

Summary

The constructor accepts an arbitrary IERC20 airdropToken without verifying that the address contains deployed bytecode. On zkSync, if the USDC contract is not deployed at the expected address, or if Deploy.s.sol is accidentally run on a different chain (e.g., mainnet Ethereum where that address holds a different contract or EOA), the MerkleAirdrop contract will be silently initialized with a non-contract token address. All subsequent safeTransfer calls will either revert with obscure errors or silently succeed (depending on OpenZeppelin's SafeERC20 behavior with zero-code addresses).

Vulnerability Details

constructor(bytes32 merkleRoot, IERC20 airdropToken) Ownable(msg.sender) {
i_merkleRoot = merkleRoot;
i_airdropToken = airdropToken; // No check: is this a contract? Does it implement IERC20?
}

In OZ v5 SafeERC20, calls to addresses with no code will revert due to the Address.functionCall check — however, this failure happens at runtime (during claim()) rather than at deployment. The contract is deployed "successfully" but is entirely non-functional, with no warning to the deployer.

Additionally, the Deploy.s.sol script has mismatched USDC addresses (MEDIUM-1 from first audit), meaning the constructor could receive one USDC address while transfer() is called on a different one — the contract is funded with the wrong token or not funded at all.

Impact

  • Contract deployed in broken state with no immediate indication.

  • Users call claim(), pay the fee, and always revert.

  • ETH fees accumulate but tokens are never distributed.

  • Silent misconfiguration is difficult to diagnose post-deployment on zkSync.

Tools Used

  • Manual analysis

  • OZ SafeERC20 v5 code review

Recommendations

Add a deployment-time sanity check:

+ error MerkleAirdrop__InvalidToken();
+
constructor(bytes32 merkleRoot, IERC20 airdropToken) Ownable(msg.sender) {
+ if (address(airdropToken).code.length == 0) revert MerkleAirdrop__InvalidToken();
i_merkleRoot = merkleRoot;
i_airdropToken = airdropToken;
}

And in Deploy.s.sol, add post-deployment assertions:

// After deployment
require(address(airdrop.getAirdropToken()) == s_zkSyncUSDC, "Wrong token");
require(IERC20(s_zkSyncUSDC).balanceOf(address(airdrop)) == s_amountToAirdrop, "Not funded");
Updates

Lead Judging Commences

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