AirDropper

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

Fee-on-transfer or non-standard airdrop token under-delivers: claim transfers amount but the recipient receives less, breaking allocations

Assuming exact-amount delivery breaks allocations when the airdrop token is fee-on-transfer or rebasing

Description

claim() calls i_airdropToken.safeTransfer(account, amount) and assumes the recipient receives exactly amount. A fee-on-transfer or rebasing token delivers less than amount, so per-recipient accounting silently diverges from the Merkle-tree intent.

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); // @> assumes `amount` is received; FoT/rebasing delivers less

(src/MerkleAirdrop.sol:34-39)

Risk

Likelihood: Low

The vulnerability only manifests if the deployed i_airdropToken is a non-standard (fee-on-transfer or rebasing) ERC20. Most airdrops use a plain token, so this depends on a specific, less-common deployment choice.

Impact: Medium

Early claimers are under-paid relative to their leaf, the Claimed event over-reports the received amount, and the contract can run short before the final eligible recipients claim, leaving them unable to withdraw their full allocation.

Proof of Concept

With a 1% fee-on-transfer token, a recipient entitled to amount receives strictly less while the event still logs amount.

function test_feeOnTransferUnderpays() public {
airdrop.claim{value: airdrop.getFee()}(account, amount, proof);
// FoT token charged 1% on transfer
assertLt(token.balanceOf(account), amount); // received less than allocated
}

Recommended Mitigation

Document and restrict the airdrop to standard ERC20s, or measure the delivered balance delta and account on the actual received amount.

- i_airdropToken.safeTransfer(account, amount);
+ uint256 balBefore = i_airdropToken.balanceOf(account);
+ i_airdropToken.safeTransfer(account, amount);
+ uint256 received = i_airdropToken.balanceOf(account) - balBefore;
+ // require(received == amount, "non-standard token unsupported");
Updates

Lead Judging Commences

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