ZkSynk Smart Contracts
May 17, 2023

ZkSynk Cross-chain governance Smart Contract | Деплой смарт контракта ЗкСинк

Это уже второй смарт контракт, так что если еще не деплоили ни одного, то можно начать с этого

Требования к серверу:

2/4/50 - минимальные

я взяла С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

Напоминаю, что это второй контракт из серии, не забывайте выполнить первый и подписывайтесь - скоро будут еще!

Спасибо за внимание!
@kulikovae

Smart Hamster Nodes Studio

YouTube канал тут!

Наша новостная группа тут!

Курс "Обучение по нодам" тут!