disclaimer: this is a duplicate from https://stackoverflow.com/questions/71126128/ I couldn't find out how to move the question, so I copied it here.
Issue description: Brownie tests containing either
pytest.raises(exceptions.VirtualMachineError)
or
brownie.reverts()
producing an error, when the request by design fails (is reverted).
Repository on Github for testing GitHub brownie.reverts() example code
Expected behavior: The test should either fail or succeed, depending on the outcome of the transaction.
Noticeable: When the test is set up in a way, that the transaction is not reverted, the test works correctly without producing an error.
What I have tried so far: I reduced the code to remove all unnecessary dependencies to contain only the problematic parts. After a complete new setup of a python virtual environment with only the necessary features (described below under setup), I ran the code again, to make sure the issue prevails and is not solved.
Source code of 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);
}
}
Exhibit A Source code of 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})
Exhibit B Alternative making use of 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})
Exhibit C Alternative with not reverting transaction, which is correctly working:
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]})
With the result:
>raise AssertionError("Transaction did not revert")
E AssertionError: Transaction did not revert
Complete terminal output for exhibits A and B
(.venv) testcode>brownie test
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():
example_contract = ExampleContract.deploy({"from": accounts[0]})
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
example_contract.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 -------------------------------------------------------------------------------------------------------
ExampleContract 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>
My setting: The root directory contains the .venv virtual python environment. Under venv Cython and Brownie are installed. In VSC I am using the latest solidity compiler. In order to install ganache I opened a powershell with admin rights and activated the virtual environment, installed nodeenv, and connected it with venv. After this step I installed ganache and tested it. Everything is working correctly. Complete projects are tested and work correctly in this setup, besides tests with brownie.reverts().
Here in short the command line overview:
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