我们将要实现的另一个最佳实践称为防御性示例(Defensive Example),这是一种模式,它允许我们在不强制原始交易失败的情况下重新处理失败的消息。让我们解释一下它的工作原理。
接收和处理消息
当目标区块链接收到消息时,CCIP路由器会调用ccipReceive
函数。此函数作为合约处理传入CCIP消息的入口点,通过onlyRouter
和onlyAllowlisted
修饰符实施关键的安全检查。
以下是该过程的逐步分解:
通过ccipReceive
进入:
ccipReceive
函数被调用,传入一个包含待处理消息的Any2EVMMessage
结构体。
安全检查确保调用来自授权的路由器、已允许的源链和已允许的发送者。
处理消息:
ccipReceive
调用processMessage
函数,该函数是外部的,以利用Solidity的try/catch错误处理机制。注意:onlySelf
修饰符确保只有合约本身可以调用此函数。
在processMessage
内部,使用s_simRevert
状态变量检查模拟回滚条件。这个模拟由只有合约所有者可调用的setSimRevert
函数来切换。
如果s_simRevert
为假,processMessage
调用_ccipReceive
函数进行进一步的消息处理。
_ccipReceive
中的消息处理:
_ccipReceive
从消息中提取并存储各种信息,如messageId
、解码后的sender
地址、代币数量和数据。
然后发出一个MessageReceived
事件,表示消息处理成功。
错误处理:
如果在处理过程中发生错误(或触发了模拟回滚),ccipReceive
内的catch块将被执行。
失败消息的messageId
被添加到s_failedMessages
中,消息内容被存储在s_messageContents
中。
发出一个MessageFailed
事件,允许以后识别和重处理失败的消息。
失败消息的重新处理
retryFailedMessage
函数提供了一种机制,如果CCIP消息处理失败,可以用来恢复资产。它专门设计用来处理消息数据问题导致整个处理无法进行,但允许代币恢复的场景:
初始化:
只有合约所有者可以调用此函数,提供失败消息的messageId
和代币恢复用的tokenReceiver
地址。
验证:
使用s_failedMessages.get(messageId)
检查消息是否已失败。如果没有,就回滚交易。
状态更新:
将消息的错误代码更新为RESOLVED
,以防止重复进入和多次重试。
代币恢复:
使用s_messageContents[messageId]
检索失败的消息内容。
将与失败消息关联的锁定代币转移到指定的tokenReceiver
,作为一个逃生舱口,而无需再次处理整个消息。
事件发出:
发出一个MessageRecovered
事件,以信号代币已成功恢复。
这个函数展示了一个优雅的资产恢复解决方案,即使在消息处理遇到问题时也能保护用户的价值。
转移代币与数据 - 防御性示例
本教程扩展了可编程代币传输的示例。它使用 Chainlink CCIP 在不同区块链上的智能合约之间传输代币和任意数据,专注于接收合约中的防御性编码。如果在 CCIP 消息接收期间发生指定错误,合约将锁定代币。锁定代币允许所有者根据需要恢复和重定向它们。防御性编码至关重要,因为它使得恢复锁定的代币成为可能,并确保了用户资产的保护。
开始之前
您应该了解如何编写、编译、部署和资助智能合约。如果您需要复习基础知识,请阅读本教程,它将指导您使用 Solidity 编程语言,在 MetaMask 钱包中交互以及在 Remix 开发环境中工作。
您的账户必须在 Avalanche Fuji 上有一些 AVAX 和 LINK 代币,在以太坊 Sepolia 上有一些 ETH 代币。了解如何 获取测试网 LINK。
了解如何 获取 CCIP 测试代币。按照本指南操作后,您应该拥有 CCIP-BnM 代币,并且 CCIP-BnM 应该出现在 MetaMask 中您的代币列表中。
了解如何 资助您的合约。本指南展示了如何使用 LINK 资助您的合约,但您可以使用相同的指南为合约资助任何 ERC20 代币,只要它们出现在 MetaMask 的代币列表中。
按照前一个教程:使用 数据传输代币 来学习如何使用 CCIP 进行可编程代币传输。
编码时间!
在这个练习中,我们将从 Avalanche Fuji 上的智能合约启动交易,向 以太坊 Sepolia 上的另一个智能合约发送字符串文本和 CCIP-BnM 代币,使用 CCIP。然而,在到达接收合约时,处理逻辑将故意失败。本教程将展示一种优雅的错误处理方法,允许合约所有者恢复被锁定的代币。
正确估计您的 gas 限制
彻底测试所有场景以准确估计所需的 gas 限制至关重要,包括失败场景。请注意,用于执行失败场景的错误处理逻辑的 gas 可能高于成功场景。
要使用此合约:
编译您的合约。
部署并在 Avalanche Fuji 上资助您的发送者合约,并启用向以太坊 Sepolia 发送消息的功能:
打开 MetaMask 并选择网络 Avalanche Fuji。
在 Remix IDE 中,点击 Deploy & Run Transactions,从环境列表中选择 Injected Provider - MetaMask。然后 Remix 将与您的 MetaMask 钱包交互,以与 Avalanche Fuji 通信。
点击 transact
按钮。确认交易后,合约地址将出现在 已部署合约 列表中。记下您的合约地址。
打开 MetaMask 并用 CCIP-BnM 代币为您的合约注资。您可以向您的合约转账 0.002
CCIP-BnM。
启用您的合约以向 以太坊 Sepolia 发送 CCIP 消息:
在 Remix IDE 中,转到 Deploy & Run Transactions,在您部署在 Avalanche Fuji 上的智能合约的函数列表中。
调用 allowlistDestinationChain
,传入 16015286601757825753
作为目标链选择器,以及 true
设置为允许。每个链选择器都可以在 支持的网络页面 上找到。
在 以太坊 Sepolia 上部署您的接收者合约,并启用从您的发送者合约接收消息:
打开 MetaMask 并选择网络 以太坊 Sepolia。
在 Remix IDE 中,转到 Deploy & Run Transactions,确保环境仍然是 Injected Provider - MetaMask。
点击 transact
按钮。确认交易后,合约地址将出现在 已部署合约列表 中。记下您的合约地址。
启用您的合约以接收来自 Avalanche Fuji 的 CCIP 消息::
在 Remix IDE 中,转到 Deploy & Run Transactions,打开您部署在 以太坊 Sepolia 上的智能合约的函数列表。
调用 allowlistSourceChain
,传入 14767482510784806043
作为源链选择器,以及 true
设置为允许。每个链选择器都可以在 支持的网络页面 上找到。
启用您的合约以接收来自您部署在 Avalanche Fuji 上的合约的 CCIP 消息:
在 Remix IDE 中,转到 Deploy & Run Transactions,打开您部署在 以太坊 Sepolia 上的智能合约的函数列表。
调用 allowlistSender
,传入您部署在 Avalanche Fuji 上的合约地址,并设置为 true
。
调用 setSimRevert
函数,传入 true
作为参数,然后等待交易确认。将 s_simRever
t 设置为 true
会在处理接收到的消息时模拟失败。更多详情请参阅解释部分。
此时,您在 Avalanche Fuji 上有一个发送者合约,在 以太坊 Sepolia 上有一个接收者合约。作为安全措施,您已启用发送者合约向 以太坊 Sepolia 发送 CCIP 消息,以及接收者合约接收来自 Avalanche Fuji 上发送者的 CCIP 消息。接收者合约无法处理消息,因此,它不会抛出异常,而是会锁定接收到的代币,使所有者能够恢复它们。
注意:另一项安全措施强制只有路由器可以调用 _ccipReceive 函数。更多详情请参阅解释部分。
转移 0.001 CCIP-BnM 和一些文本。使用 CCIP 的 CCIP 费用将以 LINK 支付。
打开 MetaMask 并连接到 Avalanche Fuji。用 LINK 代币为您的合约注资。您可以向您的合约转账 0.5
LINK。在此示例中,LINK 用于支付 CCIP 费用。
从 Avalanche Fuji 发送带有代币的字符串数据::
打开 MetaMask 并选择网络 Avalanche Fuji.
在 Remix IDE 中,转到 Deploy & Run Transactions,打开您部署在 Avalanche Fuji 上的智能合约的函数列表。
填写 sendMessagePayLINK 函数的参数:
点击 transact
并在 MetaMask 上确认交易。
交易成功后,请记录交易哈希。以下是在Avalanche Fuji
上进行交易的一个示例。
注意
在gas价格飙升期间,您的交易可能会失败,需要超过 0.5 LINK 的gas费才能继续。如果您的交易失败,请向您的合约中注入更多的 LINK 代币,并尝试重新提交交易。
打开CCIP浏览器,通过交易哈希搜索你的跨链交易。
CCIP交易在状态被标记为“Success”时完成。在这个例子中,CCIP消息ID是0x120367995ef71f83d64a05bd7793862afda9d04049da4cb32851934490d03ae4。
检查目标链上的接收方合约:
打开 MetaMask 并选择 Ethereum Sepolia 网络。
在 Remix IDE 中,在“部署与运行交易”(Deploy & Run Transactions)部分,打开您在以太坊 Sepolia 上部署的智能合约的函数列表。
调用 getFailedMessages
函数,使用偏移量(offset)为 0
和限制(limit)为 1
,以检索第一个失败的消息。
请注意返回的值有:0x120367995ef71f83d64a05bd7793862afda9d04049da4cb32851934490d03ae4(消息ID)和 1(表示失败的错误代码)。
为了恢复被锁定的代币,请调用 retryFailedMessage
函数:
确认交易后,您可以在区块链浏览器中打开它。请注意,被锁定的资金已转移到 tokenReceiver
地址。
再次调用 getFailedMessages
函数,使用偏移量(offset)为 0
和限制(limit)为 1
,以检索第一个失败的消息。请注意,现在错误代码是 0,表示该消息已经解决。
注意:这些示例合约被设计为可以双向工作。作为练习,您可以使用它们将带有数据的代币从Avalanche Fuji转移到以太坊Sepolia,也可以从以太坊Sepolia转回Avalanche Fuji。
本教程中展示的智能合约旨在与CCIP交互,以传输和接收代币和数据。该合约代码与 传输代币及数据教程 中的代码相似,因此,您可以参阅 其代码解释。我们只将解释主要的不同之处。
sendMessagePayLINK
函数与 传输代币及数据 教程中的 sendMessagePayLINK
函数类似。主要的区别在于增加了gas限制,以适应处理错误逻辑所需的额外gas。
当目标区块链接收到消息时,CCIP路由器会调用ccipReceive
函数。此函数作为合约处理传入CCIP消息的入口点,通过onlyRouter
和onlyAllowlisted
修饰符实施关键的安全检查。
以下是该过程的逐步分解:
通过ccipReceive
进入:
ccipReceive
函数被调用,传入一个包含待处理消息的Any2EVMMessage结构体。
安全检查确保调用来自授权的路由器、已允许的源链和已允许的发送者。
处理消息:
ccipReceive
调用processMessage
函数,该函数是外部的,以利用Solidity的try/catch错误处理机制。注意:onlySelf
修饰符确保只有合约本身可以调用此函数。
在processMessage
内部,使用s_simRevert
状态变量检查是否模拟回滚条件。这个模拟由合约所有者调用的setSimRevert
函数来切换。 如
果s_simRevert
为false,processMessage
调用_ccipReceive
函数进行进一步的消息处理。
在_ccipReceive
中处理消息:
_ccipReceive
从消息中提取并存储各种信息,如messageId、解码后的发送者地址、代币数量和数据。
然后发出一个MessageReceived
事件,表示消息处理成功。
错误处理:
如果处理过程中发生错误(或触发模拟回滚),ccipReceive
内的catch块将被执行。
失败消息的messageId被添加到s_failedMessages
中,消息内容被存储在s_messageContents
中。
发出一个MessageFailed
事件,允许以后识别和重处理失败的消息。
retryFailedMessage
函数提供了一种机制,用于在 CCIP 消息处理失败时恢复资产。它专门设计用于处理那些消息数据问题导致整个处理过程受阻,但仍然允许代币恢复的场景:
初始化:
只有合约所有者可以调用此函数,提供失败消息的 messageId
和用于代币恢复的 tokenReceiver
地址。
验证:
它使用 s_failedMessages.get(messageId)
检查消息是否已失败。如果没有,它将回滚交易。
状态更新:
将消息的错误代码更新为 RESOLVED
,以防止重复条目和多次重试。
代币恢复:
使用 s_messageContents[messageId]
检索失败的消息内容。
将与失败消息关联的锁定代币转移到指定的 tokenReceiver
,作为一个逃生舱口,而无需再次处理整个消息。
事件发出:
发出一个 MessageRecovered
事件,用以标示代币恢复操作已成功完成。
这个函数展示了一个优雅的资产恢复解决方案,即使在消息处理遇到问题时也能保护用户的价值。
参数 | 值和描述 |
---|---|
参数 | 描述 |
---|---|
messageId
失败消息的唯一标识符。
tokenReceiver
代币将被发送到的地址。
_destinationChainSelector
16015286601757825753
这是目标区块链的CCIP链标识符(在这个例子中是 以太坊 Sepolia)。您可以在 支持的网络页面 上找到每个链选择器。
_receiver
您在 以太坊 Sepolia 上的接收者合约地址。 目标合约地址。
_text
_token
0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4
这是源链(本例中为 Avalanche Fuji)上的 CCIP-BnM 合约地址。您可以在 支持的网络页面 上找到每个支持的区块链的所有地址。
_amount
Hello World!
任何 string
1000000000000000
代币数量(对应0.001 CCIP-BnM)。