From b5ccc20cffa36f1395360d8762f1762f7409835f Mon Sep 17 00:00:00 2001 From: koloz193 Date: Fri, 12 Jul 2024 13:49:12 -0400 Subject: [PATCH 01/26] added batch root sending to executor fn (#611) --- .../state-transition/chain-deps/facets/Executor.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 7c5909528..c92e0788e 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -368,8 +368,14 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { _checkBatchData(_storedBatch, _executedBatchIdx, priorityOperationsHash); s.priorityTree.processBatch(_priorityOpsData); + uint256 currentBatchNumber = _storedBatch.batchNumber; + // Save root hash of L2 -> L1 logs tree s.l2LogsRootHashes[_storedBatch.batchNumber] = _storedBatch.l2LogsTreeRoot; + + // Once the batch is executed, we include its message to the message root. + IMessageRoot messageRootContract = IBridgehub(s.bridgehub).messageRoot(); + messageRootContract.addChainBatchRoot(s.chainId, currentBatchNumber, _storedBatch.l2LogsTreeRoot); } function executeBatchesSharedBridge( From d4124fe5d936b4dca38f17e985f71236f46ce694 Mon Sep 17 00:00:00 2001 From: koloz193 Date: Mon, 22 Jul 2024 13:29:54 -0400 Subject: [PATCH 02/26] add gas test ci for l1 contracts (#626) --- .github/workflows/l1-contracts-ci.yaml | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.github/workflows/l1-contracts-ci.yaml b/.github/workflows/l1-contracts-ci.yaml index d761b63ed..d417a8773 100644 --- a/.github/workflows/l1-contracts-ci.yaml +++ b/.github/workflows/l1-contracts-ci.yaml @@ -215,3 +215,56 @@ jobs: coverage-files: ./l1-contracts/lcov.info working-directory: l1-contracts minimum-coverage: 85 # Set coverage threshold. + + gas-report: + needs: [build, lint, test-foundry] + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Use Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 18.18.0 + cache: yarn + + - name: Install dependencies + run: yarn + + - name: Restore artifacts cache + uses: actions/cache/restore@v3 + with: + fail-on-cache-miss: true + key: artifacts-l1-${{ github.sha }} + path: | + l1-contracts/artifacts + l1-contracts/cache + l1-contracts/typechain + + # Add any step generating a gas report to a temporary file named gasreport.ansi. For example: + - name: Run tests + run: yarn l1 test:foundry --gas-report | tee gasreport.ansi # <- this file name should be unique in your repository! + + - name: Compare gas reports + uses: Rubilmax/foundry-gas-diff@v3.18 + with: + summaryQuantile: 0.0 # only display the 10% most significant gas diffs in the summary (defaults to 20%) + sortCriteria: avg,max # sort diff rows by criteria + sortOrders: desc,asc # and directions + ignore: test-foundry/**/* # filter out gas reports from specific paths (test/ is included by default) + id: gas_diff + + - name: Add gas diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + # delete the comment in case changes no longer impact gas costs + delete: ${{ !steps.gas_diff.outputs.markdown }} + message: ${{ steps.gas_diff.outputs.markdown }} From 8536918340b4a747cd1a63176cb5efe84a1d73fc Mon Sep 17 00:00:00 2001 From: koloz193 Date: Thu, 8 Aug 2024 14:36:00 -0400 Subject: [PATCH 03/26] Increase Test Coverage (#632) --- .github/workflows/l1-contracts-ci.yaml | 4 +- .../contracts/bridgehub/IMessageRoot.sol | 2 - .../contracts/bridgehub/MessageRoot.sol | 42 ------- .../StateTransitionManager.sol | 2 +- .../unit/concrete/Bridgehub/MessageRoot.t.sol | 115 ++++++++++++++++++ .../foundry/unit/concrete/Utils/Utils.sol | 3 +- .../StateTransitionManager/Admin.t.sol | 34 ++++++ .../CreateNewChain.t.sol | 25 ++-- .../SetNewVersionUpgrade.t.sol | 6 + .../SetValidatorTimelock.t.sol | 19 +++ system-contracts/SystemContractsHashes.json | 52 ++++---- .../contracts/interfaces/IMessageRoot.sol | 1 - 12 files changed, 223 insertions(+), 82 deletions(-) create mode 100644 l1-contracts/test/foundry/unit/concrete/Bridgehub/MessageRoot.t.sol create mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/Admin.t.sol diff --git a/.github/workflows/l1-contracts-ci.yaml b/.github/workflows/l1-contracts-ci.yaml index d417a8773..181e6f5cb 100644 --- a/.github/workflows/l1-contracts-ci.yaml +++ b/.github/workflows/l1-contracts-ci.yaml @@ -217,7 +217,7 @@ jobs: minimum-coverage: 85 # Set coverage threshold. gas-report: - needs: [build, lint, test-foundry] + needs: [build, lint] runs-on: ubuntu-latest steps: @@ -258,7 +258,7 @@ jobs: summaryQuantile: 0.0 # only display the 10% most significant gas diffs in the summary (defaults to 20%) sortCriteria: avg,max # sort diff rows by criteria sortOrders: desc,asc # and directions - ignore: test-foundry/**/* # filter out gas reports from specific paths (test/ is included by default) + ignore: test-foundry/**/*,l1-contracts/contracts/dev-contracts/**/*,l1-contracts/lib/**/*,l1-contracts/contracts/common/Dependencies.sol id: gas_diff - name: Add gas diff to sticky comment diff --git a/l1-contracts/contracts/bridgehub/IMessageRoot.sol b/l1-contracts/contracts/bridgehub/IMessageRoot.sol index 25542bab7..ecfa33682 100644 --- a/l1-contracts/contracts/bridgehub/IMessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/IMessageRoot.sol @@ -11,7 +11,5 @@ interface IMessageRoot { function addChainBatchRoot(uint256 _chainId, uint256 _batchNumber, bytes32 _chainBatchRoot) external; - function clearTreeAndProvidePubdata() external returns (bytes memory pubdata); - function addNewChainIfNeeded(uint256 _chainId) external; } diff --git a/l1-contracts/contracts/bridgehub/MessageRoot.sol b/l1-contracts/contracts/bridgehub/MessageRoot.sol index c98549229..a4aa9ef63 100644 --- a/l1-contracts/contracts/bridgehub/MessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/MessageRoot.sol @@ -154,46 +154,4 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { // slither-disable-next-line unused-return sharedTree.updateAllLeaves(newLeaves); } - - // It is expected that the root is present - // `_updateTree` should be false only if the caller ensures that it is followed by updating the entire tree. - function _unsafeResetChainRoot(uint256 _index, bool _updateTree) internal { - uint256 chainId = chainIndexToId[_index]; - bytes32 initialRoot = chainTree[chainId].setup(CHAIN_TREE_EMPTY_ENTRY_HASH); - - if (_updateTree) { - // slither-disable-next-line unused-return - sharedTree.updateLeaf(_index, Messaging.chainIdLeafHash(initialRoot, chainId)); - } - } - - /// IMPORTANT FIXME!!!: split into two: provide pubdata and clear state. The "provide pubdata" part should be used by SL. - /// NO DA is provided here ATM !!! - /// @notice To be called by the bootloader by the L1Messenger at the end of the batch to produce the final root and send it to the underlying layer. - /// @return pubdata The pubdata to be relayed to the DA layer. - function clearTreeAndProvidePubdata() external returns (bytes memory) { - // FIXME: access control: only to be called by the l1 messenger. - // uint256 cachedChainCount = chainCount; - // // We will send the updated roots for all chains. - // // While it will mean that we'll pay even for unchanged roots: - // // - It is the simplest approach - // // - The alternative is to send pairs of (chainId, root), which is less efficient if at least half of the chains are active. - // // - // // There are of course ways to optimize it further, but it will be done in the future. - // bytes memory pubdata = new bytes(cachedChainCount * 32); - // for (uint256 i = 0; i < cachedChainCount; i++) { - // // It is the responsibility of each chain to provide the roots of its L2->L1 messages if it wants to see those. - // // However, for the security of the system as a whole, the chain roots need to be provided for all chains. - // bytes32 chainRoot = chainTree[chainIndexToId[i]].root(); - // assembly { - // mstore(add(pubdata, add(32, mul(i, 32))), chainRoot) - // } - // // Clearing up the state. - // // Note that it *does not* delete any storage slots, so in terms of pubdata savings, it is useless. - // // However, the chains paid for these changes anyway, so it is considered acceptable. - // // In the future, further optimizations will be available. - // _unsafeResetChainRoot(i, false); - // } - // updateFullTree(); - } } diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index f1cabbf22..4a0d6fd4c 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -102,7 +102,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own chainAddresses = new address[](keys.length); uint256 keysLength = keys.length; for (uint256 i = 0; i < keysLength; ++i) { - chainAddresses[i] = hyperchainMap.get(i); + chainAddresses[i] = hyperchainMap.get(keys[i]); } } diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/MessageRoot.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/MessageRoot.t.sol new file mode 100644 index 000000000..497ec4731 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/MessageRoot.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +// Chain tree consists of batch commitments as their leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant CHAIN_TREE_EMPTY_ENTRY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +// Chain tree consists of batch commitments as their leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant SHARED_ROOT_TREE_EMPTY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +contract MessageRootTest is Test { + address bridgeHub; + MessageRoot messageRoot; + + function setUp() public { + bridgeHub = makeAddr("bridgeHub"); + messageRoot = new MessageRoot(IBridgehub(bridgeHub)); + } + + function test_init() public { + assertEq(messageRoot.getAggregatedRoot(), CHAIN_TREE_EMPTY_ENTRY_HASH); + } + + function test_RevertWhen_addChainNotBridgeHub() public { + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + uint256 betaChainId = uint256(uint160(makeAddr("betaChainId"))); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 1"); + + vm.expectRevert("MR: only bridgehub"); + messageRoot.addNewChain(alphaChainId); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 2"); + } + + function test_addNewChain() public { + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + uint256 betaChainId = uint256(uint160(makeAddr("betaChainId"))); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 1"); + assertFalse(messageRoot.chainRegistered(betaChainId), "beta chain 1"); + + vm.prank(bridgeHub); + vm.expectEmit(true, false, false, false); + emit MessageRoot.AddedChain(alphaChainId, 0); + messageRoot.addNewChain(alphaChainId); + + assertTrue(messageRoot.chainRegistered(alphaChainId), "alpha chain 2"); + assertFalse(messageRoot.chainRegistered(betaChainId), "beta chain 2"); + + assertEq(messageRoot.getChainRoot(alphaChainId), bytes32(0)); + } + + function test_RevertWhen_ChainNotRegistered() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(alphaChainSender); + vm.expectRevert("MR: not registered"); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + } + + function test_addChainBatchRoot() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(bridgeHub); + messageRoot.addNewChain(alphaChainId); + + vm.prank(alphaChainSender); + vm.expectEmit(true, false, false, false); + emit MessageRoot.Preimage(bytes32(0), bytes32(0)); + vm.expectEmit(true, false, false, false); + emit MessageRoot.AppendedChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + } + + function test_updateFullTree() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(bridgeHub); + messageRoot.addNewChain(alphaChainId); + + vm.prank(alphaChainSender); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + + messageRoot.updateFullTree(); + + assertEq(messageRoot.getAggregatedRoot(), 0xbad7e1cf889e30252b8ce93820f79d50651b78587844bc1c588dea123effa4ea); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol index 7b7ad25be..6f54bcaac 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol +++ b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol @@ -208,7 +208,7 @@ library Utils { } function getGettersSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](29); + bytes4[] memory selectors = new bytes4[](30); selectors[0] = GettersFacet.getVerifier.selector; selectors[1] = GettersFacet.getAdmin.selector; selectors[2] = GettersFacet.getPendingAdmin.selector; @@ -238,6 +238,7 @@ library Utils { selectors[26] = GettersFacet.getTotalBatchesVerified.selector; selectors[27] = GettersFacet.getTotalBatchesExecuted.selector; selectors[28] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; + selectors[29] = GettersFacet.getProtocolVersion.selector; return selectors; } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/Admin.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/Admin.t.sol new file mode 100644 index 000000000..a214c0374 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/Admin.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; + +contract AdminTest is StateTransitionManagerTest { + function test_setPendingAdmin() public { + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(true, true, true, false); + emit IStateTransitionManager.NewPendingAdmin(address(0), newAdmin); + chainContractAddress.setPendingAdmin(newAdmin); + } + + function test_acceptPendingAdmin() public { + address newAdmin = makeAddr("newAdmin"); + + chainContractAddress.setPendingAdmin(newAdmin); + + // Need this because in shared setup we start a prank as the governor + vm.stopPrank(); + vm.prank(newAdmin); + vm.expectEmit(true, true, true, false); + emit IStateTransitionManager.NewPendingAdmin(newAdmin, address(0)); + vm.expectEmit(true, true, true, false); + emit IStateTransitionManager.NewAdmin(address(0), newAdmin); + chainContractAddress.acceptAdmin(); + + address currentAdmin = chainContractAddress.admin(); + + assertEq(currentAdmin, newAdmin); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol index 86239cfd9..7eef43b79 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol @@ -28,13 +28,24 @@ contract createNewChainTest is StateTransitionManagerTest { }); } - // function test_SuccessfulCreationOfNewChain() public { - // createNewChain(getDiamondCutData(diamondInit)); + function test_SuccessfulCreationOfNewChain() public { + createNewChain(getDiamondCutData(diamondInit)); - // address admin = chainContractAddress.getChainAdmin(chainId); - // address newChainAddress = chainContractAddress.getHyperchain(chainId); + address admin = chainContractAddress.getChainAdmin(chainId); + address newChainAddress = chainContractAddress.getHyperchain(chainId); - // assertEq(newChainAdmin, admin); - // assertNotEq(newChainAddress, address(0)); - // } + assertEq(newChainAdmin, admin); + assertNotEq(newChainAddress, address(0)); + + address[] memory chainAddresses = chainContractAddress.getAllHyperchains(); + assertEq(chainAddresses.length, 1); + assertEq(chainAddresses[0], newChainAddress); + + uint256[] memory chainIds = chainContractAddress.getAllHyperchainChainIDs(); + assertEq(chainIds.length, 1); + assertEq(chainIds[0], chainId); + + uint256 protocolVersion = chainContractAddress.getProtocolVersion(chainId); + assertEq(protocolVersion, 0); + } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol index ced7e3f7d..b1153a495 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol @@ -16,5 +16,11 @@ contract setNewVersionUpgradeTest is StateTransitionManagerTest { assertEq(chainContractAddress.upgradeCutHash(0), newCutHash, "Diamond cut upgrade was not successful"); assertEq(chainContractAddress.protocolVersion(), 1, "New protocol version is not correct"); + + (uint32 major, uint32 minor, uint32 patch) = chainContractAddress.getSemverProtocolVersion(); + + assertEq(major, 0); + assertEq(minor, 0); + assertEq(patch, 1); } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol index d290a8767..85267cf41 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol @@ -20,4 +20,23 @@ contract setValidatorTimelockTest is StateTransitionManagerTest { "Validator timelock update was not successful" ); } + + function test_RevertWhen_NotOwner() public { + // Need this because in shared setup we start a prank as the governor + vm.stopPrank(); + + address notOwner = makeAddr("notOwner"); + assertEq( + chainContractAddress.validatorTimelock(), + validator, + "Initial validator timelock address is not correct" + ); + + vm.prank(notOwner); + vm.expectRevert("STM: not owner or admin"); + address newValidatorTimelock = address(0x0000000000000000000000000000000000004235); + chainContractAddress.setValidatorTimelock(newValidatorTimelock); + + assertEq(chainContractAddress.validatorTimelock(), validator, "Validator should not have been updated"); + } } diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index b263ada0c..9bbaff926 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": "0x0100005d0d5963b3b09cf356e290f28e43e05db5d9c1469fac8c8dc7b3abf2af", + "bytecodeHash": "0x0100005d4ef45dc2f9203f11d4f10a5cc9d8fc90c94038348907f9951d62920d", "sourceCodeHash": "0x69d2533e5481ff13e65f4442e650f4b90c46a48ac643cac9798bbbf421194353" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c76573d4e76a30b911b65daa0af4a643ec8f9414262521b2491b505e49", + "bytecodeHash": "0x010007c7cd7174a30f11c5f0a0caaa072403120d61beef26c0563ba50b0a19a0", "sourceCodeHash": "0x26060f33c7c63bd1f8a1a2f3b368b97ef8dd939bc53e95090f2c556248b99dce" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004dd7c52ea918d3bab6d8850449b80692184d0297b3c042eaae751225fe", + "bytecodeHash": "0x0100004dd93c94ac92e4bfebaa1613c8e614b0189e1a1da02e550c87777fede1", "sourceCodeHash": "0xdde7c49a94cc3cd34c3e7ced1b5ba45e4740df68d26243871edbe393e7298f7a" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100013f1d46c6bcfb0caabd15a0dc907f91460e085260c2fe40a108a505513a", + "bytecodeHash": "0x0100013f46aaf4388b6deb1da3c32c958bf354bd5dcbf58e000a2f7b84b7255c", "sourceCodeHash": "0xb0cec0016f481ce023478f71727fbc0d82e967ddc0508e4d47f5c52292a3f790" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e5a00bf6227b8b2203553a4557b7bc326647c20c1173f21707792f02ad", + "bytecodeHash": "0x010004e56946cb9626a0f1ddcbc9fb3bc0e38d35cea63885e887a4193506b54a", "sourceCodeHash": "0x1657e45c2ff4d6a892afaf71dab71bb8dc7db2c6863e66240e83dfdd2535420d" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x01000049a7d2acae5204fb196e8eec1dde41171b0da737cea75dbd49e80f9be6", + "bytecodeHash": "0x01000049c41dac96c98ae1c3c2bebef5265c912470ccaad8de3178fd4f952370", "sourceCodeHash": "0x217e65f55c8add77982171da65e0db8cc10141ba75159af582973b332a4e098a" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055de356de05b75c83195567a6688d9050a17b58ccc5c5c91d05cd2bfb6d", + "bytecodeHash": "0x0100055d06870300325a282f315e80d5f23404614262b4116564262b7f6f516d", "sourceCodeHash": "0xeb5ac8fc83e1c8619db058a9b6973958bd6ed1b6f4938f8f4541d702f12e085d" }, { @@ -59,63 +59,63 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x0100003bd47713cb1de6f8839bd2f1f8679a4360da7f423e2f3eaa6bb3e86028", + "bytecodeHash": "0x0100003b22c0da6d42c2bdcb2548fca995f2ecfdbc431360d7364d69d64440f8", "sourceCodeHash": "0x4212e99cbc1722887cfb5b4cb967f278ac8642834786f0e3c6f3b324a9316815" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006fc57f216e7189ec08828a4fe4b1e67cb7336ca0173e5fe6e44e830f7f", + "bytecodeHash": "0x0100006fea745ee8452ea9c25847b06ec1b04f2a399560f6be79153d07553e7b", "sourceCodeHash": "0x8da495a9fc5aa0d7d20a165a4fc8bc77012bec29c472015ea5ecc0a2bd706137" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010001e9ec59f6aad98b91b7853b805e6c5b935112247cf5267094d0d1785067", + "bytecodeHash": "0x010001e9e9dddb58df68080b9d39c4a7f893b56c11702da71a3411f71dde1e25", "sourceCodeHash": "0x2bc0dd1f7c3a9e1c8b7f2045e4be3cbec84079b5ec3863a90f71741f30ea1a6c" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x01000105a82f8eec41fbe25de2a833b9816fb171de7e2f650a44ad085dc08bc9", + "bytecodeHash": "0x010001056f6dd9c97bd5fbf9ad44130184713a7b8ad52b5650051f7a69888a5f", "sourceCodeHash": "0x4cdafafd4cfdf410b31641e14487ea657be3af25e5ec1754fcd7ad67ec23d8be" }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x010000974222230beff5f3bdcfb4b58940fe547a2907fc7417d4dd26a7abe07f", + "bytecodeHash": "0x01000097b1655a8c65edba54e363ea8779c10b9b094a33c0e432a75b66311482", "sourceCodeHash": "0xcb190d0dfd41bbc809409a8aa04a4847b86edfe010b1d75e23b4c8d07b13a9d0" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005dec0422e4514f78cc4ad2435ba011998d59b584c77438d9505eb03b4a", + "bytecodeHash": "0x0100005da1a0a5544aa0ff74faf3ccf199cb7eb96de4d480645cab60abb31c62", "sourceCodeHash": "0x4834adf62dbaefa1a1c15d36b5ad1bf2826e7d888a17be495f7ed4e4ea381aa8" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000db7a7a40b0058bfc4d65c3067ecfc59be76f3e4e46a82197ed36dce36f", + "bytecodeHash": "0x010000db40bae05cc6a07f252460f2d434c7ca6c4c215ac93e7a364bfbe2c8af", "sourceCodeHash": "0xaa2ed3a26af30032c00a612ac327e0cdf5288b7c932ae903462355f863f950cb" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x0100004bb2b5083d5de7af7f05491bbd733d3b90917168c461f25dd0e0eda0c0", + "bytecodeHash": "0x0100004bf15739ec4061594a3af7eefc9bd23171e758460cd9b62a61326ca5d1", "sourceCodeHash": "0xc442fd1abb821c95e8229f4ef1575f789e89d60d1001489f5ac9250155cd11dc" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a7276ef9e99743b84cd8ba4d7f8bc15359c73fec896f68e3c9c458e5a4", + "bytecodeHash": "0x010001a7273821c26fd3cecf5cba13c713ac0863c52306dc6cc6325a25bf40b1", "sourceCodeHash": "0x532a962209042f948e8a13e3f4cf12b6d53631e0fc5fa53083c7e2d8062771c0" }, { @@ -185,35 +185,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x010003cb0d9a7fd2cd5a66c58de8f4a53b1b7e35eaaa18a73b6da4ac74e45f4e", - "sourceCodeHash": "0x492c3bae09bfa1016e848fcb8e914b56ac0638a21a55b3f13e403acb8f06ca38" + "bytecodeHash": "0x010003cb0617f94a9ef85b8d99ca605ad0a4d6307a27cc9e1f63bce4161274ad", + "sourceCodeHash": "0x5a83a36a9c32a275584bc1471fddce46468d361ad972a398ce94dafdd4095c15" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010009554a9569ee48f845ec9b0068714ff68b99f65764db567d4d3f2922f734", - "sourceCodeHash": "0x52a157b3320e621fda2ee069dcecdc7df466263db343bb4af054e6647c282790" + "bytecodeHash": "0x010009556abe078c6aa331ef2b4a253234a2fec2e253da04639e230e65d39d63", + "sourceCodeHash": "0xfbcf27dcb278cb19aa2830e1a060150bb7a70430f7453622c15df4e854db41a8" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010008db84b2abb44133921b7598dec788a7c8df0dc76a06da46e54a2ac0f422", - "sourceCodeHash": "0x027e26ad98372a45fae1d70ef9e1b4efcbf5bc51bd400de5c0ce5650d55b7b43" + "bytecodeHash": "0x010008dbc08ccbee5ed443a0b8b2478710c59eb2647d1d1a86c69cd815a54569", + "sourceCodeHash": "0xb266eb5395ec756fa3fd89faf065c1f0678704296585da37d87f70e71c3b9be1" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100095b12c6e008ba4e00bc8bddb609d99125d2bbdd423bcb377afc344729a0", - "sourceCodeHash": "0x1f3a2b06df0a4a4ddd8164e0542cb07e309a51408bd21ff6ecfa1df3aea47cbb" + "bytecodeHash": "0x0100095be2849088e6c70607dfe687563aba539bc1e576b68c9f719847791bba", + "sourceCodeHash": "0xb2481246d5c6c4b82fb361b7559d14e3374111946a603288b36dfa491bb462a4" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008eb70b467979695d3f240d8db04b1b179dd02c0d7fd45a027fb4bd9ecaf", - "sourceCodeHash": "0x310e9a53b96b1861fee5eff8f4e338f88005a7d0012068fb0ee7f1b5476e5d96" + "bytecodeHash": "0x010008eb5b4e5f60e1a9a6ef185290ea38081796c84ebb84901eca32f0b94793", + "sourceCodeHash": "0xe47787d4d1f883a021054eaee00d7fb4f0ef7613b5fde99f2cd1c58f5cb9354f" } ] diff --git a/system-contracts/contracts/interfaces/IMessageRoot.sol b/system-contracts/contracts/interfaces/IMessageRoot.sol index 0983b71a4..f158b4918 100644 --- a/system-contracts/contracts/interfaces/IMessageRoot.sol +++ b/system-contracts/contracts/interfaces/IMessageRoot.sol @@ -3,6 +3,5 @@ pragma solidity 0.8.20; interface IMessageRoot { - function clearTreeAndProvidePubdata() external returns (bytes memory pubdata); function getAggregatedRoot() external view returns (bytes32 aggregatedRoot); } From e6cd619db7c6e722d7dd5af9d1122ea8fe1ce6df Mon Sep 17 00:00:00 2001 From: kelemeno <34402761+kelemeno@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:12:42 +0100 Subject: [PATCH 04/26] fix: gateway audit all fixes (#684) Co-authored-by: Stanislav Bezkorovainyi --- da-contracts/contracts/CalldataDA.sol | 42 +++-- .../contracts/RollupL1DAValidator.sol | 4 +- .../contracts/ValidiumL1DAValidator.sol | 4 +- .../contracts/bridge/L1AssetRouter.sol | 61 ++++---- .../contracts/bridge/interfaces/IL2Bridge.sol | 1 + .../bridge/interfaces/IL2BridgeLegacy.sol | 1 + .../contracts/bridgehub/Bridgehub.sol | 146 ++++++++++++------ .../contracts/bridgehub/IBridgehub.sol | 14 +- .../contracts/bridgehub/IMessageRoot.sol | 2 + .../bridgehub/ISTMDeploymentTracker.sol | 2 + .../contracts/bridgehub/MessageRoot.sol | 44 ++++-- .../bridgehub/STMDeploymentTracker.sol | 32 ++-- l1-contracts/contracts/common/Config.sol | 11 +- .../contracts/common/L2ContractAddresses.sol | 1 + .../libraries/DynamicIncrementalMerkle.sol | 25 ++- .../contracts/common/libraries/FullMerkle.sol | 24 ++- .../{Messaging.sol => MessageHashing.sol} | 8 +- .../dev-contracts/test/DummySharedBridge.sol | 8 +- .../IStateTransitionManager.sol | 2 +- .../StateTransitionManager.sol | 82 +++++++--- .../chain-deps/ZkSyncHyperchainStorage.sol | 2 +- .../chain-deps/facets/Admin.sol | 41 ++--- .../chain-deps/facets/Executor.sol | 21 ++- .../chain-deps/facets/Getters.sol | 9 +- .../chain-deps/facets/Mailbox.sol | 82 +++++----- .../chain-interfaces/IAdmin.sol | 17 +- .../chain-interfaces/IGetters.sol | 7 +- .../chain-interfaces/IMailbox.sol | 13 +- .../IZkSyncHyperchainBase.sol | 2 +- .../data-availability/CalldataDA.sol | 29 ++-- .../RelayedSLDAValidator.sol | 2 +- .../l2-deps/IComplexUpgrader.sol | 6 +- .../l2-deps/IL2GenesisUpgrade.sol | 6 +- .../l2-deps/ISystemContext.sol | 7 +- .../libraries/PriorityTree.sol | 11 +- .../contracts/upgrades/IL1GenesisUpgrade.sol | 5 +- .../contracts/upgrades/L1GenesisUpgrade.sol | 5 +- l1-contracts/deploy-scripts/AcceptAdmin.s.sol | 3 +- l1-contracts/deploy-scripts/Gateway.s.sol | 4 +- l1-contracts/scripts/sync-layer.ts | 93 ++++++----- l1-contracts/src.ts/deploy.ts | 16 +- .../integration/BridgeHubInvariantTests.t.sol | 4 +- .../foundry/integration/BridgehubTests.t.sol | 4 +- .../foundry/integration/GatewayTests.t.sol | 5 +- .../Bridgehub/experimental_bridge.t.sol | 22 +-- .../L1SharedBridge/L1SharedBridgeFails.t.sol | 56 +++---- .../L1SharedBridgeHyperEnabled.t.sol | 2 +- .../L1SharedBridge/L1SharedBridgeLegacy.t.sol | 2 +- .../concrete/Executor/_Executor_Shared.t.sol | 3 +- .../foundry/unit/concrete/Utils/Utils.sol | 5 +- .../Getters/PriorityQueueFrontOperation.t.sol | 16 -- .../test/unit_tests/custom_base_token.spec.ts | 6 +- .../unit_tests/l1_shared_bridge_test.spec.ts | 14 +- .../test/unit_tests/legacy_era_test.spec.ts | 10 +- .../test/unit_tests/synclayer.spec.ts | 101 +++--------- l2-contracts/contracts/L2ContractHelper.sol | 14 +- .../contracts/bridge/L2NativeTokenVault.sol | 26 ++-- .../bridge/interfaces/IL2NativeTokenVault.sol | 4 +- .../data-availability/RollupL2DAValidator.sol | 15 +- .../StateDiffL2DAValidator.sol | 25 +-- .../ValidiumL2DAValidator.sol | 2 - .../contracts/interfaces/IL2DAValidator.sol | 10 +- .../deploy-shared-bridge-on-l2-through-l1.ts | 4 +- l2-contracts/test/erc20.test.ts | 3 +- system-contracts/SystemContractsHashes.json | 56 +++---- system-contracts/contracts/L1Messenger.sol | 24 ++- .../contracts/PubdataChunkPublisher.sol | 8 +- .../contracts/interfaces/IComplexUpgrader.sol | 4 +- .../contracts/interfaces/IL2DAValidator.sol | 8 +- .../interfaces/IL2GenesisUpgrade.sol | 3 +- .../contracts/interfaces/ISystemContract.sol | 13 +- 71 files changed, 720 insertions(+), 644 deletions(-) rename l1-contracts/contracts/common/libraries/{Messaging.sol => MessageHashing.sol} (60%) delete mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol diff --git a/da-contracts/contracts/CalldataDA.sol b/da-contracts/contracts/CalldataDA.sol index 91ef71db9..6a2f0f5a0 100644 --- a/da-contracts/contracts/CalldataDA.sol +++ b/da-contracts/contracts/CalldataDA.sol @@ -8,10 +8,13 @@ import {BLOB_SIZE_BYTES} from "./DAUtils.sol"; uint256 constant BLOBS_SUPPORTED = 6; -/// @notice Contract that contains the functionality for process the calldata DA. +// the state diff hash, hash of pubdata + the number of blobs. +uint256 constant BLOB_DATA_OFFSET = 65; + +/// @notice Contract that contains the functionality for processing the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { - /// @notice Parses the input that the l2 Da validator has provided to the contract. + /// @notice Parses the input that the l2 DA validator has provided to the contract. /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// @param _operatorDAInput The DA input by the operator provided on L1. @@ -30,14 +33,14 @@ abstract contract CalldataDA { bytes calldata l1DaInput ) { - // The preimage under the hash `l2DAValidatorOutputHash` is expected to be in the following format: + // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: // - First 32 bytes are the hash of the uncompressed state diff. // - Then, there is a 32-byte hash of the full pubdata. // - Then, there is the 1-byte number of blobs published. // - Then, there are linear hashes of the published blobs, 32 bytes each. // Check that it accommodates enough pubdata for the state diff hash, hash of pubdata + the number of blobs. - require(_operatorDAInput.length >= 32 + 32 + 1, "too small"); + require(_operatorDAInput.length >= BLOB_DATA_OFFSET, "too small"); stateDiffHash = bytes32(_operatorDAInput[:32]); fullPubdataHash = bytes32(_operatorDAInput[32:64]); @@ -49,41 +52,46 @@ abstract contract CalldataDA { // the `_maxBlobsSupported` blobsLinearHashes = new bytes32[](_maxBlobsSupported); - require(_operatorDAInput.length >= 65 + 32 * blobsProvided, "invalid blobs hashes"); - - uint256 ptr = 65; + require(_operatorDAInput.length >= BLOB_DATA_OFFSET + 32 * blobsProvided, "invalid blobs hashes"); - for (uint256 i = 0; i < blobsProvided; ++i) { - // Take the 32 bytes of the blob linear hash - blobsLinearHashes[i] = bytes32(_operatorDAInput[ptr:ptr + 32]); - ptr += 32; + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let blobsPtr := add(blobsLinearHashes, 0x20) + let inputPtr := add(_operatorDAInput.offset, BLOB_DATA_OFFSET) + calldatacopy(blobsPtr, inputPtr, mul(blobsProvided, 32)) } - // Now, we need to double check that the provided input was indeed retutned by the L2 DA validator. + uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; + + // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. require(keccak256(_operatorDAInput[:ptr]) == _l2DAValidatorOutputHash, "invalid l2 DA output hash"); - // The rest of the output were provided specifically by the operator + // The rest of the output was provided specifically by the operator l1DaInput = _operatorDAInput[ptr:]; } /// @notice Verify that the calldata DA was correctly provided. - /// todo: better doc comments + /// @param _blobsProvided The number of blobs provided. + /// @param _fullPubdataHash Hash of the pubdata preimage. + /// @param _maxBlobsSupported Maximum number of blobs supported. + /// @param _pubdataInput Full pubdata + an additional 32 bytes containing the blob commitment for the pubdata. + /// @dev We supply the blob commitment as part of the pubdata because even with calldata the prover will check these values. function _processCalldataDA( uint256 _blobsProvided, bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput ) internal pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + require(_blobsProvided == 1, "one blob with calldata"); + // We typically do not know whether we'll use calldata or blobs at the time when // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. blobCommitments = new bytes32[](_maxBlobsSupported); - require(_blobsProvided == 1, "one one blob with calldata"); - _pubdata = _pubdataInput[:_pubdataInput.length - 32]; - // FIXME: allow larger lengths for SyncLayer-based chains. + // FIXME: allow larger lengths for Gateway-based chains. require(_pubdata.length <= BLOB_SIZE_BYTES, "cz"); require(_fullPubdataHash == keccak256(_pubdata), "wp"); blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - 32:_pubdataInput.length]); diff --git a/da-contracts/contracts/RollupL1DAValidator.sol b/da-contracts/contracts/RollupL1DAValidator.sol index c50097906..9df028a63 100644 --- a/da-contracts/contracts/RollupL1DAValidator.sol +++ b/da-contracts/contracts/RollupL1DAValidator.sol @@ -14,7 +14,7 @@ uint256 constant BLOBS_SUPPORTED = 6; contract RollupL1DAValidator is IL1DAValidator, CalldataDA { /// @dev The published blob commitments. Note, that the correctness of blob commitment with relation to the linear hash - /// is *not* checked in this contract, but is expected to be checked at the veriifcation stage of the ZK contract. + /// is *not* checked in this contract, but is expected to be checked at the verification stage of the ZK contract. mapping(bytes32 blobCommitment => bool isPublished) public publishedBlobCommitments; /// @notice Publishes certain blobs, marking commitments to them as published. @@ -81,7 +81,7 @@ contract RollupL1DAValidator is IL1DAValidator, CalldataDA { uint256 versionedHashIndex = 0; - // we iterate over the `_operatorDAInput`, while advacning the pointer by `BLOB_DA_INPUT_SIZE` each time + // we iterate over the `_operatorDAInput`, while advancing the pointer by `BLOB_DA_INPUT_SIZE` each time for (uint256 i = 0; i < _blobsProvided; ++i) { bytes calldata commitmentData = _operatorDAInput[:PUBDATA_COMMITMENT_SIZE]; bytes32 prepublishedCommitment = bytes32( diff --git a/da-contracts/contracts/ValidiumL1DAValidator.sol b/da-contracts/contracts/ValidiumL1DAValidator.sol index b63ff5a3a..f525e04b8 100644 --- a/da-contracts/contracts/ValidiumL1DAValidator.sol +++ b/da-contracts/contracts/ValidiumL1DAValidator.sol @@ -15,7 +15,7 @@ contract ValidiumL1DAValidator is IL1DAValidator { ) external override returns (L1DAValidatorOutput memory output) { // For Validiums, we expect the operator to just provide the data for us. // We don't need to do any checks with regard to the l2DAValidatorOutputHash. - require(_operatorDAInput.length == 32); + require(_operatorDAInput.length == 32, "ValL1DA wrong input length"); bytes32 stateDiffHash = abi.decode(_operatorDAInput, (bytes32)); @@ -24,6 +24,6 @@ contract ValidiumL1DAValidator is IL1DAValidator { } function supportsInterface(bytes4 interfaceId) external pure returns (bool) { - return interfaceId == type(IL1DAValidator).interfaceId; + return (interfaceId == this.supportsInterface.selector) || (interfaceId == type(IL1DAValidator).interfaceId); } } diff --git a/l1-contracts/contracts/bridge/L1AssetRouter.sol b/l1-contracts/contracts/bridge/L1AssetRouter.sol index 3b4730a4c..d1df09d19 100644 --- a/l1-contracts/contracts/bridge/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/L1AssetRouter.sol @@ -110,7 +110,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @notice Checks that the message sender is the bridgehub. modifier onlyBridgehub() { - require(msg.sender == address(BRIDGE_HUB), "ShB not BH"); + require(msg.sender == address(BRIDGE_HUB), "L1AR: not BH"); _; } @@ -118,14 +118,14 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab modifier onlyBridgehubOrEra(uint256 _chainId) { require( msg.sender == address(BRIDGE_HUB) || (_chainId == ERA_CHAIN_ID && msg.sender == ERA_DIAMOND_PROXY), - "L1AssetRouter: msg.sender not equal to bridgehub or era chain" + "L1AR: msg.sender not equal to bridgehub or era chain" ); _; } /// @notice Checks that the message sender is the legacy bridge. modifier onlyLegacyBridge() { - require(msg.sender == address(legacyBridge), "ShB not legacy bridge"); + require(msg.sender == address(legacyBridge), "L1AR: not legacy bridge"); _; } @@ -159,7 +159,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _eraLegacyBridgeLastDepositBatch, uint256 _eraLegacyBridgeLastDepositTxNumber ) external reentrancyGuardInitializer initializer { - require(_owner != address(0), "ShB owner 0"); + require(_owner != address(0), "L1AR: owner 0"); _transferOwnership(_owner); if (eraPostDiamondUpgradeFirstBatch == 0) { eraPostDiamondUpgradeFirstBatch = _eraPostDiamondUpgradeFirstBatch; @@ -174,7 +174,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @param _token The address of the token to be transferred to NTV. function transferTokenToNTV(address _token) external { address ntvAddress = address(nativeTokenVault); - require(msg.sender == ntvAddress, "ShB: not NTV"); + require(msg.sender == ntvAddress, "L1AR: not NTV"); if (ETH_TOKEN_ADDRESS == _token) { uint256 amount = address(this).balance; bool callSuccess; @@ -182,7 +182,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab assembly { callSuccess := call(gas(), ntvAddress, amount, 0, 0, 0, 0) } - require(callSuccess, "ShB: eth transfer failed"); + require(callSuccess, "L1AR: eth transfer failed"); } else { IERC20(_token).safeTransfer(ntvAddress, IERC20(_token).balanceOf(address(this))); } @@ -193,7 +193,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @param _chainId The ID of the ZK chain. /// @param _token The address of the token which was previously deposit to shared bridge. function clearChainBalance(uint256 _chainId, address _token) external { - require(msg.sender == address(nativeTokenVault), "ShB: not NTV"); + require(msg.sender == address(nativeTokenVault), "L1AR: not NTV"); chainBalance[_chainId][_token] = 0; } @@ -201,8 +201,8 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev Should be called only once by the owner. /// @param _legacyBridge The address of the legacy bridge. function setL1Erc20Bridge(address _legacyBridge) external onlyOwner { - require(address(legacyBridge) == address(0), "ShB: legacy bridge already set"); - require(_legacyBridge != address(0), "ShB: legacy bridge 0"); + require(address(legacyBridge) == address(0), "L1AR: legacy bridge already set"); + require(_legacyBridge != address(0), "L1AR: legacy bridge 0"); legacyBridge = IL1ERC20Bridge(_legacyBridge); } @@ -210,8 +210,8 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev Should be called only once by the owner. /// @param _nativeTokenVault The address of the native token vault. function setNativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external onlyOwner { - require(address(nativeTokenVault) == address(0), "ShB: native token vault already set"); - require(address(_nativeTokenVault) != address(0), "ShB: native token vault 0"); + require(address(nativeTokenVault) == address(0), "L1AR: native token vault already set"); + require(address(_nativeTokenVault) != address(0), "L1AR: native token vault 0"); nativeTokenVault = _nativeTokenVault; } @@ -246,7 +246,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes32 _assetId, address _assetAddressOnCounterPart ) external payable returns (bytes32 l2TxHash) { - require(msg.sender == assetDeploymentTracker[_assetId] || msg.sender == owner(), "ShB: only ADT or owner"); + require(msg.sender == assetDeploymentTracker[_assetId] || msg.sender == owner(), "L1AR: only ADT or owner"); bytes memory l2Calldata = abi.encodeCall( IL2Bridge.setAssetHandlerAddress, @@ -256,7 +256,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ chainId: _chainId, l2Contract: L2_ASSET_ROUTER_ADDR, - mintValue: _mintValue, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the base token bridge for gas + mintValue: _mintValue, // l2 gas + l2 msg.value the bridgehub will withdraw the mintValue from the base token bridge for gas l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub l2Calldata: l2Calldata, l2GasLimit: _l2TxGasLimit, @@ -302,7 +302,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab l1AssetHandler = assetHandlerAddress[_assetId]; // Check if no asset handler is set if (l1AssetHandler == address(0)) { - require(uint256(_assetId) <= type(uint160).max, "ShB: only address can be registered"); + require(uint256(_assetId) <= type(uint160).max, "L1AR: only address can be registered"); l1AssetHandler = address(nativeTokenVault); nativeTokenVault.registerToken(address(uint160(uint256(_assetId)))); } @@ -389,7 +389,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab legacyDeposit = true; } - require(BRIDGE_HUB.baseTokenAssetId(_chainId) != assetId, "ShB: baseToken deposit not supported"); + require(BRIDGE_HUB.baseTokenAssetId(_chainId) != assetId, "L1AR: baseToken deposit not supported"); bytes memory l2BridgeMintCalldata = _burn({ _chainId: _chainId, @@ -485,7 +485,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes32 _txDataHash, bytes32 _txHash ) external override onlyBridgehub whenNotPaused { - require(depositHappened[_chainId][_txHash] == 0x00, "ShB tx hap"); + require(depositHappened[_chainId][_txHash] == 0x00, "L1AR: tx hap"); depositHappened[_chainId][_txHash] = _txDataHash; emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); } @@ -551,13 +551,13 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab require(proofValid, "yn"); } - require(!_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch), "ShB: legacy cFD"); + require(!_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch), "L1AR: legacy cFD"); { bytes32 dataHash = depositHappened[_chainId][_l2TxHash]; address l1Token = nativeTokenVault.tokenAddress(_assetId); (uint256 amount, address prevMsgSender) = abi.decode(_transferData, (uint256, address)); bytes32 txDataHash = keccak256(abi.encode(prevMsgSender, l1Token, amount)); - require(dataHash == txDataHash, "ShB: d.it not hap"); + require(dataHash == txDataHash, "L1AR: d.it not hap"); } delete depositHappened[_chainId][_l2TxHash]; @@ -571,7 +571,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @param _l2BatchNumber The L2 batch number for the withdrawal. /// @return Whether withdrawal was initiated on zkSync Era before diamond proxy upgrade. function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - require((_chainId != ERA_CHAIN_ID) || eraPostDiamondUpgradeFirstBatch != 0, "ShB: diamondUFB not set for Era"); + require((_chainId != ERA_CHAIN_ID) || eraPostDiamondUpgradeFirstBatch != 0, "L1AR: diamondUFB not set for Era"); return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); } @@ -582,7 +582,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { require( (_chainId != ERA_CHAIN_ID) || eraPostLegacyBridgeUpgradeFirstBatch != 0, - "ShB: LegacyUFB not set for Era" + "L1AR: LegacyUFB not set for Era" ); return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); } @@ -599,7 +599,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab ) internal view returns (bool) { require( (_chainId != ERA_CHAIN_ID) || (eraLegacyBridgeLastDepositBatch != 0), - "ShB: last deposit time not set for Era" + "L1AR: last deposit time not set for Era" ); return (_chainId == ERA_CHAIN_ID) && @@ -657,12 +657,15 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes calldata _message, bytes32[] calldata _merkleProof ) internal nonReentrant whenNotPaused returns (address l1Receiver, bytes32 assetId, uint256 amount) { - require(!isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex], "Withdrawal is already finalized"); + require( + !isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex], + "L1AR: Withdrawal is already finalized" + ); isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex] = true; // Handling special case for withdrawal from zkSync Era initiated before Shared Bridge. - require(!_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber), "ShB: legacy eth withdrawal"); - require(!_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber), "ShB: legacy token withdrawal"); + require(!_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy eth withdrawal"); + require(!_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy token withdrawal"); bytes memory transferData; { MessageParams memory messageParams = MessageParams({ @@ -713,7 +716,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab _message: l2ToL1Message, _proof: _merkleProof }); - require(success, "ShB withd w proof"); // withdrawal wrong proof + require(success, "L1AR: withd w proof"); // withdrawal wrong proof } /// @notice Parses the withdrawal message and returns withdrawal details. @@ -735,7 +738,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab // No length is assume. The assetId is decoded and the mintData is passed to respective assetHandler // So the data is expected to be at least 56 bytes long. - require(_l2ToL1message.length >= 56, "ShB wrong msg len"); // wrong message length + require(_l2ToL1message.length >= 56, "L1AR: wrong msg len"); // wrong message length uint256 amount; address l1Receiver; @@ -754,7 +757,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab // Check that the message length is correct. // It should be equal to the length of the function signature + address + address + uint256 = 4 + 20 + 20 + 32 = // 76 (bytes). - require(_l2ToL1message.length == 76, "ShB wrong msg len 2"); + require(_l2ToL1message.length == 76, "L1AR: wrong msg len 2"); (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); @@ -766,7 +769,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab (assetId, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); transferData = UnsafeBytes.readRemainingBytes(_l2ToL1message, offset); } else { - revert("ShB Incorrect message function selector"); + revert("L1AR: Incorrect message function selector"); } } @@ -864,7 +867,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _l2TxGasPerPubdataByte, address _refundRecipient ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 l2TxHash) { - require(_l1Token != L1_WETH_TOKEN, "ShB: WETH deposit not supported 2"); + require(_l1Token != L1_WETH_TOKEN, "L1AR: WETH deposit not supported 2"); bytes32 _assetId; bytes memory l2BridgeMintCalldata; diff --git a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol index 07afa8de2..b39816edb 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2Bridge { function finalizeDeposit(bytes32 _assetId, bytes calldata _data) external; diff --git a/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol index 111917acf..ffa00afdb 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2BridgeLegacy { function finalizeDeposit( address _l1Sender, diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 9f206697b..3182e6ad5 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -12,7 +12,7 @@ import {IL1AssetRouter} from "../bridge/interfaces/IL1AssetRouter.sol"; import {IStateTransitionManager} from "../state-transition/IStateTransitionManager.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {IZkSyncHyperchain} from "../state-transition/chain-interfaces/IZkSyncHyperchain.sol"; -import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS} from "../common/Config.sol"; +import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS} from "../common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; @@ -29,7 +29,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice the asset id of Eth bytes32 internal immutable ETH_TOKEN_ASSET_ID; - /// @dev The chain id of L1, this contract will be deployed on multiple layers. + /// @notice The chain id of L1, this contract will be deployed on multiple layers. uint256 public immutable L1_CHAIN_ID; /// @notice all the ether is held by the weth bridge @@ -54,12 +54,14 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus // FIXME: `messageRoot` DOES NOT contain messages that come from the current layer and go to the settlement layer. // it may make sense to store the final root somewhere for interop purposes. - // THough maybe it can be postponed. + // Though maybe it can be postponed. + /// @notice The contract that stores the cross-chain message root for each chain and the aggregated root. IMessageRoot public override messageRoot; /// @notice Mapping from chain id to encoding of the base token used for deposits / withdrawals mapping(uint256 chainId => bytes32 baseTokenAssetId) public baseTokenAssetId; + /// @notice The deployment tracker for the state transition managers. ISTMDeploymentTracker public stmDeployer; /// @dev asset info used to identify chains in the Shared Bridge @@ -68,8 +70,10 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev used to indicate the currently active settlement layer for a given chainId mapping(uint256 chainId => uint256 activeSettlementLayerChainId) public settlementLayer; + /// @notice shows whether the given chain can be used as a settlement layer. + /// @dev the Gateway will be one of the possible settlement layers. The L1 is also a settlement layer. /// @dev Sync layer chain is expected to have .. as the base token. - mapping(uint256 chainId => bool isWhitelistedSyncLayer) public whitelistedSettlementLayers; + mapping(uint256 chainId => bool isWhitelistedSettlementLayer) public whitelistedSettlementLayers; /// @notice to avoid parity hack constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { @@ -81,20 +85,33 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice used to initialize the contract /// @notice this contract is also deployed on L2 as a system contract there the owner and the related functions will not be used + /// @param _owner the owner of the contract function initialize(address _owner) external reentrancyGuardInitializer { _transferOwnership(_owner); } modifier onlyOwnerOrAdmin() { - require(msg.sender == admin || msg.sender == owner(), "Bridgehub: not owner or admin"); + require(msg.sender == admin || msg.sender == owner(), "BH: not owner or admin"); _; } + /// @param _chainId the chainId of the chain modifier onlyChainSTM(uint256 _chainId) { require(msg.sender == stateTransitionManager[_chainId], "BH: not chain STM"); _; } + modifier onlyAliasedZero() { + /// There is no sender for the wrapping, we use a virtual address. + require(msg.sender == VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, "BH: not aliased zero"); + _; + } + + modifier onlyAssetRouter() { + require(msg.sender == address(sharedBridge), "BH: not asset router"); + _; + } + //// Initialization and registration /// @inheritdoc IBridgehub @@ -121,14 +138,11 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus emit NewAdmin(previousAdmin, currentPendingAdmin); } - /// @notice To set stmDeploymetTracker, only Owner. Not done in initialize, as - /// the order of deployment is Bridgehub, Shared bridge, and then we call this - function setSTMDeployer(ISTMDeploymentTracker _stmDeployer) external onlyOwner { - stmDeployer = _stmDeployer; - } - - /// @notice To set shared bridge, only Owner. Not done in initialize, as - /// the order of deployment is Bridgehub, Shared bridge, and then we call this + /// @notice To set the addresses of some of the ecosystem contracts, only Owner. Not done in initialize, as + /// the order of deployment is Bridgehub, other contracts, and then we call this. + /// @param _sharedBridge the shared bridge address + /// @param _stmDeployer the stm deployment tracker address + /// @param _messageRoot the message root address function setAddresses( address _sharedBridge, ISTMDeploymentTracker _stmDeployer, @@ -142,46 +156,45 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus //// Registry /// @notice State Transition can be any contract with the appropriate interface/functionality + /// @param _stateTransitionManager the state transition manager address to be added function addStateTransitionManager(address _stateTransitionManager) external onlyOwner { require( !stateTransitionManagerIsRegistered[_stateTransitionManager], - "Bridgehub: state transition already registered" + "BH: state transition already registered" ); stateTransitionManagerIsRegistered[_stateTransitionManager] = true; } /// @notice State Transition can be any contract with the appropriate interface/functionality /// @notice this stops new Chains from using the STF, old chains are not affected + /// @param _stateTransitionManager the state transition manager address to be removed function removeStateTransitionManager(address _stateTransitionManager) external onlyOwner { - require( - stateTransitionManagerIsRegistered[_stateTransitionManager], - "Bridgehub: state transition not registered yet" - ); + require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered yet"); stateTransitionManagerIsRegistered[_stateTransitionManager] = false; } /// @notice token can be any contract with the appropriate interface/functionality + /// @param _token the token address to be added function addToken(address _token) external onlyOwner { - require(!tokenIsRegistered[_token], "Bridgehub: token already registered"); + require(!tokenIsRegistered[_token], "BH: token already registered"); tokenIsRegistered[_token] = true; } - /// @notice To set shared bridge, only Owner. Not done in initialize, as - /// the order of deployment is Bridgehub, Shared bridge, and then we call this - function setSharedBridge(address _sharedBridge) external onlyOwner { - sharedBridge = IL1AssetRouter(_sharedBridge); - } - - function registerSyncLayer( - uint256 _newSyncLayerChainId, + /// @notice Used to register a chain as a settlement layer. + /// @param _newSettlementLayerChainId the chainId of the chain + /// @param _isWhitelisted whether the chain is a whitelisted settlement layer + function registerSettlementLayer( + uint256 _newSettlementLayerChainId, bool _isWhitelisted - ) external onlyChainSTM(_newSyncLayerChainId) { - whitelistedSettlementLayers[_newSyncLayerChainId] = _isWhitelisted; + ) external onlyChainSTM(_newSettlementLayerChainId) { + whitelistedSettlementLayers[_newSettlementLayerChainId] = _isWhitelisted; // TODO: emit event } - /// @dev Used to set the assedAddress for a given assetInfo. + /// @dev Used to set the assetAddress for a given assetInfo. + /// @param _additionalData the additional data to identify the asset + /// @param _assetAddress the asset handler address function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetAddress) external { address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); // Todo: this might be dangerous. We should decide based on the tx type. bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); /// todo make other asse @@ -192,14 +205,19 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus ///// Getters /// @notice return the state transition chain contract for a chainId + /// @param _chainId the chainId of the chain function getHyperchain(uint256 _chainId) public view returns (address) { return IStateTransitionManager(stateTransitionManager[_chainId]).getHyperchain(_chainId); } + /// @notice return the stm asset id of a chain. + /// @param _chainId the chainId of the chain function stmAssetIdFromChainId(uint256 _chainId) public view override returns (bytes32) { return stmAssetId(stateTransitionManager[_chainId]); } + /// @notice return the stm asset id of an stm. + /// @param _stmAddress the stm address function stmAssetId(address _stmAddress) public view override returns (bytes32) { return keccak256(abi.encode(L1_CHAIN_ID, address(stmDeployer), bytes32(uint256(uint160(_stmAddress))))); } @@ -208,6 +226,13 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice register new chain /// @notice for Eth the baseToken address is 1 + /// @param _chainId the chainId of the chain + /// @param _stateTransitionManager the state transition manager address + /// @param _baseToken the base token of the chain + /// @param _salt the salt for the chainId, currently not used + /// @param _admin the admin of the chain + /// @param _initData the fixed initialization data for the chain + /// @param _factoryDeps the factory dependencies for the chain's deployment function createNewChain( uint256 _chainId, address _stateTransitionManager, @@ -319,6 +344,10 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus } /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId the chainId of the chain + /// @param _gasPrice the gas price for the l2 priority operation + /// @param _l2GasLimit the gas limit for the l2 priority operation + /// @param _l2GasPerPubdataByteLimit the gas per pubdata byte limit for the l2 priority operation function l2TransactionBaseCost( uint256 _chainId, uint256 _gasPrice, @@ -333,6 +362,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// this assumes that either ether is the base token or /// the msg.sender has approved mintValue allowance for the sharedBridge. /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token + /// @param _request the request for the L2 transaction function requestL2TransactionDirect( L2TransactionRequestDirect calldata _request ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { @@ -341,9 +371,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus { bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; if (tokenAssetId == ETH_TOKEN_ASSET_ID) { - require(msg.value == _request.mintValue, "Bridgehub: msg.value mismatch 1"); + require(msg.value == _request.mintValue, "BH: msg.value mismatch 1"); } else { - require(msg.value == 0, "Bridgehub: non-eth bridge with msg.value"); + require(msg.value == 0, "BH: non-eth bridge with msg.value"); } // slither-disable-next-line arbitrary-send-eth @@ -381,20 +411,23 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// Each contract that handles the users ERC20 tokens needs approvals from the user, this contract allows /// the user to approve for each token only its respective bridge /// @notice This function is great for contract calls to L2, the secondBridge can be any contract. + /// @param _request the request for the L2 transaction function requestL2TransactionTwoBridges( L2TransactionRequestTwoBridgesOuter calldata _request ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { + require( + _request.secondBridgeAddress > BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, + "BH: second bridge address too low" + ); // to avoid calls to precompiles + { bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; uint256 baseTokenMsgValue; if (tokenAssetId == ETH_TOKEN_ASSET_ID) { - require( - msg.value == _request.mintValue + _request.secondBridgeValue, - "Bridgehub: msg.value mismatch 2" - ); + require(msg.value == _request.mintValue + _request.secondBridgeValue, "BH: msg.value mismatch 2"); baseTokenMsgValue = _request.mintValue; } else { - require(msg.value == _request.secondBridgeValue, "Bridgehub: msg.value mismatch 3"); + require(msg.value == _request.secondBridgeValue, "BH: msg.value mismatch 3"); baseTokenMsgValue = 0; } // slither-disable-next-line arbitrary-send-eth @@ -417,14 +450,10 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _request.secondBridgeCalldata ); - require(outputRequest.magicValue == TWO_BRIDGES_MAGIC_VALUE, "Bridgehub: magic value mismatch"); + require(outputRequest.magicValue == TWO_BRIDGES_MAGIC_VALUE, "BH: magic value mismatch"); address refundRecipient = AddressAliasHelper.actualRefundRecipient(_request.refundRecipient, msg.sender); - require( - _request.secondBridgeAddress > BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, - "Bridgehub: second bridge address too low" - ); // to avoid calls to precompiles canonicalTxHash = IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction( BridgehubL2TransactionRequest({ sender: _request.secondBridgeAddress, @@ -446,16 +475,22 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus ); } - function forwardTransactionOnSyncLayer( + /// @notice Used to forward a transaction on the gateway to the chains mailbox (from L1). + /// @param _chainId the chainId of the chain + /// @param _transaction the transaction to be forwarded + /// @param _factoryDeps the factory dependencies for the transaction + /// @param _canonicalTxHash the canonical transaction hash + /// @param _expirationTimestamp the expiration timestamp for the transaction + function forwardTransactionOnGateway( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp - ) external override { + ) external override onlyAliasedZero { require(L1_CHAIN_ID != block.chainid, "BH: not in sync layer mode"); address hyperchain = getHyperchain(_chainId); - IZkSyncHyperchain(hyperchain).bridgehubRequestL2TransactionOnSyncLayer( + IZkSyncHyperchain(hyperchain).bridgehubRequestL2TransactionOnGateway( _transaction, _factoryDeps, _canonicalTxHash, @@ -467,14 +502,18 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus Chain migration //////////////////////////////////////////////////////////////*/ - /// @dev we can move assets using these + /// @notice IL1AssetHandler interface, used to migrate (transfer) a chain to the settlement layer. + /// @param _settlementChainId the chainId of the settlement chain, i.e. where the message and the migrating chain is sent. + /// @param _assetId the assetId of the migrating chain's STM + /// @param _prevMsgSender the previous message sender + /// @param _data the data for the migration function bridgeBurn( uint256 _settlementChainId, uint256, bytes32 _assetId, address _prevMsgSender, bytes calldata _data - ) external payable override returns (bytes memory bridgehubMintData) { + ) external payable override onlyAssetRouter returns (bytes memory bridgehubMintData) { require(whitelistedSettlementLayers[_settlementChainId], "BH: SL not whitelisted"); (uint256 _chainId, bytes memory _stmData, bytes memory _chainData) = abi.decode(_data, (uint256, bytes, bytes)); @@ -495,11 +534,14 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus // TODO: double check that get only returns when chain id is there. } + /// @dev IL1AssetHandler interface, used to receive a chain on the settlement layer. + /// @param _assetId the assetId of the chain's STM + /// @param _bridgehubMintData the data for the mint function bridgeMint( uint256, bytes32 _assetId, bytes calldata _bridgehubMintData - ) external payable override returns (address l1Receiver) { + ) external payable override onlyAssetRouter returns (address l1Receiver) { (uint256 _chainId, bytes memory _stmData, bytes memory _chainMintData) = abi.decode( _bridgehubMintData, (uint256, bytes, bytes) @@ -515,16 +557,20 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus hyperchain = IStateTransitionManager(stm).forwardedBridgeMint(_chainId, _stmData); } - IMessageRoot(messageRoot).addNewChainIfNeeded(_chainId); + messageRoot.addNewChainIfNeeded(_chainId); IZkSyncHyperchain(hyperchain).forwardedBridgeMint(_chainMintData); return address(0); } + /// @dev IL1AssetHandler interface, used to undo a failed migration of a chain. + /// @param _chainId the chainId of the chain + /// @param _assetId the assetId of the chain's STM + /// @param _data the data for the recovery function bridgeRecoverFailedTransfer( uint256 _chainId, bytes32 _assetId, bytes calldata _data - ) external payable override {} + ) external payable override onlyAssetRouter {} /*////////////////////////////////////////////////////////////// PAUSE diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 98f9cf09c..78d7a7b4b 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -40,6 +40,8 @@ struct L2TransactionRequestTwoBridgesInner { bytes32 txDataHash; } +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IBridgehub is IL1AssetHandler { /// @notice pendingAdmin is changed /// @dev Also emitted when new admin is accepted and in this case, `newPendingAdmin` would be zero address @@ -148,17 +150,13 @@ interface IBridgehub is IL1AssetHandler { IMessageRoot _messageRoot ) external; - // function relayTxThroughBH(uint256 _baseDestChainId, uint256 _destChainId, bytes calldata _dataToRelay) external; - - // function registerCounterpart(uint256 chainid, address _counterpart) external; - event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); function whitelistedSettlementLayers(uint256 _chainId) external view returns (bool); - function registerSyncLayer(uint256 _newSyncLayerChainId, bool _isWhitelisted) external; + function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external; - // function finalizeMigrationToSyncLayer( + // function finalizeMigrationToGateway( // uint256 _chainId, // address _baseToken, // address _sharedBridge, @@ -168,7 +166,7 @@ interface IBridgehub is IL1AssetHandler { // bytes calldata _diamondCut // ) external; - function forwardTransactionOnSyncLayer( + function forwardTransactionOnGateway( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, @@ -182,8 +180,6 @@ interface IBridgehub is IL1AssetHandler { function stmDeployer() external view returns (ISTMDeploymentTracker); - function setSTMDeployer(ISTMDeploymentTracker _stmDeployer) external; - function stmAssetIdToAddress(bytes32 _assetInfo) external view returns (address); function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetAddress) external; diff --git a/l1-contracts/contracts/bridgehub/IMessageRoot.sol b/l1-contracts/contracts/bridgehub/IMessageRoot.sol index ecfa33682..a0791b922 100644 --- a/l1-contracts/contracts/bridgehub/IMessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/IMessageRoot.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.24; import {IBridgehub} from "./IBridgehub.sol"; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IMessageRoot { function BRIDGE_HUB() external view returns (IBridgehub); diff --git a/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol index 2f54c86c8..a1f71cdbf 100644 --- a/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol @@ -5,6 +5,8 @@ pragma solidity 0.8.24; import {L2TransactionRequestTwoBridgesInner, IBridgehub} from "./IBridgehub.sol"; import {IL1AssetRouter} from "../bridge/interfaces/IL1AssetRouter.sol"; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface ISTMDeploymentTracker { function bridgehubDeposit( uint256 _chainId, diff --git a/l1-contracts/contracts/bridgehub/MessageRoot.sol b/l1-contracts/contracts/bridgehub/MessageRoot.sol index a4aa9ef63..7b03f62fe 100644 --- a/l1-contracts/contracts/bridgehub/MessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/MessageRoot.sol @@ -12,7 +12,7 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {FullMerkle} from "../common/libraries/FullMerkle.sol"; -import {Messaging} from "../common/libraries/Messaging.sol"; +import {MessageHashing} from "../common/libraries/MessageHashing.sol"; import {MAX_NUMBER_OF_HYPERCHAINS} from "../common/Config.sol"; @@ -26,6 +26,9 @@ bytes32 constant SHARED_ROOT_TREE_EMPTY_HASH = bytes32( 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 ); +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev The MessageRoot contract is responsible for storing the cross message roots of the chains and the aggregated root of all chains. contract MessageRoot is IMessageRoot, ReentrancyGuard { event AddedChain(uint256 indexed chainId, uint256 indexed chainIndex); @@ -36,21 +39,26 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. IBridgehub public immutable override BRIDGE_HUB; + /// @notice The number of chains that are registered. uint256 public chainCount; + /// @notice The mapping from chainId to chainIndex. mapping(uint256 chainId => uint256 chainIndex) public chainIndex; + /// @notice The mapping from chainIndex to chainId. mapping(uint256 chainIndex => uint256 chainId) public chainIndexToId; // There are two ways to distinguish chains: - // - Either by reserving the index 0 as a special value which denotes an unregistede chain + // - Either by reserving the index 0 as a special value which denotes an unregistered chain // - Use a separate mapping // The second approach is used due to explicitness. + /// @notice The mapping from chainId to whether the chain is registered. Used because the chainIndex can be 0. mapping(uint256 chainId => bool isRegistered) public chainRegistered; + /// @notice The shared full merkle tree storing the aggregate hash. FullMerkle.FullTree public sharedTree; - /// @dev the incremental merkle tree storing the chain message roots + /// @dev The incremental merkle tree storing the chain message roots. mapping(uint256 chainId => DynamicIncrementalMerkle.Bytes32PushTree tree) internal chainTree; /// @notice only the bridgehub can call @@ -60,12 +68,14 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { } /// @notice only the bridgehub can call + /// @param _chainId the chainId of the chain modifier onlyChain(uint256 _chainId) { require(msg.sender == BRIDGE_HUB.getHyperchain(_chainId), "MR: only chain"); _; } - /// @dev Contract is expected to be used as proxy implementation. + /// @dev Contract is expected to be used as proxy implementation on L1, but as a system contract on L2. + /// This means we call the _initialize in both the constructor and the initialize functions. /// @dev Initialize the implementation to prevent Parity hack. constructor(IBridgehub _bridgehub) reentrancyGuardInitializer { BRIDGE_HUB = _bridgehub; @@ -77,34 +87,44 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { _initialize(); } + /// @dev internal initialize. function _initialize() internal { // slither-disable-next-line unused-return sharedTree.setup(SHARED_ROOT_TREE_EMPTY_HASH); } + /// @dev Adds a new chain to the message root. + /// @param _chainId the chainId of the chain function addNewChain(uint256 _chainId) external onlyBridgehub { require(!chainRegistered[_chainId], "MR: chain exists"); _addNewChain(_chainId); } + /// @dev Adds a new chain to the message root if it has not been added yet. + /// @param _chainId the chainId of the chain function addNewChainIfNeeded(uint256 _chainId) external onlyBridgehub { if (!chainRegistered[_chainId]) { _addNewChain(_chainId); } } + /// @dev Gets the aggregated root of all chains. function getAggregatedRoot() external view returns (bytes32) { return sharedTree.root(); } + /// @dev Gets the message root of a single chain. + /// @param _chainId the chainId of the chain function getChainRoot(uint256 _chainId) external view returns (bytes32) { return chainTree[_chainId].root(); } + /// @dev Adds a single chain to the message root. + /// @param _chainId the chainId of the chain function _addNewChain(uint256 _chainId) internal { // The chain itself can not be the part of the message root. // The message root will only aggregate chains that settle on it. - require(_chainId != block.chainid); + require(_chainId != block.chainid, "MR: chainId is this chain"); chainRegistered[_chainId] = true; @@ -119,7 +139,7 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { // slither-disable-next-line unused-return bytes32 initialHash = chainTree[_chainId].setup(CHAIN_TREE_EMPTY_ENTRY_HASH); // slither-disable-next-line unused-return - sharedTree.pushNewLeaf(Messaging.chainIdLeafHash(initialHash, _chainId)); + sharedTree.pushNewLeaf(MessageHashing.chainIdLeafHash(initialHash, _chainId)); emit AddedChain(_chainId, cachedChainCount); } @@ -127,6 +147,9 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { event Preimage(bytes32 one, bytes32 two); /// @dev add a new chainBatchRoot to the chainTree + /// @param _chainId the chainId of the chain + /// @param _batchNumber the batch number + /// @param _chainBatchRoot the chain batch root function addChainBatchRoot( uint256 _chainId, uint256 _batchNumber, @@ -135,21 +158,22 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { require(chainRegistered[_chainId], "MR: not registered"); bytes32 chainRoot; // slither-disable-next-line unused-return - (, chainRoot) = chainTree[_chainId].push(Messaging.batchLeafHash(_chainBatchRoot, _batchNumber)); + (, chainRoot) = chainTree[_chainId].push(MessageHashing.batchLeafHash(_chainBatchRoot, _batchNumber)); // slither-disable-next-line unused-return - sharedTree.updateLeaf(chainIndex[_chainId], Messaging.chainIdLeafHash(chainRoot, _chainId)); + sharedTree.updateLeaf(chainIndex[_chainId], MessageHashing.chainIdLeafHash(chainRoot, _chainId)); - emit Preimage(chainRoot, Messaging.chainIdLeafHash(chainRoot, _chainId)); + emit Preimage(chainRoot, MessageHashing.chainIdLeafHash(chainRoot, _chainId)); emit AppendedChainBatchRoot(_chainId, _batchNumber, _chainBatchRoot); } + /// @dev Updates the full merkle tree with the current roots of the chains. function updateFullTree() public { uint256 cachedChainCount = chainCount; bytes32[] memory newLeaves = new bytes32[](cachedChainCount); for (uint256 i = 0; i < cachedChainCount; ++i) { - newLeaves[i] = Messaging.chainIdLeafHash(chainTree[chainIndexToId[i]].root(), chainIndexToId[i]); + newLeaves[i] = MessageHashing.chainIdLeafHash(chainTree[chainIndexToId[i]].root(), chainIndexToId[i]); } // slither-disable-next-line unused-return sharedTree.updateAllLeaves(newLeaves); diff --git a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol index a72dac000..a2cceb29f 100644 --- a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol @@ -32,7 +32,7 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable _; } - /// @dev Contract is expected to be used as proxy implementation. + /// @dev Contract is expected to be used as proxy implementation on L1. /// @dev Initialize the implementation to prevent Parity hack. constructor(IBridgehub _bridgehub, IL1AssetRouter _sharedBridge) reentrancyGuardInitializer { _disableInitializers(); @@ -41,10 +41,13 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable } /// @notice used to initialize the contract + /// @param _owner the owner of the contract function initialize(address _owner) external reentrancyGuardInitializer { _transferOwnership(_owner); } + /// @notice Used to register the stm asset in L1 contracts, AssetRouter and Bridgehub. + /// @param _stmAddress the address of the stm asset function registerSTMAssetOnL1(address _stmAddress) external onlyOwner { // solhint-disable-next-line gas-custom-errors @@ -58,11 +61,14 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable /// @dev Since the L2 settlement layers `_chainId` might potentially have ERC20 tokens as native assets, /// there are two ways to perform the L1->L2 transaction: /// - via the `Bridgehub.requestL2TransactionDirect`. However, this would require the STMDeploymentTracker to - /// hahndle the ERC20 balances to be used in the transaction. + /// handle the ERC20 balances to be used in the transaction. /// - via the `Bridgehub.requestL2TransactionTwoBridges`. This way it will be the sender that provides the funds /// for the L2 transaction. /// The second approach is used due to its simplicity even though it gives the sender slightly more control over the call: /// `gasLimit`, etc. + /// @param _chainId the chainId of the chain + /// @param _prevMsgSender the previous message sender + /// @param _data the data of the transaction // slither-disable-next-line locked-ether function bridgehubDeposit( uint256 _chainId, @@ -81,7 +87,9 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable request = _registerSTMAssetOnL2Bridgehub(_chainId, _stmL1Address, _stmL2Address); } - // todo this has to be put in L1AssetRouter via TwoBridges. Hard, because we have to have multiple msg types + // todo this has to be put in L1AssetRouter via TwoBridges for custom base tokens. Hard, because we have to have multiple msg types in bridgehubDeposit in the AssetRouter. + /// @notice Used to register the stm asset in L2 AssetRouter. + /// @param _chainId the chainId of the chain function registerSTMAssetOnL2SharedBridge( uint256 _chainId, address _stmL1Address, @@ -89,7 +97,7 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByteLimit, address _refundRecipient - ) public payable { + ) public payable onlyOwner { bytes32 assetId; { assetId = getAssetId(_stmL1Address); @@ -105,18 +113,20 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable _assetAddressOnCounterPart: L2_BRIDGEHUB_ADDR }); } + // Todo this works for now, but it will not work in the future if we want to change STM DTs. Probably ok. + /// @notice Used to register the stm asset in L2 Bridgehub. + /// @param _chainId the chainId of the chain function _registerSTMAssetOnL2Bridgehub( // solhint-disable-next-line no-unused-vars uint256 _chainId, address _stmL1Address, address _stmL2Address ) internal pure returns (L2TransactionRequestTwoBridgesInner memory request) { - bytes memory l2TxCalldata = abi.encodeWithSelector( + bytes memory l2TxCalldata = abi.encodeCall( /// todo it should not be initial in setAssetHandlerAddressInitial - IBridgehub.setAssetHandlerAddressInitial.selector, - bytes32(uint256(uint160(_stmL1Address))), - _stmL2Address + IBridgehub.setAssetHandlerAddressInitial, + (bytes32(uint256(uint160(_stmL1Address))), _stmL2Address) ); request = L2TransactionRequestTwoBridgesInner({ @@ -129,10 +139,12 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable } /// @dev we need to implement this for the bridgehub for the TwoBridges logic - function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external { - // This function is typically used on bridges for e.g. + function bridgehubConfirmL2Transaction(uint256, bytes32, bytes32) external { + // This function is typically used on bridges for e.g. confirm that a tx took place } + /// @notice Return the assetId of the STM. + /// @param _l1STM the address of the STM on L1 function getAssetId(address _l1STM) public view override returns (bytes32) { return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1STM))))); } diff --git a/l1-contracts/contracts/common/Config.sol b/l1-contracts/contracts/common/Config.sol index 1b343ea09..739c38ed4 100644 --- a/l1-contracts/contracts/common/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -102,21 +102,20 @@ uint256 constant MEMORY_OVERHEAD_GAS = 10; /// @dev The maximum gas limit for a priority transaction in L2. uint256 constant PRIORITY_TX_MAX_GAS_LIMIT = 72_000_000; +/// @dev the address used to identify eth as the base token for chains. address constant ETH_TOKEN_ADDRESS = address(1); +/// @dev the value returned in bridgehubDeposit in the TwoBridges function. bytes32 constant TWO_BRIDGES_MAGIC_VALUE = bytes32(uint256(keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1); /// @dev https://eips.ethereum.org/EIPS/eip-1352 address constant BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS = address(uint160(type(uint16).max)); +/// @dev the maximum number of supported chains, this is an arbitrary limit. uint256 constant MAX_NUMBER_OF_HYPERCHAINS = 100; -/// FIXME: move to a different file - -struct StoredBatchHashInfo { - uint256 number; - bytes32 hash; -} +/// @dev Used to when there is no sender contract on L1. This is the alias we apply to L1->L2 messages. +address constant VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS = address(uint160(0x1111000000000000000000000000000000001111)); struct PriorityTreeCommitment { uint256 nextLeafIndex; diff --git a/l1-contracts/contracts/common/L2ContractAddresses.sol b/l1-contracts/contracts/common/L2ContractAddresses.sol index 1b7baf552..9ddb1635b 100644 --- a/l1-contracts/contracts/common/L2ContractAddresses.sol +++ b/l1-contracts/contracts/common/L2ContractAddresses.sol @@ -41,6 +41,7 @@ address constant L2_GENESIS_UPGRADE_ADDR = address(0x10001); /// @dev The address of the L2 bridge hub system contract, used to start L2<>L2 transactions address constant L2_BRIDGEHUB_ADDR = address(0x10002); +/// @dev the address of the l2 asse3t router. address constant L2_ASSET_ROUTER_ADDR = address(0x10003); /// @dev An l2 system contract address, used in the assetId calculation for native assets. diff --git a/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol b/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol index f86f0858f..45f13cfaa 100644 --- a/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol +++ b/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity 0.8.24; import {Merkle} from "./Merkle.sol"; import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; @@ -47,8 +47,6 @@ library DynamicIncrementalMerkle { * @dev Initialize a {Bytes32PushTree} using {Hashes-Keccak256} to hash internal nodes. * The capacity of the tree (i.e. number of leaves) is set to `2**levels`. * - * Calling this function on MerkleTree that was already setup and used will reset it to a blank state. - * * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing * empty leaves. It should be a value that is not expected to be part of the tree. */ @@ -59,6 +57,27 @@ library DynamicIncrementalMerkle { return bytes32(0); } + /** + * @dev Resets the tree to a blank state. + * Calling this function on MerkleTree that was already setup and used will reset it to a blank state. + * @param zero The value that represents an empty leaf. + * @return initialRoot The initial root of the tree. + */ + function reset(Bytes32PushTree storage self, bytes32 zero) internal returns (bytes32 initialRoot) { + self._nextLeafIndex = 0; + uint256 length = self._zeros.length; + for (uint256 i = length; 0 < i; --i) { + self._zeros.pop(); + } + length = self._sides.length; + for (uint256 i = length; 0 < i; --i) { + self._sides.pop(); + } + self._zeros.push(zero); + self._sides.push(bytes32(0)); + return bytes32(0); + } + /** * @dev Insert a new leaf in the tree, and compute the new root. Returns the position of the inserted leaf in the * tree, and the resulting root. diff --git a/l1-contracts/contracts/common/libraries/FullMerkle.sol b/l1-contracts/contracts/common/libraries/FullMerkle.sol index 39f94fb22..d39ccde48 100644 --- a/l1-contracts/contracts/common/libraries/FullMerkle.sol +++ b/l1-contracts/contracts/common/libraries/FullMerkle.sol @@ -27,6 +27,7 @@ library FullMerkle { * * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing * empty leaves. It should be a value that is not expected to be part of the tree. + * @param zero The zero value to be used in the tree. */ function setup(FullTree storage self, bytes32 zero) internal returns (bytes32 initialRoot) { // Store depth in the dynamic array @@ -36,6 +37,10 @@ library FullMerkle { return zero; } + /** + * @dev Push a new leaf to the tree. + * @param _leaf The leaf to be added to the tree. + */ function pushNewLeaf(FullTree storage self, bytes32 _leaf) internal returns (bytes32 newRoot) { // solhint-disable-next-line gas-increment-by-one uint256 index = self._leafNumber++; @@ -52,10 +57,10 @@ library FullMerkle { uint256 oldMaxNodeNumber = index - 1; uint256 maxNodeNumber = index; for (uint256 i; i < self._height; i = i.uncheckedInc()) { - self._nodes[i].push(self._zeros[i]); if (oldMaxNodeNumber == maxNodeNumber) { break; } + self._nodes[i].push(self._zeros[i]); maxNodeNumber /= 2; oldMaxNodeNumber /= 2; } @@ -63,6 +68,11 @@ library FullMerkle { return updateLeaf(self, index, _leaf); } + /** + * @dev Update a leaf at index in the tree. + * @param _index The index of the leaf to be updated. + * @param _itemHash The new hash of the leaf. + */ function updateLeaf(FullTree storage self, uint256 _index, bytes32 _itemHash) internal returns (bytes32) { // solhint-disable-next-line gas-custom-errors uint256 maxNodeNumber = self._leafNumber - 1; @@ -85,12 +95,21 @@ library FullMerkle { return currentHash; } + /** + * @dev Updated all leaves in the tree. + * @param _newLeaves The new leaves to be added to the tree. + */ function updateAllLeaves(FullTree storage self, bytes32[] memory _newLeaves) internal returns (bytes32) { // solhint-disable-next-line gas-custom-errors require(_newLeaves.length == self._leafNumber, "FMT, wrong length"); return updateAllNodesAtHeight(self, 0, _newLeaves); } + /** + * @dev Update all nodes at a certain height in the tree. + * @param _height The height of the nodes to be updated. + * @param _newNodes The new nodes to be added to the tree. + */ function updateAllNodesAtHeight( FullTree storage self, uint256 _height, @@ -118,6 +137,9 @@ library FullMerkle { return updateAllNodesAtHeight(self, _height + 1, _newRow); } + /** + * @dev Returns the root of the tree. + */ function root(FullTree storage self) internal view returns (bytes32) { return self._nodes[self._height][0]; } diff --git a/l1-contracts/contracts/common/libraries/Messaging.sol b/l1-contracts/contracts/common/libraries/MessageHashing.sol similarity index 60% rename from l1-contracts/contracts/common/libraries/Messaging.sol rename to l1-contracts/contracts/common/libraries/MessageHashing.sol index 2cfdbcf7d..b7009482d 100644 --- a/l1-contracts/contracts/common/libraries/Messaging.sol +++ b/l1-contracts/contracts/common/libraries/MessageHashing.sol @@ -5,11 +5,17 @@ pragma solidity 0.8.24; bytes32 constant BATCH_LEAF_PADDING = keccak256("zkSync:BatchLeaf"); bytes32 constant CHAIN_ID_LEAF_PADDING = keccak256("zkSync:ChainIdLeaf"); -library Messaging { +library MessageHashing { + /// @dev Returns the leaf hash for a chain with batch number and batch root. + /// @param batchRoot The root hash of the batch. + /// @param batchNumber The number of the batch. function batchLeafHash(bytes32 batchRoot, uint256 batchNumber) internal pure returns (bytes32) { return keccak256(abi.encodePacked(BATCH_LEAF_PADDING, batchRoot, batchNumber)); } + /// @dev Returns the leaf hash for a chain with chain root and chain id. + /// @param chainIdRoot The root hash of the chain. + /// @param chainId The id of the chain. function chainIdLeafHash(bytes32 chainIdRoot, uint256 chainId) internal pure returns (bytes32) { return keccak256(abi.encodePacked(CHAIN_ID_LEAF_PADDING, chainIdRoot, chainId)); } diff --git a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol index 6ea98fa15..51b318e65 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol @@ -91,10 +91,10 @@ contract DummySharedBridge { uint256 _amount ) external payable { if (_l1Token == address(1)) { - require(msg.value == _amount, "L1AssetRouter: msg.value not equal to amount"); + require(msg.value == _amount, "L1AR: msg.value not equal to amount"); } else { // The Bridgehub also checks this, but we want to be sure - require(msg.value == 0, "ShB m.v > 0 b d.it"); + require(msg.value == 0, "L1AR: m.v > 0 b d.it"); uint256 amount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _amount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. require(amount == _amount, "5T"); // The token has non-standard transfer logic } @@ -139,8 +139,8 @@ contract DummySharedBridge { /// @dev Sets the L1ERC20Bridge contract address. Should be called only once. function setNativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external { - require(address(nativeTokenVault) == address(0), "ShB: legacy bridge already set"); - require(address(_nativeTokenVault) != address(0), "ShB: legacy bridge 0"); + require(address(nativeTokenVault) == address(0), "L1AR: legacy bridge already set"); + require(address(_nativeTokenVault) != address(0), "L1AR: legacy bridge 0"); nativeTokenVault = _nativeTokenVault; } diff --git a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol index 33a531809..46771f1dc 100644 --- a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol @@ -156,7 +156,7 @@ interface IStateTransitionManager { function getSemverProtocolVersion() external view returns (uint32, uint32, uint32); - function registerSyncLayer(uint256 _newSyncLayerChainId, bool _isWhitelisted) external; + function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external; event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index 4a0d6fd4c..7dac6c935 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -112,12 +112,15 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @notice Returns the address of the hyperchain with the corresponding chainID + /// @param _chainId the chainId of the chain + /// @return chainAddress the address of the hyperchain function getHyperchain(uint256 _chainId) public view override returns (address chainAddress) { // slither-disable-next-line unused-return (, chainAddress) = hyperchainMap.tryGet(_chainId); } /// @notice Returns the address of the hyperchain admin with the corresponding chainID + /// @param _chainId the chainId of the chain function getChainAdmin(uint256 _chainId) external view override returns (address) { return IZkSyncHyperchain(hyperchainMap.get(_chainId)).getAdmin(); } @@ -209,6 +212,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev set validatorTimelock. Cannot do it during initialization, as validatorTimelock is deployed after STM + /// @param _validatorTimelock the new validatorTimelock address function setValidatorTimelock(address _validatorTimelock) external onlyOwnerOrAdmin { address oldValidatorTimelock = validatorTimelock; validatorTimelock = _validatorTimelock; @@ -216,6 +220,10 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev set New Version with upgrade from old version + /// @param _cutData the new diamond cut data + /// @param _oldProtocolVersion the old protocol version + /// @param _oldProtocolVersionDeadline the deadline for the old protocol version + /// @param _newProtocolVersion the new protocol version function setNewVersionUpgrade( Diamond.DiamondCutData calldata _cutData, uint256 _oldProtocolVersion, @@ -234,16 +242,21 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev check that the protocolVersion is active + /// @param _protocolVersion the protocol version to check function protocolVersionIsActive(uint256 _protocolVersion) external view override returns (bool) { return block.timestamp <= protocolVersionDeadline[_protocolVersion]; } /// @dev set the protocol version timestamp + /// @param _protocolVersion the protocol version + /// @param _timestamp the timestamp is the deadline function setProtocolVersionDeadline(uint256 _protocolVersion, uint256 _timestamp) external onlyOwner { protocolVersionDeadline[_protocolVersion] = _timestamp; } /// @dev set upgrade for some protocolVersion + /// @param _cutData the new diamond cut data + /// @param _oldProtocolVersion the old protocol version function setUpgradeDiamondCut( Diamond.DiamondCutData calldata _cutData, uint256 _oldProtocolVersion @@ -254,21 +267,28 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev freezes the specified chain + /// @param _chainId the chainId of the chain function freezeChain(uint256 _chainId) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).freezeDiamond(); } /// @dev freezes the specified chain + /// @param _chainId the chainId of the chain function unfreezeChain(uint256 _chainId) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).unfreezeDiamond(); } /// @dev reverts batches on the specified chain + /// @param _chainId the chainId of the chain + /// @param _newLastBatch the new last batch function revertBatches(uint256 _chainId, uint256 _newLastBatch) external onlyOwnerOrAdmin { IZkSyncHyperchain(hyperchainMap.get(_chainId)).revertBatches(_newLastBatch); } /// @dev execute predefined upgrade + /// @param _chainId the chainId of the chain + /// @param _oldProtocolVersion the old protocol version + /// @param _diamondCut the diamond cut data function upgradeChainFromVersion( uint256 _chainId, uint256 _oldProtocolVersion, @@ -278,37 +298,46 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev executes upgrade on chain + /// @param _chainId the chainId of the chain + /// @param _diamondCut the diamond cut data function executeUpgrade(uint256 _chainId, Diamond.DiamondCutData calldata _diamondCut) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).executeUpgrade(_diamondCut); } /// @dev setPriorityTxMaxGasLimit for the specified chain - function setPriorityTxMaxGasLimit(uint256 _chainId, uint256 _maxGasLimit) external { - // onlyOwner { + /// @param _chainId the chainId of the chain + /// @param _maxGasLimit the new max gas limit + function setPriorityTxMaxGasLimit(uint256 _chainId, uint256 _maxGasLimit) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setPriorityTxMaxGasLimit(_maxGasLimit); } /// @dev setTokenMultiplier for the specified chain - function setTokenMultiplier(uint256 _chainId, uint128 _nominator, uint128 _denominator) external { - // onlyOwner { + /// @param _chainId the chainId of the chain + /// @param _nominator the new nominator of the token multiplier + /// @param _denominator the new denominator of the token multiplier + function setTokenMultiplier(uint256 _chainId, uint128 _nominator, uint128 _denominator) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setTokenMultiplier(_nominator, _denominator); } /// @dev changeFeeParams for the specified chain - function changeFeeParams(uint256 _chainId, FeeParams calldata _newFeeParams) external { - // onlyOwner { + /// @param _chainId the chainId of the chain + /// @param _newFeeParams the new fee params + function changeFeeParams(uint256 _chainId, FeeParams calldata _newFeeParams) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).changeFeeParams(_newFeeParams); } /// @dev setValidator for the specified chain - function setValidator(uint256 _chainId, address _validator, bool _active) external { - // onlyOwnerOrAdmin { + /// @param _chainId the chainId of the chain + /// @param _validator the new validator + /// @param _active whether the validator is active + function setValidator(uint256 _chainId, address _validator, bool _active) external onlyOwnerOrAdmin { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setValidator(_validator, _active); } /// @dev setPorterAvailability for the specified chain + /// @param _chainId the chainId of the chain + /// @param _zkPorterIsAvailable whether the zkPorter mode is available function setPorterAvailability(uint256 _chainId, bool _zkPorterIsAvailable) external onlyOwner { - // onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setPorterAvailability(_zkPorterIsAvailable); } @@ -323,7 +352,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own _registerNewHyperchain(_chainId, _hyperchain); } - /// deploys a full set of chains contracts + /// @dev deploys a full set of chains contracts function _deployNewChain( uint256 _chainId, address _baseToken, @@ -349,20 +378,20 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own // solhint-disable-next-line func-named-parameters mandatoryInitData = bytes.concat( bytes32(_chainId), - bytes32(uint256(uint160(address(BRIDGE_HUB)))), + bytes32(uint256(uint160(BRIDGE_HUB))), bytes32(uint256(uint160(address(this)))), - bytes32(uint256(protocolVersion)), + bytes32(protocolVersion), bytes32(uint256(uint160(_admin))), bytes32(uint256(uint160(validatorTimelock))), bytes32(uint256(uint160(_baseToken))), bytes32(uint256(uint160(_sharedBridge))), - bytes32(storedBatchZero) + storedBatchZero ); } // construct init data bytes memory initData; - /// all together 4+9*32=292 bytes + /// all together 4+9*32=292 bytes for the selector + mandatory data // solhint-disable-next-line func-named-parameters initData = bytes.concat(IDiamondInit.initialize.selector, mandatoryInitData, diamondCut.initCalldata); @@ -382,6 +411,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own /// @param _sharedBridge the shared bridge address, used as base token bridge /// @param _admin the chain's admin address /// @param _initData the diamond cut data, force deployments and factoryDeps encoded + /// @param _factoryDeps the factory dependencies used for the genesis upgrade /// that initializes the chains Diamond Proxy function createNewChain( uint256 _chainId, @@ -405,20 +435,23 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own IAdmin(hyperchainAddress).genesisUpgrade(l1GenesisUpgrade, _forceDeploymentData, _factoryDeps); } + /// @param _chainId the chainId of the chain function getProtocolVersion(uint256 _chainId) public view returns (uint256) { return IZkSyncHyperchain(hyperchainMap.get(_chainId)).getProtocolVersion(); } - function registerSyncLayer(uint256 _newSyncLayerChainId, bool _isWhitelisted) external onlyOwner { - require(_newSyncLayerChainId != 0, "Bad chain id"); + /// @param _newSettlementLayerChainId the chainId of the chain + /// @param _isWhitelisted whether the chain is whitelisted + function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external onlyOwner { + require(_newSettlementLayerChainId != 0, "Bad chain id"); // Currently, we require that the sync layer is deployed by the same STM. - address syncLayerAddress = hyperchainMap.get(_newSyncLayerChainId); + address settlementLayerAddress = hyperchainMap.get(_newSettlementLayerChainId); // TODO: Maybe `get` already ensured its existence. - require(syncLayerAddress != address(0), "STM: sync layer not registered"); + require(settlementLayerAddress != address(0), "STM: sync layer not registered"); - IBridgehub(BRIDGE_HUB).registerSyncLayer(_newSyncLayerChainId, _isWhitelisted); + IBridgehub(BRIDGE_HUB).registerSettlementLayer(_newSettlementLayerChainId, _isWhitelisted); // TODO: emit event } @@ -430,10 +463,10 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own uint256 _chainId, bytes calldata _data ) external view override onlyBridgehub returns (bytes memory stmForwardedBridgeMintData) { - (address _newSyncLayerAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); - require(_newSyncLayerAdmin != address(0), "STM: admin zero"); + (address _newGatewayAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); + require(_newGatewayAdmin != address(0), "STM: admin zero"); // todo check protocol version - return abi.encode(IBridgehub(BRIDGE_HUB).baseToken(_chainId), _newSyncLayerAdmin, protocolVersion, _diamondCut); + return abi.encode(IBridgehub(BRIDGE_HUB).baseToken(_chainId), _newGatewayAdmin, protocolVersion, _diamondCut); } /// @notice Called by the bridgehub during the migration of a chain to the current settlement layer. @@ -458,6 +491,11 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own }); } + /// @notice Called by the bridgehub during the failed migration of a chain. + /// @param _chainId the chainId of the chain + /// @param _assetInfo the assetInfo of the chain + /// @param _prevMsgSender the previous message sender + /// @param _data the data of the migration function bridgeClaimFailedBurn( uint256 _chainId, bytes32 _assetInfo, diff --git a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol index 45540177e..636756d43 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol @@ -162,7 +162,7 @@ struct ZkSyncHyperchainStorage { /// @dev the Asset Id of the baseToken bytes32 baseTokenAssetId; /// @dev address of the synclayer, only set on L1 if settling on it - address syncLayer; + address settlementLayer; /// @dev Priority tree, the new data structure for priority queue PriorityTree.Tree priorityTree; } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 8fc1eeb56..9b90bde24 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -105,6 +105,7 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { emit ValidiumModeStatusUpdate(_pricingMode); } + /// @inheritdoc IAdmin function setTransactionFilterer(address _transactionFilterer) external onlyAdmin { address oldTransactionFilterer = s.transactionFilterer; s.transactionFilterer = _transactionFilterer; @@ -166,16 +167,13 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { bytes calldata _forceDeploymentData, bytes[] calldata _factoryDeps ) external onlyStateTransitionManager { - uint256 cachedProtocolVersion = s.protocolVersion; - uint256 chainId = s.chainId; - Diamond.FacetCut[] memory emptyArray; Diamond.DiamondCutData memory cutData = Diamond.DiamondCutData({ facetCuts: emptyArray, initAddress: _l1GenesisUpgrade, initCalldata: abi.encodeCall( IL1GenesisUpgrade.genesisUpgrade, - (_l1GenesisUpgrade, chainId, cachedProtocolVersion, _forceDeploymentData, _factoryDeps) + (_l1GenesisUpgrade, s.chainId, s.protocolVersion, _forceDeploymentData, _factoryDeps) ) }); @@ -210,14 +208,14 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { CHAIN MIGRATION //////////////////////////////////////////////////////////////*/ - /// @dev we can move assets using these + /// @inheritdoc IAdmin function forwardedBridgeBurn( - address _syncLayer, + address _settlementLayer, address _prevMsgSender, bytes calldata ) external payable override onlyBridgehub returns (bytes memory chainBridgeMintData) { - // (address _newSyncLayerAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); - require(s.syncLayer == address(0), "Af: already migrated"); + // (address _newSettlementLayerAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); + require(s.settlementLayer == address(0), "Af: already migrated"); require(_prevMsgSender == s.admin, "Af: not chainAdmin"); IStateTransitionManager stm = IStateTransitionManager(s.stateTransitionManager); @@ -227,11 +225,12 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { require(currentProtocolVersion == protocolVersion, "STM: protocolVersion not up to date"); - s.syncLayer = _syncLayer; + s.settlementLayer = _settlementLayer; chainBridgeMintData = abi.encode(_prepareChainCommitment()); } - function forwardedBridgeMint(bytes calldata _data) external payable override { + /// @inheritdoc IAdmin + function forwardedBridgeMint(bytes calldata _data) external payable override onlyBridgehub { HyperchainCommitment memory _commitment = abi.decode(_data, (HyperchainCommitment)); uint256 batchesExecuted = _commitment.totalBatchesExecuted; @@ -271,12 +270,13 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { emit MigrationComplete(); } + /// @inheritdoc IAdmin function forwardedBridgeClaimFailedBurn( uint256 _chainId, bytes32 _assetInfo, address _prevMsgSender, bytes calldata _data - ) external payable override {} + ) external payable override onlyBridgehub {} // todo make internal. For now useful for testing function _prepareChainCommitment() public view returns (HyperchainCommitment memory commitment) { @@ -312,25 +312,26 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { commitment.batchHashes = batchHashes; } - function readChainCommitment() external view returns (bytes memory commitment) { + /// @inheritdoc IAdmin + function readChainCommitment() external view override returns (bytes memory commitment) { return abi.encode(_prepareChainCommitment()); } - // function recoverFromFailedMigrationToSyncLayer( - // uint256 _syncLayerChainId, + // function recoverFromFailedMigrationToGateway( + // uint256 _settlementLayerChainId, // uint256 _l2BatchNumber, // uint256 _l2MessageIndex, // uint16 _l2TxNumberInBatch, // bytes32[] calldata _merkleProof // ) external onlyAdmin { - // require(s.syncLayerState == SyncLayerState.MigratedFromL1, "not migrated L1"); + // require(s.settlementLayerState == SettlementLayerState.MigratedFromL1, "not migrated L1"); - // bytes32 migrationHash = s.syncLayerMigrationHash; + // bytes32 migrationHash = s.settlementLayerMigrationHash; // require(migrationHash != bytes32(0), "can not recover when there is no migration"); // require( // IBridgehub(s.bridgehub).proveL1ToL2TransactionStatus( - // _syncLayerChainId, + // _settlementLayerChainId, // migrationHash, // _l2BatchNumber, // _l2MessageIndex, @@ -341,9 +342,9 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { // "Migration not failed" // ); - // s.syncLayerState = SyncLayerState.ActiveOnL1; - // s.syncLayerChainId = 0; - // s.syncLayerMigrationHash = bytes32(0); + // s.settlementLayerState = SettlementLayerState.ActiveOnL1; + // s.settlementLayerChainId = 0; + // s.settlementLayerMigrationHash = bytes32(0); // // We do not need to perform any additional actions, since no changes related to the chain commitment can be performed // // while the chain is in the "migrated" state. diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index c92e0788e..13e78852d 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -39,9 +39,9 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { CommitBatchInfo calldata _newBatch, bytes32 _expectedSystemContractUpgradeTxHash ) internal returns (StoredBatchInfo memory) { - require(_newBatch.batchNumber == _previousBatch.batchNumber + 1, "f"); // only commit next batchs + require(_newBatch.batchNumber == _previousBatch.batchNumber + 1, "f"); // only commit next batch - // Check that batch contain all meta information for L2 logs. + // Check that batch contains all meta information for L2 logs. // Get the chained hash of priority transaction hashes. LogProcessingOutput memory logOutput = _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); @@ -169,13 +169,13 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { } } - // FIXME: temporarily old logs were kept for backwards compaitibility. This check can not work now. + // FIXME: temporarily old logs were kept for backwards compatibility. This check cannot work now. // - // We only require 13 logs to be checked, the 14th is if we are expecting a protocol upgrade - // Without the protocol upgrade we expect 13 logs: 2^13 - 1 = 8191 - // With the protocol upgrade we expect 14 logs: 2^14 - 1 = 16383 + // We only require 8 logs to be checked, the 9th is if we are expecting a protocol upgrade + // Without the protocol upgrade we expect 8 logs: 2^8 - 1 = 255 + // With the protocol upgrade we expect 9 logs: 2^9 - 1 = 511 if (_expectedSystemContractUpgradeTxHash == bytes32(0)) { - // require(processedLogs == 127, "b7"); + // require(processedLogs == 255, "b7"); } else { // FIXME: do restore this code to the one that was before require(_checkBit(processedLogs, uint8(SystemLogKey.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY)), "b8"); @@ -336,11 +336,6 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { bytes32 priorityOperationsHash = _collectOperationsFromPriorityQueue(_storedBatch.numberOfLayer1Txs); _checkBatchData(_storedBatch, _executedBatchIdx, priorityOperationsHash); - uint256 firstUnprocessed = s.priorityQueue.getFirstUnprocessedPriorityTx(); - uint256 treeStartIndex = s.priorityTree.startIndex; - if (firstUnprocessed > treeStartIndex) { - s.priorityTree.unprocessedIndex = firstUnprocessed - treeStartIndex; - } uint256 currentBatchNumber = _storedBatch.batchNumber; // Save root hash of L2 -> L1 logs tree @@ -378,6 +373,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { messageRootContract.addChainBatchRoot(s.chainId, currentBatchNumber, _storedBatch.l2LogsTreeRoot); } + /// @inheritdoc IExecutor function executeBatchesSharedBridge( uint256, StoredBatchInfo[] calldata _batchesData, @@ -386,6 +382,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { _executeBatches(_batchesData, _priorityOpsData); } + /// @inheritdoc IExecutor function executeBatches( StoredBatchInfo[] calldata _batchesData, PriorityOpsBatchInfo[] calldata _priorityOpsData diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol index 23144c0a5..04c34d103 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol @@ -9,7 +9,7 @@ import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; import {PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; import {VerifierParams} from "../../../state-transition/chain-interfaces/IVerifier.sol"; import {Diamond} from "../../libraries/Diamond.sol"; -import {PriorityQueue, PriorityOperation} from "../../../state-transition/libraries/PriorityQueue.sol"; +import {PriorityQueue} from "../../../state-transition/libraries/PriorityQueue.sol"; import {PriorityTree} from "../../../state-transition/libraries/PriorityTree.sol"; import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {IGetters} from "../../chain-interfaces/IGetters.sol"; @@ -131,9 +131,6 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { } } - /// @inheritdoc IGetters - function priorityQueueFrontOperation() external view returns (PriorityOperation memory op) {} - /// @inheritdoc IGetters function isValidator(address _address) external view returns (bool) { return s.validators[_address]; @@ -227,9 +224,9 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { } /// @inheritdoc IGetters - function getSyncLayer() external view returns (address) { + function getSettlementLayer() external view returns (address) { // TODO: consider making private so that no one relies on it - return s.syncLayer; + return s.settlementLayer; } function getDAValidatorPair() external view returns (address, address) { diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index 824e6ec3b..9c38cff93 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -16,19 +16,16 @@ import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.so import {PriorityTree} from "../../libraries/PriorityTree.sol"; import {TransactionValidator} from "../../libraries/TransactionValidator.sol"; import {WritePriorityOpParams, L2CanonicalTransaction, L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "../../../common/Messaging.sol"; -import {Messaging} from "../../../common/libraries/Messaging.sol"; +import {MessageHashing} from "../../../common/libraries/MessageHashing.sol"; import {FeeParams, PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {L2ContractHelper} from "../../../common/libraries/L2ContractHelper.sol"; import {AddressAliasHelper} from "../../../vendor/AddressAliasHelper.sol"; import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; -import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS} from "../../../common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS, VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS} from "../../../common/Config.sol"; import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../../common/L2ContractAddresses.sol"; import {IL1AssetRouter} from "../../../bridge/interfaces/IL1AssetRouter.sol"; -import {IBridgehub} from "../../../bridgehub/IBridgehub.sol"; - -import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; @@ -51,7 +48,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { ERA_CHAIN_ID = _eraChainId; } - /// @notice when requesting transactions through the bridgehub + /// @inheritdoc IMailbox function bridgehubRequestL2Transaction( BridgehubL2TransactionRequest calldata _request ) external onlyBridgehub returns (bytes32 canonicalTxHash) { @@ -109,7 +106,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } // /// @inheritdoc IMailbox - function proveL1ToL2TransactionStatusViaSyncLayer( + function proveL1ToL2TransactionStatusViaGateway( bytes32 _l2TxHash, uint256 _l2BatchNumber, uint256 _l2MessageIndex, @@ -120,7 +117,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { function _parseProofMetadata( bytes32 _proofMetadata - ) internal view returns (uint256 logLeafProofLen, uint256 batchLeafProofLen) { + ) internal pure returns (uint256 logLeafProofLen, uint256 batchLeafProofLen) { bytes1 metadataVersion = bytes1(_proofMetadata[0]); require(metadataVersion == 0x01, "Mailbox: unsupported proof metadata version"); @@ -139,16 +136,16 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } } + /// @inheritdoc IMailbox function proveL2LeafInclusion( uint256 _batchNumber, uint256 _leafProofMask, bytes32 _leaf, bytes32[] calldata _proof - ) external view returns (bool) { + ) external view override returns (bool) { return _proveL2LeafInclusion(_batchNumber, _leafProofMask, _leaf, _proof); } - /// Proves that a certain leaf was included as part of the log merkle tree. function _proveL2LeafInclusion( uint256 _batchNumber, uint256 _leafProofMask, @@ -172,16 +169,16 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { // Note that this logic works only for chains that do not migrate away from the synclayer back to L1. // Support for chains that migrate back to L1 will be added in the future. - if (s.syncLayer == address(0)) { + if (s.settlementLayer == address(0)) { bytes32 correctBatchRoot = s.l2LogsRootHashes[_batchNumber]; require(correctBatchRoot != bytes32(0), "local root is 0"); return correctBatchRoot == batchSettlementRoot; - } else { - require(s.l2LogsRootHashes[_batchNumber] == bytes32(0), "local root must be 0"); } - // Now, we'll have to check that the SyncLayer included the message. - bytes32 batchLeafHash = Messaging.batchLeafHash(batchSettlementRoot, _batchNumber); + require(s.l2LogsRootHashes[_batchNumber] == bytes32(0), "local root must be 0"); + + // Now, we'll have to check that the Gateway included the message. + bytes32 batchLeafHash = MessageHashing.batchLeafHash(batchSettlementRoot, _batchNumber); uint256 batchLeafProofMask = uint256(bytes32(_proof[ptr])); ++ptr; @@ -193,25 +190,25 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { ); ptr += batchLeafProofLen; - chainIdLeaf = Messaging.chainIdLeafHash(chainIdRoot, s.chainId); + chainIdLeaf = MessageHashing.chainIdLeafHash(chainIdRoot, s.chainId); } - uint256 syncLayerBatchNumber; - uint256 syncLayerBatchRootMask; + uint256 settlementLayerBatchNumber; + uint256 settlementLayerBatchRootMask; // Preventing stack too deep error { // Now, we just need to double check whether this chainId leaf was present in the tree. - uint256 syncLayerPackedBatchInfo = uint256(_proof[ptr]); + uint256 settlementLayerPackedBatchInfo = uint256(_proof[ptr]); ++ptr; - syncLayerBatchNumber = uint256(syncLayerPackedBatchInfo >> 128); - syncLayerBatchRootMask = uint256(syncLayerPackedBatchInfo & ((1 << 128) - 1)); + settlementLayerBatchNumber = uint256(settlementLayerPackedBatchInfo >> 128); + settlementLayerBatchRootMask = uint256(settlementLayerPackedBatchInfo & ((1 << 128) - 1)); } return - IMailbox(s.syncLayer).proveL2LeafInclusion( - syncLayerBatchNumber, - syncLayerBatchRootMask, + IMailbox(s.settlementLayer).proveL2LeafInclusion( + settlementLayerBatchNumber, + settlementLayerBatchRootMask, chainIdLeaf, extractSlice(_proof, ptr, _proof.length) ); @@ -293,14 +290,14 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { return Math.max(l2GasPrice, minL2GasPriceBaseToken); } - /// @dev On L1 we have to forward to SyncLayer mailbox - function requestL2TransactionToSyncLayerMailbox( + /// @inheritdoc IMailbox + function requestL2TransactionToGatewayMailbox( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp - ) external returns (bytes32 canonicalTxHash) { + ) external override returns (bytes32 canonicalTxHash) { require(IBridgehub(s.bridgehub).whitelistedSettlementLayers(s.chainId), "Mailbox SL: not SL"); require( IStateTransitionManager(s.stateTransitionManager).getHyperchain(_chainId) == msg.sender, @@ -314,16 +311,16 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { _canonicalTxHash: _canonicalTxHash, _expirationTimestamp: _expirationTimestamp }); - canonicalTxHash = _requestL2TransactionToSyncLayerFree(wrappedRequest); + canonicalTxHash = _requestL2TransactionToGatewayFree(wrappedRequest); } - /// @dev On SL the chain's mailbox - function bridgehubRequestL2TransactionOnSyncLayer( + /// @inheritdoc IMailbox + function bridgehubRequestL2TransactionOnGateway( L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp - ) external { + ) external override onlyBridgehub { _writePriorityOp(_transaction, _factoryDeps, _canonicalTxHash, _expirationTimestamp); } @@ -332,20 +329,17 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, - uint256 _expirationTimestamp + uint64 _expirationTimestamp ) internal view returns (BridgehubL2TransactionRequest memory) { // solhint-disable-next-line func-named-parameters - bytes memory data = abi.encodeWithSelector( - IBridgehub(s.bridgehub).forwardTransactionOnSyncLayer.selector, - _chainId, - _transaction, - _factoryDeps, - _canonicalTxHash, - _expirationTimestamp + bytes memory data = abi.encodeCall( + IBridgehub(s.bridgehub).forwardTransactionOnGateway, + (_chainId, _transaction, _factoryDeps, _canonicalTxHash, _expirationTimestamp) ); return BridgehubL2TransactionRequest({ - sender: s.bridgehub, + /// There is no sender for the wrapping, we use a virtual address. + sender: VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, contractL2: L2_BRIDGEHUB_ADDR, mintValue: 0, l2Value: 0, @@ -416,9 +410,9 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { (transaction, canonicalTxHash) = _validateTx(_params); _writePriorityOp(transaction, _params.request.factoryDeps, canonicalTxHash, _params.expirationTimestamp); - if (s.syncLayer != address(0)) { + if (s.settlementLayer != address(0)) { // slither-disable-next-line unused-return - IMailbox(s.syncLayer).requestL2TransactionToSyncLayerMailbox({ + IMailbox(s.settlementLayer).requestL2TransactionToGatewayMailbox({ _chainId: s.chainId, _transaction: transaction, _factoryDeps: _params.request.factoryDeps, @@ -436,7 +430,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } } - function _requestL2TransactionToSyncLayerFree( + function _requestL2TransactionToGatewayFree( BridgehubL2TransactionRequest memory _request ) internal nonReentrant returns (bytes32 canonicalTxHash) { WritePriorityOpParams memory params = WritePriorityOpParams({ @@ -478,7 +472,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { function _validateTx( WritePriorityOpParams memory _priorityOpParams - ) internal returns (L2CanonicalTransaction memory transaction, bytes32 canonicalTxHash) { + ) internal view returns (L2CanonicalTransaction memory transaction, bytes32 canonicalTxHash) { transaction = _serializeL2Transaction(_priorityOpParams); bytes memory transactionEncoding = abi.encode(transaction); TransactionValidator.validateL1ToL2Transaction( diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol index f0d1f2303..e2ec1e88e 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.24; import {IZkSyncHyperchainBase} from "../chain-interfaces/IZkSyncHyperchainBase.sol"; -import {L2CanonicalTransaction} from "../../common/Messaging.sol"; import {Diamond} from "../libraries/Diamond.sol"; import {FeeParams, PubdataPricingMode} from "../chain-deps/ZkSyncHyperchainStorage.sol"; @@ -74,7 +73,7 @@ interface IAdmin is IZkSyncHyperchainBase { /// @notice Set the L1 DA validator address as well as the L2 DA validator address. /// @dev While in principle it is possible that updating only one of the addresses is needed, /// usually these should work in pair and L1 validator typically expects a specific input from the L2 Validator. - /// That's why we change those together to prevent shooting admins of chains from shooting themselves in the foot. + /// That's why we change those together to prevent admins of chains from shooting themselves in the foot. /// @param _l1DAValidator The address of the L1 DA validator /// @param _l2DAValidator The address of the L2 DA validator function setDAValidatorPair(address _l1DAValidator, address _l2DAValidator) external; @@ -127,23 +126,19 @@ interface IAdmin is IZkSyncHyperchainBase { /// @notice New pair of DA validators set event NewL2DAValidator(address indexed oldL2DAValidator, address indexed newL2DAValidator); event NewL1DAValidator(address indexed oldL1DAValidator, address indexed newL1DAValidator); - /// @dev emitted when an chain registers and a SetChainIdUpgrade happens - event SetChainIdUpgrade( - address indexed _hyperchain, - L2CanonicalTransaction _l2Transaction, - uint256 indexed _protocolVersion - ); event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); event BridgeMint(address indexed _account, uint256 _amount); + /// @dev Similar to IL1AssetHandler interface, used to send chains. function forwardedBridgeBurn( - address _syncLayer, + address _settlementLayer, address _prevMsgSender, bytes calldata _data ) external payable returns (bytes memory _bridgeMintData); + /// @dev Similar to IL1AssetHandler interface, used to claim failed chain transfers. function forwardedBridgeClaimFailedBurn( uint256 _chainId, bytes32 _assetInfo, @@ -151,5 +146,9 @@ interface IAdmin is IZkSyncHyperchainBase { bytes calldata _data ) external payable; + /// @dev Similar to IL1AssetHandler interface, used to receive chains. function forwardedBridgeMint(bytes calldata _data) external payable; + + /// @dev Returns the commitments to the chain. + function readChainCommitment() external view returns (bytes memory); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol index 38b328452..c5d73cdb6 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.24; -import {PriorityOperation} from "../libraries/PriorityQueue.sol"; import {VerifierParams} from "../chain-interfaces/IVerifier.sol"; import {PubdataPricingMode} from "../chain-deps/ZkSyncHyperchainStorage.sol"; import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; @@ -64,10 +63,6 @@ interface IGetters is IZkSyncHyperchainBase { /// @return The number of priority operations currently in the queue function getPriorityQueueSize() external view returns (uint256); - /// @notice This function is deprecated and will return an empty priority operation. - /// @return Empty priority operation - function priorityQueueFrontOperation() external view returns (PriorityOperation memory); - /// @return Whether the address has a validator access function isValidator(address _address) external view returns (bool); @@ -156,5 +151,5 @@ interface IGetters is IZkSyncHyperchainBase { function isFacetFreezable(address _facet) external view returns (bool isFreezable); /// TODO - function getSyncLayer() external view returns (address); + function getSettlementLayer() external view returns (address); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol index db1fd0b2d..03c5c6741 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol @@ -95,18 +95,26 @@ interface IMailbox is IZkSyncHyperchainBase { address _refundRecipient ) external payable returns (bytes32 canonicalTxHash); + /// @notice when requesting transactions through the bridgehub function bridgehubRequestL2Transaction( BridgehubL2TransactionRequest calldata _request ) external returns (bytes32 canonicalTxHash); - function bridgehubRequestL2TransactionOnSyncLayer( + /// @dev On the Gateway the chain's mailbox receives the tx from the bridgehub. + function bridgehubRequestL2TransactionOnGateway( L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp ) external; - function requestL2TransactionToSyncLayerMailbox( + /// @dev On L1 we have to forward to the Gateway's mailbox which sends to the Bridgehub on the Gw + /// @param _chainId the chainId of the chain + /// @param _transaction the transaction to be relayed + /// @param _factoryDeps the factory dependencies + /// @param _canonicalTxHash the canonical transaction hash + /// @param _expirationTimestamp the expiration timestamp + function requestL2TransactionToGatewayMailbox( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, @@ -125,6 +133,7 @@ interface IMailbox is IZkSyncHyperchainBase { uint256 _l2GasPerPubdataByteLimit ) external view returns (uint256); + /// Proves that a certain leaf was included as part of the log merkle tree. function proveL2LeafInclusion( uint256 _batchNumber, uint256 _batchRootMask, diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol index 1b6629f7d..99451ab13 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.24; /// @title The interface of the zkSync contract, responsible for the main zkSync logic. diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol index 91c1cdc60..b7a1681a0 100644 --- a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol @@ -9,6 +9,9 @@ pragma solidity 0.8.24; /// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. uint256 constant BLOB_SIZE_BYTES = 126_976; +// the state diff hash, hash of pubdata + the number of blobs. +uint256 constant BLOB_DATA_OFFSET = 65; + /// @notice Contract that contains the functionality for process the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { @@ -38,7 +41,7 @@ abstract contract CalldataDA { // - Then, there are linear hashes of the published blobs, 32 bytes each. // Check that it accommodates enough pubdata for the state diff hash, hash of pubdata + the number of blobs. - require(_operatorDAInput.length >= 32 + 32 + 1, "too small"); + require(_operatorDAInput.length >= BLOB_DATA_OFFSET, "too small"); stateDiffHash = bytes32(_operatorDAInput[:32]); fullPubdataHash = bytes32(_operatorDAInput[32:64]); @@ -50,16 +53,17 @@ abstract contract CalldataDA { // the `_maxBlobsSupported` blobsLinearHashes = new bytes32[](_maxBlobsSupported); - require(_operatorDAInput.length >= 65 + 32 * blobsProvided, "invalid blobs hashes"); - - uint256 ptr = 65; + require(_operatorDAInput.length >= BLOB_DATA_OFFSET + 32 * blobsProvided, "invalid blobs hashes"); - for (uint256 i = 0; i < blobsProvided; ++i) { - // Take the 32 bytes of the blob linear hash - blobsLinearHashes[i] = bytes32(_operatorDAInput[ptr:ptr + 32]); - ptr += 32; + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let blobsPtr := add(blobsLinearHashes, 0x20) + let inputPtr := add(_operatorDAInput.offset, BLOB_DATA_OFFSET) + calldatacopy(blobsPtr, inputPtr, mul(blobsProvided, 32)) } + uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; + // Now, we need to double check that the provided input was indeed retutned by the L2 DA validator. require(keccak256(_operatorDAInput[:ptr]) == _l2DAValidatorOutputHash, "invalid l2 DA output hash"); @@ -79,18 +83,17 @@ abstract contract CalldataDA { uint256 _maxBlobsSupported, bytes calldata _pubdataInput ) internal pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + require(_blobsProvided == 1, "only one blob with calldata"); + require(_pubdataInput.length >= 32, "pubdata too small"); + // We typically do not know whether we'll use calldata or blobs at the time when // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. blobCommitments = new bytes32[](_maxBlobsSupported); - require(_blobsProvided == 1, "only one blob with calldata"); - - require(_pubdataInput.length >= 32, "pubdata too small"); - _pubdata = _pubdataInput[:_pubdataInput.length - 32]; - // FIXME: allow larger lengths for SyncLayer-based chains. + // FIXME: allow larger lengths for Gateway-based chains. require(_pubdata.length <= BLOB_SIZE_BYTES, "cz"); require(_fullPubdataHash == keccak256(_pubdata), "wp"); blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - 32:_pubdataInput.length]); diff --git a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol index 2f1f9b470..4e91c7bc4 100644 --- a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol +++ b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol @@ -12,7 +12,7 @@ import {CalldataDA} from "./CalldataDA.sol"; import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAddresses.sol"; /// @notice The DA validator intended to be used in Era-environment. -/// @dev For compaitbility reasons it accepts calldata in the same format as the `RollupL1DAValidator`, but unlike the latter it +/// @dev For compatibility reasons it accepts calldata in the same format as the `RollupL1DAValidator`, but unlike the latter it /// does not support blobs. /// @dev Note that it does not provide any compression whatsoever. contract RelayedSLDAValidator is IL1DAValidator, CalldataDA { diff --git a/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol b/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol index 905215db9..f07b879e7 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IComplexUpgrader { function upgrade(address _delegateTo, bytes calldata _calldata) external payable; } diff --git a/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol index 8873affef..2effd48cd 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; /// @notice A struct that describes a forced deployment on an address struct ForceDeployment { @@ -15,6 +15,8 @@ struct ForceDeployment { bytes input; } +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2GenesisUpgrade { function genesisUpgrade(uint256 _chainId, bytes calldata _forceDeploymentsData) external payable; } diff --git a/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol index fded0f4d2..b8319f7c4 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol @@ -1,6 +1,9 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +// 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.21; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface ISystemContext { function setChainId(uint256 _newChainId) external; } diff --git a/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol index e71a662fc..6bf3649e5 100644 --- a/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol +++ b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; +// 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.21; // solhint-disable gas-custom-errors @@ -40,7 +40,7 @@ library PriorityTree { /// @return The total number of unprocessed priority operations in a priority queue function getSize(Tree storage _tree) internal view returns (uint256) { - return uint256(_tree.tree._nextLeafIndex - _tree.unprocessedIndex); + return _tree.tree._nextLeafIndex - _tree.unprocessedIndex; } /// @notice Add the priority operation to the end of the priority queue @@ -49,15 +49,18 @@ library PriorityTree { _tree.historicalRoots[newRoot] = true; } + /// @notice Set up the tree function setup(Tree storage _tree, uint256 _startIndex) internal { _tree.tree.setup(ZERO_LEAF_HASH); _tree.startIndex = _startIndex; } + /// @return Returns the tree root. function getRoot(Tree storage _tree) internal view returns (bytes32) { return _tree.tree.root(); } + /// @notice Process the priority operations of a batch. function processBatch(Tree storage _tree, PriorityOpsBatchInfo calldata _priorityOpsData) internal { if (_priorityOpsData.itemHashes.length > 0) { bytes32 expectedRoot = Merkle.calculateRootPaths( @@ -71,6 +74,7 @@ library PriorityTree { } } + /// @notice Initialize a chain from a commitment. function initFromCommitment(Tree storage _tree, PriorityTreeCommitment memory _commitment) internal { uint256 height = _commitment.sides.length; // Height, including the root node. require(height > 0, "PT: invalid commitment"); @@ -87,6 +91,7 @@ library PriorityTree { _tree.historicalRoots[_tree.tree.root()] = true; } + /// @notice Returns the commitment to the priority tree. function getCommitment(Tree storage _tree) internal view returns (PriorityTreeCommitment memory commitment) { commitment.nextLeafIndex = _tree.tree._nextLeafIndex; commitment.startIndex = _tree.startIndex; diff --git a/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol index 1f7affaa0..345c70cbe 100644 --- a/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol +++ b/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol @@ -2,11 +2,10 @@ pragma solidity 0.8.24; -import {ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; interface IL1GenesisUpgrade { - /// @dev emitted when an chain registers and a GenesisUpgrade happens + /// @dev emitted when a chain registers and a GenesisUpgrade happens event GenesisUpgrade( address indexed _hyperchain, L2CanonicalTransaction _l2Transaction, @@ -21,6 +20,4 @@ interface IL1GenesisUpgrade { bytes calldata _forceDeployments, bytes[] calldata _factoryDeps ) external returns (bytes32); - - function upgradeInner(ProposedUpgrade calldata _proposedUpgrade) external returns (bytes32); } diff --git a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol index d4220bcff..5c20aa56b 100644 --- a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol +++ b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol @@ -87,7 +87,7 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { Diamond.DiamondCutData memory cutData = Diamond.DiamondCutData({ facetCuts: emptyArray, initAddress: _l1GenesisUpgrade, - initCalldata: abi.encodeCall(this.upgradeInner, (proposedUpgrade)) + initCalldata: abi.encodeCall(this.upgrade, (proposedUpgrade)) }); Diamond.diamondCut(cutData); @@ -95,7 +95,8 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; } - function upgradeInner(ProposedUpgrade calldata _proposedUpgrade) external override returns (bytes32) { + /// @notice the upgrade function. + function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { super.upgrade(_proposedUpgrade); return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; } diff --git a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol index f12cf14f7..4fdfb5582 100644 --- a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol +++ b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// 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.21; import {Script} from "forge-std/Script.sol"; diff --git a/l1-contracts/deploy-scripts/Gateway.s.sol b/l1-contracts/deploy-scripts/Gateway.s.sol index b32ee0926..84bc3909c 100644 --- a/l1-contracts/deploy-scripts/Gateway.s.sol +++ b/l1-contracts/deploy-scripts/Gateway.s.sol @@ -119,8 +119,8 @@ contract GatewayScript is Script { IStateTransitionManager stm = IStateTransitionManager(config.stateTransitionProxy); Ownable ownable = Ownable(config.stateTransitionProxy); vm.prank(ownable.owner()); - stm.registerSyncLayer(config.gatewayChainId, true); - // bytes memory data = abi.encodeCall(stm.registerSyncLayer, (config.chainChainId, true)); + stm.registerSettlementLayer(config.gatewayChainId, true); + // bytes memory data = abi.encodeCall(stm.registerSettlementLayer, (config.chainChainId, true)); // Utils.executeUpgrade({ // _governor: ownable.owner(), // _salt: bytes32(config.bridgehubCreateNewChainSalt), diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts index 7c440f89c..16811295e 100644 --- a/l1-contracts/scripts/sync-layer.ts +++ b/l1-contracts/scripts/sync-layer.ts @@ -138,7 +138,7 @@ async function main() { verbose: true, }); - const syncLayerChainId = getNumberFromEnv("SYNC_LAYER_CHAIN_ID"); + const gatewayChainId = getNumberFromEnv("GATEWAY_CHAIN_ID"); const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); @@ -147,37 +147,35 @@ async function main() { const stm = deployer.stateTransitionManagerContract(deployer.deployWallet); - const counterPart = getAddressFromEnv("SYNC_LAYER_STATE_TRANSITION_PROXY_ADDR"); + const counterPart = getAddressFromEnv("GATEWAY_STATE_TRANSITION_PROXY_ADDR"); // FIXME: do it more gracefully - deployer.addresses.StateTransition.AdminFacet = getAddressFromEnv("SYNC_LAYER_ADMIN_FACET_ADDR"); - deployer.addresses.StateTransition.MailboxFacet = getAddressFromEnv("SYNC_LAYER_MAILBOX_FACET_ADDR"); - deployer.addresses.StateTransition.ExecutorFacet = getAddressFromEnv("SYNC_LAYER_EXECUTOR_FACET_ADDR"); - deployer.addresses.StateTransition.GettersFacet = getAddressFromEnv("SYNC_LAYER_GETTERS_FACET_ADDR"); - deployer.addresses.StateTransition.Verifier = getAddressFromEnv("SYNC_LAYER_VERIFIER_ADDR"); - deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv( - "SYNC_LAYER_BLOB_VERSIONED_HASH_RETRIEVER_ADDR" - ); - deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("SYNC_LAYER_DIAMOND_INIT_ADDR"); + 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.Verifier = getAddressFromEnv("GATEWAY_VERIFIER_ADDR"); + deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv("GATEWAY_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"); + deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("GATEWAY_DIAMOND_INIT_ADDR"); - const receipt = await deployer.moveChainToSyncLayer(syncLayerChainId, gasPrice); + const receipt = await deployer.moveChainToGateway(gatewayChainId, gasPrice); - const syncLayerAddress = await stm.getHyperchain(syncLayerChainId); + const gatewayAddress = await stm.getHyperchain(gatewayChainId); - const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt, syncLayerAddress); + const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt, gatewayAddress); console.log("Hash of the transaction on SL chain: ", l2TxHash); - const syncLayerProvider = new ZkProvider(process.env.SYNC_LAYER_API_WEB3_JSON_RPC_HTTP_URL); + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); - const txL2Handle = syncLayerProvider.getL2TransactionFromPriorityOp( + const txL2Handle = gatewayProvider.getL2TransactionFromPriorityOp( await deployWallet.provider.getTransaction(receipt.transactionHash) ); const receiptOnSL = await (await txL2Handle).wait(); console.log("Finalized on SL with hash:", receiptOnSL.transactionHash); - const stmOnSL = IStateTransitionManagerFactory.connect(counterPart, syncLayerProvider); + const stmOnSL = IStateTransitionManagerFactory.connect(counterPart, gatewayProvider); const hyperchainAddress = await stmOnSL.getHyperchain(currentChainId); console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${hyperchainAddress}`); @@ -195,10 +193,10 @@ async function main() { .option("--diamond-upgrade-init ") .option("--only-verifier") .action(async (cmd) => { - const syncLayerChainId = getNumberFromEnv("SYNC_LAYER_CHAIN_ID"); - const syncLayerProvider = new ZkProvider(process.env.SYNC_LAYER_API_WEB3_JSON_RPC_HTTP_URL); + const gatewayChainId = getNumberFromEnv("GATEWAY_CHAIN_ID"); + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); console.log("Obtaining proof..."); - const proof = await getTxFailureProof(syncLayerProvider, cmd.failedTxL2Hash); + const proof = await getTxFailureProof(gatewayProvider, cmd.failedTxL2Hash); const deployWallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) @@ -222,8 +220,8 @@ async function main() { console.log("Executing recovery..."); await ( - await hyperchain.recoverFromFailedMigrationToSyncLayer( - syncLayerChainId, + await hyperchain.recoverFromFailedMigrationToGateway( + gatewayChainId, proof.l2BatchNumber, proof.l2MessageIndex, proof.l2TxNumberInBatch, @@ -244,16 +242,16 @@ async function main() { .option("--diamond-upgrade-init ") .option("--only-verifier") .action(async (cmd) => { - const syncLayerProvider = new ZkProvider(process.env.SYNC_LAYER_API_WEB3_JSON_RPC_HTTP_URL); + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); const currentChainId = getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); // Right now the new admin is the wallet itself. const adminWallet = cmd.privateKey - ? new ZkWallet(cmd.privateKey, syncLayerProvider) + ? new ZkWallet(cmd.privateKey, gatewayProvider) : ZkWallet.fromMnemonic( process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, "m/44'/60'/0'/0/1" - ).connect(syncLayerProvider); + ).connect(gatewayProvider); const operators = [ process.env.ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR, @@ -270,7 +268,7 @@ async function main() { console.log("Enabling validators"); // FIXME: do it in cleaner way - deployer.addresses.ValidatorTimeLock = getAddressFromEnv("SYNC_LAYER_VALIDATOR_TIMELOCK_ADDR"); + deployer.addresses.ValidatorTimeLock = getAddressFromEnv("GATEWAY_VALIDATOR_TIMELOCK_ADDR"); const timelock = deployer.validatorTimelock(deployer.deployWallet); for (const operator of operators) { @@ -284,9 +282,9 @@ async function main() { // FIXME: this method includes bridgehub manipulation, but in the future it won't. deployer.addresses.StateTransition.StateTransitionProxy = getAddressFromEnv( - "SYNC_LAYER_STATE_TRANSITION_PROXY_ADDR" + "GATEWAY_STATE_TRANSITION_PROXY_ADDR" ); - deployer.addresses.Bridgehub.BridgehubProxy = getAddressFromEnv("SYNC_LAYER_BRIDGEHUB_PROXY_ADDR"); + deployer.addresses.Bridgehub.BridgehubProxy = getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR"); // FIXME? Do we want to console.log("Setting default token multiplier"); @@ -298,7 +296,7 @@ async function main() { console.log("Setting SL DA validators"); // This logic should be distinctive between Validium and Rollup - const l1DaValidator = getAddressFromEnv("SYNC_LAYER_L1_RELAYED_SL_DA_VALIDATOR"); + const l1DaValidator = getAddressFromEnv("GATEWAY_L1_RELAYED_SL_DA_VALIDATOR"); const l2DaValidator = getAddressFromEnv("CONTRACTS_L2_DA_VALIDATOR_ADDR"); await (await hyperchain.setDAValidatorPair(l1DaValidator, l2DaValidator)).wait(); @@ -311,32 +309,32 @@ async function main() { async function registerSLContractsOnL1(deployer: Deployer) { /// STM asset info /// l2Bridgehub in L1Bridghub - const bridgehubOnSyncLayer = getAddressFromEnv("SYNC_LAYER_BRIDGEHUB_PROXY_ADDR"); + const bridgehubOnGateway = getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR"); const chainId = getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); - console.log(`Bridghub on SyncLayer: ${bridgehubOnSyncLayer}`); - console.log(`SyncLayer chain Id: ${chainId}`); + console.log(`Bridghub on Gateway: ${bridgehubOnGateway}`); + console.log(`Gateway chain Id: ${chainId}`); const l1STM = deployer.stateTransitionManagerContract(deployer.deployWallet); const l1Bridgehub = deployer.bridgehubContract(deployer.deployWallet); console.log(deployer.addresses.StateTransition.StateTransitionProxy); - const syncLayerAddress = await l1STM.getHyperchain(chainId); + const gatewayAddress = await l1STM.getHyperchain(chainId); // this script only works when owner is the deployer - console.log("Registering SyncLayer chain id on the STM"); + console.log("Registering Gateway chain id on the STM"); const receipt1 = await deployer.executeUpgrade( l1STM.address, 0, - l1Bridgehub.interface.encodeFunctionData("registerSyncLayer", [chainId, true]) + l1Bridgehub.interface.encodeFunctionData("registerSettlementLayer", [chainId, true]) ); - console.log("Registering Bridgehub counter part on the SyncLayer", receipt1.transactionHash); + console.log("Registering Bridgehub counter part on the Gateway", receipt1.transactionHash); // await deployer.executeUpgrade( // l1Bridgehub.address, // kl todo fix. The BH has the counterpart, the BH needs to be deployed on L2, and the STM needs to be registered in the L2 BH. // 0, - // l1Bridgehub.interface.encodeFunctionData("registerCounterpart", [chainId, bridgehubOnSyncLayer]) + // l1Bridgehub.interface.encodeFunctionData("registerCounterpart", [chainId, bridgehubOnGateway]) // ); - // console.log("SyncLayer registration completed in L1 Bridgehub"); + // console.log("Gateway registration completed in L1 Bridgehub"); const gasPrice = (await deployer.deployWallet.provider.getGasPrice()).mul(GAS_MULTIPLIER); const value = ( @@ -355,18 +353,19 @@ async function registerSLContractsOnL1(deployer: Deployer) { } const stmDeploymentTracker = deployer.stmDeploymentTracker(deployer.deployWallet); - const receipt2 = await ( - await stmDeploymentTracker.registerSTMAssetOnL2SharedBridge( + const receipt2 = await deployer.executeUpgrade( + stmDeploymentTracker.address, + value, + stmDeploymentTracker.encodeFunctionData("registerSTMAssetOnL2SharedBridge", [ chainId, l1STM.address, value, priorityTxMaxGasLimit, SYSTEM_CONFIG.requiredL2GasPricePerPubdata, deployer.deployWallet.address, - { value } - ) - ).wait(); - const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt2, syncLayerAddress); + ]) + ); + const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt2, gatewayAddress); console.log("STM asset registered in L2SharedBridge on SL l2 tx hash: ", l2TxHash); const receipt3 = await deployer.executeUpgrade( l1Bridgehub.address, @@ -383,12 +382,12 @@ async function registerSLContractsOnL1(deployer: Deployer) { secondBridgeValue: 0, secondBridgeCalldata: ethers.utils.defaultAbiCoder.encode( ["address", "address"], - [l1STM.address, getAddressFromEnv("SYNC_LAYER_STATE_TRANSITION_PROXY_ADDR")] + [l1STM.address, getAddressFromEnv("GATEWAY_STATE_TRANSITION_PROXY_ADDR")] ), }, ]) ); - const l2TxHash2 = zkUtils.getL2HashFromPriorityOp(receipt3, syncLayerAddress); + const l2TxHash2 = zkUtils.getL2HashFromPriorityOp(receipt3, gatewayAddress); console.log("STM asset registered in L2 Bridgehub on SL", l2TxHash2); const upgradeData = l1Bridgehub.interface.encodeFunctionData("addStateTransitionManager", [ @@ -396,7 +395,7 @@ async function registerSLContractsOnL1(deployer: Deployer) { ]); const receipt4 = await deployer.executeUpgradeOnL2( chainId, - getAddressFromEnv("SYNC_LAYER_BRIDGEHUB_PROXY_ADDR"), + getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR"), gasPrice, upgradeData, priorityTxMaxGasLimit diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index ce8e45fe3..51cb75a07 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -215,8 +215,8 @@ export class Deployer { callConstructor: true, value: 0, input: ethers.utils.defaultAbiCoder.encode( - ["bytes32", "address", "bool"], - [l2TokenProxyBytecodeHash, this.addresses.Governance, false] + ["bytes32", "address"], + [l2TokenProxyBytecodeHash, this.addresses.Governance] ), }; @@ -1059,21 +1059,21 @@ export class Deployer { } } - public async registerSyncLayer() { + public async registerSettlementLayer() { const stm = this.stateTransitionManagerContract(this.deployWallet); - const calldata = stm.interface.encodeFunctionData("registerSyncLayer", [this.chainId, true]); + const calldata = stm.interface.encodeFunctionData("registerSettlementLayer", [this.chainId, true]); await this.executeUpgrade(this.addresses.StateTransition.StateTransitionProxy, 0, calldata); if (this.verbose) { - console.log("SyncLayer registered"); + console.log("Gateway registered"); } } - public async moveChainToSyncLayer(syncLayerChainId: string, gasPrice: BigNumberish) { + public async moveChainToGateway(gatewayChainId: string, gasPrice: BigNumberish) { const bridgehub = this.bridgehubContract(this.deployWallet); // Just some large gas limit that should always be enough const l2GasLimit = ethers.BigNumber.from(72_000_000); const expectedCost = ( - await bridgehub.l2TransactionBaseCost(syncLayerChainId, gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) + await bridgehub.l2TransactionBaseCost(gatewayChainId, gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) ).mul(5); const newAdmin = this.deployWallet.address; @@ -1101,7 +1101,7 @@ export class Deployer { target: bridgehub.address, data: bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ { - chainId: syncLayerChainId, + chainId: gatewayChainId, mintValue: expectedCost, l2Value: 0, l2GasLimit: l2GasLimit, diff --git a/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol b/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol index e2e6f70f5..6a59364bc 100644 --- a/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol +++ b/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol @@ -489,7 +489,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = currentToken.balanceOf(sharedBridgeProxyAddress); if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } @@ -551,7 +551,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = sharedBridgeProxyAddress.balance; if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } diff --git a/l1-contracts/test/foundry/integration/BridgehubTests.t.sol b/l1-contracts/test/foundry/integration/BridgehubTests.t.sol index b349fbcfc..5074d6091 100644 --- a/l1-contracts/test/foundry/integration/BridgehubTests.t.sol +++ b/l1-contracts/test/foundry/integration/BridgehubTests.t.sol @@ -489,7 +489,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = currentToken.balanceOf(sharedBridgeProxyAddress); if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } @@ -551,7 +551,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = sharedBridgeProxyAddress.balance; if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } diff --git a/l1-contracts/test/foundry/integration/GatewayTests.t.sol b/l1-contracts/test/foundry/integration/GatewayTests.t.sol index 89d39ffef..55dba36b4 100644 --- a/l1-contracts/test/foundry/integration/GatewayTests.t.sol +++ b/l1-contracts/test/foundry/integration/GatewayTests.t.sol @@ -28,6 +28,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IZkSyncHyperchain} from "contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol"; import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; contract GatewayTests is L1ContractDeployer, HyperchainDeployer, TokenDeployer, L2TxMocker, GatewayDeployer { uint256 constant TEST_USERS_COUNT = 10; @@ -158,7 +159,9 @@ contract GatewayTests is L1ContractDeployer, HyperchainDeployer, TokenDeployer, reservedDynamic: "0x" }); vm.chainId(12345); - bridgehub.forwardTransactionOnSyncLayer(mintChainId, tx, new bytes[](0), bytes32(0), 0); + vm.startBroadcast(AddressAliasHelper.applyL1ToL2Alias(address(0))); + bridgehub.forwardTransactionOnGateway(mintChainId, tx, new bytes[](0), bytes32(0), 0); + vm.stopBroadcast(); } function finishMoveChain() public { diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol index a3f01cf83..2a037ef3c 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -104,7 +104,7 @@ contract ExperimentalBridgeTest is Test { function test_randomCallerCannotSetDeployer(address randomCaller, address randomDeployer) public { if (randomCaller != bridgeHub.owner() && randomCaller != bridgeHub.admin()) { vm.prank(randomCaller); - vm.expectRevert(bytes("Bridgehub: not owner or admin")); + vm.expectRevert(bytes("BH: not owner or admin")); bridgeHub.setPendingAdmin(randomDeployer); // The deployer shouldn't have changed. @@ -124,7 +124,7 @@ contract ExperimentalBridgeTest is Test { // An address that has already been registered, cannot be registered again (at least not before calling `removeStateTransitionManager`). vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition already registered")); + vm.expectRevert(bytes("BH: state transition already registered")); bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); @@ -153,7 +153,7 @@ contract ExperimentalBridgeTest is Test { // An address that has already been registered, cannot be registered again (at least not before calling `removeStateTransitionManager`). vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition already registered")); + vm.expectRevert(bytes("BH: state transition already registered")); bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Definitely not by a random caller @@ -173,7 +173,7 @@ contract ExperimentalBridgeTest is Test { // A non-existent STM cannot be removed vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Let's first register our particular stateTransitionManager @@ -192,7 +192,7 @@ contract ExperimentalBridgeTest is Test { // An already removed STM cannot be removed again vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); } @@ -212,7 +212,7 @@ contract ExperimentalBridgeTest is Test { // A non-existent STM cannot be removed vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Let's first register our particular stateTransitionManager @@ -231,7 +231,7 @@ contract ExperimentalBridgeTest is Test { // An already removed STM cannot be removed again vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Not possible by a randomcaller as well @@ -263,7 +263,7 @@ contract ExperimentalBridgeTest is Test { // An already registered token cannot be registered again vm.prank(bridgeOwner); - vm.expectRevert("Bridgehub: token already registered"); + vm.expectRevert("BH: token already registered"); bridgeHub.addToken(randomAddress); } @@ -295,7 +295,7 @@ contract ExperimentalBridgeTest is Test { // An already registered token cannot be registered again by randomCaller if (randomCaller != bridgeOwner) { vm.prank(bridgeOwner); - vm.expectRevert("Bridgehub: token already registered"); + vm.expectRevert("BH: token already registered"); bridgeHub.addToken(randomAddress); } } @@ -363,7 +363,7 @@ contract ExperimentalBridgeTest is Test { // if (randomCaller != deployerAddress && randomCaller != bridgeOwner) { // vm.prank(randomCaller); - // vm.expectRevert(bytes("Bridgehub: not owner or admin")); + // vm.expectRevert(bytes("BH: not owner or admin")); // bridgeHub.createNewChain({ // _chainId: chainId, // _stateTransitionManager: address(mockSTM), @@ -734,7 +734,7 @@ contract ExperimentalBridgeTest is Test { // vm.deal(randomCaller, 1 ether); // vm.prank(randomCaller); - // vm.expectRevert("Bridgehub: non-eth bridge with msg.value"); + // vm.expectRevert("BH: non-eth bridge with msg.value"); // bytes32 resultantHash = bridgeHub.requestL2TransactionDirect{value: randomCaller.balance}(l2TxnReqDirect); // // Now, let's call the same function with zero msg.value diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol index d1d02a15c..d65594035 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol @@ -26,7 +26,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { using stdStorage for StdStorage; function test_initialize_WrongOwner() public { - vm.expectRevert("ShB owner 0"); + vm.expectRevert("L1AR: owner 0"); new TransparentUpgradeableProxy( address(sharedBridgeImpl), admin, @@ -53,12 +53,12 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { } function test_transferTokenToNTV_wrongCaller() public { - vm.expectRevert("ShB: not NTV"); + vm.expectRevert("L1AR: not NTV"); sharedBridge.transferTokenToNTV(address(token)); } function test_clearChainBalance_wrongCaller() public { - vm.expectRevert("ShB: not NTV"); + vm.expectRevert("L1AR: not NTV"); sharedBridge.clearChainBalance(chainId, address(token)); } @@ -69,27 +69,27 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_setL1Erc20Bridge_alreadySet() public { vm.prank(owner); - vm.expectRevert("ShB: legacy bridge already set"); + vm.expectRevert("L1AR: legacy bridge already set"); sharedBridge.setL1Erc20Bridge(address(0)); } function test_setL1Erc20Bridge_emptyAddressProvided() public { stdstore.target(address(sharedBridge)).sig(sharedBridge.legacyBridge.selector).checked_write(address(0)); vm.prank(owner); - vm.expectRevert("ShB: legacy bridge 0"); + vm.expectRevert("L1AR: legacy bridge 0"); sharedBridge.setL1Erc20Bridge(address(0)); } function test_setNativeTokenVault_alreadySet() public { vm.prank(owner); - vm.expectRevert("ShB: native token vault already set"); + vm.expectRevert("L1AR: native token vault already set"); sharedBridge.setNativeTokenVault(IL1NativeTokenVault(address(0))); } function test_setNativeTokenVault_emptyAddressProvided() public { stdstore.target(address(sharedBridge)).sig(sharedBridge.nativeTokenVault.selector).checked_write(address(0)); vm.prank(owner); - vm.expectRevert("ShB: native token vault 0"); + vm.expectRevert("L1AR: native token vault 0"); sharedBridge.setNativeTokenVault(IL1NativeTokenVault(address(0))); } @@ -99,7 +99,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address refundRecipient = address(0); vm.prank(alice); - vm.expectRevert("ShB: only ADT or owner"); + vm.expectRevert("L1AR: only ADT or owner"); sharedBridge.setAssetHandlerAddressOnCounterPart( eraChainId, mintValue, @@ -114,14 +114,14 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { // function test_transferFundsToSharedBridge_Eth_CallFailed() public { // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); // vm.prank(address(nativeTokenVault)); - // vm.expectRevert("ShB: eth transfer failed"); + // vm.expectRevert("L1AR: eth transfer failed"); // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); // } // function test_transferFundsToSharedBridge_Eth_CallFailed() public { // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); // vm.prank(address(nativeTokenVault)); - // vm.expectRevert("ShB: eth transfer failed"); + // vm.expectRevert("L1AR: eth transfer failed"); // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); // } @@ -155,12 +155,12 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { .with_key(ETH_TOKEN_ASSET_ID) .checked_write(address(0)); vm.prank(bridgehubAddress); - vm.expectRevert("ShB: only address can be registered"); + vm.expectRevert("L1AR: only address can be registered"); sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); } function test_bridgehubDepositBaseToken_Eth_Token_incorrectSender() public { - vm.expectRevert("L1AssetRouter: msg.sender not equal to bridgehub or era chain"); + vm.expectRevert("L1AR: msg.sender not equal to bridgehub or era chain"); sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); } @@ -195,7 +195,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_bridgehubDeposit_Eth_baseToken() public { vm.prank(bridgehubAddress); - vm.expectRevert("ShB: baseToken deposit not supported"); + vm.expectRevert("L1AR: baseToken deposit not supported"); // solhint-disable-next-line func-named-parameters sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, 0, bob)); } @@ -239,7 +239,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); vm.prank(bridgehubAddress); - vm.expectRevert("ShB tx hap"); + vm.expectRevert("L1AR: tx hap"); sharedBridge.bridgehubConfirmL2Transaction(chainId, txDataHash, txHash); } @@ -338,7 +338,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("ShB: last deposit time not set for Era"); + vm.expectRevert("L1AR: last deposit time not set for Era"); sharedBridge.bridgeRecoverFailedTransfer({ _chainId: eraChainId, _depositSender: alice, @@ -377,7 +377,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("ShB: legacy cFD"); + vm.expectRevert("L1AR: legacy cFD"); sharedBridge.bridgeRecoverFailedTransfer({ _chainId: eraChainId, _depositSender: alice, @@ -466,7 +466,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("ShB: d.it not hap"); + vm.expectRevert("L1AR: d.it not hap"); sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, @@ -534,7 +534,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { amount ); - vm.expectRevert("ShB: legacy eth withdrawal"); + vm.expectRevert("L1AR: legacy eth withdrawal"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, _l2BatchNumber: legacyBatchNumber, @@ -578,7 +578,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { amount ); - vm.expectRevert("Withdrawal is already finalized"); + vm.expectRevert("L1AR: Withdrawal is already finalized"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, _l2BatchNumber: legacyBatchNumber, @@ -599,7 +599,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: legacy eth withdrawal"); + vm.expectRevert("L1AR: legacy eth withdrawal"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -621,7 +621,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: diamondUFB not set for Era"); + vm.expectRevert("L1AR: diamondUFB not set for Era"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -643,7 +643,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: legacy token withdrawal"); + vm.expectRevert("L1AR: legacy token withdrawal"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -665,7 +665,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: LegacyUFB not set for Era"); + vm.expectRevert("L1AR: LegacyUFB not set for Era"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -734,7 +734,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(false) ); - vm.expectRevert("ShB withd w proof"); + vm.expectRevert("L1AR: withd w proof"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, @@ -749,7 +749,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_parseL2WithdrawalMessage_wrongMsgLength() public { bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector); - vm.expectRevert("ShB wrong msg len"); + vm.expectRevert("L1AR: wrong msg len"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -763,7 +763,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_parseL2WithdrawalMessage_wrongMsgLength2() public { bytes memory message = abi.encodePacked(IL1ERC20Bridge.finalizeWithdrawal.selector, abi.encode(amount, token)); - vm.expectRevert("ShB wrong msg len 2"); + vm.expectRevert("L1AR: wrong msg len 2"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -778,7 +778,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { // notice that the selector is wrong bytes memory message = abi.encodePacked(IMailbox.proveL2LogInclusion.selector, alice, amount); - vm.expectRevert("ShB Incorrect message function selector"); + vm.expectRevert("L1AR: Incorrect message function selector"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, _l2BatchNumber: l2BatchNumber, @@ -794,7 +794,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { uint256 l2TxGasPerPubdataByte = 100; address refundRecipient = address(0); - vm.expectRevert("ShB: WETH deposit not supported 2"); + vm.expectRevert("L1AR: WETH deposit not supported 2"); vm.prank(l1ERC20BridgeAddress); sharedBridge.depositLegacyErc20Bridge({ _prevMsgSender: alice, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol index f2a08bdd6..5ee0a9550 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol @@ -141,7 +141,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { // Bridgehub bridgehub = new Bridgehub(); // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); - // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + // require(address(bridgehub.deployer()) == address(31337), "BH: deployer wrong"); vm.mockCall( bridgehubAddress, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol index 4a3753ab4..8de93bd7a 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol @@ -155,7 +155,7 @@ contract L1AssetRouterLegacyTest is L1AssetRouterTest { // Bridgehub bridgehub = new Bridgehub(); // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); - // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + // require(address(bridgehub.deployer()) == address(31337), "BH: deployer wrong"); vm.store( address(sharedBridge), keccak256(abi.encode(tokenAssetId, isWithdrawalFinalizedStorageLocation + 2)), diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol index 39a896b03..3325b9343 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol @@ -94,7 +94,7 @@ contract ExecutorTest is Test { selectors[6] = getters.getTotalPriorityTxs.selector; selectors[7] = getters.getFirstUnprocessedPriorityTx.selector; selectors[8] = getters.getPriorityQueueSize.selector; - selectors[9] = getters.priorityQueueFrontOperation.selector; + selectors[9] = getters.getTotalBatchesExecuted.selector; selectors[10] = getters.isValidator.selector; selectors[11] = getters.l2LogsRootHash.selector; selectors[12] = getters.storedBatchHash.selector; @@ -112,7 +112,6 @@ contract ExecutorTest is Test { selectors[24] = getters.isFacetFreezable.selector; selectors[25] = getters.getTotalBatchesCommitted.selector; selectors[26] = getters.getTotalBatchesVerified.selector; - selectors[27] = getters.getTotalBatchesExecuted.selector; return selectors; } diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol index 6f54bcaac..f96ed4ed6 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol +++ b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol @@ -218,7 +218,7 @@ library Utils { selectors[6] = GettersFacet.getTotalPriorityTxs.selector; selectors[7] = GettersFacet.getFirstUnprocessedPriorityTx.selector; selectors[8] = GettersFacet.getPriorityQueueSize.selector; - selectors[9] = GettersFacet.priorityQueueFrontOperation.selector; + selectors[9] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; selectors[10] = GettersFacet.isValidator.selector; selectors[11] = GettersFacet.l2LogsRootHash.selector; selectors[12] = GettersFacet.storedBatchHash.selector; @@ -237,8 +237,7 @@ library Utils { selectors[25] = GettersFacet.getTotalBatchesCommitted.selector; selectors[26] = GettersFacet.getTotalBatchesVerified.selector; selectors[27] = GettersFacet.getTotalBatchesExecuted.selector; - selectors[28] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; - selectors[29] = GettersFacet.getProtocolVersion.selector; + selectors[28] = GettersFacet.getProtocolVersion.selector; return selectors; } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol deleted file mode 100644 index 91e23f2f3..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {GettersFacetTest} from "./_Getters_Shared.t.sol"; -import {PriorityOperation} from "contracts/state-transition/libraries/PriorityQueue.sol"; - -contract GetPriorityQueueFrontOperationTest is GettersFacetTest { - function test_empty() public { - PriorityOperation memory received = gettersFacet.priorityQueueFrontOperation(); - - assertEq(received.canonicalTxHash, bytes32(0), "Priority queue front operation is incorrect"); - assertEq(received.layer2Tip, 0, "Priority queue front operation is incorrect"); - assertEq(received.expirationTimestamp, 0, "Priority queue front operation is incorrect"); - } -} diff --git a/l1-contracts/test/unit_tests/custom_base_token.spec.ts b/l1-contracts/test/unit_tests/custom_base_token.spec.ts index 9b600fcc1..fd4dac789 100644 --- a/l1-contracts/test/unit_tests/custom_base_token.spec.ts +++ b/l1-contracts/test/unit_tests/custom_base_token.spec.ts @@ -84,7 +84,7 @@ describe("Custom base token chain and bridge tests", () => { ) ); - expect(revertReason).equal("ShB not legacy bridge"); + expect(revertReason).equal("L1AR: not legacy bridge"); }); it("Should deposit base token successfully direct via bridgehub", async () => { @@ -138,13 +138,13 @@ describe("Custom base token chain and bridge tests", () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", []) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong function selector", async () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(96), []) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); }); diff --git a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts index e50187a3d..82d259377 100644 --- a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts +++ b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts @@ -205,7 +205,7 @@ describe("Shared Bridge tests", () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { @@ -221,21 +221,21 @@ describe("Shared Bridge tests", () => { [ethers.constants.HashZero] ) ); - expect(revertReason).equal("ShB wrong msg len 2"); + expect(revertReason).equal("L1AR: wrong msg len 2"); }); it("Should revert on finalizing a withdrawal with wrong function selector", async () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(96), []) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong function signature", async () => { @@ -244,7 +244,7 @@ describe("Shared Bridge tests", () => { .connect(randomSigner) .finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(76), [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); it("Should revert on finalizing a withdrawal with wrong batch number", async () => { @@ -274,7 +274,7 @@ describe("Shared Bridge tests", () => { .connect(randomSigner) .finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, [dummyProof[0], dummyProof[1]]) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); it("Should revert on finalizing a withdrawal with wrong proof", async () => { @@ -288,6 +288,6 @@ describe("Shared Bridge tests", () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, dummyProof) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); }); diff --git a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts index 497a38329..2b35808f9 100644 --- a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts +++ b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts @@ -173,7 +173,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(1, 0, 0, "0x", [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong function signature", async () => { @@ -182,7 +182,7 @@ describe("Legacy Era tests", function () { .connect(randomSigner) .finalizeWithdrawal(1, 0, 0, ethers.utils.randomBytes(76), [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); it("Should revert on finalizing a withdrawal with wrong batch number", async () => { @@ -196,7 +196,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(1, 0, 0, l2ToL1message, dummyProof) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); /////////// Mailbox. Note we have these two together because we need to fix ERA Diamond proxy Address @@ -266,7 +266,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, invalidProof) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); it("Successful deposit", async () => { @@ -297,7 +297,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF) ); - expect(revertReason).equal("Withdrawal is already finalized"); + expect(revertReason).equal("L1AR: Withdrawal is already finalized"); }); }); }); diff --git a/l1-contracts/test/unit_tests/synclayer.spec.ts b/l1-contracts/test/unit_tests/synclayer.spec.ts index 8f0a6de0f..d5ee3fe43 100644 --- a/l1-contracts/test/unit_tests/synclayer.spec.ts +++ b/l1-contracts/test/unit_tests/synclayer.spec.ts @@ -3,32 +3,25 @@ import * as ethers from "ethers"; import { Wallet } from "ethers"; import * as hardhat from "hardhat"; -import type { Bridgehub, StateTransitionManager } from "../../typechain"; -import { AdminFacetFactory, BridgehubFactory, StateTransitionManagerFactory } from "../../typechain"; +import type { Bridgehub } from "../../typechain"; +import { BridgehubFactory } from "../../typechain"; import { initialTestnetDeploymentProcess, defaultDeployerForTests, registerHyperchainWithBridgeRegistration, } from "../../src.ts/deploy-test-process"; -import { - ethTestConfig, - DIAMOND_CUT_DATA_ABI_STRING, - HYPERCHAIN_COMMITMENT_ABI_STRING, - ADDRESS_ONE, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - priorityTxMaxGasLimit, -} from "../../src.ts/utils"; +import { ethTestConfig, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, priorityTxMaxGasLimit } from "../../src.ts/utils"; import { SYSTEM_CONFIG } from "../../scripts/utils"; import type { Deployer } from "../../src.ts/deploy"; describe("Synclayer", function () { let bridgehub: Bridgehub; - let stateTransition: StateTransitionManager; + // let stateTransition: StateTransitionManager; let owner: ethers.Signer; let migratingDeployer: Deployer; - let syncLayerDeployer: Deployer; + let gatewayDeployer: Deployer; // const MAX_CODE_LEN_WORDS = (1 << 16) - 1; // const MAX_CODE_LEN_BYTES = MAX_CODE_LEN_WORDS * 32; // let forwarder: Forwarder; @@ -61,20 +54,16 @@ describe("Synclayer", function () { chainId = migratingDeployer.chainId; bridgehub = BridgehubFactory.connect(migratingDeployer.addresses.Bridgehub.BridgehubProxy, deployWallet); - stateTransition = StateTransitionManagerFactory.connect( - migratingDeployer.addresses.StateTransition.StateTransitionProxy, - deployWallet - ); - syncLayerDeployer = await defaultDeployerForTests(deployWallet, ownerAddress); - syncLayerDeployer.chainId = 10; + gatewayDeployer = await defaultDeployerForTests(deployWallet, ownerAddress); + gatewayDeployer.chainId = 10; await registerHyperchainWithBridgeRegistration( - syncLayerDeployer, + gatewayDeployer, false, [], gasPrice, undefined, - syncLayerDeployer.chainId.toString() + gatewayDeployer.chainId.toString() ); // For tests, the chainId is 9 @@ -82,13 +71,13 @@ describe("Synclayer", function () { }); it("Check register synclayer", async () => { - await syncLayerDeployer.registerSyncLayer(); + await gatewayDeployer.registerSettlementLayer(); }); it("Check start move chain to synclayer", async () => { const gasPrice = await owner.provider.getGasPrice(); - await migratingDeployer.moveChainToSyncLayer(syncLayerDeployer.chainId.toString(), gasPrice, false); - expect(await bridgehub.settlementLayer(migratingDeployer.chainId)).to.equal(syncLayerDeployer.chainId); + await migratingDeployer.moveChainToGateway(gatewayDeployer.chainId.toString(), gasPrice, false); + expect(await bridgehub.settlementLayer(migratingDeployer.chainId)).to.equal(gatewayDeployer.chainId); }); it("Check l2 registration", async () => { @@ -99,20 +88,16 @@ describe("Synclayer", function () { ).mul(10); // const baseTokenAddress = await bridgehub.baseToken(chainId); // const ethIsBaseToken = baseTokenAddress == ADDRESS_ONE; - const stmDeploymentTracker = migratingDeployer.stmDeploymentTracker(migratingDeployer.deployWallet); - await ( - await stmDeploymentTracker.registerSTMAssetOnL2SharedBridge( - chainId, - syncLayerDeployer.addresses.StateTransition.StateTransitionProxy, - value, - priorityTxMaxGasLimit, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata, - syncLayerDeployer.deployWallet.address, - { value: value } - ) - ).wait(); - // console.log("STM asset registered in L2SharedBridge on SL"); + const calldata = stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL2SharedBridge", [ + chainId, + gatewayDeployer.addresses.StateTransition.StateTransitionProxy, + value, + priorityTxMaxGasLimit, + SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + gatewayDeployer.deployWallet.address, + ]); + await migratingDeployer.executeUpgrade(stmDeploymentTracker.address, value, calldata); await migratingDeployer.executeUpgrade( bridgehub.address, value, @@ -133,48 +118,6 @@ describe("Synclayer", function () { // console.log("STM asset registered in L2 Bridgehub on SL"); }); - it("Check finish move chain", async () => { - const syncLayerChainId = syncLayerDeployer.chainId; - const assetInfo = await bridgehub.stmAssetId(migratingDeployer.addresses.StateTransition.StateTransitionProxy); - const diamondCutData = await migratingDeployer.initialZkSyncHyperchainDiamondCut(); - const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); - - const adminFacet = AdminFacetFactory.connect( - migratingDeployer.addresses.StateTransition.DiamondProxy, - migratingDeployer.deployWallet - ); - - // const chainCommitment = { - // totalBatchesExecuted: 0, - // totalBatchesVerified: 0, - // totalBatchesCommitted:0, - // priorityQueueHead: 0, - // priorityQueueTxs: [ - // { - // canonicalTxHash: '0xea79e9b7c3c46a76174b3aea3760570a7e18b593d2b5a087fce52cee95d2d57e', - // expirationTimestamp: "1716557077", - // layer2Tip: 0 - // }], - // l2SystemContractsUpgradeTxHash: ethers.constants.HashZero, - // l2SystemContractsUpgradeBatchNumber:0 , - // batchHashes: ['0xcd4e278573a3b2076a81f91b97e2dd0c85882d9f735ad81dc34b509033671e7b']} - const chainData = ethers.utils.defaultAbiCoder.encode( - [HYPERCHAIN_COMMITMENT_ABI_STRING], - [await adminFacet._prepareChainCommitment()] - ); - // const chainData = await adminFacet.readChainCommitment(); - const stmData = ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint256", "bytes"], - [ADDRESS_ONE, migratingDeployer.deployWallet.address, await stateTransition.protocolVersion(), initialDiamondCut] - ); - const bridgehubMintData = ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes", "bytes"], - [mintChainId, stmData, chainData] - ); - await bridgehub.bridgeMint(syncLayerChainId, assetInfo, bridgehubMintData); - expect(await stateTransition.getHyperchain(mintChainId)).to.not.equal(ethers.constants.AddressZero); - }); - it("Check start message to L3 on L1", async () => { const amount = ethers.utils.parseEther("2"); await bridgehub.requestL2TransactionDirect( @@ -218,6 +161,6 @@ describe("Synclayer", function () { paymasterInput: "0x", reservedDynamic: "0x", }; - bridgehub.forwardTransactionOnSyncLayer(mintChainId, tx, [], ethers.constants.HashZero, 0); + bridgehub.forwardTransactionOnGateway(mintChainId, tx, [], ethers.constants.HashZero, 0); }); }); diff --git a/l2-contracts/contracts/L2ContractHelper.sol b/l2-contracts/contracts/L2ContractHelper.sol index a2fe86080..71e91cc83 100644 --- a/l2-contracts/contracts/L2ContractHelper.sol +++ b/l2-contracts/contracts/L2ContractHelper.sol @@ -106,13 +106,6 @@ address constant BOOTLOADER_ADDRESS = address(SYSTEM_CONTRACTS_OFFSET + 0x01); address constant MSG_VALUE_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x09); address constant DEPLOYER_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x06); -address constant L2_BRIDGEHUB_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x02); - -IL2AssetRouter constant L2_ASSET_ROUTER = IL2AssetRouter(address(USER_CONTRACTS_OFFSET + 0x03)); - -/// @dev The contract responsible for handling tokens native to a single chain. -IL2NativeTokenVault constant L2_NATIVE_TOKEN_VAULT = IL2NativeTokenVault(address(USER_CONTRACTS_OFFSET + 0x04)); - IL2Messenger constant L2_MESSENGER = IL2Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); IBaseToken constant L2_BASE_TOKEN_ADDRESS = IBaseToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); @@ -123,6 +116,13 @@ IPubdataChunkPublisher constant PUBDATA_CHUNK_PUBLISHER = IPubdataChunkPublisher address(SYSTEM_CONTRACTS_OFFSET + 0x11) ); +address constant L2_BRIDGEHUB_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x02); + +IL2AssetRouter constant L2_ASSET_ROUTER = IL2AssetRouter(address(USER_CONTRACTS_OFFSET + 0x03)); + +/// @dev The contract responsible for handling tokens native to a single chain. +IL2NativeTokenVault constant L2_NATIVE_TOKEN_VAULT = IL2NativeTokenVault(address(USER_CONTRACTS_OFFSET + 0x04)); + /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev diff --git a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol index aa3f55d0c..b742314d3 100644 --- a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol @@ -25,7 +25,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { UpgradeableBeacon public l2TokenBeacon; /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. - bytes32 internal l2TokenProxyBytecodeHash; + bytes32 internal immutable l2TokenProxyBytecodeHash; mapping(bytes32 assetId => address tokenAddress) public override tokenAddress; @@ -41,8 +41,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { /// @dev Disable the initialization to prevent Parity hack. /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. /// @param _aliasedOwner The address of the governor contract. - /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed - constructor(bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner, bool _contractsDeployedAlready) { + constructor(bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner) { _disableInitializers(); if (_l2TokenProxyBytecodeHash == bytes32(0)) { revert EmptyBytes32(); @@ -51,22 +50,27 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { revert EmptyAddress(); } - if (!_contractsDeployedAlready) { - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - } - + l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; _transferOwnership(_aliasedOwner); } /// @notice Sets L2 token beacon used by wrapped ERC20 tokens deployed by NTV. /// @dev we don't call this in the constructor, as we need to provide factory deps - function setL2TokenBeacon() external { + /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed + function setL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external onlyOwner { if (address(l2TokenBeacon) != address(0)) { revert AddressMismatch(address(l2TokenBeacon), address(0)); } - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); - l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); - l2TokenBeacon.transferOwnership(owner()); + if (_contractsDeployedAlready) { + if (_l2TokenBeacon == address(0)) { + revert EmptyAddress(); + } + l2TokenBeacon = UpgradeableBeacon(_l2TokenBeacon); + } else { + address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); + l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + l2TokenBeacon.transferOwnership(owner()); + } } /// @notice Mints the wrapped asset during shared bridge deposit finalization. diff --git a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol index 0c4e61e93..fc636d6e1 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol @@ -21,11 +21,9 @@ interface IL2NativeTokenVault is IL2AssetHandler { uint256 amount ); - event L2TokenBeaconUpdated(address l2TokenBeacon, bytes32 l2TokenProxyBytecodeHash); - function tokenAddress(bytes32 _assetId) external view returns (address); function l2TokenAddress(address _l1Token) external view returns (address); - function setL2TokenBeacon() external; + function setL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external; } diff --git a/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol b/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol index 3374c84cb..d81bea055 100644 --- a/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol +++ b/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.20; -// solhint-disable gas-custom-errors, reason-string - import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; import {StateDiffL2DAValidator} from "./StateDiffL2DAValidator.sol"; import {PUBDATA_CHUNK_PUBLISHER} from "../L2ContractHelper.sol"; @@ -17,30 +15,25 @@ import {ReconstructionMismatch, PubdataField} from "./DAErrors.sol"; contract RollupL2DAValidator is IL2DAValidator, StateDiffL2DAValidator { function validatePubdata( // The rolling hash of the user L2->L1 logs. - bytes32 _chainedLogsHash, + bytes32, // The root hash of the user L2->L1 logs. bytes32, // The chained hash of the L2->L1 messages bytes32 _chainedMessagesHash, // The chained hash of uncompressed bytecodes sent to L1 - bytes32 _chainedBytescodesHash, + bytes32 _chainedBytecodesHash, // Operator data, that is related to the DA itself bytes calldata _totalL2ToL1PubdataAndStateDiffs ) external returns (bytes32 outputHash) { (bytes32 stateDiffHash, bytes calldata _totalPubdata, bytes calldata leftover) = _produceStateDiffPubdata( - _chainedLogsHash, _chainedMessagesHash, - _chainedBytescodesHash, + _chainedBytecodesHash, _totalL2ToL1PubdataAndStateDiffs ); /// Check for calldata strict format if (leftover.length != 0) { - revert ReconstructionMismatch( - PubdataField.ExtraData, - bytes32(leftover.length + _totalL2ToL1PubdataAndStateDiffs.length), - bytes32(_totalL2ToL1PubdataAndStateDiffs.length) - ); + revert ReconstructionMismatch(PubdataField.ExtraData, bytes32(0), bytes32(leftover.length)); } // The preimage under the hash `outputHash` is expected to be in the following format: diff --git a/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol b/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol index 3cfe6ccb1..2102b5c28 100644 --- a/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol +++ b/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.20; -// solhint-disable gas-custom-errors, reason-string - import {ReconstructionMismatch, PubdataField} from "./DAErrors.sol"; import {COMPRESSOR_CONTRACT, L2ContractHelper} from "../L2ContractHelper.sol"; @@ -20,16 +18,15 @@ uint256 constant STATE_DIFF_ENTRY_SIZE = 272; /// A library that could be used by any L2 DA validator to produce standard state-diff-based /// DA output. abstract contract StateDiffL2DAValidator { - /// @notice Validates, that the operator provided the correct preimages for los, messages, and bytecodes. + /// @notice Validates, that the operator provided the correct preimages for logs, messages, and bytecodes. /// @return uncompressedStateDiffHash the hash of the uncompressed state diffs /// @return totalL2Pubdata total pubdata that should be sent to L1. /// @return leftoverSuffix the suffix left after pubdata and uncompressed state diffs. /// On Era or other "vanilla" rollups it is empty, but it can be used for providing additional data by the operator, /// e.g. DA committee signatures, etc. function _produceStateDiffPubdata( - bytes32 _chainedLogsHash, bytes32 _chainedMessagesHash, - bytes32 _chainedBytescodesHash, + bytes32 _chainedBytecodesHash, bytes calldata _totalL2ToL1PubdataAndStateDiffs ) internal @@ -40,19 +37,7 @@ abstract contract StateDiffL2DAValidator { /// Check logs uint32 numberOfL2ToL1Logs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - - bytes32 reconstructedChainedLogsHash; - for (uint256 i = 0; i < numberOfL2ToL1Logs; ++i) { - bytes32 hashedLog = EfficientCall.keccak( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] - ); - calldataPtr += L2_TO_L1_LOG_SERIALIZE_SIZE; - reconstructedChainedLogsHash = keccak256(abi.encode(reconstructedChainedLogsHash, hashedLog)); - } - if (reconstructedChainedLogsHash != _chainedLogsHash) { - revert ReconstructionMismatch(PubdataField.LogsHash, _chainedLogsHash, reconstructedChainedLogsHash); - } + calldataPtr += 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE; /// Check messages uint32 numberOfMessages = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); @@ -90,10 +75,10 @@ abstract contract StateDiffL2DAValidator { ); calldataPtr += currentBytecodeLength; } - if (reconstructedChainedL1BytecodesRevealDataHash != _chainedBytescodesHash) { + if (reconstructedChainedL1BytecodesRevealDataHash != _chainedBytecodesHash) { revert ReconstructionMismatch( PubdataField.Bytecode, - _chainedBytescodesHash, + _chainedBytecodesHash, reconstructedChainedL1BytecodesRevealDataHash ); } diff --git a/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol b/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol index 0f7b8cb47..78a49aea8 100644 --- a/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol +++ b/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.20; -// solhint-disable gas-custom-errors, reason-string - import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; /// Rollup DA validator. It will publish data that would allow to use either calldata or blobs. diff --git a/l2-contracts/contracts/interfaces/IL2DAValidator.sol b/l2-contracts/contracts/interfaces/IL2DAValidator.sol index 37c6fdaf5..3289bfc54 100644 --- a/l2-contracts/contracts/interfaces/IL2DAValidator.sol +++ b/l2-contracts/contracts/interfaces/IL2DAValidator.sol @@ -5,14 +5,14 @@ pragma solidity 0.8.20; interface IL2DAValidator { function validatePubdata( // The rolling hash of the user L2->L1 logs. - bytes32 chainedLogsHash, + bytes32 _chainedLogsHash, // The root hash of the user L2->L1 logs. - bytes32 logsRootHash, + bytes32 _logsRootHash, // The chained hash of the L2->L1 messages - bytes32 chainedMessagesHash, + bytes32 _chainedMessagesHash, // The chained hash of uncompressed bytecodes sent to L1 - bytes32 chainedBytescodesHash, + bytes32 _chainedBytecodesHash, // Same operator input - bytes calldata operatorInput + bytes calldata _totalL2ToL1PubdataAndStateDiffs ) external returns (bytes32 outputHash); } diff --git a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts index 09c7d1807..433365064 100644 --- a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts +++ b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts @@ -1,6 +1,6 @@ import { Command } from "commander"; import type { BigNumberish } from "ethers"; -import { Wallet } from "ethers"; +import { Wallet, ethers } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; import { provider, publishBytecodeFromL1, priorityTxMaxGasLimit } from "./utils"; @@ -63,7 +63,7 @@ async function setL2TokenBeacon(deployer: Deployer, chainId: string, gasPrice: B chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, gasPrice, - l2NTV.interface.encodeFunctionData("setL2TokenBeacon"), + l2NTV.interface.encodeFunctionData("setL2TokenBeacon", [false, ethers.constants.AddressZero]), priorityTxMaxGasLimit ); if (deployer.verbose) { diff --git a/l2-contracts/test/erc20.test.ts b/l2-contracts/test/erc20.test.ts index c4138a8f6..27c31ed79 100644 --- a/l2-contracts/test/erc20.test.ts +++ b/l2-contracts/test/erc20.test.ts @@ -80,7 +80,8 @@ describe("ERC20Bridge", function () { ); erc20NativeTokenVault = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, l1BridgeWallet); - await erc20NativeTokenVault.setL2TokenBeacon(); + const governorNTV = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, governorWallet); + await governorNTV.setL2TokenBeacon(false, ethers.constants.AddressZero); }); it("Should finalize deposit ERC20 deposit", async function () { diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index 9bbaff926..fc2138bf1 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": "0x0100005d4ef45dc2f9203f11d4f10a5cc9d8fc90c94038348907f9951d62920d", + "bytecodeHash": "0x0100005d48cd2fb5b7fde10cf657c00e72da53e3c5ce0dc4ea5cf9ad2ec64de6", "sourceCodeHash": "0x69d2533e5481ff13e65f4442e650f4b90c46a48ac643cac9798bbbf421194353" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c7cd7174a30f11c5f0a0caaa072403120d61beef26c0563ba50b0a19a0", + "bytecodeHash": "0x010007c70212baf56e8033abd0494686eb067a28b6a845aa4a79284d904c4c73", "sourceCodeHash": "0x26060f33c7c63bd1f8a1a2f3b368b97ef8dd939bc53e95090f2c556248b99dce" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004dd93c94ac92e4bfebaa1613c8e614b0189e1a1da02e550c87777fede1", + "bytecodeHash": "0x0100004d0cd31f41763d70f16b5172db7617a7713743a6705a903059d4b9df83", "sourceCodeHash": "0xdde7c49a94cc3cd34c3e7ced1b5ba45e4740df68d26243871edbe393e7298f7a" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100013f46aaf4388b6deb1da3c32c958bf354bd5dcbf58e000a2f7b84b7255c", + "bytecodeHash": "0x0100013f89ddacb2fa6e6216f387a238bac9d12b0367d924cb5af80a4cd7dc72", "sourceCodeHash": "0xb0cec0016f481ce023478f71727fbc0d82e967ddc0508e4d47f5c52292a3f790" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e56946cb9626a0f1ddcbc9fb3bc0e38d35cea63885e887a4193506b54a", + "bytecodeHash": "0x010004e53153fb3d063209d71ae7de8a6df057101ebbd1bf8b42a6f50683e2e0", "sourceCodeHash": "0x1657e45c2ff4d6a892afaf71dab71bb8dc7db2c6863e66240e83dfdd2535420d" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x01000049c41dac96c98ae1c3c2bebef5265c912470ccaad8de3178fd4f952370", + "bytecodeHash": "0x010000491ba0449d3042a071ce34729458745517da8f024162a9f06941bf32ff", "sourceCodeHash": "0x217e65f55c8add77982171da65e0db8cc10141ba75159af582973b332a4e098a" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055d06870300325a282f315e80d5f23404614262b4116564262b7f6f516d", + "bytecodeHash": "0x0100055dab66880f8a71739a4db267e2d5c282930a06cee8e1379d94ad5a3a36", "sourceCodeHash": "0xeb5ac8fc83e1c8619db058a9b6973958bd6ed1b6f4938f8f4541d702f12e085d" }, { @@ -59,63 +59,63 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x0100003b22c0da6d42c2bdcb2548fca995f2ecfdbc431360d7364d69d64440f8", + "bytecodeHash": "0x0100003b347a54e5bafb392660e0906dd88ff2bf52006c0301379b5af3a48f2f", "sourceCodeHash": "0x4212e99cbc1722887cfb5b4cb967f278ac8642834786f0e3c6f3b324a9316815" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006fea745ee8452ea9c25847b06ec1b04f2a399560f6be79153d07553e7b", + "bytecodeHash": "0x0100006fc86ac178e134ccc1d8a2602ad6dd866116a1a2c771c92964b2d31d03", "sourceCodeHash": "0x8da495a9fc5aa0d7d20a165a4fc8bc77012bec29c472015ea5ecc0a2bd706137" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010001e9e9dddb58df68080b9d39c4a7f893b56c11702da71a3411f71dde1e25", - "sourceCodeHash": "0x2bc0dd1f7c3a9e1c8b7f2045e4be3cbec84079b5ec3863a90f71741f30ea1a6c" + "bytecodeHash": "0x010001e9bb698fa7d454bede5a444e5a73e38c4d6aeb75061b6315b68128c6f2", + "sourceCodeHash": "0xd83b345b8633affb0bba2296fec9424b4fe9483b60c20ca407d755857a385d8e" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x010001056f6dd9c97bd5fbf9ad44130184713a7b8ad52b5650051f7a69888a5f", + "bytecodeHash": "0x01000105004daa8cb1733999aa434d6dd6bd3fac19fc4381323e222483ff784e", "sourceCodeHash": "0x4cdafafd4cfdf410b31641e14487ea657be3af25e5ec1754fcd7ad67ec23d8be" }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x01000097b1655a8c65edba54e363ea8779c10b9b094a33c0e432a75b66311482", + "bytecodeHash": "0x010000977198a543ea10cb11397823868d7337e0dd26ae65518f62705f1cb6ba", "sourceCodeHash": "0xcb190d0dfd41bbc809409a8aa04a4847b86edfe010b1d75e23b4c8d07b13a9d0" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005da1a0a5544aa0ff74faf3ccf199cb7eb96de4d480645cab60abb31c62", + "bytecodeHash": "0x0100005db76a23815f8c8df5bd35d3de68d5d188f258171fbf1b16e46d5f4388", "sourceCodeHash": "0x4834adf62dbaefa1a1c15d36b5ad1bf2826e7d888a17be495f7ed4e4ea381aa8" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000db40bae05cc6a07f252460f2d434c7ca6c4c215ac93e7a364bfbe2c8af", + "bytecodeHash": "0x010000dbd70081e041f306041a0098438472df26d052b14594fae982dab57c81", "sourceCodeHash": "0xaa2ed3a26af30032c00a612ac327e0cdf5288b7c932ae903462355f863f950cb" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x0100004bf15739ec4061594a3af7eefc9bd23171e758460cd9b62a61326ca5d1", - "sourceCodeHash": "0xc442fd1abb821c95e8229f4ef1575f789e89d60d1001489f5ac9250155cd11dc" + "bytecodeHash": "0x01000049ac0f8fcba1b88c9abc8a7c4ffadf5398ba1afa8e906cc779a0e7e5c4", + "sourceCodeHash": "0x0da0d1279f906147a40e278f52bf3e4d5d4f24225935e4611cc04f4b387b5286" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a7273821c26fd3cecf5cba13c713ac0863c52306dc6cc6325a25bf40b1", + "bytecodeHash": "0x010001a71a8068cddac9fa9f1eb349060ab06f421c927c87eef033f9e61abe4e", "sourceCodeHash": "0x532a962209042f948e8a13e3f4cf12b6d53631e0fc5fa53083c7e2d8062771c0" }, { @@ -185,35 +185,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x010003cb0617f94a9ef85b8d99ca605ad0a4d6307a27cc9e1f63bce4161274ad", - "sourceCodeHash": "0x5a83a36a9c32a275584bc1471fddce46468d361ad972a398ce94dafdd4095c15" + "bytecodeHash": "0x010003cbb7bf765b4bad4b3d0e8c1a5169a2024ee3f3424aebb755874c36a6b5", + "sourceCodeHash": "0x081cdfc1e66eef8dedbc1b950eb87bd97b5d270870325e2b6121ba96f48dd89c" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010009556abe078c6aa331ef2b4a253234a2fec2e253da04639e230e65d39d63", - "sourceCodeHash": "0xfbcf27dcb278cb19aa2830e1a060150bb7a70430f7453622c15df4e854db41a8" + "bytecodeHash": "0x010009551e7e9dd0c1a0f2d805ea92cb1ae6ac1b705d66b72b0d7d05bcd019b5", + "sourceCodeHash": "0xf66f1214b4dba520efd09b4c5d27a4d8ec4d8f24269ab63467e7aa3a8a789c6c" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010008dbc08ccbee5ed443a0b8b2478710c59eb2647d1d1a86c69cd815a54569", - "sourceCodeHash": "0xb266eb5395ec756fa3fd89faf065c1f0678704296585da37d87f70e71c3b9be1" + "bytecodeHash": "0x010008db0b304a1982af85231f37200d3e8eee3f1df64bb0da41154a03a3de73", + "sourceCodeHash": "0xf7149445e5a20dca1ab7bf5a436ed4e5c05fc4ff4909ce0eb138306550eb9e96" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100095be2849088e6c70607dfe687563aba539bc1e576b68c9f719847791bba", - "sourceCodeHash": "0xb2481246d5c6c4b82fb361b7559d14e3374111946a603288b36dfa491bb462a4" + "bytecodeHash": "0x0100095b00b40a8db58a82971d43f075aa8327884c2bbfc1a81c46a330cfbc95", + "sourceCodeHash": "0xdf49fb7fff9484dda405680bf98658c3ad0ed3ff15251e0c43f0b2477e8ce7c3" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008eb5b4e5f60e1a9a6ef185290ea38081796c84ebb84901eca32f0b94793", - "sourceCodeHash": "0xe47787d4d1f883a021054eaee00d7fb4f0ef7613b5fde99f2cd1c58f5cb9354f" + "bytecodeHash": "0x010008ebdd398893ea862b2ae152113530caaa82dcde2234c41bb8b1ef0cb3a0", + "sourceCodeHash": "0x594ca6699f30a6bdef90d12bb36582da8f0120cae4ca31fa3926778d1db81f84" } ] diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index dc1d7d796..b4a28fb70 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -184,9 +184,9 @@ contract L1Messenger is IL1Messenger, ISystemContract { emit BytecodeL1PublicationRequested(_bytecodeHash); } - /// @notice Verifies that the {_totalL2ToL1PubdataAndStateDiffs} reflects what occurred within the L1Batch and that + /// @notice Verifies that the {_operatorInput} reflects what occurred within the L1Batch and that /// the compressed statediffs are equivalent to the full state diffs. - /// @param _totalL2ToL1PubdataAndStateDiffs The total pubdata and uncompressed state diffs of transactions that were + /// @param _operatorInput The total pubdata and uncompressed state diffs of transactions that were /// processed in the current L1 Batch. Pubdata consists of L2 to L1 Logs, messages, deployed bytecode, and state diffs. /// @dev Function that should be called exactly once per L1 Batch by the bootloader. /// @dev Checks that totalL2ToL1Pubdata is strictly packed data that should to be published to L1. @@ -196,7 +196,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { /// to L1 using low-level (VM) L2Log. function publishPubdataAndClearState( address _l2DAValidator, - bytes calldata _totalL2ToL1PubdataAndStateDiffs + bytes calldata _operatorInput ) external onlyCallFromBootloader { uint256 calldataPtr = 0; @@ -210,9 +210,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { // Operator data: 32 bytes for offset // 32 bytes for length - bytes4 inputL2DAValidatePubdataFunctionSig = bytes4( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4] - ); + bytes4 inputL2DAValidatePubdataFunctionSig = bytes4(_operatorInput[calldataPtr:calldataPtr + 4]); if (inputL2DAValidatePubdataFunctionSig != IL2DAValidator.validatePubdata.selector) { revert ReconstructionMismatch( PubdataField.InputDAFunctionSig, @@ -222,23 +220,23 @@ contract L1Messenger is IL1Messenger, ISystemContract { } calldataPtr += 4; - bytes32 inputChainedLogsHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedLogsHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); if (inputChainedLogsHash != chainedLogsHash) { revert ReconstructionMismatch(PubdataField.InputLogsHash, chainedLogsHash, inputChainedLogsHash); } calldataPtr += 32; // Check happens below after we reconstruct the logs root hash - bytes32 inputChainedLogsRootHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedLogsRootHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); calldataPtr += 32; - bytes32 inputChainedMsgsHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedMsgsHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); if (inputChainedMsgsHash != chainedMessagesHash) { revert ReconstructionMismatch(PubdataField.InputMsgsHash, chainedMessagesHash, inputChainedMsgsHash); } calldataPtr += 32; - bytes32 inputChainedBytecodesHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedBytecodesHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); if (inputChainedBytecodesHash != chainedL1BytecodesRevealDataHash) { revert ReconstructionMismatch( PubdataField.InputBytecodeHash, @@ -251,7 +249,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { calldataPtr += 64; /// Check logs - uint32 numberOfL2ToL1Logs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + uint32 numberOfL2ToL1Logs = uint32(bytes4(_operatorInput[calldataPtr:calldataPtr + 4])); if (numberOfL2ToL1Logs > L2_TO_L1_LOGS_MERKLE_TREE_LEAVES) { revert ReconstructionMismatch( PubdataField.NumberOfLogs, @@ -265,7 +263,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { bytes32 reconstructedChainedLogsHash; for (uint256 i = 0; i < numberOfL2ToL1Logs; ++i) { bytes32 hashedLog = EfficientCall.keccak( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] + _operatorInput[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] ); calldataPtr += L2_TO_L1_LOG_SERIALIZE_SIZE; l2ToL1LogsTreeArray[i] = hashedLog; @@ -301,7 +299,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { _gas: gasleft(), _address: _l2DAValidator, _value: 0, - _data: _totalL2ToL1PubdataAndStateDiffs, + _data: _operatorInput, _isSystem: false }); diff --git a/system-contracts/contracts/PubdataChunkPublisher.sol b/system-contracts/contracts/PubdataChunkPublisher.sol index 2e98e8b83..e0c0f02e1 100644 --- a/system-contracts/contracts/PubdataChunkPublisher.sol +++ b/system-contracts/contracts/PubdataChunkPublisher.sol @@ -25,7 +25,7 @@ contract PubdataChunkPublisher is IPubdataChunkPublisher, ISystemContract { blobLinearHashes = new bytes32[](blobCount); - // We allocate to the full size of blobLinearHashes * BLOB_SIZE_BYTES because we need to pad + // We allocate to the full size of blobCount * BLOB_SIZE_BYTES because we need to pad // the data on the right with 0s if it doesn't take up the full blob bytes memory totalBlobs = new bytes(BLOB_SIZE_BYTES * blobCount); @@ -38,12 +38,6 @@ contract PubdataChunkPublisher is IPubdataChunkPublisher, ISystemContract { for (uint256 i = 0; i < blobCount; ++i) { uint256 start = BLOB_SIZE_BYTES * i; - // We break if the pubdata isn't enough to cover all 6 blobs. On L1 it is expected that the hash - // will be bytes32(0) if a blob isn't going to be used. - if (start >= _pubdata.length) { - break; - } - bytes32 blobHash; assembly { // The pointer to the allocated memory above skipping the length. diff --git a/system-contracts/contracts/interfaces/IComplexUpgrader.sol b/system-contracts/contracts/interfaces/IComplexUpgrader.sol index 1b5e15182..8ed670a10 100644 --- a/system-contracts/contracts/interfaces/IComplexUpgrader.sol +++ b/system-contracts/contracts/interfaces/IComplexUpgrader.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; +// 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 diff --git a/system-contracts/contracts/interfaces/IL2DAValidator.sol b/system-contracts/contracts/interfaces/IL2DAValidator.sol index 3e3d1239c..4c8c6d4c4 100644 --- a/system-contracts/contracts/interfaces/IL2DAValidator.sol +++ b/system-contracts/contracts/interfaces/IL2DAValidator.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; +// 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; interface IL2DAValidator { function validatePubdata( @@ -11,8 +11,8 @@ interface IL2DAValidator { // The chained hash of the L2->L1 messages bytes32 _chainedMessagesHash, // The chained hash of uncompressed bytecodes sent to L1 - bytes32 _chainedBytescodesHash, + bytes32 _chainedBytecodesHash, // Same operator input - bytes calldata _operatorInput + bytes calldata _totalL2ToL1PubdataAndStateDiffs ) external returns (bytes32 outputHash); } diff --git a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol index cf4568515..96744b152 100644 --- a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol +++ b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT +// 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; interface IL2GenesisUpgrade { diff --git a/system-contracts/contracts/interfaces/ISystemContract.sol b/system-contracts/contracts/interfaces/ISystemContract.sol index 1f383a823..d86ba49ea 100644 --- a/system-contracts/contracts/interfaces/ISystemContract.sol +++ b/system-contracts/contracts/interfaces/ISystemContract.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.20; import {SystemContractHelper} from "../libraries/SystemContractHelper.sol"; -import {BOOTLOADER_FORMAL_ADDRESS, FORCE_DEPLOYER} from "../Constants.sol"; -import {SystemCallFlagRequired, Unauthorized, CallerMustBeSystemContract, CallerMustBeBootloader, CallerMustBeForceDeployer} from "../SystemContractErrors.sol"; +import {BOOTLOADER_FORMAL_ADDRESS} from "../Constants.sol"; +import {SystemCallFlagRequired, Unauthorized, CallerMustBeSystemContract, CallerMustBeBootloader} from "../SystemContractErrors.sol"; /** * @author Matter Labs @@ -51,13 +51,4 @@ abstract contract ISystemContract { } _; } - - /// @notice Modifier that makes sure that the method - /// can only be called from the L1 force deployer. - modifier onlyCallFromForceDeployer() { - if (msg.sender != FORCE_DEPLOYER) { - revert CallerMustBeForceDeployer(); - } - _; - } } From 19f08a4d3bd217e55b4c1a38898a39942fe8caf2 Mon Sep 17 00:00:00 2001 From: Raid5594 <52794079+Raid5594@users.noreply.github.com> Date: Fri, 9 Aug 2024 20:15:19 +0400 Subject: [PATCH 05/26] Custom Asset Bridging after OZ fixes were applied (#679) (#682) Co-authored-by: Raid Ateir Co-authored-by: kelemeno <34402761+kelemeno@users.noreply.github.com> Co-authored-by: Kalman Lajko --- .../contracts/RollupL1DAValidator.sol | 84 +-- .../test-contracts/SystemContractsCaller.sol | 2 +- .../contracts/bridge/BridgeHelper.sol | 29 + .../contracts/bridge/L1AssetRouter.sol | 708 +++++++++--------- .../contracts/bridge/L1ERC20Bridge.sol | 204 ++--- .../contracts/bridge/L1NativeTokenVault.sol | 186 ++--- .../bridge/interfaces/IL1AssetHandler.sol | 22 +- .../bridge/interfaces/IL1AssetRouter.sol | 42 +- .../bridge/interfaces/IL1ERC20Bridge.sol | 6 +- .../bridge/interfaces/IL1NativeTokenVault.sol | 10 +- .../contracts/bridge/interfaces/IL2Bridge.sol | 8 +- .../bridge/interfaces/IL2BridgeLegacy.sol | 6 + .../contracts/bridgehub/Bridgehub.sol | 256 ++++--- .../contracts/bridgehub/IBridgehub.sol | 10 +- .../contracts/bridgehub/MessageRoot.sol | 84 +-- .../bridgehub/STMDeploymentTracker.sol | 20 +- l1-contracts/contracts/common/Config.sol | 4 +- .../common/interfaces/IL2ContractDeployer.sol | 2 +- .../common/libraries/DataEncoding.sol | 90 +++ .../dev-contracts/DummyL1ERC20Bridge.sol | 10 +- .../dev-contracts/RevertReceiveAccount.sol | 2 +- .../dev-contracts/RevertTransferERC20.sol | 2 +- .../test/DummyAdminFacetNoOverlap.sol | 3 +- .../dev-contracts/test/DummySharedBridge.sol | 12 + .../dev-contracts/test/L1ERC20BridgeTest.sol | 2 +- .../contracts/governance/Governance.sol | 2 +- .../state-transition/ValidatorTimelock.sol | 4 +- .../contracts/state-transition/Verifier.sol | 4 +- .../chain-deps/facets/Executor.sol | 2 +- .../chain-deps/facets/Mailbox.sol | 4 +- .../chain-interfaces/IExecutor.sol | 8 +- .../chain-interfaces/ILegacyGetters.sol | 2 +- .../chain-interfaces/IMailbox.sol | 2 +- .../IZkSyncHyperchainBase.sol | 2 +- .../libraries/TransactionValidator.sol | 4 +- l1-contracts/deploy-scripts/DeployL1.s.sol | 12 +- .../deploy-scripts/RegisterHyperchain.s.sol | 3 +- l1-contracts/src.ts/deploy.ts | 46 +- l1-contracts/src.ts/utils.ts | 7 + .../Bridgehub/experimental_bridge.t.sol | 117 +-- .../Bridges/L1Erc20Bridge/Getters.t.sol | 2 +- .../L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol | 13 +- .../L1SharedBridge/L1SharedBridgeBase.t.sol | 46 +- .../L1SharedBridge/L1SharedBridgeFails.t.sol | 34 +- .../L1SharedBridgeHyperEnabled.t.sol | 4 +- .../L1SharedBridge/L1SharedBridgeLegacy.t.sol | 3 +- .../_L1SharedBridge_Shared.t.sol | 11 +- .../test/unit_tests/custom_base_token.spec.ts | 12 +- .../unit_tests/l1_shared_bridge_test.spec.ts | 72 +- .../test/unit_tests/legacy_era_test.spec.ts | 5 +- l2-contracts/contracts/L2ContractHelper.sol | 18 +- .../contracts/bridge/L2AssetRouter.sol | 77 +- .../contracts/bridge/L2NativeTokenVault.sol | 126 +++- .../contracts/bridge/L2StandardERC20.sol | 52 +- .../contracts/bridge/L2WrappedBaseToken.sol | 24 +- .../bridge/interfaces/IL1ERC20Bridge.sol | 1 + .../bridge/interfaces/IL2AssetHandler.sol | 24 +- .../bridge/interfaces/IL2AssetRouter.sol | 11 +- .../bridge/interfaces/IL2NativeTokenVault.sol | 7 +- .../interfaces/IL2SharedBridgeLegacy.sol | 10 - .../bridge/interfaces/IL2StandardToken.sol | 4 +- .../interfaces/ILegacyL2SharedBridge.sol | 3 + .../common/libraries/DataEncoding.sol | 90 +++ l2-contracts/test/erc20.test.ts | 31 +- system-contracts/SystemContractsHashes.json | 58 +- .../contracts/AccountCodeStorage.sol | 2 +- .../contracts/BootloaderUtilities.sol | 4 +- system-contracts/contracts/Constants.sol | 2 +- .../contracts/ContractDeployer.sol | 2 +- .../libraries/SystemContractsCaller.sol | 2 +- .../contracts/libraries/TransactionHelper.sol | 14 +- .../contracts/libraries/Utils.sol | 2 +- tools/data/verifier_contract_template.txt | 4 +- 73 files changed, 1529 insertions(+), 1264 deletions(-) create mode 100644 l1-contracts/contracts/bridge/BridgeHelper.sol create mode 100644 l1-contracts/contracts/common/libraries/DataEncoding.sol delete mode 100644 l2-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol create mode 100644 l2-contracts/contracts/common/libraries/DataEncoding.sol diff --git a/da-contracts/contracts/RollupL1DAValidator.sol b/da-contracts/contracts/RollupL1DAValidator.sol index 9df028a63..37ea2e433 100644 --- a/da-contracts/contracts/RollupL1DAValidator.sol +++ b/da-contracts/contracts/RollupL1DAValidator.sol @@ -37,6 +37,48 @@ contract RollupL1DAValidator is IL1DAValidator, CalldataDA { } } + /// @inheritdoc IL1DAValidator + function checkDA( + uint256, // _chainId + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported + ) external view returns (L1DAValidatorOutput memory output) { + ( + bytes32 stateDiffHash, + bytes32 fullPubdataHash, + bytes32[] memory blobsLinearHashes, + uint256 blobsProvided, + bytes calldata l1DaInput + ) = _processL2RollupDAValidatorOutputHash(_l2DAValidatorOutputHash, _maxBlobsSupported, _operatorDAInput); + + uint8 pubdataSource = uint8(l1DaInput[0]); + bytes32[] memory blobCommitments; + + if (pubdataSource == uint8(PubdataSource.Blob)) { + blobCommitments = _processBlobDA(blobsProvided, _maxBlobsSupported, l1DaInput[1:]); + } else if (pubdataSource == uint8(PubdataSource.Calldata)) { + (blobCommitments, ) = _processCalldataDA(blobsProvided, fullPubdataHash, _maxBlobsSupported, l1DaInput[1:]); + } else { + revert("l1-da-validator/invalid-pubdata-source"); + } + + // We verify that for each set of blobHash/blobCommitment are either both empty + // or there are values for both. + // This is mostly a sanity check and it is not strictly required. + for (uint256 i = 0; i < _maxBlobsSupported; ++i) { + require( + (blobsLinearHashes[i] == bytes32(0) && blobCommitments[i] == bytes32(0)) || + (blobsLinearHashes[i] != bytes32(0) && blobCommitments[i] != bytes32(0)), + "bh" + ); + } + + output.stateDiffHash = stateDiffHash; + output.blobsLinearHashes = blobsLinearHashes; + output.blobsOpeningCommitments = blobCommitments; + } + /// @notice Generated the blob commitemnt to be used in the cryptographic proof by calling the point evaluation precompile. /// @param _index The index of the blob in this transaction. /// @param _commitment The packed: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes @@ -109,48 +151,6 @@ contract RollupL1DAValidator is IL1DAValidator, CalldataDA { require(versionedHash == bytes32(0), "lh"); } - /// @inheritdoc IL1DAValidator - function checkDA( - uint256, // _chainId - bytes32 _l2DAValidatorOutputHash, - bytes calldata _operatorDAInput, - uint256 _maxBlobsSupported - ) external returns (L1DAValidatorOutput memory output) { - ( - bytes32 stateDiffHash, - bytes32 fullPubdataHash, - bytes32[] memory blobsLinearHashes, - uint256 blobsProvided, - bytes calldata l1DaInput - ) = _processL2RollupDAValidatorOutputHash(_l2DAValidatorOutputHash, _maxBlobsSupported, _operatorDAInput); - - uint8 pubdataSource = uint8(l1DaInput[0]); - bytes32[] memory blobCommitments; - - if (pubdataSource == uint8(PubdataSource.Blob)) { - blobCommitments = _processBlobDA(blobsProvided, _maxBlobsSupported, l1DaInput[1:]); - } else if (pubdataSource == uint8(PubdataSource.Calldata)) { - (blobCommitments, ) = _processCalldataDA(blobsProvided, fullPubdataHash, _maxBlobsSupported, l1DaInput[1:]); - } else { - revert("l1-da-validator/invalid-pubdata-source"); - } - - // We verify that for each set of blobHash/blobCommitment are either both empty - // or there are values for both. - // This is mostly a sanity check and it is not strictly required. - for (uint256 i = 0; i < _maxBlobsSupported; ++i) { - require( - (blobsLinearHashes[i] == bytes32(0) && blobCommitments[i] == bytes32(0)) || - (blobsLinearHashes[i] != bytes32(0) && blobCommitments[i] != bytes32(0)), - "bh" - ); - } - - output.stateDiffHash = stateDiffHash; - output.blobsLinearHashes = blobsLinearHashes; - output.blobsOpeningCommitments = blobCommitments; - } - /// @notice Calls the point evaluation precompile and verifies the output /// Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. /// Also verify that the provided commitment matches the provided versioned_hash. diff --git a/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol b/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol index ca7c870c7..1f154e270 100644 --- a/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol +++ b/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol @@ -6,7 +6,7 @@ import {MSG_VALUE_SYSTEM_CONTRACT, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT} from "@mat import {Utils} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/Utils.sol"; // Addresses used for the compiler to be replaced with the -// zkSync-specific opcodes during the compilation. +// ZKsync-specific opcodes during the compilation. // IMPORTANT: these are just compile-time constants and are used // only if used in-place by Yul optimizer. address constant TO_L1_CALL_ADDRESS = address((1 << 16) - 1); diff --git a/l1-contracts/contracts/bridge/BridgeHelper.sol b/l1-contracts/contracts/bridge/BridgeHelper.sol new file mode 100644 index 000000000..9fc9b7cfc --- /dev/null +++ b/l1-contracts/contracts/bridge/BridgeHelper.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// solhint-disable gas-custom-errors + +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for working with L2 contracts on L1. + */ +library BridgeHelper { + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token, address _ethTokenAddress) internal view returns (bytes memory) { + if (_token == _ethTokenAddress) { + bytes memory name = abi.encode("Ether"); + bytes memory symbol = abi.encode("ETH"); + bytes memory decimals = abi.encode(uint8(18)); + return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 + } + + (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); + (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); + (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + return abi.encode(data1, data2, data3); + } +} diff --git a/l1-contracts/contracts/bridge/L1AssetRouter.sol b/l1-contracts/contracts/bridge/L1AssetRouter.sol index d1df09d19..b6845cba0 100644 --- a/l1-contracts/contracts/bridge/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/L1AssetRouter.sol @@ -7,7 +7,6 @@ pragma solidity 0.8.24; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; -import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -22,6 +21,7 @@ import {IMailbox} from "../state-transition/chain-interfaces/IMailbox.sol"; import {L2Message, TxStatus} from "../common/Messaging.sol"; import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {TWO_BRIDGES_MAGIC_VALUE, ETH_TOKEN_ADDRESS} from "../common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; @@ -29,6 +29,8 @@ import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; import {IBridgehub, L2TransactionRequestTwoBridgesInner, L2TransactionRequestDirect} from "../bridgehub/IBridgehub.sol"; import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "../common/L2ContractAddresses.sol"; +import {BridgeHelper} from "./BridgeHelper.sol"; + /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @dev Bridges assets between L1 and ZK chain, supporting both ETH and ERC20 tokens. @@ -45,20 +47,20 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev Era's chainID uint256 internal immutable ERA_CHAIN_ID; - /// @dev The address of zkSync Era diamond proxy contract. + /// @dev The address of ZKsync Era diamond proxy contract. address internal immutable ERA_DIAMOND_PROXY; - /// @dev Stores the first batch number on the zkSync Era Diamond Proxy that was settled after Diamond proxy upgrade. + /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after Diamond proxy upgrade. /// This variable is used to differentiate between pre-upgrade and post-upgrade Eth withdrawals. Withdrawals from batches older /// than this value are considered to have been finalized prior to the upgrade and handled separately. uint256 internal eraPostDiamondUpgradeFirstBatch; - /// @dev Stores the first batch number on the zkSync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. + /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. /// This variable is used to differentiate between pre-upgrade and post-upgrade ERC20 withdrawals. Withdrawals from batches older /// than this value are considered to have been finalized prior to the upgrade and handled separately. uint256 internal eraPostLegacyBridgeUpgradeFirstBatch; - /// @dev Stores the zkSync Era batch number that processes the last deposit tx initiated by the legacy bridge. + /// @dev Stores the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge /// This variable (together with eraLegacyBridgeLastDepositTxNumber) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older batches /// than this value are considered to have been processed prior to the upgrade and handled separately. /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. @@ -76,8 +78,10 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev A mapping chainId => bridgeProxy. Used to store the bridge proxy's address, and to see if it has been deployed yet. mapping(uint256 chainId => address l2Bridge) public __DEPRECATED_l2BridgeAddress; - /// @dev A mapping chainId => L2 deposit transaction hash => keccak256(abi.encode(account, tokenAddress, amount)). - /// @dev Tracks deposit transactions from L2 to enable users to claim their funds if a deposit fails. + /// @dev A mapping chainId => L2 deposit transaction hash => dataHash + // keccak256(abi.encode(account, tokenAddress, amount)) for legacy transfers + // keccak256(abi.encode(_prevMsgSender, assetId, transferData)) for new transfers + /// @dev Tracks deposit transactions to L2 to enable users to claim their funds if a deposit fails. mapping(uint256 chainId => mapping(bytes32 l2DepositTxHash => bytes32 depositDataHash)) public override depositHappened; @@ -86,6 +90,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab mapping(uint256 chainId => mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized))) public isWithdrawalFinalized; + /// @notice Deprecated. Kept for backwards compatibility. /// @dev Indicates whether the hyperbridging is enabled for a given chain. // slither-disable-next-line uninitialized-state mapping(uint256 chainId => bool enabled) public hyperbridgingEnabled; @@ -192,7 +197,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev This function is part of the upgrade process used to nullify chain balances once they are credited to NTV. /// @param _chainId The ID of the ZK chain. /// @param _token The address of the token which was previously deposit to shared bridge. - function clearChainBalance(uint256 _chainId, address _token) external { + function nullifyChainBalanceByNTV(uint256 _chainId, address _token) external { require(msg.sender == address(nativeTokenVault), "L1AR: not NTV"); chainBalance[_chainId][_token] = 0; } @@ -206,7 +211,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab legacyBridge = IL1ERC20Bridge(_legacyBridge); } - /// @notice Sets the L1ERC20Bridge contract address. + /// @notice Sets the nativeTokenVault contract address. /// @dev Should be called only once by the owner. /// @param _nativeTokenVault The address of the native token vault. function setNativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external onlyOwner { @@ -215,16 +220,36 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab nativeTokenVault = _nativeTokenVault; } - /// @notice Sets the asset handler address for a given asset ID. - /// @dev No access control on the caller, as msg.sender is encoded in the assetId. - /// @param _assetData In most cases this parameter is bytes32 encoded token address. However, it can include extra information used by custom asset handlers. - /// @param _assetHandlerAddress The address of the asset handler, which will hold the token of interest. - function setAssetHandlerAddressInitial(bytes32 _assetData, address _assetHandlerAddress) external { - address sender = msg.sender == address(nativeTokenVault) ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; - bytes32 assetId = keccak256(abi.encode(uint256(block.chainid), sender, _assetData)); + /// @notice Used to set the assed deployment tracker address for given asset data. + /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. + /// @param _assetDeploymentTracker The whitelisted address of asset deployment tracker for provided asset. + function setAssetDeploymentTracker( + bytes32 _assetRegistrationData, + address _assetDeploymentTracker + ) external onlyOwner { + bytes32 assetId = keccak256( + abi.encode(uint256(block.chainid), _assetDeploymentTracker, _assetRegistrationData) + ); + assetDeploymentTracker[assetId] = _assetDeploymentTracker; + emit AssetDeploymentTrackerSet(assetId, _assetDeploymentTracker, _assetRegistrationData); + } + + /// @notice Sets the asset handler address for a specified asset ID on the chain of the asset deployment tracker. + /// @dev The caller of this function is encoded within the `assetId`, therefore, it should be invoked by the asset deployment tracker contract. + /// @dev Typically, for most tokens, ADT is the native token vault. However, custom tokens may have their own specific asset deployment trackers. + /// @dev `setAssetHandlerAddressOnCounterPart` should be called on L1 to set asset handlers on L2 chains for a specific asset ID. + /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. + /// @param _assetHandlerAddress The address of the asset handler to be set for the provided asset. + function setAssetHandlerAddressInitial(bytes32 _assetRegistrationData, address _assetHandlerAddress) external { + bool senderIsNTV = msg.sender == address(nativeTokenVault); + address sender = senderIsNTV ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; + bytes32 assetId = DataEncoding.encodeAssetId(block.chainid, _assetRegistrationData, sender); + require(senderIsNTV || msg.sender == assetDeploymentTracker[assetId], "ShB: not NTV or ADT"); assetHandlerAddress[assetId] = _assetHandlerAddress; - assetDeploymentTracker[assetId] = msg.sender; - emit AssetHandlerRegisteredInitial(assetId, _assetHandlerAddress, _assetData, sender); + if (senderIsNTV) { + assetDeploymentTracker[assetId] = msg.sender; + } + emit AssetHandlerRegisteredInitial(assetId, _assetHandlerAddress, _assetRegistrationData, sender); } /// @notice Used to set the asset handler address for a given asset ID on a remote ZK chain @@ -235,8 +260,8 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction. /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. /// @param _assetId The encoding of asset ID. - /// @param _assetAddressOnCounterPart The address of the asset handler, which will hold the token of interest. - /// @return l2TxHash The L2 transaction hash of setting asset handler on remote chain. + /// @param _assetHandlerAddressOnCounterPart The address of the asset handler, which will hold the token of interest. + /// @return txHash The L2 transaction hash of setting asset handler on remote chain. function setAssetHandlerAddressOnCounterPart( uint256 _chainId, uint256 _mintValue, @@ -244,27 +269,27 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _l2TxGasPerPubdataByte, address _refundRecipient, bytes32 _assetId, - address _assetAddressOnCounterPart - ) external payable returns (bytes32 l2TxHash) { + address _assetHandlerAddressOnCounterPart + ) external payable returns (bytes32 txHash) { require(msg.sender == assetDeploymentTracker[_assetId] || msg.sender == owner(), "L1AR: only ADT or owner"); bytes memory l2Calldata = abi.encodeCall( IL2Bridge.setAssetHandlerAddress, - (_assetId, _assetAddressOnCounterPart) + (_assetId, _assetHandlerAddressOnCounterPart) ); L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ chainId: _chainId, l2Contract: L2_ASSET_ROUTER_ADDR, mintValue: _mintValue, // l2 gas + l2 msg.value the bridgehub will withdraw the mintValue from the base token bridge for gas - l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub + l2Value: 0, // For base token deposits, there is no msg.value during the call, as the base token is minted to the recipient address l2Calldata: l2Calldata, l2GasLimit: _l2TxGasLimit, l2GasPerPubdataByteLimit: _l2TxGasPerPubdataByte, factoryDeps: new bytes[](0), refundRecipient: _refundRecipient }); - l2TxHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); + txHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); } /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. @@ -278,13 +303,15 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes32 _assetId, address _prevMsgSender, uint256 _amount - ) external payable virtual onlyBridgehubOrEra(_chainId) whenNotPaused { - address l1AssetHandler = _getAssetHandler(_assetId); + ) external payable onlyBridgehubOrEra(_chainId) whenNotPaused { + address l1AssetHandler = assetHandlerAddress[_assetId]; + require(l1AssetHandler != address(0), "ShB: asset handler not set"); + _transferAllowanceToNTV(_assetId, _amount, _prevMsgSender); // slither-disable-next-line unused-return IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msg.value}({ _chainId: _chainId, - _mintValue: _amount, + _l2Value: 0, _assetId: _assetId, _prevMsgSender: _prevMsgSender, _data: abi.encode(_amount, address(0)) @@ -294,70 +321,6 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab emit BridgehubDepositBaseTokenInitiated(_chainId, _prevMsgSender, _assetId, _amount); } - /// @notice Returns the address of asset handler. - /// @dev If asset handler is not set for the asset, the asset is registered. - /// @param _assetId The encoding of asset ID. - /// @return l1AssetHandler The address of asset handler for provided asset ID. - function _getAssetHandler(bytes32 _assetId) internal returns (address l1AssetHandler) { - l1AssetHandler = assetHandlerAddress[_assetId]; - // Check if no asset handler is set - if (l1AssetHandler == address(0)) { - require(uint256(_assetId) <= type(uint160).max, "L1AR: only address can be registered"); - l1AssetHandler = address(nativeTokenVault); - nativeTokenVault.registerToken(address(uint160(uint256(_assetId)))); - } - } - - /// @notice Decodes the transfer input for legacy data and transfers allowance to NTV. - /// @dev Is not applicable for custom asset handlers. - /// @param _data The encoded transfer data (address _l1Token, uint256 _depositAmount, address _l2Receiver). - /// @param _prevMsgSender The address of the deposit initiator. - /// @return Tuple of asset ID and encoded transfer data to conform with new encoding standard. - function _handleLegacyData(bytes calldata _data, address _prevMsgSender) internal returns (bytes32, bytes memory) { - (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( - _data, - (address, uint256, address) - ); - bytes32 assetId = _ensureTokenRegisteredWithNTV(_l1Token); - _transferAllowanceToNTV(assetId, _depositAmount, _prevMsgSender); - return (assetId, abi.encode(_depositAmount, _l2Receiver)); - } - - /// @notice Ensures that token is registered with native token vault. - /// @dev Only used when deposit is made with legacy data encoding format. - /// @param _l1Token The L1 token address which should be registered with native token vault. - /// @return assetId The asset ID of the token provided. - function _ensureTokenRegisteredWithNTV(address _l1Token) internal returns (bytes32 assetId) { - assetId = nativeTokenVault.getAssetId(_l1Token); - if (nativeTokenVault.tokenAddress(assetId) == address(0)) { - nativeTokenVault.registerToken(_l1Token); - } - } - - /// @notice Transfers allowance to Native Token Vault, if the asset is registered with it. Does nothing for ETH or non-registered tokens. - /// @dev assetId is not the padded address, but the correct encoded ID (NTV stores respective format for IDs). - /// @param _assetId The encoding of asset ID. - /// @param _amount The asset amount to be transferred to native token vault. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - function _transferAllowanceToNTV(bytes32 _assetId, uint256 _amount, address _prevMsgSender) internal { - address l1TokenAddress = nativeTokenVault.tokenAddress(_assetId); - if (l1TokenAddress == address(0) || l1TokenAddress == ETH_TOKEN_ADDRESS) { - return; - } - IERC20 l1Token = IERC20(l1TokenAddress); - - // Do the transfer if allowance to Shared bridge is bigger than amount - // And if there is not enough allowance for the NTV - if ( - l1Token.allowance(_prevMsgSender, address(this)) >= _amount && - l1Token.allowance(_prevMsgSender, address(nativeTokenVault)) < _amount - ) { - // slither-disable-next-line arbitrary-send-erc20 - l1Token.safeTransferFrom(_prevMsgSender, address(this), _amount); - l1Token.safeIncreaseAllowance(address(nativeTokenVault), _amount); - } - } - /// @notice Initiates a deposit transaction within Bridgehub, used by `requestL2TransactionTwoBridges`. /// @param _chainId The chain ID of the ZK chain to which deposit. /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. @@ -382,8 +345,14 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bool legacyDeposit = false; bytes1 encodingVersion = _data[0]; + // The new encoding ensures that the calldata is collision-resistant with respect to the legacy format. + // In the legacy calldata, the first input was the address, meaning the most significant byte was always `0x00`. if (encodingVersion == 0x01) { (assetId, transferData) = abi.decode(_data[1:], (bytes32, bytes)); + require( + assetHandlerAddress[assetId] != address(nativeTokenVault), + "ShB: new encoding format not yet supported for NTV" + ); } else { (assetId, transferData) = _handleLegacyData(_data, _prevMsgSender); legacyDeposit = true; @@ -391,27 +360,20 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab require(BRIDGE_HUB.baseTokenAssetId(_chainId) != assetId, "L1AR: baseToken deposit not supported"); - bytes memory l2BridgeMintCalldata = _burn({ + bytes memory bridgeMintCalldata = _burn({ _chainId: _chainId, _l2Value: _l2Value, _assetId: assetId, _prevMsgSender: _prevMsgSender, - _transferData: transferData + _transferData: transferData, + _passValue: true }); - bytes32 txDataHash; - - if (legacyDeposit) { - (uint256 _depositAmount, ) = abi.decode(transferData, (uint256, address)); - txDataHash = keccak256(abi.encode(_prevMsgSender, nativeTokenVault.tokenAddress(assetId), _depositAmount)); - } else { - txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(_prevMsgSender, assetId, transferData))); - } + bytes32 txDataHash = this.encodeTxDataHash(legacyDeposit, _prevMsgSender, assetId, transferData); request = _requestToBridge({ - _chainId: _chainId, _prevMsgSender: _prevMsgSender, _assetId: assetId, - _l2BridgeMintCalldata: l2BridgeMintCalldata, + _bridgeMintCalldata: bridgeMintCalldata, _txDataHash: txDataHash }); @@ -420,58 +382,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab txDataHash: txDataHash, from: _prevMsgSender, assetId: assetId, - l2BridgeMintCalldata: l2BridgeMintCalldata - }); - } - - /// @notice Forwards the burn request for specific asset to respective asset handler. - /// @param _chainId The chain ID of the ZK chain to which deposit. - /// @param _l2Value The L2 `msg.value` from the L1 -> L2 deposit transaction. - /// @param _assetId The deposited asset ID. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. - /// @return l2BridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. - function _burn( - uint256 _chainId, - uint256 _l2Value, - bytes32 _assetId, - address _prevMsgSender, - bytes memory _transferData - ) internal returns (bytes memory l2BridgeMintCalldata) { - address l1AssetHandler = assetHandlerAddress[_assetId]; - l2BridgeMintCalldata = IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msg.value}({ - _chainId: _chainId, - _mintValue: _l2Value, - _assetId: _assetId, - _prevMsgSender: _prevMsgSender, - _data: _transferData - }); - } - - /// @dev The request data that is passed to the bridgehub. - /// @param _chainId The chain ID of the ZK chain to which deposit. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _assetId The deposited asset ID. - /// @param _l2BridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. - /// @param _txDataHash The keccak256 hash of 0x01 || abi.encode(bytes32, bytes) to identify deposits. - /// @return request The data used by the bridgehub to create L2 transaction request to specific ZK chain. - function _requestToBridge( - // solhint-disable-next-line no-unused-vars - uint256 _chainId, - address _prevMsgSender, - bytes32 _assetId, - bytes memory _l2BridgeMintCalldata, - bytes32 _txDataHash - ) internal view returns (L2TransactionRequestTwoBridgesInner memory request) { - // Request the finalization of the deposit on the L2 side - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, _l2BridgeMintCalldata); - - request = L2TransactionRequestTwoBridgesInner({ - magicValue: TWO_BRIDGES_MAGIC_VALUE, - l2Contract: L2_ASSET_ROUTER_ADDR, - l2Calldata: l2TxCalldata, - factoryDeps: new bytes[](0), - txDataHash: _txDataHash + bridgeMintCalldata: bridgeMintCalldata }); } @@ -490,37 +401,51 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); } - /// @notice Generates a calldata for calling the deposit finalization on the L2 native token contract. - /// @param _l1Sender The address of the deposit initiator. - /// @param _assetId The deposited asset ID. - /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. - /// @return Returns calldata used on ZK chain. - function _getDepositL2Calldata( - address _l1Sender, + /// @notice Finalize the withdrawal and release funds + /// @param _chainId The chain ID of the transaction to check + /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _message The L2 withdraw data, stored in an L2 -> L1 message + /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization + function finalizeWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external override { + _finalizeWithdrawal({ + _chainId: _chainId, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _message: _message, + _merkleProof: _merkleProof + }); + } + + /// @dev Calls the internal `_encodeTxDataHash`. Used as a wrapped for try / catch case. + /// @param _isLegacyEncoding Boolean flag indicating whether to use the legacy encoding standard (true) or the latest encoding standard (false). + /// @param _prevMsgSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @return txDataHash The resulting encoded transaction data hash. + function encodeTxDataHash( + bool _isLegacyEncoding, + address _prevMsgSender, bytes32 _assetId, - bytes memory _transferData - ) internal view returns (bytes memory) { - // First branch covers the case when asset is not registered with NTV (custom asset handler) - // Second branch handles tokens registered with NTV and uses legacy calldata encoding - if (nativeTokenVault.tokenAddress(_assetId) == address(0)) { - return abi.encodeCall(IL2Bridge.finalizeDeposit, (_assetId, _transferData)); - } else { - (uint256 _amount, , address _l2Receiver, bytes memory _gettersData, address _parsedL1Token) = abi.decode( - _transferData, - (uint256, address, address, bytes, address) - ); - return - abi.encodeCall( - IL2BridgeLegacy.finalizeDeposit, - (_l1Sender, _l2Receiver, _parsedL1Token, _amount, _gettersData) - ); - } + bytes calldata _transferData + ) external view returns (bytes32 txDataHash) { + return _encodeTxDataHash(_isLegacyEncoding, _prevMsgSender, _assetId, _transferData); } /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. - /// @param _depositSender The address of the deposit initiator. - /// @param _assetId The address of the deposited L1 ERC20 token. - /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. + /// @param _chainId The ZK chain id to which deposit was initiated. + /// @param _depositSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _assetData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. Might include extra information. /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. @@ -531,7 +456,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _chainId, address _depositSender, bytes32 _assetId, - bytes memory _transferData, + bytes memory _assetData, bytes32 _l2TxHash, uint256 _l2BatchNumber, uint256 _l2MessageIndex, @@ -554,82 +479,59 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab require(!_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch), "L1AR: legacy cFD"); { bytes32 dataHash = depositHappened[_chainId][_l2TxHash]; - address l1Token = nativeTokenVault.tokenAddress(_assetId); - (uint256 amount, address prevMsgSender) = abi.decode(_transferData, (uint256, address)); - bytes32 txDataHash = keccak256(abi.encode(prevMsgSender, l1Token, amount)); - require(dataHash == txDataHash, "L1AR: d.it not hap"); + // Determine if the given dataHash matches the calculated legacy transaction hash. + bool isLegacyTxDataHash = _isLegacyTxDataHash(_depositSender, _assetId, _assetData, dataHash); + // If the dataHash matches the legacy transaction hash, skip the next step. + // Otherwise, perform the check using the new transaction data hash encoding. + if (!isLegacyTxDataHash) { + bytes32 txDataHash = _encodeTxDataHash(false, _depositSender, _assetId, _assetData); + require(dataHash == txDataHash, "L1AR: d.it not hap"); + } } delete depositHappened[_chainId][_l2TxHash]; - IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer(_chainId, _assetId, _transferData); + IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer( + _chainId, + _assetId, + _depositSender, + _assetData + ); - emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _assetId, _transferData); + emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _assetId, _assetData); } - /// @dev Determines if an eth withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on zkSync Era before diamond proxy upgrade. - function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - require((_chainId != ERA_CHAIN_ID) || eraPostDiamondUpgradeFirstBatch != 0, "L1AR: diamondUFB not set for Era"); - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); - } - - /// @dev Determines if a token withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on zkSync Era before Legacy Bridge upgrade. - function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - require( - (_chainId != ERA_CHAIN_ID) || eraPostLegacyBridgeUpgradeFirstBatch != 0, - "L1AR: LegacyUFB not set for Era" - ); - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token) public view returns (bytes memory) { + return BridgeHelper.getERC20Getters(_token, ETH_TOKEN_ADDRESS); } - /// @dev Determines if a deposit was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the deposit where it was processed. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. - /// @return Whether deposit was initiated on zkSync Era before Shared Bridge upgrade. - function _isEraLegacyDeposit( + /// @dev send the burn message to the asset + /// @notice Forwards the burn request for specific asset to respective asset handler + /// @param _chainId The chain ID of the ZK chain to which deposit. + /// @param _l2Value The L2 `msg.value` from the L1 -> L2 deposit transaction. + /// @param _assetId The deposited asset ID. + /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. + /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. + /// @param _passValue Boolean indicating whether to pass msg.value in the call. + /// @return bridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. + function _burn( uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2TxNumberInBatch - ) internal view returns (bool) { - require( - (_chainId != ERA_CHAIN_ID) || (eraLegacyBridgeLastDepositBatch != 0), - "L1AR: last deposit time not set for Era" - ); - return - (_chainId == ERA_CHAIN_ID) && - (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || - (_l2TxNumberInBatch < eraLegacyBridgeLastDepositTxNumber && - _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); - } + uint256 _l2Value, + bytes32 _assetId, + address _prevMsgSender, + bytes memory _transferData, + bool _passValue + ) internal returns (bytes memory bridgeMintCalldata) { + address l1AssetHandler = assetHandlerAddress[_assetId]; + require(l1AssetHandler != address(0), "ShB: asset handler does not exist for assetId"); - /// @notice Finalize the withdrawal and release funds. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent. - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message. - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization. - function finalizeWithdrawal( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external override { - _finalizeWithdrawal({ + uint256 msgValue = _passValue ? msg.value : 0; + bridgeMintCalldata = IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msgValue}({ _chainId: _chainId, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof + _l2Value: _l2Value, + _assetId: _assetId, + _prevMsgSender: _prevMsgSender, + _data: _transferData }); } @@ -663,9 +565,10 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab ); isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex] = true; - // Handling special case for withdrawal from zkSync Era initiated before Shared Bridge. + // Handling special case for withdrawal from ZKsync Era initiated before Shared Bridge. require(!_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy eth withdrawal"); require(!_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy token withdrawal"); + bytes memory transferData; { MessageParams memory messageParams = MessageParams({ @@ -683,6 +586,172 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab emit WithdrawalFinalizedSharedBridge(_chainId, l1Receiver, assetId, amount); } + /// @notice Decodes the transfer input for legacy data and transfers allowance to NTV + /// @dev Is not applicable for custom asset handlers + /// @param _data encoded transfer data (address _l1Token, uint256 _depositAmount, address _l2Receiver) + /// @param _prevMsgSender address of the deposit initiator + function _handleLegacyData(bytes calldata _data, address _prevMsgSender) internal returns (bytes32, bytes memory) { + (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( + _data, + (address, uint256, address) + ); + bytes32 assetId = _ensureTokenRegisteredWithNTV(_l1Token); + _transferAllowanceToNTV(assetId, _depositAmount, _prevMsgSender); + return (assetId, abi.encode(_depositAmount, _l2Receiver)); + } + + function _ensureTokenRegisteredWithNTV(address _l1Token) internal returns (bytes32 assetId) { + assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); + if (nativeTokenVault.tokenAddress(assetId) == address(0)) { + nativeTokenVault.registerToken(_l1Token); + } + } + + /// @notice Transfers allowance to Native Token Vault, if the asset is registered with it. Does nothing for ETH or non-registered tokens. + /// @dev assetId is not the padded address, but the correct encoded id (NTV stores respective format for IDs) + function _transferAllowanceToNTV(bytes32 _assetId, uint256 _amount, address _prevMsgSender) internal { + address l1TokenAddress = nativeTokenVault.tokenAddress(_assetId); + if (l1TokenAddress == address(0) || l1TokenAddress == ETH_TOKEN_ADDRESS) { + return; + } + IERC20 l1Token = IERC20(l1TokenAddress); + + // Do the transfer if allowance to Shared bridge is bigger than amount + // And if there is not enough allowance for the NTV + if ( + l1Token.allowance(_prevMsgSender, address(this)) >= _amount && + l1Token.allowance(_prevMsgSender, address(nativeTokenVault)) < _amount + ) { + // slither-disable-next-line arbitrary-send-erc20 + l1Token.safeTransferFrom(_prevMsgSender, address(this), _amount); + l1Token.forceApprove(address(nativeTokenVault), _amount); + } + } + + /// @dev The request data that is passed to the bridgehub + function _requestToBridge( + address _prevMsgSender, + bytes32 _assetId, + bytes memory _bridgeMintCalldata, + bytes32 _txDataHash + ) internal view returns (L2TransactionRequestTwoBridgesInner memory request) { + // Request the finalization of the deposit on the L2 side + bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, _bridgeMintCalldata); + + request = L2TransactionRequestTwoBridgesInner({ + magicValue: TWO_BRIDGES_MAGIC_VALUE, + l2Contract: L2_ASSET_ROUTER_ADDR, + l2Calldata: l2TxCalldata, + factoryDeps: new bytes[](0), + txDataHash: _txDataHash + }); + } + + /// @dev Generate a calldata for calling the deposit finalization on the L2 bridge contract + function _getDepositL2Calldata( + address _l1Sender, + bytes32 _assetId, + bytes memory _assetData + ) internal view returns (bytes memory) { + // First branch covers the case when asset is not registered with NTV (custom asset handler) + // Second branch handles tokens registered with NTV and uses legacy calldata encoding + if (nativeTokenVault.tokenAddress(_assetId) == address(0)) { + return abi.encodeCall(IL2Bridge.finalizeDeposit, (_assetId, _assetData)); + } else { + // slither-disable-next-line unused-return + (, address _l2Receiver, address _parsedL1Token, uint256 _amount, bytes memory _gettersData) = DataEncoding + .decodeBridgeMintData(_assetData); + return + abi.encodeCall( + IL2BridgeLegacy.finalizeDeposit, + (_l1Sender, _l2Receiver, _parsedL1Token, _amount, _gettersData) + ); + } + } + + /// @dev Determines if an eth withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. + /// @param _chainId The chain ID of the transaction to check. + /// @param _l2BatchNumber The L2 batch number for the withdrawal. + /// @return Whether withdrawal was initiated on ZKsync Era before diamond proxy upgrade. + function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { + require((_chainId != ERA_CHAIN_ID) || eraPostDiamondUpgradeFirstBatch != 0, "L1AR: diamondUFB not set for Era"); + return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); + } + + /// @dev Determines if a token withdrawal was initiated on ZKsync Era before the upgrade to the Shared Bridge. + /// @param _chainId The chain ID of the transaction to check. + /// @param _l2BatchNumber The L2 batch number for the withdrawal. + /// @return Whether withdrawal was initiated on ZKsync Era before Legacy Bridge upgrade. + function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { + require( + (_chainId != ERA_CHAIN_ID) || eraPostLegacyBridgeUpgradeFirstBatch != 0, + "L1AR: LegacyUFB not set for Era" + ); + return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); + } + + /// @dev Determines if the provided data for a failed deposit corresponds to a legacy failed deposit. + /// @param _prevMsgSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @param _expectedTxDataHash The nullifier data hash stored for the failed deposit. + /// @return isLegacyTxDataHash True if the transaction is legacy, false otherwise. + function _isLegacyTxDataHash( + address _prevMsgSender, + bytes32 _assetId, + bytes memory _transferData, + bytes32 _expectedTxDataHash + ) internal view returns (bool isLegacyTxDataHash) { + try this.encodeTxDataHash(true, _prevMsgSender, _assetId, _transferData) returns (bytes32 txDataHash) { + return txDataHash == _expectedTxDataHash; + } catch { + return false; + } + } + + /// @dev Encodes the transaction data hash using either the latest encoding standard or the legacy standard. + /// @param _isLegacyEncoding Boolean flag indicating whether to use the legacy encoding standard (true) or the latest encoding standard (false). + /// @param _prevMsgSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @return txDataHash The resulting encoded transaction data hash. + function _encodeTxDataHash( + bool _isLegacyEncoding, + address _prevMsgSender, + bytes32 _assetId, + bytes memory _transferData + ) internal view returns (bytes32 txDataHash) { + if (_isLegacyEncoding) { + (uint256 depositAmount, ) = abi.decode(_transferData, (uint256, address)); + txDataHash = keccak256(abi.encode(_prevMsgSender, nativeTokenVault.tokenAddress(_assetId), depositAmount)); + } else { + // Similarly to calldata, the txDataHash is collision-resistant. + // In the legacy data hash, the first encoded variable was the address, which is padded with zeros during `abi.encode`. + txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(_prevMsgSender, _assetId, _transferData))); + } + } + + /// @dev Determines if a deposit was initiated on zkSync Era before the upgrade to the Shared Bridge. + /// @param _chainId The chain ID of the transaction to check. + /// @param _l2BatchNumber The L2 batch number for the deposit where it was processed. + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. + /// @return Whether deposit was initiated on ZKsync Era before Shared Bridge upgrade. + function _isEraLegacyDeposit( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2TxNumberInBatch + ) internal view returns (bool) { + require( + (_chainId != ERA_CHAIN_ID) || (eraLegacyBridgeLastDepositBatch != 0), + "L1AR: last deposit time not set for Era" + ); + return + (_chainId == ERA_CHAIN_ID) && + (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || + (_l2TxNumberInBatch <= eraLegacyBridgeLastDepositTxNumber && + _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); + } + /// @notice Verifies the validity of a withdrawal message from L2 and returns withdrawal details. /// @param _chainId The chain ID of the transaction to check. /// @param _messageParams The message params, which include batch number, message index, and L2 tx number in batch. @@ -731,28 +800,32 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes memory _l2ToL1message ) internal view returns (bytes32 assetId, bytes memory transferData) { // We check that the message is long enough to read the data. - // Please note that there are two versions of the message: - // 1. The message that is sent by `withdraw(address _l1Receiver)` + // Please note that there are three versions of the message: + // 1. The message that is sent by `withdraw(address _l1Receiver)` or `withdrawWithMessage`. In the second case, this function ignores the extra data // It should be equal to the length of the bytes4 function signature + address l1Receiver + uint256 amount = 4 + 20 + 32 = 56 (bytes). - // 2. The message that is encoded by `getL1WithdrawMessage(bytes32 _assetId, bytes memory _bridgeMintData)` - // No length is assume. The assetId is decoded and the mintData is passed to respective assetHandler - - // So the data is expected to be at least 56 bytes long. - require(_l2ToL1message.length >= 56, "L1AR: wrong msg len"); // wrong message length - uint256 amount; - address l1Receiver; + // 2. The legacy `getL1WithdrawMessage`, the length of the data is known. + // 3. The message that is encoded by `getL1WithdrawMessage(bytes32 _assetId, bytes memory _bridgeMintData)` + // No length is assumed. The assetId is decoded and the mintData is passed to respective assetHandler (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0); if (bytes4(functionSignature) == IMailbox.finalizeEthWithdrawal.selector) { + uint256 amount; + address l1Receiver; + + // The data is expected to be at least 56 bytes long. + require(_l2ToL1message.length >= 56, "L1AR: wrong msg len"); // wrong message length // this message is a base token withdrawal (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); + // slither-disable-next-line unused-return + (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); assetId = BRIDGE_HUB.baseTokenAssetId(_chainId); transferData = abi.encode(amount, l1Receiver); } else if (bytes4(functionSignature) == IL1ERC20Bridge.finalizeWithdrawal.selector) { - // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. address l1Token; - // this message is a token withdrawal + uint256 amount; + address l1Receiver; + // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. + // This message is a token withdrawal // Check that the message length is correct. // It should be equal to the length of the function signature + address + address + uint256 = 4 + 20 + 20 + 32 = @@ -760,12 +833,14 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab require(_l2ToL1message.length == 76, "L1AR: wrong msg len 2"); (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); + // slither-disable-next-line unused-return + (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); - assetId = keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, l1Token)); + assetId = DataEncoding.encodeNTVAssetId(block.chainid, l1Token); transferData = abi.encode(amount, l1Receiver); } else if (bytes4(functionSignature) == this.finalizeWithdrawal.selector) { - //todo + // The data is expected to be at least 36 bytes long to contain assetId. + require(_l2ToL1message.length >= 36, "L1AR: wrong msg len"); // wrong message length (assetId, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); transferData = UnsafeBytes.readRemainingBytes(_l2ToL1message, offset); } else { @@ -773,30 +848,15 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab } } - /// @notice Receives and parses (name, symbol, decimals) from the token contract. - /// @param _token The address of token of interest. - /// @return Returns encoded name, symbol, and decimals for specific token. - function getERC20Getters(address _token) public view returns (bytes memory) { - if (_token == ETH_TOKEN_ADDRESS) { - bytes memory name = bytes("Ether"); - bytes memory symbol = bytes("ETH"); - bytes memory decimals = abi.encode(uint8(18)); - return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 - } - - (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); - (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); - (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); - return abi.encode(data1, data2, data3); - } - /*////////////////////////////////////////////////////////////// SHARED BRIDGE TOKEN BRIDGING LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ - /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @dev Cannot be used to claim deposits made with new encoding. + /// @param _chainId The ZK chain id to which deposit was initiated. /// @param _depositSender The address of the deposit initiator. - /// @param _l1Token The address of the deposited L1 ERC20 token. + /// @param _l1Asset The address of the deposited L1 ERC20 token. /// @param _amount The amount of the deposit that failed. /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. @@ -806,7 +866,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab function claimFailedDeposit( uint256 _chainId, address _depositSender, - address _l1Token, + address _l1Asset, uint256 _amount, bytes32 _l2TxHash, uint256 _l2BatchNumber, @@ -814,13 +874,14 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint16 _l2TxNumberInBatch, bytes32[] calldata _merkleProof ) external override { - bytes32 assetId = nativeTokenVault.getAssetId(_l1Token); - bytes memory transferData = abi.encode(_amount, _depositSender); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Asset); + // For legacy deposits, the l2 receiver is not required to check tx data hash + bytes memory transferData = abi.encode(_amount, address(0)); bridgeRecoverFailedTransfer({ _chainId: _chainId, _depositSender: _depositSender, _assetId: assetId, - _transferData: transferData, + _assetData: transferData, _l2TxHash: _l2TxHash, _l2BatchNumber: _l2BatchNumber, _l2MessageIndex: _l2MessageIndex, @@ -857,7 +918,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and /// the funds would be lost. - /// @return l2TxHash The L2 transaction hash of deposit finalization. + /// @return txHash The L2 transaction hash of deposit finalization. function depositLegacyErc20Bridge( address _prevMsgSender, address _l2Receiver, @@ -866,28 +927,31 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte, address _refundRecipient - ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 l2TxHash) { + ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 txHash) { require(_l1Token != L1_WETH_TOKEN, "L1AR: WETH deposit not supported 2"); bytes32 _assetId; - bytes memory l2BridgeMintCalldata; + bytes memory bridgeMintCalldata; { // Inner call to encode data to decrease local var numbers _assetId = _ensureTokenRegisteredWithNTV(_l1Token); + IERC20(_l1Token).forceApprove(address(nativeTokenVault), _amount); + } - // solhint-disable-next-line func-named-parameters - l2BridgeMintCalldata = abi.encode( - _amount, - _prevMsgSender, - _l2Receiver, - getERC20Getters(_l1Token), - _l1Token - ); + { + bridgeMintCalldata = _burn({ + _chainId: ERA_CHAIN_ID, + _l2Value: 0, + _assetId: _assetId, + _prevMsgSender: _prevMsgSender, + _transferData: abi.encode(_amount, _l2Receiver), + _passValue: false + }); } { - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, l2BridgeMintCalldata); + bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, bridgeMintCalldata); // If the refund recipient is not specified, the refund will be sent to the sender of the transaction. // Otherwise, the refund will be sent to the specified address. @@ -897,7 +961,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ chainId: ERA_CHAIN_ID, l2Contract: L2_ASSET_ROUTER_ADDR, - mintValue: msg.value, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the base token bridge for gas + mintValue: msg.value, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the shared bridge (base token bridge) for gas l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub l2Calldata: l2TxCalldata, l2GasLimit: _l2TxGasLimit, @@ -905,15 +969,15 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab factoryDeps: new bytes[](0), refundRecipient: refundRecipient }); - l2TxHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); + txHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); } // Save the deposited amount to claim funds on L1 if the deposit failed on L2 - depositHappened[ERA_CHAIN_ID][l2TxHash] = keccak256(abi.encode(_prevMsgSender, _l1Token, _amount)); + depositHappened[ERA_CHAIN_ID][txHash] = keccak256(abi.encode(_prevMsgSender, _l1Token, _amount)); emit LegacyDepositInitiated({ chainId: ERA_CHAIN_ID, - l2DepositTxHash: l2TxHash, + l2DepositTxHash: txHash, from: _prevMsgSender, to: _l2Receiver, l1Asset: _l1Token, @@ -950,42 +1014,6 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab l1Asset = nativeTokenVault.tokenAddress(assetId); } - /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on zkSync Era chain. - /// This function is specifically designed for maintaining backward-compatibility with legacy `claimFailedDeposit` - /// method in `L1ERC20Bridge`. - /// - /// @param _depositSender The address of the deposit initiator. - /// @param _l1Asset The address of the deposited L1 ERC20 token. - /// @param _amount The amount of the deposit that failed. - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent. - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization. - function claimFailedDepositLegacyErc20Bridge( - address _depositSender, - address _l1Asset, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external override onlyLegacyBridge { - bytes memory transferData = abi.encode(_amount, _depositSender); - bridgeRecoverFailedTransfer({ - _chainId: ERA_CHAIN_ID, - _depositSender: _depositSender, - _assetId: nativeTokenVault.getAssetId(_l1Asset), - _transferData: transferData, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - } - /*////////////////////////////////////////////////////////////// PAUSE //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol index 2200d358a..c0065916a 100644 --- a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol @@ -17,7 +17,7 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to ZK chains -/// @dev It is a legacy bridge from zkSync Era, that was deprecated in favour of shared bridge. +/// @dev It is a legacy bridge from ZKsync Era, that was deprecated in favour of shared bridge. /// It is needed for backward compatibility with already integrated projects. contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { using SafeERC20 for IERC20; @@ -28,26 +28,29 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev The native token vault, which holds deposited tokens. IL1NativeTokenVault public immutable override NATIVE_TOKEN_VAULT; + /// @dev The chainId of Era + uint256 public immutable ERA_CHAIN_ID; + /// @dev A mapping L2 batch number => message number => flag. - /// @dev Used to indicate that L2 -> L1 message was already processed for zkSync Era withdrawals. + /// @dev Used to indicate that L2 -> L1 message was already processed for ZKsync Era withdrawals. // slither-disable-next-line uninitialized-state mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized)) public isWithdrawalFinalized; /// @dev A mapping account => L1 token address => L2 deposit transaction hash => amount. - /// @dev Used for saving the number of deposited funds, to claim them in case the deposit transaction will fail in zkSync Era. + /// @dev Used for saving the number of deposited funds, to claim them in case the deposit transaction will fail in ZKsync Era. mapping(address account => mapping(address l1Token => mapping(bytes32 depositL2TxHash => uint256 amount))) public depositAmount; - /// @dev The address that is used as a L2 native token vault in zkSync Era. + /// @dev The address that is used as a L2 Shared Bridge in ZKsync Era. // slither-disable-next-line uninitialized-state - address public l2NativeTokenVault; + address public l2Bridge; - /// @dev The address that is used as a beacon for L2 tokens in zkSync Era. + /// @dev The address that is used as a beacon for L2 tokens in ZKsync Era. // slither-disable-next-line uninitialized-state address public l2TokenBeacon; - /// @dev Stores the hash of the L2 token proxy contract's bytecode on zkSync Era. + /// @dev Stores the hash of the L2 token proxy contract's bytecode on ZKsync Era. // slither-disable-next-line uninitialized-state bytes32 public l2TokenProxyBytecodeHash; @@ -62,30 +65,52 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. - constructor(IL1AssetRouter _sharedBridge, IL1NativeTokenVault _nativeTokenVault) reentrancyGuardInitializer { + constructor( + IL1AssetRouter _sharedBridge, + IL1NativeTokenVault _nativeTokenVault, + uint256 _eraChainId + ) reentrancyGuardInitializer { SHARED_BRIDGE = _sharedBridge; NATIVE_TOKEN_VAULT = _nativeTokenVault; + ERA_CHAIN_ID = _eraChainId; } /// @dev Initializes the reentrancy guard. Expected to be used in the proxy. function initialize() external reentrancyGuardInitializer {} - /*////////////////////////////////////////////////////////////// - ERA LEGACY GETTERS - //////////////////////////////////////////////////////////////*/ - - /// @return The L2 token address that would be minted for deposit of the given L1 token on zkSync Era. - function l2TokenAddress(address _l1Token) external view returns (address) { - bytes32 constructorInputHash = keccak256(abi.encode(l2TokenBeacon, "")); - bytes32 salt = bytes32(uint256(uint160(_l1Token))); + /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @param _depositSender The address of the deposit initiator + /// @param _l1Token The address of the deposited L1 ERC20 token + /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization + /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent + /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization + function claimFailedDeposit( + address _depositSender, + address _l1Token, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external nonReentrant { + uint256 amount = depositAmount[_depositSender][_l1Token][_l2TxHash]; + require(amount != 0, "2T"); // empty deposit + delete depositAmount[_depositSender][_l1Token][_l2TxHash]; - return - L2ContractHelper.computeCreate2Address( - l2NativeTokenVault, - salt, - l2TokenProxyBytecodeHash, - constructorInputHash - ); + SHARED_BRIDGE.claimFailedDeposit({ + _chainId: ERA_CHAIN_ID, + _depositSender: _depositSender, + _l1Token: _l1Token, + _amount: amount, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + emit ClaimedFailedDeposit(_depositSender, _l1Token, amount); } /*////////////////////////////////////////////////////////////// @@ -102,7 +127,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @param _amount The total amount of tokens to be bridged /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction - /// @return l2TxHash The L2 transaction hash of deposit finalization + /// @return txHash The L2 transaction hash of deposit finalization /// NOTE: the function doesn't use `nonreentrant` modifier, because the inner method does. function deposit( address _l2Receiver, @@ -110,8 +135,8 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { uint256 _amount, uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte - ) external payable returns (bytes32 l2TxHash) { - l2TxHash = deposit({ + ) external payable returns (bytes32 txHash) { + txHash = deposit({ _l2Receiver: _l2Receiver, _l1Token: _l1Token, _amount: _amount, @@ -121,6 +146,32 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { }); } + /// @notice Finalize the withdrawal and release funds + /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _message The L2 withdraw data, stored in an L2 -> L1 message + /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization + function finalizeWithdrawal( + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external nonReentrant { + require(!isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "pw"); + // We don't need to set finalizeWithdrawal here, as we set it in the shared bridge + + (address l1Receiver, address l1Token, uint256 amount) = SHARED_BRIDGE.finalizeWithdrawalLegacyErc20Bridge({ + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _message: _message, + _merkleProof: _merkleProof + }); + emit WithdrawalFinalized(l1Receiver, l1Token, amount); + } + /// @notice Initiates a deposit by locking funds on the contract and sending the request /// @dev Initiates a deposit by locking funds on the contract and sending the request /// of processing an L2 transaction where tokens would be minted @@ -145,7 +196,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and /// the funds would be lost. - /// @return l2TxHash The L2 transaction hash of deposit finalization + /// @return txHash The L2 transaction hash of deposit finalization function deposit( address _l2Receiver, address _l1Token, @@ -153,13 +204,13 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte, address _refundRecipient - ) public payable nonReentrant returns (bytes32 l2TxHash) { + ) public payable nonReentrant returns (bytes32 txHash) { require(_amount != 0, "0T"); // empty deposit - uint256 amount = _depositFundsToNTV(msg.sender, IERC20(_l1Token), _amount); + uint256 amount = _depositFundsToSharedBridge(msg.sender, IERC20(_l1Token), _amount); require(amount == _amount, "3T"); // The token has non-standard transfer logic - l2TxHash = SHARED_BRIDGE.depositLegacyErc20Bridge{value: msg.value}({ - _msgSender: msg.sender, + txHash = SHARED_BRIDGE.depositLegacyErc20Bridge{value: msg.value}({ + _prevMsgSender: msg.sender, _l2Receiver: _l2Receiver, _l1Token: _l1Token, _amount: _amount, @@ -167,83 +218,34 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { _l2TxGasPerPubdataByte: _l2TxGasPerPubdataByte, _refundRecipient: _refundRecipient }); - depositAmount[msg.sender][_l1Token][l2TxHash] = _amount; - // solhint-disable-next-line func-named-parameters - emit DepositInitiated(l2TxHash, msg.sender, _l2Receiver, _l1Token, _amount); + depositAmount[msg.sender][_l1Token][txHash] = _amount; + emit DepositInitiated({ + l2DepositTxHash: txHash, + from: msg.sender, + to: _l2Receiver, + l1Token: _l1Token, + amount: _amount + }); } - /// @dev Transfers tokens from the depositor address to the native token vault address. + /// @dev Transfers tokens from the depositor address to the shared bridge address. /// @return The difference between the contract balance before and after the transferring of funds. - function _depositFundsToNTV(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(NATIVE_TOKEN_VAULT)); - _token.safeTransferFrom(_from, address(NATIVE_TOKEN_VAULT), _amount); - uint256 balanceAfter = _token.balanceOf(address(NATIVE_TOKEN_VAULT)); - + function _depositFundsToSharedBridge(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(SHARED_BRIDGE)); + _token.safeTransferFrom(_from, address(SHARED_BRIDGE), _amount); + uint256 balanceAfter = _token.balanceOf(address(SHARED_BRIDGE)); return balanceAfter - balanceBefore; } - /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. - /// @param _depositSender The address of the deposit initiator - /// @param _l1Token The address of the deposited L1 ERC20 token - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization - function claimFailedDeposit( - address _depositSender, - address _l1Token, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external nonReentrant { - uint256 amount = depositAmount[_depositSender][_l1Token][_l2TxHash]; - require(amount != 0, "2T"); // empty deposit - delete depositAmount[_depositSender][_l1Token][_l2TxHash]; - - SHARED_BRIDGE.claimFailedDepositLegacyErc20Bridge({ - _depositSender: _depositSender, - _l1Token: _l1Token, - _amount: amount, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - emit ClaimedFailedDeposit(_depositSender, _l1Token, amount); - } - - /// @notice Finalize the withdrawal and release funds - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization - function finalizeWithdrawal( - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external nonReentrant { - require(!isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "pw"); - // We don't need to set finalizeWithdrawal here, as we set it in the shared bridge + /*////////////////////////////////////////////////////////////// + ERA LEGACY GETTERS + //////////////////////////////////////////////////////////////*/ - (address l1Receiver, address l1Token, uint256 amount) = SHARED_BRIDGE.finalizeWithdrawalLegacyErc20Bridge({ - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof - }); - emit WithdrawalFinalized(l1Receiver, l1Token, amount); - } + /// @return The L2 token address that would be minted for deposit of the given L1 token on zkSync Era. + function l2TokenAddress(address _l1Token) external view returns (address) { + bytes32 constructorInputHash = keccak256(abi.encode(l2TokenBeacon, "")); + bytes32 salt = bytes32(uint256(uint160(_l1Token))); - /// @notice View-only function for backward compatibility - function l2Bridge() external view returns (address) { - return l2NativeTokenVault; + return L2ContractHelper.computeCreate2Address(l2Bridge, salt, l2TokenProxyBytecodeHash, constructorInputHash); } } diff --git a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol index 12f6c1107..1d8cd0973 100644 --- a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol @@ -7,7 +7,6 @@ pragma solidity 0.8.24; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; -import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -16,7 +15,9 @@ import {IL1AssetHandler} from "./interfaces/IL1AssetHandler.sol"; import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; import {ETH_TOKEN_ADDRESS} from "../common/Config.sol"; -import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; + +import {BridgeHelper} from "./BridgeHelper.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -31,9 +32,6 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste /// @dev L1 Shared Bridge smart contract that handles communication with its counterparts on L2s IL1AssetRouter public immutable override L1_SHARED_BRIDGE; - /// @dev Era's chainID - uint256 public immutable ERA_CHAIN_ID; - /// @dev Maps token balances for each chain to prevent unauthorized spending across ZK chains. /// This serves as a security measure until hyperbridging is implemented. /// NOTE: this function may be removed in the future, don't rely on it! @@ -42,40 +40,33 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste /// @dev A mapping assetId => tokenAddress mapping(bytes32 assetId => address tokenAddress) public tokenAddress; - /// @notice Checks that the message sender is the bridgehub. + /// @notice Checks that the message sender is the bridge. modifier onlyBridge() { require(msg.sender == address(L1_SHARED_BRIDGE), "NTV not ShB"); _; } - /// @notice Checks that the message sender is the native token vault itself. - modifier onlySelf() { - require(msg.sender == address(this), "NTV only"); - _; - } - /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. - constructor(address _l1WethAddress, IL1AssetRouter _l1SharedBridge, uint256 _eraChainId) { + constructor(address _l1WethAddress, IL1AssetRouter _l1SharedBridge) { _disableInitializers(); L1_WETH_TOKEN = _l1WethAddress; - ERA_CHAIN_ID = _eraChainId; L1_SHARED_BRIDGE = _l1SharedBridge; } - /// @dev Initializes a contract for later use. Expected to be used in the proxy. - /// @param _owner Address which can change pause / unpause the NTV. + /// @dev Accepts ether only from the Shared Bridge. + receive() external payable { + require(address(L1_SHARED_BRIDGE) == msg.sender, "NTV: ETH only accepted from Shared Bridge"); + } + + /// @dev Initializes a contract for later use. Expected to be used in the proxy + /// @param _owner Address which can change pause / unpause the NTV /// implementation. The owner is the Governor and separate from the ProxyAdmin from now on, so that the Governor can call the bridge. function initialize(address _owner) external initializer { require(_owner != address(0), "NTV owner 0"); _transferOwnership(_owner); } - /// @dev Accepts ether only from the Shared Bridge. - receive() external payable { - require(address(L1_SHARED_BRIDGE) == msg.sender, "NTV: ETH only accepted from Shared Bridge"); - } - /// @notice Transfers tokens from shared bridge as part of the migration process. /// @dev Both ETH and ERC20 tokens can be transferred. Exhausts balance of shared bridge after the first call. /// @dev Calling second time for the same token will revert. @@ -100,10 +91,10 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste /// @dev Clears chain balance on the shared bridge after the first call. Subsequent calls will not affect the state. /// @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 transferBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { + function updateChainBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { uint256 sharedBridgeChainBalance = L1_SHARED_BRIDGE.chainBalance(_targetChainId, _token); chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + sharedBridgeChainBalance; - L1_SHARED_BRIDGE.clearChainBalance(_targetChainId, _token); + L1_SHARED_BRIDGE.nullifyChainBalanceByNTV(_targetChainId, _token); } /// @notice Registers tokens within the NTV. @@ -113,11 +104,39 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste function registerToken(address _l1Token) external { require(_l1Token != L1_WETH_TOKEN, "NTV: WETH deposit not supported"); require(_l1Token == ETH_TOKEN_ADDRESS || _l1Token.code.length > 0, "NTV: empty token"); - bytes32 assetId = getAssetId(_l1Token); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); L1_SHARED_BRIDGE.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_l1Token))), address(this)); tokenAddress[assetId] = _l1Token; } + /// @inheritdoc IL1AssetHandler + function bridgeMint( + uint256 _chainId, + bytes32 _assetId, + bytes calldata _data + ) external payable override onlyBridge whenNotPaused returns (address l1Receiver) { + // here we are minting the tokens after the bridgeBurn has happened on an L2, so we can assume the l1Token is not zero + address l1Token = tokenAddress[_assetId]; + uint256 amount; + (amount, l1Receiver) = abi.decode(_data, (uint256, address)); + // Check that the chain has sufficient balance + require(chainBalance[_chainId][l1Token] >= amount, "NTV: not enough funds"); // not enough funds + chainBalance[_chainId][l1Token] -= amount; + + if (l1Token == ETH_TOKEN_ADDRESS) { + bool callSuccess; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), l1Receiver, amount, 0, 0, 0, 0) + } + require(callSuccess, "NTV: withdrawal failed, no funds or cannot transfer to receiver"); + } else { + // Withdraw funds + IERC20(l1Token).safeTransfer(l1Receiver, amount); + } + emit BridgeMint(_chainId, _assetId, l1Receiver, amount); + } + /// @inheritdoc IL1AssetHandler /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. /// @dev In case of native token vault _data is the tuple of _depositAmount and _l2Receiver. @@ -154,92 +173,36 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste chainBalance[_chainId][l1Token] += amount; - // solhint-disable-next-line func-named-parameters - _bridgeMintData = abi.encode(amount, _prevMsgSender, _l2Receiver, getERC20Getters(l1Token), l1Token); - // solhint-disable-next-line func-named-parameters - emit BridgeBurn(_chainId, _assetId, _prevMsgSender, _l2Receiver, amount); - } - - /// @notice Transfers tokens from the depositor address to the smart contract address. - /// @param _from The address of the depositor. - /// @param _token The ERC20 token to be transferred. - /// @param _amount The amount to be transferred. - /// @return The difference between the contract balance before and after the transferring of funds. - function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(this)); - address from = _from; - // in the legacy scenario the SharedBridge was granting the allowance, we have to transfer from them instead of the user - if ( - _token.allowance(address(L1_SHARED_BRIDGE), address(this)) >= _amount && - _token.allowance(_from, address(this)) < _amount - ) { - from = address(L1_SHARED_BRIDGE); - } - // slither-disable-next-line arbitrary-send-erc20 - _token.safeTransferFrom(from, address(this), _amount); - uint256 balanceAfter = _token.balanceOf(address(this)); - - return balanceAfter - balanceBefore; - } - - /// @notice Receives and parses (name, symbol, decimals) from the token contract. - /// @param _token The address of token of interest. - /// @return Returns encoded name, symbol, and decimals for specific token. - function getERC20Getters(address _token) public view returns (bytes memory) { - if (_token == ETH_TOKEN_ADDRESS) { - bytes memory name = bytes("Ether"); - bytes memory symbol = bytes("ETH"); - bytes memory decimals = abi.encode(uint8(18)); - return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 - } - - (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); - (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); - (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); - return abi.encode(data1, data2, data3); - } - - /// @inheritdoc IL1AssetHandler - function bridgeMint( - uint256 _chainId, - bytes32 _assetId, - bytes calldata _data - ) external payable override onlyBridge whenNotPaused returns (address l1Receiver) { - // here we are minting the tokens after the bridgeBurn has happened on an L2, so we can assume the l1Token is not zero - address l1Token = tokenAddress[_assetId]; - uint256 amount; - (amount, l1Receiver) = abi.decode(_data, (uint256, address)); - // Check that the chain has sufficient balance - require(chainBalance[_chainId][l1Token] >= amount, "NTV not enough funds 2"); // not enough funds - chainBalance[_chainId][l1Token] -= amount; - - if (l1Token == ETH_TOKEN_ADDRESS) { - bool callSuccess; - // Low-level assembly call, to avoid any memory copying (save gas) - assembly { - callSuccess := call(gas(), l1Receiver, amount, 0, 0, 0, 0) - } - require(callSuccess, "NTV: withdrawal failed, no funds or cannot transfer to receiver"); - } else { - // Withdraw funds - IERC20(l1Token).safeTransfer(l1Receiver, amount); - } - // solhint-disable-next-line func-named-parameters - emit BridgeMint(_chainId, _assetId, l1Receiver, amount); + _bridgeMintData = DataEncoding.encodeBridgeMintData({ + _prevMsgSender: _prevMsgSender, + _l2Receiver: _l2Receiver, + _l1Token: l1Token, + _amount: amount, + _erc20Metadata: getERC20Getters(l1Token) + }); + + emit BridgeBurn({ + chainId: _chainId, + assetId: _assetId, + l1Sender: _prevMsgSender, + l2receiver: _l2Receiver, + amount: amount + }); } /// @inheritdoc IL1AssetHandler function bridgeRecoverFailedTransfer( uint256 _chainId, bytes32 _assetId, + address _depositSender, bytes calldata _data ) external payable override onlyBridge whenNotPaused { - (uint256 _amount, address _depositSender) = abi.decode(_data, (uint256, address)); + (uint256 _amount, ) = abi.decode(_data, (uint256, address)); address l1Token = tokenAddress[_assetId]; require(_amount > 0, "y1"); // check that the chain has sufficient balance - require(chainBalance[_chainId][l1Token] >= _amount, "NTV n funds"); + require(chainBalance[_chainId][l1Token] >= _amount, "NTV: not enough funds 2"); chainBalance[_chainId][l1Token] -= _amount; if (l1Token == ETH_TOKEN_ADDRESS) { @@ -256,11 +219,28 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste } } - /// @notice Returns the parsed assetId. - /// @param _l1TokenAddress The address of the token to be parsed. - /// @return The asset ID. - function getAssetId(address _l1TokenAddress) public view override returns (bytes32) { - return keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, _l1TokenAddress)); + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token) public view returns (bytes memory) { + return BridgeHelper.getERC20Getters(_token, ETH_TOKEN_ADDRESS); + } + + /// @dev Transfers tokens from the depositor address to the smart contract address. + /// @return The difference between the contract balance before and after the transferring of funds. + function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(this)); + address from = _from; + // in the legacy scenario the SharedBridge was granting the allowance, we have to transfer from them instead of the user + if ( + _token.allowance(address(L1_SHARED_BRIDGE), address(this)) >= _amount && + _token.allowance(_from, address(this)) < _amount + ) { + from = address(L1_SHARED_BRIDGE); + } + // slither-disable-next-line arbitrary-send-erc20 + _token.safeTransferFrom(from, address(this), _amount); + uint256 balanceAfter = _token.balanceOf(address(this)); + + return balanceAfter - balanceBefore; } /*////////////////////////////////////////////////////////////// diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol index 9df9a3b7a..a707da173 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol @@ -11,15 +11,15 @@ interface IL1AssetHandler { event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); /// @dev Emitted when a token is minted - event BridgeMint(uint256 indexed _chainId, bytes32 indexed _assetId, address _l1Receiver, uint256 _amount); + event BridgeMint(uint256 indexed chainId, bytes32 indexed assetId, address l1Receiver, uint256 amount); /// @dev Emitted when a token is burned event BridgeBurn( - uint256 indexed _chainId, - bytes32 indexed _assetId, + uint256 indexed chainId, + bytes32 indexed assetId, address indexed l1Sender, - address _l2receiver, - uint256 _amount + address l2receiver, + uint256 amount ); /// @param _chainId the chainId that the message is from @@ -32,13 +32,13 @@ interface IL1AssetHandler { ) external payable returns (address l1Receiver); /// @param _chainId the chainId that the message will be sent to - /// param mintValue the amount of base tokens to be minted on L2, will be used by Weth AssetHandler + /// @param _l2Value the msg.value of the L2 transaction /// @param _assetId the assetId of the asset being bridged /// @param _prevMsgSender the original caller of the Bridgehub, /// @param _data the actual data specified for the function function bridgeBurn( uint256 _chainId, - uint256 _mintValue, + uint256 _l2Value, bytes32 _assetId, address _prevMsgSender, bytes calldata _data @@ -46,6 +46,12 @@ interface IL1AssetHandler { /// @param _chainId the chainId that the message will be sent to /// @param _assetId the assetId of the asset being bridged + /// @param _depositSender the address of the entity that initiated the deposit. /// @param _data the actual data specified for the function - function bridgeRecoverFailedTransfer(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable; + function bridgeRecoverFailedTransfer( + uint256 _chainId, + bytes32 _assetId, + address _depositSender, + bytes calldata _data + ) external payable; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol index 9efd977fe..631cb8718 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol @@ -25,7 +25,7 @@ interface IL1AssetRouter { bytes32 indexed txDataHash, address indexed from, bytes32 assetId, - bytes l2BridgeMintCalldata + bytes bridgeMintCalldata ); event BridgehubDepositBaseTokenInitiated( @@ -57,6 +57,12 @@ interface IL1AssetRouter { bytes assetData ); + event AssetDeploymentTrackerSet( + bytes32 indexed assetId, + address indexed assetDeploymentTracker, + bytes32 indexed additionalData + ); + event AssetHandlerRegisteredInitial( bytes32 indexed assetId, address indexed assetHandlerAddress, @@ -64,16 +70,14 @@ interface IL1AssetRouter { address sender ); - event AssetHandlerRegistered(bytes32 indexed assetId, address indexed assetHandlerAddress); - function isWithdrawalFinalized( uint256 _chainId, uint256 _l2BatchNumber, - uint256 _l2MessageIndex + uint256 _l2ToL1MessageNumber ) external view returns (bool); function depositLegacyErc20Bridge( - address _msgSender, + address _prevMsgSender, address _l2Receiver, address _l1Token, uint256 _amount, @@ -82,17 +86,6 @@ interface IL1AssetRouter { address _refundRecipient ) external payable returns (bytes32 txHash); - function claimFailedDepositLegacyErc20Bridge( - address _depositSender, - address _l1Token, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external; - function claimFailedDeposit( uint256 _chainId, address _depositSender, @@ -111,7 +104,7 @@ interface IL1AssetRouter { uint16 _l2TxNumberInBatch, bytes calldata _message, bytes32[] calldata _merkleProof - ) external returns (address l1Receiver, address l1Token, uint256 amount); + ) external returns (address l1Receiver, address l1Asset, uint256 amount); function finalizeWithdrawal( uint256 _chainId, @@ -128,12 +121,15 @@ interface IL1AssetRouter { function legacyBridge() external view returns (IL1ERC20Bridge); - function depositHappened(uint256 _chainId, bytes32 _l2TxHash) external view returns (bytes32); + function depositHappened(uint256 _chainId, bytes32 _l2DepositTxHash) external view returns (bytes32); - /// data is abi encoded : + /// @dev Data has the following abi encoding for legacy deposits: /// address _l1Token, /// uint256 _amount, /// address _l2Receiver + /// for new deposits: + /// bytes32 _assetId, + /// bytes _transferData function bridgehubDeposit( uint256 _chainId, address _prevMsgSender, @@ -152,6 +148,8 @@ interface IL1AssetRouter { function hyperbridgingEnabled(uint256 _chainId) external view returns (bool); + function setAssetDeploymentTracker(bytes32 _assetRegistrationData, address _assetDeploymentTracker) external; + function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetHandlerAddress) external; function setAssetHandlerAddressOnCounterPart( @@ -174,7 +172,7 @@ interface IL1AssetRouter { uint256 _chainId, address _depositSender, bytes32 _assetId, - bytes calldata _tokenData, + bytes calldata _assetData, bytes32 _l2TxHash, uint256 _l2BatchNumber, uint256 _l2MessageIndex, @@ -182,9 +180,9 @@ interface IL1AssetRouter { bytes32[] calldata _merkleProof ) external; - function chainBalance(uint256 _chainId, address _token) external view returns (uint256); + function chainBalance(uint256 _chainId, address _l1Token) external view returns (uint256); function transferTokenToNTV(address _token) external; - function clearChainBalance(uint256 _chainId, address _token) external; + function nullifyChainBalanceByNTV(uint256 _chainId, address _token) external; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index 1ecb95165..2fcdef189 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -8,7 +8,7 @@ import {IL1NativeTokenVault} from "./IL1NativeTokenVault.sol"; /// @title L1 Bridge contract legacy interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @notice Legacy Bridge interface before ZK chain migration, used for backward compatibility with zkSync Era +/// @notice Legacy Bridge interface before ZK chain migration, used for backward compatibility with ZKsync Era interface IL1ERC20Bridge { event DepositInitiated( bytes32 indexed l2DepositTxHash, @@ -67,13 +67,11 @@ interface IL1ERC20Bridge { function l2TokenBeacon() external view returns (address); - function l2NativeTokenVault() external view returns (address); - function l2Bridge() external view returns (address); function depositAmount( address _account, address _l1Token, bytes32 _depositL2TxHash - ) external returns (uint256 amount); + ) external view returns (uint256 amount); } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol b/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol index 1f540ccf0..4572d8e01 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol @@ -18,12 +18,12 @@ interface IL1NativeTokenVault { /// @notice Used to register a token in the vault function registerToken(address _l1Token) external; - /// @notice Used to get the assetId of a token - function getAssetId(address l1TokenAddress) external view returns (bytes32); - - /// @notice Used to get the the ERC20 data for a token + /// @notice Used to get the ERC20 data for a token function getERC20Getters(address _token) external view returns (bytes memory); + /// @notice Used the get token balance for specific ZK chain in shared bridge + function chainBalance(uint256 _chainId, address _l1Token) external view returns (uint256); + /// @notice Used to get the token address of an assetId - function tokenAddress(bytes32 assetId) external view returns (address); + function tokenAddress(bytes32 _assetId) external view returns (address); } diff --git a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol index b39816edb..c0f404a5b 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol @@ -5,13 +5,9 @@ pragma solidity 0.8.24; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IL2Bridge { - function finalizeDeposit(bytes32 _assetId, bytes calldata _data) external; + function withdraw(bytes32 _assetId, bytes memory _assetData) external; - function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; - - function l1TokenAddress(address _l2Token) external view returns (address); - - function l2TokenAddress(address _l1Token) external view returns (address); + function finalizeDeposit(bytes32 _assetId, bytes calldata _transferData) external; function l1Bridge() external view returns (address); diff --git a/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol index ffa00afdb..b163262c7 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol @@ -5,6 +5,8 @@ pragma solidity 0.8.24; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IL2BridgeLegacy { + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + function finalizeDeposit( address _l1Sender, address _l2Receiver, @@ -12,4 +14,8 @@ interface IL2BridgeLegacy { uint256 _amount, bytes calldata _data ) external payable; + + function l1TokenAddress(address _l2Token) external view returns (address); + + function l2TokenAddress(address _l1Token) external view returns (address); } diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 3182e6ad5..e69b326d2 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -11,9 +11,10 @@ import {IBridgehub, L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOu import {IL1AssetRouter} from "../bridge/interfaces/IL1AssetRouter.sol"; import {IStateTransitionManager} from "../state-transition/IStateTransitionManager.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {IZkSyncHyperchain} from "../state-transition/chain-interfaces/IZkSyncHyperchain.sol"; + import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS} from "../common/Config.sol"; -import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {IMessageRoot} from "./IMessageRoot.sol"; @@ -36,15 +37,15 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus IL1AssetRouter public sharedBridge; /// @notice we store registered stateTransitionManagers - mapping(address stateTransitionManager => bool) public stateTransitionManagerIsRegistered; + mapping(address _stateTransitionManager => bool) public stateTransitionManagerIsRegistered; /// @notice we store registered tokens (for arbitrary base token) - mapping(address token => bool) public tokenIsRegistered; + mapping(address _baseToken => bool) public tokenIsRegistered; /// @notice chainID => StateTransitionManager contract address, storing StateTransitionManager - mapping(uint256 chainId => address) public stateTransitionManager; + mapping(uint256 _chainId => address) public stateTransitionManager; /// @notice chainID => baseToken contract address, storing baseToken - mapping(uint256 chainId => address) public baseToken; + mapping(uint256 _chainId => address) public baseToken; /// @dev used to manage non critical updates address public admin; @@ -59,7 +60,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus IMessageRoot public override messageRoot; /// @notice Mapping from chain id to encoding of the base token used for deposits / withdrawals - mapping(uint256 chainId => bytes32 baseTokenAssetId) public baseTokenAssetId; + mapping(uint256 _chainId => bytes32) public baseTokenAssetId; /// @notice The deployment tracker for the state transition managers. ISTMDeploymentTracker public stmDeployer; @@ -75,11 +76,21 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev Sync layer chain is expected to have .. as the base token. mapping(uint256 chainId => bool isWhitelistedSettlementLayer) public whitelistedSettlementLayers; + modifier onlyOwnerOrAdmin() { + require(msg.sender == admin || msg.sender == owner(), "BH: not owner or admin"); + _; + } + + modifier onlyChainSTM(uint256 _chainId) { + require(msg.sender == stateTransitionManager[_chainId], "BH: not chain STM"); + _; + } + /// @notice to avoid parity hack constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { _disableInitializers(); L1_CHAIN_ID = _l1ChainId; - ETH_TOKEN_ASSET_ID = keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, ETH_TOKEN_ADDRESS)); + ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); _transferOwnership(_owner); } @@ -90,17 +101,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _transferOwnership(_owner); } - modifier onlyOwnerOrAdmin() { - require(msg.sender == admin || msg.sender == owner(), "BH: not owner or admin"); - _; - } - - /// @param _chainId the chainId of the chain - modifier onlyChainSTM(uint256 _chainId) { - require(msg.sender == stateTransitionManager[_chainId], "BH: not chain STM"); - _; - } - modifier onlyAliasedZero() { /// There is no sender for the wrapping, we use a virtual address. require(msg.sender == VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, "BH: not aliased zero"); @@ -163,6 +163,8 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus "BH: state transition already registered" ); stateTransitionManagerIsRegistered[_stateTransitionManager] = true; + + emit StateTransitionManagerAdded(_stateTransitionManager); } /// @notice State Transition can be any contract with the appropriate interface/functionality @@ -171,13 +173,25 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus function removeStateTransitionManager(address _stateTransitionManager) external onlyOwner { require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered yet"); stateTransitionManagerIsRegistered[_stateTransitionManager] = false; + + emit StateTransitionManagerRemoved(_stateTransitionManager); } /// @notice token can be any contract with the appropriate interface/functionality - /// @param _token the token address to be added + /// @param _token address of base token to be registered function addToken(address _token) external onlyOwner { require(!tokenIsRegistered[_token], "BH: token already registered"); tokenIsRegistered[_token] = true; + + emit TokenRegistered(_token); + } + + /// @notice To set shared bridge, only Owner. Not done in initialize, as + /// the order of deployment is Bridgehub, Shared bridge, and then we call this + function setSharedBridge(address _sharedBridge) external onlyOwner { + sharedBridge = IL1AssetRouter(_sharedBridge); + + emit SharedBridgeUpdated(_sharedBridge); } /// @notice Used to register a chain as a settlement layer. @@ -202,27 +216,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus emit AssetRegistered(assetInfo, _assetAddress, _additionalData, msg.sender); } - ///// Getters - - /// @notice return the state transition chain contract for a chainId - /// @param _chainId the chainId of the chain - function getHyperchain(uint256 _chainId) public view returns (address) { - return IStateTransitionManager(stateTransitionManager[_chainId]).getHyperchain(_chainId); - } - - /// @notice return the stm asset id of a chain. - /// @param _chainId the chainId of the chain - function stmAssetIdFromChainId(uint256 _chainId) public view override returns (bytes32) { - return stmAssetId(stateTransitionManager[_chainId]); - } - - /// @notice return the stm asset id of an stm. - /// @param _stmAddress the stm address - function stmAssetId(address _stmAddress) public view override returns (bytes32) { - return keccak256(abi.encode(L1_CHAIN_ID, address(stmDeployer), bytes32(uint256(uint160(_stmAddress))))); - } - - /// New chain + /*////////////////////////////////////////////////////////////// + Chain Registration + //////////////////////////////////////////////////////////////*/ /// @notice register new chain /// @notice for Eth the baseToken address is 1 @@ -248,14 +244,15 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered"); require(tokenIsRegistered[_baseToken], "BH: token not registered"); - require(address(sharedBridge) != address(0), "BH: weth bridge not set"); + require(address(sharedBridge) != address(0), "BH: shared bridge not set"); require(stateTransitionManager[_chainId] == address(0), "BH: chainId already registered"); stateTransitionManager[_chainId] = _stateTransitionManager; baseToken[_chainId] = _baseToken; + /// For now all base tokens have to use the NTV. - baseTokenAssetId[_chainId] = sharedBridge.nativeTokenVault().getAssetId(_baseToken); + baseTokenAssetId[_chainId] = DataEncoding.encodeNTVAssetId(block.chainid, _baseToken); settlementLayer[_chainId] = block.chainid; IStateTransitionManager(_stateTransitionManager).createNewChain({ @@ -273,96 +270,31 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus } /*////////////////////////////////////////////////////////////// - Mailbox forwarder + Getters //////////////////////////////////////////////////////////////*/ - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L2 message inclusion. - /// @param _batchNumber The executed L2 batch number in which the message appeared - /// @param _index The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _message Information about the sent message: sender address, the message itself, tx index in the L2 batch where the message was sent - /// @param _proof Merkle proof for inclusion of L2 log that was sent with the message - /// @return Whether the proof is valid - function proveL2MessageInclusion( - uint256 _chainId, - uint256 _batchNumber, - uint256 _index, - L2Message calldata _message, - bytes32[] calldata _proof - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).proveL2MessageInclusion(_batchNumber, _index, _message, _proof); + /// @notice return the state transition chain contract for a chainId + function getHyperchain(uint256 _chainId) public view returns (address) { + return IStateTransitionManager(stateTransitionManager[_chainId]).getHyperchain(_chainId); } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L2 log inclusion. - /// @param _batchNumber The executed L2 batch number in which the log appeared - /// @param _index The position of the l2log in the L2 logs Merkle tree - /// @param _log Information about the sent log - /// @param _proof Merkle proof for inclusion of the L2 log - /// @return Whether the proof is correct and L2 log is included in batch - function proveL2LogInclusion( - uint256 _chainId, - uint256 _batchNumber, - uint256 _index, - L2Log calldata _log, - bytes32[] calldata _proof - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).proveL2LogInclusion(_batchNumber, _index, _log, _proof); + function stmAssetIdFromChainId(uint256 _chainId) public view override returns (bytes32) { + return stmAssetId(stateTransitionManager[_chainId]); } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L1->L2 tx status. - /// @param _l2TxHash The L2 canonical transaction hash - /// @param _l2BatchNumber The L2 batch number where the transaction was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction - /// @param _status The execution status of the L1 -> L2 transaction (true - success & 0 - fail) - /// @return Whether the proof is correct and the transaction was actually executed with provided status - /// NOTE: It may return `false` for incorrect proof, but it doesn't mean that the L1 -> L2 transaction has an opposite status! - function proveL1ToL2TransactionStatus( - uint256 _chainId, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof, - TxStatus _status - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return - IZkSyncHyperchain(hyperchain).proveL1ToL2TransactionStatus({ - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof, - _status: _status - }); + function stmAssetId(address _stmAddress) public view override returns (bytes32) { + return keccak256(abi.encode(L1_CHAIN_ID, address(stmDeployer), bytes32(uint256(uint160(_stmAddress))))); } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId the chainId of the chain - /// @param _gasPrice the gas price for the l2 priority operation - /// @param _l2GasLimit the gas limit for the l2 priority operation - /// @param _l2GasPerPubdataByteLimit the gas per pubdata byte limit for the l2 priority operation - function l2TransactionBaseCost( - uint256 _chainId, - uint256 _gasPrice, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit - ) external view returns (uint256) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); - } + /*////////////////////////////////////////////////////////////// + Mailbox forwarder + //////////////////////////////////////////////////////////////*/ /// @notice the mailbox is called directly after the sharedBridge received the deposit /// this assumes that either ether is the base token or - /// the msg.sender has approved mintValue allowance for the sharedBridge. - /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token - /// @param _request the request for the L2 transaction + /// the msg.sender has approved mintValue allowance for the nativeTokenVault. + /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token. + /// In case allowance is provided to the Shared Bridge, then it will be transferred to NTV. function requestL2TransactionDirect( L2TransactionRequestDirect calldata _request ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { @@ -405,8 +337,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice After depositing funds to the sharedBridge, the secondBridge is called /// to return the actual L2 message which is sent to the Mailbox. /// This assumes that either ether is the base token or - /// the msg.sender has approved the sharedBridge with the mintValue, + /// the msg.sender has approved the nativeTokenVault with the mintValue, /// and also the necessary approvals are given for the second bridge. + /// In case allowance is provided to the Shared Bridge, then it will be transferred to NTV. /// @notice The logic of this bridge is to allow easy depositing for bridges. /// Each contract that handles the users ERC20 tokens needs approvals from the user, this contract allows /// the user to approve for each token only its respective bridge @@ -498,6 +431,84 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus ); } + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the hyperchain where to prove L2 message inclusion. + /// @param _batchNumber The executed L2 batch number in which the message appeared + /// @param _index The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _message Information about the sent message: sender address, the message itself, tx index in the L2 batch where the message was sent + /// @param _proof Merkle proof for inclusion of L2 log that was sent with the message + /// @return Whether the proof is valid + function proveL2MessageInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Message calldata _message, + bytes32[] calldata _proof + ) external view override returns (bool) { + address hyperchain = getHyperchain(_chainId); + return IZkSyncHyperchain(hyperchain).proveL2MessageInclusion(_batchNumber, _index, _message, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the hyperchain where to prove L2 log inclusion. + /// @param _batchNumber The executed L2 batch number in which the log appeared + /// @param _index The position of the l2log in the L2 logs Merkle tree + /// @param _log Information about the sent log + /// @param _proof Merkle proof for inclusion of the L2 log + /// @return Whether the proof is correct and L2 log is included in batch + function proveL2LogInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Log calldata _log, + bytes32[] calldata _proof + ) external view override returns (bool) { + address hyperchain = getHyperchain(_chainId); + return IZkSyncHyperchain(hyperchain).proveL2LogInclusion(_batchNumber, _index, _log, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the hyperchain where to prove L1->L2 tx status. + /// @param _l2TxHash The L2 canonical transaction hash + /// @param _l2BatchNumber The L2 batch number where the transaction was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction + /// @param _status The execution status of the L1 -> L2 transaction (true - success & 0 - fail) + /// @return Whether the proof is correct and the transaction was actually executed with provided status + /// NOTE: It may return `false` for incorrect proof, but it doesn't mean that the L1 -> L2 transaction has an opposite status! + function proveL1ToL2TransactionStatus( + uint256 _chainId, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof, + TxStatus _status + ) external view override returns (bool) { + address hyperchain = getHyperchain(_chainId); + return + IZkSyncHyperchain(hyperchain).proveL1ToL2TransactionStatus({ + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof, + _status: _status + }); + } + + /// @notice forwards function call to Mailbox based on ChainId + function l2TransactionBaseCost( + uint256 _chainId, + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) external view returns (uint256) { + address hyperchain = getHyperchain(_chainId); + return IZkSyncHyperchain(hyperchain).l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); + } + /*////////////////////////////////////////////////////////////// Chain migration //////////////////////////////////////////////////////////////*/ @@ -569,6 +580,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus function bridgeRecoverFailedTransfer( uint256 _chainId, bytes32 _assetId, + address _depositSender, bytes calldata _data ) external payable override onlyAssetRouter {} diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 78d7a7b4b..1184ed068 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -58,7 +58,7 @@ interface IBridgehub is IL1AssetHandler { address sender ); - /// @notice Starts the transfer of admin rights. Only the current admin can propose a new pending one. + /// @notice Starts the transfer of admin rights. Only the current admin or owner can propose a new pending one. /// @notice New admin can accept admin rights by calling `acceptAdmin` function. /// @param _newPendingAdmin Address of the new admin function setPendingAdmin(address _newPendingAdmin) external; @@ -152,6 +152,14 @@ interface IBridgehub is IL1AssetHandler { event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); + event StateTransitionManagerAdded(address indexed stateTransitionManager); + + event StateTransitionManagerRemoved(address indexed stateTransitionManager); + + event TokenRegistered(address indexed token); + + event SharedBridgeUpdated(address indexed sharedBridge); + function whitelistedSettlementLayers(uint256 _chainId) external view returns (bool); function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external; diff --git a/l1-contracts/contracts/bridgehub/MessageRoot.sol b/l1-contracts/contracts/bridgehub/MessageRoot.sol index 7b03f62fe..9f70febd4 100644 --- a/l1-contracts/contracts/bridgehub/MessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/MessageRoot.sol @@ -30,12 +30,15 @@ bytes32 constant SHARED_ROOT_TREE_EMPTY_HASH = bytes32( /// @custom:security-contact security@matterlabs.dev /// @dev The MessageRoot contract is responsible for storing the cross message roots of the chains and the aggregated root of all chains. contract MessageRoot is IMessageRoot, ReentrancyGuard { + using FullMerkle for FullMerkle.FullTree; + using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + event AddedChain(uint256 indexed chainId, uint256 indexed chainIndex); event AppendedChainBatchRoot(uint256 indexed chainId, uint256 indexed batchNumber, bytes32 batchRoot); - using FullMerkle for FullMerkle.FullTree; - using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + event Preimage(bytes32 one, bytes32 two); + /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. IBridgehub public immutable override BRIDGE_HUB; @@ -87,14 +90,6 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { _initialize(); } - /// @dev internal initialize. - function _initialize() internal { - // slither-disable-next-line unused-return - sharedTree.setup(SHARED_ROOT_TREE_EMPTY_HASH); - } - - /// @dev Adds a new chain to the message root. - /// @param _chainId the chainId of the chain function addNewChain(uint256 _chainId) external onlyBridgehub { require(!chainRegistered[_chainId], "MR: chain exists"); _addNewChain(_chainId); @@ -108,6 +103,25 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { } } + /// @dev add a new chainBatchRoot to the chainTree + function addChainBatchRoot( + uint256 _chainId, + uint256 _batchNumber, + bytes32 _chainBatchRoot + ) external onlyChain(_chainId) { + require(chainRegistered[_chainId], "MR: not registered"); + bytes32 chainRoot; + // slither-disable-next-line unused-return + (, chainRoot) = chainTree[_chainId].push(MessageHashing.batchLeafHash(_chainBatchRoot, _batchNumber)); + + // slither-disable-next-line unused-return + sharedTree.updateLeaf(chainIndex[_chainId], MessageHashing.chainIdLeafHash(chainRoot, _chainId)); + + emit Preimage(chainRoot, MessageHashing.chainIdLeafHash(chainRoot, _chainId)); + + emit AppendedChainBatchRoot(_chainId, _batchNumber, _chainBatchRoot); + } + /// @dev Gets the aggregated root of all chains. function getAggregatedRoot() external view returns (bytes32) { return sharedTree.root(); @@ -119,6 +133,21 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { return chainTree[_chainId].root(); } + function updateFullTree() public { + uint256 cachedChainCount = chainCount; + bytes32[] memory newLeaves = new bytes32[](cachedChainCount); + for (uint256 i = 0; i < cachedChainCount; ++i) { + newLeaves[i] = MessageHashing.chainIdLeafHash(chainTree[chainIndexToId[i]].root(), chainIndexToId[i]); + } + // slither-disable-next-line unused-return + sharedTree.updateAllLeaves(newLeaves); + } + + function _initialize() internal { + // slither-disable-next-line unused-return + sharedTree.setup(SHARED_ROOT_TREE_EMPTY_HASH); + } + /// @dev Adds a single chain to the message root. /// @param _chainId the chainId of the chain function _addNewChain(uint256 _chainId) internal { @@ -143,39 +172,4 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { emit AddedChain(_chainId, cachedChainCount); } - - event Preimage(bytes32 one, bytes32 two); - - /// @dev add a new chainBatchRoot to the chainTree - /// @param _chainId the chainId of the chain - /// @param _batchNumber the batch number - /// @param _chainBatchRoot the chain batch root - function addChainBatchRoot( - uint256 _chainId, - uint256 _batchNumber, - bytes32 _chainBatchRoot - ) external onlyChain(_chainId) { - require(chainRegistered[_chainId], "MR: not registered"); - bytes32 chainRoot; - // slither-disable-next-line unused-return - (, chainRoot) = chainTree[_chainId].push(MessageHashing.batchLeafHash(_chainBatchRoot, _batchNumber)); - - // slither-disable-next-line unused-return - sharedTree.updateLeaf(chainIndex[_chainId], MessageHashing.chainIdLeafHash(chainRoot, _chainId)); - - emit Preimage(chainRoot, MessageHashing.chainIdLeafHash(chainRoot, _chainId)); - - emit AppendedChainBatchRoot(_chainId, _batchNumber, _chainBatchRoot); - } - - /// @dev Updates the full merkle tree with the current roots of the chains. - function updateFullTree() public { - uint256 cachedChainCount = chainCount; - bytes32[] memory newLeaves = new bytes32[](cachedChainCount); - for (uint256 i = 0; i < cachedChainCount; ++i) { - newLeaves[i] = MessageHashing.chainIdLeafHash(chainTree[chainIndexToId[i]].root(), chainIndexToId[i]); - } - // slither-disable-next-line unused-return - sharedTree.updateAllLeaves(newLeaves); - } } diff --git a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol index a2cceb29f..697e8331e 100644 --- a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol @@ -87,6 +87,11 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable request = _registerSTMAssetOnL2Bridgehub(_chainId, _stmL1Address, _stmL2Address); } + /// @dev we need to implement this for the bridgehub for the TwoBridges logic + function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external { + // This function is typically used on bridges for e.g. + } + // todo this has to be put in L1AssetRouter via TwoBridges for custom base tokens. Hard, because we have to have multiple msg types in bridgehubDeposit in the AssetRouter. /// @notice Used to register the stm asset in L2 AssetRouter. /// @param _chainId the chainId of the chain @@ -114,6 +119,10 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable }); } + function getAssetId(address _l1STM) public view override returns (bytes32) { + return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1STM))))); + } + // Todo this works for now, but it will not work in the future if we want to change STM DTs. Probably ok. /// @notice Used to register the stm asset in L2 Bridgehub. /// @param _chainId the chainId of the chain @@ -137,15 +146,4 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable txDataHash: bytes32(0) }); } - - /// @dev we need to implement this for the bridgehub for the TwoBridges logic - function bridgehubConfirmL2Transaction(uint256, bytes32, bytes32) external { - // This function is typically used on bridges for e.g. confirm that a tx took place - } - - /// @notice Return the assetId of the STM. - /// @param _l1STM the address of the STM on L1 - function getAssetId(address _l1STM) public view override returns (bytes32) { - return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1STM))))); - } } diff --git a/l1-contracts/contracts/common/Config.sol b/l1-contracts/contracts/common/Config.sol index 739c38ed4..79a28d9f5 100644 --- a/l1-contracts/contracts/common/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -21,10 +21,10 @@ bytes32 constant L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH = 0x72abee45b59e344af8a6e5202 // TODO: change constant to the real root hash of empty Merkle tree (SMA-184) bytes32 constant DEFAULT_L2_LOGS_TREE_ROOT_HASH = bytes32(0); -/// @dev Denotes the type of the zkSync transaction that came from L1. +/// @dev Denotes the type of the ZKsync transaction that came from L1. uint256 constant PRIORITY_OPERATION_L2_TX_TYPE = 255; -/// @dev Denotes the type of the zkSync transaction that is used for system upgrades. +/// @dev Denotes the type of the ZKsync transaction that is used for system upgrades. uint256 constant SYSTEM_UPGRADE_L2_TX_TYPE = 254; /// @dev The maximal allowed difference between protocol minor versions in an upgrade. The 100 gap is needed diff --git a/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol b/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol index 31d796d45..08ddcd9e9 100644 --- a/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol +++ b/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; /** * @author Matter Labs - * @notice System smart contract that is responsible for deploying other smart contracts on a zkSync hyperchain. + * @notice System smart contract that is responsible for deploying other smart contracts on a ZKsync hyperchain. */ interface IL2ContractDeployer { /// @notice A struct that describes a forced deployment on an address. diff --git a/l1-contracts/contracts/common/libraries/DataEncoding.sol b/l1-contracts/contracts/common/libraries/DataEncoding.sol new file mode 100644 index 000000000..39dcef4d5 --- /dev/null +++ b/l1-contracts/contracts/common/libraries/DataEncoding.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../L2ContractAddresses.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for transfer data encoding and decoding to reduce possibility of errors. + */ +library DataEncoding { + /// @notice Abi.encodes the data required for bridgeMint on remote chain. + /// @param _prevMsgSender The address which initiated the transfer. + /// @param _l2Receiver The address which to receive tokens on remote chain. + /// @param _l1Token The transferred token address. + /// @param _amount The amount of token to be transferred. + /// @param _erc20Metadata The transferred token metadata. + /// @return The encoded bridgeMint data + function encodeBridgeMintData( + address _prevMsgSender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) internal pure returns (bytes memory) { + // solhint-disable-next-line func-named-parameters + return abi.encode(_prevMsgSender, _l2Receiver, _l1Token, _amount, _erc20Metadata); + } + + /// @notice Function decoding transfer data previously encoded with this library. + /// @param _bridgeMintData The encoded bridgeMint data + /// @return _prevMsgSender The address which initiated the transfer. + /// @return _l2Receiver The address which to receive tokens on remote chain. + /// @return _parsedL1Token The transferred token address. + /// @return _amount The amount of token to be transferred. + /// @return _erc20Metadata The transferred token metadata. + function decodeBridgeMintData( + bytes memory _bridgeMintData + ) + internal + pure + returns ( + address _prevMsgSender, + address _l2Receiver, + address _parsedL1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) + { + (_prevMsgSender, _l2Receiver, _parsedL1Token, _amount, _erc20Metadata) = abi.decode( + _bridgeMintData, + (address, address, address, uint256, bytes) + ); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, bytes32 _assetData, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAaddress The address of token that has to be encoded (asset data is the address itself). + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, address _tokenAaddress, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _tokenAaddress)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, bytes32 _assetData) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAddress The address of token that has to be encoded (asset data is the address itself). + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, address _tokenAddress) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, _tokenAddress)); + } +} diff --git a/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol b/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol index 75b7a155d..f3cf869cf 100644 --- a/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol +++ b/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol @@ -10,14 +10,10 @@ contract DummyL1ERC20Bridge is L1ERC20Bridge { constructor( IL1AssetRouter _l1SharedBridge, IL1NativeTokenVault _l1NativeTokenVault - ) L1ERC20Bridge(_l1SharedBridge, _l1NativeTokenVault) {} + ) L1ERC20Bridge(_l1SharedBridge, _l1NativeTokenVault, 1) {} - function setValues( - address _l2NativeTokenVault, - address _l2TokenBeacon, - bytes32 _l2TokenProxyBytecodeHash - ) external { - l2NativeTokenVault = _l2NativeTokenVault; + function setValues(address _l2SharedBridge, address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external { + l2Bridge = _l2SharedBridge; l2TokenBeacon = _l2TokenBeacon; l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; } diff --git a/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol b/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol index 31575de1b..663afdfdc 100644 --- a/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol +++ b/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.24; /// @title RevertReceiveAccount - An account which reverts receiving funds depending on the flag -/// @dev Used for testing failed withdrawals from the zkSync smart contract +/// @dev Used for testing failed withdrawals from the ZKsync smart contract contract RevertReceiveAccount { // add this to be excluded from coverage report function test() internal virtual {} diff --git a/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol b/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol index bd018276d..dcc1f71f7 100644 --- a/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol +++ b/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.24; import {TestnetERC20Token} from "./TestnetERC20Token.sol"; /// @title RevertTransferERC20Token - A ERC20 token contract which can revert transfers depending on a flag -/// @dev Used for testing failed ERC-20 withdrawals from the zkSync smart contract +/// @dev Used for testing failed ERC-20 withdrawals from the ZKsync smart contract contract RevertTransferERC20 is TestnetERC20Token { // add this to be excluded from coverage report function test() internal override {} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol index edc10d428..030006109 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.24; import {Diamond} from "../../state-transition/libraries/Diamond.sol"; import {ZkSyncHyperchainBase} from "../../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; import {IL1AssetRouter} from "../../bridge/interfaces/IL1AssetRouter.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; /// selectors do not overlap with normal facet selectors (getName does not count) contract DummyAdminFacetNoOverlap is ZkSyncHyperchainBase { @@ -17,7 +18,7 @@ contract DummyAdminFacetNoOverlap is ZkSyncHyperchainBase { function executeUpgradeNoOverlap(Diamond.DiamondCutData calldata _diamondCut) external { Diamond.diamondCut(_diamondCut); - s.baseTokenAssetId = IL1AssetRouter(s.baseTokenBridge).nativeTokenVault().getAssetId(s.baseToken); + s.baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, s.baseToken); } function receiveEther() external payable {} diff --git a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol index 51b318e65..82cb9562a 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol @@ -70,6 +70,18 @@ contract DummySharedBridge { bytes32[] calldata // _merkleProof ) external {} + function claimFailedDeposit( + uint256, // _chainId, + address, // _depositSender, + address, // _l1Asset, + uint256, // _amount, + bytes32, // _l2TxHash, + uint256, // _l2BatchNumber, + uint256, // _l2MessageIndex, + uint16, // _l2TxNumberInBatch, + bytes32[] calldata //_merkleProof + ) external {} + function finalizeWithdrawalLegacyErc20Bridge( uint256, //_l2BatchNumber, uint256, //_l2MessageIndex, diff --git a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol index 350d5c8a9..ea65333c5 100644 --- a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol @@ -11,5 +11,5 @@ contract L1ERC20BridgeTest is L1ERC20Bridge { // add this to be excluded from coverage report function test() internal virtual {} - constructor(IBridgehub _zkSync) L1ERC20Bridge(IL1AssetRouter(address(0)), IL1NativeTokenVault(address(0))) {} + constructor(IBridgehub _zkSync) L1ERC20Bridge(IL1AssetRouter(address(0)), IL1NativeTokenVault(address(0)), 1) {} } diff --git a/l1-contracts/contracts/governance/Governance.sol b/l1-contracts/contracts/governance/Governance.sol index 3f40721e9..656cbeff4 100644 --- a/l1-contracts/contracts/governance/Governance.sol +++ b/l1-contracts/contracts/governance/Governance.sol @@ -13,7 +13,7 @@ import {IGovernance} from "./IGovernance.sol"; /// @notice This contract manages operations (calls with preconditions) for governance tasks. /// The contract allows for operations to be scheduled, executed, and canceled with /// appropriate permissions and delays. It is used for managing and coordinating upgrades -/// and changes in all zkSync hyperchain governed contracts. +/// and changes in all ZKsync hyperchain governed contracts. /// /// Operations can be proposed as either fully transparent upgrades with on-chain data, /// or "shadow" upgrades where upgrade data is not published on-chain before execution. Proposed operations diff --git a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol index acba1f53a..a813e5d02 100644 --- a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol +++ b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol @@ -16,7 +16,7 @@ import {PriorityOpsBatchInfo} from "./libraries/PriorityTree.sol"; /// @dev The primary purpose of this contract is to provide a trustless means of delaying batch execution without /// modifying the main hyperchain diamond contract. As such, even if this contract is compromised, it will not impact the main /// contract. -/// @dev zkSync actively monitors the chain activity and reacts to any suspicious activity by freezing the chain. +/// @dev ZKsync actively monitors the chain activity and reacts to any suspicious activity by freezing the chain. /// This allows time for investigation and mitigation before resuming normal operations. /// @dev The contract overloads all of the 4 methods, that are used in state transition. When the batch is committed, /// the timestamp is stored for it. Later, when the owner calls the batch execution, the contract checks that batch @@ -207,7 +207,7 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { // Note: if the `commitBatchTimestamp` is zero, that means either: // * The batch was committed, but not through this contract. - // * The batch wasn't committed at all, so execution will fail in the zkSync contract. + // * The batch wasn't committed at all, so execution will fail in the ZKsync contract. // We allow executing such batches. require(block.timestamp >= commitBatchTimestamp + delay, "5c"); // The delay is not passed diff --git a/l1-contracts/contracts/state-transition/Verifier.sol b/l1-contracts/contracts/state-transition/Verifier.sol index cfc1f848b..a74ecb12c 100644 --- a/l1-contracts/contracts/state-transition/Verifier.sol +++ b/l1-contracts/contracts/state-transition/Verifier.sol @@ -8,14 +8,14 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @author Matter Labs /// @notice Modified version of the Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of /// Knowledge (PLONK) verifier. -/// Modifications have been made to optimize the proof system for zkSync hyperchain circuits. +/// Modifications have been made to optimize the proof system for ZKsync hyperchain circuits. /// @dev Contract was generated from a verification key with a hash of 0x14f97b81e54b35fe673d8708cc1a19e1ea5b5e348e12d31e39824ed4f42bbca2 /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. /// @dev For a better understanding of the verifier algorithm please refer to the following papers: /// * Original Plonk Article: https://eprint.iacr.org/2019/953.pdf /// * Original LookUp Article: https://eprint.iacr.org/2020/315.pdf -/// * Plonk for zkSync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf +/// * Plonk for ZKsync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf /// The notation used in the code is the same as in the papers. /* solhint-enable max-line-length */ contract Verifier is IVerifier { diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 13e78852d..34fd00626 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -20,7 +20,7 @@ import {IL1DAValidator, L1DAValidatorOutput} from "../../chain-interfaces/IL1DAV // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; -/// @title zkSync hyperchain Executor contract capable of processing events emitted in the zkSync hyperchain protocol. +/// @title ZKsync hyperchain Executor contract capable of processing events emitted in the ZKsync hyperchain protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index 9c38cff93..0d8a671b6 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -30,7 +30,7 @@ import {IL1AssetRouter} from "../../../bridge/interfaces/IL1AssetRouter.sol"; // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; -/// @title zkSync Mailbox contract providing interfaces for L1 <-> L2 interaction. +/// @title ZKsync Mailbox contract providing interfaces for L1 <-> L2 interaction. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { @@ -397,7 +397,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { request.refundRecipient = AddressAliasHelper.actualRefundRecipient(request.refundRecipient, request.sender); // Change the sender address if it is a smart contract to prevent address collision between L1 and L2. - // Please note, currently zkSync address derivation is different from Ethereum one, but it may be changed in the future. + // Please note, currently ZKsync address derivation is different from Ethereum one, but it may be changed in the future. // slither-disable-next-line tx-origin if (request.sender != tx.origin) { request.sender = AddressAliasHelper.applyL1ToL2Alias(request.sender); diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol index 6f3930372..41ce9d33b 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol @@ -47,7 +47,7 @@ uint256 constant MAX_NUMBER_OF_BLOBS = 6; /// than the maximal number of blobs supported by the contract (`MAX_NUMBER_OF_BLOBS`). uint256 constant TOTAL_BLOBS_IN_COMMITMENT = 16; -/// @title The interface of the zkSync Executor contract capable of processing events emitted in the zkSync protocol. +/// @title The interface of the ZKsync Executor contract capable of processing events emitted in the ZKsync protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IExecutor is IZkSyncHyperchainBase { @@ -59,7 +59,7 @@ interface IExecutor is IZkSyncHyperchainBase { /// @param priorityOperationsHash Hash of all priority operations from this batch /// @param l2LogsTreeRoot Root hash of tree that contains L2 -> L1 messages from this batch /// @param timestamp Rollup batch timestamp, have the same format as Ethereum batch constant - /// @param commitment Verified input for the zkSync circuit + /// @param commitment Verified input for the ZKsync circuit // solhint-disable-next-line gas-struct-packing struct StoredBatchInfo { uint64 batchNumber; @@ -175,7 +175,7 @@ interface IExecutor is IZkSyncHyperchainBase { /// @notice Event emitted when a batch is committed /// @param batchNumber Number of the batch committed /// @param batchHash Hash of the L2 batch - /// @param commitment Calculated input for the zkSync circuit + /// @param commitment Calculated input for the ZKsync circuit /// @dev It has the name "BlockCommit" and not "BatchCommit" due to backward compatibility considerations event BlockCommit(uint256 indexed batchNumber, bytes32 indexed batchHash, bytes32 indexed commitment); @@ -188,7 +188,7 @@ interface IExecutor is IZkSyncHyperchainBase { /// @notice Event emitted when a batch is executed /// @param batchNumber Number of the batch executed /// @param batchHash Hash of the L2 batch - /// @param commitment Verified input for the zkSync circuit + /// @param commitment Verified input for the ZKsync circuit /// @dev It has the name "BlockExecution" and not "BatchExecution" due to backward compatibility considerations event BlockExecution(uint256 indexed batchNumber, bytes32 indexed batchHash, bytes32 indexed commitment); diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol index 5d3c36094..5f758b6c2 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.24; import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; /// @author Matter Labs -/// @dev This interface contains getters for the zkSync contract that should not be used, +/// @dev This interface contains getters for the ZKsync contract that should not be used, /// but still are kept for backward compatibility. /// @custom:security-contact security@matterlabs.dev interface ILegacyGetters is IZkSyncHyperchainBase { diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol index 03c5c6741..b6fc837f2 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.24; import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; import {L2CanonicalTransaction, L2Log, L2Message, TxStatus, BridgehubL2TransactionRequest} from "../../common/Messaging.sol"; -/// @title The interface of the zkSync Mailbox contract that provides interfaces for L1 <-> L2 interaction. +/// @title The interface of the ZKsync Mailbox contract that provides interfaces for L1 <-> L2 interaction. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IMailbox is IZkSyncHyperchainBase { diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol index 99451ab13..a598821fb 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -/// @title The interface of the zkSync contract, responsible for the main zkSync logic. +/// @title The interface of the ZKsync contract, responsible for the main ZKsync logic. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IZkSyncHyperchainBase { diff --git a/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol b/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol index 86bdb4294..781c74303 100644 --- a/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol +++ b/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol @@ -9,7 +9,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {L2CanonicalTransaction} from "../../common/Messaging.sol"; import {TX_SLOT_OVERHEAD_L2_GAS, MEMORY_OVERHEAD_GAS, L1_TX_INTRINSIC_L2_GAS, L1_TX_DELTA_544_ENCODING_BYTES, L1_TX_DELTA_FACTORY_DEPS_L2_GAS, L1_TX_MIN_L2_GAS_BASE, L1_TX_INTRINSIC_PUBDATA, L1_TX_DELTA_FACTORY_DEPS_PUBDATA} from "../../common/Config.sol"; -/// @title zkSync Library for validating L1 -> L2 transactions +/// @title ZKsync Library for validating L1 -> L2 transactions /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev library TransactionValidator { @@ -124,7 +124,7 @@ library TransactionValidator { /// @notice Based on the total L2 gas limit and several other parameters of the transaction /// returns the part of the L2 gas that will be spent on the batch's overhead. /// @dev The details of how this function works can be checked in the documentation - /// of the fee model of zkSync. The appropriate comments are also present + /// of the fee model of ZKsync. The appropriate comments are also present /// in the Rust implementation description of function `get_maximal_allowed_overhead`. /// @param _encodingLength The length of the binary encoding of the transaction in bytes function getOverheadForTransaction( diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index c44bd03c4..0af406abb 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -577,13 +577,19 @@ contract DeployL1Script is Script { vm.startBroadcast(msg.sender); bridgehub.addStateTransitionManager(addresses.stateTransition.stateTransitionProxy); console.log("StateTransitionManager registered"); - STMDeploymentTracker stmDT = STMDeploymentTracker(addresses.bridgehub.stmDeploymentTrackerProxy); // vm.startBroadcast(msg.sender); + L1AssetRouter sharedBridge = L1AssetRouter(addresses.bridges.sharedBridgeProxy); + sharedBridge.setAssetDeploymentTracker( + bytes32(uint256(uint160(addresses.stateTransition.stateTransitionProxy))), + address(stmDT) + ); + console.log("STM DT whitelisted"); + stmDT.registerSTMAssetOnL1(addresses.stateTransition.stateTransitionProxy); vm.stopBroadcast(); console.log("STM registered in STMDeploymentTracker"); - L1AssetRouter sharedBridge = L1AssetRouter(addresses.bridges.sharedBridgeProxy); + bytes32 assetId = bridgehub.stmAssetId(addresses.stateTransition.stateTransitionProxy); // console.log(address(bridgehub.stmDeployer()), addresses.bridgehub.stmDeploymentTrackerProxy); // console.log(address(bridgehub.stmDeployer().BRIDGE_HUB()), addresses.bridgehub.bridgehubProxy); @@ -674,7 +680,7 @@ contract DeployL1Script is Script { function deployErc20BridgeImplementation() internal { bytes memory bytecode = abi.encodePacked( type(L1ERC20Bridge).creationCode, - abi.encode(addresses.bridges.sharedBridgeProxy, addresses.vaults.l1NativeTokenVaultProxy) + abi.encode(addresses.bridges.sharedBridgeProxy, addresses.vaults.l1NativeTokenVaultProxy, config.eraChainId) ); address contractAddress = deployViaCreate2(bytecode); console.log("Erc20BridgeImplementation deployed at:", contractAddress); diff --git a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol index 5365c2b78..1c2980737 100644 --- a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol +++ b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol @@ -16,6 +16,7 @@ import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; import {Utils} from "./Utils.sol"; import {PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; import {IL1NativeTokenVault} from "contracts/bridge/interfaces/IL1NativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; contract RegisterHyperchainScript is Script { using stdToml for string; @@ -150,7 +151,7 @@ contract RegisterHyperchainScript is Script { function registerTokenOnNTV() internal { IL1NativeTokenVault ntv = IL1NativeTokenVault(config.nativeTokenVault); // Ownable ownable = Ownable(config.nativeTokenVault); - bytes32 assetId = ntv.getAssetId(config.baseToken); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, config.baseToken); if (ntv.tokenAddress(assetId) != address(0)) { console.log("Token already registered on NTV"); } else { diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 51cb75a07..2ac5a0473 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -611,9 +611,10 @@ export class Deployer { ethTxOptions: ethers.providers.TransactionRequest, dummy: boolean = false ) { + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const contractAddress = await this.deployViaCreate2( dummy ? "DummyL1ERC20Bridge" : "L1ERC20Bridge", - [this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridges.NativeTokenVaultProxy], + [this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridges.NativeTokenVaultProxy, eraChainId], create2Salt, ethTxOptions ); @@ -820,19 +821,19 @@ export class Deployer { create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest ) { - const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); + // const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const tokens = getTokens(); const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; const contractAddress = await this.deployViaCreate2( "L1NativeTokenVault", - [l1WethToken, this.addresses.Bridges.SharedBridgeProxy, eraChainId], + [l1WethToken, this.addresses.Bridges.SharedBridgeProxy], create2Salt, ethTxOptions ); if (this.verbose) { - console.log(`With era chain id ${eraChainId}`); + // console.log(`With era chain id ${eraChainId}`); console.log(`CONTRACTS_L1_NATIVE_TOKEN_VAULT_IMPL_ADDR=${contractAddress}`); } @@ -925,22 +926,22 @@ export class Deployer { public async registerAddresses() { const bridgehub = this.bridgehubContract(this.deployWallet); - /// registering ETH as a valid token, with address 1. - const upgradeData1 = bridgehub.interface.encodeFunctionData("addToken", [ADDRESS_ONE]); - await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData1); - if (this.verbose) { - console.log("ETH token registered in Bridgehub"); - } - - const upgradeData2 = bridgehub.interface.encodeFunctionData("setAddresses", [ + const upgradeData1 = await bridgehub.interface.encodeFunctionData("setAddresses", [ this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridgehub.STMDeploymentTrackerProxy, this.addresses.Bridgehub.MessageRootProxy, ]); - await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData2); + await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData1); if (this.verbose) { console.log("Shared bridge was registered in Bridgehub"); } + + /// registering ETH as a valid token, with address 1. + const upgradeData2 = bridgehub.interface.encodeFunctionData("addToken", [ADDRESS_ONE]); + await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData2); + if (this.verbose) { + console.log("ETH token registered in Bridgehub"); + } } public async registerTokenInNativeTokenVault(token: string) { @@ -1046,12 +1047,26 @@ export class Deployer { } const stmDeploymentTracker = this.stmDeploymentTracker(this.deployWallet); + + const l1AssetRouter = this.defaultSharedBridge(this.deployWallet); + const whitelistData = l1AssetRouter.interface.encodeFunctionData("setAssetDeploymentTracker", [ + ethers.utils.hexZeroPad(this.addresses.StateTransition.StateTransitionProxy, 32), + stmDeploymentTracker.address, + ]); + const receipt2 = await this.executeUpgrade(l1AssetRouter.address, 0, whitelistData); + if (this.verbose) { + console.log("STM deployment tracker whitelisted in L1 Shared Bridge", receipt2.gasUsed.toString()); + console.log( + `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); + } + const data1 = stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL1", [ this.addresses.StateTransition.StateTransitionProxy, ]); - const receipt2 = await this.executeUpgrade(this.addresses.Bridgehub.STMDeploymentTrackerProxy, 0, data1); + const receipt3 = await this.executeUpgrade(this.addresses.Bridgehub.STMDeploymentTrackerProxy, 0, data1); if (this.verbose) { - console.log("STM asset registered in L1 Shared Bridge via STM Deployment Tracker", receipt2.gasUsed.toString()); + console.log("STM asset registered in L1 Shared Bridge via STM Deployment Tracker", receipt3.gasUsed.toString()); console.log( `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` ); @@ -1324,7 +1339,6 @@ export class Deployer { public async registerTokenBridgehub(tokenAddress: string, useGovernance: boolean = false) { const bridgehub = this.bridgehubContract(this.deployWallet); - const receipt = await this.executeDirectOrGovernance(useGovernance, bridgehub, "addToken", [tokenAddress], 0); if (this.verbose) { diff --git a/l1-contracts/src.ts/utils.ts b/l1-contracts/src.ts/utils.ts index cf5f6d4c9..f328c5759 100644 --- a/l1-contracts/src.ts/utils.ts +++ b/l1-contracts/src.ts/utils.ts @@ -22,6 +22,7 @@ export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../SystemConfig.jso export const SYSTEM_UPGRADE_L2_TX_TYPE = 254; export const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; +export const ADDRESS_TWO_NTV = "0x0000000000000000000000000000000000000002"; export const ETH_ADDRESS_IN_CONTRACTS = ADDRESS_ONE; export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; export const L2_BRIDGEHUB_ADDRESS = "0x0000000000000000000000000000000000010002"; @@ -104,6 +105,12 @@ export function computeL2Create2Address( return ethers.utils.hexDataSlice(data, 12); } +export function encodeNTVAssetId(chainId: number, assetData: BytesLike) { + return ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode(["uint256", "address", "bytes32"], [chainId, ADDRESS_TWO_NTV, assetData]) + ); +} + export function getAddressFromEnv(envName: string): string { const address = process.env[envName]; if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol index 2a037ef3c..abc39a84d 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -19,12 +19,14 @@ import {L1NativeTokenVault} from "contracts/bridge/L1NativeTokenVault.sol"; import {L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "contracts/common/Messaging.sol"; import {ETH_TOKEN_ADDRESS, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, MAX_NEW_FACTORY_DEPS} from "contracts/common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "contracts/common/L2ContractAddresses.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; contract ExperimentalBridgeTest is Test { using stdStorage for StdStorage; Bridgehub bridgeHub; address public bridgeOwner; + address public testTokenAddress; DummyStateTransitionManagerWBH mockSTM; DummyHyperchain mockChainContract; DummySharedBridge mockSharedBridge; @@ -50,14 +52,15 @@ contract ExperimentalBridgeTest is Test { mockChainContract = new DummyHyperchain(address(bridgeHub), eraChainId); mockSharedBridge = new DummySharedBridge(keccak256("0xabc")); mockSecondSharedBridge = new DummySharedBridge(keccak256("0xdef")); - ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(mockSharedBridge)), eraChainId); + ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(mockSharedBridge))); mockSharedBridge.setNativeTokenVault(ntv); mockSecondSharedBridge.setNativeTokenVault(ntv); testToken = new TestnetERC20Token("ZKSTT", "ZkSync Test Token", 18); + testTokenAddress = address(testToken); vm.prank(address(ntv)); ntv.registerToken(ETH_TOKEN_ADDRESS); ntv.registerToken(address(testToken)); - tokenAssetId = ntv.getAssetId(address(testToken)); + tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(testToken)); // test if the ownership of the bridgeHub is set correctly or not address defaultOwner = bridgeHub.owner(); @@ -242,61 +245,60 @@ contract ExperimentalBridgeTest is Test { } } - function test_addToken(address, address randomAddress) public { - assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + function test_addToken(address randomAddress) public { + vm.startPrank(bridgeOwner); + bridgeHub.setSharedBridge(address(mockSharedBridge)); + vm.stopPrank(); + + assertTrue(!bridgeHub.tokenIsRegistered(testTokenAddress), "This random address is not registered as a token"); vm.prank(bridgeOwner); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); assertTrue( - bridgeHub.tokenIsRegistered(randomAddress), + bridgeHub.tokenIsRegistered(testTokenAddress), "after call from the bridgeowner, this randomAddress should be a registered token" ); - if (randomAddress != address(testToken)) { - // Testing to see if an actual ERC20 implementation can also be added or not + if (randomAddress != address(testTokenAddress)) { + // Testing to see if a random address can also be added or not vm.prank(bridgeOwner); - bridgeHub.addToken(address(testToken)); - - assertTrue(bridgeHub.tokenIsRegistered(address(testToken))); + bridgeHub.addToken(address(randomAddress)); + assertTrue(bridgeHub.tokenIsRegistered(randomAddress)); } // An already registered token cannot be registered again vm.prank(bridgeOwner); vm.expectRevert("BH: token already registered"); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); } function test_addToken_cannotBeCalledByRandomAddress(address randomAddress, address randomCaller) public { + vm.startPrank(bridgeOwner); + bridgeHub.setSharedBridge(address(mockSharedBridge)); + vm.stopPrank(); + if (randomCaller != bridgeOwner) { vm.prank(randomCaller); vm.expectRevert(bytes("Ownable: caller is not the owner")); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); } - assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + assertTrue(!bridgeHub.tokenIsRegistered(testTokenAddress), "This random address is not registered as a token"); vm.prank(bridgeOwner); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); assertTrue( - bridgeHub.tokenIsRegistered(randomAddress), - "after call from the bridgeowner, this randomAddress should be a registered token" + bridgeHub.tokenIsRegistered(testTokenAddress), + "after call from the bridgeowner, this testTokenAddress should be a registered token" ); - if (randomAddress != address(testToken)) { - // Testing to see if an actual ERC20 implementation can also be added or not - vm.prank(bridgeOwner); - bridgeHub.addToken(address(testToken)); - - assertTrue(bridgeHub.tokenIsRegistered(address(testToken))); - } - // An already registered token cannot be registered again by randomCaller if (randomCaller != bridgeOwner) { vm.prank(bridgeOwner); vm.expectRevert("BH: token already registered"); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); } } @@ -336,8 +338,8 @@ contract ExperimentalBridgeTest is Test { // ); // } - uint256 newChainId; - address admin; + // uint256 newChainId; + // address admin; // function test_createNewChain( // address randomCaller, @@ -351,15 +353,16 @@ contract ExperimentalBridgeTest is Test { // admin = makeAddr("NEW_CHAIN_ADMIN"); // // Diamond.DiamondCutData memory dcData; - // vm.prank(bridgeOwner); - // bridgeHub.setPendingAdmin(deployerAddress); - // vm.prank(deployerAddress); - // bridgeHub.acceptAdmin(); - // vm.startPrank(bridgeOwner); - // bridgeHub.addStateTransitionManager(address(mockSTM)); - // bridgeHub.addToken(address(testToken)); - // bridgeHub.setSharedBridge(address(mockSharedBridge)); - // vm.stopPrank(); + // vm.prank(bridgeOwner); + // bridgeHub.setPendingAdmin(deployerAddress); + // vm.prank(deployerAddress); + // bridgeHub.acceptAdmin(); + // vm.startPrank(bridgeOwner); + // bridgeHub.setSharedBridge(address(mockSharedBridge)); + // bridgeHub.addStateTransitionManager(address(mockSTM)); + // bridgeHub.addToken(testTokenAddress); + // bridgeHub.setSharedBridge(address(mockSharedBridge)); + // vm.stopPrank(); // if (randomCaller != deployerAddress && randomCaller != bridgeOwner) { // vm.prank(randomCaller); @@ -367,11 +370,10 @@ contract ExperimentalBridgeTest is Test { // bridgeHub.createNewChain({ // _chainId: chainId, // _stateTransitionManager: address(mockSTM), - // _baseToken: address(testToken), + // _baseToken: testTokenAddress, // _salt: uint256(123), // _admin: admin, - // _initData: abi.encode(bytes(""), bytes("")), - // _factoryDeps: new bytes[](0) + // _initData: bytes("") // }); // } @@ -389,36 +391,35 @@ contract ExperimentalBridgeTest is Test { // mockSTM.setHyperchain(chainId, address(mockChainContract)); // assertTrue(mockSTM.getHyperchain(chainId) == address(mockChainContract)); - // vm.startPrank(deployerAddress); - // vm.mockCall( - // address(mockSTM), - // // solhint-disable-next-line func-named-parameters - // abi.encodeWithSelector( - // mockSTM.createNewChain.selector, - // chainId, - // address(testToken), - // address(mockSharedBridge), - // admin, - // _newChainInitData - // ), - // bytes("") - // ); + // vm.startPrank(deployerAddress); + // vm.mockCall( + // address(mockSTM), + // // solhint-disable-next-line func-named-parameters + // abi.encodeWithSelector( + // mockSTM.createNewChain.selector, + // chainId, + // testTokenAddress, + // address(mockSharedBridge), + // admin, + // _newChainInitData + // ), + // bytes("") + // ); // newChainId = bridgeHub.createNewChain({ // _chainId: chainId, // _stateTransitionManager: address(mockSTM), - // _baseToken: address(testToken), + // _baseToken: testTokenAddress, // _salt: uint256(chainId * 2), // _admin: admin, - // _initData: _newChainInitData, - // _factoryDeps: new bytes[](0) + // _initData: _newChainInitData // }); // vm.stopPrank(); // vm.clearMockedCalls(); // assertTrue(bridgeHub.stateTransitionManager(newChainId) == address(mockSTM)); - // assertTrue(bridgeHub.baseToken(newChainId) == address(testToken)); + // assertTrue(bridgeHub.baseToken(newChainId) == testTokenAddress); // } // function test_getHyperchain(uint256 mockChainId) public { diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol index a9913f344..679dd70dd 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol @@ -12,7 +12,7 @@ contract GettersTest is L1Erc20BridgeTest { address daiOnEthereum = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address daiOnEra = 0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656; - stdstore.target(address(bridge)).sig("l2NativeTokenVault()").checked_write( + stdstore.target(address(bridge)).sig("l2Bridge()").checked_write( address(0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102) ); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol index 5be79002f..f88026e70 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol @@ -34,15 +34,20 @@ contract L1Erc20BridgeTest is Test { alice = makeAddr("alice"); dummySharedBridge = new DummySharedBridge(dummyL2DepositTxHash); - bridge = new L1ERC20Bridge(IL1AssetRouter(address(dummySharedBridge)), IL1NativeTokenVault(address(1))); - uint256 eraChainId = 1; + uint256 eraChainId = 9; + bridge = new L1ERC20Bridge( + IL1AssetRouter(address(dummySharedBridge)), + IL1NativeTokenVault(address(1)), + eraChainId + ); + address weth = makeAddr("weth"); - L1NativeTokenVault ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(dummySharedBridge)), eraChainId); + L1NativeTokenVault ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(dummySharedBridge))); vm.store(address(bridge), bytes32(uint256(212)), bytes32(0)); reenterL1ERC20Bridge = new ReenterL1ERC20Bridge(); - bridgeReenterItself = new L1ERC20Bridge(IL1AssetRouter(address(reenterL1ERC20Bridge)), ntv); + bridgeReenterItself = new L1ERC20Bridge(IL1AssetRouter(address(reenterL1ERC20Bridge)), ntv, eraChainId); reenterL1ERC20Bridge.setBridge(bridgeReenterItself); token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol index 62430cad8..3ced5fb4e 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol @@ -84,36 +84,11 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); bytes memory mintCalldata = abi.encode( - amount, alice, bob, - nativeTokenVault.getERC20Getters(address(ETH_TOKEN_ADDRESS)), - address(ETH_TOKEN_ADDRESS) - ); - // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - vm.prank(bridgehubAddress); - emit BridgehubDepositInitiated({ - chainId: chainId, - txDataHash: txDataHash, - from: alice, - assetId: ETH_TOKEN_ASSET_ID, - bridgeMintCalldata: mintCalldata - }); - sharedBridge.bridgehubDeposit{value: amount}(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, amount, bob)); - } - - function test_bridgehubDeposit_Eth_NewEncoding() public { - _setBaseTokenAssetId(tokenAssetId); - - bytes memory transferData = abi.encode(amount, bob); - bytes32 txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(alice, ETH_TOKEN_ASSET_ID, transferData))); - bytes memory mintCalldata = abi.encode( + address(ETH_TOKEN_ADDRESS), amount, - alice, - bob, - nativeTokenVault.getERC20Getters(address(ETH_TOKEN_ADDRESS)), - address(ETH_TOKEN_ADDRESS) + nativeTokenVault.getERC20Getters(address(ETH_TOKEN_ADDRESS)) ); // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, true, address(sharedBridge)); @@ -125,12 +100,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { assetId: ETH_TOKEN_ASSET_ID, bridgeMintCalldata: mintCalldata }); - sharedBridge.bridgehubDeposit{value: amount}( - chainId, - alice, - 0, - bytes.concat(bytes1(0x01), abi.encode(ETH_TOKEN_ASSET_ID, transferData)) - ); + sharedBridge.bridgehubDeposit{value: amount}(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, amount, bob)); } function test_bridgehubDeposit_Erc() public { @@ -200,7 +170,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: address(token), + _l1Asset: address(token), _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -242,7 +212,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -286,7 +256,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { _chainId: chainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -508,7 +478,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { vm.expectEmit(true, true, false, true, address(token)); emit IERC20.Transfer(address(sharedBridge), address(nativeTokenVault), amount); nativeTokenVault.transferFundsFromSharedBridge(address(token)); - nativeTokenVault.transferBalancesFromSharedBridge(address(token), chainId); + nativeTokenVault.updateChainBalancesFromSharedBridge(address(token), chainId); uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, address(token)); assertEq(endBalanceNtv - startBalanceNtv, amount); } @@ -517,7 +487,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { uint256 startEthBalanceNtv = address(nativeTokenVault).balance; uint256 startBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ADDRESS); nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); - nativeTokenVault.transferBalancesFromSharedBridge(ETH_TOKEN_ADDRESS, chainId); + nativeTokenVault.updateChainBalancesFromSharedBridge(ETH_TOKEN_ADDRESS, chainId); uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ADDRESS); uint256 endEthBalanceNtv = address(nativeTokenVault).balance; assertEq(endBalanceNtv - startBalanceNtv, amount); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol index d65594035..ccfac0b39 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol @@ -57,9 +57,9 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.transferTokenToNTV(address(token)); } - function test_clearChainBalance_wrongCaller() public { + function test_nullifyChainBalanceByNTV_wrongCaller() public { vm.expectRevert("L1AR: not NTV"); - sharedBridge.clearChainBalance(chainId, address(token)); + sharedBridge.nullifyChainBalanceByNTV(chainId, address(token)); } function test_registerToken_noCode() public { @@ -147,18 +147,6 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { nativeTokenVault.transferFundsFromSharedBridge(address(token)); } - function test_bridgehubDepositBaseToken_Eth_Token_notRegisteredTokenID() public { - // ToDo: Shall we do it properly instead of mocking? - stdstore - .target(address(sharedBridge)) - .sig("assetHandlerAddress(bytes32)") - .with_key(ETH_TOKEN_ASSET_ID) - .checked_write(address(0)); - vm.prank(bridgehubAddress); - vm.expectRevert("L1AR: only address can be registered"); - sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); - } - function test_bridgehubDepositBaseToken_Eth_Token_incorrectSender() public { vm.expectRevert("L1AR: msg.sender not equal to bridgehub or era chain"); sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); @@ -305,7 +293,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { _chainId: chainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -343,7 +331,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { _chainId: eraChainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -382,7 +370,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { _chainId: eraChainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -403,7 +391,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -437,7 +425,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: 0, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -470,7 +458,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -503,11 +491,11 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("NTV n funds"); + vm.expectRevert("NTV: not enough funds 2"); sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -700,7 +688,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { ); _setNativeTokenVaultChainBalance(chainId, ETH_TOKEN_ADDRESS, 0); - vm.expectRevert("NTV not enough funds 2"); + vm.expectRevert("NTV: not enough funds"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol index 5ee0a9550..c0a170689 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol @@ -123,7 +123,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: address(token), + _l1Asset: address(token), _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -166,7 +166,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol index 8de93bd7a..84ab99ab7 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol @@ -187,7 +187,8 @@ contract L1AssetRouterLegacyTest is L1AssetRouterTest { emit ClaimedFailedDepositSharedBridge(eraChainId, alice, (tokenAssetId), abi.encode(bytes32(0))); vm.prank(l1ERC20BridgeAddress); - sharedBridge.claimFailedDepositLegacyErc20Bridge({ + sharedBridge.claimFailedDeposit({ + _chainId: eraChainId, _depositSender: alice, _l1Asset: address(token), _amount: amount, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol index 6f516a2d4..3c9b90e5b 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol @@ -15,6 +15,7 @@ import {L1NativeTokenVault} from "contracts/bridge/L1NativeTokenVault.sol"; import {IL1NativeTokenVault} from "contracts/bridge/interfaces/IL1NativeTokenVault.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; contract L1AssetRouterTest is Test { using stdStorage for StdStorage; @@ -98,10 +99,7 @@ contract L1AssetRouterTest is Test { uint256 legacyBatchNumber = 0; uint256 isWithdrawalFinalizedStorageLocation = uint256(8 - 1 + (1 + 49) + 0 + (1 + 49) + 50 + 1 + 50); - bytes32 ETH_TOKEN_ASSET_ID = - keccak256( - abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, bytes32(uint256(uint160(ETH_TOKEN_ADDRESS)))) - ); + bytes32 ETH_TOKEN_ASSET_ID = keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, ETH_TOKEN_ADDRESS)); function setUp() public { owner = makeAddr("owner"); @@ -142,8 +140,7 @@ contract L1AssetRouterTest is Test { sharedBridge = L1AssetRouter(payable(sharedBridgeProxy)); nativeTokenVaultImpl = new L1NativeTokenVault({ _l1WethAddress: l1WethAddress, - _l1SharedBridge: IL1AssetRouter(address(sharedBridge)), - _eraChainId: eraChainId + _l1SharedBridge: IL1AssetRouter(address(sharedBridge)) }); TransparentUpgradeableProxy nativeTokenVaultProxy = new TransparentUpgradeableProxy( address(nativeTokenVaultImpl), @@ -153,7 +150,7 @@ contract L1AssetRouterTest is Test { nativeTokenVault = L1NativeTokenVault(payable(nativeTokenVaultProxy)); vm.prank(owner); sharedBridge.setL1Erc20Bridge(l1ERC20BridgeAddress); - tokenAssetId = nativeTokenVault.getAssetId(address(token)); + tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(token)); vm.prank(owner); sharedBridge.setNativeTokenVault(IL1NativeTokenVault(address(nativeTokenVault))); vm.prank(address(nativeTokenVault)); diff --git a/l1-contracts/test/unit_tests/custom_base_token.spec.ts b/l1-contracts/test/unit_tests/custom_base_token.spec.ts index fd4dac789..f413d0491 100644 --- a/l1-contracts/test/unit_tests/custom_base_token.spec.ts +++ b/l1-contracts/test/unit_tests/custom_base_token.spec.ts @@ -8,6 +8,8 @@ import type { IBridgehub } from "../../typechain/IBridgehub"; import { IBridgehubFactory } from "../../typechain/IBridgehubFactory"; import type { IL1AssetRouter } from "../../typechain/IL1AssetRouter"; import { IL1AssetRouterFactory } from "../../typechain/IL1AssetRouterFactory"; +import type { IL1NativeTokenVault } from "../../typechain/IL1NativeTokenVault"; +import { IL1NativeTokenVaultFactory } from "../../typechain/IL1NativeTokenVaultFactory"; import { getTokens } from "../../src.ts/deploy-token"; import type { Deployer } from "../../src.ts/deploy"; @@ -23,6 +25,7 @@ describe("Custom base token chain and bridge tests", () => { let deployer: Deployer; let l1SharedBridge: IL1AssetRouter; let bridgehub: IBridgehub; + let nativeTokenVault: IL1NativeTokenVault; let baseToken: TestnetERC20Token; let baseTokenAddress: string; let altTokenAddress: string; @@ -61,6 +64,11 @@ describe("Custom base token chain and bridge tests", () => { // prepare the bridge l1SharedBridge = IL1AssetRouterFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + + nativeTokenVault = IL1NativeTokenVaultFactory.connect( + deployer.addresses.Bridges.NativeTokenVaultProxy, + deployWallet + ); }); it("Should have correct base token", async () => { @@ -106,6 +114,7 @@ describe("Custom base token chain and bridge tests", () => { }); it("Should deposit alternative token successfully twoBridges method", async () => { + nativeTokenVault.registerToken(altTokenAddress); const altTokenAmount = ethers.utils.parseUnits("800", 18); const baseTokenAmount = ethers.utils.parseUnits("800", 18); @@ -135,8 +144,9 @@ describe("Custom base token chain and bridge tests", () => { }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const mailboxFunctionSignature = "0x6c0960f9"; const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", []) + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, []) ); expect(revertReason).equal("L1AR: wrong msg len"); }); diff --git a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts index 82d259377..84aab5abd 100644 --- a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts +++ b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts @@ -28,7 +28,7 @@ describe("Shared Bridge tests", () => { let proxyAsMockExecutor: MockExecutorFacet; let l1SharedBridge: L1AssetRouter; let erc20TestToken: ethers.Contract; - const functionSignature = "0x6c0960f9"; + const mailboxFunctionSignature = "0x6c0960f9"; const ERC20functionSignature = "0x11a2ccc1"; const dummyProof = Array(9).fill(ethers.constants.HashZero); dummyProof[0] = DUMMY_MERKLE_PROOF_START; @@ -111,16 +111,10 @@ describe("Shared Bridge tests", () => { refundRecipient: ethers.constants.AddressZero, secondBridgeAddress: l1SharedBridge.address, secondBridgeValue: 0, - secondBridgeCalldata: ethers.utils.concat([ - ethers.utils.hexlify(1), - new ethers.utils.AbiCoder().encode( - ["bytes32", "bytes"], - [ - await l1NativeTokenVault.getAssetId(erc20TestToken.address), - new ethers.utils.AbiCoder().encode(["uint256", "address"], [0, await randomSigner.getAddress()]), - ] - ), - ]), + secondBridgeCalldata: new ethers.utils.AbiCoder().encode( + ["address", "uint256", "address"], + [erc20TestToken.address, 0, await randomSigner.getAddress()] + ), }, { value: mintValue } ) @@ -128,46 +122,6 @@ describe("Shared Bridge tests", () => { expect(revertReason).equal("6T"); }); - it("Should deposit successfully", async () => { - const amount = ethers.utils.parseEther("1"); - const mintValue = ethers.utils.parseEther("2"); - - await erc20TestToken.connect(randomSigner).mint(await randomSigner.getAddress(), amount.mul(10)); - - const balanceBefore = await erc20TestToken.balanceOf(await randomSigner.getAddress()); - const balanceNTVBefore = await erc20TestToken.balanceOf(l1NativeTokenVault.address); - - const assetId = await l1NativeTokenVault.getAssetId(erc20TestToken.address); - await (await erc20TestToken.connect(randomSigner).approve(l1NativeTokenVault.address, amount.mul(10))).wait(); - await bridgehub.connect(randomSigner).requestL2TransactionTwoBridges( - { - chainId, - mintValue, - l2Value: amount, - l2GasLimit: 1000000, - l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - refundRecipient: ethers.constants.AddressZero, - secondBridgeAddress: l1SharedBridge.address, - secondBridgeValue: 0, - secondBridgeCalldata: ethers.utils.concat([ - ethers.utils.hexlify(1), - new ethers.utils.AbiCoder().encode( - ["bytes32", "bytes"], - [ - assetId, - new ethers.utils.AbiCoder().encode(["uint256", "address"], [amount, await randomSigner.getAddress()]), - ] - ), - ]), - }, - { value: mintValue } - ); - const balanceAfter = await erc20TestToken.balanceOf(await randomSigner.getAddress()); - expect(balanceAfter).equal(balanceBefore.sub(amount)); - const balanceNTVAfter = await erc20TestToken.balanceOf(l1NativeTokenVault.address); - expect(balanceNTVAfter).equal(balanceNTVBefore.add(amount)); - }); - it("Should deposit successfully legacy encoding", async () => { const amount = ethers.utils.parseEther("1"); const mintValue = ethers.utils.parseEther("2"); @@ -203,7 +157,9 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with short message length", async () => { const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); expect(revertReason).equal("L1AR: wrong msg len"); }); @@ -217,7 +173,7 @@ describe("Shared Bridge tests", () => { 0, 0, 0, - ethers.utils.hexConcat([ERC20functionSignature, l1SharedBridge.address, ethers.utils.randomBytes(72)]), + ethers.utils.hexConcat([ERC20functionSignature, l1SharedBridge.address, mailboxFunctionSignature]), [ethers.constants.HashZero] ) ); @@ -233,7 +189,9 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong message length", async () => { const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); expect(revertReason).equal("L1AR: wrong msg len"); }); @@ -250,7 +208,7 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong batch number", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, @@ -264,7 +222,7 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, @@ -280,7 +238,7 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong proof", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, diff --git a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts index 2b35808f9..88361ed05 100644 --- a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts +++ b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts @@ -170,8 +170,11 @@ describe("Legacy Era tests", function () { }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const mailboxFunctionSignature = "0x6c0960f9"; const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(1, 0, 0, "0x", [ethers.constants.HashZero]) + l1ERC20Bridge + .connect(randomSigner) + .finalizeWithdrawal(1, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); expect(revertReason).equal("L1AR: wrong msg len"); }); diff --git a/l2-contracts/contracts/L2ContractHelper.sol b/l2-contracts/contracts/L2ContractHelper.sol index 71e91cc83..907060281 100644 --- a/l2-contracts/contracts/L2ContractHelper.sol +++ b/l2-contracts/contracts/L2ContractHelper.sol @@ -106,6 +106,15 @@ address constant BOOTLOADER_ADDRESS = address(SYSTEM_CONTRACTS_OFFSET + 0x01); address constant MSG_VALUE_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x09); address constant DEPLOYER_SYSTEM_CONTRACT = address(SYSTEM_CONTRACTS_OFFSET + 0x06); +address constant L2_BRIDGEHUB_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x02); + +IL2AssetRouter constant L2_ASSET_ROUTER = IL2AssetRouter(address(USER_CONTRACTS_OFFSET + 0x03)); + +/// @dev The contract responsible for handling tokens native to a single chain. +IL2NativeTokenVault constant L2_NATIVE_TOKEN_VAULT = IL2NativeTokenVault(address(USER_CONTRACTS_OFFSET + 0x04)); + +uint256 constant L1_CHAIN_ID = 1; + IL2Messenger constant L2_MESSENGER = IL2Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); IBaseToken constant L2_BASE_TOKEN_ADDRESS = IBaseToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); @@ -116,13 +125,6 @@ IPubdataChunkPublisher constant PUBDATA_CHUNK_PUBLISHER = IPubdataChunkPublisher address(SYSTEM_CONTRACTS_OFFSET + 0x11) ); -address constant L2_BRIDGEHUB_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x02); - -IL2AssetRouter constant L2_ASSET_ROUTER = IL2AssetRouter(address(USER_CONTRACTS_OFFSET + 0x03)); - -/// @dev The contract responsible for handling tokens native to a single chain. -IL2NativeTokenVault constant L2_NATIVE_TOKEN_VAULT = IL2NativeTokenVault(address(USER_CONTRACTS_OFFSET + 0x04)); - /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev @@ -193,7 +195,7 @@ library L2ContractHelper { } } -/// @notice Structure used to represent a zkSync transaction. +/// @notice Structure used to represent a ZKsync transaction. struct Transaction { // The type of the transaction. uint256 txType; diff --git a/l2-contracts/contracts/bridge/L2AssetRouter.sol b/l2-contracts/contracts/bridge/L2AssetRouter.sol index 4ed6f062c..20be7d767 100644 --- a/l2-contracts/contracts/bridge/L2AssetRouter.sol +++ b/l2-contracts/contracts/bridge/L2AssetRouter.sol @@ -9,11 +9,12 @@ import {IL2AssetRouter} from "./interfaces/IL2AssetRouter.sol"; import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; import {IL2AssetHandler} from "./interfaces/IL2AssetHandler.sol"; -import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; +import {IL2NativeTokenVault} from "./interfaces/IL2NativeTokenVault.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {L2ContractHelper, L2_NATIVE_TOKEN_VAULT} from "../L2ContractHelper.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {EmptyAddress, InvalidCaller} from "../L2ContractErrors.sol"; @@ -38,14 +39,19 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. bytes32 internal DEPRECATED_l2TokenProxyBytecodeHash; - /// @dev A mapping l2 token address => l1 token address. - mapping(address l2TokenAddress => address l1TokenAddress) public override l1TokenAddress; + /// @notice Deprecated. Kept for backwards compatibility. + /// @dev A mapping l2 token address => l1 token address + mapping(address l2Token => address l1Token) public override l1TokenAddress; + /// @notice Obsolete, as all calls are performed via L1 Shared Bridge. Kept for backwards compatibility. /// @dev The address of the legacy L1 erc20 bridge counterpart. /// This is non-zero only on Era, and should not be renamed for backward compatibility with the SDKs. address public override l1Bridge; - /// @dev A mapping l2 token address => l1 token address. + /// @dev The contract responsible for handling tokens native to a single chain. + IL2NativeTokenVault public nativeTokenVault; + + /// @dev A mapping of asset ID to asset handler address mapping(bytes32 assetId => address assetHandlerAddress) public override assetHandlerAddress; /// @notice Checks that the message sender is the legacy bridge. @@ -83,9 +89,16 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { _disableInitializers(); } - /// @notice Finalizes the deposit and mint funds. - /// @param _assetId The encoding of the asset on L2. - /// @param _transferData The encoded data required for deposit (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken). + /// @dev Used to set the assedAddress for a given assetId. + /// @dev Will be used by ZK Gateway + function setAssetHandlerAddress(bytes32 _assetId, address _assetAddress) external onlyL1Bridge { + assetHandlerAddress[_assetId] = _assetAddress; + emit AssetHandlerRegistered(_assetId, _assetAddress); + } + + /// @notice Finalize the deposit and mint funds + /// @param _assetId The encoding of the asset on L2 + /// @param _transferData The encoded data required for deposit (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken) function finalizeDeposit(bytes32 _assetId, bytes memory _transferData) public override onlyL1Bridge { address assetHandler = assetHandlerAddress[_assetId]; if (assetHandler != address(0)) { @@ -95,27 +108,27 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { assetHandlerAddress[_assetId] = address(L2_NATIVE_TOKEN_VAULT); } - emit FinalizeDepositSharedBridge(L1_CHAIN_ID, _assetId, keccak256(_transferData)); + emit FinalizeDepositSharedBridge(L1_CHAIN_ID, _assetId, _transferData); } /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 - /// where tokens would be unlocked. - /// @param _assetId The encoding of the asset on L2 which is withdrawn. - /// @param _transferData The data that is passed to the asset handler contract. - function withdraw(bytes32 _assetId, bytes memory _transferData) public override { + /// where tokens would be unlocked + /// @param _assetId The asset id of the withdrawn asset + /// @param _assetData The data that is passed to the asset handler contract + function withdraw(bytes32 _assetId, bytes memory _assetData) public override { address assetHandler = assetHandlerAddress[_assetId]; bytes memory _l1bridgeMintData = IL2AssetHandler(assetHandler).bridgeBurn({ _chainId: L1_CHAIN_ID, _mintValue: 0, _assetId: _assetId, _prevMsgSender: msg.sender, - _transferData: _transferData + _data: _assetData }); bytes memory message = _getL1WithdrawMessage(_assetId, _l1bridgeMintData); L2ContractHelper.sendMessageToL1(message); - emit WithdrawalInitiatedSharedBridge(L1_CHAIN_ID, msg.sender, _assetId, keccak256(_transferData)); + emit WithdrawalInitiatedSharedBridge(L1_CHAIN_ID, msg.sender, _assetId, _assetData); } /// @notice Encodes the message for l2ToL1log sent during withdraw initialization. @@ -125,42 +138,33 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { bytes32 _assetId, bytes memory _l1bridgeMintData ) internal pure returns (bytes memory) { - // note we use the IL1ERC20Bridge.finalizeWithdrawal function selector to specify the selector for L1<>L2 messages, + // note we use the IL1SharedBridge.finalizeWithdrawal function selector to specify the selector for L1<>L2 messages, // and we use this interface so that when the switch happened the old messages could be processed // solhint-disable-next-line func-named-parameters return abi.encodePacked(IL1AssetRouter.finalizeWithdrawal.selector, _assetId, _l1bridgeMintData); } - /// @notice Sets the asset handler address for a given assetId. - /// @dev Will be called by ZK Gateway. - /// @param _assetId The encoding of the asset on L2. - /// @param _assetHandlerAddress The address of the asset handler, which will hold the token of interest. - function setAssetHandlerAddress(bytes32 _assetId, address _assetHandlerAddress) external onlyL1Bridge { - assetHandlerAddress[_assetId] = _assetHandlerAddress; - emit AssetHandlerRegistered(_assetId, _assetHandlerAddress); - } - /*////////////////////////////////////////////////////////////// LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ - /// @notice Finalizes the deposit and mint funds. + /// @notice Legacy finalizeDeposit. + /// @dev Finalizes the deposit and mint funds. /// @param _l1Sender The address of token sender on L1. /// @param _l2Receiver The address of token receiver on L2. /// @param _l1Token The address of the token transferred. /// @param _amount The amount of the token transferred. - /// @param erc20Data The ERC20 metadata of the token transferred. + /// @param _data The metadata of the token transferred. function finalizeDeposit( address _l1Sender, address _l2Receiver, address _l1Token, uint256 _amount, - bytes calldata erc20Data + bytes calldata _data ) external override { - // onlyBridge { - bytes32 assetId = keccak256(abi.encode(L1_CHAIN_ID, address(L2_NATIVE_TOKEN_VAULT), _l1Token)); + bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); // solhint-disable-next-line func-named-parameters - bytes memory data = abi.encode(_l1Sender, _amount, _l2Receiver, erc20Data, _l1Token); + bytes memory data = DataEncoding.encodeBridgeMintData(_l1Sender, _l2Receiver, _l1Token, _amount, _data); finalizeDeposit(assetId, data); } @@ -170,23 +174,24 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { /// @param _l2Token The address of the token transferred. /// @param _amount The amount of the token transferred. function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external { - bytes32 assetId = keccak256( - abi.encode(L1_CHAIN_ID, address(L2_NATIVE_TOKEN_VAULT), getL1TokenAddress(_l2Token)) - ); + bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, getL1TokenAddress(_l2Token)); bytes memory data = abi.encode(_amount, _l1Receiver); withdraw(assetId, data); } - /// @notice Retrieves L1 address corresponding to L2 wrapped token. + /// @notice Legacy getL1TokenAddress. /// @param _l2Token The address of token on L2. /// @return The address of token on L1. function getL1TokenAddress(address _l2Token) public view returns (address) { return IL2StandardToken(_l2Token).l1Address(); } - /// @notice Retrieves L2 wrapped token address corresponding to L1 token counterpart. + /// @notice Legacy function used for backward compatibility to return L2 wrapped token + /// @notice address corresponding to provided L1 token address and deployed through NTV. + /// @dev However, the shared bridge can use custom asset handlers such that L2 addresses differ, + /// @dev or an L1 token may not have an L2 counterpart. /// @param _l1Token The address of token on L1. - /// @return The address of token on L2. + /// @return Address of an L2 token counterpart function l2TokenAddress(address _l1Token) public view returns (address) { return L2_NATIVE_TOKEN_VAULT.l2TokenAddress(_l1Token); } diff --git a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol index b742314d3..1cab56ae3 100644 --- a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol @@ -10,8 +10,9 @@ import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; import {IL2NativeTokenVault} from "./interfaces/IL2NativeTokenVault.sol"; import {L2StandardERC20} from "./L2StandardERC20.sol"; -import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_NATIVE_TOKEN_VAULT, L2_ASSET_ROUTER, IContractDeployer} from "../L2ContractHelper.sol"; +import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER, IContractDeployer} from "../L2ContractHelper.sol"; import {SystemContractsCaller} from "../SystemContractsCaller.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {EmptyAddress, EmptyBytes32, AddressMismatch, AssetIdMismatch, DeployFailed, AmountMustBeGreaterThanZero, InvalidCaller} from "../L2ContractErrors.sol"; @@ -20,15 +21,19 @@ import {EmptyAddress, EmptyBytes32, AddressMismatch, AssetIdMismatch, DeployFail /// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not /// support any custom token logic, i.e. rebase tokens' functionality is not supported. contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { + /// @dev Chain ID of L1 for bridging reasons. + uint256 public immutable L1_CHAIN_ID; + + bytes32 internal l2TokenProxyBytecodeHash; + /// @dev Contract that stores the implementation address for token. /// @dev For more details see https://docs.openzeppelin.com/contracts/3.x/api/proxy#UpgradeableBeacon. UpgradeableBeacon public l2TokenBeacon; - /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. - bytes32 internal immutable l2TokenProxyBytecodeHash; - mapping(bytes32 assetId => address tokenAddress) public override tokenAddress; + /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. + modifier onlyBridge() { if (msg.sender != address(L2_ASSET_ROUTER)) { revert InvalidCaller(msg.sender); @@ -37,11 +42,13 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { _; } - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Disable the initialization to prevent Parity hack. + /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. + /// @param _l1ChainId The L1 chain id differs between mainnet and testnets. /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. /// @param _aliasedOwner The address of the governor contract. - constructor(bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner) { + constructor(uint256 _l1ChainId, bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner) { + L1_CHAIN_ID = _l1ChainId; + _disableInitializers(); if (_l2TokenProxyBytecodeHash == bytes32(0)) { revert EmptyBytes32(); @@ -55,9 +62,19 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { } /// @notice Sets L2 token beacon used by wrapped ERC20 tokens deployed by NTV. - /// @dev we don't call this in the constructor, as we need to provide factory deps - /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed - function setL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external onlyOwner { + /// @dev Sets the l2TokenBeacon, called after initialize. + /// @param _l2TokenBeacon The address of L2 token beacon implementation. + /// @param _l2TokenProxyBytecodeHash The bytecode hash of the L2 token proxy that will be deployed for wrapped tokens. + function setL2TokenBeacon(address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external onlyOwner { + l2TokenBeacon = UpgradeableBeacon(_l2TokenBeacon); + l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; + emit L2TokenBeaconUpdated(_l2TokenBeacon, _l2TokenProxyBytecodeHash); + } + + /// @notice Configure L2 token beacon used by wrapped ERC20 tokens deployed by NTV. + /// @dev we don't call this in the constructor, as we need to provide factory deps. + /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed. + function configureL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external { if (address(l2TokenBeacon) != address(0)) { revert AddressMismatch(address(l2TokenBeacon), address(0)); } @@ -73,53 +90,63 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { } } - /// @notice Mints the wrapped asset during shared bridge deposit finalization. + /// @notice Used when the chain receives a transfer from L1 Shared Bridge and correspondingly mints the asset. /// @param _chainId The chainId that the message is from. /// @param _assetId The assetId of the asset being bridged. - /// @param _transferData The abi.encoded transfer data. - function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _transferData) external payable override { + /// @param _data The abi.encoded transfer data. + function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable override onlyBridge { address token = tokenAddress[_assetId]; - (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken) = abi - .decode(_transferData, (address, uint256, address, bytes, address)); - address expectedToken = l2TokenAddress(originToken); + ( + address _l1Sender, + address _l2Receiver, + address originToken, + uint256 _amount, + bytes memory erc20Data + ) = DataEncoding.decodeBridgeMintData(_data); + if (token == address(0)) { - bytes32 expectedAssetId = keccak256( - abi.encode(_chainId, address(L2_NATIVE_TOKEN_VAULT), bytes32(uint256(uint160(originToken)))) - ); + address expectedToken = _calculateCreate2TokenAddress(originToken); + bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, originToken); if (_assetId != expectedAssetId) { // Make sure that a NativeTokenVault sent the message - revert AssetIdMismatch(_assetId, expectedAssetId); + revert AssetIdMismatch(expectedAssetId, _assetId); } address deployedToken = _deployL2Token(originToken, erc20Data); if (deployedToken != expectedToken) { revert AddressMismatch(expectedToken, deployedToken); } tokenAddress[_assetId] = expectedToken; + token = expectedToken; } - IL2StandardToken(expectedToken).bridgeMint(_l2Receiver, _amount); + IL2StandardToken(token).bridgeMint(_l2Receiver, _amount); /// backwards compatible event - emit FinalizeDeposit(_l1Sender, _l2Receiver, expectedToken, _amount); - // solhint-disable-next-line func-named-parameters - emit BridgeMint(_chainId, _assetId, _l1Sender, _l2Receiver, _amount); + emit FinalizeDeposit(_l1Sender, _l2Receiver, token, _amount); + emit BridgeMint({ + chainId: _chainId, + assetId: _assetId, + sender: _l1Sender, + l2Receiver: _l2Receiver, + amount: _amount + }); } /// @notice Burns wrapped tokens and returns the calldata for L2 -> L1 message. - /// @dev In case of native token vault _transferData is the tuple of _depositAmount and _l2Receiver. + /// @dev In case of native token vault _data is the tuple of _depositAmount and _l2Receiver. /// @param _chainId The chainId that the message will be sent to. /// @param _mintValue The L1 base token value bridged. /// @param _assetId The L2 assetId of the asset being bridged. /// @param _prevMsgSender The original caller of the shared bridge. - /// @param _transferData The abi.encoded transfer data. + /// @param _data The abi.encoded transfer data. /// @return l1BridgeMintData The calldata used by l1 asset handler to unlock tokens for recipient. function bridgeBurn( uint256 _chainId, uint256 _mintValue, bytes32 _assetId, address _prevMsgSender, - bytes calldata _transferData + bytes calldata _data ) external payable override onlyBridge returns (bytes memory l1BridgeMintData) { - (uint256 _amount, address _l1Receiver) = abi.decode(_transferData, (uint256, address)); + (uint256 _amount, address _l1Receiver) = abi.decode(_data, (uint256, address)); if (_amount == 0) { // "Amount cannot be zero"); revert AmountMustBeGreaterThanZero(); @@ -130,9 +157,26 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { /// backwards compatible event emit WithdrawalInitiated(_prevMsgSender, _l1Receiver, l2Token, _amount); - // solhint-disable-next-line func-named-parameters - emit BridgeBurn(_chainId, _assetId, _prevMsgSender, _l1Receiver, _mintValue, _amount); - l1BridgeMintData = _transferData; + emit BridgeBurn({ + chainId: _chainId, + assetId: _assetId, + l2Sender: _prevMsgSender, + receiver: _l1Receiver, + mintValue: _mintValue, + amount: _amount + }); + l1BridgeMintData = _data; + } + + /// @notice Calculates L2 wrapped token address corresponding to L1 token counterpart. + /// @param _l1Token The address of token on L1. + /// @return expectedToken The address of token on L2. + function l2TokenAddress(address _l1Token) public view override returns (address expectedToken) { + bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); + expectedToken = tokenAddress[expectedAssetId]; + if (expectedToken == address(0)) { + expectedToken = _calculateCreate2TokenAddress(_l1Token); + } } /// @notice Deploys and initializes the L2 token for the L1 counterpart. @@ -171,20 +215,20 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { proxy = BeaconProxy(abi.decode(returndata, (address))); } - /// @notice Converts the L1 token address to the create2 salt of deployed L2 token. - /// @param _l1Token The address of token on L1. - /// @return salt The salt used to compute address of wrapped token on L2 and for beacon proxy deployment. - function _getCreate2Salt(address _l1Token) internal pure returns (bytes32 salt) { - salt = bytes32(uint256(uint160(_l1Token))); - } - - /// @notice Calculates L2 wrapped token address corresponding to L1 token counterpart. + /// @notice Calculates L2 wrapped token address given the currently stored beacon proxy bytecode hash and beacon address. /// @param _l1Token The address of token on L1. - /// @return The address of token on L2. - function l2TokenAddress(address _l1Token) public view override returns (address) { + /// @return Address of an L2 token counterpart. + function _calculateCreate2TokenAddress(address _l1Token) internal view returns (address) { bytes32 constructorInputHash = keccak256(abi.encode(address(l2TokenBeacon), "")); bytes32 salt = _getCreate2Salt(_l1Token); return L2ContractHelper.computeCreate2Address(address(this), salt, l2TokenProxyBytecodeHash, constructorInputHash); } + + /// @notice Converts the L1 token address to the create2 salt of deployed L2 token. + /// @param _l1Token The address of token on L1. + /// @return salt The salt used to compute address of wrapped token on L2 and for beacon proxy deployment. + function _getCreate2Salt(address _l1Token) internal pure returns (bytes32 salt) { + salt = bytes32(uint256(uint160(_l1Token))); + } } diff --git a/l2-contracts/contracts/bridge/L2StandardERC20.sol b/l2-contracts/contracts/bridge/L2StandardERC20.sol index 2104629f4..cb3f54977 100644 --- a/l2-contracts/contracts/bridge/L2StandardERC20.sol +++ b/l2-contracts/contracts/bridge/L2StandardERC20.sol @@ -37,6 +37,22 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg /// @dev Address of the L1 token that can be deposited to mint this L2 token address public override l1Address; + modifier onlyBridge() { + if (msg.sender != l2Bridge) { + revert Unauthorized(); + } + _; + } + + modifier onlyNextVersion(uint8 _version) { + // The version should be incremented by 1. Otherwise, the governor risks disabling + // future reinitialization of the token by providing too large a version. + if (_version != _getInitializedVersion() + 1) { + revert NonSequentialVersion(); + } + _; + } + /// @dev Contract is expected to be used as proxy implementation. constructor() { // Disable initialization to prevent Parity hack. @@ -131,22 +147,6 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg emit BridgeInitialize(l1Address, _newName, _newSymbol, decimals_); } - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(); - } - _; - } - - modifier onlyNextVersion(uint8 _version) { - // The version should be incremented by 1. Otherwise, the governor risks disabling - // future reinitialization of the token by providing too large a version. - if (_version != _getInitializedVersion() + 1) { - revert NonSequentialVersion(); - } - _; - } - /// @dev Mint tokens to a given account. /// @param _to The account that will receive the created tokens. /// @param _amount The amount that will be created. @@ -165,6 +165,16 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg emit BridgeBurn(_from, _amount); } + /// @dev External function to decode a string from bytes. + function decodeString(bytes calldata _input) external pure returns (string memory result) { + (result) = abi.decode(_input, (string)); + } + + /// @dev External function to decode a uint8 from bytes. + function decodeUint8(bytes calldata _input) external pure returns (uint8 result) { + (result) = abi.decode(_input, (uint8)); + } + function name() public view override returns (string memory) { // If method is not available, behave like a token that does not implement this method - revert on call. if (availableGetters.ignoreName) revert Unimplemented(); @@ -182,14 +192,4 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg if (availableGetters.ignoreDecimals) revert Unimplemented(); return decimals_; } - - /// @dev External function to decode a string from bytes. - function decodeString(bytes calldata _input) external pure returns (string memory result) { - (result) = abi.decode(_input, (string)); - } - - /// @dev External function to decode a uint8 from bytes. - function decodeUint8(bytes calldata _input) external pure returns (uint8 result) { - (result) = abi.decode(_input, (uint8)); - } } diff --git a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol index 8fc3b05b9..03c4bd0c3 100644 --- a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol +++ b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol @@ -29,12 +29,24 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S /// @dev Address of the L1 base token. It can be deposited to mint this L2 token. address public override l1Address; + modifier onlyBridge() { + if (msg.sender != l2Bridge) { + revert Unauthorized(); + } + _; + } + /// @dev Contract is expected to be used as proxy implementation. constructor() { // Disable initialization to prevent Parity hack. _disableInitializers(); } + /// @dev Fallback function to allow receiving Ether. + receive() external payable { + depositTo(msg.sender); + } + /// @notice Initializes a contract token for later use. Expected to be used in the proxy. /// @notice This function is used to integrate the previously deployed WETH token with the bridge. /// @dev Sets up `name`/`symbol`/`decimals` getters. @@ -68,13 +80,6 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S emit Initialize(name_, symbol_, 18); } - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(); - } - _; - } - /// @notice Function for minting tokens on L2, implemented only to be compatible with IL2StandardToken interface. /// Always reverts instead of minting anything! /// Note: Use `deposit`/`depositTo` methods instead. @@ -122,9 +127,4 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S revert WithdrawFailed(); } } - - /// @dev Fallback function to allow receiving Ether. - receive() external payable { - depositTo(msg.sender); - } } diff --git a/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index 407669613..4213d1939 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.20; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev // note we use the IL1ERC20Bridge only to send L1<>L2 messages, // and we use this interface so that when the switch happened the old messages could be processed interface IL1ERC20Bridge { diff --git a/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol b/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol index 30ee71528..53f6708d7 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol @@ -2,22 +2,24 @@ pragma solidity 0.8.20; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2AssetHandler { event BridgeMint( - uint256 indexed _chainId, - bytes32 indexed _assetId, - address indexed _sender, - address _l2Receiver, - uint256 _amount + uint256 indexed chainId, + bytes32 indexed assetId, + address indexed sender, + address l2Receiver, + uint256 amount ); event BridgeBurn( - uint256 indexed _chainId, - bytes32 indexed _assetId, + uint256 indexed chainId, + bytes32 indexed assetId, address indexed l2Sender, - address _receiver, - uint256 _mintValue, - uint256 _amount + address receiver, + uint256 mintValue, + uint256 amount ); function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _transferData) external payable; @@ -27,6 +29,6 @@ interface IL2AssetHandler { uint256 _mintValue, bytes32 _assetId, address _prevMsgSender, - bytes calldata _transferData + bytes calldata _data ) external payable returns (bytes memory _l1BridgeMintData); } diff --git a/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol b/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol index 1245fc3d6..edd677dc6 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol @@ -3,28 +3,29 @@ pragma solidity 0.8.20; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2AssetRouter { - event FinalizeDepositSharedBridge(uint256 chainId, bytes32 indexed assetId, bytes32 assetDataHash); + event FinalizeDepositSharedBridge(uint256 chainId, bytes32 indexed assetId, bytes assetData); event WithdrawalInitiatedSharedBridge( uint256 chainId, address indexed l2Sender, bytes32 indexed assetId, - bytes32 assetDataHash + bytes assetData ); event AssetHandlerRegisteredInitial( bytes32 indexed assetId, - address indexed _assetAddress, + address indexed assetAddress, bytes32 indexed additionalData, address sender ); event AssetHandlerRegistered(bytes32 indexed assetId, address indexed _assetAddress); - function finalizeDeposit(bytes32 _assetId, bytes calldata _data) external; + function finalizeDeposit(bytes32 _assetId, bytes calldata _transferData) external; - function withdraw(bytes32 _assetId, bytes calldata _data) external; + function withdraw(bytes32 _assetId, bytes calldata _transferData) external; function l1Bridge() external view returns (address); diff --git a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol index fc636d6e1..8b44eba55 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.20; import {IL2AssetHandler} from "./IL2AssetHandler.sol"; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2NativeTokenVault is IL2AssetHandler { event FinalizeDeposit( address indexed l1Sender, @@ -21,9 +22,13 @@ interface IL2NativeTokenVault is IL2AssetHandler { uint256 amount ); + event L2TokenBeaconUpdated(address indexed l2TokenBeacon, bytes32 indexed l2TokenProxyBytecodeHash); + function tokenAddress(bytes32 _assetId) external view returns (address); function l2TokenAddress(address _l1Token) external view returns (address); - function setL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external; + function setL2TokenBeacon(address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external; + + function configureL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external; } diff --git a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol b/l2-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol deleted file mode 100644 index ee944bb11..000000000 --- a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -/// @author Matter Labs -interface IL2SharedBridgeLegacy { - function l1TokenAddress(address _l2Token) external view returns (address); - - function l2TokenAddress(address _l1Token) external view returns (address); -} diff --git a/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol b/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol index 6ceb1ae80..b94c7abff 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol @@ -5,9 +5,9 @@ pragma solidity 0.8.20; interface IL2StandardToken { event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); - event BridgeMint(address indexed _account, uint256 _amount); + event BridgeMint(address indexed account, uint256 amount); - event BridgeBurn(address indexed _account, uint256 _amount); + event BridgeBurn(address indexed account, uint256 amount); function bridgeMint(address _account, uint256 _amount) external; diff --git a/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol b/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol index 8d9ed32eb..a73f888ab 100644 --- a/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol +++ b/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.20; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface ILegacyL2SharedBridge { function finalizeDeposit( address _l1Sender, @@ -14,5 +15,7 @@ interface ILegacyL2SharedBridge { function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + function getL1TokenAddress(address _l2Token) external view returns (address); + function l2TokenAddress(address _l1Token) external view returns (address); } diff --git a/l2-contracts/contracts/common/libraries/DataEncoding.sol b/l2-contracts/contracts/common/libraries/DataEncoding.sol new file mode 100644 index 000000000..16c97c11a --- /dev/null +++ b/l2-contracts/contracts/common/libraries/DataEncoding.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L2_NATIVE_TOKEN_VAULT} from "../../L2ContractHelper.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for transfer data encoding and decoding to reduce possibility of errors. + */ +library DataEncoding { + /// @notice Abi.encodes the data required for bridgeMint on remote chain. + /// @param _prevMsgSender The address which initiated the transfer. + /// @param _l2Receiver The address which to receive tokens on remote chain. + /// @param _l1Token The transferred token address. + /// @param _amount The amount of token to be transferred. + /// @param _erc20Metadata The transferred token metadata. + /// @return The encoded bridgeMint data + function encodeBridgeMintData( + address _prevMsgSender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) internal pure returns (bytes memory) { + // solhint-disable-next-line func-named-parameters + return abi.encode(_prevMsgSender, _l2Receiver, _l1Token, _amount, _erc20Metadata); + } + + /// @notice Function decoding transfer data previously encoded with this library. + /// @param _bridgeMintData The encoded bridgeMint data + /// @return _prevMsgSender The address which initiated the transfer. + /// @return _l2Receiver The address which to receive tokens on remote chain. + /// @return _parsedL1Token The transferred token address. + /// @return _amount The amount of token to be transferred. + /// @return _erc20Metadata The transferred token metadata. + function decodeBridgeMintData( + bytes memory _bridgeMintData + ) + internal + pure + returns ( + address _prevMsgSender, + address _l2Receiver, + address _parsedL1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) + { + (_prevMsgSender, _l2Receiver, _parsedL1Token, _amount, _erc20Metadata) = abi.decode( + _bridgeMintData, + (address, address, address, uint256, bytes) + ); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, bytes32 _assetData, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAaddress The address of token that has to be encoded (asset data is the address itself). + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, address _tokenAaddress, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _tokenAaddress)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, bytes32 _assetData) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAddress The address of token that has to be encoded (asset data is the address itself). + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, address _tokenAddress) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT, _tokenAddress)); + } +} diff --git a/l2-contracts/test/erc20.test.ts b/l2-contracts/test/erc20.test.ts index 27c31ed79..ec531f7aa 100644 --- a/l2-contracts/test/erc20.test.ts +++ b/l2-contracts/test/erc20.test.ts @@ -39,6 +39,7 @@ describe("ERC20Bridge", function () { // We won't actually deploy an L1 token in these tests, but we need some address for it. const L1_TOKEN_ADDRESS = "0x1111000000000000000000000000000000001111"; + const L2_ASSET_ROUTER_ADDRESS = "0x0000000000000000000000000000000000010003"; const L2_NATIVE_TOKEN_VAULT_ADDRESS = "0x0000000000000000000000000000000000010004"; const testChainId = 9; @@ -58,18 +59,25 @@ describe("ERC20Bridge", function () { ]); await deployer.deploy(await deployer.loadArtifact("BeaconProxy"), [l2Erc20TokenBeacon.address, "0x"]); const beaconProxyBytecodeHash = hashBytecode((await deployer.loadArtifact("BeaconProxy")).bytecode); - const erc20BridgeContract = await deployer.deploy(await deployer.loadArtifact("L2AssetRouter"), [ - testChainId, - 1, - unapplyL1ToL2Alias(l1BridgeWallet.address), - unapplyL1ToL2Alias(l1BridgeWallet.address), - ]); - erc20Bridge = L2AssetRouterFactory.connect(erc20BridgeContract.address, deployerWallet); + let constructorArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address", "address"], + /// note in real deployment we have to transfer ownership of standard deployer here + [testChainId, 1, unapplyL1ToL2Alias(l1BridgeWallet.address), unapplyL1ToL2Alias(l1BridgeWallet.address)] + ); + await setCode( + deployerWallet, + L2_ASSET_ROUTER_ADDRESS, + (await deployer.loadArtifact("L2AssetRouter")).bytecode, + true, + constructorArgs + ); + + erc20Bridge = L2AssetRouterFactory.connect(L2_ASSET_ROUTER_ADDRESS, deployerWallet); const l2NativeTokenVaultArtifact = await deployer.loadArtifact("L2NativeTokenVault"); - const constructorArgs = ethers.utils.defaultAbiCoder.encode( - ["bytes32", "address", "bool"], + constructorArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "bytes32", "address", "bool"], /// note in real deployment we have to transfer ownership of standard deployer here - [beaconProxyBytecodeHash, governorWallet.address, contractsDeployedAlready] + [1, beaconProxyBytecodeHash, governorWallet.address, contractsDeployedAlready] ); await setCode( deployerWallet, @@ -81,12 +89,11 @@ describe("ERC20Bridge", function () { erc20NativeTokenVault = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, l1BridgeWallet); const governorNTV = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, governorWallet); - await governorNTV.setL2TokenBeacon(false, ethers.constants.AddressZero); + await governorNTV.configureL2TokenBeacon(false, ethers.constants.AddressZero); }); it("Should finalize deposit ERC20 deposit", async function () { const erc20BridgeWithL1BridgeWallet = L2AssetRouterFactory.connect(erc20Bridge.address, proxyAdminWallet); - const l1Depositor = ethers.Wallet.createRandom(); const l2Receiver = ethers.Wallet.createRandom(); const l1Bridge = await hre.ethers.getImpersonatedSigner(l1BridgeWallet.address); diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index fc2138bf1..779330fdc 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": "0x0100005d48cd2fb5b7fde10cf657c00e72da53e3c5ce0dc4ea5cf9ad2ec64de6", - "sourceCodeHash": "0x69d2533e5481ff13e65f4442e650f4b90c46a48ac643cac9798bbbf421194353" + "bytecodeHash": "0x0100005d1aca0cbed0567a1c7e07a1da321386b1665e440da22053addd4639e1", + "sourceCodeHash": "0xea3806fcaf7728463f559fe195d8acdc47a7659d58119e0a51efcf86a691b61b" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c70212baf56e8033abd0494686eb067a28b6a845aa4a79284d904c4c73", - "sourceCodeHash": "0x26060f33c7c63bd1f8a1a2f3b368b97ef8dd939bc53e95090f2c556248b99dce" + "bytecodeHash": "0x010007c7149301f5f29fa1757a600b5088354d9e280a6d8f69bcce4f2dbce660", + "sourceCodeHash": "0x9d2b7376c4cd9b143ddd5dfe001a9faae99b9125ccd45f2915c3ce0099643ed9" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004d0cd31f41763d70f16b5172db7617a7713743a6705a903059d4b9df83", + "bytecodeHash": "0x0100004deb11ca32277ab54e2d036f4d33b4a7e218ced1fee63b5b4713ff50ff", "sourceCodeHash": "0xdde7c49a94cc3cd34c3e7ced1b5ba45e4740df68d26243871edbe393e7298f7a" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100013f89ddacb2fa6e6216f387a238bac9d12b0367d924cb5af80a4cd7dc72", + "bytecodeHash": "0x0100013fc989177bcb6ec29d851cd01f845de31963ea5817ea7a684767c36368", "sourceCodeHash": "0xb0cec0016f481ce023478f71727fbc0d82e967ddc0508e4d47f5c52292a3f790" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e53153fb3d063209d71ae7de8a6df057101ebbd1bf8b42a6f50683e2e0", - "sourceCodeHash": "0x1657e45c2ff4d6a892afaf71dab71bb8dc7db2c6863e66240e83dfdd2535420d" + "bytecodeHash": "0x010004e5711fff19f0048d745b0177b8b73952963b6de79ff4e16c902dbcc091", + "sourceCodeHash": "0xea9627fd5e6e905c268ba801e87bf2d9022bea036982d2b54425f2388b27e6b1" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x010000491ba0449d3042a071ce34729458745517da8f024162a9f06941bf32ff", + "bytecodeHash": "0x0100004937dba13ac3e393def7fe6cf01da88bbe9b087c397e950301fe14377d", "sourceCodeHash": "0x217e65f55c8add77982171da65e0db8cc10141ba75159af582973b332a4e098a" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055dab66880f8a71739a4db267e2d5c282930a06cee8e1379d94ad5a3a36", + "bytecodeHash": "0x0100055d7adab6efac115df578d88bc113738dc6ad811329c7575c2af3d91756", "sourceCodeHash": "0xeb5ac8fc83e1c8619db058a9b6973958bd6ed1b6f4938f8f4541d702f12e085d" }, { @@ -59,63 +59,63 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x0100003b347a54e5bafb392660e0906dd88ff2bf52006c0301379b5af3a48f2f", + "bytecodeHash": "0x0100003bf60f81cb3074170af6420e8d74b710fea0b1fa04e291a080ec17f98a", "sourceCodeHash": "0x4212e99cbc1722887cfb5b4cb967f278ac8642834786f0e3c6f3b324a9316815" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006fc86ac178e134ccc1d8a2602ad6dd866116a1a2c771c92964b2d31d03", + "bytecodeHash": "0x0100006f5cf65a28234e0791927389664cced66dd3d600aefbe120d63e9debae", "sourceCodeHash": "0x8da495a9fc5aa0d7d20a165a4fc8bc77012bec29c472015ea5ecc0a2bd706137" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010001e9bb698fa7d454bede5a444e5a73e38c4d6aeb75061b6315b68128c6f2", + "bytecodeHash": "0x010001e9765b885f7e6422722e36e6d375beffc916047f5cec419d11d178baea", "sourceCodeHash": "0xd83b345b8633affb0bba2296fec9424b4fe9483b60c20ca407d755857a385d8e" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x01000105004daa8cb1733999aa434d6dd6bd3fac19fc4381323e222483ff784e", + "bytecodeHash": "0x0100010517992363aa510731a717db3c7740d1c31e69718090d07f73d47ba960", "sourceCodeHash": "0x4cdafafd4cfdf410b31641e14487ea657be3af25e5ec1754fcd7ad67ec23d8be" }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x010000977198a543ea10cb11397823868d7337e0dd26ae65518f62705f1cb6ba", + "bytecodeHash": "0x010000975d32dfb09a7935f71e54783c30c9eaab051e961d7f834730e52f3a14", "sourceCodeHash": "0xcb190d0dfd41bbc809409a8aa04a4847b86edfe010b1d75e23b4c8d07b13a9d0" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005db76a23815f8c8df5bd35d3de68d5d188f258171fbf1b16e46d5f4388", + "bytecodeHash": "0x0100005d3182a51477d7ee3488aafab351bb5f0560412e4df7e5a5e21ca87cd5", "sourceCodeHash": "0x4834adf62dbaefa1a1c15d36b5ad1bf2826e7d888a17be495f7ed4e4ea381aa8" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000dbd70081e041f306041a0098438472df26d052b14594fae982dab57c81", + "bytecodeHash": "0x010000db9e6c4608ca06cd3a69484b23f5e4ee196fa046cb2db8c0b56d3a2163", "sourceCodeHash": "0xaa2ed3a26af30032c00a612ac327e0cdf5288b7c932ae903462355f863f950cb" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x01000049ac0f8fcba1b88c9abc8a7c4ffadf5398ba1afa8e906cc779a0e7e5c4", + "bytecodeHash": "0x01000049825a39e7057700666867a2b2be806c15d9b2addb60d335bb61b405d9", "sourceCodeHash": "0x0da0d1279f906147a40e278f52bf3e4d5d4f24225935e4611cc04f4b387b5286" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a71a8068cddac9fa9f1eb349060ab06f421c927c87eef033f9e61abe4e", + "bytecodeHash": "0x010001a722bf92cd15264a662582a6806db24101493ad7a1f202428a2a10e7bc", "sourceCodeHash": "0x532a962209042f948e8a13e3f4cf12b6d53631e0fc5fa53083c7e2d8062771c0" }, { @@ -185,35 +185,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x010003cbb7bf765b4bad4b3d0e8c1a5169a2024ee3f3424aebb755874c36a6b5", - "sourceCodeHash": "0x081cdfc1e66eef8dedbc1b950eb87bd97b5d270870325e2b6121ba96f48dd89c" + "bytecodeHash": "0x010003cb983fb1cded326ff4429b8a637a7b045233c427dc498373be58312969", + "sourceCodeHash": "0xa4f83a28bcc3d3a79c197a77de03dce464b2141c3aaf970ad3f3487f41ae5690" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010009551e7e9dd0c1a0f2d805ea92cb1ae6ac1b705d66b72b0d7d05bcd019b5", - "sourceCodeHash": "0xf66f1214b4dba520efd09b4c5d27a4d8ec4d8f24269ab63467e7aa3a8a789c6c" + "bytecodeHash": "0x010009559ef1268a8b83687828c5dfc804c58a018028694fc931df712fb67f58", + "sourceCodeHash": "0xe7970c1738f2817b50bfcd16038227c5c059f12309407977df453bc6d365d31e" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010008db0b304a1982af85231f37200d3e8eee3f1df64bb0da41154a03a3de73", - "sourceCodeHash": "0xf7149445e5a20dca1ab7bf5a436ed4e5c05fc4ff4909ce0eb138306550eb9e96" + "bytecodeHash": "0x010008db4c71130d6a96d77f2d3bf3573a5cddc72ecee030ecc9c3bc22764039", + "sourceCodeHash": "0x1b6ef61d0dbbbaa049946b95dc6d12d9335baed03b8c3364a0cbdb404495c045" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100095b00b40a8db58a82971d43f075aa8327884c2bbfc1a81c46a330cfbc95", - "sourceCodeHash": "0xdf49fb7fff9484dda405680bf98658c3ad0ed3ff15251e0c43f0b2477e8ce7c3" + "bytecodeHash": "0x0100095b01a95cb5caa633702442aeeac8ea64bf4dab31d534e0bb815b5c2820", + "sourceCodeHash": "0xb5a2f9d7d8990f9a1296f699573a5dffe3a1d2ed53d9d3c60b313cd9443221ab" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008ebdd398893ea862b2ae152113530caaa82dcde2234c41bb8b1ef0cb3a0", - "sourceCodeHash": "0x594ca6699f30a6bdef90d12bb36582da8f0120cae4ca31fa3926778d1db81f84" + "bytecodeHash": "0x010008eba57f69c88344eced34109c75d34b4bf84db66f47b47b4579b930355e", + "sourceCodeHash": "0xb7697ee1c00b1b8af52c166e3a6deb862281616ca8861ab3aa0b51e34aed8715" } ] diff --git a/system-contracts/contracts/AccountCodeStorage.sol b/system-contracts/contracts/AccountCodeStorage.sol index 5df2fdd8b..820d57305 100644 --- a/system-contracts/contracts/AccountCodeStorage.sol +++ b/system-contracts/contracts/AccountCodeStorage.sol @@ -14,7 +14,7 @@ import {Unauthorized, InvalidCodeHash, CodeHashReason} from "./SystemContractErr * @dev Code hash is not strictly a hash, it's a structure where the first byte denotes the version of the hash, * the second byte denotes whether the contract is constructed, and the next two bytes denote the length in 32-byte words. * And then the next 28 bytes are the truncated hash. - * @dev In this version of zkSync, the first byte of the hash MUST be 1. + * @dev In this version of ZKsync, the first byte of the hash MUST be 1. * @dev The length of each bytecode MUST be odd. It's internal code format requirements, due to padding of SHA256 function. * @dev It is also assumed that all the bytecode hashes are *known*, i.e. the full bytecodes * were published on L1 as calldata. This contract trusts the ContractDeployer and the KnownCodesStorage diff --git a/system-contracts/contracts/BootloaderUtilities.sol b/system-contracts/contracts/BootloaderUtilities.sol index 0aafee7be..642f9641e 100644 --- a/system-contracts/contracts/BootloaderUtilities.sol +++ b/system-contracts/contracts/BootloaderUtilities.sol @@ -177,7 +177,7 @@ contract BootloaderUtilities is IBootloaderUtilities { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory rEncoded; @@ -276,7 +276,7 @@ contract BootloaderUtilities is IBootloaderUtilities { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory rEncoded; diff --git a/system-contracts/contracts/Constants.sol b/system-contracts/contracts/Constants.sol index e8a8db0a5..b253d1e61 100644 --- a/system-contracts/contracts/Constants.sol +++ b/system-contracts/contracts/Constants.sol @@ -16,7 +16,7 @@ import {IBootloaderUtilities} from "./interfaces/IBootloaderUtilities.sol"; import {IPubdataChunkPublisher} from "./interfaces/IPubdataChunkPublisher.sol"; import {IMessageRoot} from "./interfaces/IMessageRoot.sol"; -/// @dev All the system contracts introduced by zkSync have their addresses +/// @dev All the system contracts introduced by ZKsync have their addresses /// started from 2^15 in order to avoid collision with Ethereum precompiles. uint160 constant SYSTEM_CONTRACTS_OFFSET = {{SYSTEM_CONTRACTS_OFFSET}}; // 2^15 diff --git a/system-contracts/contracts/ContractDeployer.sol b/system-contracts/contracts/ContractDeployer.sol index b03109862..2584c3a52 100644 --- a/system-contracts/contracts/ContractDeployer.sol +++ b/system-contracts/contracts/ContractDeployer.sol @@ -15,7 +15,7 @@ import {Unauthorized, InvalidNonceOrderingChange, ValuesNotEqual, EmptyBytes32, /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev - * @notice System smart contract that is responsible for deploying other smart contracts on zkSync. + * @notice System smart contract that is responsible for deploying other smart contracts on ZKsync. * @dev The contract is responsible for generating the address of the deployed smart contract, * incrementing the deployment nonce and making sure that the constructor is never called twice in a contract. * Note, contracts with bytecode that have already been published to L1 once diff --git a/system-contracts/contracts/libraries/SystemContractsCaller.sol b/system-contracts/contracts/libraries/SystemContractsCaller.sol index d964fbbe7..9364b68ec 100644 --- a/system-contracts/contracts/libraries/SystemContractsCaller.sol +++ b/system-contracts/contracts/libraries/SystemContractsCaller.sol @@ -6,7 +6,7 @@ import {MSG_VALUE_SYSTEM_CONTRACT, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT} from "../C import {Utils} from "./Utils.sol"; // Addresses used for the compiler to be replaced with the -// zkSync-specific opcodes during the compilation. +// ZKsync-specific opcodes during the compilation. // IMPORTANT: these are just compile-time constants and are used // only if used in-place by Yul optimizer. address constant TO_L1_CALL_ADDRESS = address((1 << 16) - 1); diff --git a/system-contracts/contracts/libraries/TransactionHelper.sol b/system-contracts/contracts/libraries/TransactionHelper.sol index fbdc57919..701fd9985 100644 --- a/system-contracts/contracts/libraries/TransactionHelper.sol +++ b/system-contracts/contracts/libraries/TransactionHelper.sol @@ -11,7 +11,7 @@ import {RLPEncoder} from "./RLPEncoder.sol"; import {EfficientCall} from "./EfficientCall.sol"; import {UnsupportedTxType, InvalidInput, UnsupportedPaymasterFlow} from "../SystemContractErrors.sol"; -/// @dev The type id of zkSync's EIP-712-signed transaction. +/// @dev The type id of ZKsync's EIP-712-signed transaction. uint8 constant EIP_712_TX_TYPE = 0x71; /// @dev The type id of legacy transactions. @@ -21,7 +21,7 @@ uint8 constant EIP_2930_TX_TYPE = 0x01; /// @dev The type id of EIP1559 transactions. uint8 constant EIP_1559_TX_TYPE = 0x02; -/// @notice Structure used to represent a zkSync transaction. +/// @notice Structure used to represent a ZKsync transaction. struct Transaction { // The type of the transaction. uint256 txType; @@ -114,7 +114,7 @@ library TransactionHelper { } } - /// @notice Encode hash of the zkSync native transaction type. + /// @notice Encode hash of the ZKsync native transaction type. /// @return keccak256 hash of the EIP-712 encoded representation of transaction function _encodeHashEIP712Transaction(Transaction calldata _transaction) private view returns (bytes32) { bytes32 structHash = keccak256( @@ -223,7 +223,7 @@ library TransactionHelper { // Hash of EIP2930 transactions is encoded the following way: // H(0x01 || RLP(chain_id, nonce, gas_price, gas_limit, destination, amount, data, access_list)) // - // Note, that on zkSync access lists are not supported and should always be empty. + // Note, that on ZKsync access lists are not supported and should always be empty. // Encode all fixed-length params to avoid "stack too deep error" bytes memory encodedFixedLengthParams; @@ -261,7 +261,7 @@ library TransactionHelper { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory encodedListLength; @@ -295,7 +295,7 @@ library TransactionHelper { // Hash of EIP1559 transactions is encoded the following way: // H(0x02 || RLP(chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list)) // - // Note, that on zkSync access lists are not supported and should always be empty. + // Note, that on ZKsync access lists are not supported and should always be empty. // Encode all fixed-length params to avoid "stack too deep error" bytes memory encodedFixedLengthParams; @@ -335,7 +335,7 @@ library TransactionHelper { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory encodedListLength; diff --git a/system-contracts/contracts/libraries/Utils.sol b/system-contracts/contracts/libraries/Utils.sol index 4f535e2c1..cfad32cf7 100644 --- a/system-contracts/contracts/libraries/Utils.sol +++ b/system-contracts/contracts/libraries/Utils.sol @@ -7,7 +7,7 @@ import {MalformedBytecode, BytecodeError, Overflow} from "../SystemContractError /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev - * @dev Common utilities used in zkSync system contracts + * @dev Common utilities used in ZKsync system contracts */ library Utils { /// @dev Bit mask of bytecode hash "isConstructor" marker diff --git a/tools/data/verifier_contract_template.txt b/tools/data/verifier_contract_template.txt index 972123961..5ef32b2c5 100644 --- a/tools/data/verifier_contract_template.txt +++ b/tools/data/verifier_contract_template.txt @@ -8,14 +8,14 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @author Matter Labs /// @notice Modified version of the Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of /// Knowledge (PLONK) verifier. -/// Modifications have been made to optimize the proof system for zkSync hyperchain circuits. +/// Modifications have been made to optimize the proof system for ZKsync hyperchain circuits. /// @dev Contract was generated from a verification key with a hash of 0x{{vk_hash}} /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. /// @dev For a better understanding of the verifier algorithm please refer to the following papers: /// * Original Plonk Article: https://eprint.iacr.org/2019/953.pdf /// * Original LookUp Article: https://eprint.iacr.org/2020/315.pdf -/// * Plonk for zkSync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf +/// * Plonk for ZKsync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf /// The notation used in the code is the same as in the papers. /* solhint-enable max-line-length */ contract Verifier is IVerifier { From 0144965996df60731bca40a63ac916341d01ac8a Mon Sep 17 00:00:00 2001 From: Marcin M <128217157+mm-zk@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:31:20 +0200 Subject: [PATCH 06/26] fix: more docs, don't accept new batches when chain was migrated (#686) --- .../contracts/bridgehub/Bridgehub.sol | 22 +++++++++------ .../chain-deps/ZkSyncHyperchainStorage.sol | 3 +- .../chain-deps/facets/Admin.sol | 3 ++ .../chain-deps/facets/Executor.sol | 14 +++++++--- l1-contracts/src.ts/deploy.ts | 28 ++++++++++++------- 5 files changed, 47 insertions(+), 23 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index e69b326d2..7fa28c990 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -26,25 +26,28 @@ import {L2CanonicalTransaction} from "../common/Messaging.sol"; /// @dev The Bridgehub contract serves as the primary entry point for L1<->L2 communication, /// facilitating interactions between end user and bridges. /// It also manages state transition managers, base tokens, and chain registrations. +/// Bridgehub is also an IL1AssetHandler for the chains themselves, which is used to migrate the chains +/// between different settlement layers (for example from L1 to Gateway). contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, PausableUpgradeable { /// @notice the asset id of Eth bytes32 internal immutable ETH_TOKEN_ASSET_ID; - /// @notice The chain id of L1, this contract will be deployed on multiple layers. + /// @notice The chain id of L1. This contract can be deployed on multiple layers, but this value is still equal to the + /// L1 that is at the most base layer. uint256 public immutable L1_CHAIN_ID; - /// @notice all the ether is held by the weth bridge + /// @notice all the ether and ERC20 tokens are held by NativeVaultToken managed by this shared Bridge. IL1AssetRouter public sharedBridge; - /// @notice we store registered stateTransitionManagers + /// @notice StateTransitionManagers that are registered, and ZKchains that use these STMs can use this bridgehub as settlement layer. mapping(address _stateTransitionManager => bool) public stateTransitionManagerIsRegistered; /// @notice we store registered tokens (for arbitrary base token) mapping(address _baseToken => bool) public tokenIsRegistered; - /// @notice chainID => StateTransitionManager contract address, storing StateTransitionManager + /// @notice chainID => StateTransitionManager contract address, STM that is managing rules for a given ZKchain. mapping(uint256 _chainId => address) public stateTransitionManager; - /// @notice chainID => baseToken contract address, storing baseToken + /// @notice chainID => baseToken contract address, token that is used as 'base token' by a given child chain. mapping(uint256 _chainId => address) public baseToken; /// @dev used to manage non critical updates @@ -90,6 +93,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { _disableInitializers(); L1_CHAIN_ID = _l1ChainId; + // TODO: this assumes that the bridgehub is deployed only on the chains that have ETH as base token. ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); _transferOwnership(_owner); } @@ -220,7 +224,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus Chain Registration //////////////////////////////////////////////////////////////*/ - /// @notice register new chain + /// @notice register new chain. New chains can be only registered on Bridgehub deployed on L1. Later they can be moved to any other layer. /// @notice for Eth the baseToken address is 1 /// @param _chainId the chainId of the chain /// @param _stateTransitionManager the state transition manager address @@ -239,8 +243,10 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes calldata _initData, bytes[] calldata _factoryDeps ) external onlyOwnerOrAdmin nonReentrant whenNotPaused returns (uint256) { + require(L1_CHAIN_ID == block.chainid, "BH: New chain registration only allowed on L1"); require(_chainId != 0, "BH: chainId cannot be 0"); require(_chainId <= type(uint48).max, "BH: chainId too large"); + require(_chainId != block.chainid, "BH: chain id must not match current chainid"); require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered"); require(tokenIsRegistered[_baseToken], "BH: token not registered"); @@ -520,7 +526,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @param _data the data for the migration function bridgeBurn( uint256 _settlementChainId, - uint256, + uint256, // mintValue bytes32 _assetId, address _prevMsgSender, bytes calldata _data @@ -549,7 +555,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @param _assetId the assetId of the chain's STM /// @param _bridgehubMintData the data for the mint function bridgeMint( - uint256, + uint256, // chainId bytes32 _assetId, bytes calldata _bridgehubMintData ) external payable override onlyAssetRouter returns (address l1Receiver) { diff --git a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol index 636756d43..c4abf9007 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol @@ -161,7 +161,8 @@ struct ZkSyncHyperchainStorage { address l2DAValidator; /// @dev the Asset Id of the baseToken bytes32 baseTokenAssetId; - /// @dev address of the synclayer, only set on L1 if settling on it + /// @dev If this ZKchain settles on this chain, then this is zero. Otherwise it is the address of the ZKchain that is a + /// settlement layer for this ZKchain. (think about it as a 'forwarding' address for the chain that migrated away). address settlementLayer; /// @dev Priority tree, the new data structure for priority queue PriorityTree.Tree priorityTree; diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 9b90bde24..2b67b88a0 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -265,6 +265,9 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { s.l2SystemContractsUpgradeTxHash = _commitment.l2SystemContractsUpgradeTxHash; s.l2SystemContractsUpgradeBatchNumber = _commitment.l2SystemContractsUpgradeBatchNumber; + // Set the settlement to 0 - as this is the current settlement chain. + s.settlementLayer = address(0); + _setDAValidatorPair(address(0), address(0)); emit MigrationComplete(); diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 34fd00626..b2f079b14 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -31,6 +31,12 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { /// @inheritdoc IZkSyncHyperchainBase string public constant override getName = "ExecutorFacet"; + /// @dev Checks that the chain is connected to the current bridehub and not migrated away. + modifier chainOnCurrentBridgehub() { + require(s.settlementLayer == address(0), "Chain was migrated"); + _; + } + /// @dev Process one batch commit using the previous batch StoredBatchInfo /// @dev returns new batch StoredBatchInfo /// @notice Does not change storage @@ -202,7 +208,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { function _commitBatches( StoredBatchInfo memory _lastCommittedBatchData, CommitBatchInfo[] calldata _newBatchesData - ) internal { + ) internal chainOnCurrentBridgehub { // check that we have the right protocol version // three comments: // 1. A chain has to keep their protocol version up to date, as processing a block requires the latest or previous protocol version @@ -393,7 +399,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { function _executeBatches( StoredBatchInfo[] calldata _batchesData, PriorityOpsBatchInfo[] calldata _priorityOpsData - ) internal { + ) internal chainOnCurrentBridgehub { uint256 nBatches = _batchesData.length; require(_batchesData.length == _priorityOpsData.length, "bp"); @@ -443,7 +449,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { StoredBatchInfo calldata _prevBatch, StoredBatchInfo[] calldata _committedBatches, ProofInput calldata _proof - ) internal { + ) internal chainOnCurrentBridgehub { // Save the variables into the stack to save gas on reading them later uint256 currentTotalBatchesVerified = s.totalBatchesVerified; uint256 committedBatchesLength = _committedBatches.length; @@ -506,7 +512,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { _revertBatches(_newLastBatch); } - function _revertBatches(uint256 _newLastBatch) internal { + function _revertBatches(uint256 _newLastBatch) internal chainOnCurrentBridgehub { require(s.totalBatchesCommitted > _newLastBatch, "v1"); // The last committed batch is less than new last batch require(_newLastBatch >= s.totalBatchesExecuted, "v2"); // Already executed batches cannot be reverted diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 2ac5a0473..84ee4b438 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -387,9 +387,8 @@ export class Deployer { if (this.verbose) { console.log( - `Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}, tx hash ${rec.transactionHash}, expected address: ${ - proxyAdmin.address - }` + `Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}, tx hash ${rec.transactionHash}, expected address: + ${proxyAdmin.address}` ); console.log(`CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=${proxyAdmin.address}`); } @@ -401,9 +400,8 @@ export class Deployer { if (this.verbose) { console.log( - `ProxyAdmin ownership transferred to Governance in tx ${ - receipt.transactionHash - }, gas used: ${receipt.gasUsed.toString()}` + `ProxyAdmin ownership transferred to Governance in tx + ${receipt.transactionHash}, gas used: ${receipt.gasUsed.toString()}` ); } } @@ -1083,6 +1081,7 @@ export class Deployer { } } + // Main function to move the current chain (that is hooked to l1), on top of the syncLayer chain. public async moveChainToGateway(gatewayChainId: string, gasPrice: BigNumberish) { const bridgehub = this.bridgehubContract(this.deployWallet); // Just some large gas limit that should always be enough @@ -1091,6 +1090,7 @@ export class Deployer { await bridgehub.l2TransactionBaseCost(gatewayChainId, gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) ).mul(5); + // We are creating the new DiamondProxy for our chain, to be deployed on top of sync Layer. const newAdmin = this.deployWallet.address; const diamondCutData = await this.initialZkSyncHyperchainDiamondCut(); const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); @@ -1104,17 +1104,26 @@ export class Deployer { // console.log("bridgehubData", bridgehubData) // console.log("this.addresses.ChainAssetInfo", this.addresses.ChainAssetInfo) + + // The stmAssetIFromChainId gives us a unique 'asset' identifier for a given chain. + const chainAssetId = await bridgehub.stmAssetIdFromChainId(this.chainId); + console.log("Chain asset id is: ", chainAssetId); + let sharedBridgeData = ethers.utils.defaultAbiCoder.encode( ["bytes32", "bytes"], - [await bridgehub.stmAssetIdFromChainId(this.chainId), bridgehubData] + [chainAssetId, bridgehubData] ); + // The 0x01 is the encoding for the L1AssetRouter. sharedBridgeData = "0x01" + sharedBridgeData.slice(2); + // And now we 'transfer' the chain through the bridge (it behaves like a 'regular' asset, where we 'freeze' it in L1 + // and then create on SyncLayer). You can see these methods in Admin.sol (part of DiamondProxy). const receipt = await this.executeChainAdminMulticall([ { target: bridgehub.address, data: bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ + // These arguments must match L2TransactionRequestTwoBridgesOuter struct. { chainId: gatewayChainId, mintValue: expectedCost, @@ -1256,9 +1265,8 @@ export class Deployer { const receiptRegisterValidator = await txRegisterValidator.wait(); if (this.verbose) { console.log( - `Validator registered, gas used: ${receiptRegisterValidator.gasUsed.toString()}, tx hash: ${ - txRegisterValidator.hash - }` + `Validator registered, gas used: ${receiptRegisterValidator.gasUsed.toString()}, tx hash: + ${txRegisterValidator.hash}` ); } From 4fb4ab2d79e485eca529eb0fe9683147f246ed4b Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 12:00:19 +0200 Subject: [PATCH 07/26] set of fixes, including extended gateway calldata da --- da-contracts/contracts/CalldataDA.sol | 53 ++++++++++++------- da-contracts/contracts/IL1DAValidator.sol | 19 +++---- .../contracts/RollupL1DAValidator.sol | 1 + .../contracts/ValidiumL1DAValidator.sol | 1 + .../contracts/bridgehub/Bridgehub.sol | 39 +++++++++----- .../contracts/bridgehub/IBridgehub.sol | 2 + .../StateTransitionManager.sol | 9 +--- .../chain-deps/facets/Executor.sol | 1 + .../chain-interfaces/IL1DAValidator.sol | 18 ++++--- .../data-availability/CalldataDA.sol | 39 +++++++++----- .../data-availability/CalldataDAGateway.sol | 34 ++++++++++++ .../RelayedSLDAValidator.sol | 20 ++++--- .../RelayedSLDAValidator.t.sol | 4 +- 13 files changed, 163 insertions(+), 77 deletions(-) create mode 100644 l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol diff --git a/da-contracts/contracts/CalldataDA.sol b/da-contracts/contracts/CalldataDA.sol index 6a2f0f5a0..48dcc8d01 100644 --- a/da-contracts/contracts/CalldataDA.sol +++ b/da-contracts/contracts/CalldataDA.sol @@ -4,17 +4,21 @@ pragma solidity 0.8.24; // solhint-disable gas-custom-errors, reason-string -import {BLOB_SIZE_BYTES} from "./DAUtils.sol"; +/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element +/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element +/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. +uint256 constant BLOB_SIZE_BYTES = 126_976; -uint256 constant BLOBS_SUPPORTED = 6; - -// the state diff hash, hash of pubdata + the number of blobs. +/// @dev The state diff hash, hash of pubdata + the number of blobs. uint256 constant BLOB_DATA_OFFSET = 65; -/// @notice Contract that contains the functionality for processing the calldata DA. +/// @dev The size of the commitment for a single blob. +uint256 constant BLOB_COMMITMENT_SIZE = 32; + +/// @notice Contract that contains the functionality for process the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { - /// @notice Parses the input that the l2 DA validator has provided to the contract. + /// @notice Parses the input that the l2 Da validator has provided to the contract. /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// @param _operatorDAInput The DA input by the operator provided on L1. @@ -33,7 +37,7 @@ abstract contract CalldataDA { bytes calldata l1DaInput ) { - // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: + // The preimage under the hash `l2DAValidatorOutputHash` is expected to be in the following format: // - First 32 bytes are the hash of the uncompressed state diff. // - Then, there is a 32-byte hash of the full pubdata. // - Then, there is the 1-byte number of blobs published. @@ -54,19 +58,14 @@ abstract contract CalldataDA { require(_operatorDAInput.length >= BLOB_DATA_OFFSET + 32 * blobsProvided, "invalid blobs hashes"); - assembly { - // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. - let blobsPtr := add(blobsLinearHashes, 0x20) - let inputPtr := add(_operatorDAInput.offset, BLOB_DATA_OFFSET) - calldatacopy(blobsPtr, inputPtr, mul(blobsProvided, 32)) - } + cloneCalldata(blobsLinearHashes, _operatorDAInput[BLOB_DATA_OFFSET:], blobsProvided); uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; - // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. + // Now, we need to double check that the provided input was indeed retutned by the L2 DA validator. require(keccak256(_operatorDAInput[:ptr]) == _l2DAValidatorOutputHash, "invalid l2 DA output hash"); - // The rest of the output was provided specifically by the operator + // The rest of the output were provided specifically by the operator l1DaInput = _operatorDAInput[ptr:]; } @@ -81,7 +80,7 @@ abstract contract CalldataDA { bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput - ) internal pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + ) internal virtual pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { require(_blobsProvided == 1, "one blob with calldata"); // We typically do not know whether we'll use calldata or blobs at the time when @@ -89,11 +88,27 @@ abstract contract CalldataDA { blobCommitments = new bytes32[](_maxBlobsSupported); - _pubdata = _pubdataInput[:_pubdataInput.length - 32]; + _pubdata = _pubdataInput[:_pubdataInput.length - BLOB_COMMITMENT_SIZE]; - // FIXME: allow larger lengths for Gateway-based chains. require(_pubdata.length <= BLOB_SIZE_BYTES, "cz"); require(_fullPubdataHash == keccak256(_pubdata), "wp"); - blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - 32:_pubdataInput.length]); + blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - BLOB_COMMITMENT_SIZE:_pubdataInput.length]); + } + + /// @notice Method that clones a slice of calldata into a bytes32[] memory array. + /// @param _dst The destination array. + /// @param _input The input calldata. + /// @param _len The length of the slice in 32-byte words to clone. + function cloneCalldata( + bytes32[] memory _dst, + bytes calldata _input, + uint256 _len + ) internal pure { + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let dstPtr := add(_dst, 0x20) + let inputPtr := _input.offset + calldatacopy(dstPtr, inputPtr, mul(_len, 32)) + } } } diff --git a/da-contracts/contracts/IL1DAValidator.sol b/da-contracts/contracts/IL1DAValidator.sol index 3b4c339b8..c22e9c557 100644 --- a/da-contracts/contracts/IL1DAValidator.sol +++ b/da-contracts/contracts/IL1DAValidator.sol @@ -14,21 +14,22 @@ struct L1DAValidatorOutput { bytes32[] blobsOpeningCommitments; } -// TODO: require EIP165 support as this will allow changes for future compatibility. interface IL1DAValidator { /// @notice The function that checks the data availability for the given batch input. - /// @param chainId The chain id of the chain that is being committed. - /// @param l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. - /// @param operatorDAInput The DA input by the operator provided on L1. - /// @param maxBlobsSupported The maximal number of blobs supported by the chain. + /// @param _chainId The chain id of the chain that is being committed. + /// @param _chainId The batch number for which the data availability is being checked. + /// @param _l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. + /// @param _operatorDAInput The DA input by the operator provided on L1. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// We provide this value for future compatibility. /// This is needed because the corresponding `blobsLinearHashes`/`blobsOpeningCommitments` /// in the `L1DAValidatorOutput` struct will have to have this length as it is required /// to be static by the circuits. function checkDA( - uint256 chainId, - bytes32 l2DAValidatorOutputHash, - bytes calldata operatorDAInput, - uint256 maxBlobsSupported + uint256 _chainId, + uint256 _batchNumber, + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported ) external returns (L1DAValidatorOutput memory output); } diff --git a/da-contracts/contracts/RollupL1DAValidator.sol b/da-contracts/contracts/RollupL1DAValidator.sol index 37ea2e433..d9077b20b 100644 --- a/da-contracts/contracts/RollupL1DAValidator.sol +++ b/da-contracts/contracts/RollupL1DAValidator.sol @@ -40,6 +40,7 @@ contract RollupL1DAValidator is IL1DAValidator, CalldataDA { /// @inheritdoc IL1DAValidator function checkDA( uint256, // _chainId + uint256, // _batchNumber bytes32 _l2DAValidatorOutputHash, bytes calldata _operatorDAInput, uint256 _maxBlobsSupported diff --git a/da-contracts/contracts/ValidiumL1DAValidator.sol b/da-contracts/contracts/ValidiumL1DAValidator.sol index f525e04b8..501a56879 100644 --- a/da-contracts/contracts/ValidiumL1DAValidator.sol +++ b/da-contracts/contracts/ValidiumL1DAValidator.sol @@ -9,6 +9,7 @@ import {IL1DAValidator, L1DAValidatorOutput} from "./IL1DAValidator.sol"; contract ValidiumL1DAValidator is IL1DAValidator { function checkDA( uint256, // _chainId + uint256, // _batchNumber bytes32, // _l2DAValidatorOutputHash bytes calldata _operatorDAInput, uint256 // maxBlobsSupported diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 7fa28c990..d8024e800 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -56,10 +56,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev used to accept the admin role address private pendingAdmin; - // FIXME: `messageRoot` DOES NOT contain messages that come from the current layer and go to the settlement layer. - // it may make sense to store the final root somewhere for interop purposes. - // Though maybe it can be postponed. /// @notice The contract that stores the cross-chain message root for each chain and the aggregated root. + /// @dev Note that the message root does not contain messages from the chain it is deployed on. It may + /// be added later on if needed. IMessageRoot public override messageRoot; /// @notice Mapping from chain id to encoding of the base token used for deposits / withdrawals @@ -89,6 +88,11 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _; } + modifier onlyL1() { + require(L1_CHAIN_ID == block.chainid, "BH: not L1"); + _; + } + /// @notice to avoid parity hack constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { _disableInitializers(); @@ -204,18 +208,27 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus function registerSettlementLayer( uint256 _newSettlementLayerChainId, bool _isWhitelisted - ) external onlyChainSTM(_newSettlementLayerChainId) { + ) external onlyChainSTM(_newSettlementLayerChainId) onlyL1 { whitelistedSettlementLayers[_newSettlementLayerChainId] = _isWhitelisted; - - // TODO: emit event + emit SettlementLayerRegistered(_newSettlementLayerChainId, _isWhitelisted); } /// @dev Used to set the assetAddress for a given assetInfo. /// @param _additionalData the additional data to identify the asset /// @param _assetAddress the asset handler address function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetAddress) external { - address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); // Todo: this might be dangerous. We should decide based on the tx type. - bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); /// todo make other asse + // It is a simplified version of the logic used by the AssetRouter to manage asset handlers. + // STM's assetId is `keccak256(abi.encode(L1_CHAIN_ID, stmDeployer, stmAddress))`. + // And the STMDeployer is considered the deployment tracker for the STM asset. + // + // The STMDeployer will call this method to set the asset handler address for the assetId. + // If the chain is not the same as L1, we assume that it is done via L1->L2 communication and so we unalias the sender. + // + // For simpler handling we allow anyone to call this method. It is okay, since during bridging operations + // it is double checked that `assetId` is indeed derived from the `stmDeployer`. + + address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); + bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); stmAssetIdToAddress[assetInfo] = _assetAddress; emit AssetRegistered(assetInfo, _assetAddress, _additionalData, msg.sender); } @@ -242,7 +255,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus address _admin, bytes calldata _initData, bytes[] calldata _factoryDeps - ) external onlyOwnerOrAdmin nonReentrant whenNotPaused returns (uint256) { + ) external onlyOwnerOrAdmin nonReentrant whenNotPaused onlyL1 returns (uint256) { require(L1_CHAIN_ID == block.chainid, "BH: New chain registration only allowed on L1"); require(_chainId != 0, "BH: chainId cannot be 0"); require(_chainId <= type(uint48).max, "BH: chainId too large"); @@ -303,7 +316,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// In case allowance is provided to the Shared Bridge, then it will be transferred to NTV. function requestL2TransactionDirect( L2TransactionRequestDirect calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { + ) external payable override nonReentrant whenNotPaused onlyL1 returns (bytes32 canonicalTxHash) { // Note: If the hyperchain with corresponding `chainId` is not yet created, // the transaction will revert on `bridgehubRequestL2Transaction` as call to zero address. { @@ -353,7 +366,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @param _request the request for the L2 transaction function requestL2TransactionTwoBridges( L2TransactionRequestTwoBridgesOuter calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { + ) external payable override nonReentrant whenNotPaused onlyL1 returns (bytes32 canonicalTxHash) { require( _request.secondBridgeAddress > BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, "BH: second bridge address too low" @@ -530,7 +543,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes32 _assetId, address _prevMsgSender, bytes calldata _data - ) external payable override onlyAssetRouter returns (bytes memory bridgehubMintData) { + ) external payable override onlyAssetRouter onlyL1 returns (bytes memory bridgehubMintData) { require(whitelistedSettlementLayers[_settlementChainId], "BH: SL not whitelisted"); (uint256 _chainId, bytes memory _stmData, bytes memory _chainData) = abi.decode(_data, (uint256, bytes, bytes)); @@ -588,7 +601,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes32 _assetId, address _depositSender, bytes calldata _data - ) external payable override onlyAssetRouter {} + ) external payable override onlyAssetRouter onlyL1 {} /*////////////////////////////////////////////////////////////// PAUSE diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 1184ed068..3195dc6e3 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -58,6 +58,8 @@ interface IBridgehub is IL1AssetHandler { address sender ); + event SettlementLayerRegistered(uint256 indexed chainId, bool indexed isWhitelisted); + /// @notice Starts the transfer of admin rights. Only the current admin or owner can propose a new pending one. /// @notice New admin can accept admin rights by calling `acceptAdmin` function. /// @param _newPendingAdmin Address of the new admin diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index 7dac6c935..dfb85c0fd 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -422,7 +422,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own bytes[] calldata _factoryDeps ) external onlyBridgehub { (bytes memory _diamondCut, bytes memory _forceDeploymentData) = abi.decode(_initData, (bytes, bytes)); - // TODO: only allow on L1. + // solhint-disable-next-line func-named-parameters address hyperchainAddress = _deployNewChain(_chainId, _baseToken, _sharedBridge, _admin, _diamondCut); @@ -446,14 +446,9 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own require(_newSettlementLayerChainId != 0, "Bad chain id"); // Currently, we require that the sync layer is deployed by the same STM. - address settlementLayerAddress = hyperchainMap.get(_newSettlementLayerChainId); - - // TODO: Maybe `get` already ensured its existence. - require(settlementLayerAddress != address(0), "STM: sync layer not registered"); + require(hyperchainMap.contains(_newSettlementLayerChainId), "STM: sync layer not registered"); IBridgehub(BRIDGE_HUB).registerSettlementLayer(_newSettlementLayerChainId, _isWhitelisted); - - // TODO: emit event } /// @notice Called by the bridgehub during the migration of a chain to another settlement layer. diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index b2f079b14..c661379fe 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -53,6 +53,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { L1DAValidatorOutput memory daOutput = IL1DAValidator(s.l1DAValidator).checkDA( s.chainId, + uint256(_newBatch.batchNumber), logOutput.l2DAValidatorOutputHash, _newBatch.operatorDAInput, TOTAL_BLOBS_IN_COMMITMENT diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol index 9abb301ca..a4fe56b01 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol @@ -22,18 +22,20 @@ struct L1DAValidatorOutput { interface IL1DAValidator { /// @notice The function that checks the data availability for the given batch input. - /// @param chainId The chain id of the chain that is being committed. - /// @param l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. - /// @param operatorDAInput The DA input by the operator provided on L1. - /// @param maxBlobsSupported The maximal number of blobs supported by the chain. + /// @param _chainId The chain id of the chain that is being committed. + /// @param _chainId The batch number for which the data availability is being checked. + /// @param _l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. + /// @param _operatorDAInput The DA input by the operator provided on L1. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// We provide this value for future compatibility. /// This is needed because the corresponding `blobsLinearHashes`/`blobsOpeningCommitments` /// in the `L1DAValidatorOutput` struct will have to have this length as it is required /// to be static by the circuits. function checkDA( - uint256 chainId, - bytes32 l2DAValidatorOutputHash, - bytes calldata operatorDAInput, - uint256 maxBlobsSupported + uint256 _chainId, + uint256 _batchNumber, + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported ) external returns (L1DAValidatorOutput memory output); } diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol index b7a1681a0..48dcc8d01 100644 --- a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol @@ -9,9 +9,12 @@ pragma solidity 0.8.24; /// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. uint256 constant BLOB_SIZE_BYTES = 126_976; -// the state diff hash, hash of pubdata + the number of blobs. +/// @dev The state diff hash, hash of pubdata + the number of blobs. uint256 constant BLOB_DATA_OFFSET = 65; +/// @dev The size of the commitment for a single blob. +uint256 constant BLOB_COMMITMENT_SIZE = 32; + /// @notice Contract that contains the functionality for process the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { @@ -55,12 +58,7 @@ abstract contract CalldataDA { require(_operatorDAInput.length >= BLOB_DATA_OFFSET + 32 * blobsProvided, "invalid blobs hashes"); - assembly { - // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. - let blobsPtr := add(blobsLinearHashes, 0x20) - let inputPtr := add(_operatorDAInput.offset, BLOB_DATA_OFFSET) - calldatacopy(blobsPtr, inputPtr, mul(blobsProvided, 32)) - } + cloneCalldata(blobsLinearHashes, _operatorDAInput[BLOB_DATA_OFFSET:], blobsProvided); uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; @@ -82,20 +80,35 @@ abstract contract CalldataDA { bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput - ) internal pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { - require(_blobsProvided == 1, "only one blob with calldata"); - require(_pubdataInput.length >= 32, "pubdata too small"); + ) internal virtual pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + require(_blobsProvided == 1, "one blob with calldata"); // We typically do not know whether we'll use calldata or blobs at the time when // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. blobCommitments = new bytes32[](_maxBlobsSupported); - _pubdata = _pubdataInput[:_pubdataInput.length - 32]; + _pubdata = _pubdataInput[:_pubdataInput.length - BLOB_COMMITMENT_SIZE]; - // FIXME: allow larger lengths for Gateway-based chains. require(_pubdata.length <= BLOB_SIZE_BYTES, "cz"); require(_fullPubdataHash == keccak256(_pubdata), "wp"); - blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - 32:_pubdataInput.length]); + blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - BLOB_COMMITMENT_SIZE:_pubdataInput.length]); + } + + /// @notice Method that clones a slice of calldata into a bytes32[] memory array. + /// @param _dst The destination array. + /// @param _input The input calldata. + /// @param _len The length of the slice in 32-byte words to clone. + function cloneCalldata( + bytes32[] memory _dst, + bytes calldata _input, + uint256 _len + ) internal pure { + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let dstPtr := add(_dst, 0x20) + let inputPtr := _input.offset + calldatacopy(dstPtr, inputPtr, mul(_len, 32)) + } } } diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol new file mode 100644 index 000000000..5899eee05 --- /dev/null +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import { CalldataDA, BLOB_COMMITMENT_SIZE, BLOB_SIZE_BYTES } from "./CalldataDA.sol"; + +// solhint-disable gas-custom-errors, reason-string + +/// @notice Contract that contains the functionality for process the calldata DA. +/// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. +abstract contract CalldataDAGateway is CalldataDA { + /// @inheritdoc CalldataDA + function _processCalldataDA( + uint256 _blobsProvided, + bytes32 _fullPubdataHash, + uint256 _maxBlobsSupported, + bytes calldata _pubdataInput + ) internal override pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + require(_pubdataInput.length >= _blobsProvided * BLOB_COMMITMENT_SIZE, "pubdata too small"); + + // We typically do not know whether we'll use calldata or blobs at the time when + // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. + blobCommitments = new bytes32[](_maxBlobsSupported); + + _pubdata = _pubdataInput[:_pubdataInput.length - _blobsProvided * BLOB_COMMITMENT_SIZE]; + + require(_pubdata.length <= _blobsProvided * BLOB_SIZE_BYTES, "cz"); + require(_fullPubdataHash == keccak256(_pubdata), "wp"); + + bytes calldata providedCommitments = _pubdataInput[_pubdataInput.length - _blobsProvided * BLOB_COMMITMENT_SIZE:]; + + cloneCalldata(blobCommitments, providedCommitments, _blobsProvided); + } +} diff --git a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol index 4e91c7bc4..6d26d03f3 100644 --- a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol +++ b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol @@ -7,7 +7,7 @@ pragma solidity 0.8.24; import {IL1DAValidator, L1DAValidatorOutput, PubdataSource} from "../chain-interfaces/IL1DAValidator.sol"; import {IL1Messenger} from "../../common/interfaces/IL1Messenger.sol"; -import {CalldataDA} from "./CalldataDA.sol"; +import {CalldataDAGateway} from "./CalldataDAGateway.sol"; import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAddresses.sol"; @@ -15,10 +15,21 @@ import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAd /// @dev For compatibility reasons it accepts calldata in the same format as the `RollupL1DAValidator`, but unlike the latter it /// does not support blobs. /// @dev Note that it does not provide any compression whatsoever. -contract RelayedSLDAValidator is IL1DAValidator, CalldataDA { +contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { + function _relayCalldata( + uint256 _chainId, + uint256 _batchNumber, + bytes calldata _pubdata + ) internal { + // Re-sending all the pubdata in pure form to L1. + // slither-disable-next-line unused-return + IL1Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR).sendToL1(abi.encode(_chainId, _batchNumber, _pubdata)); + } + /// @inheritdoc IL1DAValidator function checkDA( uint256 _chainId, + uint256 _batchNumber, bytes32 _l2DAValidatorOutputHash, bytes calldata _operatorDAInput, uint256 _maxBlobsSupported @@ -56,10 +67,7 @@ contract RelayedSLDAValidator is IL1DAValidator, CalldataDA { l1DaInput[1:] ); - // Re-sending all the pubdata in pure form to L1. - // FIXME: we should also supply batch number, this is needed for logs to work. - // slither-disable-next-line unused-return - IL1Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR).sendToL1(abi.encode(_chainId, pubdata)); + _relayCalldata(_chainId, _batchNumber, pubdata); output.blobsOpeningCommitments = blobCommitments; } else { diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol index d193d1e5e..622a2e9a7 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol @@ -42,7 +42,7 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, l1DaInput); vm.expectRevert("l1-da-validator/invalid-pubdata-source"); - daValidator.checkDA(CHAIN_ID, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); } function test_revertWhen_PubdataInputTooSmall() public { @@ -64,7 +64,7 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); vm.expectRevert("pubdata too small"); - daValidator.checkDA(CHAIN_ID, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); } function test_checkDA() public { From bb506784c33b2d078f3a634bdde5dd5e9bba824a Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 12:20:06 +0200 Subject: [PATCH 08/26] fix length --- .../RelayedSLDAValidator.sol | 21 +++++++++++++++++-- system-contracts/contracts/L1Messenger.sol | 11 ++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol index 6d26d03f3..75fd1b850 100644 --- a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol +++ b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol @@ -9,13 +9,26 @@ import {IL1Messenger} from "../../common/interfaces/IL1Messenger.sol"; import {CalldataDAGateway} from "./CalldataDAGateway.sol"; -import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAddresses.sol"; +import { IBridgehub } from "../../bridgehub/IBridgehub.sol"; +import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../common/L2ContractAddresses.sol"; /// @notice The DA validator intended to be used in Era-environment. /// @dev For compatibility reasons it accepts calldata in the same format as the `RollupL1DAValidator`, but unlike the latter it /// does not support blobs. /// @dev Note that it does not provide any compression whatsoever. contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { + /// @dev Ensures that the sender is the chain that is supposed to send the message. + /// @param _chainId The chain id of the chain that is supposed to send the message. + function _ensureOnlyChainSender(uint256 _chainId) internal view { + // Note that this contract is only supposed to be deployed on L2, where the + // bridgehub is predeployed at `L2_BRIDGEHUB_ADDR` address. + require(IBridgehub(L2_BRIDGEHUB_ADDR).getHyperchain(_chainId) == msg.sender, "l1-da-validator/invalid-sender"); + } + + /// @dev Relays the calldata to L1. + /// @param _chainId The chain id of the chain that is supposed to send the message. + /// @param _batchNumber The batch number for which the data availability is being checked. + /// @param _pubdata The pubdata to be relayed to L1. function _relayCalldata( uint256 _chainId, uint256 _batchNumber, @@ -33,7 +46,11 @@ contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { bytes32 _l2DAValidatorOutputHash, bytes calldata _operatorDAInput, uint256 _maxBlobsSupported - ) external returns (L1DAValidatorOutput memory output) { + ) external returns (L1DAValidatorOutput memory output) { + // Unfortunately we have to use a method call instead of a modifier + // because of the stack-too-deep error caused by it. + _ensureOnlyChainSender(_chainId); + // Preventing "stack too deep" error uint256 blobsProvided; bytes32 fullPubdataHash; diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index b4a28fb70..29e37a860 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -245,6 +245,14 @@ contract L1Messenger is IL1Messenger, ISystemContract { ); } calldataPtr += 32; + + uint256 offset = uint256(_operatorInput[calldataPtr:calldataPtr + 32]); + // The length of the pubdata input should be stored right next to the calldata. + // We need to change offset by 32 - 4 = 28 bytes, since 32 bytes is the length of the offset + // itself and the 4 bytes are the selector which is not included inside the offset. + require(offset == calldataPtr + 28, "invalid offset"); + uint256 length = uint256(_operatorInput[calldataPtr + 32:calldataPtr + 64]); + // Shift calldata ptr past the pubdata offset and len calldataPtr += 64; @@ -259,6 +267,9 @@ contract L1Messenger is IL1Messenger, ISystemContract { } calldataPtr += 4; + // We need to ensure that length is enough to read all logs + require(length >= 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE, "invalid length"); + bytes32[] memory l2ToL1LogsTreeArray = new bytes32[](L2_TO_L1_LOGS_MERKLE_TREE_LEAVES); bytes32 reconstructedChainedLogsHash; for (uint256 i = 0; i < numberOfL2ToL1Logs; ++i) { From 383491fa4cf40f73b421761928818761a30c3c2f Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 12:27:29 +0200 Subject: [PATCH 09/26] resolve a todo --- .../contracts/bridgehub/STMDeploymentTracker.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol index 697e8331e..fb84bea33 100644 --- a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol @@ -87,10 +87,9 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable request = _registerSTMAssetOnL2Bridgehub(_chainId, _stmL1Address, _stmL2Address); } - /// @dev we need to implement this for the bridgehub for the TwoBridges logic - function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external { - // This function is typically used on bridges for e.g. - } + /// @notice The function called by the Bridgehub after the L2 transaction has been initiated. + /// @dev Not used in this contract. In case the transaction fails, we can just re-try it. + function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external {} // todo this has to be put in L1AssetRouter via TwoBridges for custom base tokens. Hard, because we have to have multiple msg types in bridgehubDeposit in the AssetRouter. /// @notice Used to register the stm asset in L2 AssetRouter. @@ -143,6 +142,10 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable l2Contract: L2_BRIDGEHUB_ADDR, l2Calldata: l2TxCalldata, factoryDeps: new bytes[](0), + // The `txDataHash` is typically used in usual ERC20 bridges to commit to the transaction data + // so that the user can recover funds in case the bridging fails on L2. + // However, this contract uses the `requestL2TransactionTwoBridges` method just to perform an L1->L2 transaction. + // We do not need to recover anything and so `bytes32(0)` here is okay. txDataHash: bytes32(0) }); } From 2e018f6fef9e9df52c6350d9f23f1454beff6780 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 12:49:26 +0200 Subject: [PATCH 10/26] additional fixes for stm deployment tracker --- docs/gateway/contracts-review-gateway.md | 2 +- l1-contracts/contracts/bridge/L1AssetRouter.sol | 2 +- l1-contracts/contracts/bridge/L1NativeTokenVault.sol | 2 +- .../contracts/bridge/interfaces/IL1AssetRouter.sol | 2 +- l1-contracts/contracts/bridgehub/Bridgehub.sol | 2 +- l1-contracts/contracts/bridgehub/IBridgehub.sol | 2 +- l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol | 8 +++----- .../contracts/dev-contracts/test/DummySharedBridge.sol | 2 +- 8 files changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/gateway/contracts-review-gateway.md b/docs/gateway/contracts-review-gateway.md index e4a34126c..1b2980c37 100644 --- a/docs/gateway/contracts-review-gateway.md +++ b/docs/gateway/contracts-review-gateway.md @@ -26,7 +26,7 @@ Known issues, and features that still need to be implemented: - Upgrade process, how do we upgrade to CAB bridge, to the new system contracts. - We had the syncLayer internal name previously for the Gateway. This has not been replaced everywhere yet. - permissions for some functions are not properly restricted yet, mostly they are missing a modifier. -- Bridgehub setAssetHandlerAddressInitial `address sender` might be an issue. +- Bridgehub setAssetHandlerAddress `address sender` might be an issue. - MessageRoot should be renamed to MessageRootAggregator ![Untitled](./Hyperchain-scheme.png) diff --git a/l1-contracts/contracts/bridge/L1AssetRouter.sol b/l1-contracts/contracts/bridge/L1AssetRouter.sol index b6845cba0..6dff9cc36 100644 --- a/l1-contracts/contracts/bridge/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/L1AssetRouter.sol @@ -240,7 +240,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev `setAssetHandlerAddressOnCounterPart` should be called on L1 to set asset handlers on L2 chains for a specific asset ID. /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. /// @param _assetHandlerAddress The address of the asset handler to be set for the provided asset. - function setAssetHandlerAddressInitial(bytes32 _assetRegistrationData, address _assetHandlerAddress) external { + function setAssetHandlerAddressThisChain(bytes32 _assetRegistrationData, address _assetHandlerAddress) external { bool senderIsNTV = msg.sender == address(nativeTokenVault); address sender = senderIsNTV ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; bytes32 assetId = DataEncoding.encodeAssetId(block.chainid, _assetRegistrationData, sender); diff --git a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol index 1d8cd0973..366bbf260 100644 --- a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol @@ -105,7 +105,7 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste require(_l1Token != L1_WETH_TOKEN, "NTV: WETH deposit not supported"); require(_l1Token == ETH_TOKEN_ADDRESS || _l1Token.code.length > 0, "NTV: empty token"); bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); - L1_SHARED_BRIDGE.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_l1Token))), address(this)); + L1_SHARED_BRIDGE.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_l1Token))), address(this)); tokenAddress[assetId] = _l1Token; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol index 631cb8718..c5cbbc87f 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol @@ -150,7 +150,7 @@ interface IL1AssetRouter { function setAssetDeploymentTracker(bytes32 _assetRegistrationData, address _assetDeploymentTracker) external; - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetHandlerAddress) external; + function setAssetHandlerAddressThisChain(bytes32 _additionalData, address _assetHandlerAddress) external; function setAssetHandlerAddressOnCounterPart( uint256 _chainId, diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index d8024e800..65326b7f2 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -216,7 +216,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev Used to set the assetAddress for a given assetInfo. /// @param _additionalData the additional data to identify the asset /// @param _assetAddress the asset handler address - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetAddress) external { + function setAssetHandlerAddress(bytes32 _additionalData, address _assetAddress) external { // It is a simplified version of the logic used by the AssetRouter to manage asset handlers. // STM's assetId is `keccak256(abi.encode(L1_CHAIN_ID, stmDeployer, stmAddress))`. // And the STMDeployer is considered the deployment tracker for the STM asset. diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 3195dc6e3..37efceadd 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -192,7 +192,7 @@ interface IBridgehub is IL1AssetHandler { function stmAssetIdToAddress(bytes32 _assetInfo) external view returns (address); - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetAddress) external; + function setAssetHandlerAddress(bytes32 _additionalData, address _assetAddress) external; function L1_CHAIN_ID() external view returns (uint256); } diff --git a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol index fb84bea33..3c99e61d7 100644 --- a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol @@ -52,8 +52,8 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable // solhint-disable-next-line gas-custom-errors require(BRIDGE_HUB.stateTransitionManagerIsRegistered(_stmAddress), "STMDT: stm not registered"); - SHARED_BRIDGE.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_stmAddress))), address(BRIDGE_HUB)); - BRIDGE_HUB.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_stmAddress))), _stmAddress); + SHARED_BRIDGE.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_stmAddress))), address(BRIDGE_HUB)); + BRIDGE_HUB.setAssetHandlerAddress(bytes32(uint256(uint160(_stmAddress))), _stmAddress); } /// @notice The function responsible for registering the L2 counterpart of an STM asset on the L2 Bridgehub. @@ -122,7 +122,6 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1STM))))); } - // Todo this works for now, but it will not work in the future if we want to change STM DTs. Probably ok. /// @notice Used to register the stm asset in L2 Bridgehub. /// @param _chainId the chainId of the chain function _registerSTMAssetOnL2Bridgehub( @@ -132,8 +131,7 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable address _stmL2Address ) internal pure returns (L2TransactionRequestTwoBridgesInner memory request) { bytes memory l2TxCalldata = abi.encodeCall( - /// todo it should not be initial in setAssetHandlerAddressInitial - IBridgehub.setAssetHandlerAddressInitial, + IBridgehub.setAssetHandlerAddress, (bytes32(uint256(uint160(_stmL1Address))), _stmL2Address) ); diff --git a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol index 82cb9562a..2c3769ddc 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol @@ -157,7 +157,7 @@ contract DummySharedBridge { } /// @dev Used to set the assedAddress for a given assetId. - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetHandlerAddress) external { + function setAssetHandlerAddressThisChain(bytes32 _additionalData, address _assetHandlerAddress) external { address sender = msg.sender == address(nativeTokenVault) ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; bytes32 assetId = keccak256(abi.encode(uint256(block.chainid), sender, _additionalData)); assetHandlerAddress[assetId] = _assetHandlerAddress; From e9b3829eac27af4cbef34fd923af751add8c1cd6 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 12:54:48 +0200 Subject: [PATCH 11/26] mini refactor + ensure that only chainadmin can migrate chain --- .../contracts/bridgehub/Bridgehub.sol | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 65326b7f2..d34d1acc6 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -93,6 +93,17 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _; } + modifier onlyAliasedZero() { + /// There is no sender for the wrapping, we use a virtual address. + require(msg.sender == VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, "BH: not aliased zero"); + _; + } + + modifier onlyAssetRouter() { + require(msg.sender == address(sharedBridge), "BH: not asset router"); + _; + } + /// @notice to avoid parity hack constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { _disableInitializers(); @@ -109,17 +120,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _transferOwnership(_owner); } - modifier onlyAliasedZero() { - /// There is no sender for the wrapping, we use a virtual address. - require(msg.sender == VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, "BH: not aliased zero"); - _; - } - - modifier onlyAssetRouter() { - require(msg.sender == address(sharedBridge), "BH: not asset router"); - _; - } - //// Initialization and registration /// @inheritdoc IBridgehub @@ -551,11 +551,14 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus require(settlementLayer[_chainId] == block.chainid, "BH: not current SL"); settlementLayer[_chainId] = _settlementChainId; + address hyperchain = getHyperchain(_chainId); + require(_prevMsgSender == IZkSyncHyperchain(hyperchain).getAdmin(), "BH: incorrect sender"); + bytes memory stmMintData = IStateTransitionManager(stateTransitionManager[_chainId]).forwardedBridgeBurn( _chainId, _stmData ); - bytes memory chainMintData = IZkSyncHyperchain(getHyperchain(_chainId)).forwardedBridgeBurn( + bytes memory chainMintData = IZkSyncHyperchain(hyperchain).forwardedBridgeBurn( getHyperchain(_settlementChainId), _prevMsgSender, _chainData From e6ab07807222f919681af629f7aee0fe26b284ed Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 13:09:40 +0200 Subject: [PATCH 12/26] some more fixes --- l1-contracts/contracts/bridgehub/Bridgehub.sol | 6 ++++-- .../state-transition/StateTransitionManager.sol | 13 +++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index d34d1acc6..9bb5e47eb 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -108,7 +108,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { _disableInitializers(); L1_CHAIN_ID = _l1ChainId; - // TODO: this assumes that the bridgehub is deployed only on the chains that have ETH as base token. + + // Note that this assumes that the bridgehub only accepts transactions on chains with ETH base token only. + // This is indeed true, since the only methods where this immutable is used are the ones with `onlyL1` modifier. ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); _transferOwnership(_owner); } @@ -552,6 +554,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus settlementLayer[_chainId] = _settlementChainId; address hyperchain = getHyperchain(_chainId); + require(hyperchain != address(0), "BH: hyperchain not registered"); require(_prevMsgSender == IZkSyncHyperchain(hyperchain).getAdmin(), "BH: incorrect sender"); bytes memory stmMintData = IStateTransitionManager(stateTransitionManager[_chainId]).forwardedBridgeBurn( @@ -564,7 +567,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _chainData ); bridgehubMintData = abi.encode(_chainId, stmMintData, chainMintData); - // TODO: double check that get only returns when chain id is there. } /// @dev IL1AssetHandler interface, used to receive a chain on the settlement layer. diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index dfb85c0fd..645914542 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -458,9 +458,16 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own uint256 _chainId, bytes calldata _data ) external view override onlyBridgehub returns (bytes memory stmForwardedBridgeMintData) { + // Note that the `_diamondCut` here is not for the current chain, for the chain where the migration + // happens. The correctness of it will be checked on the STM on the new settlement layer. (address _newGatewayAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); require(_newGatewayAdmin != address(0), "STM: admin zero"); - // todo check protocol version + + // We ensure that the chain has the latest protocol version to avoid edge cases + // related to different protocol version support. + address hyperchain = hyperchainMap.get(_chainId); + require(IZkSyncHyperchain(hyperchain).getProtocolVersion() == protocolVersion, "STM: outdated pv"); + return abi.encode(IBridgehub(BRIDGE_HUB).baseToken(_chainId), _newGatewayAdmin, protocolVersion, _diamondCut); } @@ -475,8 +482,10 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own _stmData, (address, address, uint256, bytes) ); + + // We ensure that the chain has the latest protocol version to avoid edge cases + // related to different protocol version support. require(_protocolVersion == protocolVersion, "STM, outdated pv"); - // todo porotocl version check chainAddress = _deployNewChain({ _chainId: _chainId, _baseToken: _baseToken, From ef388fb522bfb17ce5382e5288bd8589c436c598 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 13:19:48 +0200 Subject: [PATCH 13/26] change the value of relayed sender --- l1-contracts/contracts/bridgehub/Bridgehub.sol | 8 ++++---- l1-contracts/contracts/common/Config.sol | 4 ++-- .../state-transition/chain-deps/facets/Mailbox.sol | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 9bb5e47eb..6f0017ffc 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -14,7 +14,7 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {IZkSyncHyperchain} from "../state-transition/chain-interfaces/IZkSyncHyperchain.sol"; -import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS} from "../common/Config.sol"; +import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "../common/Config.sol"; import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {IMessageRoot} from "./IMessageRoot.sol"; @@ -93,9 +93,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _; } - modifier onlyAliasedZero() { + modifier onlySettlementLayerRelayedSender() { /// There is no sender for the wrapping, we use a virtual address. - require(msg.sender == VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, "BH: not aliased zero"); + require(msg.sender == SETTLEMENT_LAYER_RELAY_SENDER, "BH: not aliased zero"); _; } @@ -441,7 +441,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp - ) external override onlyAliasedZero { + ) external override onlySettlementLayerRelayedSender { require(L1_CHAIN_ID != block.chainid, "BH: not in sync layer mode"); address hyperchain = getHyperchain(_chainId); IZkSyncHyperchain(hyperchain).bridgehubRequestL2TransactionOnGateway( diff --git a/l1-contracts/contracts/common/Config.sol b/l1-contracts/contracts/common/Config.sol index 79a28d9f5..d1b580182 100644 --- a/l1-contracts/contracts/common/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -114,8 +114,8 @@ address constant BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS = address(uint160(type(uint /// @dev the maximum number of supported chains, this is an arbitrary limit. uint256 constant MAX_NUMBER_OF_HYPERCHAINS = 100; -/// @dev Used to when there is no sender contract on L1. This is the alias we apply to L1->L2 messages. -address constant VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS = address(uint160(0x1111000000000000000000000000000000001111)); +/// @dev Used as the `msg.sender` for transactions that relayed via a settlement layer. +address constant SETTLEMENT_LAYER_RELAY_SENDER = address(uint160(0x1111111111111111111111111111111111111111)); struct PriorityTreeCommitment { uint256 nextLeafIndex; diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index 0d8a671b6..32642fcba 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -22,7 +22,7 @@ import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {L2ContractHelper} from "../../../common/libraries/L2ContractHelper.sol"; import {AddressAliasHelper} from "../../../vendor/AddressAliasHelper.sol"; import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; -import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS, VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS} from "../../../common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS, SETTLEMENT_LAYER_RELAY_SENDER} from "../../../common/Config.sol"; import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../../common/L2ContractAddresses.sol"; import {IL1AssetRouter} from "../../../bridge/interfaces/IL1AssetRouter.sol"; @@ -339,7 +339,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { return BridgehubL2TransactionRequest({ /// There is no sender for the wrapping, we use a virtual address. - sender: VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, + sender: SETTLEMENT_LAYER_RELAY_SENDER, contractL2: L2_BRIDGEHUB_ADDR, mintValue: 0, l2Value: 0, From 393cc4605519765bdd2eb96952999fb4c38f2c08 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 13:43:20 +0200 Subject: [PATCH 14/26] delete unneeded debug method and preserve a helpful one --- .../state-transition/chain-deps/facets/Admin.sol | 11 ++++------- .../state-transition/chain-interfaces/IAdmin.sol | 3 --- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 2b67b88a0..5cc477372 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -281,8 +281,10 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { bytes calldata _data ) external payable override onlyBridgehub {} - // todo make internal. For now useful for testing - function _prepareChainCommitment() public view returns (HyperchainCommitment memory commitment) { + /// @notice Returns the commitment for a chain. + /// @dev Note, that this is a getter method helpful for debugging and should not be relied upon by clients. + /// @return commitment The commitment for the chain. + function prepareChainCommitment() public view returns (HyperchainCommitment memory commitment) { require(s.priorityQueue.getFirstUnprocessedPriorityTx() >= s.priorityTree.startIndex, "PQ not ready"); commitment.totalBatchesCommitted = s.totalBatchesCommitted; @@ -315,11 +317,6 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { commitment.batchHashes = batchHashes; } - /// @inheritdoc IAdmin - function readChainCommitment() external view override returns (bytes memory commitment) { - return abi.encode(_prepareChainCommitment()); - } - // function recoverFromFailedMigrationToGateway( // uint256 _settlementLayerChainId, // uint256 _l2BatchNumber, diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol index e2ec1e88e..a8ebbbc3c 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol @@ -148,7 +148,4 @@ interface IAdmin is IZkSyncHyperchainBase { /// @dev Similar to IL1AssetHandler interface, used to receive chains. function forwardedBridgeMint(bytes calldata _data) external payable; - - /// @dev Returns the commitments to the chain. - function readChainCommitment() external view returns (bytes memory); } From c3a16bfd3d5680393aa91db2f9f00e95e12d930a Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 15:19:33 +0200 Subject: [PATCH 15/26] foundry unit tests pass --- da-contracts/contracts/CalldataDA.sol | 1 + .../contracts/bridgehub/Bridgehub.sol | 2 +- .../chain-deps/facets/Admin.sol | 2 +- .../data-availability/CalldataDA.sol | 3 +- .../foundry/integration/GatewayTests.t.sol | 6 ++-- .../RelayedSLDAValidator.t.sol | 34 +++++++++++++++++++ 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/da-contracts/contracts/CalldataDA.sol b/da-contracts/contracts/CalldataDA.sol index 48dcc8d01..5067c3f94 100644 --- a/da-contracts/contracts/CalldataDA.sol +++ b/da-contracts/contracts/CalldataDA.sol @@ -82,6 +82,7 @@ abstract contract CalldataDA { bytes calldata _pubdataInput ) internal virtual pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { require(_blobsProvided == 1, "one blob with calldata"); + require(_pubdataInput.length >= BLOB_COMMITMENT_SIZE, "pubdata too small"); // We typically do not know whether we'll use calldata or blobs at the time when // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 6f0017ffc..56c51a816 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -95,7 +95,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus modifier onlySettlementLayerRelayedSender() { /// There is no sender for the wrapping, we use a virtual address. - require(msg.sender == SETTLEMENT_LAYER_RELAY_SENDER, "BH: not aliased zero"); + require(msg.sender == SETTLEMENT_LAYER_RELAY_SENDER, "BH: not relayed senser"); _; } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 5cc477372..400a43f08 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -226,7 +226,7 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { require(currentProtocolVersion == protocolVersion, "STM: protocolVersion not up to date"); s.settlementLayer = _settlementLayer; - chainBridgeMintData = abi.encode(_prepareChainCommitment()); + chainBridgeMintData = abi.encode(prepareChainCommitment()); } /// @inheritdoc IAdmin diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol index 48dcc8d01..ff743a73c 100644 --- a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol @@ -81,7 +81,8 @@ abstract contract CalldataDA { uint256 _maxBlobsSupported, bytes calldata _pubdataInput ) internal virtual pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { - require(_blobsProvided == 1, "one blob with calldata"); + require(_blobsProvided == 1, "only one blob with calldata"); + require(_pubdataInput.length >= BLOB_COMMITMENT_SIZE, "pubdata too small"); // We typically do not know whether we'll use calldata or blobs at the time when // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. diff --git a/l1-contracts/test/foundry/integration/GatewayTests.t.sol b/l1-contracts/test/foundry/integration/GatewayTests.t.sol index 55dba36b4..dc5e23643 100644 --- a/l1-contracts/test/foundry/integration/GatewayTests.t.sol +++ b/l1-contracts/test/foundry/integration/GatewayTests.t.sol @@ -16,7 +16,7 @@ import {TokenDeployer} from "./_SharedTokenDeployer.t.sol"; import {HyperchainDeployer} from "./_SharedHyperchainDeployer.t.sol"; import {GatewayDeployer} from "./_SharedGatewayDeployer.t.sol"; import {L2TxMocker} from "./_SharedL2TxMocker.t.sol"; -import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; import {L2Message} from "contracts/common/Messaging.sol"; @@ -159,7 +159,7 @@ contract GatewayTests is L1ContractDeployer, HyperchainDeployer, TokenDeployer, reservedDynamic: "0x" }); vm.chainId(12345); - vm.startBroadcast(AddressAliasHelper.applyL1ToL2Alias(address(0))); + vm.startBroadcast(SETTLEMENT_LAYER_RELAY_SENDER); bridgehub.forwardTransactionOnGateway(mintChainId, tx, new bytes[](0), bytes32(0), 0); vm.stopBroadcast(); } @@ -171,7 +171,7 @@ contract GatewayTests is L1ContractDeployer, HyperchainDeployer, TokenDeployer, bytes32 assetId = bridgehub.stmAssetIdFromChainId(migratingChainId); bytes memory initialDiamondCut = l1Script.getInitialDiamondCutData(); - bytes memory chainData = abi.encode(AdminFacet(address(chain))._prepareChainCommitment()); + bytes memory chainData = abi.encode(AdminFacet(address(chain)).prepareChainCommitment()); bytes memory stmData = abi.encode(address(1), msg.sender, stm.protocolVersion(), initialDiamondCut); bytes memory bridgehubMintData = abi.encode(mintChainId, stmData, chainData); vm.startBroadcast(address(bridgehub.sharedBridge())); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol index 622a2e9a7..f84cb77d5 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol @@ -8,9 +8,12 @@ import {RelayedSLDAValidator} from "contracts/state-transition/data-availability import {L1DAValidatorOutput, PubdataSource} from "contracts/state-transition/chain-interfaces/IL1DAValidator.sol"; import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IL1Messenger} from "contracts/common/interfaces/IL1Messenger.sol"; +import {L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; contract RelayedSLDAValidatorTest is Test { uint256 constant CHAIN_ID = 193; + address constant CHAIN_ADDRESS = address(0x1234); RelayedSLDAValidator daValidator; function setUp() public { @@ -21,6 +24,11 @@ contract RelayedSLDAValidatorTest is Test { abi.encodeWithSelector(IL1Messenger.sendToL1.selector), abi.encode(bytes32(0)) ); + vm.mockCall( + L2_BRIDGEHUB_ADDR, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, (CHAIN_ID)), + abi.encode(CHAIN_ADDRESS) + ); } /*////////////////////////////////////////////////////////////////////////// @@ -41,6 +49,7 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, l1DaInput); + vm.prank(CHAIN_ADDRESS); vm.expectRevert("l1-da-validator/invalid-pubdata-source"); daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); } @@ -63,10 +72,33 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + vm.prank(CHAIN_ADDRESS); vm.expectRevert("pubdata too small"); daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); } + function test_revertWhenInvalidSender() public { + bytes memory pubdata = "verifydont"; + console.logBytes(pubdata); + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + uint8 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + uint8 pubdataSource = uint8(PubdataSource.Calldata); + bytes memory l1DaInput = "verifydonttrust"; + bytes32 fullPubdataHash = keccak256(pubdata); + + bytes memory daInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + + bytes32 l2DAValidatorOutputHash = keccak256(daInput); + + bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + + vm.expectRevert("l1-da-validator/invalid-sender"); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + } + function test_checkDA() public { bytes memory pubdata = "verifydont"; console.logBytes(pubdata); @@ -85,8 +117,10 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + vm.prank(CHAIN_ADDRESS); L1DAValidatorOutput memory output = daValidator.checkDA( CHAIN_ID, + 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported From fb7b1892ace7bdb3a378dcb9287d05eafb452f67 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 15:49:33 +0200 Subject: [PATCH 16/26] lint fix --- da-contracts/contracts/CalldataDA.sol | 12 +++---- .../contracts/bridgehub/Bridgehub.sol | 12 +++---- .../StateTransitionManager.sol | 8 ++--- .../chain-deps/facets/Admin.sol | 2 +- .../data-availability/CalldataDA.sol | 12 +++---- .../data-availability/CalldataDAGateway.sol | 10 +++--- .../RelayedSLDAValidator.sol | 14 +++----- .../RelayedSLDAValidator.t.sol | 4 +-- .../test/test_config/constant/hardhat.json | 32 +++++++++---------- system-contracts/contracts/L1Messenger.sol | 2 +- 10 files changed, 49 insertions(+), 59 deletions(-) diff --git a/da-contracts/contracts/CalldataDA.sol b/da-contracts/contracts/CalldataDA.sol index 5067c3f94..e6574468d 100644 --- a/da-contracts/contracts/CalldataDA.sol +++ b/da-contracts/contracts/CalldataDA.sol @@ -12,7 +12,7 @@ uint256 constant BLOB_SIZE_BYTES = 126_976; /// @dev The state diff hash, hash of pubdata + the number of blobs. uint256 constant BLOB_DATA_OFFSET = 65; -/// @dev The size of the commitment for a single blob. +/// @dev The size of the commitment for a single blob. uint256 constant BLOB_COMMITMENT_SIZE = 32; /// @notice Contract that contains the functionality for process the calldata DA. @@ -80,7 +80,7 @@ abstract contract CalldataDA { bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput - ) internal virtual pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + ) internal pure virtual returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { require(_blobsProvided == 1, "one blob with calldata"); require(_pubdataInput.length >= BLOB_COMMITMENT_SIZE, "pubdata too small"); @@ -98,13 +98,9 @@ abstract contract CalldataDA { /// @notice Method that clones a slice of calldata into a bytes32[] memory array. /// @param _dst The destination array. - /// @param _input The input calldata. + /// @param _input The input calldata. /// @param _len The length of the slice in 32-byte words to clone. - function cloneCalldata( - bytes32[] memory _dst, - bytes calldata _input, - uint256 _len - ) internal pure { + function cloneCalldata(bytes32[] memory _dst, bytes calldata _input, uint256 _len) internal pure { assembly { // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. let dstPtr := add(_dst, 0x20) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 56c51a816..f6079e428 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -57,8 +57,8 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus address private pendingAdmin; /// @notice The contract that stores the cross-chain message root for each chain and the aggregated root. - /// @dev Note that the message root does not contain messages from the chain it is deployed on. It may - /// be added later on if needed. + /// @dev Note that the message root does not contain messages from the chain it is deployed on. It may + /// be added later on if needed. IMessageRoot public override messageRoot; /// @notice Mapping from chain id to encoding of the base token used for deposits / withdrawals @@ -109,7 +109,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _disableInitializers(); L1_CHAIN_ID = _l1ChainId; - // Note that this assumes that the bridgehub only accepts transactions on chains with ETH base token only. + // Note that this assumes that the bridgehub only accepts transactions on chains with ETH base token only. // This is indeed true, since the only methods where this immutable is used are the ones with `onlyL1` modifier. ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); _transferOwnership(_owner); @@ -219,14 +219,14 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @param _additionalData the additional data to identify the asset /// @param _assetAddress the asset handler address function setAssetHandlerAddress(bytes32 _additionalData, address _assetAddress) external { - // It is a simplified version of the logic used by the AssetRouter to manage asset handlers. + // It is a simplified version of the logic used by the AssetRouter to manage asset handlers. // STM's assetId is `keccak256(abi.encode(L1_CHAIN_ID, stmDeployer, stmAddress))`. // And the STMDeployer is considered the deployment tracker for the STM asset. // // The STMDeployer will call this method to set the asset handler address for the assetId. // If the chain is not the same as L1, we assume that it is done via L1->L2 communication and so we unalias the sender. - // - // For simpler handling we allow anyone to call this method. It is okay, since during bridging operations + // + // For simpler handling we allow anyone to call this method. It is okay, since during bridging operations // it is double checked that `assetId` is indeed derived from the `stmDeployer`. address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index 645914542..f8baf7a98 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -422,7 +422,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own bytes[] calldata _factoryDeps ) external onlyBridgehub { (bytes memory _diamondCut, bytes memory _forceDeploymentData) = abi.decode(_initData, (bytes, bytes)); - + // solhint-disable-next-line func-named-parameters address hyperchainAddress = _deployNewChain(_chainId, _baseToken, _sharedBridge, _admin, _diamondCut); @@ -459,11 +459,11 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own bytes calldata _data ) external view override onlyBridgehub returns (bytes memory stmForwardedBridgeMintData) { // Note that the `_diamondCut` here is not for the current chain, for the chain where the migration - // happens. The correctness of it will be checked on the STM on the new settlement layer. + // happens. The correctness of it will be checked on the STM on the new settlement layer. (address _newGatewayAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); require(_newGatewayAdmin != address(0), "STM: admin zero"); - // We ensure that the chain has the latest protocol version to avoid edge cases + // We ensure that the chain has the latest protocol version to avoid edge cases // related to different protocol version support. address hyperchain = hyperchainMap.get(_chainId); require(IZkSyncHyperchain(hyperchain).getProtocolVersion() == protocolVersion, "STM: outdated pv"); @@ -483,7 +483,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own (address, address, uint256, bytes) ); - // We ensure that the chain has the latest protocol version to avoid edge cases + // We ensure that the chain has the latest protocol version to avoid edge cases // related to different protocol version support. require(_protocolVersion == protocolVersion, "STM, outdated pv"); chainAddress = _deployNewChain({ diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 400a43f08..e68bf5623 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -283,7 +283,7 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { /// @notice Returns the commitment for a chain. /// @dev Note, that this is a getter method helpful for debugging and should not be relied upon by clients. - /// @return commitment The commitment for the chain. + /// @return commitment The commitment for the chain. function prepareChainCommitment() public view returns (HyperchainCommitment memory commitment) { require(s.priorityQueue.getFirstUnprocessedPriorityTx() >= s.priorityTree.startIndex, "PQ not ready"); diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol index ff743a73c..e5ea0ecdd 100644 --- a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol @@ -12,7 +12,7 @@ uint256 constant BLOB_SIZE_BYTES = 126_976; /// @dev The state diff hash, hash of pubdata + the number of blobs. uint256 constant BLOB_DATA_OFFSET = 65; -/// @dev The size of the commitment for a single blob. +/// @dev The size of the commitment for a single blob. uint256 constant BLOB_COMMITMENT_SIZE = 32; /// @notice Contract that contains the functionality for process the calldata DA. @@ -80,7 +80,7 @@ abstract contract CalldataDA { bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput - ) internal virtual pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + ) internal pure virtual returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { require(_blobsProvided == 1, "only one blob with calldata"); require(_pubdataInput.length >= BLOB_COMMITMENT_SIZE, "pubdata too small"); @@ -98,13 +98,9 @@ abstract contract CalldataDA { /// @notice Method that clones a slice of calldata into a bytes32[] memory array. /// @param _dst The destination array. - /// @param _input The input calldata. + /// @param _input The input calldata. /// @param _len The length of the slice in 32-byte words to clone. - function cloneCalldata( - bytes32[] memory _dst, - bytes calldata _input, - uint256 _len - ) internal pure { + function cloneCalldata(bytes32[] memory _dst, bytes calldata _input, uint256 _len) internal pure { assembly { // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. let dstPtr := add(_dst, 0x20) diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol index 5899eee05..0525cefd8 100644 --- a/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; -import { CalldataDA, BLOB_COMMITMENT_SIZE, BLOB_SIZE_BYTES } from "./CalldataDA.sol"; +import {CalldataDA, BLOB_COMMITMENT_SIZE, BLOB_SIZE_BYTES} from "./CalldataDA.sol"; // solhint-disable gas-custom-errors, reason-string @@ -15,7 +15,7 @@ abstract contract CalldataDAGateway is CalldataDA { bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput - ) internal override pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + ) internal pure override returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { require(_pubdataInput.length >= _blobsProvided * BLOB_COMMITMENT_SIZE, "pubdata too small"); // We typically do not know whether we'll use calldata or blobs at the time when @@ -27,8 +27,10 @@ abstract contract CalldataDAGateway is CalldataDA { require(_pubdata.length <= _blobsProvided * BLOB_SIZE_BYTES, "cz"); require(_fullPubdataHash == keccak256(_pubdata), "wp"); - bytes calldata providedCommitments = _pubdataInput[_pubdataInput.length - _blobsProvided * BLOB_COMMITMENT_SIZE:]; - + bytes calldata providedCommitments = _pubdataInput[_pubdataInput.length - + _blobsProvided * + BLOB_COMMITMENT_SIZE:]; + cloneCalldata(blobCommitments, providedCommitments, _blobsProvided); } } diff --git a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol index 75fd1b850..a528d162d 100644 --- a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol +++ b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol @@ -9,7 +9,7 @@ import {IL1Messenger} from "../../common/interfaces/IL1Messenger.sol"; import {CalldataDAGateway} from "./CalldataDAGateway.sol"; -import { IBridgehub } from "../../bridgehub/IBridgehub.sol"; +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../common/L2ContractAddresses.sol"; /// @notice The DA validator intended to be used in Era-environment. @@ -20,8 +20,8 @@ contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { /// @dev Ensures that the sender is the chain that is supposed to send the message. /// @param _chainId The chain id of the chain that is supposed to send the message. function _ensureOnlyChainSender(uint256 _chainId) internal view { - // Note that this contract is only supposed to be deployed on L2, where the - // bridgehub is predeployed at `L2_BRIDGEHUB_ADDR` address. + // Note that this contract is only supposed to be deployed on L2, where the + // bridgehub is predeployed at `L2_BRIDGEHUB_ADDR` address. require(IBridgehub(L2_BRIDGEHUB_ADDR).getHyperchain(_chainId) == msg.sender, "l1-da-validator/invalid-sender"); } @@ -29,11 +29,7 @@ contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { /// @param _chainId The chain id of the chain that is supposed to send the message. /// @param _batchNumber The batch number for which the data availability is being checked. /// @param _pubdata The pubdata to be relayed to L1. - function _relayCalldata( - uint256 _chainId, - uint256 _batchNumber, - bytes calldata _pubdata - ) internal { + function _relayCalldata(uint256 _chainId, uint256 _batchNumber, bytes calldata _pubdata) internal { // Re-sending all the pubdata in pure form to L1. // slither-disable-next-line unused-return IL1Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR).sendToL1(abi.encode(_chainId, _batchNumber, _pubdata)); @@ -46,7 +42,7 @@ contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { bytes32 _l2DAValidatorOutputHash, bytes calldata _operatorDAInput, uint256 _maxBlobsSupported - ) external returns (L1DAValidatorOutput memory output) { + ) external returns (L1DAValidatorOutput memory output) { // Unfortunately we have to use a method call instead of a modifier // because of the stack-too-deep error caused by it. _ensureOnlyChainSender(_chainId); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol index f84cb77d5..a6fb8e570 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol @@ -25,8 +25,8 @@ contract RelayedSLDAValidatorTest is Test { abi.encode(bytes32(0)) ); vm.mockCall( - L2_BRIDGEHUB_ADDR, - abi.encodeWithSelector(IBridgehub.getHyperchain.selector, (CHAIN_ID)), + L2_BRIDGEHUB_ADDR, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, (CHAIN_ID)), abi.encode(CHAIN_ADDRESS) ); } diff --git a/l1-contracts/test/test_config/constant/hardhat.json b/l1-contracts/test/test_config/constant/hardhat.json index 1a59b10d3..4b38cf5a1 100644 --- a/l1-contracts/test/test_config/constant/hardhat.json +++ b/l1-contracts/test/test_config/constant/hardhat.json @@ -3,96 +3,96 @@ "name": "DAI", "symbol": "DAI", "decimals": 18, - "address": "0x9F9Cd69A2a3b296B8C3b0E59A942d1B893c6c988" + "address": "0x4e5261FDDB30B6FaC019ab8517119B06fb65A8D8" }, { "name": "wBTC", "symbol": "wBTC", "decimals": 8, - "address": "0xe7B8C0dd29D50D54b9d75e923FB96562B7513A6f" + "address": "0xC04fcb89ea8AF6E0d7407304F6f8e2471975f676" }, { "name": "BAT", "symbol": "BAT", "decimals": 18, - "address": "0x4C56e415d1C59c69FE953aEd7C41686f5ee33B2c" + "address": "0x0bADaf09ddaC0F1Fd1ef1bc6F9871F322245F075" }, { "name": "GNT", "symbol": "GNT", "decimals": 18, - "address": "0x7D12865902a998Ae6C7B8Bea02277dF1707bB7E2" + "address": "0x57E6A02f8622D71B293f9c291177C857a1d3FadB" }, { "name": "MLTT", "symbol": "MLTT", "decimals": 18, - "address": "0xD4Ba730aA7b2E7Bb7515b265c39dd0796cF7d440" + "address": "0x9F9Cd69A2a3b296B8C3b0E59A942d1B893c6c988" }, { "name": "DAIK", "symbol": "DAIK", "decimals": 18, - "address": "0xee80cFA1F62427E52A62197A86f76a16eA7b7627" + "address": "0xe7B8C0dd29D50D54b9d75e923FB96562B7513A6f" }, { "name": "wBTCK", "symbol": "wBTCK", "decimals": 8, - "address": "0x2dD8d8B7E8489E361fa3a455888a371eDcB645d4" + "address": "0x4C56e415d1C59c69FE953aEd7C41686f5ee33B2c" }, { "name": "BATK", "symbol": "BATS", "decimals": 18, - "address": "0x3dE741Ebc93DbEC9C97eccbbA1aD2577b4335980" + "address": "0x7D12865902a998Ae6C7B8Bea02277dF1707bB7E2" }, { "name": "GNTK", "symbol": "GNTS", "decimals": 18, - "address": "0x6989065500a6B9AAF59F3DCC4cf9e30d0ea9d394" + "address": "0xD4Ba730aA7b2E7Bb7515b265c39dd0796cF7d440" }, { "name": "MLTTK", "symbol": "MLTTS", "decimals": 18, - "address": "0x18c1BC9b6049FCC6780549Ad2aA247426f81e916" + "address": "0xee80cFA1F62427E52A62197A86f76a16eA7b7627" }, { "name": "DAIL", "symbol": "DAIL", "decimals": 18, - "address": "0x75d34909F783D56B7B8Be71085fE63777Dc8fDFE" + "address": "0x2dD8d8B7E8489E361fa3a455888a371eDcB645d4" }, { "name": "wBTCL", "symbol": "wBTCP", "decimals": 8, - "address": "0x3577F97253469b560CD6442AB37A262a292003f3" + "address": "0x3dE741Ebc93DbEC9C97eccbbA1aD2577b4335980" }, { "name": "BATL", "symbol": "BATW", "decimals": 18, - "address": "0x4A9D48Db0008F8778160dDF142b28a858c427B48" + "address": "0x6989065500a6B9AAF59F3DCC4cf9e30d0ea9d394" }, { "name": "GNTL", "symbol": "GNTW", "decimals": 18, - "address": "0x8ce06E5aF9A1221a88282A5Ce65D750BE16b0079" + "address": "0x18c1BC9b6049FCC6780549Ad2aA247426f81e916" }, { "name": "MLTTL", "symbol": "MLTTW", "decimals": 18, - "address": "0xF1286aD858DeE56B79D5F23f14040849fA3631dA" + "address": "0x75d34909F783D56B7B8Be71085fE63777Dc8fDFE" }, { "name": "Wrapped Ether", "symbol": "WETH", "decimals": 18, - "address": "0x9267631d42C7D2747f8e5573169BdceAE87535b8" + "address": "0x3577F97253469b560CD6442AB37A262a292003f3" } ] diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index 29e37a860..cdcff3a58 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -249,7 +249,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { uint256 offset = uint256(_operatorInput[calldataPtr:calldataPtr + 32]); // The length of the pubdata input should be stored right next to the calldata. // We need to change offset by 32 - 4 = 28 bytes, since 32 bytes is the length of the offset - // itself and the 4 bytes are the selector which is not included inside the offset. + // itself and the 4 bytes are the selector which is not included inside the offset. require(offset == calldataPtr + 28, "invalid offset"); uint256 length = uint256(_operatorInput[calldataPtr + 32:calldataPtr + 64]); From e98126a126a6354cd0a81e9b2c14dd57a675479c Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 16:29:44 +0200 Subject: [PATCH 17/26] fix lint --- .../chain-deps/facets/Executor.sol | 14 +++++++------- system-contracts/contracts/L1Messenger.sol | 16 ++++++++++++++-- .../contracts/SystemContractErrors.sol | 4 +++- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index c661379fe..57444cbac 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -51,13 +51,13 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { // Get the chained hash of priority transaction hashes. LogProcessingOutput memory logOutput = _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); - L1DAValidatorOutput memory daOutput = IL1DAValidator(s.l1DAValidator).checkDA( - s.chainId, - uint256(_newBatch.batchNumber), - logOutput.l2DAValidatorOutputHash, - _newBatch.operatorDAInput, - TOTAL_BLOBS_IN_COMMITMENT - ); + L1DAValidatorOutput memory daOutput = IL1DAValidator(s.l1DAValidator).checkDA({ + _chainId: s.chainId, + _batchNumber: uint256(_newBatch.batchNumber), + _l2DAValidatorOutputHash: logOutput.l2DAValidatorOutputHash, + _operatorDAInput: _newBatch.operatorDAInput, + _maxBlobsSupported: TOTAL_BLOBS_IN_COMMITMENT + }); require(_previousBatch.batchHash == logOutput.previousBatchHash, "l"); // Check that the priority operation hash in the L2 logs is as expected diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index cdcff3a58..ef2774891 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -250,7 +250,13 @@ contract L1Messenger is IL1Messenger, ISystemContract { // The length of the pubdata input should be stored right next to the calldata. // We need to change offset by 32 - 4 = 28 bytes, since 32 bytes is the length of the offset // itself and the 4 bytes are the selector which is not included inside the offset. - require(offset == calldataPtr + 28, "invalid offset"); + if (offset != calldataPtr + 28) { + revert ReconstructionMismatch( + PubdataField.Offset, + bytes32(L2_TO_L1_LOGS_MERKLE_TREE_LEAVES), + bytes32(uint256(numberOfL2ToL1Logs)) + ); + } uint256 length = uint256(_operatorInput[calldataPtr + 32:calldataPtr + 64]); // Shift calldata ptr past the pubdata offset and len @@ -268,7 +274,13 @@ contract L1Messenger is IL1Messenger, ISystemContract { calldataPtr += 4; // We need to ensure that length is enough to read all logs - require(length >= 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE, "invalid length"); + if (length < 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE) { + revert ReconstructionMismatch( + PubdataField.Length, + bytes32(L2_TO_L1_LOGS_MERKLE_TREE_LEAVES), + bytes32(uint256(numberOfL2ToL1Logs)) + ); + } bytes32[] memory l2ToL1LogsTreeArray = new bytes32[](L2_TO_L1_LOGS_MERKLE_TREE_LEAVES); bytes32 reconstructedChainedLogsHash; diff --git a/system-contracts/contracts/SystemContractErrors.sol b/system-contracts/contracts/SystemContractErrors.sol index fd553c3cb..aa4857c49 100644 --- a/system-contracts/contracts/SystemContractErrors.sol +++ b/system-contracts/contracts/SystemContractErrors.sol @@ -68,7 +68,9 @@ enum PubdataField { InputLogsHash, InputLogsRootHash, InputMsgsHash, - InputBytecodeHash + InputBytecodeHash, + Offset, + Length } enum BytecodeError { From fd1d20df857e360300727d2eb175598d95bb2fe4 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 16:37:48 +0200 Subject: [PATCH 18/26] fix system contracts build --- system-contracts/contracts/L1Messenger.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index ef2774891..75e91516e 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -246,18 +246,18 @@ contract L1Messenger is IL1Messenger, ISystemContract { } calldataPtr += 32; - uint256 offset = uint256(_operatorInput[calldataPtr:calldataPtr + 32]); + uint256 offset = uint256(bytes32(_operatorInput[calldataPtr:calldataPtr + 32])); // The length of the pubdata input should be stored right next to the calldata. // We need to change offset by 32 - 4 = 28 bytes, since 32 bytes is the length of the offset // itself and the 4 bytes are the selector which is not included inside the offset. if (offset != calldataPtr + 28) { revert ReconstructionMismatch( PubdataField.Offset, - bytes32(L2_TO_L1_LOGS_MERKLE_TREE_LEAVES), - bytes32(uint256(numberOfL2ToL1Logs)) + bytes32(calldataPtr + 28), + bytes32(offset) ); } - uint256 length = uint256(_operatorInput[calldataPtr + 32:calldataPtr + 64]); + uint256 length = uint256(bytes32(_operatorInput[calldataPtr + 32:calldataPtr + 64])); // Shift calldata ptr past the pubdata offset and len calldataPtr += 64; @@ -277,8 +277,8 @@ contract L1Messenger is IL1Messenger, ISystemContract { if (length < 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE) { revert ReconstructionMismatch( PubdataField.Length, - bytes32(L2_TO_L1_LOGS_MERKLE_TREE_LEAVES), - bytes32(uint256(numberOfL2ToL1Logs)) + bytes32(4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE), + bytes32(length) ); } From ee7c367432ccef07d78e1882f593200184754922 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 16:44:19 +0200 Subject: [PATCH 19/26] fix some unneeded updates --- da-contracts/contracts/CalldataDA.sol | 8 ++--- .../data-availability/CalldataDA.sol | 8 ++--- .../test/test_config/constant/hardhat.json | 32 +++++++++---------- system-contracts/bootloader/bootloader.yul | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/da-contracts/contracts/CalldataDA.sol b/da-contracts/contracts/CalldataDA.sol index e6574468d..c434e540d 100644 --- a/da-contracts/contracts/CalldataDA.sol +++ b/da-contracts/contracts/CalldataDA.sol @@ -18,7 +18,7 @@ uint256 constant BLOB_COMMITMENT_SIZE = 32; /// @notice Contract that contains the functionality for process the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { - /// @notice Parses the input that the l2 Da validator has provided to the contract. + /// @notice Parses the input that the L2 DA validator has provided to the contract. /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// @param _operatorDAInput The DA input by the operator provided on L1. @@ -37,7 +37,7 @@ abstract contract CalldataDA { bytes calldata l1DaInput ) { - // The preimage under the hash `l2DAValidatorOutputHash` is expected to be in the following format: + // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: // - First 32 bytes are the hash of the uncompressed state diff. // - Then, there is a 32-byte hash of the full pubdata. // - Then, there is the 1-byte number of blobs published. @@ -62,10 +62,10 @@ abstract contract CalldataDA { uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; - // Now, we need to double check that the provided input was indeed retutned by the L2 DA validator. + // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. require(keccak256(_operatorDAInput[:ptr]) == _l2DAValidatorOutputHash, "invalid l2 DA output hash"); - // The rest of the output were provided specifically by the operator + // The rest of the output was provided specifically by the operator l1DaInput = _operatorDAInput[ptr:]; } diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol index e5ea0ecdd..1d618310b 100644 --- a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol @@ -18,7 +18,7 @@ uint256 constant BLOB_COMMITMENT_SIZE = 32; /// @notice Contract that contains the functionality for process the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { - /// @notice Parses the input that the l2 Da validator has provided to the contract. + /// @notice Parses the input that the L2 DA validator has provided to the contract. /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// @param _operatorDAInput The DA input by the operator provided on L1. @@ -37,7 +37,7 @@ abstract contract CalldataDA { bytes calldata l1DaInput ) { - // The preimage under the hash `l2DAValidatorOutputHash` is expected to be in the following format: + // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: // - First 32 bytes are the hash of the uncompressed state diff. // - Then, there is a 32-byte hash of the full pubdata. // - Then, there is the 1-byte number of blobs published. @@ -62,10 +62,10 @@ abstract contract CalldataDA { uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; - // Now, we need to double check that the provided input was indeed retutned by the L2 DA validator. + // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. require(keccak256(_operatorDAInput[:ptr]) == _l2DAValidatorOutputHash, "invalid l2 DA output hash"); - // The rest of the output were provided specifically by the operator + // The rest of the output was provided specifically by the operator l1DaInput = _operatorDAInput[ptr:]; } diff --git a/l1-contracts/test/test_config/constant/hardhat.json b/l1-contracts/test/test_config/constant/hardhat.json index 4b38cf5a1..1a59b10d3 100644 --- a/l1-contracts/test/test_config/constant/hardhat.json +++ b/l1-contracts/test/test_config/constant/hardhat.json @@ -3,96 +3,96 @@ "name": "DAI", "symbol": "DAI", "decimals": 18, - "address": "0x4e5261FDDB30B6FaC019ab8517119B06fb65A8D8" + "address": "0x9F9Cd69A2a3b296B8C3b0E59A942d1B893c6c988" }, { "name": "wBTC", "symbol": "wBTC", "decimals": 8, - "address": "0xC04fcb89ea8AF6E0d7407304F6f8e2471975f676" + "address": "0xe7B8C0dd29D50D54b9d75e923FB96562B7513A6f" }, { "name": "BAT", "symbol": "BAT", "decimals": 18, - "address": "0x0bADaf09ddaC0F1Fd1ef1bc6F9871F322245F075" + "address": "0x4C56e415d1C59c69FE953aEd7C41686f5ee33B2c" }, { "name": "GNT", "symbol": "GNT", "decimals": 18, - "address": "0x57E6A02f8622D71B293f9c291177C857a1d3FadB" + "address": "0x7D12865902a998Ae6C7B8Bea02277dF1707bB7E2" }, { "name": "MLTT", "symbol": "MLTT", "decimals": 18, - "address": "0x9F9Cd69A2a3b296B8C3b0E59A942d1B893c6c988" + "address": "0xD4Ba730aA7b2E7Bb7515b265c39dd0796cF7d440" }, { "name": "DAIK", "symbol": "DAIK", "decimals": 18, - "address": "0xe7B8C0dd29D50D54b9d75e923FB96562B7513A6f" + "address": "0xee80cFA1F62427E52A62197A86f76a16eA7b7627" }, { "name": "wBTCK", "symbol": "wBTCK", "decimals": 8, - "address": "0x4C56e415d1C59c69FE953aEd7C41686f5ee33B2c" + "address": "0x2dD8d8B7E8489E361fa3a455888a371eDcB645d4" }, { "name": "BATK", "symbol": "BATS", "decimals": 18, - "address": "0x7D12865902a998Ae6C7B8Bea02277dF1707bB7E2" + "address": "0x3dE741Ebc93DbEC9C97eccbbA1aD2577b4335980" }, { "name": "GNTK", "symbol": "GNTS", "decimals": 18, - "address": "0xD4Ba730aA7b2E7Bb7515b265c39dd0796cF7d440" + "address": "0x6989065500a6B9AAF59F3DCC4cf9e30d0ea9d394" }, { "name": "MLTTK", "symbol": "MLTTS", "decimals": 18, - "address": "0xee80cFA1F62427E52A62197A86f76a16eA7b7627" + "address": "0x18c1BC9b6049FCC6780549Ad2aA247426f81e916" }, { "name": "DAIL", "symbol": "DAIL", "decimals": 18, - "address": "0x2dD8d8B7E8489E361fa3a455888a371eDcB645d4" + "address": "0x75d34909F783D56B7B8Be71085fE63777Dc8fDFE" }, { "name": "wBTCL", "symbol": "wBTCP", "decimals": 8, - "address": "0x3dE741Ebc93DbEC9C97eccbbA1aD2577b4335980" + "address": "0x3577F97253469b560CD6442AB37A262a292003f3" }, { "name": "BATL", "symbol": "BATW", "decimals": 18, - "address": "0x6989065500a6B9AAF59F3DCC4cf9e30d0ea9d394" + "address": "0x4A9D48Db0008F8778160dDF142b28a858c427B48" }, { "name": "GNTL", "symbol": "GNTW", "decimals": 18, - "address": "0x18c1BC9b6049FCC6780549Ad2aA247426f81e916" + "address": "0x8ce06E5aF9A1221a88282A5Ce65D750BE16b0079" }, { "name": "MLTTL", "symbol": "MLTTW", "decimals": 18, - "address": "0x75d34909F783D56B7B8Be71085fE63777Dc8fDFE" + "address": "0xF1286aD858DeE56B79D5F23f14040849fA3631dA" }, { "name": "Wrapped Ether", "symbol": "WETH", "decimals": 18, - "address": "0x3577F97253469b560CD6442AB37A262a292003f3" + "address": "0x9267631d42C7D2747f8e5573169BdceAE87535b8" } ] diff --git a/system-contracts/bootloader/bootloader.yul b/system-contracts/bootloader/bootloader.yul index 87f3190d3..3c1c669bd 100644 --- a/system-contracts/bootloader/bootloader.yul +++ b/system-contracts/bootloader/bootloader.yul @@ -2744,7 +2744,7 @@ object "Bootloader" { // Third slot -- length of pubdata let len := mload(add(ptr, 96)) - // 4 bytes for selector, 32 bytes for ABI-encoded l2 DA validator address, + // 4 bytes for selector, 32 bytes for ABI-encoded L2 DA validator address, // 32 bytes for array offset and 32 bytes for array length let fullLen := add(len, 100) From 828d0f6d4972b3c5fa4ef8a6a3a4d245fb715790 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 17:16:14 +0200 Subject: [PATCH 20/26] fix lint --- system-contracts/contracts/L1Messenger.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index 75e91516e..53d518bc8 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -251,11 +251,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { // We need to change offset by 32 - 4 = 28 bytes, since 32 bytes is the length of the offset // itself and the 4 bytes are the selector which is not included inside the offset. if (offset != calldataPtr + 28) { - revert ReconstructionMismatch( - PubdataField.Offset, - bytes32(calldataPtr + 28), - bytes32(offset) - ); + revert ReconstructionMismatch(PubdataField.Offset, bytes32(calldataPtr + 28), bytes32(offset)); } uint256 length = uint256(bytes32(_operatorInput[calldataPtr + 32:calldataPtr + 64])); From 8109652563530455a1c971a880aa2ddc39a4dad9 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 19:47:16 +0200 Subject: [PATCH 21/26] fix check hashes --- .../contracts/bridgehub/Bridgehub.sol | 1 + system-contracts/SystemContractsHashes.json | 54 +++++++++---------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index f6079e428..fc7e30545 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -228,6 +228,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus // // For simpler handling we allow anyone to call this method. It is okay, since during bridging operations // it is double checked that `assetId` is indeed derived from the `stmDeployer`. + // TODO(EVM-703): This logic shold be revised once interchain communication is implemented. address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index 779330fdc..049311271 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": "0x0100005d1aca0cbed0567a1c7e07a1da321386b1665e440da22053addd4639e1", + "bytecodeHash": "0x0100005d25ca74d6dd4855495f92b76a7542f3b9a1145129bef7c874d11de0cd", "sourceCodeHash": "0xea3806fcaf7728463f559fe195d8acdc47a7659d58119e0a51efcf86a691b61b" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c7149301f5f29fa1757a600b5088354d9e280a6d8f69bcce4f2dbce660", + "bytecodeHash": "0x010007c7fe668eff6936a7eb0bfd603671014aaa47c52e4847ddc1f65e4940bf", "sourceCodeHash": "0x9d2b7376c4cd9b143ddd5dfe001a9faae99b9125ccd45f2915c3ce0099643ed9" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004deb11ca32277ab54e2d036f4d33b4a7e218ced1fee63b5b4713ff50ff", + "bytecodeHash": "0x0100004d0dcb8cebf6f81f46011cd8ec8984e7ee82448ddd99e2c15da45fcaa2", "sourceCodeHash": "0xdde7c49a94cc3cd34c3e7ced1b5ba45e4740df68d26243871edbe393e7298f7a" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100013fc989177bcb6ec29d851cd01f845de31963ea5817ea7a684767c36368", + "bytecodeHash": "0x0100013ffa6d46d34d3c93202665387eb170af768e5e40e4e98f4b3484dac3ca", "sourceCodeHash": "0xb0cec0016f481ce023478f71727fbc0d82e967ddc0508e4d47f5c52292a3f790" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e5711fff19f0048d745b0177b8b73952963b6de79ff4e16c902dbcc091", + "bytecodeHash": "0x010004e5d1fbad7f0a7e656eaf78187f7c46b83122643853f132c61e8c431019", "sourceCodeHash": "0xea9627fd5e6e905c268ba801e87bf2d9022bea036982d2b54425f2388b27e6b1" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x0100004937dba13ac3e393def7fe6cf01da88bbe9b087c397e950301fe14377d", + "bytecodeHash": "0x0100004993b764833fd41d3324324b8a5408b83a79bca1c5f2218e25f6540e65", "sourceCodeHash": "0x217e65f55c8add77982171da65e0db8cc10141ba75159af582973b332a4e098a" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055d7adab6efac115df578d88bc113738dc6ad811329c7575c2af3d91756", + "bytecodeHash": "0x0100055dbf6d8f56faa896e4cc8e987aa24aaaee5fe85fc17243d908b9b74f69", "sourceCodeHash": "0xeb5ac8fc83e1c8619db058a9b6973958bd6ed1b6f4938f8f4541d702f12e085d" }, { @@ -59,63 +59,63 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x0100003bf60f81cb3074170af6420e8d74b710fea0b1fa04e291a080ec17f98a", + "bytecodeHash": "0x0100003b05ace5d6b4b0f296b19d72c048f79bdb8cb3ee647806b07f4e5bbcd8", "sourceCodeHash": "0x4212e99cbc1722887cfb5b4cb967f278ac8642834786f0e3c6f3b324a9316815" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006f5cf65a28234e0791927389664cced66dd3d600aefbe120d63e9debae", + "bytecodeHash": "0x0100006fa9bd44feb2af80203eb413026decdd7c1ccb52b539e7a8e8f2f42f29", "sourceCodeHash": "0x8da495a9fc5aa0d7d20a165a4fc8bc77012bec29c472015ea5ecc0a2bd706137" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010001e9765b885f7e6422722e36e6d375beffc916047f5cec419d11d178baea", - "sourceCodeHash": "0xd83b345b8633affb0bba2296fec9424b4fe9483b60c20ca407d755857a385d8e" + "bytecodeHash": "0x010001f5931d8a6310005972dbb01adb4211b29e5ad0a22c682d70f34c4b4e48", + "sourceCodeHash": "0xa275cd393320fba29e5c94f399c1ae6743b4221b05f13b395a00648dcedc2540" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x0100010517992363aa510731a717db3c7740d1c31e69718090d07f73d47ba960", + "bytecodeHash": "0x010001052a89f68a2a3a2e4b4c67b740859d3fd95a0cde8317441047cf60db63", "sourceCodeHash": "0x4cdafafd4cfdf410b31641e14487ea657be3af25e5ec1754fcd7ad67ec23d8be" }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x010000975d32dfb09a7935f71e54783c30c9eaab051e961d7f834730e52f3a14", + "bytecodeHash": "0x0100009732b021d0c301d499dde61446b021b62c0ea93c78f3e3cd8501bf09c5", "sourceCodeHash": "0xcb190d0dfd41bbc809409a8aa04a4847b86edfe010b1d75e23b4c8d07b13a9d0" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005d3182a51477d7ee3488aafab351bb5f0560412e4df7e5a5e21ca87cd5", + "bytecodeHash": "0x0100005d2589089ef8e7f7af29293876bc496266bafaa2b704d09627bf271fbd", "sourceCodeHash": "0x4834adf62dbaefa1a1c15d36b5ad1bf2826e7d888a17be495f7ed4e4ea381aa8" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000db9e6c4608ca06cd3a69484b23f5e4ee196fa046cb2db8c0b56d3a2163", + "bytecodeHash": "0x010000db506ed94921e7f4145fdacc21063000067bfe50e8af0f061519c04b6c", "sourceCodeHash": "0xaa2ed3a26af30032c00a612ac327e0cdf5288b7c932ae903462355f863f950cb" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x01000049825a39e7057700666867a2b2be806c15d9b2addb60d335bb61b405d9", + "bytecodeHash": "0x010000494900cd3cd761e2676208fc86bc197228ce615e4665e5223bc31a9bd8", "sourceCodeHash": "0x0da0d1279f906147a40e278f52bf3e4d5d4f24225935e4611cc04f4b387b5286" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a722bf92cd15264a662582a6806db24101493ad7a1f202428a2a10e7bc", + "bytecodeHash": "0x010001a7d4d930ab07e144e2b7cf47ec3042b7c84bdfd6122ed13662e5ad790d", "sourceCodeHash": "0x532a962209042f948e8a13e3f4cf12b6d53631e0fc5fa53083c7e2d8062771c0" }, { @@ -185,35 +185,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x010003cb983fb1cded326ff4429b8a637a7b045233c427dc498373be58312969", - "sourceCodeHash": "0xa4f83a28bcc3d3a79c197a77de03dce464b2141c3aaf970ad3f3487f41ae5690" + "bytecodeHash": "0x010003cbab1bb46695dfda825246f92120398b9573f355c1ff795c30184ac2b2", + "sourceCodeHash": "0x02b3e8d6c1d542a123d50aa6d04bc9736a92376e922c3304650001636e942a95" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010009559ef1268a8b83687828c5dfc804c58a018028694fc931df712fb67f58", - "sourceCodeHash": "0xe7970c1738f2817b50bfcd16038227c5c059f12309407977df453bc6d365d31e" + "bytecodeHash": "0x0100095516027c64c3252e7193d9a6d6148100e2c7b9387f15d856e6713aa0e9", + "sourceCodeHash": "0x7a1d1d2a5534c659fcc343178e9f9d68ccb3f49ce8e54a81a6c20d15c46b2fd9" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010008db4c71130d6a96d77f2d3bf3573a5cddc72ecee030ecc9c3bc22764039", - "sourceCodeHash": "0x1b6ef61d0dbbbaa049946b95dc6d12d9335baed03b8c3364a0cbdb404495c045" + "bytecodeHash": "0x010008db6f54b61ecdb2c6a8f7575fa87c205402839318f429f6fa2beb8b00c2", + "sourceCodeHash": "0x37f3b77504a53d38ce29c8521264c10dc6f9bbc80f15814862a0e4a29f0f1612" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100095b01a95cb5caa633702442aeeac8ea64bf4dab31d534e0bb815b5c2820", - "sourceCodeHash": "0xb5a2f9d7d8990f9a1296f699573a5dffe3a1d2ed53d9d3c60b313cd9443221ab" + "bytecodeHash": "0x0100095b6cbccf0b0af548f8ccb027d02c90681ae9890a41a23ecf4b590e72c4", + "sourceCodeHash": "0x1c9b760bc9abd39183ecaf1397465c149c63c317e107214ac8e72737c49cd5da" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008eba57f69c88344eced34109c75d34b4bf84db66f47b47b4579b930355e", - "sourceCodeHash": "0xb7697ee1c00b1b8af52c166e3a6deb862281616ca8861ab3aa0b51e34aed8715" + "bytecodeHash": "0x010008eb2ba1ec6290b553a401b9609363d9cc89796b3dd1aad8fb24cd88702e", + "sourceCodeHash": "0x3230de3b65d823d6f5142d7217a0597948d0f8744a8c1f0a9271b7eb40cfdf5b" } ] From b4b7c037f34ecfac91a90761947396d4b56c5f3d Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 13 Aug 2024 19:50:07 +0200 Subject: [PATCH 22/26] fix spell --- l1-contracts/contracts/bridgehub/Bridgehub.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index fc7e30545..edee1af5b 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -228,7 +228,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus // // For simpler handling we allow anyone to call this method. It is okay, since during bridging operations // it is double checked that `assetId` is indeed derived from the `stmDeployer`. - // TODO(EVM-703): This logic shold be revised once interchain communication is implemented. + // TODO(EVM-703): This logic should be revised once interchain communication is implemented. address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); From 2a2e6cf566edf7208bf6ad54c756280f74b3bfe4 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 14 Aug 2024 10:33:17 +0200 Subject: [PATCH 23/26] amend some functions --- l1-contracts/src.ts/deploy.ts | 4 ++-- l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 84ee4b438..de6125a12 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -215,8 +215,8 @@ export class Deployer { callConstructor: true, value: 0, input: ethers.utils.defaultAbiCoder.encode( - ["bytes32", "address"], - [l2TokenProxyBytecodeHash, this.addresses.Governance] + ["uint256", "bytes32", "address"], + [getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), l2TokenProxyBytecodeHash, applyL1ToL2Alias(this.addresses.Governance)] ), }; diff --git a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts index 433365064..3ca81bf74 100644 --- a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts +++ b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts @@ -63,7 +63,7 @@ async function setL2TokenBeacon(deployer: Deployer, chainId: string, gasPrice: B chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, gasPrice, - l2NTV.interface.encodeFunctionData("setL2TokenBeacon", [false, ethers.constants.AddressZero]), + l2NTV.interface.encodeFunctionData("configureL2TokenBeacon", [false, ethers.constants.AddressZero]), priorityTxMaxGasLimit ); if (deployer.verbose) { From 9f6d439494f39f57312ca1fd015618e722340191 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 14 Aug 2024 11:37:33 +0200 Subject: [PATCH 24/26] make scripts work --- l1-contracts/scripts/sync-layer.ts | 2 +- l1-contracts/src.ts/deploy.ts | 54 +++++++++++++++--------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts index 16811295e..d6f790573 100644 --- a/l1-contracts/scripts/sync-layer.ts +++ b/l1-contracts/scripts/sync-layer.ts @@ -356,7 +356,7 @@ async function registerSLContractsOnL1(deployer: Deployer) { const receipt2 = await deployer.executeUpgrade( stmDeploymentTracker.address, value, - stmDeploymentTracker.encodeFunctionData("registerSTMAssetOnL2SharedBridge", [ + stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL2SharedBridge", [ chainId, l1STM.address, value, diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index de6125a12..86eddeeee 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -62,7 +62,7 @@ import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory" import type { FacetCut } from "./diamondCut"; import { getCurrentFacetCutsForAdd } from "./diamondCut"; -import { ChainAdminFactory, ERC20Factory, StateTransitionManagerFactory } from "../typechain"; +import { BridgehubFactory, ChainAdminFactory, ERC20Factory, StateTransitionManagerFactory } from "../typechain"; import { IL1AssetRouterFactory } from "../typechain/IL1AssetRouterFactory"; import { IL1NativeTokenVaultFactory } from "../typechain/IL1NativeTokenVaultFactory"; @@ -1042,32 +1042,32 @@ export class Deployer { if (this.verbose) { console.log(`StateTransition System registered, gas used: ${receipt1.gasUsed.toString()}`); } - } - - const stmDeploymentTracker = this.stmDeploymentTracker(this.deployWallet); - - const l1AssetRouter = this.defaultSharedBridge(this.deployWallet); - const whitelistData = l1AssetRouter.interface.encodeFunctionData("setAssetDeploymentTracker", [ - ethers.utils.hexZeroPad(this.addresses.StateTransition.StateTransitionProxy, 32), - stmDeploymentTracker.address, - ]); - const receipt2 = await this.executeUpgrade(l1AssetRouter.address, 0, whitelistData); - if (this.verbose) { - console.log("STM deployment tracker whitelisted in L1 Shared Bridge", receipt2.gasUsed.toString()); - console.log( - `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` - ); - } + + const stmDeploymentTracker = this.stmDeploymentTracker(this.deployWallet); + + const l1AssetRouter = this.defaultSharedBridge(this.deployWallet); + const whitelistData = l1AssetRouter.interface.encodeFunctionData("setAssetDeploymentTracker", [ + ethers.utils.hexZeroPad(this.addresses.StateTransition.StateTransitionProxy, 32), + stmDeploymentTracker.address, + ]); + const receipt2 = await this.executeUpgrade(l1AssetRouter.address, 0, whitelistData); + if (this.verbose) { + console.log("STM deployment tracker whitelisted in L1 Shared Bridge", receipt2.gasUsed.toString()); + console.log( + `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); + } - const data1 = stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL1", [ - this.addresses.StateTransition.StateTransitionProxy, - ]); - const receipt3 = await this.executeUpgrade(this.addresses.Bridgehub.STMDeploymentTrackerProxy, 0, data1); - if (this.verbose) { - console.log("STM asset registered in L1 Shared Bridge via STM Deployment Tracker", receipt3.gasUsed.toString()); - console.log( - `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` - ); + const data1 = stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL1", [ + this.addresses.StateTransition.StateTransitionProxy, + ]); + const receipt3 = await this.executeUpgrade(this.addresses.Bridgehub.STMDeploymentTrackerProxy, 0, data1); + if (this.verbose) { + console.log("STM asset registered in L1 Shared Bridge via STM Deployment Tracker", receipt3.gasUsed.toString()); + console.log( + `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); + } } } } @@ -1483,7 +1483,7 @@ export class Deployer { } public bridgehubContract(signerOrProvider: Signer | providers.Provider) { - return IBridgehubFactory.connect(this.addresses.Bridgehub.BridgehubProxy, signerOrProvider); + return BridgehubFactory.connect(this.addresses.Bridgehub.BridgehubProxy, signerOrProvider); } public stateTransitionManagerContract(signerOrProvider: Signer | providers.Provider) { From 576a59c5abb03cec0f07c7973837e9e1dc146c77 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 14 Aug 2024 11:39:06 +0200 Subject: [PATCH 25/26] fmt --- l1-contracts/src.ts/deploy.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 86eddeeee..a8a618a6d 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -1042,7 +1042,7 @@ export class Deployer { if (this.verbose) { console.log(`StateTransition System registered, gas used: ${receipt1.gasUsed.toString()}`); } - + const stmDeploymentTracker = this.stmDeploymentTracker(this.deployWallet); const l1AssetRouter = this.defaultSharedBridge(this.deployWallet); @@ -1063,7 +1063,10 @@ export class Deployer { ]); const receipt3 = await this.executeUpgrade(this.addresses.Bridgehub.STMDeploymentTrackerProxy, 0, data1); if (this.verbose) { - console.log("STM asset registered in L1 Shared Bridge via STM Deployment Tracker", receipt3.gasUsed.toString()); + console.log( + "STM asset registered in L1 Shared Bridge via STM Deployment Tracker", + receipt3.gasUsed.toString() + ); console.log( `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` ); From 779c707226f1178a9237b1f59201d003fd94ce2a Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 14 Aug 2024 11:41:22 +0200 Subject: [PATCH 26/26] fix lint --- l1-contracts/src.ts/deploy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index a8a618a6d..82c1e0ff5 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -48,7 +48,6 @@ import { // priorityTxMaxGasLimit, } from "./utils"; import type { ChainAdminCall } from "./utils"; -import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; import { ITransparentUpgradeableProxyFactory } from "../typechain/ITransparentUpgradeableProxyFactory"; import { ProxyAdminFactory } from "../typechain/ProxyAdminFactory";