# User might be able to double withdraw during migration

During zkSync migration, a user could finalize an ERC20 withdrawal on the old L1ERC20Bridge and again on L1SharedBridge if their L2 batch number equals eraFirstPostUpgradeBatch, bypassing the legacy withdrawal check.

- Payout: Contest reward
- Severity: medium
- Program: zkSync
- Credited: oakcobalt
- Disclosed: 2024-05-29
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-03-zksync-findings/issues/34
- Library page: https://bugbountylibrary.com/r/c4%3A2024-03-zksync%3AM-04
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** L1SharedBridge.sol finalizeWithdrawal / L1ERC20Bridge.sol migration
**Root cause:** The _isEraLegacyWithdrawal check only verifies legacy bridge withdrawal status when _l2BatchNumber < eraFirstPostUpgradeBatch. A withdrawal included in the upgrade batch (batch number >= eraFirstPostUpgradeBatch) bypasses this check, allowing a second finalization on L1SharedBridge after the first on the legacy bridge.
**Steps:**
1. Step A: L1SharedBridge deployed and initialized with eraFirstPostUpgradeBatch.
2. UserA initiates an ERC20 withdrawal on L2 (old bridge).
3. Step B: Era upgrade occurs; UserA's withdrawal tx is included in batch number == eraFirstPostUpgradeBatch.
4. UserA calls finalizeWithdrawal on old L1ERC20Bridge.sol and receives funds (funds not yet migrated).
5. Step C: L1ERC20Bridge upgraded and funds migrated to L1SharedBridge.
6. UserA calls finalizeWithdrawal on L1SharedBridge.sol with same _l2BatchNumber; _isEraLegacyWithdrawal returns false, skipping legacyBridge.isWithdrawalFinalized check; UserA receives funds again.
**How found:** Auditor reviewed the migration process steps and the _isEraLegacyWithdrawal logic, noticing that the legacy withdrawal status check is skipped for batch numbers >= eraFirstPostUpgradeBatch, creating a window during upgrade where double finalization is possible.
**Exploitation:** Attacker times an ERC20 withdrawal to be included in the upgrade batch, finalizes on the legacy bridge before migration, then finalizes again on L1SharedBridge after migration, extracting double the withdrawal amount.
**Insight:** When a contract upgrade introduces a new bridge that checks legacy withdrawal status only for pre-upgrade batches, examine boundary conditions around the upgrade batch number—transactions included in the upgrade batch itself may bypass legacy checks, enabling double-spend.
