AirDropper

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

Address case inconsistency

Root + Impact

Description

  • The deployment script is designed to deploy the MerkleAirdrop contract and transfer the correct USDC token amount to it, using a consistent reference to the zkSync USDC token address throughout the deployment process.

  • The deployment script uses two different case variations of the same zkSync USDC address - one stored in the state variable s_zkSyncUSDC and another hardcoded in the run() function with different character casing, creating inconsistency that could lead to deployment errors or transferring tokens to an unintended address.

// Deploy.s.sol
contract Deploy is Script {
// @> First address with uppercase 'D' at position 4
address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
uint256 public s_amountToAirdrop = 4 * (25 * 1e6);
function run() public {
vm.startBroadcast();
// Uses state variable (uppercase D)
MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
// @> Second address with lowercase 'd' at position 4
// @> Inconsistent casing could cause issues with EIP-55 checksum validation
IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
// ^ lowercase 'd' here instead of 'D'
vm.stopBroadcast();
}
function deployMerkleDropper(bytes32 merkleRoot, IERC20 zkSyncUSDC) public returns (MerkleAirdrop) {
return (new MerkleAirdrop(merkleRoot, zkSyncUSDC));
}
}

Risk

Likelihood:

  • Developers manually typing addresses during deployment or modifications will introduce case inconsistencies

  • EIP-55 checksum validation tools and some blockchain explorers strictly validate address checksums and may reject incorrectly cased addresses

  • The deployment script executes automatically without manual verification of address consistency during the broadcast phase

  • Copy-paste errors when updating addresses from different sources (blockchain explorers, documentation) preserve inconsistent casing

Impact:

  • Deployment fails when strict EIP-55 validation is enabled in the development environment or deployment tooling

  • Potential transfer of tokens to a different address if the case difference represents a different checksum-valid address (extremely unlikely but theoretically possible)

  • Code maintainability issues where future developers cannot easily verify the addresses match without converting them to checksummed format

  • Confusion during audits and code reviews when comparing addresses across the codebase

Proof of Concept

function testAddressInconsistency() public {
address addr1 = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4; // From s_zkSyncUSDC
address addr2 = 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4; // From run() function
assertEq(addr1, addr2);
}
// Example EIP-55 checksum validation (in JavaScript/TypeScript tooling)
const Web3 = require('web3');
const web3 = new Web3();
// Correct checksummed address
const correctAddress = '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4';
// Address from code with different casing
const codeAddress = '0x1D17CbCf0D6d143135be902365d2e5E2a16538d4';
// Some strict tools will reject if not matching checksum
if (!web3.utils.checkAddressChecksum(codeAddress)) {
throw new Error('Invalid address checksum');
}

Recommended Mitigation

Use a single constant with the correct EIP-55 checksummed address throughout the deployment script:

contract Deploy is Script {
- address public s_zkSyncUSDC = 0x1D17CbCf0D6d143135be902365d2e5E2a16538d4;
+ address public constant ZKSYNC_USDC = 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4;
bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
- uint256 public s_amountToAirdrop = 4 * (25 * 1e6);
+ uint256 public constant AMOUNT_TO_AIRDROP = 4 * (25 * 1e6);
function run() public {
vm.startBroadcast();
- MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(s_zkSyncUSDC));
+ MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(ZKSYNC_USDC));
- IERC20(0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4).transfer(address(airdrop), s_amountToAirdrop);
+ IERC20(ZKSYNC_USDC).transfer(address(airdrop), AMOUNT_TO_AIRDROP);
vm.stopBroadcast();
}
function deployMerkleDropper(bytes32 merkleRoot, IERC20 zkSyncUSDC) public returns (MerkleAirdrop) {
return (new MerkleAirdrop(merkleRoot, zkSyncUSDC));
}
}

Additional recommendation: Verify checksum during deployment

contract Deploy is Script {
address public constant ZKSYNC_USDC = 0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4;
bytes32 public s_merkleRoot = 0xf69aaa25bd4dd10deb2ccd8235266f7cc815f6e9d539e9f4d47cae16e0c36a05;
uint256 public constant AMOUNT_TO_AIRDROP = 4 * (25 * 1e6);
function run() public {
+ // Verify address is not zero
+ require(ZKSYNC_USDC != address(0), "Invalid USDC address");
+
vm.startBroadcast();
MerkleAirdrop airdrop = deployMerkleDropper(s_merkleRoot, IERC20(ZKSYNC_USDC));
+
+ // Verify token has correct interface before transfer
+ require(IERC20(ZKSYNC_USDC).totalSupply() > 0, "Invalid token");
+
IERC20(ZKSYNC_USDC).transfer(address(airdrop), AMOUNT_TO_AIRDROP);
vm.stopBroadcast();
}
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 3 days 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!