LayerZero跨链协议入门教程

2022-05-25 16:16:08 浏览数 (1)

译文出自:登链翻译计划[1] 译者:翻译小组[2] 校对:Tiny 熊[3]

前提条件

本教程用 LayerZero 建立一个简单的跨链消息转账合约,需要你对 Solidity Hardhat[4]有一定的程度了解。

概述

首先,我们先来了解一下LayerZero[5], LayerZero 是一个 Omnichain 互操作性协议,设计用于跨链的轻量级消息传递。LayerZero 提供了无需信任、且真实的、有保证的、可配置的消息传递。LayerZero 是由一套低费用(gas-efficient)、不可升级的智能合约实现。

1. 初始化 hardhat 项目

创建一个空目录,在目录下运行npm init,按照提示,填写项目信息,完成之后,运行npm install --save-dev hardhat 安装 Hardhat。

要创建 Hardhat 项目,在项目文件夹运行npx hardhat,如下图:

我们选择 Create an advanced sample project 为教程创建一个 hardhat 项目。

要发送跨链消息,合约在源链调用端点(endpoint)的 send()方法,然后在目标链调用 lzReceive()方法接收消息。为了使用端点合约,我们需要从 LayerZero 库[6] 中导入接口。

备注:端点(endpoint)是在各链部署的合约,参考文档[7]

2. 创建 Solidity 合约

创建合约文件LayerZeroDemo1.sol

代码语言:javascript复制
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
pragma abicoder v2;

import "../interfaces/ILayerZeroEndpoint.sol";
import "../interfaces/ILayerZeroReceiver.sol";
import "hardhat/console.sol";

contract LayerZeroDemo1 is ILayerZeroReceiver {
    event ReceiveMsg(
        uint16 _srcChainId,
        address _from,
        uint16 _count,
        bytes _payload
    );
    ILayerZeroEndpoint public endpoint;
    uint16 public messageCount;
    bytes public message;

    constructor(address _endpoint) {
        endpoint = ILayerZeroEndpoint(_endpoint);
    }

    function sendMsg(
        uint16 _dstChainId,
        bytes calldata _destination,
        bytes calldata payload
    ) public payable {
        endpoint.send{value: msg.value}(
            _dstChainId,
            _destination,
            payload,
            payable(msg.sender),
            address(this),
            bytes("")
        );
    }

    function lzReceive(
        uint16 _srcChainId,
        bytes memory _from,
        uint64,
        bytes memory _payload
    ) external override {
        require(msg.sender == address(endpoint));
        address from;
        assembly {
            from := mload(add(_from, 20))
        }
        if (
            keccak256(abi.encodePacked((_payload))) ==
            keccak256(abi.encodePacked((bytes10("ff"))))
        ) {
            endpoint.receivePayload(
                1,
                bytes(""),
                address(0x0),
                1,
                1,
                bytes("")
            );
        }
        message = _payload;
        messageCount  = 1;
        emit ReceiveMsg(_srcChainId, from, messageCount, message);
    }

    // Endpoint.sol estimateFees() returns the fees for the message
    function estimateFees(
        uint16 _dstChainId,
        address _userApplication,
        bytes calldata _payload,
        bool _payInZRO,
        bytes calldata _adapterParams
    ) external view returns (uint256 nativeFee, uint256 zroFee) {
        return
            endpoint.estimateFees(
                _dstChainId,
                _userApplication,
                _payload,
                _payInZRO,
                _adapterParams
            );
    }
}

LayerZeroDemo1 合约从源链向目标链发送一条消息,在合约构造时使用了端点地址,并且使用了两个接口:ILayerZeroEndpointILayerZeroReceiver

自定义函数sendMsg()封装了endpoint.send(…),其将在目标链上触发对lzReceive()的调用。

在源链调用endpoint.send(…)后,接收链上会自动调用重载的lzReceive函数。

自定义函数estimateFees()封装了endpoint.estimateFees(…),该函数将返回跨链消息的所需的费用。

3. 在不同的链上部署合约

Fantom 测试网络创建部署脚本:

代码语言:javascript复制
const hre = require("hardhat");

