From 56f5540266b76fba93629af3be3d215d9f4e4573 Mon Sep 17 00:00:00 2001 From: manuelwedler Date: Mon, 14 Aug 2023 13:43:35 +0200 Subject: [PATCH 1/3] contracts: Add ZkSyncL2Messenger contract --- ape-config.yaml | 4 ++ .../chains/zksync/AddressAliasHelper.sol | 43 +++++++++++++++++++ .../chains/zksync/L2ContractHelper.sol | 6 +++ .../chains/zksync/ZkSyncMessengers.sol | 27 ++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 contracts/contracts/chains/zksync/AddressAliasHelper.sol create mode 100644 contracts/contracts/chains/zksync/L2ContractHelper.sol create mode 100644 contracts/contracts/chains/zksync/ZkSyncMessengers.sol diff --git a/ape-config.yaml b/ape-config.yaml index 3049e0df6..99cd513d0 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -24,6 +24,9 @@ dependencies: - name: Polygon-ZkEVM local: ./contracts/contracts/chains/ contracts_folder: ./polygon_zkevm + - name: ZkSync + local: ./contracts/contracts/chains/ + contracts_folder: ./zksync default_ecosystem: ethereum @@ -35,6 +38,7 @@ solidity: - 'interfaces=Interfaces' - 'optimism=Optimism' - 'polygon_zkevm=Polygon-ZkEVM' + - 'zksync=ZkSync' ganache: server: diff --git a/contracts/contracts/chains/zksync/AddressAliasHelper.sol b/contracts/contracts/chains/zksync/AddressAliasHelper.sol new file mode 100644 index 000000000..6281f665f --- /dev/null +++ b/contracts/contracts/chains/zksync/AddressAliasHelper.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2019-2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pragma solidity ^0.8.19; + +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + unchecked { + l2Address = address(uint160(l1Address) + offset); + } + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + unchecked { + l1Address = address(uint160(l2Address) - offset); + } + } +} \ No newline at end of file diff --git a/contracts/contracts/chains/zksync/L2ContractHelper.sol b/contracts/contracts/chains/zksync/L2ContractHelper.sol new file mode 100644 index 000000000..eac9e2a29 --- /dev/null +++ b/contracts/contracts/chains/zksync/L2ContractHelper.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IL2Messenger { + function sendToL1(bytes memory _message) external returns (bytes32); +} diff --git a/contracts/contracts/chains/zksync/ZkSyncMessengers.sol b/contracts/contracts/chains/zksync/ZkSyncMessengers.sol new file mode 100644 index 000000000..5b0c77b57 --- /dev/null +++ b/contracts/contracts/chains/zksync/ZkSyncMessengers.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "../../../interfaces/IMessenger.sol"; +import "../../RestrictedCalls.sol"; +import "zksync/AddressAliasHelper.sol"; +import "zksync/L2ContractHelper.sol"; + +address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(0x8008); + +contract ZkSyncL2Messenger is IMessenger, RestrictedCalls { + function callAllowed( + address caller, + address courier + ) external view returns (bool) { + return courier == + AddressAliasHelper.applyL1ToL2Alias(caller); + } + + function sendMessage( + address, + bytes calldata message + ) external restricted(block.chainid) { + IL2Messenger messenger = IL2Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR); + messenger.sendToL1(message); + } +} From ae7aac4bd90b962005ad2aa4298f0c5cc358d18f Mon Sep 17 00:00:00 2001 From: manuelwedler Date: Thu, 17 Aug 2023 15:39:30 +0200 Subject: [PATCH 2/3] contracts: Add l2 message execution function to ZkSyncL1Messenger --- .../contracts/chains/zksync/IMailbox.sol | 17 +++++ .../chains/zksync/ZkSyncMessengers.sol | 65 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 contracts/contracts/chains/zksync/IMailbox.sol diff --git a/contracts/contracts/chains/zksync/IMailbox.sol b/contracts/contracts/chains/zksync/IMailbox.sol new file mode 100644 index 000000000..e6a75477b --- /dev/null +++ b/contracts/contracts/chains/zksync/IMailbox.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +struct L2Message { + uint16 txNumberInBlock; + address sender; + bytes data; +} + +interface IMailbox { + function proveL2MessageInclusion( + uint256 _blockNumber, + uint256 _index, + L2Message calldata _message, + bytes32[] calldata _proof + ) external view returns (bool); +} diff --git a/contracts/contracts/chains/zksync/ZkSyncMessengers.sol b/contracts/contracts/chains/zksync/ZkSyncMessengers.sol index 5b0c77b57..5630b1d7c 100644 --- a/contracts/contracts/chains/zksync/ZkSyncMessengers.sol +++ b/contracts/contracts/chains/zksync/ZkSyncMessengers.sol @@ -3,10 +3,75 @@ pragma solidity ^0.8.19; import "../../../interfaces/IMessenger.sol"; import "../../RestrictedCalls.sol"; +import "../../Resolver.sol"; import "zksync/AddressAliasHelper.sol"; +import "zksync/IMailbox.sol"; import "zksync/L2ContractHelper.sol"; address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(0x8008); +address constant MAILBOX_FACET_ADDR = 0xb2097DBe4410B538a45574B1FCD767E2303c7867; + +contract ZkSyncL1Messenger is IMessenger, RestrictedCalls { + + // NOTE: The zkSync contract implements only the functionality for proving that a message belongs to a block + // but does not guarantee that such a proof was used only once. That's why a contract that uses L2 -> L1 + // communication must take care of the double handling of the message. + /// @dev mapping L2 block number => message number => flag + /// @dev Used to indicated that zkSync L2 -> L1 message was already processed + mapping(uint256 => mapping(uint256 => bool)) public isL2ToL1MessageProcessed; + + Resolver public immutable resolver; + address public immutable l2MessengerAddress; + + constructor(address resolver_, address l2MessengerAddress_) { + resolver = Resolver(resolver_); + l2MessengerAddress = l2MessengerAddress_; + } + + function callAllowed( + address caller, + address courier + ) external view returns (bool) { + // TODO + return true; + } + + function sendMessage( + address, + bytes calldata message + ) external restricted(block.chainid) { + // TODO + } + + function executeMessageFromL2( + // zkSync block number in which the message was sent + uint256 l2BlockNumber, + // Message index, that can be received via API + uint256 index, + // The tx number in block + uint16 l2TxNumberInBlock, + // The message that was sent from l2 + bytes calldata message, + // Merkle proof for the message + bytes32[] calldata proof + ) external { + IMailbox mailbox = IMailbox(MAILBOX_FACET_ADDR); + L2Message memory l2Message = L2Message({sender: l2MessengerAddress, data: message, txNumberInBlock: l2TxNumberInBlock}); + + bool success = mailbox.proveL2MessageInclusion( + l2BlockNumber, + index, + l2Message, + proof + ); + require(success, "Failed to prove message inclusion"); + + isL2ToL1MessageProcessed[l2BlockNumber][index] = true; + + (bool sent, ) = address(resolver).call(message); + require(sent, "Relaying message failed"); + } +} contract ZkSyncL2Messenger is IMessenger, RestrictedCalls { function callAllowed( From 412e47443dd7709d608ef9baf6a83029b5db84cf Mon Sep 17 00:00:00 2001 From: manuelwedler Date: Wed, 30 Aug 2023 17:34:51 +0200 Subject: [PATCH 3/3] contracts: Add sendMessage function to ZkSyncL1Messenger (WIP) --- .../contracts/chains/zksync/IMailbox.sol | 16 ++++ .../chains/zksync/ZkSyncMessengers.sol | 78 +++++++++++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/contracts/contracts/chains/zksync/IMailbox.sol b/contracts/contracts/chains/zksync/IMailbox.sol index e6a75477b..22b34386c 100644 --- a/contracts/contracts/chains/zksync/IMailbox.sol +++ b/contracts/contracts/chains/zksync/IMailbox.sol @@ -14,4 +14,20 @@ interface IMailbox { L2Message calldata _message, bytes32[] calldata _proof ) external view returns (bool); + + function l2TransactionBaseCost( + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) external view returns (uint256); + + function requestL2Transaction( + address _contractL2, + uint256 _l2Value, + bytes calldata _calldata, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit, + bytes[] calldata _factoryDeps, + address _refundRecipient + ) external payable returns (bytes32 canonicalTxHash); } diff --git a/contracts/contracts/chains/zksync/ZkSyncMessengers.sol b/contracts/contracts/chains/zksync/ZkSyncMessengers.sol index 5630b1d7c..30911a641 100644 --- a/contracts/contracts/chains/zksync/ZkSyncMessengers.sol +++ b/contracts/contracts/chains/zksync/ZkSyncMessengers.sol @@ -9,9 +9,15 @@ import "zksync/IMailbox.sol"; import "zksync/L2ContractHelper.sol"; address constant L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR = address(0x8008); -address constant MAILBOX_FACET_ADDR = 0xb2097DBe4410B538a45574B1FCD767E2303c7867; +// TODO remove +// address constant MAILBOX_FACET_ADDR = 0xb2097DBe4410B538a45574B1FCD767E2303c7867; +// uint256 constant REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; contract ZkSyncL1Messenger is IMessenger, RestrictedCalls { + Resolver public immutable resolver; + address public immutable l2MessengerAddress; + IMailbox public immutable mailbox; + uint256 public immutable l2GasPerPubdataByteLimit; // NOTE: The zkSync contract implements only the functionality for proving that a message belongs to a block // but does not guarantee that such a proof was used only once. That's why a contract that uses L2 -> L1 @@ -20,27 +26,84 @@ contract ZkSyncL1Messenger is IMessenger, RestrictedCalls { /// @dev Used to indicated that zkSync L2 -> L1 message was already processed mapping(uint256 => mapping(uint256 => bool)) public isL2ToL1MessageProcessed; - Resolver public immutable resolver; - address public immutable l2MessengerAddress; + /// Maps addresses to ETH deposits to be used for paying the base cost. + mapping(address depositor => uint256 deposit) public deposits; - constructor(address resolver_, address l2MessengerAddress_) { + constructor( + address resolver_, + address l2MessengerAddress_, + address mailboxAddress_, + uint256 l2GasPerPubdataByteLimit_) + { resolver = Resolver(resolver_); l2MessengerAddress = l2MessengerAddress_; + mailbox = IMailbox(mailboxAddress_); } function callAllowed( address caller, address courier ) external view returns (bool) { - // TODO return true; + // TODO + // return + // courier == address(zkSyncMailbox) && + // caller == nativeMessenger.xDomainMessageSender(); + } + + function deposit() external payable { + deposits[msg.sender] += msg.value; + } + + function _withdraw(address target) private { + uint256 amount = deposits[target]; + require(amount > 0, "nothing to withdraw"); + + deposits[target] = 0; + + (bool sent, ) = target.call{value: amount}(""); + require(sent, "failed to send Ether"); + } + + function withdraw() public { + _withdraw(msg.sender); } function sendMessage( - address, + address target, bytes calldata message ) external restricted(block.chainid) { - // TODO + // 2. get current l1 gas price + uint256 l1GasPrice = tx.gasprice; + + // TODO estimate l2 gas + // only easily possible via zks_estimateGasL1ToL2 rpc method. + // Find solution to provide it externally or calculate an estimate here in the contract + // the address of this contract needs to be aliased when estimating l2 gas! + uint256 l2GasLimit = 0; + + uint256 baseCost = mailbox.l2TransactionBaseCost( + l1GasPrice, + l2GasLimit, + l2GasPerPubdataByteLimit + ) + + uint256 depositOrigin = deposits[tx.origin]; + require(depositOrigin >= baseCost, "insufficient deposit"); + + deposits[tx.origin] = depositOrigin - baseCost; + + mailbox.requestL2Transaction{value: baseCost}( + target, + 0, + message, + l2GasLimit, + l2GasPerPubdataByteLimit, + new bytes[](0), + tx.origin, + ); + + if (deposits[tx.origin] > 0) _withdraw(tx.origin); } function executeMessageFromL2( @@ -55,7 +118,6 @@ contract ZkSyncL1Messenger is IMessenger, RestrictedCalls { // Merkle proof for the message bytes32[] calldata proof ) external { - IMailbox mailbox = IMailbox(MAILBOX_FACET_ADDR); L2Message memory l2Message = L2Message({sender: l2MessengerAddress, data: message, txNumberInBlock: l2TxNumberInBlock}); bool success = mailbox.proveL2MessageInclusion(