diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3f5485..2f67290 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: rev: v0.991 hooks: - id: mypy - additional_dependencies: [types-PyYAML, types-requests, types-setuptools] + additional_dependencies: [types-PyYAML, types-requests, types-setuptools, pydantic] - repo: https://github.com/executablebooks/mdformat rev: 0.7.14 diff --git a/README.md b/README.md index 505b482..42e293a 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,10 @@ from web3 import HTTPProvider, Web3 from evm_trace import TraceFrame web3 = Web3(HTTPProvider("https://path.to.my.node")) +txn_hash = "0x..." struct_logs = web3.manager.request_blocking("debug_traceTransaction", [txn_hash]).structLogs for item in struct_logs: - yield TraceFrame(**item) + frame = TraceFrame.parse_obj(item) ``` If you want to get the call-tree node, you can do: diff --git a/evm_trace/geth.py b/evm_trace/geth.py index 00cf707..85c7763 100644 --- a/evm_trace/geth.py +++ b/evm_trace/geth.py @@ -86,14 +86,46 @@ def get_calltree_from_geth_trace( :class:`~evm_trace.base.CallTreeNode`: Call tree of transaction trace. """ - return _create_node_from_call( + return _create_node( trace=trace, show_internal=show_internal, **root_node_kwargs, ) -def _extract_memory(offset: HexBytes, size: HexBytes, memory: List[HexBytes]) -> HexBytes: +def create_call_node_data(frame: TraceFrame) -> Dict: + """ + Parse a CALL-opcode frame into an address and calldata. + + Args: + frame (:class:`~evm_trace.geth.TraceFrame`): The call frame to parse. + + Returns: + Tuple[str, HexBytes]: A tuple of the address str and the calldata. + """ + + data = {"address": frame.stack[-2][-20:], "depth": frame.depth} + if frame.op == CallType.CALL.value: + data["call_type"] = CallType.CALL + data["value"] = int(frame.stack[-3].hex(), 16) + data["calldata"] = extract_memory( + offset=frame.stack[-4], size=frame.stack[-5], memory=frame.memory + ) + elif frame.op == CallType.DELEGATECALL.value: + data["call_type"] = CallType.DELEGATECALL + data["calldata"] = extract_memory( + offset=frame.stack[-3], size=frame.stack[-4], memory=frame.memory + ) + else: + data["call_type"] = CallType.STATICCALL + data["calldata"] = extract_memory( + offset=frame.stack[-3], size=frame.stack[-4], memory=frame.memory + ) + + return data + + +def extract_memory(offset: HexBytes, size: HexBytes, memory: List[HexBytes]) -> HexBytes: """ Extracts memory from the EVM stack. @@ -129,7 +161,7 @@ def _extract_memory(offset: HexBytes, size: HexBytes, memory: List[HexBytes]) -> return HexBytes(return_bytes) -def _create_node_from_call( +def _create_node( trace: Iterator[TraceFrame], show_internal: bool = False, **node_kwargs ) -> CallTreeNode: """ @@ -145,29 +177,9 @@ def _create_node_from_call( if frame.op in [x.value for x in CALL_OPCODES]: # NOTE: Because of the different meanings in structLog style gas values, # gas is not set for nodes created this way. - child_node_kwargs = {"address": frame.stack[-2][-20:], "depth": frame.depth} - - if frame.op == CallType.CALL.value: - child_node_kwargs["call_type"] = CallType.CALL - child_node_kwargs["value"] = int(frame.stack[-3].hex(), 16) - child_node_kwargs["calldata"] = _extract_memory( - offset=frame.stack[-4], size=frame.stack[-5], memory=frame.memory - ) - elif frame.op == CallType.DELEGATECALL.value: - child_node_kwargs["call_type"] = CallType.DELEGATECALL - child_node_kwargs["calldata"] = _extract_memory( - offset=frame.stack[-3], size=frame.stack[-4], memory=frame.memory - ) - else: - child_node_kwargs["call_type"] = CallType.STATICCALL - child_node_kwargs["calldata"] = _extract_memory( - offset=frame.stack[-3], size=frame.stack[-4], memory=frame.memory - ) - - child_node = _create_node_from_call( - trace=trace, show_internal=show_internal, **child_node_kwargs - ) - node.calls.append(child_node) + data = create_call_node_data(frame) + child = _create_node(trace=trace, show_internal=show_internal, **data) + node.calls.append(child) # TODO: Handle internal nodes using JUMP and JUMPI @@ -181,7 +193,7 @@ def _create_node_from_call( break elif frame.op in ("RETURN", "REVERT") and not node.returndata: - node.returndata = _extract_memory( + node.returndata = extract_memory( offset=frame.stack[-1], size=frame.stack[-2], memory=frame.memory ) # TODO: Handle "execution halted" vs. gas limit reached diff --git a/evm_trace/vmtrace.py b/evm_trace/vmtrace.py index 8bb8c78..7553d47 100644 --- a/evm_trace/vmtrace.py +++ b/evm_trace/vmtrace.py @@ -5,7 +5,7 @@ from eth.vm.memory import Memory from eth.vm.stack import Stack from eth_utils import to_checksum_address -from hexbytes import HexBytes +from ethpm_types import HexBytes from msgspec import Struct from msgspec.json import Decoder diff --git a/pyproject.toml b/pyproject.toml index 1d255ac..7418a1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ requires = ["setuptools>=51.1.1", "wheel", "setuptools_scm[toml]>=5.0"] [tool.mypy] exclude = "build/" +plugins = ["pydantic.mypy"] [tool.setuptools_scm] write_to = "evm_trace/version.py" diff --git a/setup.py b/setup.py index 56699d9..8d19311 100644 --- a/setup.py +++ b/setup.py @@ -59,9 +59,8 @@ install_requires=[ "pydantic>=1.10.1,<2", "py-evm>=0.6.0a1", - "hexbytes>=0.2.3,<1", "eth-utils>=2", - "ethpm-types>=0.3.7,<0.4", + "ethpm-types>=0.4.0,<0.5", "msgspec>=0.8", ], python_requires=">=3.8,<4", diff --git a/tests/test_geth.py b/tests/test_geth.py index d415b6c..a6132d3 100644 --- a/tests/test_geth.py +++ b/tests/test_geth.py @@ -1,5 +1,5 @@ import pytest -from hexbytes import HexBytes +from ethpm_types import HexBytes from pydantic import ValidationError from evm_trace.enums import CallType