AirDropper

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

Deploy Script Funds Airdrop with Wrong Token Address

Deploy Script Funds Airdrop with Wrong Token Address

The MerkleAirdrop constructor receives s_zkSyncUSDC (0x1D17CbCf0D6d143135**b**e...) as its token, but the transfer call on the next line uses a different address (0x1d17CBcF0D6D143135**a**E...) — the addresses differ at nibble 18 (b vs a), confirmed by lowercasing both.

The airdrop contract is therefore funded from a different contract than the one it expects, leaving it with zero claimable balance and causing every claim() call to revert.

Description

  • The expected behavior is that the deployment script initializes the MerkleAirdrop contract with a specific ERC20 token address and then funds the airdrop contract with that same token so users can successfully claim their allocations.

  • However, the deployment script uses two different token addresses. The MerkleAirdrop contract is deployed with the token stored in s_zkSyncUSDC, while the script funds the contract using a hardcoded address that differs by one nibble. As a result, the airdrop contract expects one token but receives another.


    Because the airdrop contract checks and transfers the token defined in its constructor, it will hold zero balance of the expected token, causing every claim() execution to revert due to insufficient balance.

contract Deploy is Script {
address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
function run() public {
vm.startBroadcast();
MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
// @> Funds are sent using a different token address
IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4)
.transfer(address(airdrop), s_amountToAirdrop);
vm.stopBroadcast();
}
}

Risk

Likelihood: High

  • The issue originates from a deterministic error in the deployment script where a hardcoded token address differs from the constructor token address. Any deployment performed using this script will consistently fund the contract with the wrong token.

  • Since the deployment script executes this logic unconditionally, the vulnerability will occur every time the script is used, making the likelihood of occurrence very high.

Impact: Medium

  • The airdrop contract is funded with a different token than the one configured in the constructor. As a result, the contract holds zero balance of the expected token, causing every claim() call to revert.

  • This effectively breaks the entire airdrop distribution, preventing all eligible users from claiming their tokens. Additionally, the tokens transferred from the incorrect contract may become permanently locked in the airdrop contract if no recovery mechanism exists.

Recommended Mitigation

Use the same token address used in the constructor when funding the airdrop contract.

- IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
+ IERC20(s_zkSyncUSDC).transfer(address(airdrop), s_amountToAirdrop);

This guarantees that the contract receives the correct token used during initialization.

Updates

Lead Judging Commences

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

[H-01] Address of USDC token in `Deploy.s.sol` is wrong causing the claiming process to fail

## Description The `s_zkSyncUSDC` address in `Deploy.s.sol` is incorrectly set, leading to a failure in the claiming process. This error results in funds being stuck in the `MerkleAirdrop` contract due to the immutability of the token address. ## Impact All funds become permanently trapped in the `MerkleAirdrop` contract, rendering them inaccessible for claiming or transfer. **Proof of Concept:** To demonstrate the issue, a test contract can be added and executed using the following command: `forge test --zksync --rpc-url $RPC_ZKSYNC --mt testDeployOnZkSync` Use the RPC URL `https://mainnet.era.zksync.io` for testing. <details> <summary>Proof Of Code</summary> ```javascript // SPDX-License-Identifier: MIT pragma solidity 0.8.24; import { MerkleAirdrop, IERC20 } from "../src/MerkleAirdrop.sol"; import { Test, console2 } from "forge-std/Test.sol"; contract MerkleAirdropTest is Test { MerkleAirdrop public s_airdrop; uint256 s_amountToCollect = (25 * 1e6); // 25.000000 address s_collectorOne = 0x20F41376c713072937eb02Be70ee1eD0D639966C; bytes32 s_proofOne = 0x32cee63464b09930b5c3f59f955c86694a4c640a03aa57e6f743d8a3ca5c8838; bytes32 s_proofTwo = 0x8ff683185668cbe035a18fccec4080d7a0331bb1bbc532324f40501de5e8ea5c; bytes32[] s_proof = [s_proofOne, s_proofTwo]; address public deployer; // From Deploy.t.sol bytes32 public s_merkleRoot = 0x3b2e22da63ae414086bec9c9da6b685f790c6fab200c7918f2879f08793d77bd; address public s_zkSyncUSDC = 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4; uint256 public s_amountToAirdrop = 4 * (25 * 1e6); function setUp() public { deployer = makeAddr("deployer"); deal(0x1D17CbCf0D6d143135be902365d2e5E2a16538d4, deployer, 100 * 1e6); vm.deal(s_collectorOne, 100 ether); } function testDeployOnZkSync() public { if (block.chainid != 324) { return; } vm.startPrank(deployer); // From here there is the code from run() s_airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC)); // Send USDC -> Merkle Air Dropper IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(s_airdrop), s_amountToAirdrop); // end code from run vm.stopPrank(); vm.startPrank(s_collectorOne); s_airdrop.claim{ value: s_airdrop.getFee() }(s_collectorOne, s_amountToCollect, s_proof); vm.stopPrank(); } function deployMerkleDropper(bytes32 merkleRoot, IERC20 zkSyncUSDC) public returns (MerkleAirdrop) { return (new MerkleAirdrop(merkleRoot, zkSyncUSDC)); } } ``` </details> ## Recommendations To resolve the issue, update the s_zkSyncUSDC address in Deploy.s.sol to the correct value: ```diff - address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4; + address public s_zkSyncUSDC = 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4; ```

Support

FAQs

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

Give us feedback!