Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -35,6 +38,7 @@ solidity:
- 'interfaces=Interfaces'
- 'optimism=Optimism'
- 'polygon_zkevm=Polygon-ZkEVM'
- 'zksync=ZkSync'

ganache:
server:
Expand Down
43 changes: 43 additions & 0 deletions contracts/contracts/chains/zksync/AddressAliasHelper.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
33 changes: 33 additions & 0 deletions contracts/contracts/chains/zksync/IMailbox.sol
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 6 additions & 0 deletions contracts/contracts/chains/zksync/L2ContractHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IL2Messenger {
function sendToL1(bytes memory _message) external returns (bytes32);
}
154 changes: 154 additions & 0 deletions contracts/contracts/chains/zksync/ZkSyncMessengers.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}