Skip to content

Commit

Permalink
perf: most performant .return_value calculation (#2225)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Aug 21, 2024
1 parent 7308609 commit 04934de
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/ape/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def __init__(
contract_address: Optional["AddressType"] = None,
source_traceback: _SOURCE_TRACEBACK_ARG = None,
project: Optional["ProjectManager"] = None,
set_ape_traceback: bool = False, # Overriden in ContractLogicError
set_ape_traceback: bool = False, # Overridden in ContractLogicError
):
message = message or (str(base_err) if base_err else self.DEFAULT_MESSAGE)
self.message = message
Expand Down
1 change: 0 additions & 1 deletion src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1254,7 +1254,6 @@ def _enrich_returndata(self, call: dict, method_abi: MethodABI, **kwargs) -> dic
default_return_value = "<?>"
returndata = call.get("returndata", "")
is_hexstr = isinstance(returndata, str) and is_0x_prefixed(returndata)
return_value_bytes = None

# Check if return is only a revert string.
call = self._enrich_revert_message(call)
Expand Down
25 changes: 18 additions & 7 deletions src/ape_ethereum/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,21 @@ def return_value(self) -> Any:
return self._return_value_from_enriched_calltree

elif abi := self.root_method_abi:
return_data = self._return_data_from_trace_frames
if return_data is not None:
try:
return self._ecosystem.decode_returndata(abi, return_data)
except Exception as err:
logger.debug(f"Failed decoding return data from trace frames. Error: {err}")
# Use enrichment method. It is slow but it'll at least work.
if self.call_trace_approach is TraceApproach.GETH_STRUCT_LOG_PARSE:
return_data = self._return_data_from_trace_frames
if return_data is not None:
try:
return self._ecosystem.decode_returndata(abi, return_data)
except Exception as err:
logger.debug(f"Failed decoding return data from trace frames. Error: {err}")
# Use enrichment method. It is slow but it'll at least work.

else:
# Barely enrich a calltree for performance reasons
# (likely not a need to enrich the whole thing).
calltree = self.get_raw_calltree()
enriched_calltree = self._ecosystem._enrich_returndata(calltree, abi)
return self._get_return_value_from_calltree(enriched_calltree)

return self._return_value_from_enriched_calltree

Expand All @@ -244,6 +252,9 @@ def _return_value_from_enriched_calltree(self) -> Any:
if "return_value" in self.__dict__:
return self.__dict__["return_value"]

return self._get_return_value_from_calltree(calltree)

def _get_return_value_from_calltree(self, calltree: dict) -> Any:
# If enriching too much, Ethereum places regular values in a key
# named "unenriched_return_values".
if "unenriched_return_values" in calltree:
Expand Down
15 changes: 13 additions & 2 deletions tests/functional/geth/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,13 @@ def test_call_trace_supports_debug_trace_call(geth_contract, geth_account):


@geth_process_test
def test_return_value(geth_contract, geth_account):
receipt = geth_contract.getFilledArray.transact(sender=geth_account)
def test_return_value(benchmark, geth_contract, geth_account):
receipt = benchmark.pedantic(
geth_contract.getFilledArray.transact,
kwargs={"sender": geth_account},
rounds=5,
warmup_rounds=1,
)
trace = receipt.trace
expected = [1, 2, 3] # Hardcoded in contract
assert receipt.return_value == expected
Expand All @@ -324,3 +329,9 @@ def test_return_value(geth_contract, geth_account):
# NOTE: This is very important from a performance perspective!
# (VERY IMPORTANT). We shouldn't need to enrich anything.
assert trace._enriched_calltree is None

# Seeing 0.14.
# Before https://github.com/ApeWorX/ape/pull/2225, was seeing 0.17.
# In CI, can see up to 0.4 though.
avg = benchmark.stats["mean"]
assert avg < 0.6

0 comments on commit 04934de

Please sign in to comment.