{ 跳至内容 }

Solidity 0.6.x 功能:try/catch 语句

由 Elena Gesheva 发表 于 2020 年 1 月 29 日

解释器

这篇文章最初发表在以太坊博客.

0.6.0 中引入的 try/catch 语法 可以说是 Solidity 中错误处理能力的重大飞跃,因为自revertrequire 在 v0.4.22 中发布以来,reason 字符串就一直存在。 trycatch 都是 自 v0.5.9 以来保留的关键字,现在我们可以使用它们来处理 external 函数调用中的失败,而不会回滚整个事务(被调用函数中的状态更改仍然会被回滚,但调用函数中的状态更改则不会)。

我们正在从事务生命周期中纯粹的“全有或全无”方法中迈出一步,这种方法无法满足我们经常需要的实际行为。

处理外部调用失败

try/catch 语句允许您对失败的 external 调用和 合约创建 调用做出反应,因此您不能将其用于 internal 函数调用。请注意,要使用 try/catch 包装同一合约中公共函数调用,可以通过 this. 调用函数将其设为外部函数。

下面的示例演示了如何在工厂模式中使用 try/catch,其中合约创建可能会失败。以下 CharitySplitter 合约在其构造函数中需要一个强制性地址属性 _owner

    pragma solidity ^0.6.1;

    contract CharitySplitter {
        address public owner;
        constructor (address _owner) public {
            require(_owner != address(0), "no-owner-provided");
            owner = _owner;
        }
    }

有一个工厂合约 - CharitySplitterFactory,用于创建和管理 CharitySplitter 的实例。在工厂中,我们可以将 new CharitySplitter(charityOwner) 包装在 try/catch 中,作为当构造函数由于传递的空 charityOwner 而可能失败时的保险措施。

    pragma solidity ^0.6.1;
    import "./CharitySplitter.sol";
    contract CharitySplitterFactory {
        mapping (address => CharitySplitter) public charitySplitters;
        uint public errorCount;
        event ErrorHandled(string reason);
        event ErrorNotHandled(bytes reason);
        function createCharitySplitter(address charityOwner) public {
            try new CharitySplitter(charityOwner)
                returns (CharitySplitter newCharitySplitter)
            {
                charitySplitters[msg.sender] = newCharitySplitter;
            } catch {
                errorCount++;
            }
        }
    }

请注意,使用 try/catch,只有在外部调用本身内部发生的异常会被捕获。表达式内部的错误不会被捕获,例如,如果 new CharitySplitter 的输入参数本身是内部调用的部分,它引发的任何错误都不会被捕获。演示此行为的示例是修改后的 createCharitySplitter 函数。这里 CharitySplitter 构造函数的输入参数是从另一个函数 - getCharityOwner 动态获取的。如果该函数回滚,在本示例中,使用 "revert-required-for-testing",则不会在 try/catch 语句中捕获它。

    function createCharitySplitter(address _charityOwner) public {
        try new CharitySplitter(getCharityOwner(_charityOwner, false))
            returns (CharitySplitter newCharitySplitter)
        {
            charitySplitters[msg.sender] = newCharitySplitter;
        } catch (bytes memory reason) {
            ...
        }
    }
    function getCharityOwner(address _charityOwner, bool _toPass)
            internal returns (address) {
        require(_toPass, "revert-required-for-testing");
        return _charityOwner;
    }

检索错误消息

我们可以在 createCharitySplitter 函数中进一步扩展 try/catch 逻辑,以便在失败的 revertrequire 发出错误消息时检索该消息,并在事件中发出它。有两种方法可以实现这一点

1. 使用 catch Error(string memory reason)

    function createCharitySplitter(address _charityOwner) public {
        try new CharitySplitter(_charityOwner) returns (CharitySplitter newCharitySplitter)
        {
            charitySplitters[msg.sender] = newCharitySplitter;
        }
        catch Error(string memory reason)
        {
            errorCount++;
            CharitySplitter newCharitySplitter = new
                CharitySplitter(msg.sender);
            charitySplitters[msg.sender] = newCharitySplitter;
            // Emitting the error in event
            emit ErrorHandled(reason);
        }
        catch
        {
            errorCount++;
        }
    }

在构造函数需要错误失败时发出以下事件

CharitySplitterFactory.ErrorHandled(
    reason: 'no-owner-provided' (type: string)
)

2. 使用 catch (bytes memory reason)

    function createCharitySplitter(address charityOwner) public {
        try new CharitySplitter(charityOwner)
            returns (CharitySplitter newCharitySplitter)
        {
            charitySplitters[msg.sender] = newCharitySplitter;
        }
        catch (bytes memory reason) {
            errorCount++;
            emit ErrorNotHandled(reason);
        }
    }

在构造函数需要错误失败时发出以下事件

CharitySplitterFactory.ErrorNotHandled(
  reason: hex'08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000116e6f2d6f776e65722d70726f7669646564000000000000000000000000000000' (type: bytes)

上面两种检索错误字符串的方法会产生类似的结果。区别在于第二种方法不会 ABI 解码错误字符串。第二种方法的优势在于它也会在 ABI 解码错误字符串失败或没有提供原因时执行。

未来计划

计划发布对错误类型的支持,这意味着我们将能够以类似于事件的方式声明错误,允许我们捕获不同类型的错误,例如

    catch CustomErrorA(uint data1) {}
    catch CustomErrorB(uint[] memory data2) {}
    catch {}

上一篇文章

下一篇文章

参与其中

GitHub

Twitter

Mastodon

Matrix

了解更多

博客文档用例贡献关于论坛

2024Solidity 团队

安全策略

行为准则