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
assertions
and other internal checks like division by zero or arithmetic overflow do not use theinvalid opcode
but instead therevert opcode
This 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大法好 🤙