阅读(3525) (13)

错误和 Revert 语句

2022-05-13 10:46:32 更新

Solidity 中的错误提供了一种方便且高效的方式来向用户解释操作失败的原因。它们可以在合约内部和外部定义(包括接口和库)。

它们必须与revert 语句一起使用, 这会导致当前调用中的所有更改都被还原并将错误数据传递回调用者。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

/// Insufficient balance for transfer. Needed `required` but only
/// `available` available.
/// @param available balance available.
/// @param required requested amount to transfer.
error InsufficientBalance(uint256 available, uint256 required);

contract TestToken {
    mapping(address => uint) balance;
    function transfer(address to, uint256 amount) public {
        if (amount > balance[msg.sender])
            revert InsufficientBalance({
                available: balance[msg.sender],
                required: amount
            });
        balance[msg.sender] -= amount;
        balance[to] += amount;
    }
    // ...
}

错误不能被重载或覆盖,而是被继承。只要范围不同,就可以在多个地方定义相同的错误。错误实例只能使用revert语句创建。

该错误会创建数据,然后通过还原操作将其传递给调用者,以返回到链外组件或在try/catch 语句中捕获它。请注意,错误只能在来自外部调用时被捕获,在内部调用或同一函数内部发生的还原无法被捕获。

如果不提供任何参数,则错误只需要四个字节的数据,您可以使用上面的NatSpec进一步解释错误背后的原因,它没有存储在链上。这使得它同时成为一个非常便宜和方便的错误报告功能。

更具体地说,错误实例以与函数调用相同名称和类型的函数相同的方式进行 ABI 编码,然后将其用作revert操作码中的返回数据。这意味着数据包含一个 4 字节选择器,后跟ABI 编码数据。选择器由错误类型签名的 keccak256-hash 的前四个字节组成。

笔记

合同可能会因同名的不同错误或什至在调用者无法区分的不同位置定义的错误而恢复。对于外部,即 ABI,只有错误的名称是相关的,而不是定义它的合同或文件。

如果您可以定义 ,该语句将等效于 . 但是请注意,这是一个内置类型,不能在用户提供的代码中定义。require(condition, "description");if (!condition) revert Error("description")error Error(string)Error

同样,失败assert或类似的情况将恢复为内置类型的错误Panic(uint256)。

笔记

错误数据应该只用于给出失败的指示,而不是作为控制流的手段。原因是内部调用的还原数据默认通过外部调用链传播回来。这意味着内部调用可以“伪造”恢复看起来可能来自调用它的合约的数据。