异常处理
本章学习 Solidity
处理异常的常用方法。
在 Solidity
中,异常是指在合约执行过程中出现的意外情况或错误情况。
当合约中的某个条件不满足或发生内部错误时,就会触发异常。比如,在合约中进行除法运算时,除数等于 0。
异常会导致合约在执行过程中,出现不可预测的结果。所以,我们在合约中必须进行异常处理。
1. 内置异常处理
Solidity
提供了几种内置的预定义异常类型,用于在合约中处理特定的错误情况。这些预定义异常包括:
assert
当断言条件失败时触发,表示出现了不应该发生的情况。它会导致合约立即停止执行,并回滚所有状态更改。
assert
被触发通常表示合约中存在严重的逻辑错误,继续执行会出现不可预测的结果。
比如:除数为 0、数组越界、位移数为负数等。
assert
一旦被触发,则会消耗掉当前交易中提供的全部 gas。这样设计是为了惩罚可能的恶意行为。
require
当要求条件失败时触发,用于检查函数的前置条件。如果条件不满足,则会导致函数立即停止执行,并回滚所有状态更改。
require
常常用来检测输入的参数是否符合要求。
require
一旦被触发,则会扣除掉前面已经消耗的 gas,而剩余的 gas 将会返回给调用者。
revert
用于处理意外情况或未预料到的错误。当需要在异常情况下中止函数执行并回滚状态更改时,可以使用 revert
。
revert
被触发,则会扣除掉前面已经消耗的 gas,而剩余的 gas 将会返回给调用者。
其实,require
完全可以使用 if
语句 和 revert
语句来代替。
revert
允许合约返回一个错误消息,以向调用者提供有关异常的信息。
那么如何使用 revert
呢?
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Revert { // 转账函数 function transfer(address to, uint256 amount) public pure { if(to == address(0)) { revert("address `to` is zero"); // 回滚交易,返回错误信息 } // 执行转账操作 } }
revert
会终止代码的执行,回滚交易,并返回错误信息。
2. try...catch 机制
Solidity
默认的异常处理机制是一旦发生异常,当前交易会被终止,合约状态也会被回滚。
有时候,我们并不想这样简单处理,要是能够捕获异常,然后根据情况自行处理不是更好吗?所以,在这种场景下,就适应于使用 try...catch
语句。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Exception { function divide(uint256 a, uint256 b) external view returns (uint256) { try this.divideInternal(a, b) returns (uint256 result) { // 成功执行,返回计算结果 return result; } catch Error(string memory errorMessage) { // 处理用户定义的异常 revert(errorMessage); } catch { // 处理未预料到的异常 revert("Unknown error occurred"); } } function divideInternal(uint256 a, uint256 b) external pure returns (uint256) { if (b == 0) { // 抛出用户定义的异常 revert("Division by zero"); } return a / b; } }
在 Solidity
中,try
语句只能用于外部函数调用和合约创建调用,不能用于内部函数调用。内部函数的调用只能使用断言语句。
这是在合约创建时,使用 try
的案例:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // 合约A contract ContractA { uint public value; constructor(uint _value) { value = _value; } } // 合约B contract ContractB { ContractA public contractAInstance; constructor(uint _value) { // 尝试创建调用合约A的实例 try new ContractA(_value) returns (ContractA instance) { contractAInstance = instance; } catch Error(string memory reason) { // 捕获可能的异常并处理 revert(reason); } catch { revert("Unknown error occurred"); } } }
try
语句在实际的智能合约中并不常用,我们只需了解即可。