diff --git a/contracts b/contracts index 8cc766e6f949..32ca4e665da8 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 8cc766e6f94906907c331acab012bb24dbb06614 +Subproject commit 32ca4e665da89f5b4f2f705eee40d91024ad5b48 diff --git a/core/lib/contracts/src/lib.rs b/core/lib/contracts/src/lib.rs index 6ab80e18e943..e27728272150 100644 --- a/core/lib/contracts/src/lib.rs +++ b/core/lib/contracts/src/lib.rs @@ -591,7 +591,8 @@ pub static PRE_BOOJUM_COMMIT_FUNCTION: Lazy = Lazy::new(|| { }); pub static SET_CHAIN_ID_EVENT: Lazy = Lazy::new(|| { - let abi = r#"{ + let abi = r#" + { "anonymous": false, "inputs": [ { @@ -681,3 +682,124 @@ pub static SET_CHAIN_ID_EVENT: Lazy = Lazy::new(|| { }"#; serde_json::from_str(abi).unwrap() }); + +// The function that was used in the pre-v23 versions of the contract to upgrade the diamond proxy. +pub static ADMIN_EXECUTE_UPGRADE_FUNCTION: Lazy = Lazy::new(|| { + let abi = r#" + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + }, + { + "internalType": "enum Diamond.Action", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "isFreezable", + "type": "bool" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "internalType": "struct Diamond.FacetCut[]", + "name": "facetCuts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "initAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "initCalldata", + "type": "bytes" + } + ], + "internalType": "struct Diamond.DiamondCutData", + "name": "_diamondCut", + "type": "tuple" + } + ], + "name": "executeUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }"#; + serde_json::from_str(abi).unwrap() +}); + +// The function that is used in post-v23 chains to upgrade the chain +pub static ADMIN_UPGRADE_CHAIN_FROM_VERSION_FUNCTION: Lazy = Lazy::new(|| { + let abi = r#" + { + "inputs": [ + { + "internalType": "uint256", + "name": "_oldProtocolVersion", + "type": "uint256" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + }, + { + "internalType": "enum Diamond.Action", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "isFreezable", + "type": "bool" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "internalType": "struct Diamond.FacetCut[]", + "name": "facetCuts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "initAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "initCalldata", + "type": "bytes" + } + ], + "internalType": "struct Diamond.DiamondCutData", + "name": "_diamondCut", + "type": "tuple" + } + ], + "name": "upgradeChainFromVersion", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }"#; + serde_json::from_str(abi).unwrap() +}); diff --git a/core/lib/types/src/protocol_upgrade.rs b/core/lib/types/src/protocol_upgrade.rs index e6861060c000..d374854b8139 100644 --- a/core/lib/types/src/protocol_upgrade.rs +++ b/core/lib/types/src/protocol_upgrade.rs @@ -1,10 +1,16 @@ use std::convert::{TryFrom, TryInto}; use serde::{Deserialize, Serialize}; -use zksync_basic_types::protocol_version::{ - L1VerifierConfig, ProtocolSemanticVersion, ProtocolVersionId, VerifierParams, +use zksync_basic_types::{ + ethabi, + protocol_version::{ + L1VerifierConfig, ProtocolSemanticVersion, ProtocolVersionId, VerifierParams, + }, +}; +use zksync_contracts::{ + BaseSystemContractsHashes, ADMIN_EXECUTE_UPGRADE_FUNCTION, + ADMIN_UPGRADE_CHAIN_FROM_VERSION_FUNCTION, }; -use zksync_contracts::BaseSystemContractsHashes; use zksync_utils::{h256_to_u256, u256_to_account_address}; use crate::{ @@ -95,30 +101,13 @@ fn get_transaction_param_type() -> ParamType { ]) } -impl TryFrom for ProtocolUpgrade { - type Error = crate::ethabi::Error; - - fn try_from(event: Log) -> Result { - let facet_cut_param_type = ParamType::Tuple(vec![ - ParamType::Address, - ParamType::Uint(8), - ParamType::Bool, - ParamType::Array(Box::new(ParamType::FixedBytes(4))), - ]); - let diamond_cut_data_param_type = ParamType::Tuple(vec![ - ParamType::Array(Box::new(facet_cut_param_type)), - ParamType::Address, - ParamType::Bytes, - ]); - let mut decoded = decode( - &[diamond_cut_data_param_type, ParamType::FixedBytes(32)], - &event.data.0, - )?; - - let init_calldata = match decoded.remove(0) { - Token::Tuple(tokens) => tokens[2].clone().into_bytes().unwrap(), - _ => unreachable!(), - }; +impl ProtocolUpgrade { + fn try_from_decoded_tokens( + tokens: Vec, + transaction_hash: H256, + transaction_block_number: u64, + ) -> Result { + let init_calldata = tokens[2].clone().into_bytes().unwrap(); let transaction_param_type: ParamType = get_transaction_param_type(); let verifier_params_type = ParamType::Tuple(vec![ @@ -155,15 +144,12 @@ impl TryFrom for ProtocolUpgrade { let factory_deps = decoded.remove(0).into_array().unwrap(); - let eth_hash = event - .transaction_hash - .expect("Event transaction hash is missing"); - let eth_block = event - .block_number - .expect("Event block number is missing") - .as_u64(); - - let tx = ProtocolUpgradeTx::decode_tx(transaction, eth_hash, eth_block, factory_deps); + let tx = ProtocolUpgradeTx::decode_tx( + transaction, + transaction_hash, + transaction_block_number, + factory_deps, + ); let bootloader_code_hash = H256::from_slice(&decoded.remove(0).into_fixed_bytes().unwrap()); let default_account_code_hash = H256::from_slice(&decoded.remove(0).into_fixed_bytes().unwrap()); @@ -350,32 +336,47 @@ impl TryFrom for ProtocolUpgrade { type Error = crate::ethabi::Error; fn try_from(call: Call) -> Result { - // Reuses `ProtocolUpgrade::try_from`. - // `ProtocolUpgrade::try_from` only uses 3 log fields: `data`, `block_number`, `transaction_hash`. - // Others can be filled with dummy values. - // We build data as `call.data` without first 4 bytes which are for selector - // and append it with `bytes32(0)` for compatibility with old event data. - let data = call - .data - .into_iter() - .skip(4) - .chain(encode(&[Token::FixedBytes(H256::zero().0.to_vec())])) - .collect::>() - .into(); - let log = Log { - address: Default::default(), - topics: Default::default(), + let Call { data, - block_hash: Default::default(), - block_number: Some(call.eth_block.into()), - transaction_hash: Some(call.eth_hash), - transaction_index: Default::default(), - log_index: Default::default(), - transaction_log_index: Default::default(), - log_type: Default::default(), - removed: Default::default(), - }; - ProtocolUpgrade::try_from(log) + eth_hash, + eth_block, + .. + } = call; + + if data.len() < 4 { + return Err(crate::ethabi::Error::InvalidData); + } + + let (signature, data) = data.split_at(4); + + let diamond_cut_tokens = + if signature.to_vec() == ADMIN_EXECUTE_UPGRADE_FUNCTION.short_signature().to_vec() { + ADMIN_EXECUTE_UPGRADE_FUNCTION + .decode_input(data)? + .pop() + .unwrap() + .into_tuple() + .unwrap() + } else if signature.to_vec() + == ADMIN_UPGRADE_CHAIN_FROM_VERSION_FUNCTION + .short_signature() + .to_vec() + { + let mut data = ADMIN_UPGRADE_CHAIN_FROM_VERSION_FUNCTION.decode_input(data)?; + + assert_eq!( + data.len(), + 2, + "The second method is expected to accept exactly 2 arguments" + ); + + // The second item must be a tuple of diamond cut data + data.pop().unwrap().into_tuple().unwrap() + } else { + return Err(crate::ethabi::Error::InvalidData); + }; + + ProtocolUpgrade::try_from_decoded_tokens(diamond_cut_tokens, eth_hash, eth_block) } } diff --git a/core/lib/zksync_core_leftovers/src/lib.rs b/core/lib/zksync_core_leftovers/src/lib.rs index 49d1109e934f..b0104cc795e3 100644 --- a/core/lib/zksync_core_leftovers/src/lib.rs +++ b/core/lib/zksync_core_leftovers/src/lib.rs @@ -914,7 +914,6 @@ pub async fn start_eth_watch( let eth_watch = EthWatch::new( diamond_proxy_addr, - state_transition_manager_addr, &governance.0, Box::new(eth_client), pool, diff --git a/core/node/eth_watch/src/event_processors/governance_upgrades.rs b/core/node/eth_watch/src/event_processors/governance_upgrades.rs index 12f07669a6d2..ddd74440cecb 100644 --- a/core/node/eth_watch/src/event_processors/governance_upgrades.rs +++ b/core/node/eth_watch/src/event_processors/governance_upgrades.rs @@ -14,7 +14,7 @@ use crate::{ /// Listens to operation events coming from the governance contract and saves new protocol upgrade proposals to the database. #[derive(Debug)] pub struct GovernanceUpgradesEventProcessor { - // zkSync diamond proxy if pre-shared bridge; state transition manager if post shared bridge. + // zkSync diamond proxy target_contract_address: Address, /// Last protocol version seen. Used to skip events for already known upgrade proposals. last_seen_protocol_version: ProtocolSemanticVersion, diff --git a/core/node/eth_watch/src/lib.rs b/core/node/eth_watch/src/lib.rs index cf281d78b392..d91427dafcb1 100644 --- a/core/node/eth_watch/src/lib.rs +++ b/core/node/eth_watch/src/lib.rs @@ -49,7 +49,6 @@ pub struct EthWatch { impl EthWatch { pub async fn new( diamond_proxy_addr: Address, - state_transition_manager_address: Option
, governance_contract: &Contract, mut client: Box, pool: ConnectionPool, @@ -63,7 +62,7 @@ impl EthWatch { let priority_ops_processor = PriorityOpsEventProcessor::new(state.next_expected_priority_id)?; let governance_upgrades_processor = GovernanceUpgradesEventProcessor::new( - state_transition_manager_address.unwrap_or(diamond_proxy_addr), + diamond_proxy_addr, state.last_seen_protocol_version, governance_contract, ); diff --git a/core/node/eth_watch/src/tests.rs b/core/node/eth_watch/src/tests.rs index 0a690890e17a..f6abe93b35f0 100644 --- a/core/node/eth_watch/src/tests.rs +++ b/core/node/eth_watch/src/tests.rs @@ -192,7 +192,6 @@ async fn create_test_watcher(connection_pool: ConnectionPool) -> (EthWatch let client = MockEthClient::new(); let watcher = EthWatch::new( Address::default(), - None, &governance_contract(), Box::new(client.clone()), connection_pool, @@ -284,7 +283,6 @@ async fn test_normal_operation_governance_upgrades() { let mut client = MockEthClient::new(); let mut watcher = EthWatch::new( Address::default(), - None, &governance_contract(), Box::new(client.clone()), connection_pool.clone(), diff --git a/core/node/node_framework/src/implementations/layers/eth_watch.rs b/core/node/node_framework/src/implementations/layers/eth_watch.rs index 167e59a48691..c12d92907534 100644 --- a/core/node/node_framework/src/implementations/layers/eth_watch.rs +++ b/core/node/node_framework/src/implementations/layers/eth_watch.rs @@ -43,12 +43,6 @@ impl WiringLayer for EthWatchLayer { let client = context.get_resource::().await?.0; - let state_transition_manager_address = self - .contracts_config - .ecosystem_contracts - .as_ref() - .map(|a| a.state_transition_proxy_addr); - let eth_client = EthHttpQueryClient::new( client, self.contracts_config.diamond_proxy_addr, @@ -62,7 +56,6 @@ impl WiringLayer for EthWatchLayer { main_pool, client: eth_client, governance_contract: governance_contract(), - state_transition_manager_address, diamond_proxy_address: self.contracts_config.diamond_proxy_addr, poll_interval: self.eth_watch_config.poll_interval(), })); @@ -76,7 +69,6 @@ struct EthWatchTask { main_pool: ConnectionPool, client: EthHttpQueryClient, governance_contract: Contract, - state_transition_manager_address: Option
, diamond_proxy_address: Address, poll_interval: Duration, } @@ -90,7 +82,6 @@ impl Task for EthWatchTask { async fn run(self: Box, stop_receiver: StopReceiver) -> anyhow::Result<()> { let eth_watch = EthWatch::new( self.diamond_proxy_address, - self.state_transition_manager_address, &self.governance_contract, Box::new(self.client), self.main_pool, diff --git a/core/tests/upgrade-test/tests/upgrade.test.ts b/core/tests/upgrade-test/tests/upgrade.test.ts index 7fe97e727a64..9d4ff8f05f75 100644 --- a/core/tests/upgrade-test/tests/upgrade.test.ts +++ b/core/tests/upgrade-test/tests/upgrade.test.ts @@ -39,7 +39,6 @@ describe('Upgrade test', function () { let bootloaderHash: string; let scheduleTransparentOperation: string; let executeOperation: string; - let finalizeOperation: string; let forceDeployAddress: string; let forceDeployBytecode: string; let logs: fs.WriteStream; @@ -175,38 +174,39 @@ describe('Upgrade test', function () { const delegateCalldata = L2_FORCE_DEPLOY_UPGRADER_ABI.encodeFunctionData('forceDeploy', [[forceDeployment]]); const data = COMPLEX_UPGRADER_ABI.encodeFunctionData('upgrade', [delegateTo, delegateCalldata]); - const calldata = await prepareUpgradeCalldata(govWallet, alice._providerL2(), { - l2ProtocolUpgradeTx: { - txType: 254, - from: '0x0000000000000000000000000000000000008007', // FORCE_DEPLOYER address - to: '0x000000000000000000000000000000000000800f', // ComplexUpgrader address - gasLimit: process.env.CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT!, - gasPerPubdataByteLimit: zksync.utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - paymaster: 0, - value: 0, - reserved: [0, 0, 0, 0], - data, - signature: '0x', - factoryDeps: [zksync.utils.hashBytecode(forceDeployBytecode)], - paymasterInput: '0x', - reservedDynamic: '0x' - }, - factoryDeps: [forceDeployBytecode], - bootloaderHash, - upgradeTimestamp: 0 - }); - scheduleTransparentOperation = calldata.scheduleTransparentOperation; - executeOperation = calldata.executeOperation; - finalizeOperation = calldata.finalizeOperation; - - const scheduleUpgrade = await govWallet.sendTransaction({ - to: governanceContract.address, - data: scheduleTransparentOperation, - type: 0 - }); - await scheduleUpgrade.wait(); + const { stmUpgradeData, chainUpgradeData } = await prepareUpgradeCalldata( + govWallet, + alice._providerL2(), + mainContract.address, + { + l2ProtocolUpgradeTx: { + txType: 254, + from: '0x0000000000000000000000000000000000008007', // FORCE_DEPLOYER address + to: '0x000000000000000000000000000000000000800f', // ComplexUpgrader address + gasLimit: process.env.CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT!, + gasPerPubdataByteLimit: zksync.utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymaster: 0, + value: 0, + reserved: [0, 0, 0, 0], + data, + signature: '0x', + factoryDeps: [zksync.utils.hashBytecode(forceDeployBytecode)], + paymasterInput: '0x', + reservedDynamic: '0x' + }, + factoryDeps: [forceDeployBytecode], + bootloaderHash, + upgradeTimestamp: 0 + } + ); + scheduleTransparentOperation = chainUpgradeData.scheduleTransparentOperation; + executeOperation = chainUpgradeData.executeOperation; + + await sendGovernanceOperation(stmUpgradeData.scheduleTransparentOperation); + await sendGovernanceOperation(stmUpgradeData.executeOperation); + await sendGovernanceOperation(scheduleTransparentOperation); // Wait for server to process L1 event. await utils.sleep(2); @@ -218,7 +218,7 @@ describe('Upgrade test', function () { expect(batchDetails.baseSystemContractsHashes.bootloader).to.eq(bootloaderHash); }); - step('Execute upgrade', async () => { + step('Finalize upgrade on the target chain', async () => { // Wait for batches with old bootloader to be executed on L1. let l1BatchNumber = await alice.provider.getL1BatchNumber(); while ( @@ -239,23 +239,8 @@ describe('Upgrade test', function () { throw new Error('Server did not execute old blocks'); } - // Send execute tx. - const execute = await govWallet.sendTransaction({ - to: governanceContract.address, - data: executeOperation, - type: 0 - }); - await execute.wait(); - }); - - step('Finalize upgrade on the target chain', async () => { - // Send finalize tx. - const finalize = await govWallet.sendTransaction({ - to: mainContract.address, - data: finalizeOperation, - type: 0 - }); - await finalize.wait(); + // Execute the upgrade + await sendGovernanceOperation(executeOperation); let bootloaderHashL1 = await mainContract.getL2BootloaderBytecodeHash(); expect(bootloaderHashL1).eq(bootloaderHash); @@ -293,6 +278,16 @@ describe('Upgrade test', function () { await utils.exec('pkill zksync_server'); } catch (_) {} }); + + async function sendGovernanceOperation(data: string) { + await ( + await govWallet.sendTransaction({ + to: governanceContract.address, + data: data, + type: 0 + }) + ).wait(); + } }); async function checkedRandomTransfer( @@ -354,6 +349,7 @@ async function waitForNewL1Batch(wallet: zksync.Wallet): Promise