恢复交易的布朗尼测试不适用于 pytest.raises() 或 brownie.reverts()

Brownie testing for reverted transactions does not work with pytest.raises() or brownie.reverts()

问题描述: 布朗尼测试包含

pytest.raises(exceptions.VirtualMachineError)

brownie.reverts()

当设计请求失败(被还原)时产生错误。

在 GitHub: https://github.com/n4n0b1t3/brownie.reverts.example

预期行为: 测试应该失败或成功,具体取决于 t运行saction.

的结果

明显: 当以某种方式设置测试时,t运行saction 不会被还原,测试会正常工作而不会产生错误。

目前我尝试过的: 我减少了代码以删除所有不必要的依赖项以仅包含有问题的部分。在 python 虚拟环境的全新设置后,只有必要的功能(在下面的设置中描述),我再次 运行 代码,以确保问题普遍存在并且没有得到解决。

projectroot/sourcecode/contracts/ExampleContract.sol

的源代码
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.6 <0.9.0;

contract ExampleContract {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "only owner");
        _;
    }

    function withdraw() public payable onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }
}

图表 A projectroot/sourcecode/test/test_reverts.py

的源代码
import brownie
from brownie import ExampleContract, accounts, exceptions


def test_only_owner_can_withdraw():
    example_contract = ExampleContract.deploy({"from": accounts[0]})
    bad_account = accounts.add()
    with brownie.reverts():
        example_contract.withdraw({"from": bad_account})

图表 B 替代使用pytest.raises()

import brownie
import pytest
from brownie import ExampleContract, accounts, exceptions

def test_only_owner_can_withdraw():
    example_contract = ExampleContract.deploy({"from": accounts[0]})
    bad_account = accounts.add()
    with pytest.raises(exceptions.VirtualMachineError):
        example_contract.withdraw({"from": bad_account})

图表 C 不恢复 t运行saction 的替代方案,它可以正常工作:

import brownie
from brownie import ExampleContract, accounts, exceptions

def test_only_owner_can_withdraw():
    example_contract = ExampleContract.deploy({"from": accounts[0]})
    bad_account = accounts.add()
    with brownie.reverts():
        example_contract.withdraw({"from": accounts[0]})

结果:

>raise AssertionError("Transaction did not revert")
E AssertionError: Transaction did not revert

展品 A 和 B 的完整终端输出

(.venv) testcode>brownie test
INFORMATION: Es konnten keine Dateien mit dem angegebenen
Muster gefunden werden.
Brownie v1.16.4 - Python development framework for Ethereum

======================================================================================================= test session starts =======================================================================================================
platform win32 -- Python 3.10.2, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: C:\Users\Zarathustra\blockchainProjects\testing.revert\testcode
plugins: eth-brownie-1.16.4, hypothesis-6.21.6, forked-1.3.0, xdist-1.34.0, web3-5.23.1
collected 1 item

Launching 'ganache-cli.cmd --accounts 10 --hardfork istanbul --gasLimit 12000000 --mnemonic brownie --port 8545'...

tests\test_reverts.py F                                                                                                                                                                                                      [100%]

============================================================================================================ FAILURES =============================================================================================================
__________________________________________________________________________________________________ test_only_owner_can_withdraw ___________________________________________________________________________________________________

    def test_only_owner_can_withdraw():
        fund_me = ExampleContract.deploy({"from": accounts[0]})
        print(f"FundMe deployed to {fund_me.address}")
        good = accounts[0]
        bad = accounts.add()
        # with pytest.raises(exceptions.VirtualMachineError):
>       with brownie.reverts():

tests\test_reverts.py:12:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests\test_reverts.py:13: in test_only_owner_can_withdraw
    fund_me.withdraw({"from": bad})
..\.venv\lib\site-packages\brownie\network\contract.py:1693: in __call__
    return self.transact(*args)
