AirDropper

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

MerkleAirdrop::claim uses strict equality msg.value != FEE, permanently blocking eligible users who overpay by any amount

Root + Impact

Normal behavior

claim() requires callers to pay a fee of 1e9 wei (1 Gwei) in order to claim their USDC allocation. The fee check is intended to ensure the protocol collects its fee before processing the claim.

Description

The issue

The fee validation uses strict equality (!=) rather than a minimum threshold (<). Any caller who sends even 1 wei more than exactly 1e9 will have their transaction reverted with MerkleAirdrop__InvalidFeeAmount. This is a denial-of-service against eligible users because:

  • Wallets and front-ends sometimes add small buffer amounts to msg.value to account for gas estimation rounding

  • Smart contract wallets and account abstraction systems may forward rounded amounts

  • A user manually constructing a transaction with 1e9 + 1 wei is permanently locked out with no recourse — they cannot reclaim their allocation by sending exactly 1e9 on a retry without first understanding this strict requirement

uint256 private constant FEE = 1e9;
function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
// @> Strict equality: any overpayment causes permanent revert for that call
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
// ...
}

Risk

Likelihood:

  • A user sends msg.value = 2e9 (expecting to be safe by sending more) — their transaction reverts and they cannot access their allocation until they understand the strict requirement

  • A front-end wallet adds a 1-wei buffer for gas estimation purposes, silently blocking every single user of that front-end

Impact:

  • Eligible users are denied their USDC allocation through no fault of their own

  • The contract continues to hold tokens that rightfully belong to blocked users with no recovery mechanism available to them

  • Protocol reputation is damaged when users report that claim() inexplicably reverts

Proof of Concept

Add this test to test/MerkleAirdropTest.t.sol and run forge test --match-test test_OverpaymentBlocksClaim -vvv:

function test_OverpaymentBlocksClaim() public {
// Give collectorOne slightly more than the fee
uint256 overpaidFee = airdrop.getFee() + 1; // 1e9 + 1 wei
vm.deal(collectorOne, overpaidFee);
vm.startPrank(collectorOne);
// This reverts with MerkleAirdrop__InvalidFeeAmount even though
// the caller is sending MORE than the required fee
vm.expectRevert(MerkleAirdrop.MerkleAirdrop__InvalidFeeAmount.selector);
airdrop.claim{ value: overpaidFee }(collectorOne, amountToCollect, proof);
vm.stopPrank();
// collectorOne receives nothing despite being eligible
assertEq(token.balanceOf(collectorOne), 0);
}

Recommended Mitigation

Change the fee check to a minimum threshold. Optionally refund excess ETH to the caller.

function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
- if (msg.value != FEE) {
+ if (msg.value < FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
+ // Refund any excess payment
+ if (msg.value > FEE) {
+ (bool refunded,) = payable(msg.sender).call{ value: msg.value - FEE }("");
+ if (!refunded) revert MerkleAirdrop__TransferFailed();
+ }
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert MerkleAirdrop__InvalidProof();
}
emit Claimed(account, amount);
i_airdropToken.safeTransfer(account, amount);
}
Updates

Lead Judging Commences

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