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

All the funds can be stolen from MerkleAirdrop

Summary

User can steal all the funds because MerkleAidrop::claim is vulnarable

Vulnerability Details

One of the four lucky addresses which will be aidropped usdc tokens can call MerkleAidrop::claim as many times as they wish untill there are funds in the contract.

The MerkleProof in MerkleAidrop::claim checks if the account is eligible for that amount, but it doesn't check if the account has already claimed their reward.

function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
//@audit-issue - HIGH - what if one of the four accounts call it 4 times with 25 amount
//it would get the usdc of the other users
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert MerkleAirdrop__InvalidProof();
}
emit Claimed(account, amount);
i_airdropToken.safeTransfer(account, amount);
}

As can be seen in the test, the user can steal all the funds from the contract:

Paste this in MerkleAirdropTest.t.sol and run forge test --match-test testUserCanStealAllTheFunds -vvv

function testUserCanStealAllTheFunds() public {
uint256 startingBalance = token.balanceOf(collectorOne);
vm.deal(collectorOne, 4 * airdrop.getFee());
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();
uint256 endingBalance = token.balanceOf(collectorOne);
assertEq(endingBalance - startingBalance, amountToSend);
}

Impact

The user can steal all the funds from the contract.

Tools Used

Unit Tests

Manual Review

Recommendations

+ mapping(address => bool) addressHasClaimed;
+ error MerkleAirdrop_AlreadyClaimed();
function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
+ if (addressHasClaimed[account]) {
+ revert MerkleAirdrop_AlreadyClaimed();
+ }
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert MerkleAirdrop__InvalidProof();
}
+ addressHasClaimed[account] = true;
emit Claimed(account, amount);
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.