AirDropper

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

Account abstraction wallets cannot claim — L1 addresses differ from zkSync Era addresses

Title: Account abstraction wallets cannot claim — L1 addresses differ from zkSync Era addresses
Severity: High
Impact: Users with AA wallets are permanently unable to claim their airdrop allocation.
Likelihood: High — applies to any of the 4 recipients using account abstraction wallets.
Reference Files: script/Deploy.s.sol:9, makeMerkle.js:11-15

Description

The merkle tree is built with Ethereum L1 addresses, but claims happen on zkSync Era where account abstraction wallets have different addresses than their L1 counterparts. An AA wallet user's L1 address does not match their zkSync address, so the claim function cannot be called from the address encoded in the merkle leaf. The vulnerable setup:

// Deploy.s.sol — L1 addresses hardcoded into merkle tree
bytes32 public s_merkleRoot = 0xf69aaa25...;
// Tree built from Ethereum L1 addresses:
// 0x20F41376c713072937eb02Be70ee1eD0D639966C (L1 address)
// 0x277D26a45Add5775F21256159F089769892CEa5B (L1 address)
// ...
// MerkleAirdrop.sol — claim verified against L1 address leaf
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) revert MerkleAirdrop__InvalidProof();

The merkle proof is tied to the L1 address, but the user's zkSync Era address from their AA wallet is different. The proof can never verify from the AA wallet's zkSync address.

Risk

Impact: High. Any of the 4 recipients using account abstraction wallets permanently lose their 25 USDC allocation. The tokens sit in the contract unclaimable.
Likelihood: High. Account abstraction is widely used on zkSync Era. The merkle tree was built with L1 addresses per the contest documentation.
A user with Argent or Safe AA wallet on zkSync sees their L1 address listed as eligible but cannot produce a valid merkle proof from their zkSync address — 25 USDC locked forever.

Proof of Concept

function testAAWalletCannotClaim() public {
// Alice's L1 address: 0x20F41376c713072937eb02Be70ee1eD0D639966C
// Alice's zkSync AA address: 0xDifferentAddressOnZKsync...
address aliceL1 = 0x20F41376c713072937eb02Be70ee1eD0D639966C;
address aliceZKsync = makeAddr("aliceAA"); // different on zkSync
// Merkle proof built with aliceL1 — cannot verify from aliceZKsync
vm.prank(aliceZKsync);
vm.expectRevert(MerkleAirdrop.MerkleAirdrop__InvalidProof.selector);
airdrop.claim{value: airdrop.getFee()}(aliceZKsync, amountToCollect, proof);
// Proof requires aliceL1 — but aliceZKsync has no way to produce
// a valid proof for a different address
}

The AA wallet user's zkSync address cannot match the L1 address encoded in the merkle tree — the claim is impossible.

Recommended Mitigation

Build the merkle tree using the recipients' zkSync Era addresses instead of their Ethereum L1 addresses:

const values = [
["0xZK_SYNC_ADDRESS_1", amount], // zkSync-era addresses
["0xZK_SYNC_ADDRESS_2", amount],
...
]

Ensure all eligible addresses are collected for the zkSync Era chain where the airdrop contract is deployed, accounting for account abstraction wallet address derivation differences.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day 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!