Hello World - 2022年2月
type: channel
link: https://wlwl.host/index.php/2022/02/
description: Just So So ...
items: 以太坊合约错误处理

以太坊合约错误处理
type: item
date: 2022-02-24T14:01:00+00:00
description: Solidity使用状态恢复异常来处理错误。这种异常将撤消对当前调用（及其所有子调用）中的状态所做的所有更改，并且还向调用者标记错误。当子调用中发生异常时，它们会自动“冒泡”（即异常被重新抛出），除非它们在try/catch语句中被捕获。但是如果是在send和低级别函数（call, delegatecall和staticcall）调用里发生异常时，他们会返回false（第一个返回值） 而不是冒泡异常。注意：根据EVM的设计，如果被调用的地址不存在，低级别函数call,delegatecall和staticcall的第一个返回值同样是true。如果需要，请在调用之前检查账号的存在性。异常可以包含传递给调用者的数据。该数据由一个4字节函数选择器和随后的ABI编码数据组成。异常可以以错误实例的形式返回错误数据给调用者。目前，Solidity支持两种错误函数签名：Error(string)和Panic(uint256)。Error用于“常规”错误条件，而Panic用于无错误代码中意外出现的错误。assert检查异常(Panic)Assert只能用于测试内部错误和检查不变量。正常运行的代码不应该造成Panic，即使是在无效的外部输入上也是如此。如果发生这种情况，那么您的合同中有一个错误，您应该修复它。在以下情况下会生成Panic异常，提供的错误码编号，用来指示Panic的类型。0x00：用于通用编译器插入的Panic。0x01：如果你调用assert的参数（表达式）结果为false。0x11：在unchecked { … }外，如果算术运算结果向上或向下溢出。0x12：如果你用零当除数做除法或模运算（例如：5 / 0或23 % 0）0x21：如果你将一个太大的数或负数值转换为一个枚举类型。0x22：如果你访问一个没有正确编码的存储类型字节数组。0x31：如果在空数组上.pop()。0x32：如果你访问bytesN数组（或切片）的索引太大或为负数。0x41：如果你分配了太多的内内存或创建了太大的数组。0x51：如果你调用了零初始化内部函数类型变量。可以提供消息字符串给require（第二个参数）require检查错误(Error)require函数会创建一个Error(string)类型的错误或者没有数据的错误，并且require函数应该用于确认条件有效性，例如输入变量，或合约状态变量是否满足条件，或验证外部合约调用返回的值。下列情况将会产生一个Error(string)（或没有数据）的错误：如果你调用require的参数（表达式）最终结果为false。如果你在不包含代码的合约上执行外部函数调用。如果您使用revert()或revert("description")。如果你通过合约接收以太币，而又没有payable修饰符的公有函数（包括构造函数和fallback函数）。如果你的合约通过公有getter函数接收Ether。错误“冒泡”Solidity会转发来自子（外部）调用的错误（如果提供），这意味着它可能会导致Error或Panic（或给出的任何其他内容）：如果.transfer()失败。如果你通过消息调用调用某个函数，但该函数没有正确结束（例如, 它耗尽了gas，没有匹配函数，或者本身抛出一个异常），除非使用低级操作call、send、delegatecall、callcode或staticcall的函数调用。低级操作不会抛出异常，而是通过返回 false来指示失败。如果你使用new关键字创建合约，但合约创建没有正确结束。revertrevert函数是另一个可以在代码块中处理异常的方法,可以用来标记错误并回退当前的调用。revert调用中还可以包含有关错误信息的参数，这个信息会被返回给调用者，并且产生一个Error(string)错误。下边的例子展示了错误字符串如何使用 revert (等价于 require ) ：// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.1; contract VendingMachine { function buy(uint amount) payable { if (amount > msg.value / 2 ether) revert("Not enough Ether provided."); // 下边是等价的方法来做同样的检查： require( amount <= msg.value / 2 ether, "Not enough Ether provided." ); // 执行购买操作 } }如果直接提供错误原因字符串，则这两个语法是等效的。提供的字符串将经过ABI编码 如果它调用Error(string)函数。在上边的例子…