AirDropper

AI First Flight #5
Beginner FriendlyDeFiFoundry
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

`Deploy.s.sol` uses bare `transfer` with no post-deploy funding invariant, risking an empty or unfunded airdrop on zkSync

Description

  • The deployment script instantiates MerkleAirdrop and transfers USDC from the broadcaster to the new contract. It uses the standard IERC20 transfer return value without SafeERC20, and never asserts the airdrop’s final token balance.

  • Slither excludes script/ via filter_paths, so this path was not reported by static analysis. The script is in audit scope per README.

function run() public {
vm.startBroadcast();
MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
// @> No SafeERC20, no balance check, silent failure if transfer returns false
IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
vm.stopBroadcast();
}

Risk

Likelihood:

  • The broadcaster account lacks 100e6 USDC at broadcast time on zkSync Era.

  • USDC transfer returns false instead of reverting for insufficient balance (token-dependent behavior).

Impact:

  • An empty MerkleAirdrop is deployed on-chain; all user claims revert on safeTransfer.

  • Operations must redeploy or manually fund the contract, wasting gas and blocking the scheduled airdrop window.

Proof of Concept

// Simulate deploy where broadcaster has 0 USDC
function test_PoC_deployWithoutFunding() public {
uint256 balBefore = usdc.balanceOf(address(airdrop));
// broadcast run() with zero USDC balance
uint256 balAfter = usdc.balanceOf(address(airdrop));
assertEq(balAfter, balBefore); // contract deployed but unfunded
}

Recommended Mitigation

+ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
function run() public {
vm.startBroadcast();
MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
- IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
+ IERC20 usdc = IERC20(s_zkSyncUSDC);
+ usdc.safeTransferFrom(msg.sender, address(airdrop), s_amountToAirdrop);
+ require(usdc.balanceOf(address(airdrop)) >= s_amountToAirdrop, "Airdrop underfunded");
vm.stopBroadcast();
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!