# Solidity 课程 12: 异常 **Published by:** [Novar](https://paragraph.com/@novar/) **Published on:** 2022-06-07 **URL:** https://paragraph.com/@novar/solidity-12 ## Content solidity一个错误将撤消在交易期间对状态所做的所有改变。有三种抛出异常的方法:error,require和assert。1、Error、revertError是solidity 0.8版本新加的内容,方便且高效(省gas)的向用户解释操作失败的原因。人们可以在contract之外定义异常。 下面,我们定义一个TransferNotOwner异常,当用户不是代币owner的时候尝试转账,会抛出错误:error TransferNotOwner(); // 自定义errorfunction transferOwner1(uint256 tokenId, address newOwner) public { if(_owners[tokenId] != msg.sender){ revert TransferNotOwner(); } _owners[tokenId] = newOwner; } 注意:在执行当中,error 必须搭配 revert 命令使用。// custom error error InsufficientBalance(uint balance, uint withdrawAmount); function testCustomError(uint _withdrawAmount) public view { uint bal = address(this).balance; if (bal < _withdrawAmount) { revert InsufficientBalance({balance: bal, withdrawAmount: _withdrawAmount}); } } 我们也可以直接使用revert: function testRevert(uint _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"); } } 2、Requirerequire命令是solidity 0.8 版本之前抛出异常的常用方法,目前很多主流合约仍然还在使用它。 他很好用,唯一的缺点就是gas随着描述异常的字符串长度增加,比error命令要高。使用方法:require(检查条件,”异常的描述”),当检查条件不成立的时候,就会抛出异常。 我们用require命令重写一下上面的transferOwner函数:function transferOwner2(uint256 tokenId, address newOwner) public { require(_owners[tokenId] == msg.sender, "Transfer Not Owner"); _owners[tokenId] = newOwner; } function testRequire(uint _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"); } 3、Assertassert应该只用于调试,因为他不能解释抛出异常的原因(比require少个字符串)。他的用法很简单,assert(检查条件),当检查条件不成立的时候,就会抛出异常。 我们用assert命令重写一下上面的transferOwner函数:function transferOwner3(uint256 tokenId, address newOwner) public { assert(_owners[tokenId] == msg.sender); _owners[tokenId] = newOwner; } 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); } 4、三种方法的gas比较我们比较一下三种抛出异常的gas消耗,方法很简单,部署合约,分别运行写的transferOwner函数的三个版本。error方法gas消耗:24445require方法gas消耗:24743assert方法gas消耗:24446我们可以看到,error方法gas cost最少,其次是assert,require方法消耗gas最多。因此,error是最佳的,既可以告知用户抛出异常的原因,又能省gas。5、统计产生异常的部分原因下列情况将会产生一个 assert 式异常:如果你访问数组的索引太大或为负数(例如 x[i] 其中 i >= x.length 或 i < 0)。如果你访问固定长度 bytesN 的索引太大或为负数。如果你用零当除数做除法或模运算(例如 5 / 0 或 23 % 0 )。如果你移位负数位。如果你将一个太大或负数值转换为一个枚举类型。如果你调用内部函数类型的零初始化变量。如果你调用 assert 的参数(表达式)最终结算为 false。下列情况将会产生一个 require 式异常:调用 throw 。如果你调用 require 的参数(表达式)最终结算为 false 。如果你通过消息调用调用某个函数,但该函数没有正确结束(它耗尽了 gas,没有匹配函数,或者本身抛出一个异常),上述函数不包括低级别的操作 call , send , delegatecall 或者 callcode 。低级操作不会抛出异常,而通过返回 false 来指示失败。如果你使用 new 关键字创建合约,但合约没有正确创建(请参阅上条有关”未正确完成“的定义)。如果你对不包含代码的合约执行外部函数调用。如果你的合约通过一个没有 payable 修饰符的公有函数(包括构造函数和 fallback 函数)接收 Ether。如果你的合约通过公有 getter 函数接收 Ether 。如果 .transfer() 失败。总结这节学习了处理异常的3种方法,revert(error)、require 和 assert,其中 error 最合适,既能省gas,又可以知道抛出的异常。 Code: https://github.com/Luca-Hsu/SuperSolidity Reference: https://solidity-cn.readthedocs.io/zh/develop/control-structures.html#assert-require-revert-and-exceptions https://mirror.xyz/ninjak.eth/XhhLu7PV1cAhOp9_m-dk9OoTj7offC7DkYYgsV3e31I https://solidity-by-example.org/error ## Publication Information - [Novar](https://paragraph.com/@novar/): Publication homepage - [All Posts](https://paragraph.com/@novar/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@novar): Subscribe to updates