From cadf921d5a9f3e505e60cb3c2402299ee79628d5 Mon Sep 17 00:00:00 2001 From: Lyova Potyomkin Date: Fri, 13 Sep 2024 14:31:34 +0300 Subject: [PATCH 1/5] feat: compute migrated chain's diamond proxy address (#804) --- l1-contracts/scripts/sync-layer.ts | 79 +++++++++++++++++++++++++++++- l1-contracts/src.ts/deploy.ts | 4 +- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts index e51a6da3c..ab4b00282 100644 --- a/l1-contracts/scripts/sync-layer.ts +++ b/l1-contracts/scripts/sync-layer.ts @@ -16,10 +16,13 @@ import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA, priorityTxMaxGasLimit, L2_BRIDGEHUB_ADDRESS, + computeL2Create2Address, + DIAMOND_CUT_DATA_ABI_STRING, } from "../src.ts/utils"; import { Wallet as ZkWallet, Provider as ZkProvider, utils as zkUtils } from "zksync-ethers"; import { IStateTransitionManagerFactory } from "../typechain/IStateTransitionManagerFactory"; +import { IDiamondInitFactory } from "../typechain/IDiamondInitFactory"; import { TestnetERC20TokenFactory } from "../typechain/TestnetERC20TokenFactory"; import { BOOTLOADER_FORMAL_ADDRESS } from "zksync-ethers/build/utils"; @@ -30,6 +33,80 @@ async function main() { program.version("0.1.0").name("deploy").description("deploy L1 contracts"); + program + .command("compute-migrated-chain-address") + .requiredOption("--chain-id ") + .option("--private-key ") + .action(async (cmd) => { + if (process.env.CONTRACTS_BASE_NETWORK_ZKSYNC !== "true") { + throw new Error("This script is only for zkSync network"); + } + + const provider = new ZkProvider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const ethProvider = new ethers.providers.JsonRpcProvider(process.env.ETH_CLIENT_WEB3_URL); + const deployWallet = cmd.privateKey + ? new ZkWallet(cmd.privateKey, provider) + : (ZkWallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider) as ethers.Wallet | ZkWallet); + + const deployer = new Deployer({ + deployWallet, + addresses: deployedAddressesFromEnv(), + verbose: true, + }); + + deployer.addresses.StateTransition.AdminFacet = getAddressFromEnv("GATEWAY_ADMIN_FACET_ADDR"); + deployer.addresses.StateTransition.MailboxFacet = getAddressFromEnv("GATEWAY_MAILBOX_FACET_ADDR"); + deployer.addresses.StateTransition.ExecutorFacet = getAddressFromEnv("GATEWAY_EXECUTOR_FACET_ADDR"); + deployer.addresses.StateTransition.GettersFacet = getAddressFromEnv("GATEWAY_GETTERS_FACET_ADDR"); + deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("GATEWAY_DIAMOND_INIT_ADDR"); + deployer.addresses.StateTransition.Verifier = getAddressFromEnv("GATEWAY_VERIFIER_ADDR"); + deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv("GATEWAY_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"); + deployer.addresses.ValidatorTimeLock = getAddressFromEnv("GATEWAY_VALIDATOR_TIMELOCK_ADDR"); + deployer.addresses.Bridges.SharedBridgeProxy = getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"); + deployer.addresses.StateTransition.StateTransitionProxy = getAddressFromEnv( + "GATEWAY_STATE_TRANSITION_PROXY_ADDR" + ); + + const stm = deployer.stateTransitionManagerContract(provider); + const bridgehub = deployer.bridgehubContract(ethProvider); + const diamondInit = IDiamondInitFactory.connect(deployer.addresses.StateTransition.DiamondInit, provider); + const bytes32 = (x: ethers.BigNumberish) => ethers.utils.hexZeroPad(ethers.utils.hexlify(x), 32); + + const diamondCut = await deployer.initialZkSyncHyperchainDiamondCut([], true); + const mandatoryInitData = [ + diamondInit.interface.getSighash("initialize"), + bytes32(parseInt(cmd.chainId)), + bytes32(getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR")), + bytes32(deployer.addresses.StateTransition.StateTransitionProxy), + bytes32(await stm.protocolVersion()), + bytes32(deployer.deployWallet.address), + bytes32(deployer.addresses.ValidatorTimeLock), + await bridgehub.baseTokenAssetId(cmd.chainId), + bytes32(deployer.addresses.Bridges.SharedBridgeProxy), + await stm.storedBatchZero(), + ]; + + diamondCut.initCalldata = ethers.utils.hexConcat([...mandatoryInitData, diamondCut.initCalldata]); + const bytecode = hardhat.artifacts.readArtifactSync("DiamondProxy").bytecode; + const gatewayChainId = (await provider.getNetwork()).chainId; + const constructorData = new ethers.utils.AbiCoder().encode( + ["uint256", DIAMOND_CUT_DATA_ABI_STRING], + [gatewayChainId, diamondCut] + ); + + const address = computeL2Create2Address( + deployer.addresses.StateTransition.StateTransitionProxy, + bytecode, + constructorData, + ethers.constants.HashZero + ); + + console.log(address); + }); + program .command("deploy-sync-layer-contracts") .option("--private-key ") @@ -62,7 +139,7 @@ async function main() { : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + const nonce = await deployWallet.getTransactionCount(); console.log(`Using nonce: ${nonce}`); const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index cdc48a800..7a352d0ea 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -174,7 +174,7 @@ export class Deployer { const hashFromSTM = await stm.initialCutHash(); if (hash != hashFromSTM) { - throw new Error(`Has from STM ${hashFromSTM} does not match the computed hash ${hash}`); + throw new Error(`Hash from STM ${hashFromSTM} does not match the computed hash ${hash}`); } } @@ -909,7 +909,7 @@ export class Deployer { this.addresses.Bridges.NativeTokenVaultProxy = contractAddress; const sharedBridge = this.defaultSharedBridge(this.deployWallet); - const data = await sharedBridge.interface.encodeFunctionData("setNativeTokenVault", [ + const data = sharedBridge.interface.encodeFunctionData("setNativeTokenVault", [ this.addresses.Bridges.NativeTokenVaultProxy, ]); await this.executeUpgrade(this.addresses.Bridges.SharedBridgeProxy, 0, data); From 0c2c4f161b9e08f3329b789520cff0497dd5570e Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Fri, 13 Sep 2024 15:59:04 +0200 Subject: [PATCH 2/5] Copy consensus contracts from main (#800) --- l2-contracts/contracts/ConsensusRegistry.sol | 486 ++++++++++++++++++ .../interfaces/IConsensusRegistry.sol | 161 ++++++ 2 files changed, 647 insertions(+) create mode 100644 l2-contracts/contracts/ConsensusRegistry.sol create mode 100644 l2-contracts/contracts/interfaces/IConsensusRegistry.sol diff --git a/l2-contracts/contracts/ConsensusRegistry.sol b/l2-contracts/contracts/ConsensusRegistry.sol new file mode 100644 index 000000000..de5af6340 --- /dev/null +++ b/l2-contracts/contracts/ConsensusRegistry.sol @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable-v4/proxy/utils/Initializable.sol"; +import {IConsensusRegistry} from "./interfaces/IConsensusRegistry.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @title ConsensusRegistry +/// @dev Manages consensus nodes and committees for the L2 consensus protocol, +/// owned by Matter Labs Multisig. Nodes act as both validators and attesters, +/// each playing a distinct role in the consensus process. This contract facilitates +/// the rotation of validator and attester committees, which represent a subset of nodes +/// expected to actively participate in the consensus process during a specific time window. +/// @dev Designed for use with a proxy for upgradability. +contract ConsensusRegistry is IConsensusRegistry, Initializable, Ownable2StepUpgradeable { + /// @dev An array to keep track of node owners. + address[] public nodeOwners; + /// @dev A mapping of node owners => nodes. + mapping(address => Node) public nodes; + /// @dev A mapping for enabling efficient lookups when checking whether a given attester public key exists. + mapping(bytes32 => bool) public attesterPubKeyHashes; + /// @dev A mapping for enabling efficient lookups when checking whether a given validator public key exists. + mapping(bytes32 => bool) public validatorPubKeyHashes; + /// @dev Counter that increments with each new commit to the attester committee. + uint32 public attestersCommit; + /// @dev Counter that increments with each new commit to the validator committee. + uint32 public validatorsCommit; + + modifier onlyOwnerOrNodeOwner(address _nodeOwner) { + if (owner() != msg.sender && _nodeOwner != msg.sender) { + revert UnauthorizedOnlyOwnerOrNodeOwner(); + } + _; + } + + function initialize(address _initialOwner) external initializer { + if (_initialOwner == address(0)) { + revert InvalidInputNodeOwnerAddress(); + } + _transferOwnership(_initialOwner); + } + + /// @notice Adds a new node to the registry. + /// @dev Fails if node owner already exists. + /// @dev Fails if a validator/attester with the same public key already exists. + /// @param _nodeOwner The address of the new node's owner. + /// @param _validatorWeight The voting weight of the validator. + /// @param _validatorPubKey The BLS12-381 public key of the validator. + /// @param _validatorPoP The proof-of-possession (PoP) of the validator's public key. + /// @param _attesterWeight The voting weight of the attester. + /// @param _attesterPubKey The ECDSA public key of the attester. + function add( + address _nodeOwner, + uint32 _validatorWeight, + BLS12_381PublicKey calldata _validatorPubKey, + BLS12_381Signature calldata _validatorPoP, + uint32 _attesterWeight, + Secp256k1PublicKey calldata _attesterPubKey + ) external onlyOwner { + // Verify input. + _verifyInputAddress(_nodeOwner); + _verifyInputBLS12_381PublicKey(_validatorPubKey); + _verifyInputBLS12_381Signature(_validatorPoP); + _verifyInputSecp256k1PublicKey(_attesterPubKey); + + // Verify storage. + _verifyNodeOwnerDoesNotExist(_nodeOwner); + bytes32 attesterPubKeyHash = _hashAttesterPubKey(_attesterPubKey); + _verifyAttesterPubKeyDoesNotExist(attesterPubKeyHash); + bytes32 validatorPubKeyHash = _hashValidatorPubKey(_validatorPubKey); + _verifyValidatorPubKeyDoesNotExist(validatorPubKeyHash); + + uint32 nodeOwnerIdx = uint32(nodeOwners.length); + nodeOwners.push(_nodeOwner); + nodes[_nodeOwner] = Node({ + attesterLatest: AttesterAttr({ + active: true, + removed: false, + weight: _attesterWeight, + pubKey: _attesterPubKey + }), + attesterSnapshot: AttesterAttr({ + active: false, + removed: false, + weight: 0, + pubKey: Secp256k1PublicKey({tag: bytes1(0), x: bytes32(0)}) + }), + attesterLastUpdateCommit: attestersCommit, + validatorLatest: ValidatorAttr({ + active: true, + removed: false, + weight: _validatorWeight, + pubKey: _validatorPubKey, + proofOfPossession: _validatorPoP + }), + validatorSnapshot: ValidatorAttr({ + active: false, + removed: false, + weight: 0, + pubKey: BLS12_381PublicKey({a: bytes32(0), b: bytes32(0), c: bytes32(0)}), + proofOfPossession: BLS12_381Signature({a: bytes32(0), b: bytes16(0)}) + }), + validatorLastUpdateCommit: validatorsCommit, + nodeOwnerIdx: nodeOwnerIdx + }); + attesterPubKeyHashes[attesterPubKeyHash] = true; + validatorPubKeyHashes[validatorPubKeyHash] = true; + + emit NodeAdded({ + nodeOwner: _nodeOwner, + validatorWeight: _validatorWeight, + validatorPubKey: _validatorPubKey, + validatorPoP: _validatorPoP, + attesterWeight: _attesterWeight, + attesterPubKey: _attesterPubKey + }); + } + + /// @notice Deactivates a node, preventing it from participating in committees. + /// @dev Only callable by the contract owner or the node owner. + /// @dev Verifies that the node owner exists in the registry. + /// @param _nodeOwner The address of the node's owner to be inactivated. + function deactivate(address _nodeOwner) external onlyOwnerOrNodeOwner(_nodeOwner) { + _verifyNodeOwnerExists(_nodeOwner); + (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); + if (deleted) { + return; + } + + _ensureAttesterSnapshot(node); + node.attesterLatest.active = false; + _ensureValidatorSnapshot(node); + node.validatorLatest.active = false; + + emit NodeDeactivated(_nodeOwner); + } + + /// @notice Activates a previously inactive node, allowing it to participate in committees. + /// @dev Only callable by the contract owner or the node owner. + /// @dev Verifies that the node owner exists in the registry. + /// @param _nodeOwner The address of the node's owner to be activated. + function activate(address _nodeOwner) external onlyOwnerOrNodeOwner(_nodeOwner) { + _verifyNodeOwnerExists(_nodeOwner); + (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); + if (deleted) { + return; + } + + _ensureAttesterSnapshot(node); + node.attesterLatest.active = true; + _ensureValidatorSnapshot(node); + node.validatorLatest.active = true; + + emit NodeActivated(_nodeOwner); + } + + /// @notice Removes a node from the registry. + /// @dev Only callable by the contract owner. + /// @dev Verifies that the node owner exists in the registry. + /// @param _nodeOwner The address of the node's owner to be removed. + function remove(address _nodeOwner) external onlyOwner { + _verifyNodeOwnerExists(_nodeOwner); + (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); + if (deleted) { + return; + } + + _ensureAttesterSnapshot(node); + node.attesterLatest.removed = true; + _ensureValidatorSnapshot(node); + node.validatorLatest.removed = true; + + emit NodeRemoved(_nodeOwner); + } + + /// @notice Changes the validator weight of a node in the registry. + /// @dev Only callable by the contract owner. + /// @dev Verifies that the node owner exists in the registry. + /// @param _nodeOwner The address of the node's owner whose validator weight will be changed. + /// @param _weight The new validator weight to assign to the node. + function changeValidatorWeight(address _nodeOwner, uint32 _weight) external onlyOwner { + _verifyNodeOwnerExists(_nodeOwner); + (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); + if (deleted) { + return; + } + + _ensureValidatorSnapshot(node); + node.validatorLatest.weight = _weight; + + emit NodeValidatorWeightChanged(_nodeOwner, _weight); + } + + /// @notice Changes the attester weight of a node in the registry. + /// @dev Only callable by the contract owner. + /// @dev Verifies that the node owner exists in the registry. + /// @param _nodeOwner The address of the node's owner whose attester weight will be changed. + /// @param _weight The new attester weight to assign to the node. + function changeAttesterWeight(address _nodeOwner, uint32 _weight) external onlyOwner { + _verifyNodeOwnerExists(_nodeOwner); + (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); + if (deleted) { + return; + } + + _ensureAttesterSnapshot(node); + node.attesterLatest.weight = _weight; + + emit NodeAttesterWeightChanged(_nodeOwner, _weight); + } + + /// @notice Changes the validator's public key and proof-of-possession in the registry. + /// @dev Only callable by the contract owner or the node owner. + /// @dev Verifies that the node owner exists in the registry. + /// @param _nodeOwner The address of the node's owner whose validator key and PoP will be changed. + /// @param _pubKey The new BLS12-381 public key to assign to the node's validator. + /// @param _pop The new proof-of-possession (PoP) to assign to the node's validator. + function changeValidatorKey( + address _nodeOwner, + BLS12_381PublicKey calldata _pubKey, + BLS12_381Signature calldata _pop + ) external onlyOwnerOrNodeOwner(_nodeOwner) { + _verifyInputBLS12_381PublicKey(_pubKey); + _verifyInputBLS12_381Signature(_pop); + _verifyNodeOwnerExists(_nodeOwner); + (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); + if (deleted) { + return; + } + + bytes32 prevHash = _hashValidatorPubKey(node.validatorLatest.pubKey); + delete validatorPubKeyHashes[prevHash]; + bytes32 newHash = _hashValidatorPubKey(_pubKey); + _verifyValidatorPubKeyDoesNotExist(newHash); + validatorPubKeyHashes[newHash] = true; + _ensureValidatorSnapshot(node); + node.validatorLatest.pubKey = _pubKey; + node.validatorLatest.proofOfPossession = _pop; + + emit NodeValidatorKeyChanged(_nodeOwner, _pubKey, _pop); + } + + /// @notice Changes the attester's public key of a node in the registry. + /// @dev Only callable by the contract owner or the node owner. + /// @dev Verifies that the node owner exists in the registry. + /// @param _nodeOwner The address of the node's owner whose attester public key will be changed. + /// @param _pubKey The new ECDSA public key to assign to the node's attester. + function changeAttesterKey( + address _nodeOwner, + Secp256k1PublicKey calldata _pubKey + ) external onlyOwnerOrNodeOwner(_nodeOwner) { + _verifyInputSecp256k1PublicKey(_pubKey); + _verifyNodeOwnerExists(_nodeOwner); + (Node storage node, bool deleted) = _getNodeAndDeleteIfRequired(_nodeOwner); + if (deleted) { + return; + } + + bytes32 prevHash = _hashAttesterPubKey(node.attesterLatest.pubKey); + delete attesterPubKeyHashes[prevHash]; + bytes32 newHash = _hashAttesterPubKey(_pubKey); + _verifyAttesterPubKeyDoesNotExist(newHash); + attesterPubKeyHashes[newHash] = true; + + _ensureAttesterSnapshot(node); + node.attesterLatest.pubKey = _pubKey; + + emit NodeAttesterKeyChanged(_nodeOwner, _pubKey); + } + + /// @notice Adds a new commit to the attester committee. + /// @dev Implicitly updates the attester committee by affecting readers based on the current state of a node's attester attributes: + /// - If "attestersCommit" > "node.attesterLastUpdateCommit", read "node.attesterLatest". + /// - If "attestersCommit" == "node.attesterLastUpdateCommit", read "node.attesterSnapshot". + /// @dev Only callable by the contract owner. + function commitAttesterCommittee() external onlyOwner { + ++attestersCommit; + + emit AttestersCommitted(attestersCommit); + } + + /// @notice Adds a new commit to the validator committee. + /// @dev Implicitly updates the validator committee by affecting readers based on the current state of a node's validator attributes: + /// - If "validatorsCommit" > "node.validatorLastUpdateCommit", read "node.validatorLatest". + /// - If "validatorsCommit" == "node.validatorLastUpdateCommit", read "node.validatorSnapshot". + /// @dev Only callable by the contract owner. + function commitValidatorCommittee() external onlyOwner { + ++validatorsCommit; + + emit ValidatorsCommitted(validatorsCommit); + } + + /// @notice Returns an array of `AttesterAttr` structs representing the current attester committee. + /// @dev Collects active and non-removed attesters based on the latest commit to the committee. + function getAttesterCommittee() public view returns (CommitteeAttester[] memory) { + uint256 len = nodeOwners.length; + CommitteeAttester[] memory committee = new CommitteeAttester[](len); + uint256 count = 0; + + for (uint256 i = 0; i < len; ++i) { + Node storage node = nodes[nodeOwners[i]]; + AttesterAttr memory attester = attestersCommit > node.attesterLastUpdateCommit + ? node.attesterLatest + : node.attesterSnapshot; + if (attester.active && !attester.removed) { + committee[count] = CommitteeAttester({weight: attester.weight, pubKey: attester.pubKey}); + ++count; + } + } + + // Resize the array. + assembly { + mstore(committee, count) + } + return committee; + } + + /// @notice Returns an array of `ValidatorAttr` structs representing the current attester committee. + /// @dev Collects active and non-removed validators based on the latest commit to the committee. + function getValidatorCommittee() public view returns (CommitteeValidator[] memory) { + uint256 len = nodeOwners.length; + CommitteeValidator[] memory committee = new CommitteeValidator[](len); + uint256 count = 0; + + for (uint256 i = 0; i < len; ++i) { + Node storage node = nodes[nodeOwners[i]]; + ValidatorAttr memory validator = validatorsCommit > node.validatorLastUpdateCommit + ? node.validatorLatest + : node.validatorSnapshot; + if (validator.active && !validator.removed) { + committee[count] = CommitteeValidator({ + weight: validator.weight, + pubKey: validator.pubKey, + proofOfPossession: validator.proofOfPossession + }); + ++count; + } + } + + // Resize the array. + assembly { + mstore(committee, count) + } + return committee; + } + + function numNodes() public view returns (uint256) { + return nodeOwners.length; + } + + function _getNodeAndDeleteIfRequired(address _nodeOwner) private returns (Node storage, bool) { + Node storage node = nodes[_nodeOwner]; + bool pendingDeletion = _isNodePendingDeletion(node); + if (pendingDeletion) { + _deleteNode(_nodeOwner, node); + } + return (node, pendingDeletion); + } + + function _isNodePendingDeletion(Node storage _node) private returns (bool) { + bool attesterRemoved = (attestersCommit > _node.attesterLastUpdateCommit) + ? _node.attesterLatest.removed + : _node.attesterSnapshot.removed; + bool validatorRemoved = (validatorsCommit > _node.validatorLastUpdateCommit) + ? _node.validatorLatest.removed + : _node.validatorSnapshot.removed; + return attesterRemoved && validatorRemoved; + } + + function _deleteNode(address _nodeOwner, Node storage _node) private { + // Delete from array by swapping the last node owner (gas-efficient, not preserving order). + address lastNodeOwner = nodeOwners[nodeOwners.length - 1]; + nodeOwners[_node.nodeOwnerIdx] = lastNodeOwner; + nodeOwners.pop(); + // Update the node owned by the last node owner. + nodes[lastNodeOwner].nodeOwnerIdx = _node.nodeOwnerIdx; + + // Delete from the remaining mapping. + delete attesterPubKeyHashes[_hashAttesterPubKey(_node.attesterLatest.pubKey)]; + delete validatorPubKeyHashes[_hashValidatorPubKey(_node.validatorLatest.pubKey)]; + delete nodes[_nodeOwner]; + + emit NodeDeleted(_nodeOwner); + } + + function _ensureAttesterSnapshot(Node storage _node) private { + if (_node.attesterLastUpdateCommit < attestersCommit) { + _node.attesterSnapshot = _node.attesterLatest; + _node.attesterLastUpdateCommit = attestersCommit; + } + } + + function _ensureValidatorSnapshot(Node storage _node) private { + if (_node.validatorLastUpdateCommit < validatorsCommit) { + _node.validatorSnapshot = _node.validatorLatest; + _node.validatorLastUpdateCommit = validatorsCommit; + } + } + + function _isNodeOwnerExists(address _nodeOwner) private view returns (bool) { + BLS12_381PublicKey storage pubKey = nodes[_nodeOwner].validatorLatest.pubKey; + if (pubKey.a == bytes32(0) && pubKey.b == bytes32(0) && pubKey.c == bytes32(0)) { + return false; + } + return true; + } + + function _verifyNodeOwnerExists(address _nodeOwner) private view { + if (!_isNodeOwnerExists(_nodeOwner)) { + revert NodeOwnerDoesNotExist(); + } + } + + function _verifyNodeOwnerDoesNotExist(address _nodeOwner) private view { + if (_isNodeOwnerExists(_nodeOwner)) { + revert NodeOwnerExists(); + } + } + + function _hashAttesterPubKey(Secp256k1PublicKey storage _pubKey) private view returns (bytes32) { + return keccak256(abi.encode(_pubKey.tag, _pubKey.x)); + } + + function _hashAttesterPubKey(Secp256k1PublicKey calldata _pubKey) private pure returns (bytes32) { + return keccak256(abi.encode(_pubKey.tag, _pubKey.x)); + } + + function _hashValidatorPubKey(BLS12_381PublicKey storage _pubKey) private view returns (bytes32) { + return keccak256(abi.encode(_pubKey.a, _pubKey.b, _pubKey.c)); + } + + function _hashValidatorPubKey(BLS12_381PublicKey calldata _pubKey) private pure returns (bytes32) { + return keccak256(abi.encode(_pubKey.a, _pubKey.b, _pubKey.c)); + } + + function _verifyInputAddress(address _nodeOwner) private pure { + if (_nodeOwner == address(0)) { + revert InvalidInputNodeOwnerAddress(); + } + } + + function _verifyAttesterPubKeyDoesNotExist(bytes32 _hash) private view { + if (attesterPubKeyHashes[_hash]) { + revert AttesterPubKeyExists(); + } + } + + function _verifyValidatorPubKeyDoesNotExist(bytes32 _hash) private { + if (validatorPubKeyHashes[_hash]) { + revert ValidatorPubKeyExists(); + } + } + + function _verifyInputBLS12_381PublicKey(BLS12_381PublicKey calldata _pubKey) private pure { + if (_isEmptyBLS12_381PublicKey(_pubKey)) { + revert InvalidInputBLS12_381PublicKey(); + } + } + + function _verifyInputBLS12_381Signature(BLS12_381Signature calldata _pop) private pure { + if (_isEmptyBLS12_381Signature(_pop)) { + revert InvalidInputBLS12_381Signature(); + } + } + + function _verifyInputSecp256k1PublicKey(Secp256k1PublicKey calldata _pubKey) private pure { + if (_isEmptySecp256k1PublicKey(_pubKey)) { + revert InvalidInputSecp256k1PublicKey(); + } + } + + function _isEmptyBLS12_381PublicKey(BLS12_381PublicKey calldata _pubKey) private pure returns (bool) { + return _pubKey.a == bytes32(0) && _pubKey.b == bytes32(0) && _pubKey.c == bytes32(0); + } + + function _isEmptyBLS12_381Signature(BLS12_381Signature calldata _pop) private pure returns (bool) { + return _pop.a == bytes32(0) && _pop.b == bytes16(0); + } + + function _isEmptySecp256k1PublicKey(Secp256k1PublicKey calldata _pubKey) private pure returns (bool) { + return _pubKey.tag == bytes1(0) && _pubKey.x == bytes32(0); + } +} diff --git a/l2-contracts/contracts/interfaces/IConsensusRegistry.sol b/l2-contracts/contracts/interfaces/IConsensusRegistry.sol new file mode 100644 index 000000000..89c4041ee --- /dev/null +++ b/l2-contracts/contracts/interfaces/IConsensusRegistry.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @title ConsensusRegistry contract interface +interface IConsensusRegistry { + /// @dev Represents a consensus node. + /// @param attesterLastUpdateCommit The latest `attestersCommit` where the node's attester attributes were updated. + /// @param attesterLatest Attester attributes to read if `node.attesterLastUpdateCommit` < `attestersCommit`. + /// @param attesterSnapshot Attester attributes to read if `node.attesterLastUpdateCommit` == `attestersCommit`. + /// @param validatorLastUpdateCommit The latest `validatorsCommit` where the node's validator attributes were updated. + /// @param validatorLatest Validator attributes to read if `node.validatorLastUpdateCommit` < `validatorsCommit`. + /// @param validatorSnapshot Validator attributes to read if `node.validatorLastUpdateCommit` == `validatorsCommit`. + /// @param nodeOwnerIdx Index of the node owner within the array of node owners. + struct Node { + uint32 attesterLastUpdateCommit; + uint32 validatorLastUpdateCommit; + uint32 nodeOwnerIdx; + AttesterAttr attesterLatest; + AttesterAttr attesterSnapshot; + ValidatorAttr validatorLatest; + ValidatorAttr validatorSnapshot; + } + + /// @dev Represents the attester attributes of a consensus node. + /// @param active A flag stating if the attester is active. + /// @param removed A flag stating if the attester has been removed (and is pending a deletion). + /// @param weight Attester's voting weight. + /// @param pubKey Attester's Secp256k1 public key. + struct AttesterAttr { + bool active; + bool removed; + uint32 weight; + Secp256k1PublicKey pubKey; + } + + /// @dev Represents an attester within a committee. + /// @param weight Attester's voting weight. + /// @param pubKey Attester's Secp256k1 public key. + struct CommitteeAttester { + uint32 weight; + Secp256k1PublicKey pubKey; + } + + /// @dev Represents the validator attributes of a consensus node. + /// @param active A flag stating if the validator is active. + /// @param removed A flag stating if the validator has been removed (and is pending a deletion). + /// @param weight Validator's voting weight. + /// @param pubKey Validator's BLS12-381 public key. + /// @param proofOfPossession Validator's Proof-of-possession (a signature over the public key). + struct ValidatorAttr { + bool active; + bool removed; + uint32 weight; + BLS12_381PublicKey pubKey; + BLS12_381Signature proofOfPossession; + } + + /// @dev Represents a validator within a committee. + /// @param weight Validator's voting weight. + /// @param pubKey Validator's BLS12-381 public key. + /// @param proofOfPossession Validator's Proof-of-possession (a signature over the public key). + struct CommitteeValidator { + uint32 weight; + BLS12_381PublicKey pubKey; + BLS12_381Signature proofOfPossession; + } + + /// @dev Represents BLS12_381 public key. + /// @param a First component of the BLS12-381 public key. + /// @param b Second component of the BLS12-381 public key. + /// @param c Third component of the BLS12-381 public key. + struct BLS12_381PublicKey { + bytes32 a; + bytes32 b; + bytes32 c; + } + + /// @dev Represents BLS12_381 signature. + /// @param a First component of the BLS12-381 signature. + /// @param b Second component of the BLS12-381 signature. + struct BLS12_381Signature { + bytes32 a; + bytes16 b; + } + + /// @dev Represents Secp256k1 public key. + /// @param tag Y-coordinate's even/odd indicator of the Secp256k1 public key. + /// @param x X-coordinate component of the Secp256k1 public key. + struct Secp256k1PublicKey { + bytes1 tag; + bytes32 x; + } + + error UnauthorizedOnlyOwnerOrNodeOwner(); + error NodeOwnerExists(); + error NodeOwnerDoesNotExist(); + error NodeOwnerNotFound(); + error ValidatorPubKeyExists(); + error AttesterPubKeyExists(); + error InvalidInputNodeOwnerAddress(); + error InvalidInputBLS12_381PublicKey(); + error InvalidInputBLS12_381Signature(); + error InvalidInputSecp256k1PublicKey(); + + event NodeAdded( + address indexed nodeOwner, + uint32 validatorWeight, + BLS12_381PublicKey validatorPubKey, + BLS12_381Signature validatorPoP, + uint32 attesterWeight, + Secp256k1PublicKey attesterPubKey + ); + event NodeDeactivated(address indexed nodeOwner); + event NodeActivated(address indexed nodeOwner); + event NodeRemoved(address indexed nodeOwner); + event NodeDeleted(address indexed nodeOwner); + event NodeValidatorWeightChanged(address indexed nodeOwner, uint32 newWeight); + event NodeAttesterWeightChanged(address indexed nodeOwner, uint32 newWeight); + event NodeValidatorKeyChanged(address indexed nodeOwner, BLS12_381PublicKey newPubKey, BLS12_381Signature newPoP); + event NodeAttesterKeyChanged(address indexed nodeOwner, Secp256k1PublicKey newPubKey); + event ValidatorsCommitted(uint32 commit); + event AttestersCommitted(uint32 commit); + + function add( + address _nodeOwner, + uint32 _validatorWeight, + BLS12_381PublicKey calldata _validatorPubKey, + BLS12_381Signature calldata _validatorPoP, + uint32 _attesterWeight, + Secp256k1PublicKey calldata _attesterPubKey + ) external; + + function deactivate(address _nodeOwner) external; + + function activate(address _nodeOwner) external; + + function remove(address _nodeOwner) external; + + function changeValidatorWeight(address _nodeOwner, uint32 _weight) external; + + function changeAttesterWeight(address _nodeOwner, uint32 _weight) external; + + function changeValidatorKey( + address _nodeOwner, + BLS12_381PublicKey calldata _pubKey, + BLS12_381Signature calldata _pop + ) external; + + function changeAttesterKey(address _nodeOwner, Secp256k1PublicKey calldata _pubKey) external; + + function commitAttesterCommittee() external; + + function commitValidatorCommittee() external; + + function getAttesterCommittee() external view returns (CommitteeAttester[] memory); + + function getValidatorCommittee() external view returns (CommitteeValidator[] memory); +} From 7206350a0ffd9f2de0ecd38e7f9e2cd711a5dd81 Mon Sep 17 00:00:00 2001 From: kelemeno <34402761+kelemeno@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:39:49 +0100 Subject: [PATCH 3/5] fix: get the server to start (#803) Co-authored-by: Lyova Potyomkin Co-authored-by: Stanislav Bezkorovainyi --- .../contracts/bridge/L1ERC20Bridge.sol | 2 +- l1-contracts/contracts/bridge/L1Nullifier.sol | 9 + .../bridge/asset-router/L1AssetRouter.sol | 2 +- .../bridge/asset-router/L2AssetRouter.sol | 12 +- .../bridge/interfaces/IL1Nullifier.sol | 4 +- .../bridge/ntv/L1NativeTokenVault.sol | 2 +- .../contracts/bridge/ntv/NativeTokenVault.sol | 2 +- .../contracts/common/L1ContractErrors.sol | 10 +- .../dev-contracts/L1NullifierDev.sol | 20 ++ .../l2-deps/IL2GenesisUpgrade.sol | 9 +- .../libraries/BatchDecoder.sol | 4 - .../contracts/upgrades/GatewayUpgrade.sol | 4 +- .../contracts/upgrades/L1GenesisUpgrade.sol | 21 +- l1-contracts/deploy-scripts/DeployL1.s.sol | 3 +- .../GenerateForceDeploymentsData.s.sol | 42 --- ...ter-hyperchain.ts => register-zk-chain.ts} | 9 +- l1-contracts/scripts/sync-layer.ts | 79 ++++- l1-contracts/src.ts/deploy-process.ts | 3 +- l1-contracts/src.ts/deploy-test-process.ts | 3 +- l1-contracts/src.ts/deploy-token.ts | 8 +- l1-contracts/src.ts/deploy-utils-zk.ts | 15 +- l1-contracts/src.ts/deploy-utils.ts | 101 ++++++- l1-contracts/src.ts/deploy.ts | 280 +++++++++++++----- l1-contracts/src.ts/utils.ts | 8 + .../integration/BridgeHubInvariantTests.t.sol | 8 +- .../l1/integration/BridgehubTests.t.sol | 8 +- .../_SharedL1ContractDeployer.t.sol | 2 +- .../L1Erc20Bridge/FinalizeWithdrawal.sol | 6 + .../_L1SharedBridge_Shared.t.sol | 2 +- .../Governance/PermanentRestriction.t.sol | 14 + .../_ChainTypeManager_Shared.t.sol | 7 +- .../l2/unit/erc20/L2Erc20BridgeTest.t.sol | 2 +- .../test/foundry/l2/unit/utils/L2Utils.sol | 6 +- .../initial_deployment_test.spec.ts | 6 + system-contracts/SystemContractsHashes.json | 34 +-- system-contracts/contracts/Constants.sol | 3 +- .../contracts/L2GenesisUpgrade.sol | 89 +++++- .../interfaces/IL2GenesisUpgrade.sol | 22 +- .../test-contracts/DummyBridgehub.sol | 11 + .../test-contracts/DummyL2AssetRouter.sol | 13 + .../DummyL2NativeTokenVault.sol | 15 + .../test-contracts/DummyMessageRoot.sol | 7 + .../test/L2GenesisUpgrade.spec.ts | 57 +++- system-contracts/test/shared/constants.ts | 2 + 44 files changed, 767 insertions(+), 199 deletions(-) create mode 100644 l1-contracts/contracts/dev-contracts/L1NullifierDev.sol rename l1-contracts/scripts/{register-hyperchain.ts => register-zk-chain.ts} (93%) create mode 100644 system-contracts/contracts/test-contracts/DummyBridgehub.sol create mode 100644 system-contracts/contracts/test-contracts/DummyL2AssetRouter.sol create mode 100644 system-contracts/contracts/test-contracts/DummyL2NativeTokenVault.sol create mode 100644 system-contracts/contracts/test-contracts/DummyMessageRoot.sol diff --git a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol index 3eb231950..0cd72ccc7 100644 --- a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol @@ -140,7 +140,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { chainId: ERA_CHAIN_ID, l2BatchNumber: _l2BatchNumber, l2MessageIndex: _l2MessageIndex, - l2Sender: l2Bridge, + l2Sender: L1_NULLIFIER.l2BridgeAddress(ERA_CHAIN_ID), l2TxNumberInBatch: _l2TxNumberInBatch, message: _message, merkleProof: _merkleProof diff --git a/l1-contracts/contracts/bridge/L1Nullifier.sol b/l1-contracts/contracts/bridge/L1Nullifier.sol index 789f122df..6dda093c9 100644 --- a/l1-contracts/contracts/bridge/L1Nullifier.sol +++ b/l1-contracts/contracts/bridge/L1Nullifier.sol @@ -217,6 +217,15 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, return __DEPRECATED_l2BridgeAddress[_chainId]; } + /// @notice Legacy function used for migration, do not use! + /// @param _chainId The chain id we want to get the balance for. + /// @param _token The address of the token. + // slither-disable-next-line uninitialized-state-variables + function chainBalance(uint256 _chainId, address _token) external view returns (uint256) { + // slither-disable-next-line uninitialized-state-variables + return __DEPRECATED_chainBalance[_chainId][_token]; + } + /// @notice Sets the L1ERC20Bridge contract address. /// @dev Should be called only once by the owner. /// @param _legacyBridge The address of the legacy bridge. diff --git a/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol index 0c3b2001f..6e44f4cae 100644 --- a/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol @@ -540,7 +540,7 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard { ) external override { /// @dev We use a deprecated field to support L2->L1 legacy withdrawals, which were started /// by the legacy bridge. - address legacyL2Bridge = L1_NULLIFIER.__DEPRECATED_l2BridgeAddress(_chainId); + address legacyL2Bridge = L1_NULLIFIER.l2BridgeAddress(_chainId); FinalizeL1DepositParams memory finalizeWithdrawalParams = FinalizeL1DepositParams({ chainId: _chainId, l2BatchNumber: _l2BatchNumber, diff --git a/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol index 6c122ccc6..b75eb58e8 100644 --- a/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol @@ -73,7 +73,8 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { uint256 _eraChainId, address _l1AssetRouter, address _legacySharedBridge, - bytes32 _baseTokenAssetId + bytes32 _baseTokenAssetId, + address _aliasedOwner ) AssetRouterBase(_l1ChainId, _eraChainId, IBridgehub(L2_BRIDGEHUB_ADDR)) { L2_LEGACY_SHARED_BRIDGE = _legacySharedBridge; if (_l1AssetRouter == address(0)) { @@ -83,6 +84,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { assetHandlerAddress[_baseTokenAssetId] = L2_NATIVE_TOKEN_VAULT_ADDR; BASE_TOKEN_ASSET_ID = _baseTokenAssetId; _disableInitializers(); + _transferOwnership(_aliasedOwner); } /// @inheritdoc IL2AssetRouter @@ -164,8 +166,12 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { // slither-disable-next-line unused-return L2ContractHelper.sendMessageToL1(message); } else { - address l1Token = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).tokenAddress(_assetId); - require(l1Token != address(0), "Unsupported asset Id by NTV"); + address l1Token = IBridgedStandardToken( + IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).tokenAddress(_assetId) + ).originToken(); + if (l1Token == address(0)) { + revert AssetIdNotSupported(_assetId); + } (uint256 amount, address l1Receiver) = abi.decode(_assetData, (uint256, address)); message = _getSharedBridgeWithdrawMessage(l1Receiver, l1Token, amount); IL2SharedBridgeLegacy(L2_LEGACY_SHARED_BRIDGE).sendMessageToL1(message); diff --git a/l1-contracts/contracts/bridge/interfaces/IL1Nullifier.sol b/l1-contracts/contracts/bridge/interfaces/IL1Nullifier.sol index f5bd3539c..61bf38516 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1Nullifier.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1Nullifier.sol @@ -78,9 +78,9 @@ interface IL1Nullifier { function setL1AssetRouter(address _l1AssetRouter) external; - function __DEPRECATED_chainBalance(uint256 _chainId, address _token) external view returns (uint256); + function chainBalance(uint256 _chainId, address _token) external view returns (uint256); - function __DEPRECATED_l2BridgeAddress(uint256 _chainId) external view returns (address); + function l2BridgeAddress(uint256 _chainId) external view returns (address); function transferTokenToNTV(address _token) external; diff --git a/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol index be456db43..f1d14834d 100644 --- a/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol @@ -116,7 +116,7 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, NativeToken /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). /// @param _targetChainId The chain ID of the corresponding ZK chain. function updateChainBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { - uint256 nullifierChainBalance = L1_NULLIFIER.__DEPRECATED_chainBalance(_targetChainId, _token); + uint256 nullifierChainBalance = L1_NULLIFIER.chainBalance(_targetChainId, _token); bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _token); chainBalance[_targetChainId][assetId] = chainBalance[_targetChainId][assetId] + nullifierChainBalance; originChainId[assetId] = block.chainid; diff --git a/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol index 5e76d7630..02e865d5d 100644 --- a/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol @@ -259,8 +259,8 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2 if (msg.value != 0) { revert NonEmptyMsgValue(); } - _handleChainBalanceIncrease(_chainId, _assetId, amount, true); amount = _depositAmount; + _handleChainBalanceIncrease(_chainId, _assetId, amount, true); if (!_depositChecked) { uint256 expectedDepositAmount = _depositFunds(_originalCaller, IERC20(nativeToken), _depositAmount); // note if _originalCaller is this contract, this will return 0. This does not happen. // The token has non-standard transfer logic diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 7f2bc781d..18766497f 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -25,19 +25,19 @@ error RemovingPermanentRestriction(); error UnallowedImplementation(bytes32 implementationHash); // 0x1ff9d522 error AddressAlreadyUsed(address addr); -// +// 0x0dfb42bf error AddressAlreadySet(address addr); // 0x86bb51b8 error AddressHasNoCode(address); -// +// 0x1f73225f error AddressMismatch(address expected, address supplied); // 0x1eee5481 error AddressTooLow(address); -// +// 0x5e85ae73 error AmountMustBeGreaterThanZero(); -// +// 0xfde974f4 error AssetHandlerDoesNotExist(bytes32 assetId); -// +// 0x1294e9e1 error AssetIdMismatch(bytes32 expected, bytes32 supplied); // error AssetIdAlreadyRegistered(); diff --git a/l1-contracts/contracts/dev-contracts/L1NullifierDev.sol b/l1-contracts/contracts/dev-contracts/L1NullifierDev.sol new file mode 100644 index 000000000..062d168cd --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/L1NullifierDev.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L1Nullifier, IBridgehub} from "../bridge/L1Nullifier.sol"; + +contract L1NullifierDev is L1Nullifier { + constructor( + IBridgehub _bridgehub, + uint256 _eraChainId, + address _eraDiamondProxy + ) L1Nullifier(_bridgehub, _eraChainId, _eraDiamondProxy) {} + + function setL2LegacySharedBridge(uint256 _chainId, address _l2Bridge) external { + __DEPRECATED_l2BridgeAddress[_chainId] = _l2Bridge; + } + + // add this to be excluded from coverage report + function test() internal virtual {} +} diff --git a/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol index daddd142a..6221f2e18 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol @@ -15,6 +15,12 @@ struct ForceDeployment { bytes input; } +struct ZKChainSpecificForceDeploymentsData { + bytes32 baseTokenAssetId; + address l2LegacySharedBridge; + address l2Weth; +} + /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IL2GenesisUpgrade { @@ -23,6 +29,7 @@ interface IL2GenesisUpgrade { function genesisUpgrade( uint256 _chainId, address _ctmDeployer, - bytes calldata _forceDeploymentsData + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData ) external payable; } diff --git a/l1-contracts/contracts/state-transition/libraries/BatchDecoder.sol b/l1-contracts/contracts/state-transition/libraries/BatchDecoder.sol index 05b16537b..a8af4b7ab 100644 --- a/l1-contracts/contracts/state-transition/libraries/BatchDecoder.sol +++ b/l1-contracts/contracts/state-transition/libraries/BatchDecoder.sol @@ -99,10 +99,6 @@ library BatchDecoder { uint256[] memory proof ) { - if (_proofData.length == 0) { - revert EmptyData(); - } - uint8 encodingVersion = uint8(_proofData[0]); if (encodingVersion == SUPPORTED_ENCODING_VERSION) { (prevBatch, provedBatches, proof) = abi.decode( diff --git a/l1-contracts/contracts/upgrades/GatewayUpgrade.sol b/l1-contracts/contracts/upgrades/GatewayUpgrade.sol index 9ce428f96..08d05989e 100644 --- a/l1-contracts/contracts/upgrades/GatewayUpgrade.sol +++ b/l1-contracts/contracts/upgrades/GatewayUpgrade.sol @@ -40,7 +40,9 @@ contract GatewayUpgrade is BaseZkSyncUpgrade, Initializable { s.baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, s.__DEPRECATED_baseToken); s.priorityTree.setup(s.priorityQueue.getTotalPriorityTxs()); - IBridgehub(s.bridgehub).setLegacyBaseTokenAssetId(s.chainId); + IBridgehub bridgehub = IBridgehub(s.bridgehub); + s.baseTokenBridge = bridgehub.sharedBridge(); // we change the assetRouter + bridgehub.setLegacyBaseTokenAssetId(s.chainId); ProposedUpgrade memory proposedUpgrade = _proposedUpgrade; address l2LegacyBridge = IL1SharedBridgeLegacy(s.baseTokenBridge).l2BridgeAddress(s.chainId); proposedUpgrade.l2ProtocolUpgradeTx.data = bytes.concat( diff --git a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol index 4637c535d..d6cb769c0 100644 --- a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol +++ b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol @@ -8,8 +8,10 @@ import {Diamond} from "../state-transition/libraries/Diamond.sol"; import {BaseZkSyncUpgradeGenesis} from "./BaseZkSyncUpgradeGenesis.sol"; import {ProposedUpgrade} from "./IDefaultUpgrade.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; -import {IL2GenesisUpgrade} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; +import {IL2GenesisUpgrade, ZKChainSpecificForceDeploymentsData} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; import {IL1GenesisUpgrade} from "./IL1GenesisUpgrade.sol"; +import {IL1Nullifier} from "../bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "../bridge/asset-router/IL1AssetRouter.sol"; import {IComplexUpgrader} from "../state-transition/l2-deps/IComplexUpgrader.sol"; import {L2_FORCE_DEPLOYER_ADDR, L2_COMPLEX_UPGRADER_ADDR, L2_GENESIS_UPGRADE_ADDR} from "../common/L2ContractAddresses.sol"; //, COMPLEX_UPGRADER_ADDR, GENESIS_UPGRADE_ADDR import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, SYSTEM_UPGRADE_L2_TX_TYPE, PRIORITY_TX_MAX_GAS_LIMIT} from "../common/Config.sol"; @@ -27,7 +29,7 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { uint256 _chainId, uint256 _protocolVersion, address _l1CtmDeployerAddress, - bytes calldata _forceDeploymentsData, + bytes calldata _fixedForceDeploymentsData, bytes[] calldata _factoryDeps ) public override returns (bytes32) { L2CanonicalTransaction memory l2ProtocolUpgradeTx; @@ -35,9 +37,10 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { { bytes memory complexUpgraderCalldata; { + bytes memory additionalForceDeploymentsData = _getZKChainSpecificForceDeploymentsData(); bytes memory l2GenesisUpgradeCalldata = abi.encodeCall( IL2GenesisUpgrade.genesisUpgrade, - (_chainId, _l1CtmDeployerAddress, _forceDeploymentsData) + (_chainId, _l1CtmDeployerAddress, _fixedForceDeploymentsData, additionalForceDeploymentsData) ); complexUpgraderCalldata = abi.encodeCall( IComplexUpgrader.upgrade, @@ -101,4 +104,16 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { super.upgrade(_proposedUpgrade); return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; } + + function _getZKChainSpecificForceDeploymentsData() internal view returns (bytes memory) { + IL1Nullifier l1Nullifier = IL1AssetRouter(s.baseTokenBridge).L1_NULLIFIER(); + address legacySharedBridge = l1Nullifier.l2BridgeAddress(s.chainId); + ZKChainSpecificForceDeploymentsData + memory additionalForceDeploymentsData = ZKChainSpecificForceDeploymentsData({ + baseTokenAssetId: s.baseTokenAssetId, + l2LegacySharedBridge: legacySharedBridge, + l2Weth: address(0) // kl todo + }); + return abi.encode(additionalForceDeploymentsData); + } } diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 868fdbc47..20ec3cbc7 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -339,7 +339,8 @@ contract DeployL1Script is Script { } function deployGenesisUpgrade() internal { - address contractAddress = deployViaCreate2(type(L1GenesisUpgrade).creationCode); + bytes memory bytecode = abi.encodePacked(type(L1GenesisUpgrade).creationCode); + address contractAddress = deployViaCreate2(bytecode); console.log("GenesisUpgrade deployed at:", contractAddress); addresses.stateTransition.genesisUpgrade = contractAddress; } diff --git a/l1-contracts/deploy-scripts/GenerateForceDeploymentsData.s.sol b/l1-contracts/deploy-scripts/GenerateForceDeploymentsData.s.sol index 3d5a1c042..1c2db4d7d 100644 --- a/l1-contracts/deploy-scripts/GenerateForceDeploymentsData.s.sol +++ b/l1-contracts/deploy-scripts/GenerateForceDeploymentsData.s.sol @@ -95,48 +95,6 @@ contract GenerateForceDeploymentsData is Script { address aliasedGovernance = AddressAliasHelper.applyL1ToL2Alias(config.governance); ForceDeployment[] memory forceDeployments = new ForceDeployment[](4); - forceDeployments[0] = ForceDeployment({ - bytecodeHash: keccak256(contracts.bridgehubBytecode), - newAddress: L2_BRIDGEHUB_ADDR, - callConstructor: true, - value: 0, - input: abi.encode(config.chainId, aliasedGovernance) - }); - - forceDeployments[1] = ForceDeployment({ - bytecodeHash: keccak256(contracts.l2AssetRouterBytecode), - newAddress: L2_ASSET_ROUTER_ADDR, - callConstructor: true, - value: 0, - // solhint-disable-next-line func-named-parameters - input: abi.encode(config.chainId, config.eraChainId, config.l1AssetRouterProxy, address(1)) - }); - - forceDeployments[2] = ForceDeployment({ - bytecodeHash: keccak256(contracts.l2NtvBytecode), - newAddress: L2_NATIVE_TOKEN_VAULT_ADDR, - callConstructor: true, - value: 0, - // solhint-disable-next-line func-named-parameters - input: abi.encode( - config.chainId, - aliasedGovernance, - keccak256(contracts.l2TokenProxyBytecode), - config.l2LegacySharedBridge, - config.l2TokenBeacon, - config.contractsDeployedAlready - ) - }); - - forceDeployments[3] = ForceDeployment({ - bytecodeHash: keccak256(contracts.messageRootBytecode), - newAddress: L2_MESSAGE_ROOT_ADDR, - callConstructor: true, - value: 0, - // solhint-disable-next-line func-named-parameters - input: abi.encode(L2_BRIDGEHUB_ADDR) - }); - config.forceDeploymentsData = abi.encode(forceDeployments); } } diff --git a/l1-contracts/scripts/register-hyperchain.ts b/l1-contracts/scripts/register-zk-chain.ts similarity index 93% rename from l1-contracts/scripts/register-hyperchain.ts rename to l1-contracts/scripts/register-zk-chain.ts index 13013a9d2..b16f81fa8 100644 --- a/l1-contracts/scripts/register-hyperchain.ts +++ b/l1-contracts/scripts/register-zk-chain.ts @@ -8,7 +8,7 @@ import * as fs from "fs"; import * as path from "path"; import { Deployer } from "../src.ts/deploy"; import { GAS_MULTIPLIER, web3Provider } from "./utils"; -import { ADDRESS_ONE, encodeNTVAssetId } from "../src.ts/utils"; +import { ADDRESS_ONE, encodeNTVAssetId, isCurrentNetworkLocal } from "../src.ts/utils"; import { getTokens } from "../src.ts/deploy-token"; const ETH_TOKEN_ADDRESS = ADDRESS_ONE; @@ -103,7 +103,9 @@ async function main() { if (!(await deployer.bridgehubContract(deployWallet).assetIdIsRegistered(baseTokenAssetId))) { await deployer.registerTokenBridgehub(baseTokenAddress, cmd.useGovernance); } - await deployer.registerTokenInNativeTokenVault(baseTokenAddress); + if (baseTokenAddress != ETH_TOKEN_ADDRESS) { + await deployer.registerTokenInNativeTokenVault(baseTokenAddress); + } await deployer.registerZKChain( baseTokenAssetId, cmd.validiumMode, @@ -112,7 +114,8 @@ async function main() { true, null, null, - cmd.useGovernance + cmd.useGovernance, + isCurrentNetworkLocal() || cmd.localLegacyBridgeTesting ); const tokenMultiplierSetterAddress = cmd.tokenMultiplierSetterAddress || ""; diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts index c463ef38d..b4e20f873 100644 --- a/l1-contracts/scripts/sync-layer.ts +++ b/l1-contracts/scripts/sync-layer.ts @@ -16,10 +16,13 @@ import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA, priorityTxMaxGasLimit, L2_BRIDGEHUB_ADDRESS, + computeL2Create2Address, + DIAMOND_CUT_DATA_ABI_STRING, } from "../src.ts/utils"; import { Wallet as ZkWallet, Provider as ZkProvider, utils as zkUtils } from "zksync-ethers"; import { IChainTypeManagerFactory } from "../typechain/IChainTypeManagerFactory"; +import { IDiamondInitFactory } from "../typechain/IDiamondInitFactory"; import { TestnetERC20TokenFactory } from "../typechain/TestnetERC20TokenFactory"; import { BOOTLOADER_FORMAL_ADDRESS } from "zksync-ethers/build/utils"; @@ -30,6 +33,80 @@ async function main() { program.version("0.1.0").name("deploy").description("deploy L1 contracts"); + program + .command("compute-migrated-chain-address") + .requiredOption("--chain-id ") + .option("--private-key ") + .action(async (cmd) => { + if (process.env.CONTRACTS_BASE_NETWORK_ZKSYNC !== "true") { + throw new Error("This script is only for zkSync network"); + } + + const provider = new ZkProvider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const ethProvider = new ethers.providers.JsonRpcProvider(process.env.ETH_CLIENT_WEB3_URL); + const deployWallet = cmd.privateKey + ? new ZkWallet(cmd.privateKey, provider) + : (ZkWallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider) as ethers.Wallet | ZkWallet); + + const deployer = new Deployer({ + deployWallet, + addresses: deployedAddressesFromEnv(), + verbose: true, + }); + + deployer.addresses.StateTransition.AdminFacet = getAddressFromEnv("GATEWAY_ADMIN_FACET_ADDR"); + deployer.addresses.StateTransition.MailboxFacet = getAddressFromEnv("GATEWAY_MAILBOX_FACET_ADDR"); + deployer.addresses.StateTransition.ExecutorFacet = getAddressFromEnv("GATEWAY_EXECUTOR_FACET_ADDR"); + deployer.addresses.StateTransition.GettersFacet = getAddressFromEnv("GATEWAY_GETTERS_FACET_ADDR"); + deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("GATEWAY_DIAMOND_INIT_ADDR"); + deployer.addresses.StateTransition.Verifier = getAddressFromEnv("GATEWAY_VERIFIER_ADDR"); + deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv("GATEWAY_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"); + deployer.addresses.ValidatorTimeLock = getAddressFromEnv("GATEWAY_VALIDATOR_TIMELOCK_ADDR"); + deployer.addresses.Bridges.SharedBridgeProxy = getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"); + deployer.addresses.StateTransition.StateTransitionProxy = getAddressFromEnv( + "GATEWAY_STATE_TRANSITION_PROXY_ADDR" + ); + + const stm = deployer.chainTypeManagerContract(provider); + const bridgehub = deployer.bridgehubContract(ethProvider); + const diamondInit = IDiamondInitFactory.connect(deployer.addresses.StateTransition.DiamondInit, provider); + const bytes32 = (x: ethers.BigNumberish) => ethers.utils.hexZeroPad(ethers.utils.hexlify(x), 32); + + const diamondCut = await deployer.initialZkSyncZKChainDiamondCut([], true); + const mandatoryInitData = [ + diamondInit.interface.getSighash("initialize"), + bytes32(parseInt(cmd.chainId)), + bytes32(getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR")), + bytes32(deployer.addresses.StateTransition.StateTransitionProxy), + bytes32(await stm.protocolVersion()), + bytes32(deployer.deployWallet.address), + bytes32(deployer.addresses.ValidatorTimeLock), + await bridgehub.baseTokenAssetId(cmd.chainId), + bytes32(deployer.addresses.Bridges.SharedBridgeProxy), + await stm.storedBatchZero(), + ]; + + diamondCut.initCalldata = ethers.utils.hexConcat([...mandatoryInitData, diamondCut.initCalldata]); + const bytecode = hardhat.artifacts.readArtifactSync("DiamondProxy").bytecode; + const gatewayChainId = (await provider.getNetwork()).chainId; + const constructorData = new ethers.utils.AbiCoder().encode( + ["uint256", DIAMOND_CUT_DATA_ABI_STRING], + [gatewayChainId, diamondCut] + ); + + const address = computeL2Create2Address( + deployer.addresses.StateTransition.StateTransitionProxy, + bytecode, + constructorData, + ethers.constants.HashZero + ); + + console.log(address); + }); + program .command("deploy-sync-layer-contracts") .option("--private-key ") @@ -62,7 +139,7 @@ async function main() { : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + const nonce = await deployWallet.getTransactionCount(); console.log(`Using nonce: ${nonce}`); const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); diff --git a/l1-contracts/src.ts/deploy-process.ts b/l1-contracts/src.ts/deploy-process.ts index c0f8fd712..4415c8109 100644 --- a/l1-contracts/src.ts/deploy-process.ts +++ b/l1-contracts/src.ts/deploy-process.ts @@ -128,6 +128,7 @@ export async function registerZKChain( false, null, chainId, - useGovernance + useGovernance, + true ); } diff --git a/l1-contracts/src.ts/deploy-test-process.ts b/l1-contracts/src.ts/deploy-test-process.ts index f6b0d00fb..07b3fcfb9 100644 --- a/l1-contracts/src.ts/deploy-test-process.ts +++ b/l1-contracts/src.ts/deploy-test-process.ts @@ -104,7 +104,8 @@ export async function initialTestnetDeploymentProcess( deployer.chainId = 9; const testnetTokens = getTokens(); - const result = await deployTokens(testnetTokens, deployer.deployWallet, null, false, deployer.verbose); + const result = await deployTokens(testnetTokens, deployer.deployWallet, null, true, deployer.verbose); + fs.writeFileSync(testnetTokenPath, JSON.stringify(result, null, 2)); // deploy the verifier first diff --git a/l1-contracts/src.ts/deploy-token.ts b/l1-contracts/src.ts/deploy-token.ts index 324e754c8..ea22d7029 100644 --- a/l1-contracts/src.ts/deploy-token.ts +++ b/l1-contracts/src.ts/deploy-token.ts @@ -125,13 +125,15 @@ export async function deployTokens( } if (token.symbol !== "WETH" && mintTokens) { - await erc20.mint(wallet.address, parseEther("3000000000")); + await erc20.mint(wallet.address, parseEther("3000000000000")); } if (mintTokens) { for (let i = 0; i < 10; ++i) { - const testWalletAddress = Wallet.fromMnemonic(mnemonic as string, "m/44'/60'/0'/0/" + i).address; + const testWalletAddress = mnemonic + ? Wallet.fromMnemonic(mnemonic as string, "m/44'/60'/0'/0/" + i).address + : wallet.address; if (token.symbol !== "WETH") { - await erc20.mint(testWalletAddress, parseEther("3000000000")); + await erc20.mint(testWalletAddress, parseEther("3000000000000")); } } } diff --git a/l1-contracts/src.ts/deploy-utils-zk.ts b/l1-contracts/src.ts/deploy-utils-zk.ts index ec9b50a03..de7287f90 100644 --- a/l1-contracts/src.ts/deploy-utils-zk.ts +++ b/l1-contracts/src.ts/deploy-utils-zk.ts @@ -9,7 +9,7 @@ import type { Wallet as ZkWallet } from "zksync-ethers"; import { utils as zkUtils, ContractFactory } from "zksync-ethers"; // import { encode } from "querystring"; // import { web3Provider, web3Url } from "../scripts/utils"; -import { ethersWalletToZkWallet, readBytecode, readInterface } from "./utils"; +import { ethersWalletToZkWallet, readBytecode, readContract, readInterface } from "./utils"; export const BUILT_IN_ZKSYNC_CREATE2_FACTORY = "0x0000000000000000000000000000000000010000"; @@ -20,12 +20,15 @@ const openzeppelinBeaconProxyArtifactsPath = path.join( "@openzeppelin/contracts-v4/proxy/beacon" ); const L2_SHARED_BRIDGE_PATH = contractArtifactsPath + "contracts/bridge"; -export const L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE = readBytecode( - openzeppelinBeaconProxyArtifactsPath, - "UpgradeableBeacon" +export const L2_STANDARD_ERC20_PROXY_FACTORY = readContract(openzeppelinBeaconProxyArtifactsPath, "UpgradeableBeacon"); +export const L2_STANDARD_ERC20_IMPLEMENTATION = readContract(L2_SHARED_BRIDGE_PATH, "BridgedStandardERC20"); +export const L2_STANDARD_TOKEN_PROXY = readContract(openzeppelinBeaconProxyArtifactsPath, "BeaconProxy"); + +export const L2_SHARED_BRIDGE_IMPLEMENTATION = readContract(L2_SHARED_BRIDGE_PATH, "L2SharedBridgeLegacy"); +export const L2_SHARED_BRIDGE_PROXY = readContract( + contractArtifactsPath + "@openzeppelin/contracts-v4/proxy/transparent", + "TransparentUpgradeableProxy" ); -export const L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE = readBytecode(L2_SHARED_BRIDGE_PATH, "BridgedStandardERC20"); -export const L2_STANDARD_TOKEN_PROXY_BYTECODE = readBytecode(openzeppelinBeaconProxyArtifactsPath, "BeaconProxy"); export async function deployViaCreate2( deployWallet: ZkWallet, diff --git a/l1-contracts/src.ts/deploy-utils.ts b/l1-contracts/src.ts/deploy-utils.ts index d9e7264c1..dcec2c180 100644 --- a/l1-contracts/src.ts/deploy-utils.ts +++ b/l1-contracts/src.ts/deploy-utils.ts @@ -1,9 +1,19 @@ import * as hardhat from "hardhat"; import "@nomiclabs/hardhat-ethers"; import { ethers } from "ethers"; +import { Interface } from "ethers/lib/utils"; import { SingletonFactoryFactory } from "../typechain"; -import { encodeNTVAssetId, getAddressFromEnv, getNumberFromEnv } from "./utils"; +import { + encodeNTVAssetId, + getAddressFromEnv, + getNumberFromEnv, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + ADDRESS_ONE, +} from "./utils"; +import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; +import { IERC20Factory } from "../typechain/IERC20Factory"; export async function deployViaCreate2( deployWallet: ethers.Wallet, @@ -98,6 +108,91 @@ export async function deployContractWithArgs( return await factory.deploy(...args, ethTxOptions); } +export function hashL2Bytecode(bytecode: ethers.BytesLike): Uint8Array { + // For getting the consistent length we first convert the bytecode to UInt8Array + const bytecodeAsArray = ethers.utils.arrayify(bytecode); + + if (bytecodeAsArray.length % 32 != 0) { + throw new Error("The bytecode length in bytes must be divisible by 32"); + } + + const hashStr = ethers.utils.sha256(bytecodeAsArray); + const hash = ethers.utils.arrayify(hashStr); + + // Note that the length of the bytecode + // should be provided in 32-byte words. + const bytecodeLengthInWords = bytecodeAsArray.length / 32; + if (bytecodeLengthInWords % 2 == 0) { + throw new Error("Bytecode length in 32-byte words must be odd"); + } + const bytecodeLength = ethers.utils.arrayify(bytecodeAsArray.length / 32); + if (bytecodeLength.length > 2) { + throw new Error("Bytecode length must be less than 2^16 bytes"); + } + // The bytecode should always take the first 2 bytes of the bytecode hash, + // so we pad it from the left in case the length is smaller than 2 bytes. + const bytecodeLengthPadded = ethers.utils.zeroPad(bytecodeLength, 2); + + const codeHashVersion = new Uint8Array([1, 0]); + hash.set(codeHashVersion, 0); + hash.set(bytecodeLengthPadded, 2); + + return hash; +} + +export async function create2DeployFromL1( + chainId: ethers.BigNumberish, + wallet: ethers.Wallet, + bytecode: ethers.BytesLike, + constructor: ethers.BytesLike, + create2Salt: ethers.BytesLike, + l2GasLimit: ethers.BigNumberish, + gasPrice?: ethers.BigNumberish, + extraFactoryDeps?: ethers.BytesLike[], + bridgehubAddress?: string, + assetRouterAddress?: string +) { + bridgehubAddress = bridgehubAddress ?? deployedAddressesFromEnv().Bridgehub.BridgehubProxy; + const bridgehub = IBridgehubFactory.connect(bridgehubAddress, wallet); + + const deployerSystemContracts = new Interface(hardhat.artifacts.readArtifactSync("IContractDeployer").abi); + const bytecodeHash = hashL2Bytecode(bytecode); + const calldata = deployerSystemContracts.encodeFunctionData("create2", [create2Salt, bytecodeHash, constructor]); + gasPrice ??= await bridgehub.provider.getGasPrice(); + const expectedCost = await bridgehub.l2TransactionBaseCost( + chainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + const baseTokenAddress = await bridgehub.baseToken(chainId); + const baseTokenBridge = assetRouterAddress ?? deployedAddressesFromEnv().Bridges.SharedBridgeProxy; + const ethIsBaseToken = ADDRESS_ONE == baseTokenAddress; + + if (!ethIsBaseToken) { + const baseToken = IERC20Factory.connect(baseTokenAddress, wallet); + const tx = await baseToken.approve(baseTokenBridge, expectedCost); + await tx.wait(); + } + const factoryDeps = extraFactoryDeps ? [bytecode, ...extraFactoryDeps] : [bytecode]; + + return await bridgehub.requestL2TransactionDirect( + { + chainId, + l2Contract: DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + mintValue: expectedCost, + l2Value: 0, + l2Calldata: calldata, + l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: factoryDeps, + refundRecipient: wallet.address, + }, + { value: ethIsBaseToken ? expectedCost : 0, gasPrice } + ); +} + export interface DeployedAddresses { Bridgehub: { BridgehubProxy: string; @@ -130,6 +225,8 @@ export interface DeployedAddresses { SharedBridgeProxy: string; L2SharedBridgeProxy: string; L2SharedBridgeImplementation: string; + L2LegacySharedBridgeProxy: string; + L2LegacySharedBridgeImplementation: string; L2NativeTokenVaultImplementation: string; L2NativeTokenVaultProxy: string; NativeTokenVaultImplementation: string; @@ -195,6 +292,8 @@ export function deployedAddressesFromEnv(): DeployedAddresses { L2NativeTokenVaultProxy: getAddressFromEnv("CONTRACTS_L2_NATIVE_TOKEN_VAULT_PROXY_ADDR"), L2SharedBridgeImplementation: getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_IMPL_ADDR"), L2SharedBridgeProxy: getAddressFromEnv("CONTRACTS_L2_SHARED_BRIDGE_ADDR"), + L2LegacySharedBridgeProxy: getAddressFromEnv("CONTRACTS_L2_LEGACY_SHARED_BRIDGE_ADDR"), + L2LegacySharedBridgeImplementation: getAddressFromEnv("CONTRACTS_L2_LEGACY_SHARED_BRIDGE_IMPL_ADDR"), NativeTokenVaultImplementation: getAddressFromEnv("CONTRACTS_L1_NATIVE_TOKEN_VAULT_IMPL_ADDR"), NativeTokenVaultProxy: getAddressFromEnv("CONTRACTS_L1_NATIVE_TOKEN_VAULT_PROXY_ADDR"), BridgedStandardERC20Implementation: getAddressFromEnv("CONTRACTS_L1_BRIDGED_STANDARD_ERC20_IMPL_ADDR"), diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index c75a46e1f..cd3e03baf 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -12,13 +12,16 @@ import { deployedAddressesFromEnv, deployBytecodeViaCreate2 as deployBytecodeViaCreate2EVM, deployViaCreate2 as deployViaCreate2EVM, + create2DeployFromL1, } from "./deploy-utils"; import { deployViaCreate2 as deployViaCreate2Zk, BUILT_IN_ZKSYNC_CREATE2_FACTORY, - L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, - L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE, - L2_STANDARD_TOKEN_PROXY_BYTECODE, + L2_STANDARD_ERC20_PROXY_FACTORY, + L2_STANDARD_ERC20_IMPLEMENTATION, + L2_STANDARD_TOKEN_PROXY, + L2_SHARED_BRIDGE_IMPLEMENTATION, + L2_SHARED_BRIDGE_PROXY, // deployBytecodeViaCreate2OnPath, // L2_SHARED_BRIDGE_PATH, } from "./deploy-utils-zk"; @@ -40,17 +43,16 @@ import { PubdataPricingMode, hashL2Bytecode, DIAMOND_CUT_DATA_ABI_STRING, - FORCE_DEPLOYMENT_ABI_STRING, - L2_BRIDGEHUB_ADDRESS, - L2_NATIVE_TOKEN_VAULT_ADDRESS, - L2_ASSET_ROUTER_ADDRESS, + FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, compileInitialCutHash, readBytecode, applyL1ToL2Alias, BRIDGEHUB_CTM_ASSET_DATA_ABI_STRING, encodeNTVAssetId, - L2_MESSAGE_ROOT_ADDRESS, + computeL2Create2Address, + priorityTxMaxGasLimit, + isCurrentNetworkLocal, } from "./utils"; import type { ChainAdminCall } from "./utils"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; @@ -59,6 +61,7 @@ import { ProxyAdminFactory } from "../typechain/ProxyAdminFactory"; import { IZKChainFactory } from "../typechain/IZKChainFactory"; import { L1AssetRouterFactory } from "../typechain/L1AssetRouterFactory"; +import { L1NullifierDevFactory } from "../typechain/L1NullifierDevFactory"; import { SingletonFactoryFactory } from "../typechain/SingletonFactoryFactory"; import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory"; @@ -196,62 +199,20 @@ export class Deployer { ); l2TokenProxyBytecodeHash = ethers.utils.hexlify(hashL2Bytecode(l2TokenProxyBytecode)); } - - const bridgehubDeployment = { - bytecodeHash: ethers.utils.hexlify(hashL2Bytecode(bridgehubZKBytecode)), - newAddress: L2_BRIDGEHUB_ADDRESS, - callConstructor: true, - value: 0, - input: ethers.utils.defaultAbiCoder.encode( - ["uint256", "address", "uint256"], - [ - getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), - applyL1ToL2Alias(this.addresses.Governance), - getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_ZK_CHAINS"), - ] - ), - }; - const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); - const assetRouterDeployment = { - bytecodeHash: ethers.utils.hexlify(hashL2Bytecode(assetRouterZKBytecode)), - newAddress: L2_ASSET_ROUTER_ADDRESS, - callConstructor: true, - value: 0, - input: ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint256", "address", "address"], - [getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), eraChainId, this.addresses.Bridges.SharedBridgeProxy, ADDRESS_ONE] - ), - }; - const tokens = getTokens(); - const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; - const ntvDeployment = { - bytecodeHash: ethers.utils.hexlify(hashL2Bytecode(nativeTokenVaultZKBytecode)), - newAddress: L2_NATIVE_TOKEN_VAULT_ADDRESS, - callConstructor: true, - value: 0, - input: ethers.utils.defaultAbiCoder.encode( - ["uint256", "address", "bytes32", "address", "address", "bool", "address"], - [ - getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), - applyL1ToL2Alias(this.addresses.Governance), - l2TokenProxyBytecodeHash, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - false, - l1WethToken, - ] - ), - }; - const messageRootDeployment = { - bytecodeHash: ethers.utils.hexlify(hashL2Bytecode(messageRootZKBytecode)), - newAddress: L2_MESSAGE_ROOT_ADDRESS, - callConstructor: true, - value: 0, - input: ethers.utils.defaultAbiCoder.encode(["address"], [L2_BRIDGEHUB_ADDRESS]), + const fixedForceDeploymentsData = { + l1ChainId: getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), + eraChainId: getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"), + l1AssetRouter: this.addresses.Bridges.SharedBridgeProxy, + l2TokenProxyBytecodeHash: l2TokenProxyBytecodeHash, + aliasedL1Governance: applyL1ToL2Alias(this.addresses.Governance), + maxNumberOfZKChains: getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_ZK_CHAINS"), + bridgehubBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(bridgehubZKBytecode)), + l2AssetRouterBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(assetRouterZKBytecode)), + l2NtvBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(nativeTokenVaultZKBytecode)), + messageRootBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(messageRootZKBytecode)), }; - const forceDeployments = [messageRootDeployment, bridgehubDeployment, assetRouterDeployment, ntvDeployment]; - return ethers.utils.defaultAbiCoder.encode([FORCE_DEPLOYMENT_ABI_STRING], [forceDeployments]); + return ethers.utils.defaultAbiCoder.encode([FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING], [fixedForceDeploymentsData]); } public async updateCreate2FactoryZkMode() { @@ -848,8 +809,9 @@ export class Deployer { // const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const eraDiamondProxy = getAddressFromEnv("CONTRACTS_ERA_DIAMOND_PROXY_ADDR"); + const contractName = isCurrentNetworkLocal() ? "L1NullifierDev" : "L1Nullifier"; const contractAddress = await this.deployViaCreate2( - "L1Nullifier", + contractName, [this.addresses.Bridgehub.BridgehubProxy, eraChainId, eraDiamondProxy], create2Salt, ethTxOptions @@ -1009,6 +971,7 @@ export class Deployer { const nullifier = this.l1NullifierContract(this.deployWallet); const assetRouter = this.defaultSharedBridge(this.deployWallet); + const ntv = this.nativeTokenVault(this.deployWallet); const data = await assetRouter.interface.encodeFunctionData("setNativeTokenVault", [ this.addresses.Bridges.NativeTokenVaultProxy, @@ -1035,6 +998,8 @@ export class Deployer { } await (await this.nativeTokenVault(this.deployWallet).registerEthToken()).wait(); + + await ntv.registerEthToken(); } public async deployCTMDeploymentTrackerImplementation( @@ -1250,6 +1215,8 @@ export class Deployer { `CONTRACTS_CTM_ASSET_INFO=${await bridgehub.ctmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` ); } + } else { + console.log(`CONTRACTS_CTM_ASSET_INFO=${getHashFromEnv("CONTRACTS_CTM_ASSET_INFO")}`); } } } @@ -1292,6 +1259,7 @@ export class Deployer { const chainAssetId = await bridgehub.ctmAssetIdFromChainId(this.chainId); if (this.verbose) { console.log("Chain asset id is: ", chainAssetId); + console.log(`CONTRACTS_CTM_ASSET_INFO=${chainAssetId}`); } let sharedBridgeData = ethers.utils.defaultAbiCoder.encode( @@ -1367,7 +1335,8 @@ export class Deployer { compareDiamondCutHash: boolean = false, nonce?, predefinedChainId?: string, - useGovernance: boolean = false + useGovernance: boolean = false, + l2LegacySharedBridge: boolean = false ) { const txOptions = this.isZkMode() ? {} : { gasLimit: 10_000_000 }; @@ -1381,6 +1350,12 @@ export class Deployer { const inputChainId = predefinedChainId || getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); const alreadyRegisteredInCTM = (await chainTypeManager.getZKChain(inputChainId)) != ethers.constants.AddressZero; + if (l2LegacySharedBridge) { + console.log("Setting L2 legacy shared bridge in L1Nullifier"); + await this.setL2LegacySharedBridgeInL1Nullifier(inputChainId); + nonce++; + } + const admin = process.env.CHAIN_ADMIN_ADDRESS || this.ownerAddress; const diamondCutData = await this.initialZkSyncZKChainDiamondCut(extraFacets, compareDiamondCutHash); const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); @@ -1389,9 +1364,9 @@ export class Deployer { let factoryDeps = []; if (process.env.CHAIN_ETH_NETWORK != "hardhat") { factoryDeps = [ - L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, - L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE, - L2_STANDARD_TOKEN_PROXY_BYTECODE, + L2_STANDARD_ERC20_PROXY_FACTORY.bytecode, + L2_STANDARD_ERC20_IMPLEMENTATION.bytecode, + L2_STANDARD_TOKEN_PROXY.bytecode, ]; } // note the factory deps are provided at genesis @@ -1431,7 +1406,9 @@ export class Deployer { console.log(`ZK chain registration tx hash: ${receipt.transactionHash}`); console.log(`CHAIN_ETH_ZKSYNC_NETWORK_ID=${parseInt(chainId, 16)}`); - + console.log( + `CONTRACTS_CTM_ASSET_INFO=${await bridgehub.ctmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); console.log(`CONTRACTS_BASE_TOKEN_ADDR=${baseTokenAddress}`); } @@ -1498,6 +1475,173 @@ export class Deployer { "BaseTokenMultiplier and Validium mode can't be set through the governance, please set it separately, using the admin account" ); } + + if (l2LegacySharedBridge) { + await this.deployL2LegacySharedBridge(inputChainId, gasPrice); + } + } + + public async setL2LegacySharedBridgeInL1Nullifier(inputChainId: string) { + const l1Nullifier = L1NullifierDevFactory.connect(this.addresses.Bridges.L1NullifierProxy, this.deployWallet); + const l1SharedBridge = this.defaultSharedBridge(this.deployWallet); + + if (isCurrentNetworkLocal()) { + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); + + const l2SharedBridgeImplementationBytecode = L2_SHARED_BRIDGE_IMPLEMENTATION.bytecode; + + const l2SharedBridgeImplAddress = computeL2Create2Address( + this.deployWallet.address, + l2SharedBridgeImplementationBytecode, + ethers.utils.defaultAbiCoder.encode(["uint256"], [eraChainId]), + ethers.constants.HashZero + ); + + const l2GovernorAddress = applyL1ToL2Alias(this.addresses.Governance); + + const l2SharedBridgeInterface = new Interface(L2_SHARED_BRIDGE_IMPLEMENTATION.abi); + const proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ + l1SharedBridge.address, + this.addresses.Bridges.ERC20BridgeProxy, + hashL2Bytecode(L2_STANDARD_TOKEN_PROXY.bytecode), + l2GovernorAddress, + ]); + + const l2SharedBridgeProxyConstructorData = ethers.utils.arrayify( + new ethers.utils.AbiCoder().encode( + ["address", "address", "bytes"], + [l2SharedBridgeImplAddress, l2GovernorAddress, proxyInitializationParams] + ) + ); + + /// compute L2SharedBridgeProxy address + const l2SharedBridgeProxyAddress = computeL2Create2Address( + this.deployWallet.address, + L2_SHARED_BRIDGE_PROXY.bytecode, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero + ); + + const tx = await l1Nullifier.setL2LegacySharedBridge(inputChainId, l2SharedBridgeProxyAddress); + const receipt8 = await tx.wait(); + if (this.verbose) { + console.log(`L2 legacy shared bridge set in L1 Nullifier, gas used: ${receipt8.gasUsed.toString()}`); + } + } + } + + public async deployL2LegacySharedBridge(inputChainId: string, gasPrice: BigNumberish) { + if (this.verbose) { + console.log("Deploying L2 legacy shared bridge"); + } + await this.deploySharedBridgeImplOnL2ThroughL1(inputChainId, gasPrice); + await this.deploySharedBridgeProxyOnL2ThroughL1(inputChainId, gasPrice); + } + + public async deploySharedBridgeImplOnL2ThroughL1(chainId: string, gasPrice: BigNumberish) { + if (this.verbose) { + console.log("Deploying L2SharedBridge Implementation"); + } + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); + + const l2SharedBridgeImplementationBytecode = L2_SHARED_BRIDGE_IMPLEMENTATION.bytecode; + // localLegacyBridgeTesting + // ? L2_DEV_SHARED_BRIDGE_IMPLEMENTATION.bytecode + // : L2_SHARED_BRIDGE_IMPLEMENTATION.bytecode; + if (!l2SharedBridgeImplementationBytecode) { + throw new Error("l2SharedBridgeImplementationBytecode not found"); + } + + if (this.verbose) { + console.log("l2SharedBridgeImplementationBytecode loaded"); + + console.log("Computing L2SharedBridge Implementation Address"); + } + + const l2SharedBridgeImplAddress = computeL2Create2Address( + this.deployWallet.address, + l2SharedBridgeImplementationBytecode, + ethers.utils.defaultAbiCoder.encode(["uint256"], [eraChainId]), + ethers.constants.HashZero + ); + this.addresses.Bridges.L2LegacySharedBridgeImplementation = l2SharedBridgeImplAddress; + + if (this.verbose) { + console.log(`L2SharedBridge Implementation Address: ${l2SharedBridgeImplAddress}`); + + console.log("Deploying L2SharedBridge Implementation"); + } + // TODO: request from API how many L2 gas needs for the transaction. + const tx2 = await create2DeployFromL1( + chainId, + this.deployWallet, + l2SharedBridgeImplementationBytecode, + ethers.utils.defaultAbiCoder.encode(["uint256"], [eraChainId]), + ethers.constants.HashZero, + priorityTxMaxGasLimit, + gasPrice, + [L2_STANDARD_TOKEN_PROXY.bytecode], + this.addresses.Bridgehub.BridgehubProxy, + this.addresses.Bridges.SharedBridgeProxy + ); + await tx2.wait(); + + if (this.verbose) { + console.log("Deployed L2SharedBridge Implementation"); + console.log(`CONTRACTS_L2_LEGACY_SHARED_BRIDGE_IMPL_ADDR=${l2SharedBridgeImplAddress}`); + } + } + + public async deploySharedBridgeProxyOnL2ThroughL1(chainId: string, gasPrice: BigNumberish) { + const l1SharedBridge = this.defaultSharedBridge(this.deployWallet); + if (this.verbose) { + console.log("Deploying L2SharedBridge Proxy"); + } + const l2GovernorAddress = applyL1ToL2Alias(this.addresses.Governance); + + const l2SharedBridgeInterface = new Interface(L2_SHARED_BRIDGE_IMPLEMENTATION.abi); + const proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ + l1SharedBridge.address, + this.addresses.Bridges.ERC20BridgeProxy, + hashL2Bytecode(L2_STANDARD_TOKEN_PROXY.bytecode), + l2GovernorAddress, + ]); + + /// prepare constructor data + const l2SharedBridgeProxyConstructorData = ethers.utils.arrayify( + new ethers.utils.AbiCoder().encode( + ["address", "address", "bytes"], + [this.addresses.Bridges.L2LegacySharedBridgeImplementation, l2GovernorAddress, proxyInitializationParams] + ) + ); + + /// compute L2SharedBridgeProxy address + const l2SharedBridgeProxyAddress = computeL2Create2Address( + this.deployWallet.address, + L2_SHARED_BRIDGE_PROXY.bytecode, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero + ); + this.addresses.Bridges.L2LegacySharedBridgeProxy = l2SharedBridgeProxyAddress; + + /// deploy L2SharedBridgeProxy + // TODO: request from API how many L2 gas needs for the transaction. + const tx3 = await create2DeployFromL1( + chainId, + this.deployWallet, + L2_SHARED_BRIDGE_PROXY.bytecode, + l2SharedBridgeProxyConstructorData, + ethers.constants.HashZero, + priorityTxMaxGasLimit, + gasPrice, + undefined, + this.addresses.Bridgehub.BridgehubProxy, + this.addresses.Bridges.SharedBridgeProxy + ); + await tx3.wait(); + if (this.verbose) { + console.log(`CONTRACTS_L2_LEGACY_SHARED_BRIDGE_ADDR=${l2SharedBridgeProxyAddress}`); + } } public async executeChainAdminMulticall(calls: ChainAdminCall[], requireSuccess: boolean = true) { diff --git a/l1-contracts/src.ts/utils.ts b/l1-contracts/src.ts/utils.ts index 92f38244f..ce57958e0 100644 --- a/l1-contracts/src.ts/utils.ts +++ b/l1-contracts/src.ts/utils.ts @@ -28,6 +28,7 @@ export const L2_BRIDGEHUB_ADDRESS = "0x0000000000000000000000000000000000010002" export const L2_ASSET_ROUTER_ADDRESS = "0x0000000000000000000000000000000000010003"; export const L2_NATIVE_TOKEN_VAULT_ADDRESS = "0x0000000000000000000000000000000000010004"; export const L2_MESSAGE_ROOT_ADDRESS = "0x0000000000000000000000000000000000010005"; +export const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; export const EMPTY_STRING_KECCAK = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate2"]); @@ -45,6 +46,9 @@ export const DIAMOND_CUT_DATA_ABI_STRING = export const FORCE_DEPLOYMENT_ABI_STRING = "tuple(bytes32 bytecodeHash, address newAddress, bool callConstructor, uint256 value, bytes input)[]"; export const BRIDGEHUB_CTM_ASSET_DATA_ABI_STRING = "tuple(uint256 chainId, bytes ctmData, bytes chainData)"; +export const FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING = + "tuple(uint256 l1ChainId, uint256 eraChainId, address l1AssetRouter, bytes32 l2TokenProxyBytecodeHash, address aliasedL1Governance, uint256 maxNumberOfZKChains, bytes32 bridgehubBytecodeHash, bytes32 l2AssetRouterBytecodeHash, bytes32 l2NtvBytecodeHash, bytes32 messageRootBytecodeHash)"; +export const ADDITIONAL_FORCE_DEPLOYMENTS_DATA_ABI_STRING = "tuple(bytes32 baseTokenAssetId, address l2Weth)"; export function applyL1ToL2Alias(address: string): string { return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); @@ -59,6 +63,10 @@ export function readInterface(path: string, fileName: string) { return new ethers.utils.Interface(abi); } +export function readContract(path: string, fileName: string) { + return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })); +} + export function hashL2Bytecode(bytecode: ethers.BytesLike): Uint8Array { // For getting the consistent length we first convert the bytecode to UInt8Array const bytecodeAsArray = ethers.utils.arrayify(bytecode); diff --git a/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol b/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol index f7bd23b28..6eb78944d 100644 --- a/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol +++ b/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol @@ -486,7 +486,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe bytes32[] memory merkleProof = new bytes32[](1); _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); - uint256 beforeChainBalance = l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); uint256 beforeBalance = currentToken.balanceOf(address(sharedBridge)); if (beforeChainBalance < amountToWithdraw) { @@ -534,7 +534,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe // check if the balance was updated correctly if (beforeChainBalance > amountToWithdraw) { assertEq( - beforeChainBalance - l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress), + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), amountToWithdraw ); assertEq(beforeBalance - currentToken.balanceOf(address(sharedBridge)), amountToWithdraw); @@ -548,7 +548,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe bytes32[] memory merkleProof = new bytes32[](1); _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); - uint256 beforeChainBalance = l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); uint256 beforeBalance = address(sharedBridge).balance; if (beforeChainBalance < amountToWithdraw) { @@ -590,7 +590,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe // check if the balance was updated correctly if (beforeChainBalance > amountToWithdraw) { assertEq( - beforeChainBalance - l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress), + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), amountToWithdraw ); assertEq(beforeBalance - address(sharedBridge).balance, amountToWithdraw); diff --git a/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol b/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol index 4e262ad50..09b07cca0 100644 --- a/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol +++ b/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol @@ -486,7 +486,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe bytes32[] memory merkleProof = new bytes32[](1); _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); - uint256 beforeChainBalance = l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); uint256 beforeBalance = currentToken.balanceOf(address(sharedBridge)); if (beforeChainBalance < amountToWithdraw) { @@ -534,7 +534,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe // check if the balance was updated correctly if (beforeChainBalance > amountToWithdraw) { assertEq( - beforeChainBalance - l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress), + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), amountToWithdraw ); assertEq(beforeBalance - currentToken.balanceOf(address(sharedBridge)), amountToWithdraw); @@ -548,7 +548,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe bytes32[] memory merkleProof = new bytes32[](1); _setSharedBridgeIsWithdrawalFinalized(currentChainId, l2BatchNumber, l2MessageIndex, false); - uint256 beforeChainBalance = l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress); + uint256 beforeChainBalance = l1Nullifier.chainBalance(currentChainId, currentTokenAddress); uint256 beforeBalance = address(sharedBridge).balance; if (beforeChainBalance < amountToWithdraw) { @@ -590,7 +590,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe // check if the balance was updated correctly if (beforeChainBalance > amountToWithdraw) { assertEq( - beforeChainBalance - l1Nullifier.__DEPRECATED_chainBalance(currentChainId, currentTokenAddress), + beforeChainBalance - l1Nullifier.chainBalance(currentChainId, currentTokenAddress), amountToWithdraw ); assertEq(beforeBalance - address(sharedBridge).balance, amountToWithdraw); diff --git a/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol index 49c6e5c43..20d0840be 100644 --- a/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol +++ b/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol @@ -91,7 +91,7 @@ contract L1ContractDeployer is Test { function _setSharedBridgeChainBalance(uint256 _chainId, address _token, uint256 _value) internal { stdstore .target(address(l1Nullifier)) - .sig(l1Nullifier.__DEPRECATED_chainBalance.selector) + .sig(l1Nullifier.chainBalance.selector) .with_key(_chainId) .with_key(_token) .checked_write(_value); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol index 6af9fb369..e5a86bc2d 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1Erc20Bridge/FinalizeWithdrawal.sol @@ -60,6 +60,12 @@ contract FinalizeWithdrawalTest is L1Erc20BridgeTest { abi.encodeWithSelector(IL1Nullifier.finalizeDeposit.selector, finalizeWithdrawalParams), abi.encode(alice, address(token), amount) ); + address l2BridgeAddress = address(12); + vm.mockCall( + l1NullifierAddress, + abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector, eraChainId), + abi.encode(l2BridgeAddress) + ); vm.prank(alice); // solhint-disable-next-line func-named-parameters diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol index 52bf0fbb0..c67fa6a36 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol @@ -281,7 +281,7 @@ contract L1AssetRouterTest is Test { function _setSharedBridgeChainBalance(uint256 _chainId, address _token, uint256 _value) internal { stdstore .target(address(l1Nullifier)) - .sig(l1Nullifier.__DEPRECATED_chainBalance.selector) + .sig(l1Nullifier.chainBalance.selector) .with_key(_chainId) .with_key(_token) .checked_write(_value); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index e6c089a56..f97ecc08a 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -20,6 +20,8 @@ import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; contract PermanentRestrictionTest is ChainTypeManagerTest { ChainAdmin internal chainAdmin; @@ -202,6 +204,18 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { bridgehub.addChainTypeManager(address(chainContractAddress)); bridgehub.addTokenAssetId(DataEncoding.encodeNTVAssetId(block.chainid, baseToken)); bridgehub.setAddresses(sharedBridge, ICTMDeploymentTracker(address(0)), new MessageRoot(bridgehub)); + address l1Nullifier = makeAddr("l1Nullifier"); + address l2LegacySharedBridge = makeAddr("l2LegacySharedBridge"); + vm.mockCall( + address(sharedBridge), + abi.encodeWithSelector(IL1AssetRouter.L1_NULLIFIER.selector), + abi.encode(l1Nullifier) + ); + vm.mockCall( + address(l1Nullifier), + abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector), + abi.encode(l2LegacySharedBridge) + ); bridgehub.createNewChain({ _chainId: chainId, _chainTypeManager: address(chainContractAddress), diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol index 6980aa52f..99d8c9859 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol @@ -16,7 +16,7 @@ import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Execut import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; -import {L1GenesisUpgrade as GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; import {InitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; import {ChainTypeManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; @@ -28,7 +28,7 @@ import {ZeroAddress} from "contracts/common/L1ContractErrors.sol"; contract ChainTypeManagerTest is Test { ChainTypeManager internal chainTypeManager; ChainTypeManager internal chainContractAddress; - GenesisUpgrade internal genesisUpgradeContract; + L1GenesisUpgrade internal genesisUpgradeContract; Bridgehub internal bridgehub; address internal diamondInit; address internal constant governor = address(0x1010101); @@ -40,6 +40,7 @@ contract ChainTypeManagerTest is Test { uint256 chainId = 112; address internal testnetVerifier = address(new TestnetVerifier()); bytes internal forceDeploymentsData = hex""; + uint256 eraChainId = 9; Diamond.FacetCut[] internal facetCuts; @@ -50,7 +51,7 @@ contract ChainTypeManagerTest is Test { vm.startPrank(address(bridgehub)); chainTypeManager = new ChainTypeManager(address(IBridgehub(address(bridgehub)))); diamondInit = address(new DiamondInit()); - genesisUpgradeContract = new GenesisUpgrade(); + genesisUpgradeContract = new L1GenesisUpgrade(); facetCuts.push( Diamond.FacetCut({ diff --git a/l1-contracts/test/foundry/l2/unit/erc20/L2Erc20BridgeTest.t.sol b/l1-contracts/test/foundry/l2/unit/erc20/L2Erc20BridgeTest.t.sol index 1b9535108..5e195353e 100644 --- a/l1-contracts/test/foundry/l2/unit/erc20/L2Erc20BridgeTest.t.sol +++ b/l1-contracts/test/foundry/l2/unit/erc20/L2Erc20BridgeTest.t.sol @@ -60,7 +60,7 @@ contract L2Erc20BridgeTest is Test { } L2Utils.initSystemContracts(); - L2Utils.forceDeployAssetRouter(L1_CHAIN_ID, ERA_CHAIN_ID, l1BridgeWallet, address(0)); + L2Utils.forceDeployAssetRouter(L1_CHAIN_ID, ERA_CHAIN_ID, ownerWallet, l1BridgeWallet, address(0)); L2Utils.forceDeployNativeTokenVault({ _l1ChainId: L1_CHAIN_ID, _aliasedOwner: ownerWallet, diff --git a/l1-contracts/test/foundry/l2/unit/utils/L2Utils.sol b/l1-contracts/test/foundry/l2/unit/utils/L2Utils.sol index 2c46774f4..9da81fe5d 100644 --- a/l1-contracts/test/foundry/l2/unit/utils/L2Utils.sol +++ b/l1-contracts/test/foundry/l2/unit/utils/L2Utils.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import {Vm} from "forge-std/Vm.sol"; +import "forge-std/console.sol"; import {DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IContractDeployer, L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; @@ -66,13 +67,14 @@ library L2Utils { function forceDeployAssetRouter( uint256 _l1ChainId, uint256 _eraChainId, + address _aliasedOwner, address _l1AssetRouter, address _legacySharedBridge ) internal { // to ensure that the bytecode is known bytes32 ethAssetId = DataEncoding.encodeNTVAssetId(_l1ChainId, ETH_TOKEN_ADDRESS); { - new L2AssetRouter(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId); + new L2AssetRouter(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId, _aliasedOwner); } bytes memory bytecode = readEraBytecode("L2AssetRouter"); @@ -85,7 +87,7 @@ library L2Utils { newAddress: L2_ASSET_ROUTER_ADDR, callConstructor: true, value: 0, - input: abi.encode(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId) + input: abi.encode(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId, _aliasedOwner) }); vm.prank(L2_FORCE_DEPLOYER_ADDR); diff --git a/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts b/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts index d670f9306..532fd57d1 100644 --- a/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts +++ b/l1-contracts/test/unit_tests/initial_deployment_test.spec.ts @@ -16,6 +16,7 @@ import { initialTestnetDeploymentProcess } from "../../src.ts/deploy-test-proces import { ethTestConfig } from "../../src.ts/utils"; import type { Deployer } from "../../src.ts/deploy"; +import { registerZKChain } from "../../src.ts/deploy-process"; describe("Initial deployment test", function () { let bridgehub: Bridgehub; @@ -100,4 +101,9 @@ describe("Initial deployment test", function () { expect(ntvAddress1.toLowerCase()).equal(ntvAddress2.toLowerCase()); expect(ntvAddress1.toLowerCase()).equal(ntvAddress3.toLowerCase()); }); + + it("Check L2SharedBridge", async () => { + const gasPrice = await owner.provider.getGasPrice(); + await registerZKChain(deployer, false, [], gasPrice, "", "0x33", true, true); + }); }); diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index aba28da40..8b0feaf78 100644 --- a/system-contracts/SystemContractsHashes.json +++ b/system-contracts/SystemContractsHashes.json @@ -3,49 +3,49 @@ "contractName": "AccountCodeStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/AccountCodeStorage.sol/AccountCodeStorage.json", "sourceCodePath": "contracts-preprocessed/AccountCodeStorage.sol", - "bytecodeHash": "0x0100005d98c166be5bf169f5830a7fe56f591af62454527a598bf38b0a9f876c", + "bytecodeHash": "0x0100005d7a6c264bffa97a66a5ba8daff43b8a34e5ab7fab4e2ddf74f0a10e4c", "sourceCodeHash": "0x2e0e09d57a04bd1e722d8bf8c6423fdf3f8bca44e5e8c4f6684f987794be066e" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c79d04c5f1985588d02630c260c40edcbb2d663ed4c38c0a1242dc2bcc", + "bytecodeHash": "0x010007c7b6bd43d607e55f594e743394b7ae6288ac7f6caad8a7904b6c990e32", "sourceCodeHash": "0x0f1213c4b95acb71f4ab5d4082cc1aeb2bd5017e1cccd46afc66e53268609d85" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004d8b6a7af5d502b7790ebbf06a6ba21d0ca03ba3ff47d02dd33f4d619e", + "bytecodeHash": "0x0100004d8c1520bc212778de21b5df751968cbbf1690ddd362ea7ab844ec0b1d", "sourceCodeHash": "0x796046a914fb676ba2bbd337b2924311ee2177ce54571c18a2c3945755c83614" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100014bec423120b050d8095b9623455d9b4bca881b9a5f0f434de66574a4ff", + "bytecodeHash": "0x0100014ba4bf3056bc1d10e64986c71972db9056c879eed689163f7c91bb596f", "sourceCodeHash": "0x7240b5fb2ea8e184522e731fb14f764ebae52b8a69d1870a55daedac9a3ed617" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e5d3f30278f686e9ad273eb77fa4d41cfd87676e88f33a63cd36329c04", + "bytecodeHash": "0x010004e5ad1de716de961c9e52e3b1d6219709891024e5b28d8cde96fe7fdc69", "sourceCodeHash": "0x92bc09da23ed9d86ba7a84f0dbf48503c99582ae58cdbebbdcc5f14ea1fcf014" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x01000049ca08dfa946db4521eed61e95a989bc52f15c81f4eaf051649e7b59af", + "bytecodeHash": "0x01000049bae223f356480d01ad05099bad8cfc3e0a91e206ae5dd72abb187cb1", "sourceCodeHash": "0x114d9322a9ca654989f3e0b3b21f1311dbc4db84f443d054cd414f6414d84de3" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055d0b56db8580a7cb4191661efc4d56f7ab91831afed87fe98123d987b6", + "bytecodeHash": "0x0100055d760f11a3d737e7fd1816e600a4cd874a9f17f7a225d1f1c537c51a1e", "sourceCodeHash": "0xebffe840ebbd9329edb1ebff8ca50f6935e7dabcc67194a896fcc2e968d46dfb" }, { @@ -59,63 +59,63 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x0100003920a44c0179abb18b63287a74e4c68d6dd4c015bed91b5aa8ec60f676", + "bytecodeHash": "0x01000039fcb0afdc4480b9a83e0a5cfc2dbc55dce4f6f1d6363778b4e9371ca9", "sourceCodeHash": "0x9659e69f7db09e8f60a8bb95314b1ed26afcc689851665cf27f5408122f60c98" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006f4f236efe0359b2d01a2c396fe9c2f5d74252b1acbeed29d0610e1280", + "bytecodeHash": "0x0100006f2889f3200b41f87f8e0835f970e47e513548bbf68239577f8bd97816", "sourceCodeHash": "0xb39b5b81168653e0c5062f7b8e1d6d15a4e186df3317f192f0cb2fc3a74f5448" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010001f7ce693213118f4780607b934fb0be4baece85ae52377a0279d0c027f5", + "bytecodeHash": "0x010001f735b209b666e9e3d377cf7b5c0483a57dff7b1cd035b58ac10c2e0771", "sourceCodeHash": "0x8d22a4019347a45cb0c27bed9e98f7033637a7bdcd90fafb1922caa48f2b05de" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x01000103d8b1dbf62114dbf0fdb7b06e94d372346b1a99936deeb9250c7c264d", + "bytecodeHash": "0x010001036ff04ddfde50fac4cf41bf9a34df472373e8b2769938cb35d293f7a7", "sourceCodeHash": "0x8bdd2b4d0b53dba84c9f0af250bbaa2aad10b3de6747bba957f0bd3721090dfa" }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x010000d5c6b00a5ad08e8990515dc11146b4150a2ab27008d78f1928159404b0", - "sourceCodeHash": "0x15bb6f306f209b618ea5e52671757934d306dcb1d53be73ce49cd200ad485688" + "bytecodeHash": "0x01000109946aaf60780aa88277365bdd6bc2719f5a2592079a91aae7058cee58", + "sourceCodeHash": "0xbfe430d992d5740c4befdc7adbac2bb9a33c25a45c30ed9fe86c2b4e0263778a" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005d3053405421f29f79d8f9346697c19bbf4d0c1d3f357025ef4073d173", + "bytecodeHash": "0x0100005dc9890cf9aca1c56e823e2147619ea5058c70e123a6a76e51bcee8956", "sourceCodeHash": "0x082f3dcbc2fe4d93706c86aae85faa683387097d1b676e7ebd00f71ee0f13b71" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000d98fcd3f30ceba0dffe1981b7402ac80e15d7f8d07686c94a16d1aef50", + "bytecodeHash": "0x010000d9ff72782330c259f50fc3ff63e5f8b0a2277e6d652c2a60c56e60efec", "sourceCodeHash": "0xcd0c0366effebf2c98c58cf96322cc242a2d1c675620ef5514b7ed1f0a869edc" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x010000496b83c897843f12da4672f6b3e549a949f7d2078b3f56c3221ed62a68", + "bytecodeHash": "0x0100004994ca7f560b82e531240c2bac414d02180c33f75363de4edc00796c15", "sourceCodeHash": "0x04d3d2e4019081c87aae5c22a060d84ae2e9d631ebce59801ecce37b9c87e4c7" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a75638c20c20ae71722f6265db5258d2d70b8cdcbc3400c618b113dc5c", + "bytecodeHash": "0x010001a7e10cfa64a3b326897067f582f992db9fbb9bf50304d5db6525de961d", "sourceCodeHash": "0xb3b8c1f57928938ac590984442bc96c2c888282793014845d5ce2f90bbf2677f" }, { diff --git a/system-contracts/contracts/Constants.sol b/system-contracts/contracts/Constants.sol index a0b22c047..072c6eab9 100644 --- a/system-contracts/contracts/Constants.sol +++ b/system-contracts/contracts/Constants.sol @@ -74,7 +74,8 @@ IBaseToken constant BASE_TOKEN_SYSTEM_CONTRACT = IBaseToken(address(SYSTEM_CONTR IBaseToken constant REAL_BASE_TOKEN_SYSTEM_CONTRACT = IBaseToken(address(REAL_SYSTEM_CONTRACTS_OFFSET + 0x0a)); address constant L2_ASSET_ROUTER = address(USER_CONTRACTS_OFFSET + 0x03); -IBridgehub constant L2_BRIDDGE_HUB = IBridgehub(address(USER_CONTRACTS_OFFSET + 0x02)); +IBridgehub constant L2_BRIDGE_HUB = IBridgehub(address(USER_CONTRACTS_OFFSET + 0x02)); +address constant L2_NATIVE_TOKEN_VAULT_ADDR = address(USER_CONTRACTS_OFFSET + 0x04); IMessageRoot constant L2_MESSAGE_ROOT = IMessageRoot(address(USER_CONTRACTS_OFFSET + 0x05)); // Hardcoded because even for tests we should keep the address. (Instead `SYSTEM_CONTRACTS_OFFSET + 0x10`) diff --git a/system-contracts/contracts/L2GenesisUpgrade.sol b/system-contracts/contracts/L2GenesisUpgrade.sol index aab22ff34..35d03b648 100644 --- a/system-contracts/contracts/L2GenesisUpgrade.sol +++ b/system-contracts/contracts/L2GenesisUpgrade.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.24; -import {DEPLOYER_SYSTEM_CONTRACT, SYSTEM_CONTEXT_CONTRACT, L2_BRIDDGE_HUB, L2_ASSET_ROUTER, L2_MESSAGE_ROOT} from "./Constants.sol"; +import {DEPLOYER_SYSTEM_CONTRACT, SYSTEM_CONTEXT_CONTRACT, L2_BRIDGE_HUB, L2_ASSET_ROUTER, L2_MESSAGE_ROOT, L2_NATIVE_TOKEN_VAULT_ADDR} from "./Constants.sol"; import {IContractDeployer, ForceDeployment} from "./interfaces/IContractDeployer.sol"; import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; import {ISystemContext} from "./interfaces/ISystemContext.sol"; -import {IL2GenesisUpgrade} from "./interfaces/IL2GenesisUpgrade.sol"; +import {IL2GenesisUpgrade, FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; /// @custom:security-contact security@matterlabs.dev /// @author Matter Labs @@ -15,12 +15,16 @@ contract L2GenesisUpgrade is IL2GenesisUpgrade { function genesisUpgrade( uint256 _chainId, address _ctmDeployer, - bytes calldata _forceDeploymentsData + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData ) external payable { // solhint-disable-next-line gas-custom-errors require(_chainId != 0, "Invalid chainId"); ISystemContext(SYSTEM_CONTEXT_CONTRACT).setChainId(_chainId); - ForceDeployment[] memory forceDeployments = abi.decode(_forceDeploymentsData, (ForceDeployment[])); + ForceDeployment[] memory forceDeployments = _getForceDeploymentsData( + _fixedForceDeploymentsData, + _additionalForceDeploymentsData + ); IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses{value: msg.value}(forceDeployments); // It is expected that either via to the force deployments above @@ -28,15 +32,15 @@ contract L2GenesisUpgrade is IL2GenesisUpgrade { // (The comment does not mention the exact order in case it changes) // However, there is still some follow up finalization that needs to be done. - address bridgehubOwner = L2_BRIDDGE_HUB.owner(); + address bridgehubOwner = L2_BRIDGE_HUB.owner(); bytes memory data = abi.encodeCall( - L2_BRIDDGE_HUB.setAddresses, + L2_BRIDGE_HUB.setAddresses, (L2_ASSET_ROUTER, _ctmDeployer, address(L2_MESSAGE_ROOT)) ); (bool success, bytes memory returnData) = SystemContractHelper.mimicCall( - address(L2_BRIDDGE_HUB), + address(L2_BRIDGE_HUB), bridgehubOwner, data ); @@ -49,4 +53,75 @@ contract L2GenesisUpgrade is IL2GenesisUpgrade { emit UpgradeComplete(_chainId); } + + function _getForceDeploymentsData( + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) internal view returns (ForceDeployment[] memory forceDeployments) { + FixedForceDeploymentsData memory fixedForceDeploymentsData = abi.decode( + _fixedForceDeploymentsData, + (FixedForceDeploymentsData) + ); + ZKChainSpecificForceDeploymentsData memory additionalForceDeploymentsData = abi.decode( + _additionalForceDeploymentsData, + (ZKChainSpecificForceDeploymentsData) + ); + + forceDeployments = new ForceDeployment[](4); + + forceDeployments[0] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.messageRootBytecodeHash, + newAddress: address(L2_MESSAGE_ROOT), + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode(address(L2_BRIDGE_HUB)) + }); + + forceDeployments[1] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.bridgehubBytecodeHash, + newAddress: address(L2_BRIDGE_HUB), + callConstructor: true, + value: 0, + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.aliasedL1Governance, + fixedForceDeploymentsData.maxNumberOfZKChains + ) + }); + + forceDeployments[2] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.l2AssetRouterBytecodeHash, + newAddress: address(L2_ASSET_ROUTER), + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.eraChainId, + fixedForceDeploymentsData.l1AssetRouter, + additionalForceDeploymentsData.l2LegacySharedBridge, + additionalForceDeploymentsData.baseTokenAssetId, + fixedForceDeploymentsData.aliasedL1Governance + ) + }); + + forceDeployments[3] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.l2NtvBytecodeHash, + newAddress: L2_NATIVE_TOKEN_VAULT_ADDR, + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.aliasedL1Governance, + fixedForceDeploymentsData.l2TokenProxyBytecodeHash, + additionalForceDeploymentsData.l2LegacySharedBridge, + address(0), // this is used if the contract were already deployed, so for the migration of Era. + false, + additionalForceDeploymentsData.l2Weth, + additionalForceDeploymentsData.baseTokenAssetId + ) + }); + } } diff --git a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol index 2b733cddb..88566d5d8 100644 --- a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol +++ b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol @@ -2,12 +2,32 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.20; +struct ZKChainSpecificForceDeploymentsData { + bytes32 baseTokenAssetId; + address l2LegacySharedBridge; + address l2Weth; +} + +struct FixedForceDeploymentsData { + uint256 l1ChainId; + uint256 eraChainId; + address l1AssetRouter; + bytes32 l2TokenProxyBytecodeHash; + address aliasedL1Governance; + uint256 maxNumberOfZKChains; + bytes32 bridgehubBytecodeHash; + bytes32 l2AssetRouterBytecodeHash; + bytes32 l2NtvBytecodeHash; + bytes32 messageRootBytecodeHash; +} + interface IL2GenesisUpgrade { event UpgradeComplete(uint256 _chainId); function genesisUpgrade( uint256 _chainId, address _ctmDeployer, - bytes calldata _forceDeploymentsData + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData ) external payable; } diff --git a/system-contracts/contracts/test-contracts/DummyBridgehub.sol b/system-contracts/contracts/test-contracts/DummyBridgehub.sol new file mode 100644 index 000000000..4beadb4ce --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyBridgehub.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyBridgehub { + address public owner; + + constructor(uint256 _l1ChainId, address _aliasedL1Governance, uint256 _maxNumberOfZKChains) { + owner = _aliasedL1Governance; + } +} diff --git a/system-contracts/contracts/test-contracts/DummyL2AssetRouter.sol b/system-contracts/contracts/test-contracts/DummyL2AssetRouter.sol new file mode 100644 index 000000000..65796aa3f --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyL2AssetRouter.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyL2AssetRouter { + constructor( + uint256 _l1ChainId, + address _l1AssetRouter, + address _aliasedL1Governance, + bytes32 _baseTokenAssetId, + uint256 _maxNumberOfZKChains + ) {} +} diff --git a/system-contracts/contracts/test-contracts/DummyL2NativeTokenVault.sol b/system-contracts/contracts/test-contracts/DummyL2NativeTokenVault.sol new file mode 100644 index 000000000..1832237d2 --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyL2NativeTokenVault.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyL2NativeTokenVault { + constructor( + uint256 _l1ChainId, + address _aliasedL1Governance, + bytes32 _l2TokenProxyBytecodeHash, + address _bridgedTokenBeacon, + bool _contractsDeployedAlready, + address _wethToken, + bytes32 _baseTokenAssetId + ) {} +} diff --git a/system-contracts/contracts/test-contracts/DummyMessageRoot.sol b/system-contracts/contracts/test-contracts/DummyMessageRoot.sol new file mode 100644 index 000000000..d49cdd50f --- /dev/null +++ b/system-contracts/contracts/test-contracts/DummyMessageRoot.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +contract DummyMessageRoot { + constructor(address) {} +} diff --git a/system-contracts/test/L2GenesisUpgrade.spec.ts b/system-contracts/test/L2GenesisUpgrade.spec.ts index e64fb116a..916807c99 100644 --- a/system-contracts/test/L2GenesisUpgrade.spec.ts +++ b/system-contracts/test/L2GenesisUpgrade.spec.ts @@ -1,5 +1,6 @@ import { expect } from "chai"; import { ethers, network } from "hardhat"; +import * as zksync from "zksync-ethers"; import type { ComplexUpgrader, L2GenesisUpgrade } from "../typechain"; import { ComplexUpgraderFactory, L2GenesisUpgradeFactory } from "../typechain"; import { @@ -8,8 +9,9 @@ import { REAL_L2_ASSET_ROUTER_ADDRESS, REAL_L2_MESSAGE_ROOT_ADDRESS, TEST_COMPLEX_UPGRADER_CONTRACT_ADDRESS, + ADDRESS_ONE, } from "./shared/constants"; -import { deployContractOnAddress } from "./shared/utils"; +import { deployContractOnAddress, loadArtifact } from "./shared/utils"; import { setResult } from "./shared/mocks"; describe("L2GenesisUpgrade tests", function () { @@ -30,6 +32,19 @@ describe("L2GenesisUpgrade tests", function () { }, ]; + let fixedForceDeploymentsData: string; + + const additionalForceDeploymentsData = ethers.utils.defaultAbiCoder.encode( + ["tuple(bytes32 baseTokenAssetId, address l2LegacySharedBridge, address l2Weth)"], + [ + { + baseTokenAssetId: "0x0100056f53fd9e940906d998a80ed53392e5c50a8eb198baf9f78fd84ce7ec70", + l2LegacySharedBridge: ADDRESS_ONE, + l2Weth: ADDRESS_ONE, + }, + ] + ); + before(async () => { const wallet = await ethers.getImpersonatedSigner(TEST_FORCE_DEPLOYER_ADDRESS); await deployContractOnAddress(TEST_COMPLEX_UPGRADER_CONTRACT_ADDRESS, "ComplexUpgrader"); @@ -60,19 +75,47 @@ describe("L2GenesisUpgrade tests", function () { failure: false, returnData: "0x", }); + + const msgRootBytecode = (await loadArtifact("DummyMessageRoot")).bytecode; + const messageRootBytecodeHash = zksync.utils.hashBytecode(msgRootBytecode); + + const ntvBytecode = (await loadArtifact("DummyL2NativeTokenVault")).bytecode; + const ntvBytecodeHash = zksync.utils.hashBytecode(ntvBytecode); + + const l2AssetRouterBytecode = (await loadArtifact("DummyL2AssetRouter")).bytecode; + const l2AssetRouterBytecodeHash = zksync.utils.hashBytecode(l2AssetRouterBytecode); + + const bridgehubBytecode = (await loadArtifact("DummyBridgehub")).bytecode; + const bridgehubBytecodeHash = zksync.utils.hashBytecode(bridgehubBytecode); + + fixedForceDeploymentsData = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 l1ChainId, uint256 eraChainId, address l1AssetRouter, bytes32 l2TokenProxyBytecodeHash, address aliasedL1Governance, uint256 maxNumberOfZKChains, bytes32 bridgehubBytecodeHash, bytes32 l2AssetRouterBytecodeHash, bytes32 l2NtvBytecodeHash, bytes32 messageRootBytecodeHash)", + ], + [ + { + l1ChainId: 1, + eraChainId: 1, + l1AssetRouter: ADDRESS_ONE, + l2TokenProxyBytecodeHash: "0x0100056f53fd9e940906d998a80ed53392e5c50a8eb198baf9f78fd84ce7ec70", + aliasedL1Governance: ADDRESS_ONE, + maxNumberOfZKChains: 100, + bridgehubBytecodeHash: bridgehubBytecodeHash, + l2AssetRouterBytecodeHash: l2AssetRouterBytecodeHash, + l2NtvBytecodeHash: ntvBytecodeHash, + messageRootBytecodeHash: messageRootBytecodeHash, + }, + ] + ); }); describe("upgrade", function () { it("successfully upgraded", async () => { - const forceDeploymentsData = ethers.utils.defaultAbiCoder.encode( - ["tuple(bytes32 bytecodeHash, address newAddress, bool callConstructor, uint256 value, bytes input)[]"], - [forceDeployments] - ); - const data = l2GenesisUpgrade.interface.encodeFunctionData("genesisUpgrade", [ chainId, ctmDeployerAddress, - forceDeploymentsData, + fixedForceDeploymentsData, + additionalForceDeploymentsData, ]); // Note, that the event is emitted at the complex upgrader, but the event declaration is taken from the l2GenesisUpgrade contract. diff --git a/system-contracts/test/shared/constants.ts b/system-contracts/test/shared/constants.ts index cb5ae3e1c..c3f82c989 100644 --- a/system-contracts/test/shared/constants.ts +++ b/system-contracts/test/shared/constants.ts @@ -34,3 +34,5 @@ export const REAL_L2_MESSAGE_ROOT_ADDRESS = "0x000000000000000000000000000000000 export const EMPTY_STRING_KECCAK = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; export const TWO_IN_256 = BigNumber.from(2).pow(256); export const ONE_BYTES32_HEX = "0x0000000000000000000000000000000000000000000000000000000000000001"; + +export const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; From b9c2fef049440f4f06630c991ac7e30b8d5e4bb1 Mon Sep 17 00:00:00 2001 From: kelemeno Date: Tue, 17 Sep 2024 11:47:36 +0100 Subject: [PATCH 4/5] lint --- l2-contracts/contracts/interfaces/IConsensusRegistry.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/l2-contracts/contracts/interfaces/IConsensusRegistry.sol b/l2-contracts/contracts/interfaces/IConsensusRegistry.sol index 5e6bea998..a5e017484 100644 --- a/l2-contracts/contracts/interfaces/IConsensusRegistry.sol +++ b/l2-contracts/contracts/interfaces/IConsensusRegistry.sol @@ -2,7 +2,6 @@ // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. pragma solidity ^0.8.20; - /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @title ConsensusRegistry contract interface From c566ee72adac20f6eb9298ab1ab6f3a24823b094 Mon Sep 17 00:00:00 2001 From: kelemeno <34402761+kelemeno@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:49:58 +0100 Subject: [PATCH 5/5] chore: Kl/merge-stable (#809) Co-authored-by: Lyova Potyomkin Co-authored-by: Stanislav Bezkorovainyi