我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
WTF技术社群discord,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity
try-catch
是现代编程语言几乎都有的处理异常的一种标准方式,solidity
0.6版本也添加了它。这一讲,我们将介绍如何利用try-catch
处理智能合约中的异常。
try-catch
在solidity
中,try-catch
只能被用于external
函数或创建合约时constructor
(被视为external
函数)的调用。基本语法如下:
try externalContract.f() {
// call成功的情况下 运行一些代码
} catch {
// call失败的情况下 运行一些代码
}
其中externalContract.f()
时某个外部合约的函数调用,try
模块在调用成功的情况下运行,而catch
模块则在调用失败时运行。
如果调用的函数有返回值,那么必须在try
之后声明returns(returnType val)
,并且在try
模块中可以使用返回的变量;如果时创建合约,那么返回值是新创建的合约变量。
try externalContract.f() returns(returnType val){
// call成功的情况下 运行一些代码
} catch {
// call失败的情况下 运行一些代码
}
另外,catch
模块支持捕获特殊的异常原因:
try externalContract.f() returns(returnType){
// call成功的情况下 运行一些代码
} catch Error(string memory reason) {
// 捕获失败的 revert() 和 require()
} catch (bytes memory reason) {
// 捕获失败的 assert()
}
try-catch
实战OnlyEven
我们创建一个外部合约OnlyEven
,并使用try-catch
来处理异常:
contract OnlyEven{
constructor(uint a){
require(a != 0, "invalid number");
assert(a != 1);
}
function onlyEven(uint256 b) external pure returns(bool success){
// 输入奇数时revert
require(b % 2 == 0, "Ups! Reverting");
success = true;
}
}
OnlyEven
合约包含一个构造函数和一个onlyEven
函数。
a
,当a=0
时,require
会抛出异常;当a=1
时,assert
会抛出异常;其他情况均正常。onlyEven
函数有一个参数b
,当b
为奇数时,require
会抛出异常。首先,在TryCatch
合约中定义一些事件和状态变量:
// 成功event
event SuccessEvent();
// 失败event
event CatchEvent(string message);
event CatchByte(bytes data);
// 声明OnlyEven合约变量
OnlyEven even;
constructor() {
even = new OnlyEven(2);
}
SuccessEvent
是调用成功会释放的事件,而CatchEvent
和CatchByte
是抛出异常时会释放的事件,分别对应require/revert
和assert
异常的情况。even
是个OnlyEven
合约类型的状态变量。
然后我们在execute
函数中使用try-catch
处理调用外部函数onlyEven
中的异常:
// 在external call中使用try-catch
function execute(uint amount) external returns (bool success) {
try even.onlyEven(amount) returns(bool _success){
// call成功的情况下
emit SuccessEvent();
return _success;
} catch Error(string memory reason){
// call不成功的情况下
emit CatchEvent(reason);
}
}
当运行execute(0)
的时候,因为0
为偶数,没有异常抛出,调用成功并释放SuccessEvent
事件;当运行execute(1)
的时候,因为1
为偶数,异常抛出,调用失败并释放CatchEvent
事件。
这里,我们利用try-catch
来处理合约创建时的异常。只需要把try
模块改写为OnlyEven
合约的创建就行:
// 在创建新合约中使用try-catch (合约创建被视为external call)
// executeNew(0)会失败并释放`CatchEvent`
// executeNew(1)会失败并释放`CatchByte`
// executeNew(2)会成功并释放`SuccessEvent`
function executeNew(uint a) external returns (bool success) {
try new OnlyEven(a) returns(OnlyEven _even){
// call成功的情况下
emit SuccessEvent();
success = _even.onlyEven(a);
} catch Error(string memory reason) {
// catch失败的 revert() 和 require()
emit CatchEvent(reason);
} catch (bytes memory reason) {
// catch失败的 assert()
emit CatchByte(reason);
}
}
大家可以运行一下executeNew(0)
,executeNew(1)
,executeNew(2)
,看看会有什么不同。
在这一讲,我们介绍了如何使用try-catch
来处理智能合约运行中的异常:
try
执行成功,返回变量必须声明,并且与返回的变量类型相同。