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

The same address can claim multiple times from 'MerkleAirdrop.sol:claim'

Summary

The same address can claim multiple times from the 'MerkleAirdrop.sol:claim' function and take more USDC than they're supposed to.

Vulnerability Details

In the 'MerkleAirdrop.sol' contract there is nothing keeping track if an address has already claimed their airdrop. This means that the same address can claim the airdrop multiple times.

Impact

The test below passes showing that the same address can claim multiple times.

function testUsersCanClaim() public {
uint256 startingBalance = token.balanceOf(collectorOne);
vm.deal(collectorOne, airdrop.getFee());
vm.startPrank(collectorOne);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
vm.stopPrank();
vm.deal(collectorOne, airdrop.getFee());
vm.startPrank(collectorOne);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
vm.stopPrank();
uint256 endingBalance = token.balanceOf(collectorOne);
assertEq(endingBalance - startingBalance, amountToCollect * 2);
}

Tools Used

--Foundry

Recommendations

It is recommended to add logic to check if a user has already claimed the airdrop

contract MerkleAirdrop is Ownable {
using SafeERC20 for IERC20;
error MerkleAirdrop__InvalidFeeAmount();
error MerkleAirdrop__InvalidProof();
error MerkleAirdrop__TransferFailed();
uint256 private constant FEE = 1e9;
IERC20 private immutable i_airdropToken;
bytes32 private immutable i_merkleRoot;
+ mapping(address => bool) public hasClaimed;
event Claimed(address account, uint256 amount);
event MerkleRootUpdated(bytes32 newMerkleRoot);
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
constructor(bytes32 merkleRoot, IERC20 airdropToken) Ownable(msg.sender) {
i_merkleRoot = merkleRoot;
i_airdropToken = airdropToken;
}
function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
+ require(!hasClaimed[account], "You have already claimed the airdrop!");
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert MerkleAirdrop__InvalidProof();
}
+ hasClaimed[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.