xyyme.eth

Posted on Mar 13, 2022Read on Mirror.xyz

Solidity中error关键字使用方法

Solidity 在 0.8.4 版本中推出了 error 关键字,可以像 event 那样定义一个错误类型,官方文档提到使用 error 关键字可以节省 gas,并且可以在错误信息中添加参数:

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.

我们直接来看官方的例子

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

// 定义错误类型
error Unauthorized();

contract VendingMachine {
    address payable owner = payable(msg.sender);

    function withdraw() public {
        if (msg.sender != owner)
            // 抛出异常
            revert Unauthorized();

        owner.transfer(address(this).balance);
    }
    // ...
}

可以看到,这种抛出异常的方法和 event 几乎一模一样:

  • event 变成 error
  • emit 变成 revert

文档提到,这种方式可以节省gas,我们来使用 require 编写一个条件校验:

function withdraw2() public {
    // if (msg.sender != owner)
    //     revert Unauthorized();
    require(msg.sender == owner, "Unauthorized");

    owner.transfer(address(this).balance);
}

使用 remix 测试,前者消耗了 23367 的 gas,后者消耗了 23449 的 gas,确实是能够稍微节省一点。

还有一点是这种新的方式可以添加参数,这样就能够直接将运行时的一些数据暴露出来,当出现失败交易的时候能够更准确地排查原因。我们继续来看官方的例子:

// 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])
            // Error call using named parameters. Equivalent to
            // revert InsufficientBalance(balance[msg.sender], amount);
            revert InsufficientBalance({
                available: balance[msg.sender],
                required: amount
            });
        balance[msg.sender] -= amount;
        balance[to] += amount;
    }
    // ...
}

这里我们发起一笔转账交易,查看 etherscan,只有一个 Fail 提示:

上传代码之前

这是因为我们还没有上传代码。上传代码之后:

上传代码之后

可以直接从错误信息看到,我们在发起这笔交易的时候,余额只有0,而想要转账的数量是100。

这里要注意的一点是,使用 require 时,不需要上传代码也能够显示出错误提示。

参考

https://blog.soliditylang.org/2021/04/21/custom-errors/