深入理解 Solidity 错误 #2 - 运行时错误

  • Tiny熊
  • 更新于 2023-08-02 16:19
  • 阅读 2395

在运行时错误是最常遇到的情况,你知道 ErrorPanic 的细微差别吗? 发生 Panic 错误真的会消耗所有的 gas 么,本文揭晓答案。

在上一篇介绍了编译时错误(由 Solidity 编译器生成的错误)之后,我们将在本文介绍运行时错误(与链上的合约交互时发生的错误)。

我们将看到,Solidity 可以生成 4 种主要类型的错误:Error(string)Panic(uint256)、自定义 errorinvalid。本文将介绍每种错误的规则和语义。最后,我们将预测一下可能添加到 Solidity 编程语言中的新错误类型。

本文内容有:

  • Solidity 中的常见错误示例
  • Solidity 错误类型
  • Error(string)
  • Panic(uint256)
    • Panic Error - 示例
    • Panic(uint256) 恐慌错误码
  • 自定义错误(Custom Errors)
    • 如何定义自定义错误?
    • 为什么使用自定义错误而不是字符串错误?
    • 自定义错误的命名参数
    • 自定义错误的 Natspec 注释
    • 自定义错误是 ABI 的一部分
  • Invalid
  • Solidity 中的未来错误类型

常见的错误示例

在许多情况下, Solidity 合约代码都可能出现运行时错误。

与 Solidity 相关的一些标准运行时错误包括:

  • 当调用require()时,参数结果为false。
  • 使用 new 关键字创建合约失败,且进程无法正常结束。
  • 将无代码(address.code.length)合约指向外部函数时。(当通过 constructor 运行时,请注意 isContract() 失效)。
  • 在调用公共 getter (view)或pure方法时发送 ethers (使用 msg.value)。
  • 向合约中未标记为 payable 的函数发送以太币 (即 msg.value) 时。
  • assert() 中的条件为 false 时。
  • 调用 function 类型的零初始化变量时。
  • 当一个很大值或负值被转换为一个 enum 时。
  • 以过大或负值的索引访问数组时。

然而,我们会发现,在这份情况列表中,每种情况都属于特定的错误类别,取决于错误的原因。

Solidity 错误类型

  • Error(string) → 通过内置函数 requirerevert 触发。
  • Panic(uint256) → 通过内置函数 assert 触发,或在某些情况下由编译器创建。
  • 自定义 error → 通过 revert CustomError() 触发。
  • invalid操作码 → 通过汇编触发。

示例 1:

Error(string)keccak256哈希值为 0x08c379a0afcc32b1a39302f7cb8073359698411ab5fd6e3edb2c02c0b5fba8aa。

如果保留前 4 个字节,我们将得到 Error(string) 错误选择器为 0x08c379a0

示例 2:

Panic(uint256)keccak256 哈希值是 0x4e487b71539e0164c9d29506cc725e49342bcac15e0927282bf30fedfe1c7268 。

如果保留前 4 个字节,我们将得到Panic(uint256) 错误选择器 0x4e487b71

下表总结了 Solidity 中不同类型的错误:

错误类型 错误签名 bytes4 错误选择器
String Error Error(string) 0x08c379a0
Panic Panic(uint256) 0x4e487b71
Custom Errors 用户错误自定义 基于自定义错误的名称 及 参数(若有)
Invalid none none

让我们看看 Solidity 代码示例,以便更好地理解

pragma solidity ^ 0.8 .0;

contract SolidityErrors {

  error InsufficientBalance(
    uint256 amount,
    uint256 balance
  );

  function testRevertErrorEmpty()
  public
  pure {
    revert();
  }

  function testRevertError()
  public
  pure {
    revert("something went wrong!");
  }

  function testPanicError(uint256 a)
  public
  pure
  returns(uint256) {
    return 10 / a;
  }

  function testCustomError(uint256 amount)
  public
  view {
    revert InsufficientBalance(
      amount,
      address(this).balance
    );
  }

  function testInvalid()
  public
  pure {
    assembly {
      invalid()
    }
  }
}

如果我们在 Remix 中调试交易,就会发现在操作码 REVERT 之前,有一个 4 字节的值被推入堆栈。这 4 个字节的值对应于正在抛出的错误类型的选择器。

Solidity - 错误类型

<p align=center>在 Remix 中调试以找到每种 Solidity 错误类型的选择器 </p>

Error(string)

内置错误 Error(string) 用于 "回退并提示错误信息"。

在下列情况下会出现 Error(string) 异常(在参数 string 中提供了错误信息):

  1. 调用 require(x, "error message"),其中 x 值为 false ,并且你提供了一条 error message

  2. 使用 revert()revert("description") 时。

  3. 如果你执行外部函数调用,而调用的目标是不包含相应的代码。

  4. 如果你的合约通过公共函数(包括构造函数和回退函数)接收以太,而该公共函数未包含payable修饰符。

  5. 如果你的合约通过公共 getter 函数接收以太币。

所提供的 string 会以调用函数 Error(string) 的方式进行 abi 编码。

Panic(uint256)

当涉及到 Solidity 中的 Panic(uint256) 类型错误时,有一条唯一的规则需要牢记:

Panic(uint256) 错误不应出现在无错误的代码中

如果你在开发和测试 Solidity 合约时遇到 Panic 类型的错误: 你应该修复你的代码!

已经部署在网络上的智能合约在运行时出现 "Panic" 错误,很可能是智能合约代码中的一个错误,或者是智能合约的设计要求没有被完全满足或正确。

在此案例中,必须修复 Solidity 代码中的错误,这样智能合约就不会在运行时再次出现同样的 "Panic" 错误。

Solidity 文档更强调了这一点:

正常运行的代码绝不会产生 "Panic",即使是无效的外部输入也不会。

如果发生了这种情况,那么你的合约中就有一个错误,你应该修复它。

Panic错误 - 示例

让我们来看一个真实世界的例子,看看 "Panic "错误是如何发生和修复的。请看下面的 Solidity 代码段。

// SPDX-License-Identifier: GPL-3.0  
pragma solidity ^0.8.0;

contract PanicErrorExample {

  function countTrailingZeroBytes(bytes32 data) public pure returns(uint256) {
    uint256 index = 31;

    // CHECK each bytes of the key, starting from the end (right to left)  
    // skip each empty bytes `0x00` to find the first non-empty byte  
    while (data[index] == 0x00) index--;

    return 32 - (index + 1);
  }

}

顾名思义,该函数用于计算bytes32数据值末尾的0x00零字节数。

例如,当提供下面的值作为 data 参数时,它将返回 4

data = 0xcafecafecafecafecafecafecafecafecafecafecafecafecafecafe00000000
result = 4

![img](https://img.learnblockchain.cn/2023/07/28/41478.png!l...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Tiny熊
Tiny熊
0xD682...E8AB
登链社区发起人 通过区块链技术让世界变得更好而尽一份力。