Solidity 异常处理
Solidity 是通过回退状态的方式来处理异常错误。
Solidity 发生异常时,会撤消当前调用和所有子调用改变的状态,同时给调用者返回一个错误标识。
Solidity 提供了require 、assert 和 revert 来处理异常。
1. require 示例
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SolidityTest { bool public flag; function setFlag() external { // 判断调用者地址 是否等于当前合约地址,成立则继续运行。 require(msg.sender == address(this)); flag = true; } }
require 函数常常用来检查输入变量或状态变量是否满足条件,以及验证调用外部合约的返回值。
require 可以有返回值,例如:require(condition, 'Something bad happened');。
require 的返回值不宜过长,因为返回信息需要消耗 gas。
2. require、assert、revert 共同点
assert()与require()语句都需要满足括号中的条件,才能进行后续操作,若不满足则抛出错误。
以下三个语句的功能完全相同:
// revert if(msg.sender != owner) { revert(); } // require require(msg.sender == owner); // assert assert(msg.sender == owner);
3. require、assert 不同点
assert(false) 编译为 0xfe,这是一个无效的操作码,所以会消耗掉所有剩余的 gas,并恢复所有的操作。
require(false) 编译为 0xfd,这是revert()的操作码,所以会退还所有剩余的 gas,同时可以返回一个自定义的报错信息。
require 的 gas 消耗要小于 assert,而且可以有返回值,使用更为灵活。
4. require、assert 使用场景
require() 函数用于检测输入变量或状态变量是否满足条件,以及验证调用外部合约的返回值。
require() 语句的失败报错,应该被看作一个正常的判断语句流程不能通过的事件。
assert()语句的失败报错,意味着发生了代码层面的错误事件,很大可能是合约中有一个bug需要修复。
使用 require() 的场景
- 验证用户输入,例如:require(input_var>100)
- 验证外部合约的调用结果,例如:require(external.send(amount))
- 在执行状态更改操作之前验证状态条件,例如:require(block.number > 49999) 或 require(balance[msg.sender]>=amount)
一般来说,使用require()的频率更多,通常应用于函数的开头。
使用 assert() 的场景
- 检查溢出
- 检查不变量
- 更改后验证状态
- 预防永远不会发生的情况
一般来说,使用assert()的频率较少,通常用于函数的结尾。
基本上,require() 应该用于检查条件,而 assert() 只是为了防止发生任何非常糟糕的事情。
5. try...catch
我们在当前合约发起对外部合约调用的话,如果外部合约调用执行失败被 revert,外部合约状态会被回滚,当前合约状态也会被回滚。
但有时候我们并不想这样,要是能够捕获外部合约调用异常,然后根据情况做自己的处理不是更好吗?所以,这种场景下适应于使用 try...catch 语句。
require 示例如下:
pragma solidity ^0.8.0; contract Manager { function count() public pure returns(int){ require(1==2,"require error"); return 2; } function test() public view returns(string memory) { try this.count() { return "success"; } catch Error(string memory reason/* 出错原因 */) { // 调用 count() 失败时执行,通常是不满足 require 语句条件或触发 revert 语句时所引起的调用失败 return reason; } catch (bytes memory) { // 调用 count() 异常时执行,通常是触发 assert 语句或除 0 等比较严重错误时会执行 return "assert error"; } } }
以上代码将会触发 catch Error(string memory reason) ,最终输出 require error。
assert 示例如下:
pragma solidity ^0.8.0; contract Manager { function count() public pure returns(int){ assert(1==2); return 2; } function test() public view returns(string memory) { try this.count() { return "success"; } catch Error(string memory reason/* 出错原因 */) { // 调用 count() 失败时执行,通常是不满足 require 语句条件或触发 revert 语句时所引起的调用失败 return reason; } catch (bytes memory) { // 调用 count() 异常时执行,通常是触发 assert 语句或除 0 等比较严重错误时会执行 return "assert error"; } } }
以上代码将会触发 catch (bytes memory) ,最终输出 assert error。
下一章:Solidity 编程风格
良好统一的编程风格,有助于提高代码的可读性和可维护性。下面是有关Solidity编程风格的几条建议。 1. 代码布局缩进使用4个空格代替制表符作为缩进,避免空格与制表符混用。空2行规则2个合约定义之间空 ...