diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 93f0a267f..7e68d396c 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -243,7 +243,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus function registerSettlementLayer( uint256 _newSettlementLayerChainId, bool _isWhitelisted - ) external onlyChainSTM(_newSettlementLayerChainId) onlyL1 { + ) external onlyOwner onlyL1 { whitelistedSettlementLayers[_newSettlementLayerChainId] = _isWhitelisted; emit SettlementLayerRegistered(_newSettlementLayerChainId, _isWhitelisted); } 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 c1b3c71d0..25f4fe71f 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -146,6 +146,15 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } } + /// @notice Extracts slice until the end of the array. + /// @dev It is used in one place in order to circumvent the stack too deep error. + function extractSliceUntilEnd( + bytes32[] calldata _proof, + uint256 _start + ) internal pure returns (bytes32[] memory slice) { + slice = extractSlice(_proof, _start, _proof.length); + } + /// @inheritdoc IMailbox function proveL2LeafInclusion( uint256 _batchNumber, @@ -162,8 +171,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { bytes32 _leaf, bytes32[] calldata _proof ) internal view returns (bool) { - // FIXME: maybe support legacy interface - uint256 ptr = 0; bytes32 chainIdLeaf; { @@ -177,9 +184,12 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { ); ptr += logLeafProofLen; - // 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.settlementLayer == address(0)) { + // If the `batchLeafProofLen` is 0, then we assume that this is L1 contract of the top-level + // in the aggregation, i.e. the batch root is stored here on L1. + if (batchLeafProofLen == 0) { + // Double checking that the batch has been executed. + require(_batchNumber <= s.totalBatchesExecuted, "xx"); + bytes32 correctBatchRoot = s.l2LogsRootHashes[_batchNumber]; require(correctBatchRoot != bytes32(0), "local root is 0"); return correctBatchRoot == batchSettlementRoot; @@ -205,6 +215,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { uint256 settlementLayerBatchNumber; uint256 settlementLayerBatchRootMask; + address settlementLayerAddress; // Preventing stack too deep error { @@ -213,14 +224,25 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { ++ptr; settlementLayerBatchNumber = uint256(settlementLayerPackedBatchInfo >> 128); settlementLayerBatchRootMask = uint256(settlementLayerPackedBatchInfo & ((1 << 128) - 1)); + + uint256 settlementLayerChainId = uint256(_proof[ptr]); + ++ptr; + + // Assuming that `settlementLayerChainId` is an honest chain, the `chainIdLeaf` should belong + // to a chain's message root only if the chain has indeed executed its batch on top of it. + // + // We trust all chains whitelisted by the Bridgehub governance. + require(IBridgehub(s.bridgehub).whitelistedSettlementLayers(settlementLayerChainId), "Mailbox: wrong STM"); + + settlementLayerAddress = IBridgehub(s.bridgehub).getHyperchain(settlementLayerChainId); } return - IMailbox(s.settlementLayer).proveL2LeafInclusion( + IMailbox(settlementLayerAddress).proveL2LeafInclusion( settlementLayerBatchNumber, settlementLayerBatchRootMask, chainIdLeaf, - extractSlice(_proof, ptr, _proof.length) + extractSliceUntilEnd(_proof, ptr) ); } @@ -231,8 +253,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { L2Log memory _log, bytes32[] calldata _proof ) internal view returns (bool) { - // require(_batchNumber <= s.totalBatchesExecuted, "xx"); - bytes32 hashedLog = keccak256( // solhint-disable-next-line func-named-parameters abi.encodePacked(_log.l2ShardId, _log.isService, _log.txNumberInBatch, _log.sender, _log.key, _log.value) diff --git a/l1-contracts/deploy-scripts/Gateway.s.sol b/l1-contracts/deploy-scripts/Gateway.s.sol index 6e24be0cf..f03867764 100644 --- a/l1-contracts/deploy-scripts/Gateway.s.sol +++ b/l1-contracts/deploy-scripts/Gateway.s.sol @@ -117,10 +117,10 @@ contract GatewayScript is Script { } function registerGateway() public { - IStateTransitionManager stm = IStateTransitionManager(config.stateTransitionProxy); - Ownable ownable = Ownable(config.stateTransitionProxy); + IBridgehub bridgehub = IBridgehub(config.bridgehub); + Ownable ownable = Ownable(config.bridgehub); vm.prank(ownable.owner()); - stm.registerSettlementLayer(config.gatewayChainId, true); + bridgehub.registerSettlementLayer(config.gatewayChainId, true); // bytes memory data = abi.encodeCall(stm.registerSettlementLayer, (config.chainChainId, true)); // Utils.executeUpgrade({ // _governor: ownable.owner(), diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts index eda5af287..d9e5a702c 100644 --- a/l1-contracts/scripts/sync-layer.ts +++ b/l1-contracts/scripts/sync-layer.ts @@ -313,14 +313,13 @@ async function registerSLContractsOnL1(deployer: Deployer) { 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 gatewayAddress = await l1STM.getHyperchain(chainId); // this script only works when owner is the deployer console.log("Registering Gateway chain id on the STM"); const receipt1 = await deployer.executeUpgrade( - l1STM.address, + l1Bridgehub.address, 0, l1Bridgehub.interface.encodeFunctionData("registerSettlementLayer", [chainId, true]) ); diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index d0c8a151a..df2857b40 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -1123,9 +1123,9 @@ export class Deployer { } public async registerSettlementLayer() { - const stm = this.stateTransitionManagerContract(this.deployWallet); - const calldata = stm.interface.encodeFunctionData("registerSettlementLayer", [this.chainId, true]); - await this.executeUpgrade(this.addresses.StateTransition.StateTransitionProxy, 0, calldata); + const bridgehub = this.bridgehubContract(this.deployWallet); + const calldata = bridgehub.interface.encodeFunctionData("registerSettlementLayer", [this.chainId, true]); + await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, calldata); if (this.verbose) { console.log("Gateway registered"); } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol index 50e5951dc..10f386d95 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/ProvingL2LogsInclusion.t.sol @@ -47,19 +47,18 @@ contract MailboxL2LogsProve is MailboxTest { index = elements.length - 1; } - // FIXME: restore the test - // function test_RevertWhen_batchNumberGreaterThanBatchesExecuted() public { - // L2Message memory message = L2Message({txNumberInBatch: 0, sender: sender, data: data}); - // bytes32[] memory proof = new bytes32[](0); - - // vm.expectRevert(bytes("xx")); - // mailboxFacet.proveL2MessageInclusion({ - // _batchNumber: batchNumber + 1, - // _index: 0, - // _message: message, - // _proof: proof - // }); - // } + function test_RevertWhen_batchNumberGreaterThanBatchesExecuted() public { + L2Message memory message = L2Message({txNumberInBatch: 0, sender: sender, data: data}); + bytes32[] memory proof = _appendProofMetadata(new bytes32[](1)); + + vm.expectRevert(bytes("xx")); + mailboxFacet.proveL2MessageInclusion({ + _batchNumber: batchNumber + 1, + _index: 0, + _message: message, + _proof: proof + }); + } function test_success_proveL2MessageInclusion() public { uint256 firstLogIndex = _addHashedLogToMerkleTree({ 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 84aab5abd..564970846 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 @@ -216,7 +216,7 @@ describe("Shared Bridge tests", () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 10, 0, 0, l2ToL1message, dummyProof) ); - expect(revertReason).equal("local root is 0"); + expect(revertReason).equal("xx"); }); it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { 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 f9ed2626e..dc88aeff0 100644 --- a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts +++ b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts @@ -193,7 +193,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(10, 0, 0, l2ToL1message, dummyProof) ); - expect(revertReason).equal("local root is 0"); + expect(revertReason).equal("xx"); }); it("Should revert on finalizing a withdrawal with wrong proof", async () => {