From 6b4e08c5d43d5004bf23f8f82dc386f863dc5663 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Wed, 21 Jul 2021 21:18:48 +0000 Subject: [PATCH 1/2] feat: support for vyper 0.2.14 optimized reverts --- brownie/project/compiler/vyper.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/brownie/project/compiler/vyper.py b/brownie/project/compiler/vyper.py index c169b7980..63532a77a 100644 --- a/brownie/project/compiler/vyper.py +++ b/brownie/project/compiler/vyper.py @@ -278,6 +278,10 @@ def _get_dependencies(ast_json: List) -> List: return sorted(set([i["name"].split(".")[-1] for i in import_nodes])) +def _is_revert_jump(pc_list: List, revert_pc: int) -> bool: + return pc_list[-1]["op"] == "JUMPI" and int(pc_list[-2].get("value", "0"), 16) == revert_pc + + def _generate_coverage_data( source_map_str: str, opcodes_str: str, contract_name: str, ast_json: List ) -> Tuple: @@ -297,6 +301,14 @@ def _generate_coverage_data( pc_list: List = [] count, pc = 0, 0 + revert_pc = -1 + if opcodes[-5] == "JUMPDEST" and opcodes[-1] == "REVERT": + # starting in vyper 0.2.14, reverts without a reason string are optimized with a jump + # to the end of the bytecode. if the bytecode ends with this pattern, we set `revert_pc` + # as the program counter of the jumpdest so we can identify these optimizer reverts + # within traces. + revert_pc = len(opcodes) - 5 + while opcodes: # format of source is [start, stop, contract_id, jump code] source = source_map.popleft() @@ -316,6 +328,10 @@ def _generate_coverage_data( len(pc_list) > 6 and pc_list[-7]["op"] == "CALLVALUE" and pc_list[-1]["op"] == "REVERT" + ) or ( + len(pc_list) > 2 + and pc_list[-3]["op"] == "CALLVALUE" + and _is_revert_jump(pc_list[-2:], revert_pc) ): # special case - initial nonpayable check on vyper >=0.2.5 pc_list[-1]["dev"] = "Cannot send ether to nonpayable function" @@ -345,7 +361,7 @@ def _generate_coverage_data( continue node = _find_node_by_offset(ast_json, offset) - if pc_list[-1]["op"] == "REVERT": + if pc_list[-1]["op"] == "REVERT" or _is_revert_jump(pc_list[-2:], revert_pc): # custom revert error strings if node["ast_type"] == "FunctionDef" and pc_list[-7]["op"] == "CALLVALUE": pc_list[-1]["dev"] = "Cannot send ether to nonpayable function" @@ -377,6 +393,8 @@ def _generate_coverage_data( pc_list[0]["path"] = "0" pc_list[0]["offset"] = [0, _convert_src(ast_json[-1]["src"])[1]] + if revert_pc != -1: + pc_list[-1]["optimizer_revert"] = True pc_map = dict((i.pop("pc"), i) for i in pc_list) From 2b7b627f231bf829b976c306674e3b38a9578f10 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Wed, 21 Jul 2021 21:21:52 +0000 Subject: [PATCH 2/2] test: update vyper test to use 0.2.14 --- tests/network/transaction/test_subcalls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/network/transaction/test_subcalls.py b/tests/network/transaction/test_subcalls.py index f10764cbf..85463298d 100644 --- a/tests/network/transaction/test_subcalls.py +++ b/tests/network/transaction/test_subcalls.py @@ -24,7 +24,7 @@ vyper_source = """ -# @version 0.2.12 +# @version 0.2.14 TARGET: constant(address) = 0xD0660cD418a64a1d44E9214ad8e459324D8157f1 @@ -54,7 +54,7 @@ def solidity_contract(accounts): @pytest.fixture() def vyper_contract(accounts): - container = compile_source(vyper_source, vyper_version="0.2.12").Vyper + container = compile_source(vyper_source, vyper_version="0.2.14").Vyper return container.deploy({"from": accounts[0]})