From deb435ba2a8842caaa57abe564352df3e1a3fbe7 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:00:55 -0700 Subject: [PATCH 01/30] First pass on Contract caller --- .../contracts/test_contract_call_interface.py | 17 ++++++++ web3/contract.py | 43 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index b65b59ad66..db1159eabd 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -411,6 +411,23 @@ def test_call_fallback_function(fallback_function_contract): assert result == [] +def test_reader_default(math_contract): + result = math_contract.reader.return13() + assert result == 13 + + +def test_reader_with_parens(math_contract): + # result = math_contract.reader().return13() + # assert result == 13 + pass + + +def test_reader_with_parens_and_transaction_dict(math_contract): + # result = math_contract.reader({transaction_dict}).return13() + # assert result == 13 + pass + + def test_throws_error_if_block_out_of_range(web3, math_contract): web3.provider.make_request(method='evm_mine', params=[20]) with pytest.raises(BlockNumberOutofRange): diff --git a/web3/contract.py b/web3/contract.py index 74622056f4..4f46b28ae1 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -101,6 +101,46 @@ ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""] +class ReaderMethod: + ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} + + def __init__(self, function, normalizers=None): + self._function = function + + def __call__(self, *args, **kwargs): + return self.__prepared_function(*args, **kwargs) + + def __prepared_function(self, *args, **kwargs): + if not kwargs: + modifier, modifier_dict = 'call', {} + elif len(kwargs) == 1: + modifier, modifier_dict = kwargs.popitem() + if modifier not in self.ALLOWED_MODIFIERS: + raise TypeError( + "The only allowed keyword arguments are: %s" % self.ALLOWED_MODIFIERS) + else: + raise TypeError("Use up to one keyword argument, one of: %s" % self.ALLOWED_MODIFIERS) + + return getattr(self._function(*args), modifier)(modifier_dict) + +class ContractReader: + def __init__(self, abi, web3, address,method_class=ReaderMethod): + if abi: + self.abi = abi + self._functions = filter_by_type('function', self.abi) + for func in self._functions: + _concise_method = method_class( + ContractFunction.factory( + func['name'], + web3=web3, + contract_abi=self.abi, + address=address, + function_identifier=func['name']) + ) + + setattr(self, func['name'], _concise_method) + + class ContractFunctions: """Class containing contract function objects """ @@ -241,6 +281,7 @@ class Contract: clone_bin = None functions = None + reader = None #: Instance of :class:`ContractEvents` presenting available Event ABIs events = None @@ -271,6 +312,7 @@ def __init__(self, address=None): raise TypeError("The address argument is required to instantiate a contract.") self.functions = ContractFunctions(self.abi, self.web3, self.address) + self.reader = ContractReader(self.abi, self.web3, self.address) # TODO: Change to caller self.events = ContractEvents(self.abi, self.web3, self.address) self.fallback = Contract.get_fallback_function(self.abi, self.web3, self.address) @@ -293,6 +335,7 @@ def factory(cls, web3, class_name=None, **kwargs): normalizers=normalizers, ) contract.functions = ContractFunctions(contract.abi, contract.web3) + contract.reader = ContractReader(contract.abi, contract.web3, contract.address) # TODO: Change to caller contract.events = ContractEvents(contract.abi, contract.web3) contract.fallback = Contract.get_fallback_function(contract.abi, contract.web3) From 7bda172d5de7be444861fc4002135954256153c6 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:04:59 -0700 Subject: [PATCH 02/30] ContractReader works with parentheses option --- .../core/contracts/test_contract_call_interface.py | 7 ++++--- web3/contract.py | 13 ++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index db1159eabd..1593513d17 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -417,14 +417,15 @@ def test_reader_default(math_contract): def test_reader_with_parens(math_contract): - # result = math_contract.reader().return13() - # assert result == 13 - pass + result = math_contract.reader().return13() + assert result == 13 def test_reader_with_parens_and_transaction_dict(math_contract): # result = math_contract.reader({transaction_dict}).return13() # assert result == 13 + # result = math_contract.reader(transaction_dict={'a': 2}).multiply7() + # assert result == 14 pass diff --git a/web3/contract.py b/web3/contract.py index 4f46b28ae1..f33dfb685f 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -124,9 +124,12 @@ def __prepared_function(self, *args, **kwargs): return getattr(self._function(*args), modifier)(modifier_dict) class ContractReader: - def __init__(self, abi, web3, address,method_class=ReaderMethod): - if abi: - self.abi = abi + def __init__(self, abi, web3, address, method_class=ReaderMethod): + self.web3 = web3 + self.address = address + self.abi = abi + + if self.abi: self._functions = filter_by_type('function', self.abi) for func in self._functions: _concise_method = method_class( @@ -139,6 +142,10 @@ def __init__(self, abi, web3, address,method_class=ReaderMethod): ) setattr(self, func['name'], _concise_method) + # TODO - make sure to handle if there is no abi + + def __call__(self): + return type(self)(self.abi, self.web3, self.address) class ContractFunctions: From 87225b8cc8cb48b104b7ce5b1c63126276774111 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:09:30 -0700 Subject: [PATCH 03/30] Allow reader to take a transaction_dict --- .../contracts/test_contract_call_interface.py | 7 +-- web3/contract.py | 43 +++++++------------ 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 1593513d17..361fbbb08d 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -422,11 +422,8 @@ def test_reader_with_parens(math_contract): def test_reader_with_parens_and_transaction_dict(math_contract): - # result = math_contract.reader({transaction_dict}).return13() - # assert result == 13 - # result = math_contract.reader(transaction_dict={'a': 2}).multiply7() - # assert result == 14 - pass + result = math_contract.reader({'from': 'notarealaddress.eth'}).add(2, 3) + assert result == 5 def test_throws_error_if_block_out_of_range(web3, math_contract): diff --git a/web3/contract.py b/web3/contract.py index f33dfb685f..f6c77c172c 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -102,29 +102,17 @@ class ReaderMethod: - ALLOWED_MODIFIERS = {'call', 'estimateGas', 'transact', 'buildTransaction'} - def __init__(self, function, normalizers=None): self._function = function def __call__(self, *args, **kwargs): - return self.__prepared_function(*args, **kwargs) - - def __prepared_function(self, *args, **kwargs): - if not kwargs: - modifier, modifier_dict = 'call', {} - elif len(kwargs) == 1: - modifier, modifier_dict = kwargs.popitem() - if modifier not in self.ALLOWED_MODIFIERS: - raise TypeError( - "The only allowed keyword arguments are: %s" % self.ALLOWED_MODIFIERS) - else: - raise TypeError("Use up to one keyword argument, one of: %s" % self.ALLOWED_MODIFIERS) + # TODO: Remove this and consolidate into ContractReader. + # I don't know why this is working but it is. + return self._function(*args, **kwargs).call() - return getattr(self._function(*args), modifier)(modifier_dict) class ContractReader: - def __init__(self, abi, web3, address, method_class=ReaderMethod): + def __init__(self, abi, web3, address, *args, **kwargs): self.web3 = web3 self.address = address self.abi = abi @@ -132,20 +120,19 @@ def __init__(self, abi, web3, address, method_class=ReaderMethod): if self.abi: self._functions = filter_by_type('function', self.abi) for func in self._functions: - _concise_method = method_class( - ContractFunction.factory( - func['name'], - web3=web3, - contract_abi=self.abi, - address=address, - function_identifier=func['name']) - ) - - setattr(self, func['name'], _concise_method) + fn = ContractFunction.factory( + func['name'], + web3=self.web3, + contract_abi=self.abi, + address=self.address, + function_identifier=func['name']) + + reader_method = ReaderMethod(fn) + setattr(self, func['name'], reader_method) # TODO - make sure to handle if there is no abi - def __call__(self): - return type(self)(self.abi, self.web3, self.address) + def __call__(self, *args, **kwargs): + return type(self)(self.abi, self.web3, self.address, *args, **kwargs) class ContractFunctions: From ceab93b4e87e9df0d53f71fc7fa52830ec0097ed Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:13:48 -0700 Subject: [PATCH 04/30] Change all 'reader' references to 'caller' --- .../contracts/test_contract_call_interface.py | 12 ++++++------ web3/contract.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 361fbbb08d..adb0a242ea 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -411,18 +411,18 @@ def test_call_fallback_function(fallback_function_contract): assert result == [] -def test_reader_default(math_contract): - result = math_contract.reader.return13() +def test_caller_default(math_contract): + result = math_contract.caller.return13() assert result == 13 -def test_reader_with_parens(math_contract): - result = math_contract.reader().return13() +def test_caller_with_parens(math_contract): + result = math_contract.caller().return13() assert result == 13 -def test_reader_with_parens_and_transaction_dict(math_contract): - result = math_contract.reader({'from': 'notarealaddress.eth'}).add(2, 3) +def test_caller_with_parens_and_transaction_dict(math_contract): + result = math_contract.caller({'from': 'notarealaddress.eth'}).add(2, 3) assert result == 5 diff --git a/web3/contract.py b/web3/contract.py index f6c77c172c..0f2252fee7 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -101,17 +101,17 @@ ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""] -class ReaderMethod: +class CallerMethod: def __init__(self, function, normalizers=None): self._function = function def __call__(self, *args, **kwargs): - # TODO: Remove this and consolidate into ContractReader. + # TODO: Remove this and consolidate into ContractCaller. # I don't know why this is working but it is. return self._function(*args, **kwargs).call() -class ContractReader: +class ContractCaller: def __init__(self, abi, web3, address, *args, **kwargs): self.web3 = web3 self.address = address @@ -127,8 +127,8 @@ def __init__(self, abi, web3, address, *args, **kwargs): address=self.address, function_identifier=func['name']) - reader_method = ReaderMethod(fn) - setattr(self, func['name'], reader_method) + caller_method = CallerMethod(fn) + setattr(self, func['name'], caller_method) # TODO - make sure to handle if there is no abi def __call__(self, *args, **kwargs): @@ -275,7 +275,7 @@ class Contract: clone_bin = None functions = None - reader = None + caller = None #: Instance of :class:`ContractEvents` presenting available Event ABIs events = None @@ -306,7 +306,7 @@ def __init__(self, address=None): raise TypeError("The address argument is required to instantiate a contract.") self.functions = ContractFunctions(self.abi, self.web3, self.address) - self.reader = ContractReader(self.abi, self.web3, self.address) # TODO: Change to caller + self.caller = ContractCaller(self.abi, self.web3, self.address) self.events = ContractEvents(self.abi, self.web3, self.address) self.fallback = Contract.get_fallback_function(self.abi, self.web3, self.address) @@ -329,7 +329,7 @@ def factory(cls, web3, class_name=None, **kwargs): normalizers=normalizers, ) contract.functions = ContractFunctions(contract.abi, contract.web3) - contract.reader = ContractReader(contract.abi, contract.web3, contract.address) # TODO: Change to caller + contract.caller = ContractCaller(contract.abi, contract.web3, contract.address) contract.events = ContractEvents(contract.abi, contract.web3) contract.fallback = Contract.get_fallback_function(contract.abi, contract.web3) From abdbd0289bb530055a40cf5e3b50a1cbf0f89d10 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:18:12 -0700 Subject: [PATCH 05/30] Add test to make sure a contract without an ABI doesn't return an error --- .../contracts/test_contract_call_interface.py | 39 ++++++----- web3/contract.py | 66 ++++++++++--------- 2 files changed, 57 insertions(+), 48 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index adb0a242ea..6ceb759bf7 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -27,7 +27,9 @@ NoABIFunctionsFound, ValidationError, ) - +from web3.contract import ( + ContractCaller +) # Ignore warning in pyethereum 1.6 - will go away with the upgrade pytestmark = pytest.mark.filterwarnings("ignore:implicit cast from 'char *'") @@ -411,21 +413,6 @@ def test_call_fallback_function(fallback_function_contract): assert result == [] -def test_caller_default(math_contract): - result = math_contract.caller.return13() - assert result == 13 - - -def test_caller_with_parens(math_contract): - result = math_contract.caller().return13() - assert result == 13 - - -def test_caller_with_parens_and_transaction_dict(math_contract): - result = math_contract.caller({'from': 'notarealaddress.eth'}).add(2, 3) - assert result == 5 - - def test_throws_error_if_block_out_of_range(web3, math_contract): web3.provider.make_request(method='evm_mine', params=[20]) with pytest.raises(BlockNumberOutofRange): @@ -626,3 +613,23 @@ def test_invalid_fixed_value_reflections(web3, fixed_reflection_contract, functi contract_func = fixed_reflection_contract.functions[function] with pytest.raises(ValidationError, match=error): contract_func(value).call({'gas': 420000}) + + +def test_caller_default(math_contract): + result = math_contract.caller.return13() + assert result == 13 + + +def test_caller_with_parens(math_contract): + result = math_contract.caller().return13() + assert result == 13 + + +def test_caller_with_parens_and_transaction_dict(math_contract): + result = math_contract.caller({'from': 'notarealaddress.eth'}).add(2, 3) + assert result == 5 + + +def test_caller_with_no_abi(web3): + result = ContractCaller(None, web3, '0x1234') + assert isinstance(result, ContractCaller) diff --git a/web3/contract.py b/web3/contract.py index 0f2252fee7..1bb998ccff 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -101,38 +101,6 @@ ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""] -class CallerMethod: - def __init__(self, function, normalizers=None): - self._function = function - - def __call__(self, *args, **kwargs): - # TODO: Remove this and consolidate into ContractCaller. - # I don't know why this is working but it is. - return self._function(*args, **kwargs).call() - - -class ContractCaller: - def __init__(self, abi, web3, address, *args, **kwargs): - self.web3 = web3 - self.address = address - self.abi = abi - - if self.abi: - self._functions = filter_by_type('function', self.abi) - for func in self._functions: - fn = ContractFunction.factory( - func['name'], - web3=self.web3, - contract_abi=self.abi, - address=self.address, - function_identifier=func['name']) - - caller_method = CallerMethod(fn) - setattr(self, func['name'], caller_method) - # TODO - make sure to handle if there is no abi - - def __call__(self, *args, **kwargs): - return type(self)(self.abi, self.web3, self.address, *args, **kwargs) class ContractFunctions: @@ -1479,6 +1447,40 @@ def factory(cls, class_name, **kwargs): return PropertyCheckingFactory(class_name, (cls,), kwargs) +class CallerMethod: + def __init__(self, function, normalizers=None): + self._function = function + + def __call__(self, *args, **kwargs): + # TODO: Remove this and consolidate into ContractCaller. + # I don't know why this is working but it is. + return self._function(*args, **kwargs).call() + + +class ContractCaller: + def __init__(self, abi, web3, address, *args, **kwargs): + self.web3 = web3 + self.address = address + self.abi = abi + + if self.abi: + self._functions = filter_by_type('function', self.abi) + for func in self._functions: + fn = ContractFunction.factory( + func['name'], + web3=self.web3, + contract_abi=self.abi, + address=self.address, + function_identifier=func['name']) + + caller_method = CallerMethod(fn) + setattr(self, func['name'], caller_method) + # TODO - make sure to handle if there is no abi + + def __call__(self, *args, **kwargs): + return type(self)(self.abi, self.web3, self.address, *args, **kwargs) + + def check_for_forbidden_api_filter_arguments(event_abi, _filters): name_indexed_inputs = {_input['name']: _input for _input in event_abi['inputs']} From 5384fbb6e325589a9aa876b4426bbc31b477a01f Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:33:59 -0700 Subject: [PATCH 06/30] Add deprecation message --- web3/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 1bb998ccff..653bc70ee9 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -101,8 +101,6 @@ ACCEPTABLE_EMPTY_STRINGS = ["0x", b"0x", "", b""] - - class ContractFunctions: """Class containing contract function objects """ @@ -916,6 +914,7 @@ class ConciseContract: > contract.functions.withdraw(amount).transact({'from': eth.accounts[1], 'gas': 100000, ...}) """ + @deprecated_for("contract.caller. or contract.caller({transaction_dict}).") def __init__(self, classic_contract, method_class=ConciseMethod): classic_contract._return_data_normalizers += CONCISE_NORMALIZERS @@ -991,6 +990,7 @@ class ImplicitContract(ConciseContract): > contract.functions.withdraw(amount).transact({}) """ + @deprecated_for("verbose contract syntax. Ex: contract.functions.withdraw(amount).transact({})") def __init__(self, classic_contract, method_class=ImplicitMethod): super().__init__(classic_contract, method_class=method_class) From 23603c81e670f6981766e35c99c78e15a8cebbc9 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:34:18 -0700 Subject: [PATCH 07/30] Get rid of ContractMethod, raise error if no ABI --- .../contracts/test_contract_call_interface.py | 15 +++++++-- web3/contract.py | 32 ++++++++++--------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 6ceb759bf7..7987cc5dda 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -631,5 +631,16 @@ def test_caller_with_parens_and_transaction_dict(math_contract): def test_caller_with_no_abi(web3): - result = ContractCaller(None, web3, '0x1234') - assert isinstance(result, ContractCaller) + with pytest.raises(NoABIFunctionsFound): + contract = web3.eth.contract() + contract.caller().thisFunctionDoesNotExist() + + +def test_caller_with_block_identifier(math_contract): + result = math_contract.caller(block_identifier=10).return13() + assert result == 13 + + +def test_caller_with_transaction_keyword(math_contract): + result = math_contract.caller(transaction={'from': 'notarealaddress.eth'}).return13() + assert result == 13 diff --git a/web3/contract.py b/web3/contract.py index 653bc70ee9..a446931858 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1447,18 +1447,8 @@ def factory(cls, class_name, **kwargs): return PropertyCheckingFactory(class_name, (cls,), kwargs) -class CallerMethod: - def __init__(self, function, normalizers=None): - self._function = function - - def __call__(self, *args, **kwargs): - # TODO: Remove this and consolidate into ContractCaller. - # I don't know why this is working but it is. - return self._function(*args, **kwargs).call() - - class ContractCaller: - def __init__(self, abi, web3, address, *args, **kwargs): + def __init__(self, abi, web3, address, *args, transaction=None, **kwargs): self.web3 = web3 self.address = address self.abi = abi @@ -1473,12 +1463,24 @@ def __init__(self, abi, web3, address, *args, **kwargs): address=self.address, function_identifier=func['name']) - caller_method = CallerMethod(fn) + caller_method = partial(self.call_function, fn, transaction=transaction) setattr(self, func['name'], caller_method) - # TODO - make sure to handle if there is no abi + else: + raise NoABIFunctionsFound( + "The abi for this contract contains no function definitions. ", + "Are you sure you provided the correct contract abi?" + ) - def __call__(self, *args, **kwargs): - return type(self)(self.abi, self.web3, self.address, *args, **kwargs) + def __call__(self, *args, transaction=None, **kwargs): + if transaction == None: + transaction = {} + return type(self)(self.abi, self.web3, self.address, transaction, **kwargs) + + @staticmethod + def call_function(fn, *args, transaction_dict=None, **kwargs): + if transaction_dict is None: + transaction_dict = {} + return fn(*args, **kwargs).call(transaction_dict) def check_for_forbidden_api_filter_arguments(event_abi, _filters): From 07bfbf32d0fc5445b624277dec17f3531188ac7f Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:43:03 -0700 Subject: [PATCH 08/30] Pass around args in a way that makes tests pass --- .../contracts/test_contract_call_interface.py | 8 ++++---- web3/contract.py | 15 +++++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 7987cc5dda..80108031ff 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -528,14 +528,14 @@ def test_function_multiple_possible_encodings(web3): def test_function_no_abi(web3): - contract = web3.eth.contract() with pytest.raises(NoABIFunctionsFound): + contract = web3.eth.contract() contract.functions.thisFunctionDoesNotExist().call() def test_call_abi_no_functions(web3): - contract = web3.eth.contract(abi=[]) with pytest.raises(NoABIFunctionsFound): + contract = web3.eth.contract(abi=[]) contract.functions.thisFunctionDoesNotExist().call() @@ -616,8 +616,8 @@ def test_invalid_fixed_value_reflections(web3, fixed_reflection_contract, functi def test_caller_default(math_contract): - result = math_contract.caller.return13() - assert result == 13 + result = math_contract.caller.add(3, 5) + assert result == 8 def test_caller_with_parens(math_contract): diff --git a/web3/contract.py b/web3/contract.py index a446931858..1b77f042dc 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1448,11 +1448,14 @@ def factory(cls, class_name, **kwargs): class ContractCaller: - def __init__(self, abi, web3, address, *args, transaction=None, **kwargs): + def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): self.web3 = web3 self.address = address self.abi = abi + if transaction_dict is None: + transaction = {} + if self.abi: self._functions = filter_by_type('function', self.abi) for func in self._functions: @@ -1463,7 +1466,7 @@ def __init__(self, abi, web3, address, *args, transaction=None, **kwargs): address=self.address, function_identifier=func['name']) - caller_method = partial(self.call_function, fn, transaction=transaction) + caller_method = partial(self.call_function, fn, transaction_dict=transaction) setattr(self, func['name'], caller_method) else: raise NoABIFunctionsFound( @@ -1471,10 +1474,10 @@ def __init__(self, abi, web3, address, *args, transaction=None, **kwargs): "Are you sure you provided the correct contract abi?" ) - def __call__(self, *args, transaction=None, **kwargs): - if transaction == None: - transaction = {} - return type(self)(self.abi, self.web3, self.address, transaction, **kwargs) + def __call__(self, *args, transaction_dict=None, **kwargs): + if transaction_dict == None: + transaction_dict = {} + return type(self)(self.abi, self.web3, self.address, transaction_dict, **kwargs) @staticmethod def call_function(fn, *args, transaction_dict=None, **kwargs): From 6844adcfc009ca6b57e3371a1635ca83e0aff522 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 12:46:59 -0700 Subject: [PATCH 09/30] Change ImplicitContract deprecation method, rename transaction_dict for consistency --- web3/contract.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index 1b77f042dc..625245775e 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -990,7 +990,7 @@ class ImplicitContract(ConciseContract): > contract.functions.withdraw(amount).transact({}) """ - @deprecated_for("verbose contract syntax. Ex: contract.functions.withdraw(amount).transact({})") + @deprecated_for("classic contract syntax. Ex: contract.functions.withdraw(amount).transact({})") def __init__(self, classic_contract, method_class=ImplicitMethod): super().__init__(classic_contract, method_class=method_class) @@ -1450,11 +1450,11 @@ def factory(cls, class_name, **kwargs): class ContractCaller: def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): self.web3 = web3 - self.address = address self.abi = abi + self.address = address if transaction_dict is None: - transaction = {} + transaction_dict = {} if self.abi: self._functions = filter_by_type('function', self.abi) @@ -1466,7 +1466,7 @@ def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): address=self.address, function_identifier=func['name']) - caller_method = partial(self.call_function, fn, transaction_dict=transaction) + caller_method = partial(self.call_function, fn, transaction_dict=transaction_dict) setattr(self, func['name'], caller_method) else: raise NoABIFunctionsFound( From 0b34a234ef3096faeb8b364fb319da74ca76f9da Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 13:05:45 -0700 Subject: [PATCH 10/30] Tests passing for ENS --- ens/main.py | 49 ++++++++++++++++----------------- ens/utils.py | 2 -- tests/ens/test_get_registry.py | 2 +- tests/ens/test_setup_address.py | 2 +- tests/ens/test_setup_name.py | 2 +- 5 files changed, 27 insertions(+), 30 deletions(-) diff --git a/ens/main.py b/ens/main.py index e0e1d4b229..8695e6e0ea 100644 --- a/ens/main.py +++ b/ens/main.py @@ -90,7 +90,6 @@ def name(self, address): """ reversed_domain = address_to_reverse_domain(address) return self.resolve(reversed_domain, get='name') - reverse = name @dict_copy def setup_address(self, name, address=default, transact={}): @@ -127,7 +126,7 @@ def setup_address(self, name, address=default, transact={}): address = EMPTY_ADDR_HEX transact['from'] = owner resolver = self._set_resolver(name, transact=transact) - return resolver.setAddr(raw_name_to_hash(name), address, transact=transact) + return resolver.functions.setAddr(raw_name_to_hash(name), address).transact(transact) @dict_copy def setup_name(self, name, address=None, transact={}): @@ -150,18 +149,18 @@ def setup_name(self, name, address=None, transact={}): return self._setup_reverse(None, address, transact=transact) else: resolved = self.address(name) - if not address: + if not address or address == EMPTY_ADDR_HEX: address = resolved - elif resolved and address != resolved: + elif resolved and address != resolved and resolved != EMPTY_ADDR_HEX: raise AddressMismatch( "Could not set address %r to point to name, because the name resolves to %r. " "To change the name for an existing address, call setup_address() first." % ( address, resolved ) ) - if not address: + if not address or address == EMPTY_ADDR_HEX: address = self.owner(name) - if not address: + if not address or address == EMPTY_ADDR_HEX: raise UnownedName("claim subdomain using setup_address() first") if is_binary_address(address): address = to_checksum_address(address) @@ -176,15 +175,17 @@ def resolve(self, name, get='addr'): normal_name = normalize_name(name) resolver = self.resolver(normal_name) if resolver: - lookup_function = getattr(resolver, get) + lookup_function = getattr(resolver.functions, get) namehash = normal_name_to_hash(normal_name) - return lookup_function(namehash) + if lookup_function(namehash).call() == EMPTY_ADDR_HEX: # TODO: is there a better way? + return None + return lookup_function(namehash).call() else: return None def resolver(self, normal_name): - resolver_addr = self.ens.resolver(normal_name_to_hash(normal_name)) - if not resolver_addr: + resolver_addr = self.ens.caller.resolver(normal_name_to_hash(normal_name)) + if not resolver_addr or resolver_addr == EMPTY_ADDR_HEX: return None return self._resolverContract(address=resolver_addr) @@ -204,7 +205,7 @@ def owner(self, name): :rtype: str """ node = raw_name_to_hash(name) - return self.ens.owner(node) + return self.ens.caller.owner(node) @dict_copy def setup_owner(self, name, new_owner=default, transact={}): @@ -265,10 +266,10 @@ def _first_owner(self, name): owner = None unowned = [] pieces = normalize_name(name).split('.') - while pieces and not owner: + while pieces and (owner == EMPTY_ADDR_HEX or not owner): name = '.'.join(pieces) owner = self.owner(name) - if not owner: + if owner == EMPTY_ADDR_HEX or not owner: unowned.append(pieces.pop(0)) return (owner, unowned, name) @@ -276,25 +277,23 @@ def _first_owner(self, name): def _claim_ownership(self, owner, unowned, owned, old_owner=None, transact={}): transact['from'] = old_owner or owner for label in reversed(unowned): - self.ens.setSubnodeOwner( + self.ens.functions.setSubnodeOwner( raw_name_to_hash(owned), label_to_hash(label), - owner, - transact=transact - ) + owner + ).transact(transact) owned = "%s.%s" % (label, owned) @dict_copy def _set_resolver(self, name, resolver_addr=None, transact={}): - if not resolver_addr: + if not resolver_addr or resolver_addr == EMPTY_ADDR_HEX: resolver_addr = self.address('resolver.eth') namehash = raw_name_to_hash(name) - if self.ens.resolver(namehash) != resolver_addr: - self.ens.setResolver( + if self.ens.caller.resolver(namehash) != resolver_addr: + self.ens.functions.setResolver( namehash, - resolver_addr, - transact=transact - ) + resolver_addr + ).transact(transact) return self._resolverContract(address=resolver_addr) @dict_copy @@ -304,8 +303,8 @@ def _setup_reverse(self, name, address, transact={}): else: name = '' transact['from'] = address - return self._reverse_registrar().setName(name, transact=transact) + return self._reverse_registrar().functions.setName(name).transact(transact) def _reverse_registrar(self): - addr = self.ens.owner(normal_name_to_hash(REVERSE_REGISTRAR_DOMAIN)) + addr = self.ens.caller.owner(normal_name_to_hash(REVERSE_REGISTRAR_DOMAIN)) return self.web3.eth.contract(address=addr, abi=abis.REVERSE_REGISTRAR) diff --git a/ens/utils.py b/ens/utils.py index 417a94b51a..439acfe106 100644 --- a/ens/utils.py +++ b/ens/utils.py @@ -57,7 +57,6 @@ def init_web3(providers=default): def customize_web3(w3): - from web3.contract import ConciseContract from web3.middleware import make_stalecheck_middleware w3.middleware_onion.remove('name_to_address') @@ -65,7 +64,6 @@ def customize_web3(w3): make_stalecheck_middleware(ACCEPTABLE_STALE_HOURS * 3600), name='stalecheck', ) - w3.eth.setContractFactory(ConciseContract) return w3 diff --git a/tests/ens/test_get_registry.py b/tests/ens/test_get_registry.py index 6bc85c2329..645fcff90d 100644 --- a/tests/ens/test_get_registry.py +++ b/tests/ens/test_get_registry.py @@ -9,7 +9,7 @@ def test_resolver_empty(ens): - with patch.object(ens.ens, 'resolver', return_value=None): + with patch.object(ens.ens.caller, 'resolver', return_value=None): assert ens.resolver('') is None diff --git a/tests/ens/test_setup_address.py b/tests/ens/test_setup_address.py index 41e63b2cb0..236c25cbf5 100644 --- a/tests/ens/test_setup_address.py +++ b/tests/ens/test_setup_address.py @@ -71,7 +71,7 @@ def test_set_address(ens, name, full_name, namehash_hex, TEST_ADDRESS): assert is_same_address(ens.address(name), TEST_ADDRESS) # check that the correct namehash is set: - assert is_same_address(ens.resolver(normal_name).addr(namehash), TEST_ADDRESS) + assert is_same_address(ens.resolver(normal_name).caller.addr(namehash), TEST_ADDRESS) # check that the correct owner is set: assert ens.owner(name) == owner diff --git a/tests/ens/test_setup_name.py b/tests/ens/test_setup_name.py index 9ceef3459a..2d8fbc76fa 100644 --- a/tests/ens/test_setup_name.py +++ b/tests/ens/test_setup_name.py @@ -64,7 +64,7 @@ def test_setup_name(ens, name, normalized_name, namehash_hex): # check that the correct namehash is set: node = Web3.toBytes(hexstr=namehash_hex) - assert ens.resolver(normalized_name).addr(node) == address + assert ens.resolver(normalized_name).caller.addr(node) == address # check that the correct owner is set: assert ens.owner(name) == owner From f4e34fc9d9132c020862524c1d58996f71d8bdca Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 13:17:38 -0700 Subject: [PATCH 11/30] Fix linting --- .../core/contracts/test_contract_call_interface.py | 5 ++--- web3/contract.py | 14 ++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 80108031ff..f9ba838a94 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -27,9 +27,8 @@ NoABIFunctionsFound, ValidationError, ) -from web3.contract import ( - ContractCaller -) + + # Ignore warning in pyethereum 1.6 - will go away with the upgrade pytestmark = pytest.mark.filterwarnings("ignore:implicit cast from 'char *'") diff --git a/web3/contract.py b/web3/contract.py index 625245775e..97dc66d265 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -914,7 +914,9 @@ class ConciseContract: > contract.functions.withdraw(amount).transact({'from': eth.accounts[1], 'gas': 100000, ...}) """ - @deprecated_for("contract.caller. or contract.caller({transaction_dict}).") + @deprecated_for( + "contract.caller. or contract.caller({transaction_dict})." + ) def __init__(self, classic_contract, method_class=ConciseMethod): classic_contract._return_data_normalizers += CONCISE_NORMALIZERS @@ -1460,11 +1462,11 @@ def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): self._functions = filter_by_type('function', self.abi) for func in self._functions: fn = ContractFunction.factory( - func['name'], - web3=self.web3, - contract_abi=self.abi, - address=self.address, - function_identifier=func['name']) + func['name'], + web3=self.web3, + contract_abi=self.abi, + address=self.address, + function_identifier=func['name']) caller_method = partial(self.call_function, fn, transaction_dict=transaction_dict) setattr(self, func['name'], caller_method) From a84cc96fe808339da4a944061faba4f8039f5413 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 13:19:01 -0700 Subject: [PATCH 12/30] ConciseContract tests passing --- tests/core/contracts/test_concise_contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/contracts/test_concise_contract.py b/tests/core/contracts/test_concise_contract.py index 982d36d731..377012b479 100644 --- a/tests/core/contracts/test_concise_contract.py +++ b/tests/core/contracts/test_concise_contract.py @@ -98,8 +98,8 @@ def test_class_construction_sets_class_vars(web3, assert classic.bytecode_runtime == decode_hex(MATH_RUNTIME) -def test_conciscecontract_keeps_custom_normalizers_on_base(web3): - base_contract = web3.eth.contract() +def test_conciscecontract_keeps_custom_normalizers_on_base(web3, MATH_ABI): + base_contract = web3.eth.contract(abi=MATH_ABI) # give different normalizers to this base instance base_contract._return_data_normalizers = base_contract._return_data_normalizers + tuple([None]) From 7e65e9c9cf242b2cedd15055ebdc1c1b1959d766 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 13:26:24 -0700 Subject: [PATCH 13/30] Allow no ABI until a function is called --- .../contracts/test_contract_call_interface.py | 6 ++--- web3/contract.py | 26 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index f9ba838a94..001c364e65 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -527,14 +527,14 @@ def test_function_multiple_possible_encodings(web3): def test_function_no_abi(web3): + contract = web3.eth.contract() with pytest.raises(NoABIFunctionsFound): - contract = web3.eth.contract() contract.functions.thisFunctionDoesNotExist().call() def test_call_abi_no_functions(web3): + contract = web3.eth.contract(abi=[]) with pytest.raises(NoABIFunctionsFound): - contract = web3.eth.contract(abi=[]) contract.functions.thisFunctionDoesNotExist().call() @@ -630,8 +630,8 @@ def test_caller_with_parens_and_transaction_dict(math_contract): def test_caller_with_no_abi(web3): + contract = web3.eth.contract() with pytest.raises(NoABIFunctionsFound): - contract = web3.eth.contract() contract.caller().thisFunctionDoesNotExist() diff --git a/web3/contract.py b/web3/contract.py index 97dc66d265..3786f3b6cd 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1451,14 +1451,13 @@ def factory(cls, class_name, **kwargs): class ContractCaller: def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): - self.web3 = web3 - self.abi = abi - self.address = address - - if transaction_dict is None: - transaction_dict = {} + if abi: + self.web3 = web3 + self.abi = abi + self.address = address + if transaction_dict is None: + transaction_dict = {} - if self.abi: self._functions = filter_by_type('function', self.abi) for func in self._functions: fn = ContractFunction.factory( @@ -1470,14 +1469,23 @@ def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): caller_method = partial(self.call_function, fn, transaction_dict=transaction_dict) setattr(self, func['name'], caller_method) - else: + + def __getattr__(self, function_name): + if '_functions' not in self.__dict__: raise NoABIFunctionsFound( "The abi for this contract contains no function definitions. ", "Are you sure you provided the correct contract abi?" ) + elif function_name not in self.__dict__['_functions']: + raise MismatchedABI( + "The function '{}' was not found in this contract's abi. ".format(function_name), + "Are you sure you provided the correct contract abi?" + ) + else: + return super().__getattribute__(function_name) def __call__(self, *args, transaction_dict=None, **kwargs): - if transaction_dict == None: + if transaction_dict is None: transaction_dict = {} return type(self)(self.abi, self.web3, self.address, transaction_dict, **kwargs) From cbb2a553bacd32e115c21393607a430a38d70944 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 14:18:19 -0700 Subject: [PATCH 14/30] Move ContractCaller tests to their own file --- .../contracts/test_contract_call_interface.py | 31 --------- .../test_contract_caller_interface.py | 66 +++++++++++++++++++ 2 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 tests/core/contracts/test_contract_caller_interface.py diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 001c364e65..0796bc1928 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -612,34 +612,3 @@ def test_invalid_fixed_value_reflections(web3, fixed_reflection_contract, functi contract_func = fixed_reflection_contract.functions[function] with pytest.raises(ValidationError, match=error): contract_func(value).call({'gas': 420000}) - - -def test_caller_default(math_contract): - result = math_contract.caller.add(3, 5) - assert result == 8 - - -def test_caller_with_parens(math_contract): - result = math_contract.caller().return13() - assert result == 13 - - -def test_caller_with_parens_and_transaction_dict(math_contract): - result = math_contract.caller({'from': 'notarealaddress.eth'}).add(2, 3) - assert result == 5 - - -def test_caller_with_no_abi(web3): - contract = web3.eth.contract() - with pytest.raises(NoABIFunctionsFound): - contract.caller().thisFunctionDoesNotExist() - - -def test_caller_with_block_identifier(math_contract): - result = math_contract.caller(block_identifier=10).return13() - assert result == 13 - - -def test_caller_with_transaction_keyword(math_contract): - result = math_contract.caller(transaction={'from': 'notarealaddress.eth'}).return13() - assert result == 13 diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py new file mode 100644 index 0000000000..bf64c52125 --- /dev/null +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -0,0 +1,66 @@ +import pytest + +from web3._utils.toolz import ( + identity, +) +from web3.exceptions import ( + MismatchedABI, + NoABIFunctionsFound, +) + + +def deploy(web3, Contract, apply_func=identity, args=None): + args = args or [] + deploy_txn = Contract.constructor(*args).transact() + deploy_receipt = web3.eth.waitForTransactionReceipt(deploy_txn) + assert deploy_receipt is not None + address = apply_func(deploy_receipt['contractAddress']) + contract = Contract(address=address) + assert contract.address == address + assert len(web3.eth.getCode(contract.address)) > 0 + return contract + + +@pytest.fixture() +def math_contract(web3, MathContract, address_conversion_func): + return deploy(web3, MathContract, address_conversion_func) + + +def test_caller_default(math_contract): + result = math_contract.caller.add(3, 5) + assert result == 8 + + +def test_caller_with_parens(math_contract): + result = math_contract.caller().return13() + assert result == 13 + + +def test_caller_with_parens_and_transaction_dict(math_contract): + result = math_contract.caller({'from': 'notarealaddress.eth'}).add(2, 3) + assert result == 5 + + +def test_caller_with_no_abi(web3): + contract = web3.eth.contract() + with pytest.raises(NoABIFunctionsFound): + contract.caller.thisFunctionDoesNotExist() + + +def test_caller_with_a_nonexistent_function(web3, math_contract): + contract = math_contract + with pytest.raises(MismatchedABI): + contract.caller.thisFunctionDoesNotExist() + + +def test_caller_with_block_identifier(math_contract): + # TODO: see how increment in the math contract changes with block_identifier + # TODO: dig into if we even want this block identifier here + result = math_contract.caller(block_identifier=10).return13() + assert result == 13 + + +def test_caller_with_transaction_keyword(math_contract): + # TODO: Make sure from is actually set correctly here. + result = math_contract.caller(transaction={'from': 'notarealaddress.eth'}).return13() + assert result == 13 From e35aeb4689ed346fccc1c497328a144cd5665dc1 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 14:26:48 -0700 Subject: [PATCH 15/30] Test that block_identifier is set correctly --- .../test_contract_caller_interface.py | 20 +++++++++----- web3/contract.py | 27 ++++++++++++++----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index bf64c52125..259a709f9e 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -47,17 +47,25 @@ def test_caller_with_no_abi(web3): contract.caller.thisFunctionDoesNotExist() -def test_caller_with_a_nonexistent_function(web3, math_contract): +def test_caller_with_a_nonexistent_function(math_contract): contract = math_contract with pytest.raises(MismatchedABI): contract.caller.thisFunctionDoesNotExist() -def test_caller_with_block_identifier(math_contract): - # TODO: see how increment in the math contract changes with block_identifier - # TODO: dig into if we even want this block identifier here - result = math_contract.caller(block_identifier=10).return13() - assert result == 13 +def test_caller_with_block_identifier(web3, math_contract): + start_num = web3.eth.getBlock('latest').number + assert math_contract.caller.counter() == 0 + + web3.provider.make_request(method='evm_mine', params=[5]) + math_contract.functions.increment().transact() + math_contract.functions.increment().transact() + + output1 = math_contract.caller(block_identifier=start_num + 6).counter() + output2 = math_contract.caller(block_identifier=start_num + 7).counter() + + assert output1 == 1 + assert output2 == 2 def test_caller_with_transaction_keyword(math_contract): diff --git a/web3/contract.py b/web3/contract.py index 3786f3b6cd..f30d3af61b 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1450,7 +1450,14 @@ def factory(cls, class_name, **kwargs): class ContractCaller: - def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): + def __init__(self, + abi, + web3, + address, + *args, + transaction_dict=None, + block_identifier='latest', + **kwargs): if abi: self.web3 = web3 self.abi = abi @@ -1467,7 +1474,9 @@ def __init__(self, abi, web3, address, *args, transaction_dict=None, **kwargs): address=self.address, function_identifier=func['name']) - caller_method = partial(self.call_function, fn, transaction_dict=transaction_dict) + block_id = parse_block_identifier(self.web3, block_identifier) + caller_method = partial(self.call_function, fn, transaction_dict=transaction_dict, block_identifier=block_id) + setattr(self, func['name'], caller_method) def __getattr__(self, function_name): @@ -1484,16 +1493,22 @@ def __getattr__(self, function_name): else: return super().__getattribute__(function_name) - def __call__(self, *args, transaction_dict=None, **kwargs): + def __call__(self, *args, transaction_dict=None, block_identifier='latest', **kwargs): if transaction_dict is None: transaction_dict = {} - return type(self)(self.abi, self.web3, self.address, transaction_dict, **kwargs) + return type(self)(self.abi, + self.web3, + self.address, + *args, + transaction_dict=transaction_dict, + block_identifier=block_identifier, + **kwargs) @staticmethod - def call_function(fn, *args, transaction_dict=None, **kwargs): + def call_function(fn, *args, transaction_dict=None, block_identifier='latest', **kwargs): if transaction_dict is None: transaction_dict = {} - return fn(*args, **kwargs).call(transaction_dict) + return fn(*args, **kwargs).call(transaction_dict, block_identifier) def check_for_forbidden_api_filter_arguments(event_abi, _filters): From 40001747dafd87b63dc16d6fc0dfb772108a7494 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 14:30:21 -0700 Subject: [PATCH 16/30] Test that address gets set correctly --- tests/core/contracts/conftest.py | 46 +++++++++++++++++++ .../test_contract_caller_interface.py | 24 ++++++---- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index ca14b2da32..9ab95343af 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -527,6 +527,52 @@ def FallballFunctionContract(web3, FALLBACK_FUNCTION_CONTRACT): return web3.eth.contract(**FALLBACK_FUNCTION_CONTRACT) +CONTRACT_RETURN_ARGS_SOURCE = """ +contract CallerTester { + function returnMeta() public payable returns (address, bytes memory, uint256, uint256) { + return (msg.sender, msg.data, gasleft(), msg.value); + } +} +""" + +CONTRACT_RETURN_ARGS_CODE = "608060405234801561001057600080fd5b50610184806100206000396000f3fe6080604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663c7fa7d668114610045575b600080fd5b61004d610106565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156100c85781810151838201526020016100b0565b50505050905090810190601f1680156100f55780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058203a3cd0d2aae32b28ac9f745c72a46e285062c22b211be4eae89ae220d06a05280029" # noqa: E501 + +CONTRACT_RETURN_ARGS_RUNTIME = "6080604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663c7fa7d668114610045575b600080fd5b61004d610106565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156100c85781810151838201526020016100b0565b50505050905090810190601f1680156100f55780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058203a3cd0d2aae32b28ac9f745c72a46e285062c22b211be4eae89ae220d06a05280029" # noqa: E501 + +CONTRACT_RETURN_ARGS_ABI = json.loads('[{ "constant": false, "inputs": [], "name": "returnMeta", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "bytes" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" } ]') # noqa: E501 + + +@pytest.fixture() +def RETURN_ARGS_CODE(): + return CONTRACT_RETURN_ARGS_CODE + + +@pytest.fixture() +def RETURN_ARGS_RUNTIME(): + return CONTRACT_RETURN_ARGS_RUNTIME + + +@pytest.fixture() +def RETURN_ARGS_ABI(): + return CONTRACT_RETURN_ARGS_ABI + + +@pytest.fixture() +def RETURN_ARGS_CONTRACT(RETURN_ARGS_CODE, + RETURN_ARGS_RUNTIME, + RETURN_ARGS_ABI): + return { + 'bytecode': RETURN_ARGS_CODE, + 'bytecode_runtime': RETURN_ARGS_RUNTIME, + 'abi': RETURN_ARGS_ABI, + } + + +@pytest.fixture() +def ReturnArgsContract(web3, RETURN_ARGS_CONTRACT): + return web3.eth.contract(**RETURN_ARGS_CONTRACT) + + class LogFunctions: LogAnonymous = 0 LogNoArguments = 1 diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index 259a709f9e..d36b8011d5 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -26,6 +26,11 @@ def math_contract(web3, MathContract, address_conversion_func): return deploy(web3, MathContract, address_conversion_func) +@pytest.fixture() +def return_args_contract(web3, ReturnArgsContract, address_conversion_func): + return deploy(web3, ReturnArgsContract, address_conversion_func) + + def test_caller_default(math_contract): result = math_contract.caller.add(3, 5) assert result == 8 @@ -36,11 +41,6 @@ def test_caller_with_parens(math_contract): assert result == 13 -def test_caller_with_parens_and_transaction_dict(math_contract): - result = math_contract.caller({'from': 'notarealaddress.eth'}).add(2, 3) - assert result == 5 - - def test_caller_with_no_abi(web3): contract = web3.eth.contract() with pytest.raises(NoABIFunctionsFound): @@ -68,7 +68,13 @@ def test_caller_with_block_identifier(web3, math_contract): assert output2 == 2 -def test_caller_with_transaction_keyword(math_contract): - # TODO: Make sure from is actually set correctly here. - result = math_contract.caller(transaction={'from': 'notarealaddress.eth'}).return13() - assert result == 13 +def test_caller_with_transaction_keyword(web3, return_args_contract): + address = web3.eth.accounts[0] + contract = return_args_contract.caller(transaction_dict={'from': address}) + assert contract.returnMeta() == [address, b'\xc7\xfa}f', 45532, 0] + + +def test_caller_with_dict_but_no_transaction_keyword(web3, return_args_contract): + address = web3.eth.accounts[0] + contract = return_args_contract.caller({'from': address}) + assert contract.returnMeta() == [address, b'\xc7\xfa}f', 45532, 0] From ec73ea1058f1bb350aa2b8d29e7ad6377a00691a Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 14:42:10 -0700 Subject: [PATCH 17/30] Add block number to CallerTester contract --- tests/core/contracts/conftest.py | 10 +++- .../test_contract_caller_interface.py | 60 ++++++++++++++++--- web3/contract.py | 15 +++-- 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 9ab95343af..0dd2d5feb6 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -529,17 +529,21 @@ def FallballFunctionContract(web3, FALLBACK_FUNCTION_CONTRACT): CONTRACT_RETURN_ARGS_SOURCE = """ contract CallerTester { + function add(int256 a, int256 b) public payable returns (int256) { + return a + b; + } + function returnMeta() public payable returns (address, bytes memory, uint256, uint256) { return (msg.sender, msg.data, gasleft(), msg.value); } } """ -CONTRACT_RETURN_ARGS_CODE = "608060405234801561001057600080fd5b50610184806100206000396000f3fe6080604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663c7fa7d668114610045575b600080fd5b61004d610106565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156100c85781810151838201526020016100b0565b50505050905090810190601f1680156100f55780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058203a3cd0d2aae32b28ac9f745c72a46e285062c22b211be4eae89ae220d06a05280029" # noqa: E501 +CONTRACT_RETURN_ARGS_CODE = "608060405234801561001057600080fd5b506101c2806100206000396000f3fe608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610140565b60408051918252519081900360200190f35b610087610144565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156101025781810151838201526020016100ea565b50505050905090810190601f16801561012f5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b0190565b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058207ce1231a8d6d01cbefd5f9e4f5bacccfe954626fcfb8111d06d96f27fc5c6a090029" # noqa: E501 -CONTRACT_RETURN_ARGS_RUNTIME = "6080604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663c7fa7d668114610045575b600080fd5b61004d610106565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156100c85781810151838201526020016100b0565b50505050905090810190601f1680156100f55780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058203a3cd0d2aae32b28ac9f745c72a46e285062c22b211be4eae89ae220d06a05280029" # noqa: E501 +CONTRACT_RETURN_ARGS_RUNTIME = "608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610140565b60408051918252519081900360200190f35b610087610144565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156101025781810151838201526020016100ea565b50505050905090810190601f16801561012f5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b0190565b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058207ce1231a8d6d01cbefd5f9e4f5bacccfe954626fcfb8111d06d96f27fc5c6a090029" # noqa: E501 -CONTRACT_RETURN_ARGS_ABI = json.loads('[{ "constant": false, "inputs": [], "name": "returnMeta", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "bytes" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" } ]') # noqa: E501 +CONTRACT_RETURN_ARGS_ABI = json.loads('[ { "constant": false, "inputs": [ { "name": "a", "type": "int256" }, { "name": "b", "type": "int256" } ], "name": "add", "outputs": [ { "name": "", "type": "int256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [], "name": "returnMeta", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "bytes" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" } ]') # noqa: E501 @pytest.fixture() diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index d36b8011d5..7881d8f344 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -37,8 +37,8 @@ def test_caller_default(math_contract): def test_caller_with_parens(math_contract): - result = math_contract.caller().return13() - assert result == 13 + result = math_contract.caller().add(3, 5) + assert result == 8 def test_caller_with_no_abi(web3): @@ -69,12 +69,56 @@ def test_caller_with_block_identifier(web3, math_contract): def test_caller_with_transaction_keyword(web3, return_args_contract): - address = web3.eth.accounts[0] - contract = return_args_contract.caller(transaction_dict={'from': address}) - assert contract.returnMeta() == [address, b'\xc7\xfa}f', 45532, 0] + address = web3.eth.accounts[1] + transaction_dict = { + 'from': address, + 'gas': 210000, + 'gasPrice': web3.toWei(.001, 'ether'), + 'value': 12345, + } + contract = return_args_contract.caller(transaction_dict=transaction_dict) + + sender, _, gasLeft, value = contract.returnMeta() + + assert address == sender + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] def test_caller_with_dict_but_no_transaction_keyword(web3, return_args_contract): - address = web3.eth.accounts[0] - contract = return_args_contract.caller({'from': address}) - assert contract.returnMeta() == [address, b'\xc7\xfa}f', 45532, 0] + address = web3.eth.accounts[1] + transaction_dict = { + 'from': address, + 'gas': 210000, + 'gasPrice': web3.toWei(.001, 'ether'), + 'value': 12345, + } + + contract = return_args_contract.caller(transaction_dict) + + sender, _, gasLeft, value = contract.returnMeta() + + assert address == sender + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] + + +def test_caller_with_args_and_no_transaction_keyword(web3, return_args_contract): + address = web3.eth.accounts[1] + transaction_dict = { + 'from': address, + 'gas': 210000, + 'gasPrice': web3.toWei(.001, 'ether'), + 'value': 12345, + } + + contract = return_args_contract.caller(transaction_dict) + + sender, _, gasLeft, value = contract.returnMeta() + + assert address == sender + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] + + add_result = contract.add(3, 5) + assert add_result == 8 diff --git a/web3/contract.py b/web3/contract.py index f30d3af61b..40e4750835 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1458,10 +1458,12 @@ def __init__(self, transaction_dict=None, block_identifier='latest', **kwargs): + self.web3 = web3 + self.address = address + if abi: - self.web3 = web3 self.abi = abi - self.address = address + if transaction_dict is None: transaction_dict = {} @@ -1475,7 +1477,11 @@ def __init__(self, function_identifier=func['name']) block_id = parse_block_identifier(self.web3, block_identifier) - caller_method = partial(self.call_function, fn, transaction_dict=transaction_dict, block_identifier=block_id) + caller_method = partial(self.call_function, + fn, + *args, + transaction_dict=transaction_dict, + block_identifier=block_id) setattr(self, func['name'], caller_method) @@ -1493,13 +1499,12 @@ def __getattr__(self, function_name): else: return super().__getattribute__(function_name) - def __call__(self, *args, transaction_dict=None, block_identifier='latest', **kwargs): + def __call__(self, transaction_dict=None, block_identifier='latest', **kwargs): if transaction_dict is None: transaction_dict = {} return type(self)(self.abi, self.web3, self.address, - *args, transaction_dict=transaction_dict, block_identifier=block_identifier, **kwargs) From 9ef6efe7e643a4b7ef46fdd8849bd167c8389e2f Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 14:51:46 -0700 Subject: [PATCH 18/30] Add block identifier to CallerTester contract Remove unneeded args and kwargs --- tests/core/contracts/conftest.py | 10 +++++----- tests/core/contracts/test_contract_caller_interface.py | 6 +++--- web3/contract.py | 10 +++------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 0dd2d5feb6..614dbef2d5 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -533,17 +533,17 @@ def FallballFunctionContract(web3, FALLBACK_FUNCTION_CONTRACT): return a + b; } - function returnMeta() public payable returns (address, bytes memory, uint256, uint256) { - return (msg.sender, msg.data, gasleft(), msg.value); + function returnMeta() public payable returns (address, bytes memory, uint256, uint, uint) { + return (msg.sender, msg.data, gasleft(), msg.value, block.number); } } """ -CONTRACT_RETURN_ARGS_CODE = "608060405234801561001057600080fd5b506101c2806100206000396000f3fe608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610140565b60408051918252519081900360200190f35b610087610144565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156101025781810151838201526020016100ea565b50505050905090810190601f16801561012f5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b0190565b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058207ce1231a8d6d01cbefd5f9e4f5bacccfe954626fcfb8111d06d96f27fc5c6a090029" # noqa: E501 +CONTRACT_RETURN_ARGS_CODE = "608060405234801561001057600080fd5b506101d4806100206000396000f3fe608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610147565b60408051918252519081900360200190f35b61008761014b565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001858152602001848152602001838152602001828103825286818151815260200191508051906020019080838360005b838110156101085781810151838201526020016100f0565b50505050905090810190601f1680156101355780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b0190565b600060606000806000336000365a344385955084848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250989e929d50949b509299509097509550505050505056fea165627a7a7230582009a1e09c8cb9406971ab18e5c44bbc09e03adbae9b66c9d6b68a9fa503d495c90029" # noqa: E501 -CONTRACT_RETURN_ARGS_RUNTIME = "608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610140565b60408051918252519081900360200190f35b610087610144565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001848152602001838152602001828103825285818151815260200191508051906020019080838360005b838110156101025781810151838201526020016100ea565b50505050905090810190601f16801561012f5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b0190565b60006060600080336000365a604080516020601f850181900481028201810190925283815234918590859081908401838280828437600092019190915250979c929b509399509197509550505050505056fea165627a7a723058207ce1231a8d6d01cbefd5f9e4f5bacccfe954626fcfb8111d06d96f27fc5c6a090029" # noqa: E501 +CONTRACT_RETURN_ARGS_RUNTIME = "608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610147565b60408051918252519081900360200190f35b61008761014b565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001858152602001848152602001838152602001828103825286818151815260200191508051906020019080838360005b838110156101085781810151838201526020016100f0565b50505050905090810190601f1680156101355780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b0190565b600060606000806000336000365a344385955084848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250989e929d50949b509299509097509550505050505056fea165627a7a7230582009a1e09c8cb9406971ab18e5c44bbc09e03adbae9b66c9d6b68a9fa503d495c90029" # noqa: E501 -CONTRACT_RETURN_ARGS_ABI = json.loads('[ { "constant": false, "inputs": [ { "name": "a", "type": "int256" }, { "name": "b", "type": "int256" } ], "name": "add", "outputs": [ { "name": "", "type": "int256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [], "name": "returnMeta", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "bytes" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" } ]') # noqa: E501 +CONTRACT_RETURN_ARGS_ABI = json.loads('[ { "constant": false, "inputs": [ { "name": "a", "type": "int256" }, { "name": "b", "type": "int256" } ], "name": "add", "outputs": [ { "name": "", "type": "int256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [], "name": "returnMeta", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "bytes" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" } ]') # noqa: E501 @pytest.fixture() diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index 7881d8f344..8709721605 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -78,7 +78,7 @@ def test_caller_with_transaction_keyword(web3, return_args_contract): } contract = return_args_contract.caller(transaction_dict=transaction_dict) - sender, _, gasLeft, value = contract.returnMeta() + sender, _, gasLeft, value, _ = contract.returnMeta() assert address == sender assert gasLeft <= transaction_dict['gas'] @@ -96,7 +96,7 @@ def test_caller_with_dict_but_no_transaction_keyword(web3, return_args_contract) contract = return_args_contract.caller(transaction_dict) - sender, _, gasLeft, value = contract.returnMeta() + sender, _, gasLeft, value, _ = contract.returnMeta() assert address == sender assert gasLeft <= transaction_dict['gas'] @@ -114,7 +114,7 @@ def test_caller_with_args_and_no_transaction_keyword(web3, return_args_contract) contract = return_args_contract.caller(transaction_dict) - sender, _, gasLeft, value = contract.returnMeta() + sender, _, gasLeft, value, _ = contract.returnMeta() assert address == sender assert gasLeft <= transaction_dict['gas'] diff --git a/web3/contract.py b/web3/contract.py index 40e4750835..56b8b45abd 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1454,10 +1454,8 @@ def __init__(self, abi, web3, address, - *args, transaction_dict=None, - block_identifier='latest', - **kwargs): + block_identifier='latest'): self.web3 = web3 self.address = address @@ -1479,7 +1477,6 @@ def __init__(self, block_id = parse_block_identifier(self.web3, block_identifier) caller_method = partial(self.call_function, fn, - *args, transaction_dict=transaction_dict, block_identifier=block_id) @@ -1499,15 +1496,14 @@ def __getattr__(self, function_name): else: return super().__getattribute__(function_name) - def __call__(self, transaction_dict=None, block_identifier='latest', **kwargs): + def __call__(self, transaction_dict=None, block_identifier='latest'): if transaction_dict is None: transaction_dict = {} return type(self)(self.abi, self.web3, self.address, transaction_dict=transaction_dict, - block_identifier=block_identifier, - **kwargs) + block_identifier=block_identifier) @staticmethod def call_function(fn, *args, transaction_dict=None, block_identifier='latest', **kwargs): From 40aa86aebad251ebbd16db1092771bf662bcf654 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 15:03:09 -0700 Subject: [PATCH 19/30] Refactor out none_or_zero_address function --- ens/main.py | 20 +++++++++++--------- ens/utils.py | 4 ++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ens/main.py b/ens/main.py index 8695e6e0ea..bf26a4b7cf 100644 --- a/ens/main.py +++ b/ens/main.py @@ -23,6 +23,7 @@ init_web3, is_valid_name, label_to_hash, + none_or_zero_address, normal_name_to_hash, normalize_name, raw_name_to_hash, @@ -112,7 +113,7 @@ def setup_address(self, name, address=default, transact={}): """ owner = self.setup_owner(name, transact=transact) self._assert_control(owner, name) - if not address or address == EMPTY_ADDR_HEX: + if none_or_zero_address(address): address = None elif address is default: address = owner @@ -149,7 +150,7 @@ def setup_name(self, name, address=None, transact={}): return self._setup_reverse(None, address, transact=transact) else: resolved = self.address(name) - if not address or address == EMPTY_ADDR_HEX: + if none_or_zero_address(address): address = resolved elif resolved and address != resolved and resolved != EMPTY_ADDR_HEX: raise AddressMismatch( @@ -158,9 +159,9 @@ def setup_name(self, name, address=None, transact={}): address, resolved ) ) - if not address or address == EMPTY_ADDR_HEX: + if none_or_zero_address(address): address = self.owner(name) - if not address or address == EMPTY_ADDR_HEX: + if none_or_zero_address(address): raise UnownedName("claim subdomain using setup_address() first") if is_binary_address(address): address = to_checksum_address(address) @@ -177,7 +178,8 @@ def resolve(self, name, get='addr'): if resolver: lookup_function = getattr(resolver.functions, get) namehash = normal_name_to_hash(normal_name) - if lookup_function(namehash).call() == EMPTY_ADDR_HEX: # TODO: is there a better way? + address = lookup_function(namehash).call() + if none_or_zero_address(address): return None return lookup_function(namehash).call() else: @@ -185,7 +187,7 @@ def resolve(self, name, get='addr'): def resolver(self, normal_name): resolver_addr = self.ens.caller.resolver(normal_name_to_hash(normal_name)) - if not resolver_addr or resolver_addr == EMPTY_ADDR_HEX: + if none_or_zero_address(resolver_addr): return None return self._resolverContract(address=resolver_addr) @@ -266,10 +268,10 @@ def _first_owner(self, name): owner = None unowned = [] pieces = normalize_name(name).split('.') - while pieces and (owner == EMPTY_ADDR_HEX or not owner): + while pieces and none_or_zero_address(owner): name = '.'.join(pieces) owner = self.owner(name) - if owner == EMPTY_ADDR_HEX or not owner: + if none_or_zero_address(owner): unowned.append(pieces.pop(0)) return (owner, unowned, name) @@ -286,7 +288,7 @@ def _claim_ownership(self, owner, unowned, owned, old_owner=None, transact={}): @dict_copy def _set_resolver(self, name, resolver_addr=None, transact={}): - if not resolver_addr or resolver_addr == EMPTY_ADDR_HEX: + if none_or_zero_address(resolver_addr): resolver_addr = self.address('resolver.eth') namehash = raw_name_to_hash(name) if self.ens.caller.resolver(namehash) != resolver_addr: diff --git a/ens/utils.py b/ens/utils.py index 439acfe106..c4c369e1bd 100644 --- a/ens/utils.py +++ b/ens/utils.py @@ -209,3 +209,7 @@ def assert_signer_in_modifier_kwargs(modifier_kwargs): raise TypeError(ERR_MSG) return modifier_dict['from'] + + +def none_or_zero_address(addr): + return not addr or addr == '0x' + '00' * 20 From 0ed10a1fc19a4dd20e384db9bdf6e4d72587cc1d Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 15:07:40 -0700 Subject: [PATCH 20/30] Add documentation to ContractCaller class --- docs/contracts.rst | 30 ++++++++++++++++++++++++++++++ web3/contract.py | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/docs/contracts.rst b/docs/contracts.rst index d0aa0e7555..d65e14acd9 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -745,3 +745,33 @@ Utils '_debatingPeriod': 604800, '_newCurator': True}) +ContractCaller +-------------- + +.. py:class:: ContractCaller + The :py:attr:`Contract.caller` is a shorthand way to call functions in a contract. This class is not to be used directly, but instead through :py:attr:`Contract.caller`. + +There are a number of different ways to invoke the :py:attr:`Contract.caller`. + +For example: + + .. code-block:: python + >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) + >>> twentyone = myContract.caller.multiply7(3) +It can also be invoked using parentheses: + + .. code-block:: python + >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) + >>> twentyone = myContract.caller().multiply7(3) +And a transaction dictionary, with or without the `transaction_dict` keyword. For example: + + .. code-block:: python + >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) + >>> twentyone = myContract.caller({'from': '0x...'}).multiply7(3) + >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) + >>> twentyone = myContract.caller(transaction_dict={'from': '0x...'}).multiply7(3) +Like :py:class:`ContractFunction`, :py:class:`ContractCaller` +provides methods to interact with contract functions. +Positional and keyword arguments supplied to the contract caller subclass +will be used to find the contract function by signature, +and forwarded to the contract function when applicable. diff --git a/web3/contract.py b/web3/contract.py index 56b8b45abd..eb795f9d76 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1450,6 +1450,19 @@ def factory(cls, class_name, **kwargs): class ContractCaller: + """ + An alternative Contract API. + This call: + > contract.caller({'from': eth.accounts[1], 'gas': 100000, ...}).add(2, 3) + is equivalent to this call in the classic contract: + > contract.functions.add(2, 3).call({'from': eth.accounts[1], 'gas': 100000, ...}) + Other options for invoking this class include: + > contract.caller.add(2, 3) + or + > contract.caller().add(2, 3) + or + > contract.caller(transaction_dict={'from': eth.accounts[1], 'gas': 100000, ...}).add(2, 3) + """ def __init__(self, abi, web3, From d0bc7cca8b62d31989e6cc758767cda1f9fcf962 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 30 Jan 2019 15:33:59 -0700 Subject: [PATCH 21/30] Whitespace --- tests/core/contracts/test_contract_call_interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py index 0796bc1928..b65b59ad66 100644 --- a/tests/core/contracts/test_contract_call_interface.py +++ b/tests/core/contracts/test_contract_call_interface.py @@ -28,7 +28,6 @@ ValidationError, ) - # Ignore warning in pyethereum 1.6 - will go away with the upgrade pytestmark = pytest.mark.filterwarnings("ignore:implicit cast from 'char *'") From 93d450196a058bab4fdd9cf9c3fe70f34e366b27 Mon Sep 17 00:00:00 2001 From: Keri Date: Thu, 31 Jan 2019 11:51:36 -0700 Subject: [PATCH 22/30] More whitespace --- docs/contracts.rst | 3 +++ web3/contract.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/contracts.rst b/docs/contracts.rst index d65e14acd9..4cbd7f5263 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -758,11 +758,13 @@ For example: .. code-block:: python >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) >>> twentyone = myContract.caller.multiply7(3) + It can also be invoked using parentheses: .. code-block:: python >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) >>> twentyone = myContract.caller().multiply7(3) + And a transaction dictionary, with or without the `transaction_dict` keyword. For example: .. code-block:: python @@ -770,6 +772,7 @@ And a transaction dictionary, with or without the `transaction_dict` keyword. Fo >>> twentyone = myContract.caller({'from': '0x...'}).multiply7(3) >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) >>> twentyone = myContract.caller(transaction_dict={'from': '0x...'}).multiply7(3) + Like :py:class:`ContractFunction`, :py:class:`ContractCaller` provides methods to interact with contract functions. Positional and keyword arguments supplied to the contract caller subclass diff --git a/web3/contract.py b/web3/contract.py index eb795f9d76..ab274f5ed0 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1452,15 +1452,23 @@ def factory(cls, class_name, **kwargs): class ContractCaller: """ An alternative Contract API. + This call: + > contract.caller({'from': eth.accounts[1], 'gas': 100000, ...}).add(2, 3) is equivalent to this call in the classic contract: > contract.functions.add(2, 3).call({'from': eth.accounts[1], 'gas': 100000, ...}) + Other options for invoking this class include: + > contract.caller.add(2, 3) + or + > contract.caller().add(2, 3) + or + > contract.caller(transaction_dict={'from': eth.accounts[1], 'gas': 100000, ...}).add(2, 3) """ def __init__(self, From e35362a2bc28be064538ea46a3a6df224e8e40e4 Mon Sep 17 00:00:00 2001 From: Keri Date: Thu, 31 Jan 2019 16:03:27 -0700 Subject: [PATCH 23/30] Test deprecation methods, Change none_or_zero_address to is_none_or_zero_address --- ens/main.py | 20 +++++++++---------- ens/utils.py | 2 +- tests/core/contracts/test_concise_contract.py | 6 ++++++ .../core/contracts/test_implicit_contract.py | 5 +++++ web3/contract.py | 2 +- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/ens/main.py b/ens/main.py index bf26a4b7cf..01793a650a 100644 --- a/ens/main.py +++ b/ens/main.py @@ -23,7 +23,7 @@ init_web3, is_valid_name, label_to_hash, - none_or_zero_address, + is_none_or_zero_address, normal_name_to_hash, normalize_name, raw_name_to_hash, @@ -113,7 +113,7 @@ def setup_address(self, name, address=default, transact={}): """ owner = self.setup_owner(name, transact=transact) self._assert_control(owner, name) - if none_or_zero_address(address): + if is_none_or_zero_address(address): address = None elif address is default: address = owner @@ -150,7 +150,7 @@ def setup_name(self, name, address=None, transact={}): return self._setup_reverse(None, address, transact=transact) else: resolved = self.address(name) - if none_or_zero_address(address): + if is_none_or_zero_address(address): address = resolved elif resolved and address != resolved and resolved != EMPTY_ADDR_HEX: raise AddressMismatch( @@ -159,9 +159,9 @@ def setup_name(self, name, address=None, transact={}): address, resolved ) ) - if none_or_zero_address(address): + if is_none_or_zero_address(address): address = self.owner(name) - if none_or_zero_address(address): + if is_none_or_zero_address(address): raise UnownedName("claim subdomain using setup_address() first") if is_binary_address(address): address = to_checksum_address(address) @@ -179,7 +179,7 @@ def resolve(self, name, get='addr'): lookup_function = getattr(resolver.functions, get) namehash = normal_name_to_hash(normal_name) address = lookup_function(namehash).call() - if none_or_zero_address(address): + if is_none_or_zero_address(address): return None return lookup_function(namehash).call() else: @@ -187,7 +187,7 @@ def resolve(self, name, get='addr'): def resolver(self, normal_name): resolver_addr = self.ens.caller.resolver(normal_name_to_hash(normal_name)) - if none_or_zero_address(resolver_addr): + if is_none_or_zero_address(resolver_addr): return None return self._resolverContract(address=resolver_addr) @@ -268,10 +268,10 @@ def _first_owner(self, name): owner = None unowned = [] pieces = normalize_name(name).split('.') - while pieces and none_or_zero_address(owner): + while pieces and is_none_or_zero_address(owner): name = '.'.join(pieces) owner = self.owner(name) - if none_or_zero_address(owner): + if is_none_or_zero_address(owner): unowned.append(pieces.pop(0)) return (owner, unowned, name) @@ -288,7 +288,7 @@ def _claim_ownership(self, owner, unowned, owned, old_owner=None, transact={}): @dict_copy def _set_resolver(self, name, resolver_addr=None, transact={}): - if none_or_zero_address(resolver_addr): + if is_none_or_zero_address(resolver_addr): resolver_addr = self.address('resolver.eth') namehash = raw_name_to_hash(name) if self.ens.caller.resolver(namehash) != resolver_addr: diff --git a/ens/utils.py b/ens/utils.py index c4c369e1bd..f420549cdb 100644 --- a/ens/utils.py +++ b/ens/utils.py @@ -211,5 +211,5 @@ def assert_signer_in_modifier_kwargs(modifier_kwargs): return modifier_dict['from'] -def none_or_zero_address(addr): +def is_none_or_zero_address(addr): return not addr or addr == '0x' + '00' * 20 diff --git a/tests/core/contracts/test_concise_contract.py b/tests/core/contracts/test_concise_contract.py index 377012b479..fa2f0d58a7 100644 --- a/tests/core/contracts/test_concise_contract.py +++ b/tests/core/contracts/test_concise_contract.py @@ -133,3 +133,9 @@ def getValue(): with pytest.raises(AttributeError, match=r'Namespace collision .* with ConciseContract API.'): concise_contract.getValue() + + +def test_concisecontract_deprecation_warning(web3, StringContract): + contract = deploy(web3, StringContract, args=["blarg"]) + with pytest.warns(DeprecationWarning): + ConciseContract(contract) diff --git a/tests/core/contracts/test_implicit_contract.py b/tests/core/contracts/test_implicit_contract.py index 5defe47617..b29af3c4ae 100644 --- a/tests/core/contracts/test_implicit_contract.py +++ b/tests/core/contracts/test_implicit_contract.py @@ -99,3 +99,8 @@ def test_implicitcontract_transact_override(math_contract, get_transaction_count assert get_transaction_count(blocknum) == starting_txns # Check that no blocks were mined assert get_transaction_count("pending") == (blocknum, 0) + + +def test_implicitcontract_deprecation_warning(math_contract): + with pytest.warns(DeprecationWarning): + math_contract.counter(transact={}) diff --git a/web3/contract.py b/web3/contract.py index ab274f5ed0..4685ced2af 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -968,6 +968,7 @@ def __call_by_default(self, args): return function_abi['constant'] if 'constant' in function_abi.keys() else False + @deprecated_for("classic contract syntax. Ex: contract.functions.withdraw(amount).transact({})") def __call__(self, *args, **kwargs): # Modifier is not provided and method is not constant/pure do a transaction instead if not kwargs and not self.__call_by_default(args): @@ -992,7 +993,6 @@ class ImplicitContract(ConciseContract): > contract.functions.withdraw(amount).transact({}) """ - @deprecated_for("classic contract syntax. Ex: contract.functions.withdraw(amount).transact({})") def __init__(self, classic_contract, method_class=ImplicitMethod): super().__init__(classic_contract, method_class=method_class) From 1b17a111fd058e548c6a381de3d5988859beb18e Mon Sep 17 00:00:00 2001 From: Keri Date: Fri, 1 Feb 2019 11:10:01 -0700 Subject: [PATCH 24/30] ContractCaller docs are tested --- docs/contracts.rst | 43 ++++++++++++++----- .../test_contract_caller_interface.py | 19 ++++++++ web3/contract.py | 23 ++++++---- web3/exceptions.py | 9 +++- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index 4cbd7f5263..b30437b658 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -755,23 +755,44 @@ There are a number of different ways to invoke the :py:attr:`Contract.caller`. For example: - .. code-block:: python - >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) - >>> twentyone = myContract.caller.multiply7(3) +.. testsetup:: + + import json + from web3 import Web3 + w3 = Web3(Web3.EthereumTesterProvider()) + bytecode = "0x606060405261022e806100126000396000f360606040523615610074576000357c01000000000000000000000000000000000000000000000000000000009004806316216f391461007657806361bc221a146100995780637cf5dab0146100bc578063a5f3c23b146100e8578063d09de08a1461011d578063dcf537b11461014057610074565b005b610083600480505061016c565b6040518082815260200191505060405180910390f35b6100a6600480505061017f565b6040518082815260200191505060405180910390f35b6100d26004808035906020019091905050610188565b6040518082815260200191505060405180910390f35b61010760048080359060200190919080359060200190919050506101ea565b6040518082815260200191505060405180910390f35b61012a6004805050610201565b6040518082815260200191505060405180910390f35b6101566004808035906020019091905050610217565b6040518082815260200191505060405180910390f35b6000600d9050805080905061017c565b90565b60006000505481565b6000816000600082828250540192505081905550600060005054905080507f3496c3ede4ec3ab3686712aa1c238593ea6a42df83f98a5ec7df9834cfa577c5816040518082815260200191505060405180910390a18090506101e5565b919050565b6000818301905080508090506101fb565b92915050565b600061020d6001610188565b9050610214565b90565b60006007820290508050809050610229565b91905056" + 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"}]') + contract = w3.eth.contract(abi=ABI, bytecode=bytecode) + deploy_txn = contract.constructor().transact() + deploy_receipt = w3.eth.waitForTransactionReceipt(deploy_txn) + address = deploy_receipt.contractAddress + +.. doctest:: + + >>> myContract = w3.eth.contract(address=address, abi=ABI) + >>> twentyone = myContract.caller.multiply7(3) + >>> twentyone + 21 It can also be invoked using parentheses: - .. code-block:: python - >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) - >>> twentyone = myContract.caller().multiply7(3) +.. doctest:: + + >>> twentyone = myContract.caller().multiply7(3) + >>> twentyone + 21 And a transaction dictionary, with or without the `transaction_dict` keyword. For example: - .. code-block:: python - >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) - >>> twentyone = myContract.caller({'from': '0x...'}).multiply7(3) - >>> myContract = web3.eth.contract(address=contract_address, abi=contract_abi) - >>> twentyone = myContract.caller(transaction_dict={'from': '0x...'}).multiply7(3) +.. doctest:: + + >>> from_address = w3.eth.accounts[1] + >>> twentyone = myContract.caller({'from': from_address}).multiply7(3) + >>> twentyone + 21 + >>> twentyone = myContract.caller(transaction_dict={'from': from_address}).multiply7(3) + >>> twentyone + 21 Like :py:class:`ContractFunction`, :py:class:`ContractCaller` provides methods to interact with contract functions. diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index 8709721605..4d5b2344a8 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -5,6 +5,7 @@ ) from web3.exceptions import ( MismatchedABI, + NoABIFound, NoABIFunctionsFound, ) @@ -43,6 +44,24 @@ def test_caller_with_parens(math_contract): def test_caller_with_no_abi(web3): contract = web3.eth.contract() + with pytest.raises(NoABIFound): + contract.caller.thisFunctionDoesNotExist() + + +def test_caller_with_no_abi_and_parens(web3): + contract = web3.eth.contract() + with pytest.raises(NoABIFound): + contract.caller().thisFunctionDoesNotExist() + + +def test_caller_with_empty_abi_and_parens(web3): + contract = web3.eth.contract(abi=[]) + with pytest.raises(NoABIFunctionsFound): + contract.caller().thisFunctionDoesNotExist() + + +def test_caller_with_empty_abi(web3): + contract = web3.eth.contract(abi=[]) with pytest.raises(NoABIFunctionsFound): contract.caller.thisFunctionDoesNotExist() diff --git a/web3/contract.py b/web3/contract.py index 4685ced2af..30b8c88bf4 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -87,6 +87,7 @@ BlockNumberOutofRange, FallbackNotFound, MismatchedABI, + NoABIFound, NoABIEventsFound, NoABIFunctionsFound, ) @@ -1479,10 +1480,10 @@ def __init__(self, block_identifier='latest'): self.web3 = web3 self.address = address + self.abi = abi + self._functions = None if abi: - self.abi = abi - if transaction_dict is None: transaction_dict = {} @@ -1504,15 +1505,21 @@ def __init__(self, setattr(self, func['name'], caller_method) def __getattr__(self, function_name): - if '_functions' not in self.__dict__: + if self.abi is None: + raise NoABIFound( + "There is no ABI found for this contract.", + ) + if not self._functions or len(self._functions) == 0: raise NoABIFunctionsFound( - "The abi for this contract contains no function definitions. ", - "Are you sure you provided the correct contract abi?" + "The ABI for this contract contains no function definitions. ", + "Are you sure you provided the correct contract ABI?" ) - elif function_name not in self.__dict__['_functions']: + elif function_name not in self._functions: + functions_available = ', '.join([fn['name'] for fn in self._functions]) raise MismatchedABI( - "The function '{}' was not found in this contract's abi. ".format(function_name), - "Are you sure you provided the correct contract abi?" + "The function '{}' was not found in this contract's ABI. ".format(function_name), + "Here is a list of all of the function names found: {}".format(functions_available), + "Did you mean to call one of those functions?" ) else: return super().__getattribute__(function_name) diff --git a/web3/exceptions.py b/web3/exceptions.py index 475d56bdfc..ea53be27db 100644 --- a/web3/exceptions.py +++ b/web3/exceptions.py @@ -82,7 +82,14 @@ class ValidationError(Exception): class NoABIFunctionsFound(AttributeError): """ - Raised when an ABI doesn't contain any functions. + Raised when an ABI is present, but doesn't contain any functions. + """ + pass + + +class NoABIFound(AttributeError): + """ + Raised when no ABI is present. """ pass From ca7919f0b78178e0cbd2186dc7078f0da911cbdc Mon Sep 17 00:00:00 2001 From: Keri Date: Fri, 1 Feb 2019 11:26:16 -0700 Subject: [PATCH 25/30] Sort imports correctly --- ens/main.py | 2 +- web3/contract.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ens/main.py b/ens/main.py index 01793a650a..9789a2d405 100644 --- a/ens/main.py +++ b/ens/main.py @@ -21,9 +21,9 @@ default, dict_copy, init_web3, + is_none_or_zero_address, is_valid_name, label_to_hash, - is_none_or_zero_address, normal_name_to_hash, normalize_name, raw_name_to_hash, diff --git a/web3/contract.py b/web3/contract.py index 30b8c88bf4..c8a6d36535 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -87,8 +87,8 @@ BlockNumberOutofRange, FallbackNotFound, MismatchedABI, - NoABIFound, NoABIEventsFound, + NoABIFound, NoABIFunctionsFound, ) From 7d551bee9f09dfb9ba06d50034aa7e797c40e502 Mon Sep 17 00:00:00 2001 From: Keri Date: Fri, 1 Feb 2019 12:39:16 -0700 Subject: [PATCH 26/30] Add deprecation warnings to ConciseContract and ImplicitContract in docs --- docs/contracts.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index b30437b658..93e5b0f179 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -26,7 +26,6 @@ To run this example, you will need to install a few extra features: from web3 import Web3 from solc import compile_source - from web3.contract import ConciseContract # Solidity source code contract_source_code = """ @@ -89,11 +88,6 @@ To run this example, you will need to install a few extra features: greeter.functions.greet().call() )) - # When issuing a lot of reads, try this more concise reader: - reader = ConciseContract(greeter) - assert reader.greet() == "Nihao" - - Contract Factories ------------------ @@ -111,6 +105,9 @@ example in :class:`ConciseContract` for specifying an alternate factory. .. py:class:: ConciseContract(Contract()) + .. warning:: Deprecated: This method is deprecated in favor of the :class:`~ContractCaller` API + or the verbose syntax + This variation of :class:`Contract` is designed for more succinct read access, without making write access more wordy. This comes at a cost of losing access to features like ``deploy()`` and properties like ``address``. It is @@ -145,6 +142,8 @@ example in :class:`ConciseContract` for specifying an alternate factory. .. py:class:: ImplicitContract(Contract()) + .. warning:: Deprecated: This method is deprecated in favor of the verbose syntax + This variation mirrors :py:class:`ConciseContract`, but it invokes all methods as a transaction rather than a call, so if the classic contract had a method like ``contract.functions.owner.transact()``, you could call it with ``implicit.owner()`` instead. @@ -749,9 +748,11 @@ ContractCaller -------------- .. py:class:: ContractCaller - The :py:attr:`Contract.caller` is a shorthand way to call functions in a contract. This class is not to be used directly, but instead through :py:attr:`Contract.caller`. -There are a number of different ways to invoke the :py:attr:`Contract.caller`. +The :py:class:``ContractCaller`` class provides an API to call functions in a contract. This class +is not to be used directly, but instead through ``Contract.caller``. + +There are a number of different ways to invoke the ``ContractCaller``. For example: @@ -782,7 +783,7 @@ It can also be invoked using parentheses: >>> twentyone 21 -And a transaction dictionary, with or without the `transaction_dict` keyword. For example: +And a transaction dictionary, with or without the ``transaction_dict`` keyword. For example: .. doctest:: From adcafa5a3bfc7ff5012e24cde5dcecb0a161c6ab Mon Sep 17 00:00:00 2001 From: kclowes Date: Fri, 1 Feb 2019 14:20:49 -0700 Subject: [PATCH 27/30] Add elif and space in error message --- web3/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web3/contract.py b/web3/contract.py index c8a6d36535..b5eed1953f 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1509,7 +1509,7 @@ def __getattr__(self, function_name): raise NoABIFound( "There is no ABI found for this contract.", ) - if not self._functions or len(self._functions) == 0: + elif not self._functions or len(self._functions) == 0: raise NoABIFunctionsFound( "The ABI for this contract contains no function definitions. ", "Are you sure you provided the correct contract ABI?" @@ -1518,7 +1518,7 @@ def __getattr__(self, function_name): functions_available = ', '.join([fn['name'] for fn in self._functions]) raise MismatchedABI( "The function '{}' was not found in this contract's ABI. ".format(function_name), - "Here is a list of all of the function names found: {}".format(functions_available), + "Here is a list of all of the function names found: {}. ".format(functions_available), "Did you mean to call one of those functions?" ) else: From e959c9c0c7e2644ebc19de90df7f01305c466e83 Mon Sep 17 00:00:00 2001 From: Keri Date: Fri, 1 Feb 2019 14:29:16 -0700 Subject: [PATCH 28/30] fix linting --- web3/contract.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web3/contract.py b/web3/contract.py index b5eed1953f..31251c1357 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1518,7 +1518,8 @@ def __getattr__(self, function_name): functions_available = ', '.join([fn['name'] for fn in self._functions]) raise MismatchedABI( "The function '{}' was not found in this contract's ABI. ".format(function_name), - "Here is a list of all of the function names found: {}. ".format(functions_available), + "Here is a list of all of the function names found: ", + "{}. ".format(functions_available), "Did you mean to call one of those functions?" ) else: From 4a6da73aba3581fa1b9b841825ca09b7b2003fc4 Mon Sep 17 00:00:00 2001 From: Keri Date: Wed, 6 Feb 2019 15:52:58 -0700 Subject: [PATCH 29/30] Rename transaction_dict to transaction. Make caller tests better --- docs/contracts.rst | 4 +- tests/core/contracts/conftest.py | 55 +++++++----- .../test_contract_caller_interface.py | 89 ++++++++++++------- web3/contract.py | 26 +++--- 4 files changed, 107 insertions(+), 67 deletions(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index 93e5b0f179..61c6dfbade 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -783,7 +783,7 @@ It can also be invoked using parentheses: >>> twentyone 21 -And a transaction dictionary, with or without the ``transaction_dict`` keyword. For example: +And a transaction dictionary, with or without the ``transaction`` keyword. For example: .. doctest:: @@ -791,7 +791,7 @@ And a transaction dictionary, with or without the ``transaction_dict`` keyword. >>> twentyone = myContract.caller({'from': from_address}).multiply7(3) >>> twentyone 21 - >>> twentyone = myContract.caller(transaction_dict={'from': from_address}).multiply7(3) + >>> twentyone = myContract.caller(transaction={'from': from_address}).multiply7(3) >>> twentyone 21 diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py index 614dbef2d5..4e72b9a213 100644 --- a/tests/core/contracts/conftest.py +++ b/tests/core/contracts/conftest.py @@ -527,54 +527,67 @@ def FallballFunctionContract(web3, FALLBACK_FUNCTION_CONTRACT): return web3.eth.contract(**FALLBACK_FUNCTION_CONTRACT) -CONTRACT_RETURN_ARGS_SOURCE = """ +CONTRACT_CALLER_TESTER_SOURCE = """ contract CallerTester { - function add(int256 a, int256 b) public payable returns (int256) { - return a + b; + int public count; + + function add(int256 a, int256 b) public payable returns (int256) { + return a + b; + } + + function increment() public returns (int256) { + return count += 1; + } + + function counter() public payable returns (int256) { + return count; } function returnMeta() public payable returns (address, bytes memory, uint256, uint, uint) { - return (msg.sender, msg.data, gasleft(), msg.value, block.number); + return (msg.sender, msg.data, gasleft(), msg.value, block.number); } } """ -CONTRACT_RETURN_ARGS_CODE = "608060405234801561001057600080fd5b506101d4806100206000396000f3fe608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610147565b60408051918252519081900360200190f35b61008761014b565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001858152602001848152602001838152602001828103825286818151815260200191508051906020019080838360005b838110156101085781810151838201526020016100f0565b50505050905090810190601f1680156101355780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b0190565b600060606000806000336000365a344385955084848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250989e929d50949b509299509097509550505050505056fea165627a7a7230582009a1e09c8cb9406971ab18e5c44bbc09e03adbae9b66c9d6b68a9fa503d495c90029" # noqa: E501 -CONTRACT_RETURN_ARGS_RUNTIME = "608060405260043610610045577c01000000000000000000000000000000000000000000000000000000006000350463a5f3c23b811461004a578063c7fa7d661461007f575b600080fd5b61006d6004803603604081101561006057600080fd5b5080359060200135610147565b60408051918252519081900360200190f35b61008761014b565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001858152602001848152602001838152602001828103825286818151815260200191508051906020019080838360005b838110156101085781810151838201526020016100f0565b50505050905090810190601f1680156101355780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b0190565b600060606000806000336000365a344385955084848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250989e929d50949b509299509097509550505050505056fea165627a7a7230582009a1e09c8cb9406971ab18e5c44bbc09e03adbae9b66c9d6b68a9fa503d495c90029" # noqa: E501 +CONTRACT_CALLER_TESTER_CODE = "608060405234801561001057600080fd5b50610241806100206000396000f3fe608060405260043610610066577c0100000000000000000000000000000000000000000000000000000000600035046306661abd811461006b57806361bc221a14610092578063a5f3c23b1461009a578063c7fa7d66146100bd578063d09de08a14610185575b600080fd5b34801561007757600080fd5b5061008061019a565b60408051918252519081900360200190f35b6100806101a0565b610080600480360360408110156100b057600080fd5b50803590602001356101a6565b6100c56101aa565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001858152602001848152602001838152602001828103825286818151815260200191508051906020019080838360005b8381101561014657818101518382015260200161012e565b50505050905090810190601f1680156101735780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b34801561019157600080fd5b50610080610207565b60005481565b60005490565b0190565b600060606000806000336000365a344385955084848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250989e929d50949b5092995090975095505050505050565b60008054600101908190559056fea165627a7a72305820ffe1620e420efa326b9c5e4ef9f93cac71cf986196246c7966d71a39259899b10029" # noqa: E501 + + +CONTRACT_CALLER_TESTER_RUNTIME = "608060405260043610610066577c0100000000000000000000000000000000000000000000000000000000600035046306661abd811461006b57806361bc221a14610092578063a5f3c23b1461009a578063c7fa7d66146100bd578063d09de08a14610185575b600080fd5b34801561007757600080fd5b5061008061019a565b60408051918252519081900360200190f35b6100806101a0565b610080600480360360408110156100b057600080fd5b50803590602001356101a6565b6100c56101aa565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001858152602001848152602001838152602001828103825286818151815260200191508051906020019080838360005b8381101561014657818101518382015260200161012e565b50505050905090810190601f1680156101735780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390f35b34801561019157600080fd5b50610080610207565b60005481565b60005490565b0190565b600060606000806000336000365a344385955084848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250989e929d50949b5092995090975095505050505050565b60008054600101908190559056fea165627a7a72305820ffe1620e420efa326b9c5e4ef9f93cac71cf986196246c7966d71a39259899b10029" # noqa: E501 + -CONTRACT_RETURN_ARGS_ABI = json.loads('[ { "constant": false, "inputs": [ { "name": "a", "type": "int256" }, { "name": "b", "type": "int256" } ], "name": "add", "outputs": [ { "name": "", "type": "int256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [], "name": "returnMeta", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "bytes" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" } ]') # noqa: E501 +CONTRACT_CALLER_TESTER_ABI = json.loads('[ { "constant": true, "inputs": [], "name": "count", "outputs": [ { "name": "", "type": "int256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "counter", "outputs": [ { "name": "", "type": "int256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [ { "name": "a", "type": "int256" }, { "name": "b", "type": "int256" } ], "name": "add", "outputs": [ { "name": "", "type": "int256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [], "name": "returnMeta", "outputs": [ { "name": "", "type": "address" }, { "name": "", "type": "bytes" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [], "name": "increment", "outputs": [ { "name": "", "type": "int256" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" } ]') # noqa: E501 @pytest.fixture() -def RETURN_ARGS_CODE(): - return CONTRACT_RETURN_ARGS_CODE +def CALLER_TESTER_CODE(): + return CONTRACT_CALLER_TESTER_CODE @pytest.fixture() -def RETURN_ARGS_RUNTIME(): - return CONTRACT_RETURN_ARGS_RUNTIME +def CALLER_TESTER_RUNTIME(): + return CONTRACT_CALLER_TESTER_RUNTIME @pytest.fixture() -def RETURN_ARGS_ABI(): - return CONTRACT_RETURN_ARGS_ABI +def CALLER_TESTER_ABI(): + return CONTRACT_CALLER_TESTER_ABI @pytest.fixture() -def RETURN_ARGS_CONTRACT(RETURN_ARGS_CODE, - RETURN_ARGS_RUNTIME, - RETURN_ARGS_ABI): +def CALLER_TESTER_CONTRACT(CALLER_TESTER_CODE, + CALLER_TESTER_RUNTIME, + CALLER_TESTER_ABI): return { - 'bytecode': RETURN_ARGS_CODE, - 'bytecode_runtime': RETURN_ARGS_RUNTIME, - 'abi': RETURN_ARGS_ABI, + 'bytecode': CALLER_TESTER_CODE, + 'bytecode_runtime': CALLER_TESTER_RUNTIME, + 'abi': CALLER_TESTER_ABI, } @pytest.fixture() -def ReturnArgsContract(web3, RETURN_ARGS_CONTRACT): - return web3.eth.contract(**RETURN_ARGS_CONTRACT) +def CallerTesterContract(web3, CALLER_TESTER_CONTRACT): + return web3.eth.contract(**CALLER_TESTER_CONTRACT) class LogFunctions: diff --git a/tests/core/contracts/test_contract_caller_interface.py b/tests/core/contracts/test_contract_caller_interface.py index 4d5b2344a8..1a66226a7c 100644 --- a/tests/core/contracts/test_contract_caller_interface.py +++ b/tests/core/contracts/test_contract_caller_interface.py @@ -22,14 +22,29 @@ def deploy(web3, Contract, apply_func=identity, args=None): return contract +@pytest.fixture() +def address(web3): + return web3.eth.accounts[1] + + @pytest.fixture() def math_contract(web3, MathContract, address_conversion_func): return deploy(web3, MathContract, address_conversion_func) @pytest.fixture() -def return_args_contract(web3, ReturnArgsContract, address_conversion_func): - return deploy(web3, ReturnArgsContract, address_conversion_func) +def caller_tester_contract(web3, CallerTesterContract, address_conversion_func): + return deploy(web3, CallerTesterContract, address_conversion_func) + + +@pytest.fixture() +def transaction_dict(web3, address): + return { + 'from': address, + 'gas': 210000, + 'gasPrice': web3.toWei(.001, 'ether'), + 'value': 12345, + } def test_caller_default(math_contract): @@ -87,15 +102,37 @@ def test_caller_with_block_identifier(web3, math_contract): assert output2 == 2 -def test_caller_with_transaction_keyword(web3, return_args_contract): - address = web3.eth.accounts[1] - transaction_dict = { - 'from': address, - 'gas': 210000, - 'gasPrice': web3.toWei(.001, 'ether'), - 'value': 12345, - } - contract = return_args_contract.caller(transaction_dict=transaction_dict) +def test_caller_with_block_identifier_and_transaction_dict(web3, + caller_tester_contract, + transaction_dict, + address): + start_num = web3.eth.getBlock('latest').number + assert caller_tester_contract.caller.counter() == 0 + + web3.provider.make_request(method='evm_mine', params=[5]) + caller_tester_contract.functions.increment().transact() + + block_id = start_num + 6 + contract = caller_tester_contract.caller( + transaction=transaction_dict, + block_identifier=block_id + ) + + sender, _, gasLeft, value, block_num = contract.returnMeta() + counter = contract.counter() + + assert sender == address + assert gasLeft <= transaction_dict['gas'] + assert value == transaction_dict['value'] + assert block_num == block_id + assert counter == 1 + + +def test_caller_with_transaction_keyword(web3, + caller_tester_contract, + transaction_dict, + address): + contract = caller_tester_contract.caller(transaction=transaction_dict) sender, _, gasLeft, value, _ = contract.returnMeta() @@ -104,16 +141,11 @@ def test_caller_with_transaction_keyword(web3, return_args_contract): assert value == transaction_dict['value'] -def test_caller_with_dict_but_no_transaction_keyword(web3, return_args_contract): - address = web3.eth.accounts[1] - transaction_dict = { - 'from': address, - 'gas': 210000, - 'gasPrice': web3.toWei(.001, 'ether'), - 'value': 12345, - } - - contract = return_args_contract.caller(transaction_dict) +def test_caller_with_dict_but_no_transaction_keyword(web3, + caller_tester_contract, + transaction_dict, + address): + contract = caller_tester_contract.caller(transaction_dict) sender, _, gasLeft, value, _ = contract.returnMeta() @@ -122,16 +154,11 @@ def test_caller_with_dict_but_no_transaction_keyword(web3, return_args_contract) assert value == transaction_dict['value'] -def test_caller_with_args_and_no_transaction_keyword(web3, return_args_contract): - address = web3.eth.accounts[1] - transaction_dict = { - 'from': address, - 'gas': 210000, - 'gasPrice': web3.toWei(.001, 'ether'), - 'value': 12345, - } - - contract = return_args_contract.caller(transaction_dict) +def test_caller_with_args_and_no_transaction_keyword(web3, + caller_tester_contract, + transaction_dict, + address): + contract = caller_tester_contract.caller(transaction_dict) sender, _, gasLeft, value, _ = contract.returnMeta() diff --git a/web3/contract.py b/web3/contract.py index 31251c1357..ddf9dbe72f 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -1470,13 +1470,13 @@ class ContractCaller: or - > contract.caller(transaction_dict={'from': eth.accounts[1], 'gas': 100000, ...}).add(2, 3) + > contract.caller(transaction={'from': eth.accounts[1], 'gas': 100000, ...}).add(2, 3) """ def __init__(self, abi, web3, address, - transaction_dict=None, + transaction=None, block_identifier='latest'): self.web3 = web3 self.address = address @@ -1484,8 +1484,8 @@ def __init__(self, self._functions = None if abi: - if transaction_dict is None: - transaction_dict = {} + if transaction is None: + transaction = {} self._functions = filter_by_type('function', self.abi) for func in self._functions: @@ -1499,7 +1499,7 @@ def __init__(self, block_id = parse_block_identifier(self.web3, block_identifier) caller_method = partial(self.call_function, fn, - transaction_dict=transaction_dict, + transaction=transaction, block_identifier=block_id) setattr(self, func['name'], caller_method) @@ -1525,20 +1525,20 @@ def __getattr__(self, function_name): else: return super().__getattribute__(function_name) - def __call__(self, transaction_dict=None, block_identifier='latest'): - if transaction_dict is None: - transaction_dict = {} + def __call__(self, transaction=None, block_identifier='latest'): + if transaction is None: + transaction = {} return type(self)(self.abi, self.web3, self.address, - transaction_dict=transaction_dict, + transaction=transaction, block_identifier=block_identifier) @staticmethod - def call_function(fn, *args, transaction_dict=None, block_identifier='latest', **kwargs): - if transaction_dict is None: - transaction_dict = {} - return fn(*args, **kwargs).call(transaction_dict, block_identifier) + def call_function(fn, *args, transaction=None, block_identifier='latest', **kwargs): + if transaction is None: + transaction = {} + return fn(*args, **kwargs).call(transaction, block_identifier) def check_for_forbidden_api_filter_arguments(event_abi, _filters): From da6888ed62f8024cac5818ef04f52c4b5ea38c0d Mon Sep 17 00:00:00 2001 From: Keri Date: Thu, 7 Feb 2019 11:28:39 -0700 Subject: [PATCH 30/30] Add block_identifier example --- docs/contracts.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index 61c6dfbade..ae7f57bd29 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -783,7 +783,8 @@ It can also be invoked using parentheses: >>> twentyone 21 -And a transaction dictionary, with or without the ``transaction`` keyword. For example: +And a transaction dictionary, with or without the ``transaction`` keyword. +You can also optionally include a block identifier. For example: .. doctest:: @@ -794,6 +795,9 @@ And a transaction dictionary, with or without the ``transaction`` keyword. For e >>> twentyone = myContract.caller(transaction={'from': from_address}).multiply7(3) >>> twentyone 21 + >>> twentyone = myContract.caller(block_identifier='latest').multiply7(3) + >>> twentyone + 21 Like :py:class:`ContractFunction`, :py:class:`ContractCaller` provides methods to interact with contract functions.