ZkSynk Smart Contracts
May 17, 2023
ZkSynk Cross-chain governance Smart Contract | Деплой смарт контракта ЗкСинк
Это уже второй смарт контракт, так что если еще не деплоили ни одного, то можно начать с этого
Требования к серверу:
я взяла СPХ31 хетцнере
Также нам понадобится:
- кошелек метамаск с ETH Goerli
- Тестовая сеть zksynk era testnet (можно подключить тут)
- Тестовые токенамы ETH в сети zksynk (кран тут)
- Приватный ключ от метамаска (не используйте кошельки с ральными деньгами!)
- RPC ETH1 goerli (можно взять на инфуре)
Подготавливаем сервер:
sudo apt-get update -y && sudo apt upgrade -y && sudo apt-get install make build-essential unzip lz4 gcc git jq chrony -y curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash source ~/.bashrc nvm -v nvm install v16.16.0 node -v #вывод - v16.16.0
corepack enable corepack prepare [email protected] --activate
mkdir L1-governance L2-counter cd L1-governance npx hardhat #нажимаем у, пойдет процесс команды, после будут еще вопросы:
yarn add -D typescript ts-node @nomiclabs/hardhat-waffle @openzeppelin/contracts @matterlabs/zksync-contracts @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle ethereum-waffle [email protected]
L1 контракт
cd contracts ll #тут необходимо убедится, что папка пуста #если в папке что-то есть, то удаляем (rm ИМЯ_ФАЙЛА) nano Governance.sol #вставляем: // 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); } }
cd .. #вы должны быть в папке L1-governance nano goerli.json #вставляем: { "nodeUrl": "<GOERLI NODE URL>", "deployerPrivateKey": "<YOUR PRIVATE KEY>" } #заменить <GOERLI NODE URL> на рпс ефир гоерли (можно взять на инфуре) #заменить <YOUR PRIVATE KEY> на приватный ключ с метамаска
rm hardhat.config.ts nano hardhat.config.ts #вставляем: import { HardhatUserConfig } from "hardhat/config"; import "@nomiclabs/hardhat-waffle"; // import file with Göerli params const goerli = require("./goerli.json"); const config: HardhatUserConfig = { solidity: { version: "0.8.19", }, networks: { // Göerli network goerli: { url: goerli.nodeUrl, accounts: [goerli.deployerPrivateKey], }, }, }; export default config;
cd scripts rm deploy.ts nano deploy.ts #вставляем: // We require the Hardhat Runtime Environment explicitly here. This is optional // but useful for running the script in a standalone fashion through `node <script>`. // // When running the script with `npx hardhat run <script>` you'll find the Hardhat // Runtime Environment's members available in the global scope. import { ethers } from "hardhat"; async function main() { // We get the contract to deploy const Governance = await ethers.getContractFactory("Governance"); const contract = await Governance.deploy(); await contract.deployed(); console.log(`Governance contract was successfully deployed at ${contract.address}`); } // We recommend always using this async/await pattern to properly handle errors. main().catch((error) => { console.error(error); process.exitCode = 1; });
cd .. #вы должны быть в папке L1-governance yarn hardhat compile yarn hardhat run --network goerli ./scripts/deploy.ts #вывод должен быть такой: Governance contract was successfully deployed at 0x613... #запоминаем адрес контракта Governance
L2 контракт
cd $HOME/L2-counter yarn init -y yarn add -D typescript ts-node @matterlabs/hardhat-zksync-deploy @ethersproject/web @types/node @ethersproject/hash @matterlabs/hardhat-zksync-deploy ethers@^5.7.2 @ethersproject/[email protected] zksync-web3 hardhat @matterlabs/hardhat-zksync-solc @matterlabs/hardhat-zksync-deploy nano hardhat.config.ts #вставляем: import "@matterlabs/hardhat-zksync-deploy"; import "@matterlabs/hardhat-zksync-solc"; module.exports = { zksolc: { version: "1.3.10", compilerSource: "binary", }, defaultNetwork: "zkSyncTestnet", networks: { hardhat: { zksync: true, }, zkSyncTestnet: { url: "https://testnet.era.zksync.dev", ethNetwork: "<GOERLI RPC URL>", zksync: true, }, }, solidity: { version: "0.8.19", }, };
#заменить <GOERLI RPC URL> на рпс ефир гоерли (можно взять на инфуре)
mkdir contracts && cd contracts nano Counter.sol #вставляем: // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.13; contract Counter { uint256 public value = 0; address public governance; constructor(address newGovernance) { governance = newGovernance; } function increment() public { require(msg.sender == governance, "Only governance is allowed"); value += 1; } }
cd .. yarn hardhat compile
mkdir deploy && cd deploy nano deploy.ts #вставляем: import { utils, Wallet } from "zksync-web3"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; // Insert the address of the governance contract const GOVERNANCE_ADDRESS = "<GOVERNANCE-ADDRESS>"; // An example of a deploy script that will deploy and call a simple contract. export default async function (hre: HardhatRuntimeEnvironment) { console.log(`Running deploy script for the Counter contract`); // Initialize the wallet. const wallet = new Wallet("<WALLET-PRIVATE-KEY>"); // Create deployer object and load the artifact of the contract you want to deploy. const deployer = new Deployer(hre, wallet); const artifact = await deployer.loadArtifact("Counter"); // Deposit some funds to L2 to be able to perform deposits. const deploymentFee = await deployer.estimateDeployFee(artifact, [utils.applyL1ToL2Alias(GOVERNANCE_ADDRESS)]); const depositHandle = await deployer.zkWallet.deposit({ to: deployer.zkWallet.address, token: utils.ETH_ADDRESS, amount: deploymentFee.mul(2), }); // Wait until the deposit is processed on zkSync await depositHandle.wait(); // Deploy this contract. The returned object will be of a `Contract` type, similar to the ones in `ethers`. // The address of the governance is an argument for contract constructor. const counterContract = await deployer.deploy(artifact, [utils.applyL1ToL2Alias(GOVERNANCE_ADDRESS)]); // Show the contract info. const contractAddress = counterContract.address; console.log(`${artifact.contractName} was deployed to ${contractAddress}`); }
#заменить <GOVERNANCE-ADDRESS> на адрес контракта говернанс #заменить <WALLET-PRIVATE-KEY> на приватный ключ с метамаска
cd .. #вы должны быть в папке L2-counter yarn hardhat deploy-zksync #вывод Counter was deployed to 0x1b6... #запоминаем адрес контракта Counter mkdir scripts
cd scripts wget https://raw.githubusercontent.com/kulikovae/zksynk/main/L2-counter/counter.json
nano display-value.ts #вставляем: import { Contract, Provider } from 'zksync-web3'; const COUNTER_ADDRESS = '<COUNTER-ADDRESS>'; const COUNTER_ABI = require('./counter.json'); async function main() { // Initialize the provider const l2Provider = new Provider('https://testnet.era.zksync.dev'); const counterContract = new Contract( COUNTER_ADDRESS, COUNTER_ABI, l2Provider ); const value = (await counterContract.value()).toString(); console.log(`The counter value is ${value}`); } main().catch((error) => { console.error(error); process.exitCode = 1; });
#заменить <COUNTER-ADDRESS> на адрес контракта каунтер
Вызов значения каунтер
cd mkdir call cd L2-counter/scripts cp counter.json display-value.ts /root/call cd cd call npm init #везде энтер нажимаем npm i typescript ts-node ethers@^5.7.2 zksync-web3 hardhat @matterlabs/hardhat-zksync-solc @matterlabs/hardhat-zksync-deploy npm i --save-dev @types/node npx ts-node ./display-value.ts #правильный вывод: The counter value is 0
Вызов L2 контракта с L1
cd cd L1-governance/artifacts/contracts/Governance.sol/ cat Governance.json #тут копируем значение массива аби(начинается и заканчивается квадратными скобками) #ниже на фото начало и конец(без запятой!):
Все, что скопировали необходимо будет вставить в следующий файл:
cd cd call nano governance.json #сюда вставили, сохранили, закрыли
nano increment-counter.ts #вставляем: import { BigNumber, Contract, ethers, Wallet } from "ethers"; import { Provider, utils } from "zksync-web3"; const GOVERNANCE_ABI = require('./governance.json'); const GOVERNANCE_ADDRESS = '<GOVERNANCE-ADDRESS>'; const COUNTER_ABI = require('./counter.json'); const COUNTER_ADDRESS = '<COUNTER-ADDRESS>'; async function main() { // Enter your Ethereum L1 provider RPC URL. const l1Provider = new ethers.providers.JsonRpcProvider("<RPC-URL>"); // Set up the Governor wallet to be the same as the one that deployed the governance contract. const wallet = new ethers.Wallet("<YOUR-PRIVATE-KEY>", l1Provider); // Set a constant that accesses the Layer 1 contract. const govcontract = new Contract(GOVERNANCE_ADDRESS, GOVERNANCE_ABI, wallet); // Initialize the L2 provider. const l2Provider = new Provider("https://testnet.era.zksync.dev"); // Get the current address of the zkSync L1 bridge. const zkSyncAddress = await l2Provider.getMainContractAddress(); // Get the `Contract` object of the zkSync bridge. const zkSyncContract = new Contract(zkSyncAddress, utils.ZKSYNC_MAIN_ABI, wallet); // Encoding the L1 transaction is done in the same way as it is done on Ethereum. // Use an Interface which gives access to the contract functions. const counterInterface = new ethers.utils.Interface(COUNTER_ABI); const data = counterInterface.encodeFunctionData("increment", []); // The price of an L1 transaction depends on the gas price used. // You should explicitly fetch the gas price before making the call. const gasPrice = await l1Provider.getGasPrice(); // Define a constant for gas limit which estimates the limit for the L1 to L2 transaction. const gasLimit = await l2Provider.estimateL1ToL2Execute({ contractAddress: COUNTER_ADDRESS, calldata: data, caller: utils.applyL1ToL2Alias(GOVERNANCE_ADDRESS) }); // baseCost takes the price and limit and formats the total in wei. // For more information on `REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT` see the [fee model documentation](../developer-guides/transactions/fee-model.md). const baseCost = await zkSyncContract.l2TransactionBaseCost(gasPrice, gasLimit, utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT); // !! If you don't include the gasPrice and baseCost in the transaction, a re-estimation of fee may generate errors. const tx = await govcontract.callZkSync(zkSyncAddress, COUNTER_ADDRESS, data, gasLimit, utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, { // Pass the necessary ETH `value` to cover the fee for the operation value: baseCost, gasPrice, }); // Wait until the L1 tx is complete. await tx.wait(); // Get the TransactionResponse object for the L2 transaction corresponding to the execution call. const l2Response = await l2Provider.getL2TransactionFromPriorityOp(tx); // Output the receipt of the L2 transaction corresponding to the call to the counter contract. const l2Receipt = await l2Response.wait(); console.log(l2Receipt); } // We recommend always using this async/await pattern to properly handle errors. main().catch((error) => { console.error(error); process.exitCode = 1; }); #заменить $GOVERNANCEADDRESS на адрес контракта говернанс #заменить $COUNTERADDRESS на адрес контракта каунтер #заменить $PRIVATEKEY на приватный ключ мета маска #заменить $RPC на рпс ефир гоерли (можно взять на инфуре)
npx ts-node ./increment-counter.ts
Проверяем успешность транзакции, вызывая еще раз скрипт:
npx ts-node ./display-value.ts #Вывод должен быть: The counter value is 1