diff --git a/ape_alchemy/provider.py b/ape_alchemy/provider.py index 7b9cb7a..570bf65 100644 --- a/ape_alchemy/provider.py +++ b/ape_alchemy/provider.py @@ -12,7 +12,6 @@ from ape.logging import logger from ape.types import BlockID from ape_ethereum.provider import Web3Provider -from ape_ethereum.trace import TransactionTrace from ape_ethereum.transactions import AccessList from eth_pydantic_types import HexBytes from eth_typing import HexStr @@ -24,6 +23,7 @@ from web3.types import RPCEndpoint from .exceptions import AlchemyFeatureNotAvailable, AlchemyProviderError, MissingProjectKeyError +from .trace import AlchemyTransactionTrace # The user must either set one of these or an ENV VAR of the pattern: # WEB3___PROJECT_ID or WEB3___API_KEY @@ -134,10 +134,7 @@ def _get_prestate_trace(self, transaction_hash: str) -> dict: ) def get_transaction_trace(self, transaction_hash: str, **kwargs) -> TraceAPI: - if "debug_trace_transaction_parameters" not in kwargs: - kwargs["debug_trace_transaction_parameters"] = {} - - return TransactionTrace(transaction_hash=transaction_hash, **kwargs) + return AlchemyTransactionTrace(transaction_hash=transaction_hash, **kwargs) def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMachineError: txn = kwargs.get("txn") diff --git a/ape_alchemy/trace.py b/ape_alchemy/trace.py new file mode 100644 index 0000000..6f7d595 --- /dev/null +++ b/ape_alchemy/trace.py @@ -0,0 +1,37 @@ +from functools import cached_property +from typing import Any, Optional + +from ape_ethereum.trace import TraceApproach, TransactionTrace +from hexbytes import HexBytes + + +class AlchemyTransactionTrace(TransactionTrace): + call_trace_approach: TraceApproach = TraceApproach.PARITY + + @cached_property + def return_value(self) -> Any: + node = self._top_level_call + if output := node.get("output"): + output_bytes = HexBytes(output) + if abi := self.root_method_abi: + return self._ecosystem.decode_returndata(abi, output_bytes) + + # ABI is not known. + return output_bytes + + return None + + @cached_property + def revert_message(self) -> Optional[str]: + node = self._top_level_call + return node.get("revertReason") + + @cached_property + def _top_level_call(self) -> dict: + return self.provider.make_request( + "debug_traceTransaction", + [ + self.transaction_hash, + {"tracer": "callTracer", "tracerConfig": {"onlyTopLevelCall": True}}, + ], + ) diff --git a/tests/test_trace.py b/tests/test_trace.py new file mode 100644 index 0000000..d35afb3 --- /dev/null +++ b/tests/test_trace.py @@ -0,0 +1,38 @@ +import pytest +from ape import chain, networks +from ethpm_types import ContractType + + +@pytest.fixture(autouse=True) +def ethereum_mainnet_alchemy(): + with networks.ethereum.mainnet.use_provider("alchemy"): + yield + + +def test_revert_message(): + txn_hash = "0x36144f609e0fc7afd3cc570d6a54582091642a44c5223a5ad59aa20008dd9577" + receipt = chain.history[txn_hash] + actual = receipt.trace.revert_message + expected = "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT" + assert actual == expected + + +def test_return_value(): + txn_hash = "0xe0897d735b67893648b20085ecef16232733425329df844292d5b2774cca436b" + receipt = chain.history[txn_hash] + + # Ensure the ABI is cached so we can decode the return value. + abi = [ + { + "type": "function", + "name": "submit", + "stateMutability": "payable", + "inputs": [{"name": "_referral", "type": "address"}], + "outputs": [{"name": "", "type": "uint256"}], + } + ] + chain.contracts[receipt.receiver] = ContractType(abi=abi) + + actual = receipt.return_value + expected = 1244617160572980465 + assert actual == expected