diff --git a/content/tutorials/how-to-send-l2-l1-message/10.index.md b/content/tutorials/how-to-send-l2-l1-message/10.index.md index d82f386..07f0dc3 100644 --- a/content/tutorials/how-to-send-l2-l1-message/10.index.md +++ b/content/tutorials/how-to-send-l2-l1-message/10.index.md @@ -4,25 +4,26 @@ description: Learn how to send an arbitrary message from a smart contract on zkS --- It is impossible to send transactions directly from L2 to L1. + Instead, you can send arbitrary-length messages from zkSync Era to Ethereum, and then handle the received message on Ethereum with an L1 smart contract. -::callout{icon="i-heroicons-exclamation-circle"} -A message is like an event on Ethereum. The difference is that a message publishes data on L1. +#### What is a message? +- A message is like an event on Ethereum. +- The difference is that a message publishes data on L1. - [Solidity representation](https://github.com/matter-labs/era-contracts/blob/6250292a98179cd442516f130540d6f862c06a16/l1-contracts/contracts/zksync/Storage.sol#L60): - `solidity -struct L2Message { - address sender; - bytes data; - uint256 txNumberInblock; -} -` -:: + `solidity + struct L2Message { + address sender; + bytes data; + uint256 txNumberInblock; + } + ` ::callout{icon="i-heroicons-light-bulb"} Verification and confirmation is possible using Ethereum data. -However, zkSync Era has an efficient [request proof function](#prove-the-result) which does the same. +However, zkSync Era has an efficient [request proof function](#retrieve-the-message-proof) which does the same. :: ## Common use cases @@ -33,7 +34,7 @@ there are some common use cases that need L2 to L1 transaction functionality, su - Bridging funds from L2 to L1. - Layer 2 governance. -## Step-by-step +## Set up 1. Create a project folder and `cd` into it @@ -42,7 +43,7 @@ there are some common use cases that need L2 to L1 transaction functionality, su cd message-l2 ``` -1. Run +1. Initialise the project: ::code-group @@ -51,336 +52,319 @@ there are some common use cases that need L2 to L1 transaction functionality, su ``` ```bash [yarn] - yarn init add -y + yarn init -y ``` :: -1. Install the `zksync-contracts` package: +1. Install the required dependencies: ::code-group ```bash [npm] - npm install -D @matterlabs/zksync-contracts + npm install -D zksync-ethers ethers typescript dotenv @matterlabs/zksync-contracts @types/node ``` ```bash [yarn] - yarn add -D @matterlabs/zksync-contracts + yarn add -D zksync-ethers ethers typescript dotenv @matterlabs/zksync-contracts @types/node ``` :: -1. Install `zksync-ethers` and other Node.js dependencies: +1. Install `ts-node` globally to execute the scripts that we're going to create: ::code-group ```bash [npm] - npm install -D zksync-ethers@5 typescript @types/node ts-node + npm install -g ts-node ``` ```bash [yarn] - yarn add -D zksync-ethers@5 ethers@5 typescript @types/node ts-node + yarn global add ts-node + ``` :: -1. In the root folder create an `.env` file with private key of wallet to use: +1. In the root folder, add a `.env` file with the private key of the wallet to use: - ```js - "RICH_WALLET_PRIV_KEY=0x.."; + ```md + WALLET_PRIVATE_KEY=0x..; ``` -1. Create a `send-message.ts` file in the root directory with the next script: +## Send the message - ::drop-panel - ::panel{label="send-message.ts"} +To send a message from L2 to L1, we are going to interact with the zkSync messenger contract. - ```ts [send-message.ts] - // 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. - import * as ethers from "ethers"; - import { Provider, utils, Wallet } from "zksync-ethers"; +Both the address and ABI are provided in the `utils.L1_MESSENGER_ADDRESS` and `utils.L1_MESSENGER` of `zksync-ethers`. +The method we're using is `sendToL1` and we're passing the message in UTF8 bytes format. - import dotenv from "dotenv"; - dotenv.config(); +Create a `1.send-message.ts` file in the root directory with the next script: - const TEST_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; +::drop-panel + ::panel{label="1.send-message.ts"} - const MESSAGE = "Some L2->L1 message"; + ```ts [1.send-message.ts] + // The following script sends a message from L2 to L1 + import * as ethers from "ethers"; + import { Provider, utils, Wallet } from "zksync-ethers"; - const l2Provider = new Provider("https://sepolia.era.zksync.dev"); - const l1Provider = ethers.getDefaultProvider("sepolia"); + import dotenv from "dotenv"; + dotenv.config(); - const wallet = new Wallet(TEST_PRIVATE_KEY, l2Provider, l1Provider); + const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; - async function sendMessageToL1(text: string) { - console.log(`Sending message to L1 with text ${text}`); - const textBytes = ethers.utils.toUtf8Bytes(MESSAGE); + if (!PRIVATE_KEY) { + throw new Error("Please provide WALLET_PRIVATE_KEY in .env file"); + } - 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; - } + const l2Provider = new Provider("https://sepolia.era.zksync.dev"); + const l1Provider = ethers.getDefaultProvider("sepolia"); - 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))); - } + const wallet = new Wallet(PRIVATE_KEY, l2Provider, l1Provider); - async function proveL2MessageInclusion(l1BatchNumber: ethers.BigNumberish, proof: any, trxIndex: number) { - const zkAddress = await l2Provider.getMainContractAddress(); + export async function sendMessageToL1(message: string): Promise { + console.log(`Sending message to L1 with text ${message}`); + const textBytes = ethers.toUtf8Bytes(message); - 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), - }; + 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); + console.log(`Check https://sepolia.explorer.zksync.io/tx/${tx.hash}`); + return tx.hash; + } - console.log(`Retrieving proof for batch ${l1BatchNumber}, transaction index ${trxIndex} and proof id ${proof.id}`); + try { + // To run this script on stand alone mode, you need to provide the message to send + const MESSAGE = "Some L2->L1 message"; + sendMessageToL1(MESSAGE); + } catch (error) { + console.error(error); + } + :: +:: - const res = await mailboxL1Contract.proveL2MessageInclusion(l1BatchNumber, proof.id, messageInfo, proof.proof); +Change the `MESSAGE` variable around line 10 and execute the script by running: - return res; - } +```bash +ts-node 1.send-message.ts +``` - /** - * 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); +You should see the following output: - console.log("Waiting for transaction to finalize..."); +```bash +Sending message to L1 with text {MESSAGE} +L2 trx hash is 0x926efb47c374478191645a138c5d110e6a6a499ea542e14bcb583918646f7db5 +Check https://sepolia.explorer.zksync.io/tx/0x926efb47c374478191645a138c5d110e6a6a499ea542e14bcb583918646f7db5 +``` - // Step 2: waiting to finalize can take a few minutes. - const l2Receipt = await l2Trx.waitFinalize(); +## Retrieve the message transaction details - // Step 3: get and validate proof (block must be verified) - const proof = await getL2MessageProof(l2Receipt.blockNumber); +In order to continue, we need the transaction that sent the message to be included in a batch and sent to L1. +This time varies depending on the network activity and could be around one hour. - console.log(`Proof is: `, proof); +For the next steps we'll need information about the L2 block and L1 batch the transaction was included into. +Create a `2.get-tx-details.ts` file in the root directory with the next script: - const { l1BatchNumber, l1BatchTxIndex } = await l2Provider.getTransactionReceipt(l2Receipt.transactionHash); +::drop-panel + ::panel{label="2.get-tx-details.ts"} - console.log("L1 Index for Tx in block :>> ", l1BatchTxIndex); + ```ts [2.get-tx-details.ts] + // The following retrieves an L2-L1 transaction details + import { Provider } from "zksync-ethers"; - console.log("L1 Batch for block :>> ", l1BatchNumber); + const l2Provider = new Provider("https://sepolia.era.zksync.dev"); - // IMPORTANT: This method requires that the block is verified - // and sent to L1! - const result = await proveL2MessageInclusion( - l1BatchNumber, - proof, - // @ts-ignore - l1BatchTxIndex - ); + export async function getTransactionDetails(hash: string) { + console.log(`Getting L2 tx details for transaction ${hash}`); + const l2Receipt = await l2Provider.getTransactionReceipt(hash); + console.log(`L2 transaction included in block ${l2Receipt.blockNumber} with index ${l2Receipt.index}`); + console.log(`L1 batch number is ${l2Receipt.l1BatchNumber} and tx index in L1 batch is ${l2Receipt.l1BatchTxIndex}`); + console.log(`Check https://sepolia.explorer.zksync.io/tx/${hash} for more details`); + return l2Receipt; + } - console.log("Result is :>> ", result); - process.exit(); - } + try { + // To run this script on stand alone mode, you need to provide the L2 tx hash + const TX_HASH = ""; + getTransactionDetails(TX_HASH); + } catch (error) { + console.error(error); + } + ``` - try { - main(); - } catch (error) { - console.error(error); - } - ``` - :: - :: + :: +:: -1. Add the following lines to your `package.json` in the root folder: +This script retrieves the transaction receipt. The fields we're interested in are: - ```json [package.json] - "scripts": { - "send-message": "ts-node send-message.ts" - } - ``` +- `blockNumber`: the L2 block number the transaction was included into. +- `index`: the index of the transaction in the L2 block. +- `l1BatchNumber`: the L1 batch number the transaction was included into. +- `l1BatchTxIndex`: the index of the transaction in the L1 batch. -1. To run the script, execute: +Enter the transaction hash from [the previous step](#send-the-message) in the `TX_HASH` variable and run the script with: - ::code-group +```bash +ts-node 2.get-tx-details.ts +``` - ```bash [npm] - npm run send-message - ``` +You'll get the following output: - ```bash [yarn] - yarn send-message - ``` +```bash +Getting L2 tx details for transaction 0x7be3434dd5f886bfe2fe446bf833f09d1be08e0a644a4996776fec569c3801a0 +L2 transaction included in block 2607311 with index 0 +L1 batch number is 9120 and tx index in L1 batch is 953 +Check https://sepolia.explorer.zksync.io/tx/0x7be3434dd5f886bfe2fe446bf833f09d1be08e0a644a4996776fec569c3801a0 for more details +``` - :: +## Retrieve the message proof + +To retrieve the proof that the message was sent to L1, create a `3.get-proof.ts` file in the root directory with the next script: + +::drop-panel + ::panel{label="3.get-proof.ts"} -### Example output + ```ts [3.get-proof.ts] + import { Provider } from "zksync-ethers"; + + const l2Provider = new Provider("https://sepolia.era.zksync.dev"); + + export async function getL2LogProof(hash: string, index: number) { + console.log(`Getting L2 message proof for transaction ${hash} and index ${index}`); + const proof = await l2Provider.getLogProof(hash, index); + console.log(`Proof is: `, proof); + return proof; + } -```sh -Sending message to L1 with text Some L2->L1 message -L2 trx hash is 0xb6816e16906788ea5867bf868693aa4e7a46b68ccd2091be345e286a984cb39b -Waiting for transaction to finalize... -Getting L2 message proof for block 5382192 + try { + // To run this script on stand alone mode, you need to provide the transaction hash and L2 tx index + const TX_HASH = "0x7be3434dd5f886bfe2fe446bf833f09d1be08e0a644a4996776fec569c3801a0"; + const L2_TX_INDEX = 0; + + getL2LogProof(TX_HASH, L2_TX_INDEX); + } catch (error) { + console.error(error); + } + ``` + + :: +:: + +The `getLogProof` method requires the L2 transaction hash and the L2 transaction index, +both of which are included in the transaction receipt that [we retrieved in the previous step](#retrieve-the-message-transaction-details). + +Enter the hash and index in the `TX_HASH` and `L2_TX_INDEX` variables and run the script with: + +```bash +ts-node 3.get-proof.ts +``` + +You'll get an output similar to this: + +```bash +Getting L2 message proof for transaction 0x7be3434dd5f886bfe2fe446bf833f09d1be08e0a644a4996776fec569c3801a0 and index 0 Proof is: { - id: 14, + id: 15, proof: [ - '0xd92e806d774b16f21a00230a5ee93555dde30138daf8dbbc8c225ad4aa670edd', - '0xf970801623a03cf02838550dcca2ecf575ace6ae824e5a3339426e69a582c2d8', - '0x389719c677f61f2681950c2136df476e78e74016268806986d4f0599e8055a4b', - '0xb1bde90366b509799bd535f03da87f4c2b68e305bfb5166e694809c4caf0df69', - '0x94b863aefb6546c8465f7700ec701f6b97ddf71a165a6d1e1ce1dc3c41db2534', + '0x871b381c5abfd7365d19ef7bf2b9bd80912b6728a4475dfbaf2f2c652f9912b6', + '0x505e3c0e95b3f2c18a11630874013b527820b729cf8443da3b39c0f029a5d354', + '0x1d49feee54b5d52f361196a133e1265481afae3fcc3ccfae74ef5df0f0c1bad6', + '0x71c3b4937077cd356e32d3f5c413eddff25caf93542a6fa05f0b1c046b6c59d5', + '0x33d776ccbbe67db6aaf1ab61ec564d406b33f7f9d12c587a85104077d13cecd3', '0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272', '0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d', '0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c', - '0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db' + '0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db', + '0x124b05ec272cecd7538fdafe53b6628d31188ffb6f345139aac3c3c1fd2e470f', + '0xc3be9cbd19304d84cca3d045e06b8db3acd68c304fc9cd4cbffe6d18036cb13f', + '0xfef7bd9f889811e59e4076a0174087135f080177302763019adaf531257e3a87', + '0xa707d1c62d8be699d34cb74804fdd7b4c568b6c1a821066f126c680d4b83e00b', + '0xf6e093070e0389d2e529d60fadb855fdded54976ec50ac709e3a36ceaa64c291' ], - root: '0xbc872eb80a7d5d35dd16283c1b1a768b1e1c36404000edaaa04868c7d6a5907c' + root: '0x98ebb6d15a0274a2a40bf7ca42d1576c994f29e23155c10597cd5a0c9ed7e367' } -L1 Index for Tx in block :>> 32 -L1 Batch for block :>> 77512 -Retrieving proof for batch 77512, transaction index 32 and proof id 14 -Result is :>> true ``` -## Messaging overview +## Verify the message transaction proof -Two transactions are required: +Once we have a proof that the message was sent from L2, we can verify that it was actually included in L1. +Create a `4.prove-inclusion.ts` file in the root directory with the next script: -- An L2 transaction which sends a message of arbitrary length. -- An L1 read; implemented by a getter function on an L1 smart contract. +::drop-panel + ::panel{label="4.prove-inclusion.ts"} -1. Get a `Contract` object that represents the - [`L1Messenger`](https://docs.zksync.io/zk-stack/components/smart-contracts/system-contracts#l1messenger) contract. + ```ts [4.prove-inclusion.ts] + import * as ethers from "ethers"; + import { Provider, utils } from "zksync-ethers"; -1. Transform the request into a raw bytes array. + const l2Provider = new Provider("https://sepolia.era.zksync.dev"); + const l1Provider = ethers.getDefaultProvider("sepolia"); -1. Use the - [`sendToL1`](https://github.com/matter-labs/era-contracts/blob/6250292a98179cd442516f130540d6f862c06a16/system-contracts/contracts/interfaces/IL1Messenger.sol#L44) - function from the - [`IL1Messenger.sol`](https://github.com/matter-labs/era-contracts/blob/6250292a98179cd442516f130540d6f862c06a16/system-contracts/contracts/interfaces/IL1Messenger.sol) - interface, passing the message as a raw bytes array. + export async function proveL2MessageInclusion(l1BatchNumber: ethers.BigNumberish, proof: any, l1BatchTxIndex: number, sender: string, message: string) { + const zkAddress = await l2Provider.getMainContractAddress(); - Each sent message emits an - [`L1MessageSent`](https://github.com/matter-labs/era-contracts/blob/6250292a98179cd442516f130540d6f862c06a16/system-contracts/contracts/interfaces/IL1Messenger.sol#L38) - event. + const mailboxL1Contract = new ethers.Contract(zkAddress, utils.ZKSYNC_MAIN_ABI, l1Provider); + // all the information of the message sent from L2 + const messageInfo = { + txNumberInBatch: l1BatchTxIndex, + sender: sender, + data: ethers.toUtf8Bytes(message), + }; - ```solidity - event L1MessageSent(address indexed _sender, bytes32 indexed _hash, bytes _message); + console.log(`Retrieving proof for batch ${l1BatchNumber}, transaction index ${l1BatchTxIndex} and proof id ${proof.id}`); - function sendToL1(bytes memory _message) external returns (bytes32); - ``` + const res = await mailboxL1Contract.proveL2MessageInclusion(l1BatchNumber, proof.id, messageInfo, proof.proof); - - The return value from `sendToL1` is the `keccak256` hash of the message bytes. - -## Prove the result - -The [`proveL2MessageInclusion`](https://github.com/matter-labs/era-contracts/blob/6250292a98179cd442516f130540d6f862c06a16/l1-contracts/contracts/zksync/facets/Mailbox.sol#L35) -function returns a boolean parameter indicating whether the message was sent successfully to L1. + return res; + } -```solidity -function proveL2MessageInclusion( - uint256 _blockNumber, - uint256 _index, - L2Message memory _message, - bytes32[] calldata _proof -) public view returns (bool) { - return _proveL2LogInclusion(_blockNumber, _index, _L2MessageToLog(_message), _proof); -} -``` + try { + // To run this script on stand alone mode, you need to provide the following details + + // The account that sent the transaction in step 1 + const SENDER = "0x..."; + // The same message we sent in step 1 + const MESSAGE = ""; + // Retrieved in step 2 + const L1_BATCH_NUMBER = ; + const L1_BATCH_TX_INDEX = 953; + // The full proof object, including root, proof and id + const PROOF = {}; + + proveL2MessageInclusion(L1_BATCH_NUMBER, PROOF, L1_BATCH_TX_INDEX, SENDER, MESSAGE); + } catch (error) { + console.error(error); + } -::callout{icon="i-heroicons-light-bulb"} + ``` -- `_blockNumber`: L1 batch number in which the L2 block was included; retrievable using the `getBlock` method. -- `_index`: Index of the L2 log in the block; returned as `id` by the - [`zks_getL2ToL1LogProof`](https://docs.zksync.io/build/api-reference/zks-rpc#zks_getl2tol1logproof) method. -- `_message`: Parameter holding the message data. It should be an object containing: - - `sender`: Address that sent the message from L2. - - `data`: Message sent in bytes. - - `txNumberInBlock`: Index of the transaction in the L2 block; returned as `transactionIndex` with - [`getTransactionReceipt`](https://docs.ethers.org/v5/single-page/#/v5/api/providers/provider/-%23-Provider-getTransactionReceipt) - on an Ethers `Provider` object. -- `_proof`: Merkle proof of the message inclusion; retrieved by observing Ethereum or using the `zks_getL2ToL1LogProof` method of the zksync web3 API. + :: :: -## Solidity example - -You can send messages directly from a smart contract deployed on zkSync, or verify that a message was included in an L2 block as shown below: - -::code-group - -```solidity [contract L2] -// The Example contract below sends its address to L1 via the Messenger system contract. -//SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.0; +This scripts interacts with the `proveL2MessageInclusion` method of the zkSync contract on L1. This method requires the following parameters: -// Importing interfaces and addresses of the system contracts -import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; +- `l1BatchNumber`: Batch number the L2 transaction was included into. +- `proofId`: the `id` of the proof retrieved in step 3. +- `messageInfo`: an object including: + - `txNumberInBatch`: the index of the transaction in the L1 batch. + - `sender`: the address of the account that sent the transaction. + - `data`: the message formated in UTF8 bytes format. +- `proof`: the `proof` retrieved in step 3. -contract Example { - function sendMessageToL1() external returns(bytes32 messageHash) { - // Construct the message directly on the contract - bytes memory message = abi.encode(address(this)); +Enter all these details in the `SENDER`, `MESSAGE`, `L1_BATCH_NUMBER`, `L1_BATCH_TX_INDEX` and `PROOF` variables and execute the script with: - messageHash = L1_MESSENGER_CONTRACT.sendToL1(message); - } -} +```bash +ts-node 4.prove-inclusion.ts ``` -```solidity [contract L1] -// This contract receives the information related to the transaction sent to the L2 messenger contract. -// It then proves that the message was included in an L2 block. -//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 to 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 to 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; - } -} +You'll get the following output: + +```bash +Retrieving proof for batch 9120, transaction index 953 and proof id 15 +Result is :>> true ``` -:: +This indicates the proof is valid and the message was actually included in L1. diff --git a/content/tutorials/how-to-test-contracts/10.index.md b/content/tutorials/how-to-test-contracts/10.index.md index 954b827..9fca6dd 100644 --- a/content/tutorials/how-to-test-contracts/10.index.md +++ b/content/tutorials/how-to-test-contracts/10.index.md @@ -1,12 +1,12 @@ --- -title: How to test smart contracts +title: How to test smart contracts with Hardhat description: Learn how to test your smart contracts locally using era-test-node and Hardhat --- -This tutorial provides a step-by-step guide on testing smart contracts using the **hardhat-chai-matchers** plugin +This tutorial provides a step-by-step guide on testing smart contracts using the `hardhat-chai-matchers` plugin in conjunction with the **zkSync Era Test Node** on your local machine. -To facilitate this process of running tests on the **zkSync Era Test Node**, you'll also utilize the **hardhat-zksync-node** plugin. +To facilitate this process of running tests on the **zkSync Era Test Node**, you'll also utilize the `hardhat-zksync-node` plugin. ## Prerequisites @@ -17,7 +17,7 @@ To facilitate this process of running tests on the **zkSync Era Test Node**, you ## Era-test-node plugin In this tutorial, the contract functionality is tested using the [zkSync Era Test Node](https://docs.zksync.io/build/test-and-debug/in-memory-node). -To start local node we use the **hardhat-zksync-node** plugin to integrate this functionality within the Hardhat project. +To start local node we use the `hardhat-zksync-node` plugin to integrate this functionality within the Hardhat project. ::callout{icon="i-heroicons-exclamation-triangle"} During the alpha phase, zkSync Era Test Nodes are currently undergoing development, wherein certain functionalities might not be fully supported or operational. @@ -25,16 +25,16 @@ During the alpha phase, zkSync Era Test Nodes are currently undergoing developme ### Installation -The `hardhat-zksync-node` is included in `hardhat-zksync` so we just need to install the latest one: +To install the `hardhat-zksync-node` plugin and additional necessary packages, execute the following command: ::code-group ```bash [npm] -npm i -D @matterlabs/hardhat-zksync +npm i -D @matterlabs/hardhat-zksync-node ``` ```bash [yarn] -yarn add -D @matterlabs/hardhat-zksync +yarn add -D @matterlabs/hardhat-zksync-node ``` :: @@ -42,7 +42,7 @@ yarn add -D @matterlabs/hardhat-zksync Once installed, add the plugin at the top of your `hardhat.config.ts` file. ```ts [hardhat.config.ts] -import "@matterlabs/hardhat-zksync"; +import "@matterlabs/hardhat-zksync-node"; ``` ### Starting the zkSync Era Test Node @@ -96,14 +96,14 @@ networks: { ## hardhat-chai-matchers plugin To leverage zkSync-specific capabilities within the [Chai](https://www.chaijs.com/) assertion library for testing smart contracts, -it's necessary to use the **hardhat-chai-matchers** plugin. +it's necessary to use the `hardhat-chai-matchers` plugin. In the root directory of your project, execute this command: ::code-group ```bash [npm] -npm i -D @nomicfoundation/hardhat-chai-matchers +npm i -D @nomicfoundation/hardhat-chai-matchers chai@4.3.6 @nomiclabs/hardhat-ethers ``` ```bash [yarn] @@ -112,13 +112,13 @@ yarn add -D @nomicfoundation/hardhat-chai-matchers chai@4.3.6 @nomiclabs/hardha :: -After installing it, add the plugin at the top of your **hardhat.config.ts** file: +After installing it, add the plugin at the top of your `hardhat.config.ts` file: -```bash +```ts [hardhat.config.ts] import "@nomicfoundation/hardhat-chai-matchers" ``` -## Smart Contract example +## Smart contract example To set up the environment for using chai matchers and writing tests, you'll need to create some contracts. Follow these steps: diff --git a/content/tutorials/how-to-test-contracts/_dir.yml b/content/tutorials/how-to-test-contracts/_dir.yml index 11221d0..c7dcc23 100644 --- a/content/tutorials/how-to-test-contracts/_dir.yml +++ b/content/tutorials/how-to-test-contracts/_dir.yml @@ -8,7 +8,7 @@ tags: - smart contracts - how-to - testing -summary: Learn how to test smart contracts on with Hardhat +summary: Learn how to test smart contracts with Hardhat description: This how-to guide explains how to test smart contracts on zkSync using Hardhat. what_you_will_learn: - How to install era-test-node. @@ -16,4 +16,6 @@ what_you_will_learn: updated: 2024-05-09 tools: - zksync-ethers + - hardhat-zksync-deploy + - hardhat-chai-matchers - era-test-node