多链智能合约部署指南
在之前的文章中,我们解析了跨链智能合约的工作原理。现在,是时候更进一步,亲自动手实现两个链之间的跨链消息传递了。启动你的代码编辑器,因为我们将深入实践实现!
跨链消息传递是使数据从一个区块链传输到另一个区块链的核心机制。可以将其视为链之间的桥梁,允许在不同生态系统之间无缝通信和操作。例如,你可以将数据从Avalanche发送到BSC(币安智能链),并在接收到数据后在BSC上触发操作。
这里有一个现实世界的用例来说明这一点:想象一个在BSC上提供比Avalanche更高回报的质押协议。使用跨链消息传递,你可以:
这个过程允许你在多个链上最大化协议的优势,而无需手动与两者交互。让我们分解一下如何在实践中构建这一点。
现在..
我们将直接深入实现两个链之间的简单跨链消息传递,而不浪费太多时间。
在本文中,我们将使用Wormhole。Wormhole提供了用于跨链消息传递的solidity SDK。我们将利用Wormhole的跨链消息传递功能将数据从一个链发送到另一个链。
你可以使用Remix IDE来编译和部署智能合约,或者通过设置Hardhat或Foundry项目在本地机器上进行相同的操作。
实现跨链消息传递的第一步是定义我们的智能合约。在这种情况下,我们将其称为WormholeCcm.sol
(跨链消息传递的缩写)。
pragma solidity ^0.8.13;
import "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol";
contract WormholeCcm is IWormholeReceiver{
}
在这初始步骤中,我们首先定义合约结构并导入必要的Wormhole接口,这些接口将帮助我们处理跨链通信。
SPDX License Identifier:
// SPDX-License-Identifier: UNLICENSED
这一行是Solidity合约中的标准惯例,用于指定代码分发的许可证。在这种情况下,它被标记为未许可,但你可以选择适合你项目的许可证。
Solidity Version:
我们将Solidity版本定义为^0.8.13
,这意味着合约将兼容Solidity版本0.8.13及以上。指定正确的版本很重要,以避免与新版本的兼容性问题。
Importing Wormhole Interfaces:
我们从Wormhole SDK导入两个重要的接口:
IWormholeRelayer
:此接口将允许我们与Wormhole的中继器交互,中继器负责发送跨链消息。IWormholeReceiver
:此接口用于接收来自其他链的消息。通过继承此接口,我们的合约将能够处理传入的跨链消息。Defining the Contract:
合约WormholeCcm
继承自IWormholeReceiver
接口。这种继承将使我们能够实现处理传入消息的功能,但目前我们只是设置合约的基本结构。
有了这个基础,我们就可以继续前进,开始实现发送和接收跨链消息的核心功能。
现在我们已经定义了WormholeCcm.sol
合约的基本结构,是时候引入状态变量来存储重要数据并处理跨链消息事件了。
event GreetingReceived(string greeting, uint16 senderChain, address sender);
uint256 constant GAS_LIMIT = 200_000;
uint16 public _srcChainId;
IWormholeRelayer public immutable wormholeRelayer;
string public latestGreeting;
address public owner;
mapping(uint16 => bytes32) private peerContracts;
让我们分解每个部分
Greeting Received Event
event GreetingReceived(string greeting, uint16 senderChain, address sender);
greeting
:从另一条链发送的问候消息。senderChain
:发送消息的链的链ID。sender
:在发送链上发起消息的地址。Gas Limit Constant:
uint256 constant GAS_LIMIT = 200_000;
GAS_LIMIT
用于执行跨链交易。Source Chain ID:
uint16 public _srcChainId;
Wormhole Relayer Interface:
IWormholeRelayer public immutable wormholeRelayer;
wormholeRelayer
,它是IWormholeRelayer
接口的一个实例。这将用于与Wormhole的中继服务交互,允许我们将跨链消息从一个区块链发送到另一个区块链。immutable
,这意味着它只能在合约部署期间设置一次,之后不能更改,确保合约的完整性。Latest Greeting:
string public latestGreeting;
public
,我们允许任何人直接从合约中查询此数据。Owner:
address public owner;
Peer Contracts mapping:
mapping (uint16 => bytes32) private peerContracts;
通过定义这些状态变量,我们现在有了存储传入数据、限制gas使用以及管理与Wormhole中继器交互的必要框架。
“onlyOwner”修饰符预先检查“msg.sender”是否为合约所有者的条件。这允许我们限制某些重要功能的访问。像“setPeer”函数这样的函数将只能由所有者调用。因此我们需要使用这个修饰符。
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
现在我们已经设置了状态变量,下一步是为我们的合约WormholeCcm.sol
定义构造函数。构造函数负责在合约部署时用重要参数初始化合约。
constructor(address _wormholeRelayer, uint16 srcChainId){
wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
_srcChainId = srcChainId;
owner = msg.sender;
}
在构造函数中,我们使用提供的_wormholeRelayer
地址分配wormholeRelayer
状态变量。这个地址必须指向部署合约的特定区块链上的Wormhole Relayer的正确实例。你可以从wormhole docs.获取这个地址。
我们使用IWormholeRelayer(_wormholeRelayer)
将地址转换为IWormholeRelayer
接口,确保我们的合约可以正确地与Wormhole Relayer的函数交互。确保我们初始化wormholeRelayer
是很重要的,因为它是不可变的。
我们还将“srcChainId”变量的值设置为传递的链ID值。这是当前链的wormhole链ID。
我们还通过将其设置为“msg.sender”来初始化所有者变量。这意味着部署地址将成为合约的所有者。它将只允许所有者访问像“setPeer”函数这样的函数。
此函数负责设置与相应链ID关联的对等合约地址。
它接受两个参数:
ChainId
:目标区块链的链ID,消息将被发送到该区块链。peerContract
:目标链上与提供的链ID关联的合约地址。这个地址应该是bytes32类型。该函数还检查提供的地址是否不是空地址,以避免为特定链ID设置空地址。
function setPeer(uint16 chainId, bytes32 peerContract) public onlyOwner{
require(peerContract != bytes32(0));
peerContracts[chainId] = peerContract;
}
在这一步中,我们实现sendMessage函数,该函数负责使用Wormhole发送跨链消息。此函数允许我们从一个区块链向另一个区块链发送问候。
function sendMessage(
uint16 targetChain,
address targetAddress,
string memory greeting) public payable {
uint256 cost = quoteCrossChainGreeting(targetChain);
require(msg.value == cost);
wormholeRelayer.sendPayloadToEvm{value: cost}(
targetChain,
targetAddress,
abi.encode(greeting, msg.sender),
0,
GAS_LIMIT,
targetChain,
msg.sender
);
}
以下是此函数的逐步工作原理:
sendMessage
,并标记为public
,意味着任何人都可以调用它。targetChain
:消息将被发送到的区块链的链ID。targetAddress
:目标链上将接收消息的合约地址。greeting
:要跨链发送的问候消息。费用计算:
uint256 cost = quoteCrossChainGreeting(targetChain);
quoteCrossChainGreeting
来确定发送消息到targetChain
的费用。发送跨链消息需要支付中继服务的费用,这取决于链。消息费用验证:
require(msg.value == cost);
msg.value
(与交易一起发送的以太币数量)不等于计算出的cost
,交易将失败。发送跨链消息:
wormholeRelayer.sendPayloadToEvm{value: cost}(...)
:sendPayloadToEvm
函数发送实际跨链消息的地方。函数参数包括:**targetChain**
(uint16):
**targetAddress**
(address):
**payload**
(bytes memory):
payload
是跨链发送的实际消息,编码为bytes
。abi.encode(greeting, msg.sender)
将问候字符串和发送者的地址(msg.sender
)打包成一个字节数组。此编码确保数据在链间正确传输,并且可以在接收端解码。**receiverValue**
(uint256):
0
,意味着不发送额外的价值,因为该函数仅传递消息负载,不涉及代币转移。**gasLimit**
(uint256):
GAS_LIMIT
常量(先前设置为200,000
)。**refundChainId**
(uint16):
**refundAddress**
(address):
此sendMessage函数封装了在区块链之间发送数据的核心逻辑。它确保用户支付正确的跨链消息费用,并使用Wormhole的中继器将编码的问候消息传输到指定的目标链和地址。
发送跨链消息会产生费用。现在要在某个区块获取跨链费用,wormhole提供了一个函数来获取跨链费用。
function quoteCrossChainGreeting(
uint16 targetChain
) public view returns (uint256 cost) {
(cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(
targetChain,
0,
GAS_LIMIT
);
}
targetChain
(目标链ID)。cost
,基于目标链和gas限制。在设置跨链消息传递后,下一个关键组件是**receiveWormholeMessages**
函数。此函数负责处理通过Wormhole从其他区块链发送的传入消息。
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory,
bytes32 sender,
uint16 sourceChain,
bytes32 ) public payable override {
require(msg.sender == address(wormholeRelayer), "Only relayer allowed");
require(peerContracts[sourceChain] == sender);
(string memory greeting, address senderAddress) = abi.decode(
payload,
(string, address)
);
latestGreeting = greeting;
emit GreetingReceived(latestGreeting, sourceChain, senderAddress);
}
以下是此函数的功能:
目的:处理从其他链接收的消息。当跨链消息到达时,此函数会解码并处理它。此函数仅由wormhole relayer合约调用。
输入:
**bytes memory payload**
:
**bytes[] memory additionalVaas**
:
**bytes32 sender**
:
**uint16 sourceChain**
:
**bytes32 deliveryHash**
:
并非所有这些参数总是被使用,但我们应该了解它们。
输出:
latestGreeting
状态变量。GreetingReceived
事件,包含更新的问候消息、源链ID和发送者的地址。安全检查:
此函数通过处理传入消息并相应地更新合约状态,完成了跨链通信过程。它确保你的合约可以响应来自其他链的消息,并提供了一种跟踪和处理接收数据的方法。
pragma solidity ^0.8.13;
import "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol";
contract WormholeCcm is IWormholeReceiver {
event GreetingReceived(string greeting, uint16 senderChain, address sender);
uint256 constant GAS_LIMIT = 200_000;
uint16 public _srcChainId;
IWormholeRelayer public immutable wormholeRelayer;
string public latestGreeting;
address public owner;
mapping(uint16 => bytes32) private peerContracts;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
constructor(address _wormholeRelayer, uint16 srcChainId){
wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
_srcChainId = srcChainId;
owner = msg.sender;
}
function quoteCrossChainGreeting( uint16 targetChain ) public view returns (uint256 cost) {
(cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(
targetChain,
0,
GAS_LIMIT
);
}
function setPeer(uint16 chainId, bytes32 peerContract) public onlyOwner{
require(peerContract != bytes32(0));
peerContracts[chainId] = peerContract;
}
function sendMessage( uint16 targetChain,
address targetAddress,
string memory greeting ) public payable {
uint256 cost = quoteCrossChainGreeting(targetChain);
require(msg.value == cost);
wormholeRelayer.sendPayloadToEvm{value: cost}(
targetChain,
targetAddress,
abi.encode(greeting, msg.sender),
0,
GAS_LIMIT,
targetChain,
msg.sender
);
}
function receiveWormholeMessages( bytes memory payload,
bytes[] memory,
bytes32 sender,
uint16 sourceChain,
bytes32 ) public payable override {
require(msg.sender == address(wormholeRelayer), "Only relayer allowed");
require(peerContracts[sourceChain] == sender);
(string memory greeting, address senderAddress) = abi.decode(
payload,
(string, address)
);
latestGreeting = greeting;
emit GreetingReceived(latestGreeting, sourceChain, senderAddress);
}
}
让我们在remix IDE上编译上述合约并将其部署在两条链上:Avalanche fuji和BSC testnet。
编译合约
在编辑器中打开合约的同时,转到侧边栏上的编译选项卡。点击编译按钮。成功编译后,进入下一步。
连接Metamask钱包与REMIX IDE并部署合约
切换到remix IDE上的“部署和运行交易”选项卡。在环境下拉菜单中选择注入的Metamask选项。
从wormhole文档中获取relayer地址, 在这里。在部署输入字段中输入连接链的relayer地址作为构造函数参数。
点击部署按钮并在Metamask中确认交易。以下截图显示了BSC Testnet链的部署交易。切换Metamask钱包到Avalanche fuji链后,重复相同的步骤并将合约部署到Avalanche fuji。
将合约部署到BSC
要设置对等合约,你需要调用**setPeer**
函数。这确保了只有相应链上的授权合约可以相互通信。
操作步骤如下:
**setPeer**
函数:在目标链(例如BSC)上,调用**setPeer**
函数以建立与Avalanche链上合约的连接。通过设置这个对等连接,你可以确保只有Avalanche链上的授权合约可以向BSC合约发送跨链消息,反之亦然。这一步对于确保跨链合约之间的通信安全,防止未经授权的消息被处理至关重要。
确保在Avalanche Fuji链上重复相同的操作,并授权BSC测试网链合约地址。
在发送跨链消息之前,确定所需的费用是至关重要的。在我们的例子中,我们使用**quoteCrossChainGreeting**
函数来获取准确的费用估算。此函数接受目标链ID,格式按照Wormhole的规范(你可以在Wormhole的文档中轻松找到链ID)。
例如,在调用该函数后,我们收到的费用估算为**2600052000000000**
wei,相当于**0.002600052**
AVAX。与其他跨链协议相比,Wormhole的费用非常低,使其成为跨链消息传递的经济选择。
有了这些信息,我们现在知道在调用**sendMessage**
函数时,我们必须传递**0.002600052**
AVAX作为所需的值,以成功发送消息。此费用确保我们的消息将在目标链上以必要的gas限制进行执行。
现在我们已经估算了费用,是时候从AVAX Fuji测试网向BSC测试网发送跨链消息了。
为此,我们将调用**sendMessage**
函数,并使用从**quoteCrossChainGreeting**
函数接收到的值,即**0.002600052**
AVAX。
以下是逐步过程:
设置值:在值字段中输入**2600052000000000**
wei(相当于**0.002600052**
AVAX)。
调用函数:通过传递所需参数来调用**sendMessage**
函数:
提供这些参数后,跨链消息将从AVAX Fuji测试网发送到BSC测试网。
发送跨链消息
发送跨链消息后,监控其状态以确保成功交付非常重要。Wormhole提供了一个易于使用的工具:Wormhole Scan。
要跟踪跨链交易的进度:
Wormhole Scan将显示你的交易状态,允许你确认消息已成功跨链传递。
已经完成了!!Wormhole非常快。跨链发送数据或消息的速度取决于我们使用的协议以及该协议用于跨链数据传输的机制。
像Chainlink CCIP这样的协议可能需要更多时间来传递消息,因为它等待目标链上交易的最终确定。而Wormhole使用不同的机制,包括守护节点网络和链上中继器。你可以在这里了解更多信息。
发送跨链消息后,你可以通过读取**latestGreeting**
变量来验证它是否已成功接收。此变量应更新为你发送的消息,在本例中为"Hey there"。
以下是确认消息接收的方法:
在Wormhole Scan上检查:
你可以通过输入交易哈希使用Wormhole Scan确认交易状态和消息接收。它将显示跨链消息的传递过程,包括其到达目标链。
在BSC测试网上读取**latestGreeting**
变量:
**latestGreeting**
getter函数以读取合约中存储的值。结果应返回你从Avalanche Fuji链发送的消息,即“Hey there”。这确认了跨链消息的成功传递。
这就是全部——只需两个关键函数:**sendMessage**
和**receiveMessage**
,以及配置端点和路径,你就可以开始使用Wormhole进行跨链消息传递了!但等等,还有其他提供跨链消息传递的协议吗?
别担心,我已经为你准备好了!虽然本指南侧重于Wormhole,不同的协议遵循类似的结构,但有一些变化。为了帮助你导航这些协议,这里有一个Remix工作区和一个GitHub仓库,你可以在其中探索使用各种跨链消息传递协议的合约:
这些资源将指导你使用其他协议(如LayerZero、Axelar等)设置跨链消息传递。
但我们必须分别学习每个协议吗?完全不需要。当你探索这些合约时,你会很快注意到,发送跨链消息基本上归结为三个关键步骤:
一旦掌握了这些基本步骤,在协议之间切换就变得简单了。无论是Wormhole、LayerZero还是其他任何协议,底层逻辑是一致的。通过这种理解,你可以自信地使用任何协议实现跨链消息传递!总之,跨链消息传递是实现区块链之间无缝通信的重要工具,使数据和交易能够在不同网络之间自由流动。无论你使用的是Wormhole、LayerZero还是其他协议,核心概念都是一致的:从源链发送消息,在目标链接收消息,并配置必要的端点和合约。
一旦掌握了这些基本步骤,你会发现实现跨链消息传递变得很简单,无论使用哪种协议。有了这些知识,你现在可以开始构建强大的跨链应用程序了。编码愉快!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!