L1 governance contract
In this tutorial we'll deploy a simple smart contract in ZKsync. Then we'll deploy another smart contract in L1 and send transactions that update the state of the contract in ZKsync. We'll use the Mailbox
What you'll learn:
- How to deploy smart contracts in L1 and L2.
- How to send transactions in L1 that interact with contracts in L2.
Tools:
- - zksync-cli
- - zksync-ethers
- - Hardhat
This tutorial shows you how to implement communication between L1 and L2 with the following example:
- A Governance Solidity smart contract is deployed on layer 1. This contract has a function that sends a transaction to ZKsync Era layer 2.
- A Counter Solidity smart contract is deployed on ZKsync Era layer 2. This contract stores a number that is incremented
by calling theincrement
method. TheGovernance
contract on layer 1 calls this function.
Prerequisites
- Make sure your machine satisfies the system requirements.
- You are already familiar with deploying smart contracts on ZKsync Era. If not, please refer to the first section of the quickstart tutorial.
- You already have some experience working with Ethereum.
- A wallet with sufficient Sepolia
ETH
on Ethereum and ZKsync Sepolia Testnet to pay for deploying smart contracts. You can get Sepolia ETH from the network faucets.- Get testnet
ETH
for ZKsync Era using bridges to bridge funds to ZKsync.
- Get testnet
- You know how to get your private key from your MetaMask wallet.
zksync-cli
for local testing.
Simply execute npx zksync-cli dev start
to initialize a local ZKsync development environment,
which includes local Ethereum and ZKsync nodes.
This method allows you to test contracts without requesting external testnet funds.
Explore more in the zksync-cli documentation.Complete Project
Download the complete project on GitHub.
Project Setup
Open a terminal window, create a new folder for the project tutorial, e.g. mkdir cross-chain-tutorial
, and cd
into
the folder.
Now create separate folders to store contracts and scripts on L1 and L2. For now we will start with L1-governance folder.
mkdir L1-governance
L1-governance
code is a default Hardhat project used to deploy a contract on L1.
The L2-counter
code includes all ZKsync dependencies and configurations for L2.L1 Governance
cd
intoL1-governance
.- Run the following to initialise and set up the L1 project:
npx hardhat init
Select the option Create a Typescript project and accept the defaults for everything else.
- Import it from the
@matterlabs/zksync-contracts
npm package (preferred). - Download it from the contracts repo.
- Install the following dependencies:
Make sure you use actual node (lts version) and actual npm version
npm i -D typescript ts-node @openzeppelin/contracts @matterlabs/zksync-contracts @nomicfoundation/hardhat-ethers @typechain/ethers-v6 @typechain/hardhat typechain ethers dotenv
Create L1 Governance Contract
L1-governance
folder.The following Solidity code defines the Governance smart contract.
The constructor sets the contract creator as the single governor.
The callZkSync
function calls a transaction on L2 which can only be called by the governor.
- Remove existing
/test
directory and any contracts that exist in/contracts
. cd
into thecontracts/
folder.- Create a file called
Governance.sol
and copy/paste the code below into it.
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;
import "@matterlabs/zksync-contracts/l1/contracts/zksync/interfaces/IZkSync.sol";
contract Governance {
address public governor;
constructor() {
governor = msg.sender;
}
function callZkSync(
address zkSyncAddress,
address contractAddr,
bytes memory data,
uint256 gasLimit,
uint256 gasPerPubdataByteLimit
) external payable {
require(msg.sender == governor, "Only governor is allowed");
IZkSync zksync = IZkSync(zkSyncAddress);
zksync.requestL2Transaction{value: msg.value}(contractAddr, 0,
data, gasLimit, gasPerPubdataByteLimit, new bytes[](0), msg.sender);
}
}
Deploy L1 Governance Contract
- Create the file
L1-Governance/.env
and copy/paste the code below, filling in the relevant values. Find node provider urls here. You have to connect your wallet to the network and add the network to the wallet in advance.L1-Governance/.envNODE_RPC_URL= PRIVATE_KEY=
- Replace the code in
hardhat.config.ts
with the following:import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; import dotenv from "dotenv"; dotenv.config(); const config: HardhatUserConfig = { solidity: "0.8.24", networks: { // Sepolia network sepolia: { url: process.env.NODE_RPC_URL, accounts: [process.env.PRIVATE_KEY as any], }, }, }; export default config;
- Create the file
Governance.ts
inside the/ignition/modules
folder and copy/paste the following code into it:import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; const GovernanceModule = buildModule("GovernanceModule", (m) => { const governance = m.contract("Governance", [], { }); return { governance }; }); export default GovernanceModule;
- From the
L1-governance
folder root, compile and deploy the contract:# compile contract npx hardhat compile # deploy contract npx hardhat ignition deploy ./ignition/modules/Governance.ts --network sepolia
You should see the following output:Deploying [ GovernanceModule ] Batch #1 Executed GovernanceModule#Governance [ GovernanceModule ] successfully deployed 🚀 Deployed Addresses GovernanceModule#Governance - 0xA7d27A1202bE1237919Cf2cb60970141100725b4
Save the address to use in a later step.