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/IMailbox.sol b/contracts/contracts/chains/zksync/IMailbox.sol new file mode 100644 index 000000000..22b34386c --- /dev/null +++ b/contracts/contracts/chains/zksync/IMailbox.sol @@ -0,0 +1,33 @@ +// 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); + + 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/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..30911a641 --- /dev/null +++ b/contracts/contracts/chains/zksync/ZkSyncMessengers.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +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); +// 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 + // 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; + + /// 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_, + address mailboxAddress_, + uint256 l2GasPerPubdataByteLimit_) + { + resolver = Resolver(resolver_); + l2MessengerAddress = l2MessengerAddress_; + mailbox = IMailbox(mailboxAddress_); + } + + function callAllowed( + address caller, + address courier + ) external view returns (bool) { + 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 target, + bytes calldata message + ) external restricted(block.chainid) { + // 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( + // 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 { + 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( + 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); + } +}