Pre:
写智能合约经常会出bug,solidity中的异常命令帮助我们debug,也可以提示用户发生了什么错误。
有4个关键词:
-
error
-
require
-
assert
-
revert
error:
error是solidity 0.8版本新加的内容,方便且高效(省gas)地向用户解释操作失败的原因。人们可以在contract之外定义异常。(模块化的前提,代码更易读)
使用方法:
下面,我们定义一个TransferNotOwner异常,当用户不是代币owner的时候尝试转账,会抛出错误:
1 | error TransferNotOwner(); // 自定义error |
require:
require is used to validate inputs and conditions before execution
用于在代码执行之前验证输入和条件,常见3种使用场景:
-
外部input的参数
-
在执行某段逻辑前,判断条件(条件很多,要通过测试不断补充)
-
其他函数的返回值
require命令是solidity 0.8版本之前抛出异常的常用方法,目前很多主流合约仍然还在使用它。
缺点就是gas随着描述异常的字符串长度增加,比error命令要高。
使用方法:
require(检查条件,"异常的描述"),当检查条件不成立的时候,就会抛出异常。
1 | function transferOwner2(uint256 tokenId, address newOwner) public { |
Uni例子:
看看成熟代码里怎么用
1 | /// @inheritdoc IUniswapV3Factory |
revert:
revert 类似 require,相比可选填写异常描述的require,准确的描述会更user friendly,但遇到复杂的检验条件时,会选用revert.
assert:
assert is used to check for code that should never be false.
Failing assertion probably means that there is a bug.
断言真假,常用于after execution的位置
使用场景:一般用于用于内部错误检查或去检查一些不变量,因为它不能解释抛出异常的原因,所以不是对外使用的,是在内部使用的。
使用方法:
它的用法很简单,assert(检查条件),当检查条件不成立的时候,就会抛出异常。
1 | function transferOwner3(uint256 tokenId, address newOwner) public { |
Uni例子:
看看成熟代码里怎么用
1 | function check_liquidityNet_invariant() internal { |
gas对比:
基于代码,简单对比,好像没啥意义。。。😨😨😨
仅仅是小小gas优化的,单个交易看不出啥,multi tx才能看出来。
| Keyword | gas |
|---|---|
| require | 24440 |
| assert | 24446 |
| error | 24457 |
| require带异常描述字符串 | 24743 |
Question
Q1: revert返还gas吗?
理论上require的报错会将余下的gas返回给user,而assert会全部没收,而15_Error中实验结果,是assert比require更少的损耗,问题出在哪里? —— issue
群友回复:
0.8版本之前 assert()会消耗完所有gas

0.8版本后assert()在编译时将操作码从FE改为了FD不会消耗完gas

Failing
assertionsand other internal checks like division by zero or arithmetic overflow do not use theinvalid opcodebut instead therevert opcodeThis will save gas on errors
assert的操作码改变了:invalid opcode ->revert opcode,会返还gas
Q2: require带错误描述为啥更耗gas?
ErrorRequire.evm Vs ErrorRequireText.evm

所有命运赠送的礼物,早已在暗中标好了价格 —— 茨威格
每个opcode都有其gas price,多了字符串要存储,就多了opcode,就多了gas
Summary:
| Keyword | 特点 | 使用场景 |
|---|---|---|
| require | 外部输入,before execution | 1.外部input的参数 2.在执行某段逻辑前判断条件 3.其他函数的返回值 |
| error | 升级版require,错误可模块化 | — |
| revert | 偷懒版require不写异常描述,复杂的检验条件 | 复杂的检验条件 |
| assert | 断言真假,after execution | 常用于func尾部 |
1 | function (input): |
-
输入用
require -
升级用
Error -
偷懒用
revert -
输出用
assert😋😋😋
opcode大法好: 0.8.0版本前后,assert的操作码改变了:invalid opcode ->revert opcode,会返还gas
给自己的建议:
-
多看英文资料,中文资料有毒,思路好乱 🤪🤪🤪
-
学语法就学语法,要注重使用场景,多写多使用,gas优化这类进阶知识,要学了evm、opcode先
-
多积累基础知识,少超前诠释知识

-
除了细读文档,还要看看版本更新了啥新特性,太难了
-
新版本出的新特性优先使用,既省gas,又可以将错误模块化,不用满天星
require语句。双倍快乐😁😁😁 -
change view step by step,think step ↓
-
evm、opcode大法好 🤙