diff --git a/ape_etherscan/client.py b/ape_etherscan/client.py index e09a436..9e5df61 100644 --- a/ape_etherscan/client.py +++ b/ape_etherscan/client.py @@ -530,7 +530,7 @@ def get_creation_data(self) -> list[ContractCreationResponse]: if not isinstance(items, list): raise ValueError("Expecting list.") - return [ContractCreationResponse(**item) for item in items] + return [ContractCreationResponse.model_validate(item) for item in items] class AccountClient(_APIClient): diff --git a/ape_etherscan/query.py b/ape_etherscan/query.py index 824581b..c0f5581 100644 --- a/ape_etherscan/query.py +++ b/ape_etherscan/query.py @@ -125,17 +125,26 @@ def get_contract_creation_receipt( self, query: ContractCreationQuery ) -> Iterator[ContractCreation]: client = self._client_factory.get_contract_client(query.contract) - creation_data = client.get_creation_data() - if len(creation_data) == 0: + creation_data_list = client.get_creation_data() + if len(creation_data_list) == 0: return - elif len(creation_data) != 1: + elif len(creation_data_list) != 1: raise ValueError("Expecting single creation data.") - receipt = self.chain_manager.get_receipt(creation_data[0].txHash) + creation_data = creation_data_list[0] + if creation_data.blockNumber is None: + # Server has an older API implementation. + # Have to look-up. + receipt = self.chain_manager.get_receipt(creation_data.txHash) + block = receipt.block_number + deployer = receipt.sender + else: + block = creation_data.blockNumber + deployer = creation_data.contractCreator + yield ContractCreation( - txn_hash=receipt.txn_hash, - block=receipt.block_number, - deployer=receipt.sender, - # factory is not implemented by this query provider - factory=None, + txn_hash=creation_data.txHash, + block=block, + deployer=deployer, + factory=creation_data.contractFactory or "", ) diff --git a/ape_etherscan/types.py b/ape_etherscan/types.py index e32a532..9a2ef22 100644 --- a/ape_etherscan/types.py +++ b/ape_etherscan/types.py @@ -1,7 +1,7 @@ import json import re from dataclasses import dataclass -from typing import Union +from typing import Optional, Union from ape.utils import cached_property from ethpm_types import BaseModel @@ -60,12 +60,17 @@ def validate_source_code(cls, value): return value -@dataclass -class ContractCreationResponse: +class ContractCreationResponse(BaseModel): contractAddress: str contractCreator: str txHash: str + # Only appears on some networks for some reason. + blockNumber: Optional[int] = None + timestamp: Optional[int] = None + contractFactory: Optional[str] = None + creationBytecode: Optional[str] = None + ResponseValue = Union[list, dict, str] diff --git a/tests/test_query.py b/tests/test_query.py index cf25533..d2e9abc 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -8,24 +8,38 @@ def query_engine(): return ManagerAccessMixin.query_manager.engines["etherscan"] -def test_contract_creation_receipt(query_engine, mock_backend): +def test_contract_creation_metadata_query(query_engine, mock_backend): address = "0x388C818CA8B9251b393131C08a736A67ccB19297" creator = "0xDB65702A9b26f8a643a31a4c84b9392589e03D7c" - - # Setup backend. params = {"action": "getcontractcreation", "contractaddresses": [address]} - return_value = [ - { - "contractAddress": address, - "contractCreator": creator.lower(), - "txHash": "0xd72cf25e4a5fe3677b6f9b2ae13771e02ad66f8d2419f333bb8bde3147bd4294", - } - ] - mock_backend.add_handler("GET", "contract", params, return_value=return_value) + creation_data = { + "contractAddress": address, + "contractCreator": creator.lower(), + "txHash": "0xd72cf25e4a5fe3677b6f9b2ae13771e02ad66f8d2419f333bb8bde3147bd4294", + } + mock_backend.add_handler("GET", "contract", params, return_value=[creation_data]) # Perform query. query = ContractCreationQuery(contract=address, columns=["*"]) result = list(query_engine.perform_query(query)) + assert len(result) == 1 + assert result[0].deployer == creator + + # Newer APIs have this extra data included. + extra_data = { + "blockNumber": "14834805", + "timestamp": "1653382011", + "contractFactory": "", + "creationBytecode": "0x60c060405234801561001057600080fd5b5060405161095d38038061095d83398101604081905261002f91610107565b6001600160a01b03821661007e5760405162461bcd60e51b81526020600482015260116024820152704c49444f5f5a45524f5f4144445245535360781b60448201526064015b60405180910390fd5b6001600160a01b0381166100d45760405162461bcd60e51b815260206004820152601560248201527f54524541535552595f5a45524f5f4144445245535300000000000000000000006044820152606401610075565b6001600160a01b039182166080521660a05261013a565b80516001600160a01b038116811461010257600080fd5b919050565b6000806040838503121561011a57600080fd5b610123836100eb565b9150610131602084016100eb565b90509250929050565b60805160a0516107e361017a60003960008181609f015281816101df01526102e90152600081816101320152818161031f015261039f01526107e36000f3fe60806040526004361061004e5760003560e01c80632d2c55651461008d578063819d4cc6146100de5780638980f11f146101005780638b21f170146101205780639342c8f41461015457600080fd5b36610088576040513481527f27f12abfe35860a9a927b465bb3d4a9c23c8428174b83f278fe45ed7b4da26629060200160405180910390a1005b600080fd5b34801561009957600080fd5b506100c17f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100ea57600080fd5b506100fe6100f93660046106bb565b610182565b005b34801561010c57600080fd5b506100fe61011b3660046106bb565b61024e565b34801561012c57600080fd5b506100c17f000000000000000000000000000000000000000000000000000000000000000081565b34801561016057600080fd5b5061017461016f3660046106f3565b610312565b6040519081526020016100d5565b6040518181526001600160a01b0383169033907f6a30e6784464f0d1f4158aa4cb65ae9239b0fa87c7f2c083ee6dde44ba97b5e69060200160405180910390a36040516323b872dd60e01b81523060048201526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166024830152604482018390528316906323b872dd90606401600060405180830381600087803b15801561023257600080fd5b505af1158015610246573d6000803e3d6000fd5b505050505050565b6000811161029a5760405162461bcd60e51b815260206004820152601460248201527316915493d7d49150d3d591549657d05353d5539560621b60448201526064015b60405180910390fd5b6040518181526001600160a01b0383169033907faca8fb252cde442184e5f10e0f2e6e4029e8cd7717cae63559079610702436aa9060200160405180910390a361030e6001600160a01b0383167f000000000000000000000000000000000000000000000000000000000000000083610418565b5050565b6000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146103855760405162461bcd60e51b81526020600482015260166024820152754f4e4c595f4c49444f5f43414e5f574954484452415760501b6044820152606401610291565b478281116103935780610395565b825b91508115610412577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634ad509b2836040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103f857600080fd5b505af115801561040c573d6000803e3d6000fd5b50505050505b50919050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b17905261046a90849061046f565b505050565b60006104c4826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166105419092919063ffffffff16565b80519091501561046a57808060200190518101906104e2919061070c565b61046a5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610291565b6060610550848460008561055a565b90505b9392505050565b6060824710156105bb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610291565b843b6106095760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610291565b600080866001600160a01b03168587604051610625919061075e565b60006040518083038185875af1925050503d8060008114610662576040519150601f19603f3d011682016040523d82523d6000602084013e610667565b606091505b5091509150610677828286610682565b979650505050505050565b60608315610691575081610553565b8251156106a15782518084602001fd5b8160405162461bcd60e51b8152600401610291919061077a565b600080604083850312156106ce57600080fd5b82356001600160a01b03811681146106e557600080fd5b946020939093013593505050565b60006020828403121561070557600080fd5b5035919050565b60006020828403121561071e57600080fd5b8151801515811461055357600080fd5b60005b83811015610749578181015183820152602001610731565b83811115610758576000848401525b50505050565b6000825161077081846020870161072e565b9190910192915050565b602081526000825180602084015261079981604085016020870161072e565b601f01601f1916919091016040019291505056fea2646970667358221220c0f03149dd58fa21e9bfb72a010b74b1e518d704a2d63d8cc44c0ad3a2f573da64736f6c63430008090033000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe840000000000000000000000003e40d73eb977dc6a537af587d48316fee66e9c8c", # noqa: E501 + # NODE: Including made-up key to show it doesn't matter (future-proof) + "lalala": 123, + } + creation_data = {**creation_data, **extra_data} + mock_backend.add_handler("GET", "contract", params, return_value=[creation_data]) + # Perform query. + query = ContractCreationQuery(contract=address, columns=["*"]) + result = list(query_engine.perform_query(query)) assert len(result) == 1 assert result[0].deployer == creator + assert result[0].block == 14834805