智能合约Solidity语言错误处理函数(require、revert、assert)使用详解

Solidity语言中定义了以下三种错误处理方式:

  • require:用于在执行前验证输入和条件;
  • revert:用于直接触发回退,可自定义异常处理;
  • assert:用于检查不应该为假的代码,失败的断言可能意味着代码层面存在错误。

异常处理将撤消当前调用对状态所做的所有更改,并且还可以向调用者抛出错误。

错误处理函数

  • Require()

    require(condition, description)
    

    require 首先检查 condition,如果条件为真则继续执行,否则提供一个消息字符串 description 用于标记错误(可选)。

  • revert()

    if (!condition) revert(); 
    if (!condition) revert(description);
    if (!condition) revert CustomError(arg1, arg2, ...);
    

    revert 可以直接触发回退,也可以抛出一个消息字符串用于标记错误,也可以自定义错误处理。

  • assert()

    assert(condition);
    

    assert 用于检查 condition 是否为真,检查失败时抛出异常。

函数共同点

以下三个语句的功能完全相同:

if (msg.sender != owner) { revert(); }
assert(msg.sender == owner);
require(msg.sender == owner);

这三个语句都用于检查当前调用者是否为合约的所有者,如果检查结果不为真则抛出异常。

函数差异化

Gas开销

assert() 将消耗所有剩余的Gas,并恢复所有的操作。

require()revert() 将退还所有剩余的Gas,同时可以返回一个值(自定义的报错信息)。

适用场景

require() 的适用场景

  • 验证用户输入,如:

    require(input > 10);
    
  • 验证外部合约响应(返回值),如:

    require(external.send(amount));
    
  • 执行合约前验证状态条件,如:

    require(block.number > SOME_BLOCK_NUMBER);	// 或者
    require(balance[msg.sender] >= amount);
    

合约中应该尽量使用 require 来处理错误,且放在函数最开始的地方使用。

revert() 的适用场景

revert 函数与 require 函数类似,但是适用更复杂处理逻辑的场景。如果代码中需要复杂的 if/else 逻辑流,那么应该考虑适用 revert 函数而不是 require 函数。

assert() 的适用场景

  • 检查整数溢出(overflow/underflow),如:

    c = a + b;
    assert(c > b);
    
  • 检查不变量(invariants),如:

    assert(this.balance >= totalSupply);
    
  • 验证改变后的状态,如:

    assert(state);
    

合约中应该尽量少用 assert 调用,如果要适用 assert 应该在函数结尾处使用。

assertrequire 函数均被用来检查条件并在条件不满足时抛出异常,它们的主要区别是 require 应该被用于函数中检查条件,assert 用于预防不应该发生的情况,即不应该使条件错误。

合约例子

例子1:下面是一个合约例子,用来演示错误处理函数的用法。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

// 错误处理及异常
contract Errors {
   // Require测试
    function testRequire(uint i) public pure {
        require(i > 10, "Input must be greater than 10");
    }

    // Revert测试
    function testRevert(uint i) public pure {
        if (i <= 10) {
            revert("Input must be greater than 10");
        }
    }

    // Assert测试
    uint public num;
    function testAssert() public view {
        assert(num == 0);
    }

    // 自定义错误
    error InsufficientBalance(uint balance, uint amount);
    function testCustomError(uint _amount) public view {
        uint bal = address(this).balance;
        if (bal < _amount) {
            revert InsufficientBalance({balance: bal, amount: _amount});
        }
    }
}

例子2:另一个合约例子,比上一个例子更复杂一些。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

// 帐号存取款异常处理
contract ErrorsAccount {
    uint public balance;
    uint public constant MAX_UINT = 2**256 - 1;

    // 存入以太币到合约
    function deposit(uint _amount) public payable {
        uint oldBalance = balance;
        uint newBalance = balance + _amount;

        // 检查溢出
        require(newBalance >= oldBalance, "Overflow");

        balance = newBalance;
        assert(balance >= oldBalance);
    }

    // 从合约提取以太币
    function withdraw(uint _amount) public payable {
        uint oldBalance = balance;

        // 检查溢出
        require(balance >= _amount, "Underflow");

        if (balance < _amount) {
            revert("Underflow");
        }

        balance -= _amount;
        assert(balance <= oldBalance);
    }
}

合约执行

我们在Remix中编译、部署和运行这个合约例子。

例子1:部署完成后的界面如下图:
在这里插入图片描述
我们分别执行 testRequiretestReverttestAsserttestCustomError 四个测试函数,观察输出结果是否和我们期望的一样。

例子2:部署完成后的界面如下图:
在这里插入图片描述
我们分别执行 depositwithdraw 两个测试函数,观察输出结果是否和我们期望的一样。

猜你喜欢

转载自blog.csdn.net/zyq55917/article/details/125527812