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)函数。在上边的例子里，会产生如下的十六进制错误返回值：0x08c379a0 // Error(string) 的函数选择器 0x0000000000000000000000000000000000000000000000000000000000000020 // 数据的偏移量（32） 0x000000000000000000000000000000000000000000000000000000000000001a // 字符串长度（26） 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // 字符串数据（"Not enough Ether provided." 的 ASCII 编码，26字节）try/catch外部调用的失败，可以通过try/catch语句来捕获，如下：pragma solidity ^0.6.0; interface DataFeed { function getData(address token) external returns (uint value); } contract FeedConsumer { DataFeed feed; uint errorCount; function rate(address token) public returns (uint value, bool success) { // 如果错误超过 10 次，永久关闭这个机制 require(errorCount < 10); try feed.getData(token) returns (uint v) { return (v, true); } catch Error(string memory /*reason*/) { // This is executed in case // revert was called inside getData // and a reason string was provided. errorCount++; return (0, false); } catch (bytes memory /*lowLevelData*/) { // This is executed in case revert() was used。 errorCount++; return (0, false); } } }try关键字后面必须跟一个表示外部函数调用或合约创建的表达式。表达式内部的错误不会被捕获（例如，如果它是一个还涉及内部函数调用的复杂表达式），只会在外部调用本身内部发生还原。后面的部分（可选）声明了与外部调用返回的类型匹配的返回变量。在没有错误的情况下，这些变量被分配并且合约的执行在第一个成功块内继续。如果到达成功块的末尾，则在块之后继续执行。Solidity根据错误类型支持不同类型的catch块：catch Error(string memory reason) { ... }：如果错误是由revert("reasonString")（或导致此类异常的内部错误）引起的，则执行此catch子句。require(false, "reasonString")catch Panic(uint errorCode) { ... }：如果错误是由panic引起的，即失败assert、被零除、无效数组访问、算术溢出等，则将运行此catch子句。catch (bytes memory lowLevelData) { ... }：如果错误签名与任何其他子句不匹配，如果在解码错误消息时出现错误，或者如果没有提供错误数据和异常，则执行此子句。在这种情况下，声明的变量提供对低级错误数据的访问。catch { ... }：如果你对错误数据不感兴趣，你可以只使用（甚至作为唯一的catch子句）而不是前面的子句。catch { ... }调用合约失败背后的原因可能是多方面的。不要假设错误消息直接来自被调用的合约：错误可能发生在调用链的更深处，而被调用的合约只是转发了它。此外，这可能是由于气体不足的情况，而不是故意的错误情况：调用者始终在调用中保留至少1/64的气体，因此即使被调用的合约耗尽气体，调用者还剩一些气。