Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TransactionReceipt.dev_revert_msg #860

Merged
merged 4 commits into from
Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions brownie/network/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def __init__(
self._events: Optional[EventDict] = None
self._return_value: Any = None
self._revert_msg: Optional[str] = None
self._dev_revert_msg: Optional[str] = None
self._modified_state: Optional[bool] = None
self._new_contracts: Optional[List] = None
self._internal_transfers: Optional[List[Dict]] = None
Expand All @@ -182,6 +183,8 @@ def __init__(
self._revert_msg, self._revert_pc, revert_type = revert_data or (None, None, None)
if self._revert_msg is None and revert_type not in ("revert", "invalid_opcode"):
self._revert_msg = revert_type
if self._revert_pc is not None:
self._dev_revert_msg = build._get_dev_revert(self._revert_pc) or None

self._await_transaction(required_confs, is_blocking)

Expand Down Expand Up @@ -240,6 +243,15 @@ def revert_msg(self) -> Optional[str]:
self._get_trace()
return self._revert_msg

@trace_property
def dev_revert_msg(self) -> Optional[str]:
if self.status:
return None
if self._dev_revert_msg is None:
self._get_trace()

return self._dev_revert_msg

@trace_property
def subcalls(self) -> Optional[List]:
if self._subcalls is None:
Expand Down Expand Up @@ -597,14 +609,20 @@ def _reverted_trace(self, trace: Sequence) -> None:
# get returned error string from stack
data = _get_memory(step, -1)[4:]
self._revert_msg = decode_abi(["string"], data)[0]
return
if self.contract_address:

elif self.contract_address:
self._revert_msg = "invalid opcode" if step["op"] == "INVALID" else ""
return

# check for dev revert string using program counter
self._revert_msg = build._get_dev_revert(step["pc"])
dev_revert = build._get_dev_revert(step["pc"])
if dev_revert is not None:
self._dev_revert_msg = dev_revert
if self._revert_msg is None:
self._revert_msg = dev_revert
if self._revert_msg is not None:
return

# if none is found, expand the trace and get it from the pcMap
self._expand_trace()
try:
Expand All @@ -614,7 +632,9 @@ def _reverted_trace(self, trace: Sequence) -> None:
i = trace.index(step) - 4
if trace[i]["pc"] != step["pc"] - 4:
step = trace[i]
self._revert_msg = pc_map[step["pc"]]["dev"]
self._dev_revert_msg = pc_map[step["pc"]]["dev"]
if self._revert_msg is None:
self._revert_msg = self._dev_revert_msg
return
except (KeyError, AttributeError, TypeError):
pass
Expand Down
12 changes: 12 additions & 0 deletions docs/api-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2132,6 +2132,18 @@ TransactionReceipt Attributes
>>> tx.contract_name
Token


.. py:attribute:: TransactionReceipt.dev_revert_msg

The :ref:`developer revert comment<dev-revert>` returned when a transaction causes the EVM to revert, if any.

.. code-block:: python

>>> tx
<Transaction object '0xd9e0fb1bd6532f6aec972fc8aef806a8d8b894349cf5c82c487335625db8d0ef'>
>>> tx.dev_revert_msg
'dev: is four'

.. py:attribute:: TransactionReceipt.events

An :func:`EventDict <brownie.network.event.EventDict>` of decoded event logs for this transaction.
Expand Down
15 changes: 15 additions & 0 deletions docs/tests-pytest-intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,21 @@ If the above function is executed in the console:
>>> tx.revert_msg
'dev: is three'

When there is an error string included in the code, you can still access the dev revert reason via :func:`TransactionReceipt.dev_revert_msg <TransactionReceipt.dev_revert_msg>`:

.. code-block:: python

>>> tx = test.revertExamples(4)
Transaction sent: 0xd9e0fb1bd6532f6aec972fc8aef806a8d8b894349cf5c82c487335625db8d0ef
test.revertExamples confirmed (cannot be four) - block: 3 gas used: 31337 (6.66%)
<Transaction object '0xd9e0fb1bd6532f6aec972fc8aef806a8d8b894349cf5c82c487335625db8d0ef'>

>>> tx.revert_msg
'cannot be four'

>>> tx.dev_revert_msg
'dev: is four'

Parametrizing Tests
===================

Expand Down
11 changes: 11 additions & 0 deletions tests/network/transaction/test_revert_msg.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,40 @@ def test_revert_msg_via_jump(ext_tester, console_mode):
def test_solidity_revert_msg(evmtester, console_mode):
tx = evmtester.revertStrings(0)
assert tx.revert_msg == "zero"
assert tx.dev_revert_msg is None
tx = evmtester.revertStrings(1)
assert tx.revert_msg == "dev: one"
assert tx.dev_revert_msg == "dev: one"
tx = evmtester.revertStrings(2)
assert tx.revert_msg == "two"
assert tx.dev_revert_msg == "dev: error"
tx = evmtester.revertStrings(3)
assert tx.revert_msg == ""
assert tx.dev_revert_msg is None
tx = evmtester.revertStrings(31337)
assert tx.revert_msg == "dev: great job"
assert tx.dev_revert_msg == "dev: great job"


def test_vyper_revert_msg(vypertester, console_mode):
tx = vypertester.revertStrings(0)
assert tx.revert_msg == "zero"
assert tx.dev_revert_msg is None
tx = vypertester.revertStrings(1)
assert tx.revert_msg == "dev: one"
assert tx.dev_revert_msg == "dev: one"
tx = vypertester.revertStrings(2)
assert tx.revert_msg == "two"
assert tx.dev_revert_msg == "dev: error"
tx = vypertester.revertStrings(3)
assert tx.revert_msg == ""
assert tx.dev_revert_msg is None
tx = vypertester.revertStrings(4)
assert tx.revert_msg == "dev: such modifiable, wow"
assert tx.dev_revert_msg == "dev: such modifiable, wow"
tx = vypertester.revertStrings(31337)
assert tx.revert_msg == "awesome show"
assert tx.dev_revert_msg == "dev: great job"


def test_nonpayable(tester, evmtester, console_mode):
Expand Down