..\.venv\lib\site-packages\brownie\network\contract.py:1566: in transact
    return tx["from"].transfer(
..\.venv\lib\site-packages\brownie\network\account.py:680: in transfer
    receipt._raise_if_reverted(exc)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <Transaction '0x1f310675db8ad41f0eb4cd9338f35f2800174168b18c019645b5b1d82ade2826'>, exc = None

    def _raise_if_reverted(self, exc: Any) -> None:
        if self.status or CONFIG.mode == "console":
            return
        if not web3.supports_traces:
            # if traces are not available, do not attempt to determine the revert reason
            raise exc or ValueError("Execution reverted")

        if self._dev_revert_msg is None:
            # no revert message and unable to check dev string - have to get trace
            self._expand_trace()
        if self.contract_address:
            source = ""
        elif CONFIG.argv["revert"]:
            source = self._traceback_string()
        else:
            source = self._error_string(1)
            contract = state._find_contract(self.receiver)
            if contract:
                marker = "//" if contract._build["language"] == "Solidity" else "#"
                line = self._traceback_string().split("\n")[-1]
                if marker + " dev: " in line:
                    self._dev_revert_msg = line[line.index(marker) + len(marker) : -5].strip()

>       raise exc._with_attr(
            source=source, revert_msg=self._revert_msg, dev_revert_msg=self._dev_revert_msg
        )
E       AttributeError: 'NoneType' object has no attribute '_with_attr'

..\.venv\lib\site-packages\brownie\network\transaction.py:420: AttributeError
------------------------------------------------------------------------------------------------------ Captured stdout call ------------------------------------------------------------------------------------------------------- 
FundMe deployed to 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87
mnemonic: 'abandon wisdom slot exclude buyer raccoon desert grid inmate flag state castle'
===================================================================================================== short test summary info ===================================================================================================== 
FAILED tests/test_reverts.py::test_only_owner_can_withdraw - AttributeError: 'NoneType' object has no attribute '_with_attr'
======================================================================================================== 1 failed in 6.40s ======================================================================================================== 
Terminating local RPC client...
(.venv) testcode>

我的设置: 根目录包含 .venv 虚拟 python 环境。 在 venv 下安装了 Cython 和 Brownie。在 VSC 中,我使用的是最新的 solidity 编译器。 为了安装 ganache,我打开了一个具有管理员权限的 powershell 并激活了虚拟环境,安装了 nodeenv,并将其与 venv 连接。在这一步之后,我安装了 ganache 并对其进行了测试。一切正常。除了使用 brownie.reverts().

进行的测试外,完整的项目都经过测试并在此设置中正常工作

命令行概述如下:

projectroot>.\.venv\Scripts\Activate.ps1
(.venv) projectroot>pip install nodeenv
(.venv) projectroot>nodeenv -p
(.venv) projectroot>npm install -g ganache
(.venv) projectroot>ganache --version
(.venv) projectroot>mkdir sourcecode
(.venv) projectroot>cd sourcecode
(.venv) sourcecode>brownie init
(.venv) sourcecode>brownie --version

这已在 Brownie v1.18.1 中修复。但是,您需要安装 Python 3.9.10 才能获得最新的 brownie。 要使其在虚拟环境中工作,您不能使用 venv。 这是一个适合我的过程:

  • 在你的标准 Python 版本上安装 virtualenv
  • 下载 python 3.9.10 并在不“添加到路径”的情况下将其安装到专用目录中,例如$home/pythonversions
  • 在你的项目目录中创建一个像这样的虚拟环境

python -m virtualenv -p="<path to the python executable >" <virtual_environment_directory>

  • 启动您的虚拟环境,例如 首页>..venv\Scripts\activate.ps1

  • 使用 python --version

    测试您的 python 版本是否是所需版本
  • 现在安装 Cython 以避免另一个错误。

  • 安装nodeenv以安装ganage

  • 使用 nodeenv -p 激活(对于此步骤,您需要具有管理员权限的 PowerShell)

  • 使用 npm 安装 ganache

  • 安装 eth-brownie 用 pip 检查你是否有最新版本

brownie --version