async function main() {
  const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1");
  const layerZeroDemo1 = await LayerZeroDemo1.deploy(
    "0x7dcAD72640F835B0FA36EFD3D6d3ec902C7E5acf"
  );
  await layerZeroDemo1.deployed();
  console.log("layerZeroDemo1 deployed to:", layerZeroDemo1.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

在 Fantom 测试网上部署合约:

代码语言:javascript复制
npx hardhat run scripts/deploy_testnet.js --network testnet

Mumbai(Polygon 测试网络)创建部署脚本:

代码语言:javascript复制
const hre = require("hardhat");

async function main() {
  const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1");
  const layerZeroDemo1 = await LayerZeroDemo1.deploy(
    "0xf69186dfBa60DdB133E91E9A4B5673624293d8F8"
  );
  await layerZeroDemo1.deployed();
  console.log("layerZeroDemo1 deployed to:", layerZeroDemo1.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

在 Mumbai 测试网络部署合约:

代码语言:javascript复制
npx hardhat run scripts/deploy_mumbai.js --network mumbai

成功部署两个合约后,我们得到了合约地址,例如:

Mubai 测试网络: 0x37587469690CC37EE19Ff6163ce7275BB1b17d3b

Fantom 测试网络: 0xD67D01D6893cC4a2E17557765987d41E778fadca

4. 测试跨链消息传递

为 Mumbai 测试网络创建一个 javascript 测试脚本:

代码语言:javascript复制
const hre = require("hardhat");
const { ethers } = require("ethers");

async function main() {
  const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1");

  const layerZeroDemo1 = await LayerZeroDemo1.attach(
    "0x37587469690CC37EE19Ff6163ce7275BB1b17d3b"
  );
  const count = await layerZeroDemo1.messageCount();
  const msg = await layerZeroDemo1.message();
  console.log(count);
  console.log(ethers.utils.toUtf8String(msg));
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

该脚本将合约实例关联到前面部署的合约地址:0x37587469690CC37EE19Ff6163ce7275BB1b17d3b。脚本将读取合约中的消息计数器和最后一条消息,现在返回的是 0 和空字符串。

使用 hardhat 运行脚本:

代码语言:javascript复制
npx hardhat run scripts/demo1_mumbai.js --network mumbai

接着在 Fantom 测试网创建一个 javascript 测试脚本:

代码语言:javascript复制
const { formatBytes32String } = require("ethers/lib/utils");
const { ethers } = require("ethers");
const hre = require("hardhat");

async function main() {
  const LayerZeroDemo1 = await hre.ethers.getContractFactory("LayerZeroDemo1");
  const layerZeroDemo1 = await LayerZeroDemo1.attach(
    "0xD67D01D6893cC4a2E17557765987d41E778fadca"
  );

  const fees = await layerZeroDemo1.estimateFees(
    10009,
    "0x37587469690CC37EE19Ff6163ce7275BB1b17d3b",
    formatBytes32String("Hello LayerZero"),
    false,
    []
  );
  console.log(ethers.utils.formatEther(fees[0].toString()));
  await layerZeroDemo1.sendMsg(
    10009,
    "0x37587469690CC37EE19Ff6163ce7275BB1b17d3b",
    formatBytes32String("Hello LayerZero"),
    { value: ethers.utils.parseEther("1") }
  );
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Fantom 测试网测试脚本将合约实例关联上地址 0xD67D01D6893cC4a2E17557765987d41E778fadca。该脚本会从 Fantom 测试网向 Mumbai 测试网上的合约(地址:0x37587469690CC37EE19Ff6163ce7275BB1b17d3b) 发送一条消息“Hello LayerZero” ,并估算了消息费用(演示目的)。最后发送带有费用的消息, 为简单起见,这里为 1FTM。如果源交易比提供的金额少,它将把额外的金额退还到我们传递的地址 _refundAddress

使用 Hardhat 运行脚本:

代码语言:javascript复制
npx hardhat run scripts/demo1_testnet.js --network testnet

脚本完成后,我们可以在 FTMScan 测试网中查找合约0xd67d01d6893cc4a2e17557765987d41e778fadca上的交易:

再次运行 Mumbai 测试脚本,控制台将打印:

小结

教程完成了,Mumbai 测试网络的合约收到 Fantom 测试链发来的消息,增加计数器。LayerZero 使整个过程变得非常简单。

教程源码:https://github.com/The-dLab/LayerZero-Demo

LayerZero 测试网地址: https://layerzero.gitbook.io/docs/technical-reference/testnet/testnet-addresseshttps://medium.com/@Tim4l1f3/layerzero-tutorial-for-beginners-d3fe9326e8b7


本翻译由 Duet Protocol[8] 赞助支持。

原文: https://medium.com/@Tim4l1f3/layerzero-tutorial-for-beginners-d3fe9326e8b7

参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

翻译小组: https://learnblockchain.cn/people/412

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

Hardhat: https://learnblockchain.cn/docs/hardhat/tutorial/

[5]

LayerZero: https://layerzero.network/

[6]

LayerZero 库: https://github.com/LayerZero-Labs/LayerZero/tree/main/contracts/interfaces

[7]

文档: https://layerzero.gitbook.io/docs/technical-reference/mainnet/supported-chain-ids

[8]

Duet Protocol: https://duet.finance/?utm_souce=learnblockchain.cn

0 人点赞