Solidity极简入门: 30. Try Catch

我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。

欢迎关注我的推特:@0xAA_Science

WTF技术社群discord,内有加微信群方法:链接

所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity


try-catch是现代编程语言几乎都有的处理异常的一种标准方式,solidity0.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是调用成功会释放的事件,而CatchEventCatchByte是抛出异常时会释放的事件,分别对应require/revertassert异常的情况。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执行成功,返回变量必须声明,并且与返回的变量类型相同。
Subscribe to 0xAA
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.