From e51bd4b551d3bdc2ff80deb04080f4ae252f2819 Mon Sep 17 00:00:00 2001 From: John Ward Date: Mon, 2 Mar 2020 14:12:24 -0600 Subject: [PATCH 1/5] Add block_identifier to --- web3/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index d6c90fa58b..0ea4cf4b29 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -586,7 +586,7 @@ def _encode_data_in_transaction(self, *args: Any, **kwargs: Any) -> HexStr: return data @combomethod - def estimateGas(self, transaction: TxParams=None) -> int: + def estimateGas(self, transaction: TxParams=None, block_identifier: BlockIdentifier=None) -> int: if transaction is None: estimate_gas_transaction: TxParams = {} else: @@ -600,7 +600,7 @@ def estimateGas(self, transaction: TxParams=None) -> int: estimate_gas_transaction['data'] = self.data_in_transaction - return self.web3.eth.estimateGas(estimate_gas_transaction) + return self.web3.eth.estimateGas(estimate_gas_transaction, block_identifier=block_identifier) @combomethod def transact(self, transaction: TxParams=None) -> HexBytes: From fbc4a88c152576e50f87851991f43d58a57fb532 Mon Sep 17 00:00:00 2001 From: John Ward Date: Mon, 2 Mar 2020 14:26:36 -0600 Subject: [PATCH 2/5] added newsfragment --- newsfragments/1588.misc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/1588.misc.rst diff --git a/newsfragments/1588.misc.rst b/newsfragments/1588.misc.rst new file mode 100644 index 0000000000..ae0429d9fc --- /dev/null +++ b/newsfragments/1588.misc.rst @@ -0,0 +1 @@ +Added block_identifier parameter to `Contract.estimateGas` \ No newline at end of file From 0662426706bf7c8a22ee520a5ce10f2b13b37d18 Mon Sep 17 00:00:00 2001 From: John Ward Date: Mon, 2 Mar 2020 17:22:34 -0600 Subject: [PATCH 3/5] fixes to address bug in issue 1560 --- newsfragments/1560.bugfix.rst | 9 +++++++++ .../contracts/test_contract_events_getattr.py | 18 ++++++++++++++++++ .../contracts/test_contract_events_hasattr.py | 11 +++++++++++ web3/contract.py | 9 ++++++++- web3/exceptions.py | 6 ++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 newsfragments/1560.bugfix.rst create mode 100644 tests/core/contracts/test_contract_events_getattr.py create mode 100644 tests/core/contracts/test_contract_events_hasattr.py diff --git a/newsfragments/1560.bugfix.rst b/newsfragments/1560.bugfix.rst new file mode 100644 index 0000000000..f059e9bf0d --- /dev/null +++ b/newsfragments/1560.bugfix.rst @@ -0,0 +1,9 @@ +Fixed hasattr overloader method in the web3.ContractEvent class by implementing a try/except handler +that returns False if an exception is raised in the __getattr__ overloader method +(since __getattr__ HAS to be called in every __hasattr__ call). + +Created an Exception class called 'ABIEventFunctionNotFound', which inherits from both AttributeError and +MismatchedABI, and replaced the MismatchedABI raise with a raise to the created class in the __getattr__ +overloader method of the web3.ContractEvent object. + +Created the test scripts 'test_contract_events_hasattr.py' and 'test_contract_events_getattr.py' \ No newline at end of file diff --git a/tests/core/contracts/test_contract_events_getattr.py b/tests/core/contracts/test_contract_events_getattr.py new file mode 100644 index 0000000000..47c181e144 --- /dev/null +++ b/tests/core/contracts/test_contract_events_getattr.py @@ -0,0 +1,18 @@ +import pytest +import json + +CONTRACT_ABI = json.loads('[{"constant":false,"inputs":[],"name":"return13","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"constant":true,"inputs":[],"name":"counter","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"amt","type":"uint256"}],"name":"increment","outputs":[{"name":"result","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"a","type":"int256"},{"name":"b","type":"int256"}],"name":"add","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"constant":false,"inputs":[],"name":"increment","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"a","type":"int256"}],"name":"multiply7","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"Increased","type":"event"}]') # noqa: E501 + +def test_contract_event_getattr(Web3, EthereumTesterProvider, event_name, expected_existence): + web3 = Web3(EthereumTesterProvider) + contract = web3.eth.contract(abi=CONTRACT_ABI) + try: + getattr(contract.events, event_name) + result = True + except Exception as e: + result = False + if result == expected_existence: + return True + else: + return False + \ No newline at end of file diff --git a/tests/core/contracts/test_contract_events_hasattr.py b/tests/core/contracts/test_contract_events_hasattr.py new file mode 100644 index 0000000000..183068b12b --- /dev/null +++ b/tests/core/contracts/test_contract_events_hasattr.py @@ -0,0 +1,11 @@ +import pytest +import json + +CONTRACT_ABI = json.loads('[{"constant":false,"inputs":[],"name":"return13","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"constant":true,"inputs":[],"name":"counter","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"amt","type":"uint256"}],"name":"increment","outputs":[{"name":"result","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"a","type":"int256"},{"name":"b","type":"int256"}],"name":"add","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"constant":false,"inputs":[],"name":"increment","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"a","type":"int256"}],"name":"multiply7","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"Increased","type":"event"}]') # noqa: E501 + +def test_contract_event_hasattr(Web3, EthereumTesterProvider, event_name, expected): + web3 = Web3(EthereumTesterProvider) + contract = web3.eth.contract(abi=CONTRACT_ABI) + if hasattr(contract.events, event_name) == expected: + return True + return False diff --git a/web3/contract.py b/web3/contract.py index 0ea4cf4b29..1df3e501a1 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -116,6 +116,7 @@ InvalidEventABI, LogTopicError, MismatchedABI, + ABIEventFunctionNotFound, NoABIEventsFound, NoABIFound, NoABIFunctionsFound, @@ -239,7 +240,7 @@ def __getattr__(self, event_name: str) -> "ContractEvent": "Are you sure you provided the correct contract abi?" ) elif event_name not in self.__dict__['_events']: - raise MismatchedABI( + raise ABIEventFunctionNotFound( "The event '{}' was not found in this contract's abi. ".format(event_name), "Are you sure you provided the correct contract abi?" ) @@ -257,6 +258,12 @@ def __iter__(self) -> Iterable["ContractEvent"]: for event in self._events: yield self[event['name']] + def __hasattr__(self, event_name: str) -> bool: + try: + return event_name in self.__dict__['_events'] + except ABIEventFunctionNotFound: + return False + class Contract: """Base class for Contract proxy classes. diff --git a/web3/exceptions.py b/web3/exceptions.py index 2241d887e3..cc7072b020 100644 --- a/web3/exceptions.py +++ b/web3/exceptions.py @@ -69,6 +69,12 @@ class MismatchedABI(Exception): """ pass +class ABIEventFunctionNotFound(AttributeError, MismatchedABI): + """ + Raised when an attempt is made to access a function/event + that does not exist in the ABI. + """ + pass class FallbackNotFound(Exception): """ From 9011a26e8cd62806cd5745352ec5bb986b2d494a Mon Sep 17 00:00:00 2001 From: John Ward Date: Mon, 2 Mar 2020 22:37:30 -0600 Subject: [PATCH 4/5] Revert "Add block_identifier to" This reverts commit e51bd4b551d3bdc2ff80deb04080f4ae252f2819. --- web3/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 1df3e501a1..4f51210a59 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -593,7 +593,7 @@ def _encode_data_in_transaction(self, *args: Any, **kwargs: Any) -> HexStr: return data @combomethod - def estimateGas(self, transaction: TxParams=None, block_identifier: BlockIdentifier=None) -> int: + def estimateGas(self, transaction: TxParams=None) -> int: if transaction is None: estimate_gas_transaction: TxParams = {} else: @@ -607,7 +607,7 @@ def estimateGas(self, transaction: TxParams=None, block_identifier: BlockIdentif estimate_gas_transaction['data'] = self.data_in_transaction - return self.web3.eth.estimateGas(estimate_gas_transaction, block_identifier=block_identifier) + return self.web3.eth.estimateGas(estimate_gas_transaction) @combomethod def transact(self, transaction: TxParams=None) -> HexBytes: From bf87bda5d1544786d51bb82f25cd9d415beab538 Mon Sep 17 00:00:00 2001 From: John Ward Date: Mon, 2 Mar 2020 22:39:36 -0600 Subject: [PATCH 5/5] reverted unrelated commit --- .../test_contract_constructor_estimateGas.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/core/contracts/test_contract_constructor_estimateGas.py diff --git a/tests/core/contracts/test_contract_constructor_estimateGas.py b/tests/core/contracts/test_contract_constructor_estimateGas.py new file mode 100644 index 0000000000..27836ecebd --- /dev/null +++ b/tests/core/contracts/test_contract_constructor_estimateGas.py @@ -0,0 +1,15 @@ +import pytest + +abi = """[{"anonymous":false,"inputs":[{"indexed":false,"name":"_bar","type":"string"}],"name":"barred","type":"event"},{"constant":false,"inputs":[{"name":"_bar","type":"string"}],"name":"setBar","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"constant":true,"inputs":[],"name":"bar","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]""" # noqa: E501 +# This bytecode is the output of compiling with +# solc version:0.5.3+commit.10d17f24.Emscripten.clang +bytecode = """608060405234801561001057600080fd5b506040805190810160405280600b81526020017f68656c6c6f20776f726c640000000000000000000000000000000000000000008152506000908051906020019061005c929190610062565b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b6103bb806101166000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c01000000000000000000000000000000000000000000000000000000009004806397bc14aa14610058578063febb0f7e14610113575b600080fd5b6101116004803603602081101561006e57600080fd5b810190808035906020019064010000000081111561008b57600080fd5b82018360208201111561009d57600080fd5b803590602001918460018302840111640100000000831117156100bf57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610196565b005b61011b61024c565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561015b578082015181840152602081019050610140565b50505050905090810190601f1680156101885780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101ac9291906102ea565b507f5f71ad82e16f082de5ff496b140e2fbc8621eeb37b36d59b185c3f1364bbd529816040518080602001828103825283818151815260200191508051906020019080838360005b8381101561020f5780820151818401526020810190506101f4565b50505050905090810190601f16801561023c5780820380516001836020036101000a031916815260200191505b509250505060405180910390a150565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102e25780601f106102b7576101008083540402835291602001916102e2565b820191906000526020600020905b8154815290600101906020018083116102c557829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061032b57805160ff1916838001178555610359565b82800160010185558215610359579182015b8281111561035857825182559160200191906001019061033d565b5b509050610366919061036a565b5090565b61038c91905b80821115610388576000816000905550600101610370565b5090565b9056fea165627a7a72305820ae6ca683d45ee8a71bba45caee29e4815147cd308f772c853a20dfe08214dbb50029""" +block_identifier = 'latest' + +def test_contract_estimateGas(web3, transaction=None): + contract = web3.eth.contract(abi=abi, bytecode=bytecode) + gas_estimate = contract.constructor().estimateGas(transaction=transaction, block_identifier=block_identifier) + deploy_txn = contract.constructor().transact({'from': Web3.toChecksumAddress(Web3.eth.accounts[0])}) + txn_receipt = web3.eth.waitForTransactionReceipt(deploy_txn) + gas_used = txn_receipt.get('gasUsed') + assert abs(gas_estimate - gas_used) < 21000 \ No newline at end of file