Solidity自定义错误类型的处理Custom Error Handling

        Solidity是区块链主要类型以太坊的智能合约编程语言,从Solidity v0.8.4版本开始,可以使用自定义错误(custom error)这种方便的方式向合约调用者解释合约调用为什么会失败。在v0.8.4版本之前,只能使用字符串给出合约调用失败更详细的信息(比如:revert("Not enough permission.");)。使用自定义错误类型通常比字符串方式描述错误更省燃料,原因是自定义错误类型可以使用错误类型的名字描述错误信息,而错误名仅需耗费四个字节编码。

        最新的Solidity官方文档对自定义错误方式的错误处理做了一些说明,但当有多个自定义错误异常上报时,如何解析捕获的错误信息、区分自定错误的不同类型、解析自定义错误中携带的数据,查阅相关技术资料和solidity论坛(https://forum.soliditylang.org/),得到的信息仅仅是Solidity虽然已经支持自定义错误类型的定义和抛出异常,但并不支持直接捕获自定义错误类型异常,只能以 “catch (bytes memory lowLevelData)”这样的方式捕获自定义错误的异常以及获取异常相关的底层数据(lowLevelData),官方文档并没有对底层数据的编码格式和解析方法做出足够的说明。

        这里将以代码为例对Solidity自定义错误的处理(错误定义、抛出异常、捕获异常、异常信息解析)进行详细说明。

1 自定义错误类型的定义

        合约内或者合约外均可使用error语句定义自定义错误类型。

        例:error NoPermission(address owner, address caller);

2 抛出自定义错误的异常

        使用revert语句抛出自定义错误类型的异常。如果自定义错误类型定义时带有参数,可以通过参数传递更多的信息给合约调用者。

        例:revert NoPermission(owner, msg.sender);

3 捕获自定义错误的异常

        根据错误类型的不同,Solidity支持以下四种异常捕获的方式。

        catch Error(string memory reason) { ... }:异常是由函数调用revert("reasonString") 或者函数调用require(false, "reasonString")抛出。

        catch Panic(uint errorCode) { ... }:断言函数assert、除数为0、无效的数组访问、算数运算的溢出等抛出的panic错误类型异常。

        catch (bytes memory lowLevelData) { ... }:如果错误类型的签名与以上两种都不匹配,且需要通过解析异常的底层数据获取更详细的错误信息时,使用这种方式捕获异常。这种情况下通过声明的变量lowLevelData访问异常的底层数据。

        catch { ... }:如果不关心异常的错误类型和错误信息时,使用这种方式捕获异常。

        如果希望捕获所有的异常情况,至少需要使用catch { ... }或者catch (bytes memory lowLevelData) { ... }中的一种。

        由于Solidity并不支持catch NoPermission(address owner, address caller) { ... }这样的方式直接捕获自定义错误类型的异常,需要使用catch (bytes memory lowLevelData) { ... }的方式捕获自定义错误类型的异常。

        注:官方可能从Solidity v0.9.0版本开始支持直接捕获自定义错误类型的异常。

4 自定义错误异常信息的解析

        通过catch (bytes memory lowLevelData) { ... }的方式捕获自定义错误类型的异常后,合约调用者可能需要进一步分析携带的具体自定义错误类型和错误信息。要解析这些信息,首先要了解自定义错误类型的实例是怎样编码为底层数据的。

4.1 自定义错误类型实例的编码

        以示例代码中自定义错误类型NoPermission(address owner, address caller)为例,对包含两个地址类型的自定义错误类型实例的编码格式进行说明。自定义错误类型包含其他数据类型的编码格式,可以通过分析所捕获异常的底层数据lowLevelData得到。

        Solidity使用ABI编码函数abi.encodeWithSignature("NoPermission(address,address)", owner, caller)对自定义错误类型实例进行编码,编码后的底层数据分为三部分:第一部分是字符串"NoPermission(address,address)" keccak256算法哈希值的4字节字节数组;第二、三部分是自定义错误类型参数一地址类型数据的32字节字节数组。

        示例代码中的owner地址是0x5b38da6a701c568545dcfcb03fcb875f56beddc4,caller地址是0xab8483f64d9c6d1ecf9b849ae677dd3315835cb2,则编码后的数据为"bytes:  0xf6ca7cfa0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2"。

 4.2 自定义错误类型异常底层数据的解码

        根据4.1章节分析的异常底层数据编码格式,对捕获到的异常的底层数据lowLevelData进行解码。解码分为三部。

        第一步,比较底层数据lowLevelData的头4个字节与自定义错误类型NoPermission的选择子selector是否相等,相等则说明捕获到的异常错误类型是所关心的自定义错误类型。

        第二步,将底层数据lowLevelData接下来的32个字节直接解码为20字节的地址类型数据,既合约CalleeContract创建者owner的外部账户地址。

        第三步,将底层数据lowLevelData再接下来的32个字节直接解码为20字节的地址类型数据,既合约CallerContract调用者caller的外部账户地址。

         示例代码如下图所示,示例的完整代码已上传github SolidityDemo仓库(仓库地址是:https://github.com/sicwolf/SolidityDemo,示例代码源文件是:05ExpressionsAndControlStructures/CustomErrorDemo.sol)。

猜你喜欢

转载自blog.csdn.net/sicwolf/article/details/126605654