调试 CCIP dApps 的简要概述
欢迎来到关于调试 Chainlink CCIP dApps 和排查跨链消息的发送与接收问题的附加章节。编写代码只是开始,真正的技能往往在于有效地排查和解决出现的问题。
要了解问题出现的地方或可能出现的问题,您首先需要收藏整个跨链消息传输流程:Processing CCIP Messages。
第二个最重要的事情是理解 EVM 中的 ABI 编码/解码工作原理。当您编译 Solidity 代码时,两个主要输出是应用程序二进制接口(ABI)和 EVM 字节码。
EVM 字节码是以太坊虚拟机执行的机器码,该字节码会被部署到以太坊区块链上。它是一组底层的、十六进制编码的指令集,EVM 可以解释并运行这些指令。字节码以可执行的形式表示智能合约的实际逻辑。
ABI 本质上是一个 JSON 格式的文本文件,描述了您的智能合约中的函数和变量。当您想与已部署的合约交互时(例如,从 Web 应用程序调用一个函数),ABI 用于将函数调用编码成 EVM 可以理解的格式。它是高级应用程序(如 JavaScript 前端)和在以太坊上运行的低级字节码之间的接口。ABI 包括关于每个函数的名称、返回类型、可见性(公共、私有等)以及其参数类型的详细信息。
每当由于接收方的错误导致您的消息未送达时,CCIP浏览器将显示错误消息。然而,如果浏览器不知道 ABI(例如,您尚未在区块浏览器上验证智能合约的源代码),它将显示原始内容而不是人类可读的错误消息。如果我们了解 ABI 编码/解码的工作原理,这仍然没问题,因为有很多工具可以帮助我们。
所以,乍一看这个消息时,您可能会陷入恐慌,以为当下 CCIP 不工作了或出现类似情况。那么让我们解码错误消息,看看出了什么问题。
由浏览器可知错误代码是:0xbf3f9389000000000000000000000000cd936a39336a2e2c5a011137e46c8120dcae0d65 这本质上是一个携带很多信息的十六进制值。但如果我们知道接收方合约的 ABI便可以很简单地解码它。
您可以使用的一些工具:
一些在线解码器,例如这个相当不错:https://bia.is/tools/abi-decoder/
Foundry 的 cast abi-decode
在这个示例中,我们使用 https://bia.is/tools/abi-decoder/ 在线解码器。我们需要提供合约的 ABI和上述错误代码,然后点击解码以获得人类可读的错误消息。
解码后的输出明确地告诉我们:抛出了 Solidity 自定义错误 SenderNotWhitelisted()
,这很可能意味着我们忘记调用接收方智能合约的 allowlistSender()
函数。
如果您更具技术背景,可以使用官方 CCIP GitHub 仓库中的一个脚本来完成相同的任务,也还可以实现更多功能。脚本地址:https://github.com/smartcontractkit/ccip/blob/ccip-develop/core/scripts/ccip/ccip-revert-reason/main.go
在调试时,您需要提供一个错误代码字符串,或者提供 chainId、txHash 和 txRequester 以及 .env
文件中的 JSON RPC URL(推荐使用归档节点)。
如果您此时直接运行 go run main.go
,使用这些预定义值的输出应该是:If you access an array, bytesN or an array slice at an out-of-bounds or negative index (i.e. x[i] where i >= x.length or i < 0).
这意味着抛出了 EVM Panic 代码 0x32,这是一个非常有帮助的错误消息。
一个最常见的错误可能是:ReceiverError. This may be due to an out of gas error on the destination chain. Error code: 0x
这很可能意味着您使用了 extraArgs: ""
语法。默认情况下 gasLimit
为 200,000,而您的 ccipReceive
函数所消耗的 gas 要大于该默认值。
为了解决这个错误,您可能只需要连接您的钱包到 CCIP浏览器并设置“Gas limit override”字段为适当的值,并点击蓝色按钮手动执行此功能。
为了防止将来出现这个问题,您可以使用以下语法,例如将 gas 限制设置为 500,000 gas
请记住:
如果您只是转移代币,gasLimit
应设置为 0,因为没有 ccipReceive
函数。
如果您希望使您的 dApp 与未来的 CCIP 升级兼容,extraArgs
应该是可变的(参见:https://docs.chain.link/ccip/best-practices#using-extraargs)。
通常,当您想在测试网上实时调试或模拟交易时,Tenderly 是一个不错的选择。全追踪功能允许您查看函数实际revert的位置,这比区块浏览器中的错误消息要有帮助得多。
在处理未验证的智能合约时,一个最大的难题就是函数追踪。虽然这个智能合约没有被验证,但函数选择器仍然是已知的,因此 Polygonscan 能够显示它。这有时对调试非常有帮助。
函数选择器是函数签名的 Keccak-256 哈希值的前四个字节。函数签名包括函数名称和括号内的参数类型列表。当进行函数调用时,EVM 通过这前四个字节来确定合约中应该执行的具体函数。
所以调用失败的函数是在未验证的智能合约调用 CCIP Router 合约的过程中触发的,且其函数选择器是 0x96f4e9f9
。如果我们有该智能合约的 ABI,找到函数选择器将非常简单。但如果没有呢?我们可以使用 OpenChain,这个网站有一个已知函数签名的数据库,因此我们可以尝试搜索我们的函数签名。
看起来我们已经找到了revert的函数。现在我们可以查看其代码实现并找出问题所在。尽管从 Tenderly 的界面已经很清楚,问题在于 CCIP 发送方合约没有资金来支付 CCIP 费用——这是最常见的用户错误之一。