异常处理

本章学习 Solidity 处理异常的常用方法。

视频Bilibili  |  Youtube
官网binschool.app
推特@BinSchool    DiscordBinDAO   微信:bkra50 

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 语句在实际的智能合约中并不常用,我们只需了解即可。