AirDropper

AI First Flight #5
Beginner FriendlyDeFiFoundry
EXP
View results
Submission Details
Severity: low
Valid

FEE is a hardcoded 1e9 wei (1 gwei), negligible and unable to deter the unlimited-replay drain

Hardcoded 1 gwei FEE is economically negligible and neither monetizes the airdrop nor deters abuse

Description

FEE is a compile-time constant of 1e9 wei (1 gwei). This amount is trivially small, so it provides no meaningful revenue and does not act as a barrier to the unbounded replay drain.

uint256 private constant FEE = 1e9; // @> 1 gwei: negligible, does not monetize or gate abuse

(src/MerkleAirdrop.sol:15)

Risk

Likelihood: Low

The fee is collected on every claim, so the design intent (some monetization / anti-spam gate) is always undermined; however, on its own this is an economic-design weakness rather than an exploit.

Impact: Low

At 1 gwei per call the protocol earns effectively nothing from claims, and the fee does not raise the cost of the AD-01 replay loop enough to discourage draining the airdrop. The fee gives a false impression of an abuse cost without providing one.

Proof of Concept

Thousands of replayed claims cost the attacker a rounding-error amount of ETH in fees.

function test_feeDoesNotDeter() public {
uint256 fee = airdrop.getFee(); // 1e9 wei
assertEq(fee, 1e9);
// 10_000 replays cost 10_000 * 1e9 = 1e13 wei = 0.00001 ETH total
assertLt(fee * 10_000, 1e15);
}

Recommended Mitigation

Make the fee configurable to a meaningful value, or remove it entirely since it does not gate abuse.

- uint256 private constant FEE = 1e9;
+ uint256 private immutable i_fee;
+ // set i_fee in the constructor to a meaningful, governance-chosen value,
+ // or remove the fee mechanism if it serves no purpose.
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[L-01] It Can Be Economically Impractical for the Contract Owner to Claim Airdrop Fees

## Description The low `MerkleAirdrop::FEE` (1 Gwei) makes it economically impractical (ETH-wise) for the owner to claim fees, even with the low gas cost of the zkSync chain. The fee should either be removed or increased to make it economically practical to claim by the owner. ## Vulnerability Details The low `MerkleAirdrop::FEE` (1 Gwei) makes it economically impractical (ETH-wise) for the owner to claim fees, even with the low gas cost of the zkSync chain. The gas cost for the owner to call `MerkleAirdrop::claimFees` is 30,479 gas units. Using the average zkSync gas price of 0.02 Gwei, the effective total gas cost would be ~609 Gwei or 0.000000609 Ether. For it to be economically sensible to claim fees (using the current fee price of 1 Gwei), there would need to be greater than or equal to 609 successful airdrop claims to meet or exceed the gas cost. Compared to the current number of addresses that are a part of the merkle tree, there is a significant discrepancy. <details> <summary>POC</summary> ### `MerkleAirdropTest.t.sol` ```javascript address owner = vm.addr(1); ... // deploy contracts as an EOA instead of contract function setUp() public { vm.startPrank(owner); token = new AirdropToken(); airdrop = new MerkleAirdrop(merkleRoot, token); token.mint(owner, amountToSend); token.transfer(address(airdrop), amountToSend); vm.stopPrank(); } ... function test_GasExeceedsFeeClaimAmount() public { uint256 assumedZksyncGasPrice = 0.00000000002 ether; // 0.02 Gwei uint256 airdropFee = airdrop.getFee(); vm.deal(collectorOne, airdropFee); vm.startPrank(collectorOne); airdrop.claim{ value: airdropFee }(collectorOne, amountToCollect, proof); vm.stopPrank(); // assert the contract and owner have the proper balances assertEq(address(airdrop).balance, airdropFee); assertEq(owner.balance, 0); vm.startPrank(owner); uint256 gasBeforeClaim = gasleft(); airdrop.claimFees(); uint256 gasAfterClaim = gasleft(); vm.stopPrank(); // assert the contract has had its fees claimed by owner assertEq(address(airdrop).balance, 0); // assert that the amount of gas spent is greater than the fees obtained (in wei) uint256 gasDelta = gasBeforeClaim - gasAfterClaim; assertGt((gasDelta * assumedZksyncGasPrice), owner.balance); } ``` ### Run Test ```bash forge test --match-test test_GasExeceedsFeeClaimAmount --gas-report -vvvv ``` #### Example Output ```bash Ran 1 test for test/MerkleAirdropTest.t.sol:MerkleAirdropTest [PASS] test_GasExeceedsFeeClaimAmount() (gas: 129297) Traces: [129297] MerkleAirdropTest::test_GasExeceedsFeeClaimAmount() │ ... ├─ [0] VM::assertGt(620640000000 [6.206e11], 1000000000 [1e9]) [staticcall] │ └─ ← () └─ ← () Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.38ms (690.20µs CPU time) | src/MerkleAirdrop.sol:MerkleAirdrop contract | | | | | | | -------------------------------------------- | --------------- | ----- | ------ | ----- | ------- | | Deployment Cost | Deployment Size | | | | | | 540806 | 2502 | | | | | | Function Name | min | avg | median | max | # calls | | claim | 59686 | 59686 | 59686 | 59686 | 1 | | claimFees | 30479 | 30479 | 30479 | 30479 | 1 | <--- | getFee | 225 | 225 | 225 | 225 | 1 | ... Ran 1 test suite in 5.26ms (2.38ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests) ``` </details> ## Impact There exists an economic disinsentive for the owner to claim fees from the contract. ## Recommendations Either remove the need for a fee to be paid during a claim or increase the claim fee to make it economically practical.

Support

FAQs

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

Give us feedback!