From 5a48e1026260024c6ae2b4d1100ee9b798a83e8d Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:00:27 +0300 Subject: [PATCH] feat(eth-watch): Integrate decentralized upgrades (#2401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Integrates new type of upgrade proposals into eth_watch ## Why ❔ Support new type of upgrade proposals ## Checklist - [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] Code has been formatted via `zk fmt` and `zk lint`. --- contracts | 2 +- core/lib/config/src/configs/contracts.rs | 2 + core/lib/config/src/testonly.rs | 1 + core/lib/contracts/src/lib.rs | 62 ++++++++ core/lib/env_config/src/contracts.rs | 2 + core/lib/protobuf_config/src/contracts.rs | 7 + .../src/proto/config/contracts.proto | 1 + core/lib/types/src/protocol_upgrade.rs | 37 ++--- core/node/eth_watch/src/client.rs | 41 +++++- .../decentralized_upgrades.rs | 134 ++++++++++++++++++ .../eth_watch/src/event_processors/mod.rs | 2 + core/node/eth_watch/src/lib.rs | 7 + core/node/eth_watch/src/tests.rs | 11 +- .../src/implementations/layers/eth_watch.rs | 6 +- core/tests/upgrade-test/tests/upgrade.test.ts | 44 ++++-- etc/env/base/contracts.toml | 1 + etc/env/file_based/contracts.yaml | 1 + .../src/hyperchain-upgrade.ts | 1 + infrastructure/zk/src/contract.ts | 1 + zk_toolbox/crates/config/src/contracts.rs | 3 + .../forge_interface/register_chain/output.rs | 1 + .../zk_inception/src/accept_ownership.rs | 6 +- .../zk_inception/src/commands/chain/init.rs | 2 +- 23 files changed, 331 insertions(+), 44 deletions(-) create mode 100644 core/node/eth_watch/src/event_processors/decentralized_upgrades.rs diff --git a/contracts b/contracts index f4ae6a1b90e2..c863a6592293 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit f4ae6a1b90e2c269542848ada44de669a5009290 +Subproject commit c863a659229319966c55cf7e66cd6542c6da9899 diff --git a/core/lib/config/src/configs/contracts.rs b/core/lib/config/src/configs/contracts.rs index 1ab032869e37..b68720ebaefe 100644 --- a/core/lib/config/src/configs/contracts.rs +++ b/core/lib/config/src/configs/contracts.rs @@ -39,6 +39,7 @@ pub struct ContractsConfig { pub ecosystem_contracts: Option, // Used by the RPC API and by the node builder in wiring the BaseTokenRatioProvider layer. pub base_token_addr: Option
, + pub chain_admin_addr: Option
, } impl ContractsConfig { @@ -59,6 +60,7 @@ impl ContractsConfig { governance_addr: Address::repeat_byte(0x13), base_token_addr: Some(Address::repeat_byte(0x14)), ecosystem_contracts: Some(EcosystemContracts::for_tests()), + chain_admin_addr: Some(Address::repeat_byte(0x18)), } } } diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index b9a78676697e..a5e51131c3a8 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -251,6 +251,7 @@ impl Distribution for EncodeDist { l2_testnet_paymaster_addr: g.gen(), l1_multicall3_addr: g.gen(), base_token_addr: g.gen(), + chain_admin_addr: g.gen(), ecosystem_contracts: self.sample(g), } } diff --git a/core/lib/contracts/src/lib.rs b/core/lib/contracts/src/lib.rs index b431085aad0b..bd7fa80b716c 100644 --- a/core/lib/contracts/src/lib.rs +++ b/core/lib/contracts/src/lib.rs @@ -47,6 +47,7 @@ const DIAMOND_INIT_CONTRACT_FILE: (&str, &str) = ( "chain-interfaces/IDiamondInit.sol/IDiamondInit.json", ); const GOVERNANCE_CONTRACT_FILE: (&str, &str) = ("governance", "IGovernance.sol/IGovernance.json"); +const CHAIN_ADMIN_CONTRACT_FILE: (&str, &str) = ("governance", "IChainAdmin.sol/IChainAdmin.json"); const MULTICALL3_CONTRACT_FILE: (&str, &str) = ("dev-contracts", "Multicall3.sol/Multicall3.json"); const VERIFIER_CONTRACT_FILE: (&str, &str) = ("state-transition", "Verifier.sol/Verifier.json"); const _IERC20_CONTRACT_FILE: &str = @@ -128,6 +129,10 @@ pub fn governance_contract() -> Contract { load_contract_for_both_compilers(GOVERNANCE_CONTRACT_FILE) } +pub fn chain_admin_contract() -> Contract { + load_contract_for_both_compilers(CHAIN_ADMIN_CONTRACT_FILE) +} + pub fn state_transition_manager_contract() -> Contract { load_contract_for_both_compilers(STATE_TRANSITION_CONTRACT_FILE) } @@ -804,3 +809,60 @@ pub static ADMIN_UPGRADE_CHAIN_FROM_VERSION_FUNCTION: Lazy = Lazy::new }"#; serde_json::from_str(abi).unwrap() }); + +pub static DIAMOND_CUT: 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": "diamondCut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }"#; + serde_json::from_str(abi).unwrap() +}); diff --git a/core/lib/env_config/src/contracts.rs b/core/lib/env_config/src/contracts.rs index ae5eb6f30c92..3365f56add77 100644 --- a/core/lib/env_config/src/contracts.rs +++ b/core/lib/env_config/src/contracts.rs @@ -71,6 +71,7 @@ mod tests { transparent_proxy_admin_addr: addr("0xdd6fa5c14e7550b4caf2aa2818d24c69cbc347e5"), }), base_token_addr: Some(SHARED_BRIDGE_ETHER_TOKEN_ADDRESS), + chain_admin_addr: Some(addr("0xdd6fa5c14e7550b4caf2aa2818d24c69cbc347ff")), } } @@ -95,6 +96,7 @@ CONTRACTS_BRIDGEHUB_PROXY_ADDR="0x35ea7f92f4c5f433efe15284e99c040110cf6297" CONTRACTS_STATE_TRANSITION_PROXY_ADDR="0xd90f1c081c6117241624e97cb6147257c3cb2097" CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR="0xdd6fa5c14e7550b4caf2aa2818d24c69cbc347e5" CONTRACTS_BASE_TOKEN_ADDR="0x0000000000000000000000000000000000000001" +CONTRACTS_CHAIN_ADMIN_ADDR="0xdd6fa5c14e7550b4caf2aa2818d24c69cbc347ff" "#; lock.set_env(config); diff --git a/core/lib/protobuf_config/src/contracts.rs b/core/lib/protobuf_config/src/contracts.rs index ac1864b7a0bd..84c404367503 100644 --- a/core/lib/protobuf_config/src/contracts.rs +++ b/core/lib/protobuf_config/src/contracts.rs @@ -101,6 +101,12 @@ impl ProtoRepr for proto::Contracts { .map(|x| parse_h160(x)) .transpose() .context("base_token_addr")?, + chain_admin_addr: l1 + .chain_admin_addr + .as_ref() + .map(|x| parse_h160(x)) + .transpose() + .context("chain_admin_addr")?, }) } @@ -132,6 +138,7 @@ impl ProtoRepr for proto::Contracts { default_upgrade_addr: Some(format!("{:?}", this.default_upgrade_addr)), multicall3_addr: Some(format!("{:?}", this.l1_multicall3_addr)), base_token_addr: this.base_token_addr.map(|a| format!("{:?}", a)), + chain_admin_addr: this.chain_admin_addr.map(|a| format!("{:?}", a)), }), l2: Some(proto::L2 { testnet_paymaster_addr: this.l2_testnet_paymaster_addr.map(|a| format!("{:?}", a)), diff --git a/core/lib/protobuf_config/src/proto/config/contracts.proto b/core/lib/protobuf_config/src/proto/config/contracts.proto index 7a9c92c08157..f4488c7901a1 100644 --- a/core/lib/protobuf_config/src/proto/config/contracts.proto +++ b/core/lib/protobuf_config/src/proto/config/contracts.proto @@ -16,6 +16,7 @@ message L1 { optional string default_upgrade_addr = 5; // required; H160 optional string multicall3_addr = 6; // required; H160 optional string base_token_addr = 7; // required; H160 + optional string chain_admin_addr = 8; // required; H160 } message L2 { diff --git a/core/lib/types/src/protocol_upgrade.rs b/core/lib/types/src/protocol_upgrade.rs index 2d7aa5c4b756..674996260204 100644 --- a/core/lib/types/src/protocol_upgrade.rs +++ b/core/lib/types/src/protocol_upgrade.rs @@ -10,7 +10,7 @@ use zksync_basic_types::{ }; use zksync_contracts::{ BaseSystemContractsHashes, ADMIN_EXECUTE_UPGRADE_FUNCTION, - ADMIN_UPGRADE_CHAIN_FROM_VERSION_FUNCTION, + ADMIN_UPGRADE_CHAIN_FROM_VERSION_FUNCTION, DIAMOND_CUT, }; use zksync_utils::h256_to_u256; @@ -28,10 +28,6 @@ pub struct Call { pub value: U256, /// The calldata to be executed on the `target` address. pub data: Vec, - /// Hash of the corresponding Ethereum transaction. Size should be 32 bytes. - pub eth_hash: H256, - /// Block in which Ethereum transaction was included. - pub eth_block: u64, } impl std::fmt::Debug for Call { @@ -40,8 +36,6 @@ impl std::fmt::Debug for Call { .field("target", &self.target) .field("value", &self.value) .field("data", &hex::encode(&self.data)) - .field("eth_hash", &self.eth_hash) - .field("eth_block", &self.eth_block) .finish() } } @@ -99,8 +93,17 @@ impl From for VerifierParams { } impl ProtocolUpgrade { + pub fn try_from_diamond_cut(diamond_cut_data: &[u8]) -> anyhow::Result { + // Unwraps are safe because we have validated the input against the function signature. + let diamond_cut_tokens = DIAMOND_CUT.decode_input(diamond_cut_data)?[0] + .clone() + .into_tuple() + .unwrap(); + Self::try_from_init_calldata(&diamond_cut_tokens[2].clone().into_bytes().unwrap()) + } + /// `l1-contracts/contracts/state-transition/libraries/diamond.sol:DiamondCutData.initCalldata` - fn try_from_init_calldata(init_calldata: &[u8], eth_block: u64) -> anyhow::Result { + fn try_from_init_calldata(init_calldata: &[u8]) -> anyhow::Result { let upgrade = ethabi::decode( &[abi::ProposedUpgrade::schema()], init_calldata.get(4..).context("need >= 4 bytes")?, @@ -124,7 +127,7 @@ impl ProtocolUpgrade { Transaction::try_from(abi::Transaction::L1 { tx: upgrade.l2_protocol_upgrade_tx, factory_deps: upgrade.factory_deps, - eth_block, + eth_block: 0, }) .context("Transaction::try_from()")? .try_into() @@ -148,10 +151,7 @@ pub fn decode_set_chain_id_event( protocol_version, Transaction::try_from(abi::Transaction::L1 { tx: tx.into(), - eth_block: event - .block_number - .expect("Event block number is missing") - .as_u64(), + eth_block: 0, factory_deps: vec![], }) .unwrap() @@ -199,7 +199,6 @@ impl TryFrom for ProtocolUpgrade { ProtocolUpgrade::try_from_init_calldata( // Unwrap is safe because we have validated the input against the function signature. &diamond_cut_tokens[2].clone().into_bytes().unwrap(), - call.eth_block, ) .context("ProtocolUpgrade::try_from_init_calldata()") } @@ -226,14 +225,6 @@ impl TryFrom for GovernanceOperation { // Extract `GovernanceOperation` data. let mut decoded_governance_operation = decoded.remove(1).into_tuple().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 calls = decoded_governance_operation.remove(0).into_array().unwrap(); let predecessor = H256::from_slice( &decoded_governance_operation @@ -260,8 +251,6 @@ impl TryFrom for GovernanceOperation { .unwrap(), value: decoded_governance_operation.remove(0).into_uint().unwrap(), data: decoded_governance_operation.remove(0).into_bytes().unwrap(), - eth_hash, - eth_block, } }) .collect(); diff --git a/core/node/eth_watch/src/client.rs b/core/node/eth_watch/src/client.rs index 764573002996..39b9b5e9f6b1 100644 --- a/core/node/eth_watch/src/client.rs +++ b/core/node/eth_watch/src/client.rs @@ -1,6 +1,7 @@ use std::fmt; -use zksync_contracts::verifier_contract; +use anyhow::Context; +use zksync_contracts::{state_transition_manager_contract, verifier_contract}; use zksync_eth_client::{ clients::{DynClient, L1}, CallFunctionArgs, ClientError, ContractCallError, EnrichedClientError, EnrichedClientResult, @@ -27,6 +28,11 @@ pub trait EthClient: 'static + fmt::Debug + Send + Sync { /// Returns scheduler verification key hash by verifier address. async fn scheduler_vk_hash(&self, verifier_address: Address) -> Result; + /// Returns upgrade diamond cut by packed protocol version. + async fn diamond_cut_by_version( + &self, + packed_version: H256, + ) -> EnrichedClientResult>>; /// Sets list of topics to return events for. fn set_topics(&mut self, topics: Vec); } @@ -42,8 +48,10 @@ pub struct EthHttpQueryClient { topics: Vec, diamond_proxy_addr: Address, governance_address: Address, + new_upgrade_cut_data_signature: H256, // Only present for post-shared bridge chains. state_transition_manager_address: Option
, + chain_admin_address: Option
, verifier_contract_abi: Contract, confirmations_for_eth_event: Option, } @@ -53,6 +61,7 @@ impl EthHttpQueryClient { client: Box>, diamond_proxy_addr: Address, state_transition_manager_address: Option
, + chain_admin_address: Option
, governance_address: Address, confirmations_for_eth_event: Option, ) -> Self { @@ -66,7 +75,13 @@ impl EthHttpQueryClient { topics: Vec::new(), diamond_proxy_addr, state_transition_manager_address, + chain_admin_address, governance_address, + new_upgrade_cut_data_signature: state_transition_manager_contract() + .event("NewUpgradeCutData") + .context("NewUpgradeCutData event is missing in ABI") + .unwrap() + .signature(), verifier_contract_abi: verifier_contract(), confirmations_for_eth_event, } @@ -84,6 +99,7 @@ impl EthHttpQueryClient { Some(self.diamond_proxy_addr), Some(self.governance_address), self.state_transition_manager_address, + self.chain_admin_address, ] .into_iter() .flatten() @@ -110,6 +126,29 @@ impl EthClient for EthHttpQueryClient { .await } + async fn diamond_cut_by_version( + &self, + packed_version: H256, + ) -> EnrichedClientResult>> { + let Some(state_transition_manager_address) = self.state_transition_manager_address else { + return Ok(None); + }; + + let filter = FilterBuilder::default() + .address(vec![state_transition_manager_address]) + .from_block(BlockNumber::Earliest) + .to_block(BlockNumber::Latest) + .topics( + Some(vec![self.new_upgrade_cut_data_signature]), + Some(vec![packed_version]), + None, + None, + ) + .build(); + let logs = self.client.logs(&filter).await?; + Ok(logs.into_iter().next().map(|log| log.data.0)) + } + async fn get_events( &self, from: BlockNumber, diff --git a/core/node/eth_watch/src/event_processors/decentralized_upgrades.rs b/core/node/eth_watch/src/event_processors/decentralized_upgrades.rs new file mode 100644 index 000000000000..dff10662e984 --- /dev/null +++ b/core/node/eth_watch/src/event_processors/decentralized_upgrades.rs @@ -0,0 +1,134 @@ +use anyhow::Context as _; +use zksync_dal::{Connection, Core, CoreDal, DalError}; +use zksync_types::{ + ethabi::Contract, protocol_version::ProtocolSemanticVersion, web3::Log, ProtocolUpgrade, H256, + U256, +}; + +use crate::{ + client::EthClient, + event_processors::{EventProcessor, EventProcessorError}, + metrics::{PollStage, METRICS}, +}; + +/// Listens to scheduling events coming from the chain admin contract and saves new protocol upgrade proposals to the database. +#[derive(Debug)] +pub struct DecentralizedUpgradesEventProcessor { + /// Last protocol version seen. Used to skip events for already known upgrade proposals. + last_seen_protocol_version: ProtocolSemanticVersion, + update_upgrade_timestamp_signature: H256, +} + +impl DecentralizedUpgradesEventProcessor { + pub fn new( + last_seen_protocol_version: ProtocolSemanticVersion, + chain_admin_contract: &Contract, + ) -> Self { + Self { + last_seen_protocol_version, + update_upgrade_timestamp_signature: chain_admin_contract + .event("UpdateUpgradeTimestamp") + .context("UpdateUpgradeTimestamp event is missing in ABI") + .unwrap() + .signature(), + } + } +} + +#[async_trait::async_trait] +impl EventProcessor for DecentralizedUpgradesEventProcessor { + async fn process_events( + &mut self, + storage: &mut Connection<'_, Core>, + client: &dyn EthClient, + events: Vec, + ) -> Result<(), EventProcessorError> { + let mut upgrades = Vec::new(); + for event in events { + let version = event.topics.get(1).copied().context("missing topic 1")?; + let timestamp: u64 = U256::from_big_endian(&event.data.0) + .try_into() + .ok() + .context("upgrade timestamp is too big")?; + + let diamond_cut = client + .diamond_cut_by_version(version) + .await? + .context("missing upgrade data on STM")?; + + let upgrade = ProtocolUpgrade { + timestamp, + ..ProtocolUpgrade::try_from_diamond_cut(&diamond_cut)? + }; + // Scheduler VK is not present in proposal event. It is hard coded in verifier contract. + let scheduler_vk_hash = if let Some(address) = upgrade.verifier_address { + Some(client.scheduler_vk_hash(address).await?) + } else { + None + }; + upgrades.push((upgrade, scheduler_vk_hash)); + } + + let new_upgrades: Vec<_> = upgrades + .into_iter() + .skip_while(|(v, _)| v.version <= self.last_seen_protocol_version) + .collect(); + + let Some((last_upgrade, _)) = new_upgrades.last() else { + return Ok(()); + }; + let versions: Vec<_> = new_upgrades + .iter() + .map(|(u, _)| u.version.to_string()) + .collect(); + tracing::debug!("Received upgrades with versions: {versions:?}"); + + let last_version = last_upgrade.version; + let stage_latency = METRICS.poll_eth_node[&PollStage::PersistUpgrades].start(); + for (upgrade, scheduler_vk_hash) in new_upgrades { + let latest_semantic_version = storage + .protocol_versions_dal() + .latest_semantic_version() + .await + .map_err(DalError::generalize)? + .context("expected some version to be present in DB")?; + + if upgrade.version > latest_semantic_version { + let latest_version = storage + .protocol_versions_dal() + .get_protocol_version_with_latest_patch(latest_semantic_version.minor) + .await + .map_err(DalError::generalize)? + .with_context(|| { + format!( + "expected minor version {} to be present in DB", + latest_semantic_version.minor as u16 + ) + })?; + + let new_version = latest_version.apply_upgrade(upgrade, scheduler_vk_hash); + if new_version.version.minor == latest_semantic_version.minor { + // Only verification parameters may change if only patch is bumped. + assert_eq!( + new_version.base_system_contracts_hashes, + latest_version.base_system_contracts_hashes + ); + assert!(new_version.tx.is_none()); + } + storage + .protocol_versions_dal() + .save_protocol_version_with_tx(&new_version) + .await + .map_err(DalError::generalize)?; + } + } + stage_latency.observe(); + + self.last_seen_protocol_version = last_version; + Ok(()) + } + + fn relevant_topic(&self) -> H256 { + self.update_upgrade_timestamp_signature + } +} diff --git a/core/node/eth_watch/src/event_processors/mod.rs b/core/node/eth_watch/src/event_processors/mod.rs index 396bcc2e1ca5..43ae259305a3 100644 --- a/core/node/eth_watch/src/event_processors/mod.rs +++ b/core/node/eth_watch/src/event_processors/mod.rs @@ -5,10 +5,12 @@ use zksync_eth_client::{ContractCallError, EnrichedClientError}; use zksync_types::{web3::Log, H256}; pub(crate) use self::{ + decentralized_upgrades::DecentralizedUpgradesEventProcessor, governance_upgrades::GovernanceUpgradesEventProcessor, priority_ops::PriorityOpsEventProcessor, }; use crate::client::EthClient; +mod decentralized_upgrades; mod governance_upgrades; mod priority_ops; diff --git a/core/node/eth_watch/src/lib.rs b/core/node/eth_watch/src/lib.rs index 7c27a6322c2f..72b6b29a2533 100644 --- a/core/node/eth_watch/src/lib.rs +++ b/core/node/eth_watch/src/lib.rs @@ -22,6 +22,7 @@ use self::{ }, metrics::{PollStage, METRICS}, }; +use crate::event_processors::DecentralizedUpgradesEventProcessor; mod client; mod event_processors; @@ -50,6 +51,7 @@ impl EthWatch { pub async fn new( diamond_proxy_addr: Address, governance_contract: &Contract, + chain_admin_contract: &Contract, mut client: Box, pool: ConnectionPool, poll_interval: Duration, @@ -66,9 +68,14 @@ impl EthWatch { state.last_seen_protocol_version, governance_contract, ); + let decentralized_upgrades_processor = DecentralizedUpgradesEventProcessor::new( + state.last_seen_protocol_version, + chain_admin_contract, + ); let event_processors: Vec> = vec![ Box::new(priority_ops_processor), Box::new(governance_upgrades_processor), + Box::new(decentralized_upgrades_processor), ]; let topics = event_processors diff --git a/core/node/eth_watch/src/tests.rs b/core/node/eth_watch/src/tests.rs index 773b7f62030a..7ae3b5494e98 100644 --- a/core/node/eth_watch/src/tests.rs +++ b/core/node/eth_watch/src/tests.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, convert::TryInto, sync::Arc}; use tokio::sync::RwLock; -use zksync_contracts::{governance_contract, hyperchain_contract}; +use zksync_contracts::{chain_admin_contract, governance_contract, hyperchain_contract}; use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; use zksync_eth_client::{ContractCallError, EnrichedClientResult}; use zksync_types::{ @@ -135,6 +135,13 @@ impl EthClient for MockEthClient { async fn finalized_block_number(&self) -> EnrichedClientResult { Ok(self.inner.read().await.last_finalized_block_number) } + + async fn diamond_cut_by_version( + &self, + _packed_version: H256, + ) -> EnrichedClientResult>> { + unimplemented!() + } } fn build_l1_tx(serial_id: u64, eth_block: u64) -> L1Tx { @@ -201,6 +208,7 @@ async fn create_test_watcher(connection_pool: ConnectionPool) -> (EthWatch let watcher = EthWatch::new( Address::default(), &governance_contract(), + &chain_admin_contract(), Box::new(client.clone()), connection_pool, std::time::Duration::from_nanos(1), @@ -293,6 +301,7 @@ async fn test_normal_operation_governance_upgrades() { let mut watcher = EthWatch::new( Address::default(), &governance_contract(), + &chain_admin_contract(), Box::new(client.clone()), connection_pool.clone(), std::time::Duration::from_nanos(1), 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 406d523e2d59..13f593644dc5 100644 --- a/core/node/node_framework/src/implementations/layers/eth_watch.rs +++ b/core/node/node_framework/src/implementations/layers/eth_watch.rs @@ -1,5 +1,5 @@ use zksync_config::{ContractsConfig, EthWatchConfig}; -use zksync_contracts::governance_contract; +use zksync_contracts::{chain_admin_contract, governance_contract}; use zksync_eth_watch::{EthHttpQueryClient, EthWatch}; use crate::{ @@ -64,7 +64,8 @@ impl WiringLayer for EthWatchLayer { self.contracts_config.diamond_proxy_addr, self.contracts_config .ecosystem_contracts - .map(|a| a.transparent_proxy_admin_addr), + .map(|a| a.state_transition_proxy_addr), + self.contracts_config.chain_admin_addr, self.contracts_config.governance_addr, self.eth_watch_config.confirmations_for_eth_event, ); @@ -72,6 +73,7 @@ impl WiringLayer for EthWatchLayer { let eth_watch = EthWatch::new( self.contracts_config.diamond_proxy_addr, &governance_contract(), + &chain_admin_contract(), Box::new(eth_client), main_pool, self.eth_watch_config.poll_interval(), diff --git a/core/tests/upgrade-test/tests/upgrade.test.ts b/core/tests/upgrade-test/tests/upgrade.test.ts index c9c454d64bbd..b111d6019b67 100644 --- a/core/tests/upgrade-test/tests/upgrade.test.ts +++ b/core/tests/upgrade-test/tests/upgrade.test.ts @@ -18,6 +18,9 @@ const GOVERNANCE_ABI = new ethers.Interface( const ADMIN_FACET_ABI = new ethers.Interface( require(`${L1_CONTRACTS_FOLDER}/state-transition/chain-interfaces/IAdmin.sol/IAdmin.json`).abi ); +const CHAIN_ADMIN_ABI = new ethers.Interface( + require(`${L1_CONTRACTS_FOLDER}/governance/ChainAdmin.sol/ChainAdmin.json`).abi +); const L2_FORCE_DEPLOY_UPGRADER_ABI = new ethers.Interface( require(`${process.env.ZKSYNC_HOME}/contracts/l2-contracts/artifacts-zk/contracts/ForceDeployUpgrader.sol/ForceDeployUpgrader.json`).abi ); @@ -40,8 +43,8 @@ describe('Upgrade test', function () { let govWallet: ethers.Wallet; let mainContract: IZkSyncHyperchain; let governanceContract: ethers.Contract; + let chainAdminContract: ethers.Contract; let bootloaderHash: string; - let scheduleTransparentOperation: string; let executeOperation: string; let forceDeployAddress: string; let forceDeployBytecode: string; @@ -96,6 +99,8 @@ describe('Upgrade test', function () { const stmContract = new ethers.Contract(stmAddr, STATE_TRANSITON_MANAGER, tester.syncWallet.providerL1); const governanceAddr = await stmContract.owner(); governanceContract = new ethers.Contract(governanceAddr, GOVERNANCE_ABI, tester.syncWallet.providerL1); + const chainAdminAddr = await mainContract.getAdmin(); + chainAdminContract = new ethers.Contract(chainAdminAddr, CHAIN_ADMIN_ABI, tester.syncWallet.providerL1); let blocksCommitted = await mainContract.getTotalBatchesCommitted(); const initialL1BatchNumber = await tester.web3Provider.getL1BatchNumber(); @@ -181,10 +186,9 @@ describe('Upgrade test', function () { const delegateCalldata = L2_FORCE_DEPLOY_UPGRADER_ABI.encodeFunctionData('forceDeploy', [[forceDeployment]]); const data = COMPLEX_UPGRADER_ABI.encodeFunctionData('upgrade', [delegateTo, delegateCalldata]); - const { stmUpgradeData, chainUpgradeData } = await prepareUpgradeCalldata( + const { stmUpgradeData, chainUpgradeCalldata, setTimestampCalldata } = await prepareUpgradeCalldata( govWallet, alice._providerL2(), - await mainContract.getAddress(), { l2ProtocolUpgradeTx: { txType: 254, @@ -208,12 +212,12 @@ describe('Upgrade test', function () { upgradeTimestamp: 0 } ); - scheduleTransparentOperation = chainUpgradeData.scheduleTransparentOperation; - executeOperation = chainUpgradeData.executeOperation; + executeOperation = chainUpgradeCalldata; await sendGovernanceOperation(stmUpgradeData.scheduleTransparentOperation); await sendGovernanceOperation(stmUpgradeData.executeOperation); - await sendGovernanceOperation(scheduleTransparentOperation); + + await sendChainAdminOperation(setTimestampCalldata); // Wait for server to process L1 event. await utils.sleep(2); @@ -247,7 +251,11 @@ describe('Upgrade test', function () { } // Execute the upgrade - await sendGovernanceOperation(executeOperation); + const executeMulticallData = chainAdminContract.interface.encodeFunctionData('multicall', [ + [[await mainContract.getAddress(), 0, executeOperation]], + true + ]); + await sendChainAdminOperation(executeMulticallData); let bootloaderHashL1 = await mainContract.getL2BootloaderBytecodeHash(); expect(bootloaderHashL1).eq(bootloaderHash); @@ -295,6 +303,16 @@ describe('Upgrade test', function () { }) ).wait(); } + + async function sendChainAdminOperation(data: string) { + await ( + await govWallet.sendTransaction({ + to: await chainAdminContract.getAddress(), + data: data, + type: 0 + }) + ).wait(); + } }); async function checkedRandomTransfer(sender: zksync.Wallet, amount: bigint): Promise { @@ -358,7 +376,6 @@ async function waitForNewL1Batch(wallet: zksync.Wallet): Promise { 'CONTRACTS_DEFAULT_UPGRADE_ADDR', 'CONTRACTS_GENESIS_UPGRADE_ADDR', 'CONTRACTS_GOVERNANCE_ADDR', + 'CONTRACTS_CHAIN_ADMIN_ADDR', 'CONTRACTS_ADMIN_FACET_ADDR', 'CONTRACTS_EXECUTOR_FACET_ADDR', 'CONTRACTS_GETTERS_FACET_ADDR', diff --git a/zk_toolbox/crates/config/src/contracts.rs b/zk_toolbox/crates/config/src/contracts.rs index a847c8a4cc93..a4c00a10a455 100644 --- a/zk_toolbox/crates/config/src/contracts.rs +++ b/zk_toolbox/crates/config/src/contracts.rs @@ -72,6 +72,7 @@ impl ContractsConfig { pub fn set_chain_contracts(&mut self, register_chain_output: &RegisterChainOutput) { self.l1.diamond_proxy_addr = register_chain_output.diamond_proxy_addr; self.l1.governance_addr = register_chain_output.governance_addr; + self.l1.chain_admin_addr = register_chain_output.chain_admin_addr; } pub fn set_l2_shared_bridge( @@ -117,6 +118,8 @@ pub struct L1Contracts { pub default_upgrade_addr: Address, pub diamond_proxy_addr: Address, pub governance_addr: Address, + #[serde(default)] + pub chain_admin_addr: Address, pub multicall3_addr: Address, pub verifier_addr: Address, pub validator_timelock_addr: Address, diff --git a/zk_toolbox/crates/config/src/forge_interface/register_chain/output.rs b/zk_toolbox/crates/config/src/forge_interface/register_chain/output.rs index 7d105b578b5b..2f39b76c3933 100644 --- a/zk_toolbox/crates/config/src/forge_interface/register_chain/output.rs +++ b/zk_toolbox/crates/config/src/forge_interface/register_chain/output.rs @@ -7,6 +7,7 @@ use crate::traits::FileConfig; pub struct RegisterChainOutput { pub diamond_proxy_addr: Address, pub governance_addr: Address, + pub chain_admin_addr: Address, } impl FileConfig for RegisterChainOutput {} diff --git a/zk_toolbox/crates/zk_inception/src/accept_ownership.rs b/zk_toolbox/crates/zk_inception/src/accept_ownership.rs index a236d437af53..567506aef670 100644 --- a/zk_toolbox/crates/zk_inception/src/accept_ownership.rs +++ b/zk_toolbox/crates/zk_inception/src/accept_ownership.rs @@ -20,7 +20,7 @@ lazy_static! { static ref ACCEPT_ADMIN: BaseContract = BaseContract::from( parse_abi(&[ "function acceptOwner(address governor, address target) public", - "function acceptAdmin(address governor, address target) public" + "function acceptAdmin(address admin, address target) public" ]) .unwrap(), ); @@ -29,7 +29,7 @@ lazy_static! { pub async fn accept_admin( shell: &Shell, ecosystem_config: &EcosystemConfig, - governor_contract: Address, + admin: Address, governor: Option, target_address: Address, forge_args: &ForgeScriptArgs, @@ -42,7 +42,7 @@ pub async fn accept_admin( forge_args.resume = false; let calldata = ACCEPT_ADMIN - .encode("acceptAdmin", (governor_contract, target_address)) + .encode("acceptAdmin", (admin, target_address)) .unwrap(); let foundry_contracts_path = ecosystem_config.path_to_foundry(); let forge = Forge::new(&foundry_contracts_path) diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs index 985885f30fe4..640f4a492869 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs @@ -93,7 +93,7 @@ pub async fn init( accept_admin( shell, ecosystem_config, - contracts_config.l1.governance_addr, + contracts_config.l1.chain_admin_addr, chain_config.get_wallets_config()?.governor_private_key(), contracts_config.l1.diamond_proxy_addr, &init_args.forge_args.clone(),