solidity入门——error 标识符 以及错误处理函数require revert assert介绍


前言

require revert assert error 这些错误处理的简单介绍,solidity 代码的演示,在EVM底层的中断指令REVERT,INVALD。

一 、require revert assert error 这些错误处理的简单介绍

在以太坊智能合约中,错误处理是一种重要的机制,用于在遇到预期之外的情况或违反合约逻辑时中断交易。以下是对require、revert和assert这三个关键字的详细解释,以及如何使用自定义错误来节省gas:

require:
    require用于在执行任何状态更改或计算之前验证输入和条件。
    如果require的条件不满足,它将立即抛出一个错误,撤销所有状态更改,并且交易会失败。
    require经常用于验证函数输入参数,确保在继续执行之前输入是有效和符合预期的。

revert:
    revert类似于require,可以在任何时候被调用,以在不满足特定条件时抛出错误。
    与require不同,revert可以用于复杂的条件检查,或者在执行了一些计算之后。
    使用revert可以在抛出错误时提供自定义的错误信息,这对于调试和用户反馈非常有用。

assert:
    assert用于检查代码中不应该为false的条件,通常用于检查程序的不变性或内部一致性。
    如果assert的条件失败,它将抛出一个错误,但与require和revert不同,assert不会消耗gas(在Solidity 0.8.0之前)。
    assert失败通常意味着合约代码中存在bug,因为它检查的是不应该发生的情况。

自定义错误(Custom Error):
    自定义错误允许开发者定义特定的错误类型,这在调试和错误处理中非常有用。
    使用自定义错误可以提供更详细的错误信息,帮助调用者理解发生了什么问题。
    自定义错误还可以帮助节省gas,因为它们允许合约开发者只包含必要的错误信息,而不是使用通用的错误消息。

下面,我们引入https://solidity-by-example.org/error/的例子来说明,源代码如下:

二、 solidity 代码的演示

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

contract Error {
    function testRequire(uint256 _i) public pure {
        // Require should be used to validate conditions such as:
        // - inputs
        // - conditions before execution
        // - return values from calls to other functions
        require(_i > 10, "Input must be greater than 10");
    }

    function testRevert(uint256 _i) public pure {
        // Revert is useful when the condition to check is complex.
        // This code does the exact same thing as the example above
        if (_i <= 10) {
            revert("Input must be greater than 10");
        }
    }

    uint256 public num;

    function testAssert() public view {
        // Assert should only be used to test for internal errors,
        // and to check invariants.

        // Here we assert that num is always equal to 0
        // since it is impossible to update the value of num
        assert(num == 0);
    }

    // custom error
    error InsufficientBalance(uint256 balance, uint256 withdrawAmount);

    function testCustomError(uint256 _withdrawAmount) public view {
        uint256 bal = address(this).balance;
        if (bal < _withdrawAmount) {
            revert InsufficientBalance({
                balance: bal,
                withdrawAmount: _withdrawAmount
            });
        }
    }
}

我们在remix部署合约之后,可以去调用这里面的函数:
testRequire函数:

function testRequire(uint256 _i) public pure {
    require(_i > 10, "Input must be greater than 10");
}

这个函数使用require来确保传入的参数_i大于10。如果不是,将抛出一个错误并包含自定义的错误消息。

testRevert函数:


function testRevert(uint256 _i) public pure {
    if (_i <= 10) {
        revert("Input must be greater than 10");
    }
}

这个函数使用revert来实现与testRequire相同的逻辑,只是在先经过条件运算后,主动调用和触发,可以看出revert()是直接抛出错误。在逻辑复杂的代码运算中,我们可以通过这种方式直接引出错误。

testAssert函数:


function testAssert() public view {
    assert(num == 0);
}

这个函数使用assert来检查num是否始终等于0。assert通常用于检查内部错误或程序的不变性。

自定义错误InsufficientBalance:


error InsufficientBalance(uint256 balance, uint256 withdrawAmount);

定义了一个名为InsufficientBalance的自定义错误,带有两个参数:当前余额和尝试提取的金额。
这种error的标识符,可以通过自定义错误的方式调用,并且可以显示出更多的已有的信息,通常来说可以节省gas费用。

testCustomError函数:


function testCustomError(uint256 _withdrawAmount) public view {
    uint256 bal = address(this).balance;
    if (bal < _withdrawAmount) {
        revert InsufficientBalance({
            balance: bal,
            withdrawAmount: _withdrawAmount
        });
    }
}

这个函数演示了如何使用自定义错误。它首先检查合约地址的余额是否小于尝试提取的金额,如果是,则使用revert error抛出自定义的error也就是InsufficientBalance错误,并提供相关参数。

三、 底层的中断函数

1 . REVERT

在以太坊虚拟机(EVM)中,REVERT操作码用于停止当前执行上下文,回滚所有状态更改(参见STATICCALL以了解哪些操作码会改变状态),并将未使用的gas返回给调用者。同时,它还会将gas退款回滚到当前上下文之前的值。如果执行被REVERT停止,调用上下文的栈上会放置一个值0,调用上下文继续正常执行。当前上下文的返回数据被设置为调用上下文给定的内存块。

以下是REVERT操作码的一些要点:

  • 栈输入:

    • 第一个参数是内存中的字节偏移量,表示调用上下文的返回数据。
    • 第二个参数是复制的字节大小(返回数据的大小)。
  • 示例:

    • 假设内存中的数据是0xFF01,偏移量是0,大小是2。
    • 栈输入将是1 0 2,表示从内存偏移量0开始,复制2个字节的数据。
  • 在以太坊的在线测试环境(如Remix的playground)中可以复现这个操作。

  • 错误情况:

    • 如果当前上下文的状态更改被回滚,可能是由于以下原因:
      • gas不足。
      • 栈上没有足够的值。
  • Gas消耗:

    • 静态gas:0
    • 动态gas:内存扩展成本

    内存扩展成本的解释可以在这里找到。

简单来说,REVERT操作码是EVM用来处理异常情况的一种机制,它可以在发生错误或需要回滚操作时使用。通过这种方式,智能合约可以安全地撤销它们所做的任何状态更改,并通知调用者发生了错误。

2.INVALD

这段笔记描述的是EVM中的一个操作码,它等同于任何其他在这个参考中不存在的操作码,但它被保证是一个无效指令。自拜占庭分叉(Byzantium fork)以来,这个操作码等同于使用REVERT操作码,并且栈参数为0,0,唯一的区别是当前上下文中的所有剩余gas都被消耗掉。

这里是一些关键点的解释:

  • 无效指令:这个操作码代表了一个无效的指令,也就是说,它不应该被正常使用,如果执行到这个操作码,EVM会视其为错误。

  • REVERT操作码等效:自拜占庭分叉以来,这个操作码的行为与REVERT操作码相同,但是它使用的是栈参数0,0。这意味着它会停止当前的执行上下文,回滚所有状态更改,并将未使用的gas返回给调用者。

  • gas消耗:与REVERT操作码不同,这个操作码会消耗当前上下文中的所有剩余gas。也就是说,不管剩余多少gas,执行这个操作码后都不会有gas返回给调用者。

这种操作码通常用于合约中故意制造一个错误,例如,当合约需要立即停止执行并且不希望剩余gas被返回时。然而,由于这种操作码消耗所有剩余gas,它在实际开发中使用较少,因为REVERT已经足够处理大多数需要回滚的情况,并且REVERT不会消耗所有剩余gas。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值