The same address can claim multiple times from the 'MerkleAirdrop.sol:claim' function and take more USDC than they're supposed to.
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.
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);
}
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);
}