Skip to content
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ dependencies = [
"eip712>=0.3.0,<0.4",
"eth_pydantic_types>=0.2.1,<0.3",
"evmchains>=0.1.0,<0.2",
"evm-trace>=0.2.6,<0.3",
"evm-trace>=0.3.0,<0.4",
]

[project.scripts]
Expand Down
6 changes: 5 additions & 1 deletion src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1482,7 +1482,11 @@ def _enrich_trace_event(
# Enrich the event-node data using the Ape ContractLog object.
log: ContractLog = contract_logs[0]
calldata = self._enrich_calldata_dict(log.event_arguments)
return {"name": log.event_name, "calldata": calldata}
enriched = {"name": log.event_name, "calldata": calldata}
if event.get("position") is not None:
enriched["position"] = event["position"]

return enriched

def _enrich_revert_message(self, call: dict) -> dict:
returndata = call.get("returndata", "")
Expand Down
18 changes: 13 additions & 5 deletions src/ape_ethereum/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def enriched_calltree(self) -> dict:

# Add top-level data if missing.
if (
not self._enriched_calltree.get("gas_cost")
self._enriched_calltree.get("gas_cost") is None
and (gas_used := self.transaction.get("gas_used"))
and "data" in self.transaction
):
Expand Down Expand Up @@ -666,19 +666,27 @@ def _debug_trace_call(self):

def parse_rich_tree(call: dict, verbose: bool = False) -> Tree:
tree = _create_tree(call, verbose=verbose)
calls = call.get("calls", [])
events_by_position = defaultdict(list)
for event in call.get("events", []):
if "calldata" not in event and "name" not in event:
# Not sure; or not worth showing.
logger.debug(f"Unknown event data: '{event}'.")
continue

event_tree = _create_event_tree(event)
tree.add(event_tree)
# Position counts preceding child calls. Older traces omit it.
position = min(event.get("position") or 0, len(calls))
events_by_position[position].append(event)

for sub_call in call.get("calls", []):
for index, sub_call in enumerate(calls):
for event in events_by_position[index]:
tree.add(_create_event_tree(event))
sub_tree = parse_rich_tree(sub_call, verbose=verbose)
tree.add(sub_tree)

for event in events_by_position[len(calls)]:
tree.add(_create_event_tree(event))

return tree


Expand Down Expand Up @@ -754,7 +762,7 @@ def _call_to_str(call: dict, stylize: bool = False, verbose: bool = False) -> st

signature += f" {value}"

if call.get("gas_cost"):
if call.get("gas_cost") is not None:
gas_value = f"[{call['gas_cost']} gas]"
if stylize:
gas_value = f"[{TraceStyles.GAS_COST}]{gas_value}[/]"
Expand Down
9 changes: 8 additions & 1 deletion tests/functional/test_ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,8 @@ def get_calltree(self) -> CallTreeNode:
assert actual["call_type"] == CallType.CALL.value


def test_enrich_trace_handles_events(ethereum, vyper_contract_instance, owner):
@pytest.mark.parametrize("position", (None, 0, 1))
def test_enrich_trace_handles_events(ethereum, vyper_contract_instance, owner, position):
tx = vyper_contract_instance.setNumber(96247783, sender=owner)

# Used Hardhat to get the data.
Expand All @@ -1207,6 +1208,9 @@ def test_enrich_trace_handles_events(ethereum, vyper_contract_instance, owner):
}
]

if position is not None:
events[0]["position"] = position

calldata = "0x3fb5c1cb000000000000000000000000000000000000000000000000000000000000007b"
call = {
"events": events,
Expand Down Expand Up @@ -1235,6 +1239,9 @@ def get_calltree(self) -> CallTreeNode:
},
}
]
if position is not None:
expected[0]["position"] = position

assert events == expected


Expand Down
52 changes: 52 additions & 0 deletions tests/functional/test_trace.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
import re
from io import StringIO

import pytest
from evm_trace import CallTreeNode, CallType
from hexbytes import HexBytes
from rich.console import Console

from ape_ethereum.trace import CallTrace, Trace, TraceApproach, TransactionTrace, parse_rich_tree
from tests.functional.data.python import (
Expand Down Expand Up @@ -100,6 +102,45 @@ def test_parse_rich_tree_handle_bad_event_data():
assert len(tree.children) == 0


@pytest.mark.parametrize("final_position", (2, 99))
def test_parse_rich_tree_event_positions(final_position):
data = {
"method_id": "root",
"events": [
{"name": "Before", "position": 0},
{"name": "Between", "position": 1},
{"name": "AlsoBetween", "position": 1},
{"name": "After", "position": final_position},
{"name": "Legacy"},
{"name": "UnknownPosition", "position": None},
],
"calls": [
{
"method_id": "first",
"calls": [{"method_id": "nested"}],
"events": [{"name": "NestedAfter", "position": 1}],
},
{"method_id": "second"},
],
}
output = StringIO()
Console(file=output, color_system=None).print(parse_rich_tree(data))
lines = [line.strip(" │├└─") for line in output.getvalue().splitlines() if line.strip()]
assert lines == [
"root()",
"log Before()",
"log Legacy()",
"log UnknownPosition()",
"first()",
"nested()",
"log NestedAfter()",
"log Between()",
"log AlsoBetween()",
"second()",
"log After()",
]


def test_get_gas_report(gas_tracker, owner, vyper_contract_instance):
tx = vyper_contract_instance.setNumber(924, sender=owner)
trace = tx.trace
Expand Down Expand Up @@ -343,6 +384,17 @@ def test_enriched_calltree_adds_missing_gas(simple_trace_cls):
assert actual["gas_cost"] == compute_gas


@pytest.mark.parametrize("gas_cost", (0, 42))
def test_enriched_calltree_preserves_known_gas(simple_trace_cls, gas_cost):
trace_cls = simple_trace_cls(
{**PASSING_TRACE, "gas_cost": gas_cost},
tx={"gas_used": 25_000, "data": HexBytes("0x12345678")},
)
trace = trace_cls.model_validate(TRACE_API_DATA)
assert trace.enriched_calltree["gas_cost"] == gas_cost
assert f"[{gas_cost} gas]" in str(trace)


class TestTraceApproach:
@pytest.mark.parametrize(
"key",
Expand Down
Loading