AirDropper

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

Merkle tree uses Ethereum L1 addresses but contract deploys on zkSync Era where account abstraction wallets have different addresses, locking affected users out permanently

Root + Impact

Description

  • The protocol README states: "Our team is looking to airdrop 100 USDC tokens on the zkSync era chain to 4 lucky addresses based on their activity on the Ethereum L1." The Merkle tree is built from the recipients' Ethereum L1 addresses.

  • On zkSync Era, native Account Abstraction (AA) means user wallets are smart contracts. AA wallets derive their address deterministically per chain — the same user's wallet address on Ethereum mainnet is different from their address on zkSync Era. A recipient whose Ethereum address is in the Merkle tree but who uses an AA wallet on zkSync will have a different address on zkSync and cannot produce a valid claim.

// makeMerkle.js — tree built with Ethereum L1 addresses
const recipients = [
"0x20F41376c713072937eb02Be70ee1eD0D639966C", // Ethereum address
"0x277D26a45Add5775F21256159F089769892CEa5B", // Ethereum address
// ...
];
// If any of these users has an AA wallet, their zkSync address differs
// MerkleAirdrop.claim(account, ...) — account must match Ethereum address in tree

Risk

Likelihood:

  • zkSync Era actively promotes AA wallets. Any recipient using a smart contract wallet (Argent, zkSync native AA, or a Safe deployed on zkSync) will have a chain-specific address that differs from their Ethereum L1 address in the Merkle tree.

Impact:

  • Affected recipients cannot claim their allocation. Their tokens remain locked in the contract with no alternative recovery path — the immutable Merkle root cannot be updated post-deployment.

Proof of Concept

The test demonstrates that claim() with an address not matching the exact leaf in the Merkle tree fails, regardless of whether the caller controls both addresses:

function testDifferentChainAddressCannotClaim() public {
address ethereumAddress = collectorOne; // in the Merkle tree
address zkSyncAAAddress = makeAddr("zkSyncWallet"); // same user, different chain address
// zkSyncAAAddress is not in the Merkle tree — proof was built for ethereumAddress
vm.deal(zkSyncAAAddress, FEE);
vm.prank(zkSyncAAAddress);
// Even with a valid proof for collectorOne, using a different account fails
vm.expectRevert(MerkleAirdrop.MerkleAirdrop__InvalidProof.selector);
airdrop.claim{value: FEE}(zkSyncAAAddress, ALICE_AMOUNT, aliceProof);
// The only way to claim is using the Ethereum address exactly
// — impossible if the user's zkSync wallet has a different address
}

The proof is bound to the specific Ethereum address. If the user's zkSync address differs, no valid proof exists and the claim is permanently inaccessible.

Recommended Mitigation

Collect recipients' zkSync Era addresses (not their Ethereum L1 addresses) when building the Merkle tree, or explicitly document that recipients must use an EOA with the same address on both chains and not an AA wallet:

- // 4 users identified by Ethereum L1 activity
+ // 4 users — MUST use EOA or a wallet with same address on zkSync Era
const recipients = [
"0x20F41376c713072937eb02Be70ee1eD0D639966C",
// ...
];

Alternatively, allow recipients to register a zkSync claim address before the snapshot is finalized.

Updates

Lead Judging Commences

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

[H-04] Unable to receive airdrop due to account abstraction

## Description The users that use account abstraction wallets have different addresses across chains for the same account. ## Vulnerability Details In the docs is said: ```javascript "Our team is looking to airdrop 100 USDC tokens on the zkSync era chain to 4 lucky addresses based on their activity on the Ethereum L1. The Ethereum addresses are: 0x20F41376c713072937eb02Be70ee1eD0D639966C 0x277D26a45Add5775F21256159F089769892CEa5B 0x0c8Ca207e27a1a8224D1b602bf856479b03319e7 0xf6dBa02C01AF48Cf926579F77C9f874Ca640D91D" ``` The user can claim his/her USDC tokens through the `MerkleAirdrop::claim` function. This function requires `account, amount and proof array`. With the help of this three arguments the merkle proof will ensure that the caller is eligible to claim. But in the generated merkle root are used the Ethereum addresses of the lucky users. But the protocol will be deployed on the zkSync era chain. If any of them uses account abstraction wallet, this lucky user will not be able to claim his/her tokens. The account abstraction wallets have different addresses in the different chains for the same account. ## Impact The users that use account abstraction wallets have different addresses on the zkSync era chain. That means these users will not be able to claim their USDC tokens, because the merkle root will require another account address (this on Ethereum). ## Recommendations Ensure that the addresses in `makeMerkle` file for the lucky users are their addresses for the zkSync era chain.

Support

FAQs

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

Give us feedback!