深入理解 Solidity 错误 #4 - try/catch

  • Tiny熊
  • 更新于 2023-08-04 23:03
  • 阅读 2450

Solidity 的try/catch 语法和常见的语言中的表现不一样,try { } 块中的代码错误是无法被catch 的,这一点要小心要非常小心。

本文是"深入理解 Solidity 错误"系列的第4篇, 在了解了运行时处理错误的方法后,我们将探讨 Solidity 中一种特殊的错误处理方式:try {} catch {}

本文讲介绍如何捕获外部调用的错误,以及在创建新合约时如何使用它。还将了解不同的 catch子句的用法,他们对应着 Solidity 运行时的那些错误类型

Try Catch 简介

Solidity 支持使用 try / catch 进行异常处理,但仅限于外部函数调用和合约创建调用。

try catch语法从版本0.6.2开始可用。它是作为对低级调用的响应而添加的,许多开发者已经在使用。

在此案例中,调用方(进行调用的智能合约)可以对被调用方(被调用的智能合约)出现的故障和错误做出反应,并使用 try / catch 语法对这些故障做出反应。

Solidity 中如何使用 try/catch

try关键字后必须跟一个表达式,该表达式是:

  • 外部函数调用
  • 合约创建 → new ContractName()
// SPDX-License-Identifier: GPL-3.0  
pragma solidity ^ 0.8 .0;

contract TryCatchExamples {

  function tryCatchExternalCall(address target) public {

    try Target(target).doSomething() returns(string memory) {

    } catch {

    }

  }

  function tryDeployingContract() public {

    try new Target() returns(Target) {

    } catch {

    }

  }

}

contract Target {

  uint256 _callCounter;

  function doSomething() public returns(string memory) {
    _callCounter++;
    return "state updated successfully";
  }
}

同样重要的是,在 trycatch 子句中定义的任何局部变量在这些子句之外都是不可访问的。它们的作用域仅限于定义它们的成功或错误块内。

function tryCatchExternalCall(address target) public {
    try Target(target).doSomething() returns (string memory) {
            uint256 a = 1;
        } catch (bytes memory) {
            uint256 b = 2;
            // Does not compile
            // DeclarationError: Undeclared identifier
            uint256 c = a;
        }
        // Does not compile
        // DeclarationError: Undeclared identifier
        uint256 d = a;
        uint256 e = b;
    }
}

try/catch 可选的返回值

对于外部调用, try 语句的 return 返回值部分是可选的,即使你尝试调用的外部函数有返回值也是如此。请参阅下面的示例:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract TryCatchExamples {

    function tryCatchExternalCall(address target) public {

        try Target(target).doSomething() {

            // if external call was successful, we continue execution here
            // we do not care of what was returned.

        } catch {

        }

    }

}

contract Target {

    uint256 _callCounter;

    function doSomething() public returns (string memory) {
        _callCounter++;
        return "state updated successfully";
    }
}

如果声明了 returns ,它就声明了外部调用返回的内容(类型)。通常会声明一个返回值变量名,以便在 try { ... } 块中处理返回值。

try Target(target).doSomething() returns (string memory message) {
    // do something with `message` returned variable
} catch {

}

请注意, returns内声明的类型必须与函数定义中定义的返回类型一致。举例说明,下面的代码片段将无法编译, 并返回以下错误:

// TypeError: Invalid type, expected string memory but got bytes memory.
try Target(target).doSomething() returns (bytes memory message)

对于通过 try / catch 创建合约,有两种使用 returns 部分的方法。

选项 1:不定义 returns 部分

在此案例中,我们只关心合约是否创建成功。如果部署成功,我们就可以在 try 块中做任何我们想做的事情。

function tryDeployingContract() public {
    try new Target() {
        // a new contract was created successfully.
        // we do not care about the address of the new contrat created
        // we can do whatever we want in the success block.
    } catch (bytes memory) {

    }
}

选项 2:定义一个 returns 部分

如果想获取新创建合约的地址等信息,可以通过定义下面的 returns 语句来实现:

try new <Contract> returns (<Contract>)

在使用 new 关键字创建合约时,在 try 代码块中定义一个 returns 部分,可以获取新创建合约的实例。returns 部分中的类型必须是我们要部署的合约的类型,见下面的示例:


try new Target() returns (Target newContract) {...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Tiny熊
Tiny熊
0xD682...E8AB
登链社区发起人 通过区块链技术让世界变得更好而尽一份力。