# Synchronization Issue Between L1 and L2 Upgrades

A failed L2 system-contract upgrade tx still clears l2SystemContractsUpgradeTxHash on L1 and bumps protocolVersion, desyncing recorded version from actual L2 state.

- Payout: Contest reward
- Severity: medium
- Program: zkSync
- Credited: anon
- Disclosed: 2024-02-29
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-10-zksync-findings/issues/214
- Library page: https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AM-16
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:business-logic

**Target:** L1 governance/upgrade contracts (DefaultUpgrade, BaseZkSyncUpgrade, Executor) and L2 bootloader
**Root cause:** Executor.executeBatches deletes s.l2SystemContractsUpgradeTxHash and advances protocolVersion based only on the upgrade tx hash matching, ignoring the success=false L2-to-L1 log emitted when the L2 upgrade tx fails in the bootloader.
**Steps:**
1. Governance::execute >> _execute >> DiamondProxy::fallback >> Admin::executeUpgrade >> diamondCut >> _initializeDiamondCut >> DefaultUpgrade::upgrade
2. DefaultUpgrade::upgrade >> BaseZkSyncUpgrade::_setL2SystemContractUpgrade stores tx hash in s.l2SystemContractsUpgradeTxHash and sets new protocolVersion
3. L2 bootloader processes txType=254, sends canonicalL1TxHash to L1, calls processL1Tx; on failure (e.g. OOG) it does not revert but emits L2->L1 log with `success = false`
4. On L1, Executor._processL2Logs checks _expectedSystemContractUpgradeTxHash == canonicalL1TxHash only, not the success flag
5. executeBatches deletes s.l2SystemContractsUpgradeTxHash and s.l2SystemContractsUpgradeBatchNumber regardless of outcome, leaving protocolVersion incremented with no actual L2 upgrade
**How found:** Code-reading of the upgrade call chain and bootloader txType=254 handling; noticed the bootloader emits success=false without reverting, and that Executor only matches the tx hash, never the success flag, before clearing upgrade state.
**Insight:** When a cross-domain upgrade/message is 'fire-and-forget' (logged, not reverted), check whether the receiving side's state machine consumes only the identifier (tx hash) rather than the success flag — that gap lets a failed execution still advance version/state.
