diff --git a/docs/.vuepress/sidebar/en.ts b/docs/.vuepress/sidebar/en.ts index 10cdcd92d9..83845344ef 100644 --- a/docs/.vuepress/sidebar/en.ts +++ b/docs/.vuepress/sidebar/en.ts @@ -12,6 +12,13 @@ export const enSidebar = sidebar({ link: "/dev/fundamentals", children: ["/dev/fundamentals/rollups.md", "/dev/fundamentals/zkSync.md", "/dev/fundamentals/interacting.md", "/dev/fundamentals/hyperscaling.md"], }, + { + text: "How to", + link: "/dev/how-to", + children: [ "/dev/how-to/estimate-gas.md", + "/dev/how-to/send-transaction-l1-l2", + "/dev/how-to/send-message-l2-l1", ], + }, { text: "Understanding zkSync Era", link: "/dev/developer-guides", @@ -23,8 +30,6 @@ export const enSidebar = sidebar({ "/dev/developer-guides/transactions/fee-model.md", "/dev/developer-guides/bridging/bridging-asset.md", "/dev/developer-guides/bridging/l1-l2-interop.md", - "/dev/developer-guides/bridging/l1-l2.md", - "/dev/developer-guides/bridging/l2-l1.md", "/dev/developer-guides/videos.md", ], }, diff --git a/docs/api/js/accounts-l1-l2.md b/docs/api/js/accounts-l1-l2.md index b48850dbc9..c1dc83a6c1 100644 --- a/docs/api/js/accounts-l1-l2.md +++ b/docs/api/js/accounts-l1-l2.md @@ -2,7 +2,7 @@ This section explores the methods which allow the [account](./accounts.md) classes to send transactions from L1 to L2. -If you want to get some background on how L1 -> L2 interaction works on zkSync, go through the [introduction](../../dev/developer-guides/bridging/l1-l2-interop.md) and the [guide](../../dev/developer-guides/bridging/l1-l2.md). +If you want to get some background on how L1 -> L2 interaction works on zkSync, go through the [L1 to L2 interoperability doc](../../dev/developer-guides/bridging/l1-l2-interop.md). ## Supported classes diff --git a/docs/dev/README.md b/docs/dev/README.md index d669157cba..fe8e44559a 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -24,8 +24,6 @@ If this is your first time using zkSync, we recommend that you kick off at the b - [Bridging of funds](./developer-guides/bridging/bridging-asset.md) - A brief intro on token bridging. - [Add tokens to bridge](./developer-guides/bridging/bridging-asset.md#add-tokens-to-the-bridge) - Add your token to the bridge. - [L1 / L2 Interoperability](./developer-guides/bridging/l1-l2-interop.md) - A quick brief on data communication between L1 and L2. -- [L1 / L2 communication](./developer-guides/bridging/l1-l2.md) - Learn how to send data from Ethereum to zkSync. -- [L2 / L1 communication](./developer-guides/bridging/l2-l1.md) - Learn how to send data from zkSync to Ethereum. - [Video resources](./developer-guides/videos.md) - Watch developer related videos and zkSync Era. ### Building on zkSync Era diff --git a/docs/dev/building-on-zksync/contracts/contract-deployment.md b/docs/dev/building-on-zksync/contracts/contract-deployment.md index 1473cb2f88..380a46e9fb 100644 --- a/docs/dev/building-on-zksync/contracts/contract-deployment.md +++ b/docs/dev/building-on-zksync/contracts/contract-deployment.md @@ -83,4 +83,4 @@ Deploying contracts on zkSync Era is also possible via L1-L2 communication. The [interface](https://github.com/matter-labs/v2-testnet-contracts/blob/main/l1/contracts/zksync/interfaces/IMailbox.sol#L78) for submitting L1->L2 transactions accepts the list of all the factory dependencies required for this particular transaction. The logic for working with them is the same as for the default L2 deployments. The only difference is that since the user has already published the full preimage for the bytecodes on L1, there is no need to publish these bytecodes again on L1. -To learn more about L1-L2 communication on zkSync Era, visit [this section of the docs](../../developer-guides/bridging/l1-l2.md). +To learn more about L1-L2 communication on zkSync Era, visit [this section of the docs](../../developer-guides/bridging/l1-l2-interop.md). diff --git a/docs/dev/developer-guides/README.md b/docs/dev/developer-guides/README.md index d9010cb018..83db859b98 100644 --- a/docs/dev/developer-guides/README.md +++ b/docs/dev/developer-guides/README.md @@ -11,7 +11,4 @@ Take a deeper dive into the zkSync ecosystem. - [Fee mechanism](./transactions/fee-model.md) - [Bridging assets](./bridging/bridging-asset.md) - [L1 / L2 Interoperability](./bridging/l1-l2-interop.md) -- [L1 / L2 communication](./bridging/l1-l2.md) -- [L2 / L1 communication](./bridging/l2-l1.md) - [Video resources](./videos.md) - diff --git a/docs/dev/developer-guides/bridging/l1-l2-interop.md b/docs/dev/developer-guides/bridging/l1-l2-interop.md index b71abedaa7..b80b62cec6 100644 --- a/docs/dev/developer-guides/bridging/l1-l2-interop.md +++ b/docs/dev/developer-guides/bridging/l1-l2-interop.md @@ -1,72 +1,42 @@ -# L1 / L2 Interoperability +# L1 / L2 interoperability -While most of the execution will happen on L2, some use cases require interoperability with the L1 chain. The main use cases are building complex bridges, maintaining governance smart contracts on one chain that govern contracts on other chains, etc. +## Common use cases -In addition, the L2 censorship resistance is derived from the underlying chain, so the ability to send messages from Ethereum to zkSync is an important part of the censorship-resistance mechanism called the [priority queue](#priority-queue). +Many use cases require multi-layer interoperability, such as: -Sending transactions from Ethereum to zkSync is done via the zkSync smart contract. It allows the sender to request transactions directly from L1. Thereby allowing the permissionless passing of any data from Ethereum into zkSync. -[Read more](../bridging/l1-l2.md) about messaging from L1 to L2. +- The network's censorship resistance. +- Custom bridges. +- Multi-layer governing smart contracts. +- Multi-layer transfers. -## Priority queue - -The goal of the priority queue is to provide a censorship-resistant way to interact with zkSync in case the operator becomes malicious or unavailable. -The way the priority queue works in zkSync Era is very close to how it worked in the previous version of zkSync. -For the full picture, we first present how the priority queue works on zkSync Lite. -This gives the rationale for the new design of the priority queue for zkSync Era. - -### How it works in zkSync Lite - -In the previous version of zkSync, we only had two operations that could be sent to zkSync from L1: - -- `Deposit` to bridge funds from Ethereum to zkSync. -- `FullExit` to bridges the funds back from Ethereum (this is essentially the same as `Withdraw` in zkSync Era). - -If users wanted to deposit funds to or withdraw funds from zkSync, they would have to send a transaction request to the smart contract which will then get appended to the queue of priority transactions. The queue has the following rules: - -- All transactions are processed sequentially. -- Each priority operation must be processed by the operator within `X` days since it was submitted to the contract. - -The first rule is strictly enforced by the smart contract. The second rule may be violated if the operator becomes malicious or unavailable. In case that happens, the system enters 'exodus mode', where no new blocks can be processed and users can withdraw their funds without cooperation from the operator. +## L1 to L2 communication -### What changes are needed? +L1 to L2 communication is governed by the [`IZkSync.sol`](https://github.com/matter-labs/v2-testnet-contracts/blob/b8449bf9c819098cc8bfee0549ff5094456be51d/l1/contracts/zksync/interfaces/IZkSync.sol#L4) inherited interfaces. -The process described above works well for a system with a small set of relatively light supported operations. zkSync Era supports general smart contract computation, and thus some principles had to be changed in order to preserve the stability of the network. +:::tip +- If you prefer to learn-by-doing, the [cross chain governance tutorial](../../tutorials/cross-chain-tutorial.md) is a practical example of layer interoperability. +::: -Firstly, all transactions need to be supported by the priority queue. Users may have their funds locked on an L2 smart contract, and not on their own L2 account. Therefore before moving their funds to L1, they need to send an `Execute` transaction to the zkSync network to release the funds from that smart contract first. +### Gas estimation -Secondly, the priority queue needs to stay censorship-resistant. But imagine what will happen if users start sending a lot of transactions that take the entirety of the block gas limit? There needs to be a way to prevent spam attacks on the system. -That's why submitting transactions to the priority queue is no longer free. -Users need to pay a certain fee to the operator for processing their transactions. It is really hard to calculate the accurate fee in a permissionless way. -Thus, the fee for a transaction is equal to `txBaseCost * gasPrice`. The `gasPrice` is the gas price of the users' transaction, while `txBaseCost` is the base cost for the transaction, which depends on its parameters (e.g. `gas_limit` for `Execute` transaction). +The SDK processes gas estimation for transactions implicitly. However, it is also possible to implement the gas estimation processes explicitly. -Thirdly, the operator can not commit to processing every transaction within `X` days. Again, this is needed to prevent spam attacks on the priority queue. We changed this rule to the following one: +:::tip L1 to L2 gas estimation for transactions +- Basic costs are measured in the amount of gas, and so the final cost depends on the gas price that the transaction assigns. +- The transaction process requires the current L1 gas price, transaction base cost, and transaction gas limit which defines the maximum amount of gas a transaction can consume. +::: -- The operator must do at least `X` amount of work (see below) on the priority queue or the priority queue should be empty. +- Find out [how to estimate gas](../../how-to/estimate-gas.md) for different scenarios. +- Find out [how to send a transaction from L1 to L2](../../how-to/send-transaction-l1-l2.md) with zkSync Era. -In other words, we require the operator to do its best instead of requiring a strict deadline. The measure of "the work" is still to be developed. Most likely it will be the number of `gas` the priority operations used. +## L2 to L1 -In the future, we will also add the ability to "prioritize" L1->L2 transactions, allowing users to speed up the inclusion of their transactions in exchange for paying a higher fee to the operator. +L2 to L1 communication is based on transferring the data as a message, and not on L1 transaction execution. -## Priority mode +- Find out [how to send a message from L2 to L1](../../how-to/send-message-l2-l1.md) with zkSync Era. -If the operator fails to process the needed L1 transactions, the system enters in 'Priority mode'. In this mode, everyone can become an operator by staking tokens. The exact details of the priority mode are still under development and will be described in more detail closer to the mainnet launch. - -To reduce risks, the alpha mainnet will start with a mechanism to instantly stop and upgrade the network, which contradicts the purpose of the priority mode. Priority mode will be gradually introduced in the following releases. - -## L2 -> L1 messaging - -The [L2 -> L1 communication](./l2-l1.md), in contrast to L1 -> L2 communication, is based only on transferring the information, and not on the transaction execution on L1. It is a built-in feature, which is made up of two parts: sending a message from L2 and reading it on L1. The first is implemented as a call to an L2 system smart contract. And the second is implemented on the zkSync L1 smart contract as a getter function. - -### Sending messages - -Each message sent from L2 to L1 contains the sender's address and the message itself. The length of the message can be arbitrarily large, but the longer the message, the more expensive it will be to send. The operator must include all messages for the corresponding merkle root (see next paragraph). Hence, all the messages are publicly available, and one does not have to rely on the operator to reveal them. - -### Reading messages - -Every message sent can be read on-chain. Moreover, it is possible to prove that a message has been sent in a specific L2 block. To make such proof as cheap as possible for both the user and the operator, we store all messages, for each L2 block, in a merkle tree. Accordingly, any L1 smart contract can consume the message sent by providing proof of inclusion in some L2 block. Proof can be generated based only on the data that the operator sent to the zkSync L1 smart contract. The proof can also be obtained via [the API](../../../api/api.md#zks-getl2tol1msgproof). +## Priority queue -### Summary on L2->L1 messaging +1. All transactions types are supported by the priority queue. -- L2 -> L1 communication requires one transaction on L2 and one on L1. -- Messages can be of arbitrary length. -- All the data needed for proving message inclusion in an L2 block can always be restored from Ethereum. However, the easiest way is to request proof from the operator via API. +2. The priority queue must be fully permissionless to prevent malicious activity. For example, malicious users might send multiple transactions which push up the block gas limit to unworkable levels. To mitigate against this, submitting transactions to the priority queue is no longer free and users must pay a fee to the operator. To obtain the cost for sending an L2 to L1 message, please refer to [step 5 of how to send an L1 to L2 transaction](../../how-to/send-transaction-l1-l2.md#step-by-step). \ No newline at end of file diff --git a/docs/dev/developer-guides/bridging/l1-l2.md b/docs/dev/developer-guides/bridging/l1-l2.md deleted file mode 100644 index a80bfab079..0000000000 --- a/docs/dev/developer-guides/bridging/l1-l2.md +++ /dev/null @@ -1,230 +0,0 @@ -# L1 -> L2 communication - -This section describes the interface for interaction with zkSync from L1. It assumes that you are already familiar with the basic concepts of working with the priority queue. If you are new to this topic, you can read the conceptual introduction [here](./l1-l2-interop.md#priority-queue). If you want to dive straight into the code, then you can read the cross-chain governance [tutorial](../../tutorials/cross-chain-tutorial.md). - -::: warning -- Please note that with the system update released in Feb 2023, the `ergs` concept is only used by the VM while the API layer operates with `gas`. -- For more information, read the [changelog](../../troubleshooting/changelog.md#hardhat-plugins-update-feb-24th-2023). -::: - -## Structure - -For the most common usecase, there is "baseFee" for a transaction, which basically means the minimum amount the user has to pay to the operator for him to include this transaction. It is derived based on the `l2gasLimit` for a transaction and the gas price on L1. -In addition, whatever fee the user pays above is called **layer2 tip** and will be used to sort the transactions by the provided L2 fee. - -At the moment, all the L1-> L2 transactions are served at the first-in-first-out basis, but in the future we will introduce "priority heap", which will allow for sorting the -transactions. -Basic costs are defined in gas and not in ETH, so the actual amount of ether that the submission of the transaction will cost depends on -the transaction gas price. Generally the flow for calling any of these methods should be the following: - -1. Fetch the gas price that you will use to send the transaction. -2. Get the base cost for the transaction. -3. Send the transaction including the needed `value`. - -## Using contract interface in your project - -To interact with the zkSync mailbox contract using Solidity, you need to use the zkSync contract interface. There are two main ways to get it: - -- By importing it from the `@matterlabs/zksync-contracts` npm package (preferred). -- By downloading the contracts from the [repo](https://github.com/matter-labs/v2-testnet-contracts). - -The `@matterlabs/zksync-contracts` package can be installed by running the following command: - -``` -yarn add -D @matterlabs/zksync-contracts -``` - -In the examples below we assume that the interface is accessed via the `@matterlabs/zksync-contracts` npm package. - -### Getting the base cost - -The following view function returns the amount of ETH that is needed to be supplied by the user to cover the base cost of the transaction. - -```solidity -function l2TransactionBaseCost( - uint256 _gasPrice, - uint256 _gasLimit, - uint256 _l2GasPerPubdataByteLimit -) external view returns (uint256); -``` - -- `_gasPrice` is a parameter that contains the transaction gas price. -- `_gasLimit` is a parameter that contains the gas limit of the transaction call. You can learn more about gas and the zkSync fee system [here](../transactions/fee-model.md). -- `_l2GasPerPubdataByteLimit` is a parameter that contains the L2 gas price for each published L1 calldata byte. - -### Estimating the gas fee - -The RPC function `zks_estimateGasL1ToL2` returns an estimate of how much gas is required to complete the L1->L2 transaction. The parameters are the same as for `eth_estimateGas`. - -`eth_estimateGas` is applicable for both zkSync L2 and Ethereum while `zks_estimateGasL1ToL2` is only available on zkSync. - -::: warning - If the sender is a smart contract, then during the L1->L2 the `msg.sender` address is aliased. Note that this is not done automatically during fee estimation, so it is your responsibility to use the zkSync [`aliasing utility`](#aliasing) to get the correct `from` address for the fee estimation. -::: - -The function is wrapped in the [Provider](../../../api/js/providers.md#provider) class. - -```ts -async estimateGasL1(transaction: utils.Deferrable): Promise { - await this.getNetwork(); - const params = await utils.resolveProperties({ - transaction: this._getTransactionRequest(transaction) - }); - if (transaction.customData != null) { - // @ts-ignore - params.transaction.customData = transaction.customData; - } - const result = await this.send('zks_estimateGasL1ToL2', [ - Provider.hexlifyTransaction(params.transaction, { from: true }) - ]); - try { - return BigNumber.from(result); - } catch (error) { - throw new Error(`bad result from backend (zks_estimateGasL1ToL2): ${result}`); - } -} -``` - -You can use the `Provider.estimateGasL1` to estimate the L2 gas needed for the L1->L2 transaction. - -The function takes the following parameters: - -- `from`: sender address. -- `to`: recipient address. -- `gas`: (optional) gas used for the transaction. -- `gasPrice`: (optional) gasPrice. -- `value`: (optional) value sent by the transaction. -- `data`: (optional) hash of the method signature and encoded parameters. - -### Interface - -The following function returns the canonical hash or the requested transaction, that can be used to track the execution of the transaction in L2. - -Use the `Provider` class, as described in the [section above](#estimating-the-gas-fee), to call the method. - -```solidity -function requestL2Transaction( - address _contractL2, - uint256 _l2Value, - bytes calldata _calldata, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit, - bytes[] calldata _factoryDeps, - address _refundRecipient -) external payable returns (bytes32 txHash); -``` - -- `_contractL2` is a parameter that defines the address of the contract to be called. -- `_l2Value` is a parameter that defines the amount of ETH you want to pass with the call to L2. This number will be used as `msg.value` for the transaction. -- `_calldata` is a parameter that contains the calldata of the transaction call. It can be encoded the same way as on Ethereum. -- `_l2GasLimit` is a parameter that contains the gas limit of the transaction call. You can learn more about gas and the zkSync fee system [here](../transactions/fee-model.md). -- `_l2GasPerPubdataByteLimit` is a parameter that contains the L2 gas price per each published to L1 calldata byte. -- `_factoryDeps` is a list of bytecodes. It should contain the bytecode of the contract being deployed. If the contract being deployed is a factory contract, i.e. it can deploy other contracts, the array should also contain the bytecodes of the contracts that can be deployed by it. -- `_refundRecipient` is an address that receives the rest of the fee after the transaction execution. If `refundRecipient == 0`, L2 `msg.sender` is used. - -::: warning - If the `_refundRecipient` is a smart contract, then during the L1->L2 the `msg.sender` address is [aliased](#aliasing). -::: - -With the method call, some amount of ETH should be supplied to cover the base cost of the transaction (including the `_l2Value`) + layer 2 operator tip. - -::: tip -A **successful** L1 -> L2 message produces an `L2Log` with `key = l2TxHash`, and `value = bytes32(1)` whereas a **failed** L1 -> L2 message produces an `L2Log` with `key = l2TxHash`, and `value = bytes32(0)`. -::: - -### Aliasing - -The L1->L2 alias function applies an alias to a given address. The L2 alias is the same alias that is applied on the L1 smart contracts during L1->L2 communication. - -```typescript -export function applyL1ToL2Alias(address: string): string { - return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); -} -``` - -### Examples - -#### Solidity - -```solidity -//SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.0; - -// Importing zkSync contract interface -import "@matterlabs/zksync-contracts/l1/contracts/zksync/interfaces/IZkSync.sol"; - -contract Example { - function callZkSync( - // The address of the zkSync smart contract. - // It is not recommended to hardcode it during the alpha testnet as regenesis may happen. - address _zkSyncAddress - ) external payable returns(bytes32 txHash) { - IZkSync zksync = IZkSync(_zkSyncAddress); - address someL2Contract = 0xDbA0833e8c4b37cecC177a665E9207962e337299; - // calling L2 smart contract from L1 Example contract - txHash = zksync.requestL2Transaction{value: msg.value}( - // The address of the L2 contract to call - someL2Contract, - // We pass no ETH with the call - 0, - // Encoding the calldata for the execute - abi.encodeWithSignature("someMethod()"), - // Gas limit - 10000, - // gas price per pubdata byte - 800, - // factory dependencies - new bytes[](0), - // refund address - address(0) - ); - } -} -``` - -#### `zksync-web3` - -```ts -import { Wallet, Provider } from "zksync-web3"; -import { ethers, BigNumber } from "ethers"; - -const TEST_PRIVATE_KEY = ""; - -const zkSyncProvider = new Provider("https://testnet.era.zksync.dev"); -const ethereumProvider = ethers.getDefaultProvider("goerli"); -const wallet = new Wallet(TEST_PRIVATE_KEY, zkSyncProvider, ethereumProvider); - -const gasPrice = await wallet.providerL1!.getGasPrice(); - -// The calldata can be encoded the same way as for Ethereum -const calldata = "0x..."; -const gasLimit = BigNumber.from(1000); -const gasPerPubdataByte = BigNumber.from(800); - -const txCostPrice = await wallet.getBaseCost({ - gasPrice, - gasLimit, - gasPerPubdataByte -}); - -console.log(`Executing the transaction will cost ${ethers.utils.formatEther(txCostPrice)} ETH`); - -// initiating L2 transfer via L1 execute from zksync wallet -const someL2Contract = "0x19a5bfcbe15f98aa073b9f81b58466521479df8d"; -const executeTx = await wallet.requestExecute({ - calldata, - l2GasLimit: gasLimit, - gasPerPubdataByte, - contractAddress: someL2Contract, - overrides: { - gasPrice, - value: txCostPrice, - }, -}); - -await executeTx.wait(); -``` - -## Why does `wait()` get stuck for L1->L2 transactions? - -If the `wait()` function takes a lot longer than expected, most likely the transaction has failed. \ No newline at end of file diff --git a/docs/dev/developer-guides/bridging/l2-l1.md b/docs/dev/developer-guides/bridging/l2-l1.md deleted file mode 100644 index a2a494b3b0..0000000000 --- a/docs/dev/developer-guides/bridging/l2-l1.md +++ /dev/null @@ -1,288 +0,0 @@ -# L2 -> L1 communication - -This section describes the interface to interact with Ethereum from L2. It assumes that you are already familiar with the basic concepts of working with L2 -> L1 communication. If you are new to this topic, you can read the conceptual introduction [here](./l1-l2-interop.md). - - -::: warning -- Please note that with the system update released in Feb 2023, the `ergs` concept is only used by the VM while the API layer operates with `gas`. -- For more information, read the [changelog](../../troubleshooting/changelog.md#hardhat-plugins-update-feb-24th-2023). -::: - -## Structure - -Unlike L1 -> L2 communication, it is impossible to directly initialize transactions from L2 to L1. However, you can send an arbitrary-length message from zkSync to Ethereum, and then handle the received message on an L1 smart contract. To send a message from the L2 side, you should call the `sendToL1` method from the [messenger system contract](../system-contracts.md#L1Messenger). It accepts only the bytes of the message that is sent to the zkSync smart contract on Ethereum. - -From the L1 side, the zkSync smart contract provides the method `proveL2MessageInclusion` to prove that the message was sent to L1 and included in a zkSync block. - -## Sending a message from L2 to L1 - -Sending messages from the L2 side requires users to call the `sendToL1` method from the [Messenger system contract](../system-contracts.md#L1Messenger). This method accepts only the bytes of the message that is being sent to the zkSync smart contract on L1. - -``` -function sendToL1(bytes memory _message) external returns (bytes32 messageHash); -``` - -- `_message` is a parameter that contains the raw bytes of the message - -::: tip - -The message sender will be determined from context. - -::: - -This function sends a message from L2 and returns the keccak256 hash of the message bytes. The message hash can be used later to get proof that the message was sent on L1. Its use is optional and is for convenience purposes only. - -More information about Messenger can be found in the [system contracts section](../system-contracts.md#L1Messenger). - -### Examples - -#### Sending a message from L2 to L1 using `zksync-web3` - -```ts -import { Wallet, Provider, Contract, utils } from "zksync-web3"; -import { ethers } from "ethers"; - -const TEST_PRIVATE_KEY = ""; - -async function main() { - const zkSyncProvider = new Provider("https://testnet.era.zksync.dev"); - - const wallet = new Wallet(TEST_PRIVATE_KEY, zkSyncProvider); - - const messengerContract = new ethers.Contract(utils.L1_MESSENGER_ADDRESS, utils.L1_MESSENGER, wallet); - - console.log(`Messenger contract address is ${messengerContract.address}`); - - const someString = ethers.utils.toUtf8Bytes("Some L2->L1 message"); - console.log(`Sending message from L2 to L1`); - const tx = await messengerContract.sendToL1(someString); - - console.log("L2 trx hash is ", tx.hash); - const receipt = await tx.waitFinalize(); - - console.log(`Transaction included in block ${receipt.blockNumber}`); - - // Get proof that the message was sent to L1 - const msgProof = await zkSyncProvider.getMessageProof(receipt.blockNumber, wallet.address, ethers.utils.keccak256(someString)); - - console.log("Proof that message was sent to L1 :>> ", msgProof); -} - -try { - main(); -} catch (error) { - console.error(error); -} -``` - -#### Smart contract in L2 that sends a message to L1 - -The following contract sends its address to L1 via the Messenger system contract: - -```solidity -//SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.0; - -// Importing interfaces and addresses of the system contracts -import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; - -contract Example { - function sendMessageToL1() external returns(bytes32 messageHash) { - // Construct the message directly on the contract - bytes memory message = abi.encode(address(this)); - - messageHash = L1_MESSENGER_CONTRACT.sendToL1(message); - } -} -``` - -## Proving the inclusion of the message into the L2 block - -From the L1 side, the zkSync smart contract provides an interface to prove that the message was sent to L1 and included in a zkSync block. - -The `proveL2MessageInclusion` function from the [Mailbox L1 contract](https://github.com/matter-labs/v2-testnet-contracts/blob/main/l1/contracts/zksync/facets/Mailbox.sol), returns a boolean value that indicates that a message with such parameters, was sent to L1. - -```solidity - - struct L2Message { - address sender; - bytes data; - uint256 txNumberInblock; - } - - function proveL2MessageInclusion( - uint256 _blockNumber, - uint256 _index, - L2Message calldata _message, - bytes32[] calldata _proof - ) external view returns (bool); -``` - -Here is a detailed description of the required parameters: - -- `_blockNumber` is the L1 batch number in which the L2 block was included. It can be retrieved using the `getBlock` method. -- `_index` is the index of the L2 log in the block. It's returned as `id` by the [`zks_getL2ToL1LogProof`](../../../api/api.html#zks-getl2tol1logproof) method of the `zksync-web3` API. -- `_message` is a parameter that contains the full information of the message sent. It should be an object containing: - - `sender`: the address that sent the message from L2. - - `data`: the message sent in bytes. - - `txNumberInBlock`: the index of the transaction in the L2 block, which is returned as `transactionIndex` using `getTransaction` -- `_proof` is a parameter that contains the Merkle proof of the message inclusion. It can be retrieved either from observing Ethereum or received from the [`zks_getL2ToL1LogProof`](../../../api/api.html#zks-getl2tol1logproof) method of the `zksync-web3` API. - -::: tip Important -- The L2 block of your transaction must be verified (and hence the transaction finalized) before proving the inclusion in L1. -::: - -### Example - -#### L1 message processing contract - -The following contract receives the information of the transaction sent to the L2 messenger contract and proves that it was included in an L2 block. - -```solidity -//SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.0; - -// Importing zkSync contract interface -import "@matterlabs/zksync-contracts/l1/contracts/zksync/interfaces/IZkSync.sol"; - -contract Example { - // 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)) isL2ToL1MessageProcessed; - - function consumeMessageFromL2( - // The address of the zkSync smart contract. - // It is not recommended to hardcode it during the alpha testnet as regenesis may happen. - address _zkSyncAddress, - // 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 { - // check that the message has not been processed yet - require(!isL2ToL1MessageProcessed[_l2BlockNumber][_index]); - - IZkSync zksync = IZkSync(_zkSyncAddress); - address someSender = 0x19A5bFCBE15f98Aa073B9F81b58466521479DF8D; - L2Message memory message = L2Message({sender: someSender, data: _message, txNumberInBlock:_l2TxNumberInBlock}); - - bool success = zksync.proveL2MessageInclusion( - _l2BlockNumber, - _index, - message, - _proof - ); - require(success, "Failed to prove message inclusion"); - - // Mark message as processed - isL2ToL1MessageProcessed[_l2BlockNumber][_index] = true; - } -} - -``` - -#### End to end - -The following script sends a message from L2 to L1, retrieves the message proof, and validates that the message received in L1 came from an L2 block. - -```typescript -import * as ethers from "ethers"; -import { Provider, utils, Wallet } from "zksync-web3"; -const TEST_PRIVATE_KEY = ""; - -const MESSAGE = "Some L2->L1 message"; - -const l2Provider = new Provider("https://testnet.era.zksync.dev"); -const l1Provider = ethers.getDefaultProvider("goerli"); - -const wallet = new Wallet(TEST_PRIVATE_KEY, l2Provider, l1Provider); - -async function sendMessageToL1(text: string) { - console.log(`Sending message to L1 with text ${text}`); - const textBytes = ethers.utils.toUtf8Bytes(MESSAGE); - - const messengerContract = new ethers.Contract(utils.L1_MESSENGER_ADDRESS, utils.L1_MESSENGER, wallet); - const tx = await messengerContract.sendToL1(textBytes); - await tx.wait(); - console.log("L2 trx hash is ", tx.hash); - return tx; -} - -async function getL2MessageProof(blockNumber: ethers.BigNumberish) { - console.log(`Getting L2 message proof for block ${blockNumber}`); - return await l2Provider.getMessageProof(blockNumber, wallet.address, ethers.utils.keccak256(ethers.utils.toUtf8Bytes(MESSAGE))); -} - -async function proveL2MessageInclusion(l1BatchNumber: ethers.BigNumberish, proof: any, trxIndex: number) { - const zkAddress = await l2Provider.getMainContractAddress(); - - const mailboxL1Contract = new ethers.Contract(zkAddress, utils.ZKSYNC_MAIN_ABI, l1Provider); - // all the information of the message sent from L2 - const messageInfo = { - txNumberInBlock: trxIndex, - sender: wallet.address, - data: ethers.utils.toUtf8Bytes(MESSAGE), - }; - - console.log(`Retrieving proof for batch ${l1BatchNumber}, transaction index ${trxIndex} and proof id ${proof.id}`); - - const res = await mailboxL1Contract.proveL2MessageInclusion(l1BatchNumber, proof.id, messageInfo, proof.proof); - - return res; -} - -/** - * Full end-to-end of an L2-L1 messaging with proof validation. - * Recommended to run in 3 steps: - * 1. Send message. - * 2. Wait for transaction to finalize and block verified - * 3. Wait for block to be verified and validate proof - */ -async function main() { - // Step 1: send message - const l2Trx = await sendMessageToL1(MESSAGE); - - console.log("Waiting for transaction to finalize..."); - - // Step 2: waiting to finalize can take a few minutes. - const l2Receipt = await l2Trx.waitFinalize(); - - // Step 3: get and validate proof (block must be verified) - const proof = await getL2MessageProof(l2Receipt.blockNumber); - - console.log(`Proof is: `, proof); - - const { l1BatchNumber, l1BatchTxIndex } = await l2Provider.getTransactionReceipt(l2Receipt.transactionHash); - - console.log("L1 Index for Tx in block :>> ", l1BatchTxIndex); - - console.log("L1 Batch for block :>> ", l1BatchNumber); - - // IMPORTANT: This method requires that the block is verified - // and sent to L1! - const result = await proveL2MessageInclusion( - l1BatchNumber, - proof, - // @ts-ignore - l1BatchTxIndex - ); - - console.log("Result is :>> ", result); - process.exit(); -} - -try { - main(); -} catch (error) { - console.error(error); -} -``` diff --git a/docs/dev/developer-guides/transactions/fee-model.md b/docs/dev/developer-guides/transactions/fee-model.md index b81fdbfcdd..ac5d6d77c1 100644 --- a/docs/dev/developer-guides/transactions/fee-model.md +++ b/docs/dev/developer-guides/transactions/fee-model.md @@ -38,6 +38,8 @@ On zkSync Era this varies because we support custom and paymaster accounts. Thes The transaction fee estimate depends on the entire transaction flow, including validation and execution. The `eth_estimateGas` function uses binary search to find the smallest gas value under which the transaction succeeds. +For more information, find out [how to estimate gas for various transactions types](../../how-to/estimate-gas.md). + For any Rust developers interested in the zkSync Era implementation for gas estimation, see the [Rust code in our repo](https://github.com/matter-labs/zksync-era/blob/48fe6e27110c1fe1a438c5375fb256890e8017b1/sdk/zksync-rs/src/operations/execute_contract.rs#L129). ### Transaction length diff --git a/docs/dev/fundamentals/zkSync.md b/docs/dev/fundamentals/zkSync.md index 9df00ee5ab..0af7e3a2ee 100644 --- a/docs/dev/fundamentals/zkSync.md +++ b/docs/dev/fundamentals/zkSync.md @@ -16,6 +16,10 @@ zkSync Era is made to look and feel like Ethereum, but with lower fees. Just lik You don't need to register a separate private key before usage; zkSync supports existing Ethereum wallets out of the box. At this time, zkSync is solely run and operated by the zkSync team's servers and is therefore centralized. However, this will be transitioned to a decentralized system shortly. +:::tip Gas fees +- Layer 2 gas fees depend on the current Ethereum gas fees for publishing and verification. +::: + ## zkSync overview