AirDropper

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

Strict equality fee check in MerkleAirdrop::claim() causes legitimate transactions to revert when wallets send slightly more than the required fee

Description

  • The claim() function requires callers to pay exactly FEE (1 Gwei) as a claim fee, validated using a strict equality check against msg.value.

  • Because the check uses != instead of <, any transaction that sends even 1 wei above the required amount is rejected. Wallets, scripts, and front-ends that round or estimate ETH values — a common behaviour — will consistently produce values that differ from the exact 1 Gwei constant, causing legitimate claim attempts to revert and leaving eligible users unable to claim without sending the exact byte-perfect amount.

uint256 private constant FEE = 1e9; // 1 Gwei
function claim(address account, uint256 amount, bytes32[] calldata merkleProof)
external payable {
// @> Strict equality — msg.value of 1e9 + 1 wei reverts
// @> No refund mechanism exists for any overpayment
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
...
}

Risk

Likelihood:

  • A user's wallet or front-end estimates the ETH value and rounds to the nearest unit, producing a msg.value that is 1–1000 wei above FEE — the transaction reverts despite the user intending to pay the correct fee

  • A developer calling the function programmatically uses 1 gwei + gasleft() or any arithmetic that adds any non-zero amount to the fee constant, causing every such call to revert unconditionally

Impact:

  • Eligible addresses are unable to claim their airdrop allocation despite paying more than the required fee, blocking legitimate access to the protocol

  • Any ETH sent above the exact fee is not lost (the revert refunds the caller), but the user must retry with a precise value — an unreasonable requirement that degrades usability and trust

Proof of Concept

The strict equality check means there is only one valid msg.value out of an infinite range of possible inputs. Any deviation — even a single wei above the fee — triggers the same revert as sending nothing at all. The following scenarios all result in a revert despite the caller's intent to pay:

// Conceptual scenarios — all of these revert:
//
// 1. User sends 1 wei too much (wallet rounding):
// airdrop.claim{value: 1e9 + 1}(account, amount, proof);
// → reverts with MerkleAirdrop__InvalidFeeAmount
//
// 2. User sends 1 wei too little (wallet rounding):
// airdrop.claim{value: 1e9 - 1}(account, amount, proof);
// → reverts with MerkleAirdrop__InvalidFeeAmount
//
// 3. Script adds buffer for safety:
// airdrop.claim{value: 1e9 * 2}(account, amount, proof);
// → reverts with MerkleAirdrop__InvalidFeeAmount
//
// 4. Only this exact value succeeds:
// airdrop.claim{value: 1e9}(account, amount, proof);
// → passes fee check
//
// There is no way to overpay and still succeed.
// The contract accepts exactly one value out of 2^256 possible inputs.

Recommended Mitigation

Replace the strict equality check with a minimum fee check using <. This accepts any msg.value at or above the required fee. Optionally, refund any excess ETH to the caller to keep the contract balance predictable and prevent unintended accumulation of overpayments.

function claim(address account, uint256 amount, bytes32[] calldata merkleProof)
external payable {
- if (msg.value != FEE) {
- revert MerkleAirdrop__InvalidFeeAmount();
- }
+ if (msg.value < FEE) {
+ revert MerkleAirdrop__InvalidFeeAmount();
+ }
+ // Refund any overpayment to the caller
+ 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();
s_hasClaimed[account] = true;
emit Claimed(account, amount);
i_airdropToken.safeTransfer(account, amount);
}
Updates

Lead Judging Commences

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