diff --git a/docs/contracts.rst b/docs/contracts.rst index afcad36b34..533ebd5010 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -108,8 +108,7 @@ Contract Factories These factories are not intended to be initialized directly. Instead, create contract objects using the :meth:`w3.eth.contract() ` -method. By default, the contract factory is :class:`Contract`. See the -example in :class:`ConciseContract` for specifying an alternate factory. +method. By default, the contract factory is :class:`Contract`. .. py:class:: Contract(address) @@ -118,59 +117,6 @@ example in :class:`ConciseContract` for specifying an alternate factory. The address parameter can be a hex address or an ENS name, like ``mycontract.eth``. -.. 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 - recommended to use the classic ``Contract`` for those use cases. - Just to be be clear, `ConciseContract` only exposes contract functions and all - other `Contract` class methods and properties are not available with the `ConciseContract` - API. This includes but is not limited to ``contract.address``, ``contract.abi``, and - ``contract.deploy()``. - - Create this type of contract by passing a :py:class:`Contract` instance to - :class:`ConciseContract`: - - - .. code-block:: python - - >>> concise = ConciseContract(myContract) - - - This variation invokes all methods as a call, so if the classic contract had a method like - ``contract.functions.owner().call()``, you could call it with ``concise.owner()`` instead. - - For access to send a transaction or estimate gas, you can add a keyword argument like so: - - - .. code-block:: python - - >>> concise.withdraw(amount, transact={'from': eth.accounts[1], 'gas': 100000, ...}) - - >>> # which is equivalent to this transaction in the classic contract: - - >>> contract.functions.withdraw(amount).transact({'from': eth.accounts[1], 'gas': 100000, ...}) - -.. 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. - - Create this type of contract by passing a :py:class:`Contract` instance to - :class:`ImplicitContract`: - - - .. code-block:: python - - >>> concise = ImplicitContract(myContract) - Properties ---------- diff --git a/docs/web3.eth.rst b/docs/web3.eth.rst index fcd837386b..85ab1d1694 100644 --- a/docs/web3.eth.rst +++ b/docs/web3.eth.rst @@ -1451,11 +1451,4 @@ Contracts .. py:method:: Eth.set_contract_factory(contractFactoryClass) Modify the default contract factory from ``Contract`` to ``contractFactoryClass``. - Future calls to ``Eth.contract()`` will then default to ``contractFactoryClass``. - - An example of an alternative Contract Factory is ``ConciseContract``. - -.. py:method:: Eth.setContractFactory(contractFactoryClass) - - .. warning:: Deprecated: This method is deprecated in favor of - :meth:`~web3.eth.Eth.set_contract_factory()` + Future calls to ``Eth.contract()`` will then default to ``contractFactoryClass``. \ No newline at end of file diff --git a/newsfragments/2505.misc.rst b/newsfragments/2505.misc.rst new file mode 100644 index 0000000000..d0322581dd --- /dev/null +++ b/newsfragments/2505.misc.rst @@ -0,0 +1 @@ +Removed ConciseContract and ImplicitContract from contract. \ No newline at end of file diff --git a/tests/core/contracts/test_concise_contract.py b/tests/core/contracts/test_concise_contract.py deleted file mode 100644 index df0ea771c5..0000000000 --- a/tests/core/contracts/test_concise_contract.py +++ /dev/null @@ -1,137 +0,0 @@ -import pytest -from unittest.mock import ( - Mock, -) - -from eth_utils import ( - decode_hex, -) - -from _utils import ( - deploy, -) -from web3.contract import ( - CONCISE_NORMALIZERS, - ConciseContract, - ConciseMethod, -) - - -@pytest.fixture() -def EMPTY_ADDR(address_conversion_func): - addr = '0x' + '00' * 20 - return address_conversion_func(addr) - - -@pytest.fixture() -def zero_address_contract(w3, WithConstructorAddressArgumentsContract, EMPTY_ADDR): - deploy_txn = WithConstructorAddressArgumentsContract.constructor( - EMPTY_ADDR, - ).transact() - deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn) - assert deploy_receipt is not None - _address_contract = WithConstructorAddressArgumentsContract( - address=deploy_receipt['contractAddress'], - ) - with pytest.warns(DeprecationWarning, match='deprecated in favor of contract.caller'): - return ConciseContract(_address_contract) - - -def test_concisecontract_call_default(): - mock = Mock() - sweet_method = ConciseMethod(mock.functions.grail) - sweet_method(1, 2) - mock.functions.grail.assert_called_once_with(1, 2) - # Checking in return_value, ie the function instance - mock.functions.grail.return_value.call.assert_called_once_with({}) - - -def test_concisecontract_custom_transact(): - mock = Mock() - sweet_method = ConciseMethod(mock.functions.grail) - sweet_method(1, 2, transact={'holy': 3}) - mock.functions.grail.assert_called_once_with(1, 2) - # Checking in return_value, ie the function instance - mock.functions.grail.return_value.transact.assert_called_once_with({'holy': 3}) - - -def test_concisecontract_two_keywords_fail(): - mock = Mock() - sweet_method = ConciseMethod(mock) - with pytest.raises(TypeError): - sweet_method(1, 2, transact={'holy': 3}, call={'count_to': 4}) - - -def test_concisecontract_unknown_keyword_fails(): - contract = Mock() - sweet_method = ConciseMethod(contract.functions.grail) - with pytest.raises(TypeError): - sweet_method(1, 2, count={'to': 5}) - - -def test_concisecontract_returns_none_for_0addr(zero_address_contract): - result = zero_address_contract.testAddr() - assert result is None - - -def test_class_construction_sets_class_vars(w3, - MATH_ABI, - MATH_CODE, - MATH_RUNTIME, - some_address, - ): - MathContract = w3.eth.contract( - abi=MATH_ABI, - bytecode=MATH_CODE, - bytecode_runtime=MATH_RUNTIME, - ) - - classic = MathContract(some_address) - assert classic.w3 == w3 - assert classic.bytecode == decode_hex(MATH_CODE) - assert classic.bytecode_runtime == decode_hex(MATH_RUNTIME) - - -def test_conciscecontract_keeps_custom_normalizers_on_base(w3, MATH_ABI): - base_contract = w3.eth.contract(abi=MATH_ABI) - # give different normalizers to this base instance - base_contract._return_data_normalizers = base_contract._return_data_normalizers + tuple([None]) - - # create concisce contract with custom contract - new_normalizers_size = len(base_contract._return_data_normalizers) - with pytest.warns(DeprecationWarning, match='deprecated in favor of contract.caller'): - concise = ConciseContract(base_contract) - - # check that concise contract includes the new normalizers - concise_normalizers_size = len(concise._classic_contract._return_data_normalizers) - assert concise_normalizers_size == new_normalizers_size + len(CONCISE_NORMALIZERS) - assert concise._classic_contract._return_data_normalizers[0] is None - - -def test_conciscecontract_function_collision( - w3, - StringContract): - - contract = deploy(w3, StringContract, args=["blarg"]) - - def getValue(): - assert 'getValue' in [ - item['name'] for item - in StringContract.abi - if 'name' in item] - - setattr(ConciseContract, 'getValue', getValue) - - with pytest.warns(DeprecationWarning, match='deprecated in favor of contract.caller'): - concise_contract = ConciseContract(contract) - - assert isinstance(concise_contract, ConciseContract) - - with pytest.raises(AttributeError, match=r'Namespace collision .* with ConciseContract API.'): - concise_contract.getValue() - - -def test_concisecontract_deprecation_warning(w3, StringContract): - contract = deploy(w3, 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 deleted file mode 100644 index c1311fc191..0000000000 --- a/tests/core/contracts/test_implicit_contract.py +++ /dev/null @@ -1,119 +0,0 @@ -import pytest - -from eth_utils import ( - is_integer, -) - -from web3.contract import ( - ImplicitContract, -) - - -@pytest.fixture() -def math_contract(w3, MATH_ABI, MATH_CODE, MATH_RUNTIME, address_conversion_func): - # Deploy math contract - # NOTE Must use non-specialized contract factory or else deploy() doesn't work - MathContract = w3.eth.contract( - abi=MATH_ABI, - bytecode=MATH_CODE, - bytecode_runtime=MATH_RUNTIME, - ) - tx_hash = MathContract.constructor().transact() - tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) - math_address = address_conversion_func(tx_receipt['contractAddress']) - # Return interactive contract instance at deployed address - # TODO Does parent class not implement 'deploy()' for a reason? - MathContract = w3.eth.contract( - abi=MATH_ABI, - bytecode=MATH_CODE, - bytecode_runtime=MATH_RUNTIME, - ContractFactoryClass=ImplicitContract, - ) - with pytest.warns(DeprecationWarning, match='deprecated in favor of contract.caller'): - contract = MathContract(math_address) - assert contract.address == math_address - return contract - - -@pytest.fixture() -def get_transaction_count(w3): - def get_transaction_count(blocknum_or_label): - block = w3.eth.get_block(blocknum_or_label) - # Return the blocknum if we requested this via labels - # so we can directly query the block next time (using the same API call) - # Either way, return the number of transactions in the given block - if blocknum_or_label in ["pending", "latest", "earliest"]: - return block.number, len(block.transactions) - else: - return len(block.transactions) - return get_transaction_count - - -def test_implicitcontract_call_default(math_contract, get_transaction_count): - # When a function is called that defaults to call - blocknum, starting_txns = get_transaction_count("pending") - with pytest.warns(DeprecationWarning, match='deprecated in favor of classic contract syntax'): - start_count = math_contract.counter() - assert is_integer(start_count) - # Check that a call was made and not a transact - # (Auto-mining is enabled, so query by block number) - assert get_transaction_count(blocknum) == starting_txns - # Check that no blocks were mined - assert get_transaction_count("pending") == (blocknum, 0) - - -def test_implicitcontract_transact_default(math_contract, get_transaction_count): - # Use to verify correct operation later on - with pytest.warns(DeprecationWarning, match='deprecated in favor of classic contract syntax'): - start_count = math_contract.counter() - - assert is_integer(start_count) # Verify correct type - # When a function is called that defaults to transact - blocknum, starting_txns = get_transaction_count("pending") - with pytest.warns(DeprecationWarning, - match='deprecated in favor of classic contract syntax') as warnings: - math_contract.increment(transact={}) - # Check that a transaction was made and not a call - assert math_contract.counter() - start_count == 1 - # Check that the correct number of warnings are raised - assert len(warnings) == 2 - # (Auto-mining is enabled, so query by block number) - assert get_transaction_count(blocknum) == starting_txns + 1 - # Check that only one block was mined - assert get_transaction_count("pending") == (blocknum + 1, 0) - - -def test_implicitcontract_call_override(math_contract, get_transaction_count): - # When a function is called with transact override that defaults to call - blocknum, starting_txns = get_transaction_count("pending") - with pytest.warns(DeprecationWarning, match='deprecated in favor of classic contract syntax'): - math_contract.counter(transact={}) - # Check that a transaction was made and not a call - # (Auto-mining is enabled, so query by block number) - assert get_transaction_count(blocknum) == starting_txns + 1 - # Check that only one block was mined - assert get_transaction_count("pending") == (blocknum + 1, 0) - - -def test_implicitcontract_transact_override(math_contract, get_transaction_count): - # Use to verify correct operation later on - with pytest.warns(DeprecationWarning, match='deprecated in favor of classic contract syntax'): - start_count = math_contract.counter() - assert is_integer(start_count) # Verify correct type - # When a function is called with call override that defaults to transact - blocknum, starting_txns = get_transaction_count("pending") - with pytest.warns(DeprecationWarning, - match='deprecated in favor of classic contract syntax') as warnings: - math_contract.increment(call={}) - # Check that a call was made and not a transact - assert math_contract.counter() - start_count == 0 - assert len(warnings) == 2 - # (Auto-mining is enabled, so query by block number) - 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, match='deprecated in favor of classic contract syntax'): - math_contract.counter(transact={}) diff --git a/web3/contract.py b/web3/contract.py index db9e5d8118..1a89bff38a 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -42,7 +42,6 @@ to_tuple, ) from eth_utils.toolz import ( - compose, partial, ) from hexbytes import ( @@ -79,9 +78,6 @@ from web3._utils.datatypes import ( PropertyCheckingFactory, ) -from web3._utils.decorators import ( - deprecated_for, -) from web3._utils.empty import ( empty, ) @@ -761,14 +757,6 @@ def find_functions_by_identifier(cls, ) -def mk_collision_prop(fn_name: str) -> Callable[[], None]: - def collision_fn() -> NoReturn: - msg = f"Namespace collision for function name {fn_name} with ConciseContract API." - raise AttributeError(msg) - collision_fn.__name__ = fn_name - return collision_fn - - class BaseContractConstructor: """ Class for contract constructor API. @@ -905,138 +893,6 @@ async def estimate_gas( ) -class ConciseMethod: - ALLOWED_MODIFIERS = {'call', 'estimate_gas', 'transact', 'build_transaction'} - - def __init__( - self, function: 'ContractFunction', - normalizers: Optional[Tuple[Callable[..., Any], ...]] = None - ) -> None: - self._function = function - self._function._return_data_normalizers = normalizers - - def __call__(self, *args: Any, **kwargs: Any) -> 'ContractFunction': - return self.__prepared_function(*args, **kwargs) - - def __prepared_function(self, *args: Any, **kwargs: Any) -> 'ContractFunction': - modifier_dict: Dict[Any, Any] - 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( - f"The only allowed keyword arguments are: {self.ALLOWED_MODIFIERS}") - else: - raise TypeError(f"Use up to one keyword argument, one of: {self.ALLOWED_MODIFIERS}") - - return getattr(self._function(*args), modifier)(modifier_dict) - - -class ConciseContract: - """ - An alternative Contract Factory which invokes all methods as `call()`, - unless you add a keyword argument. The keyword argument assigns the prep method. - - This call - - > contract.withdraw(amount, transact={'from': eth.accounts[1], 'gas': 100000, ...}) - - is equivalent to this call in the classic contract: - - > 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: Contract, - method_class: Union[Type['ConciseMethod'], Type['ImplicitMethod']] = ConciseMethod - ) -> None: - classic_contract._return_data_normalizers += CONCISE_NORMALIZERS - self._classic_contract = classic_contract - self.address = self._classic_contract.address - - protected_fn_names = [fn for fn in dir(self) if not fn.endswith('__')] - - for fn_name in self._classic_contract.functions: - - # Override namespace collisions - if fn_name in protected_fn_names: - _concise_method = cast('ConciseMethod', mk_collision_prop(fn_name)) - - else: - _classic_method = getattr( - self._classic_contract.functions, - fn_name) - - _concise_method = method_class( - _classic_method, - self._classic_contract._return_data_normalizers - ) - - setattr(self, fn_name, _concise_method) - - @classmethod - def factory(cls, *args: Any, **kwargs: Any) -> Contract: - return compose(cls, Contract.factory(*args, **kwargs)) - - -def _none_addr(datatype: str, data: ChecksumAddress) -> Tuple[str, Optional[ChecksumAddress]]: - if datatype == 'address' and int(data, base=16) == 0: - return (datatype, None) - else: - return (datatype, data) - - -CONCISE_NORMALIZERS: Tuple[Callable[..., Any]] = ( - _none_addr, -) - - -class ImplicitMethod(ConciseMethod): - def __call_by_default(self, args: Any) -> bool: - function_abi = find_matching_fn_abi(self._function.contract_abi, - self._function.w3.codec, - fn_identifier=self._function.function_identifier, - args=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: Any, **kwargs: Any) -> 'ContractFunction': - # Modifier is not provided and method is not constant/pure do a transaction instead - if not kwargs and not self.__call_by_default(args): - return super().__call__(*args, transact={}) - else: - return super().__call__(*args, **kwargs) - - -class ImplicitContract(ConciseContract): - """ - ImplicitContract class is similar to the ConciseContract class - however it performs a transaction instead of a call if no modifier - is given and the method is not marked 'constant' in the ABI. - - The transaction will use the default account to send the transaction. - - This call - - > contract.withdraw(amount) - - is equivalent to this call in the classic contract: - - > contract.functions.withdraw(amount).transact({}) - """ - def __init__( - self, - classic_contract: Contract, - method_class: Union[Type[ImplicitMethod], Type[ConciseMethod]] = ImplicitMethod - ) -> None: - super().__init__(classic_contract, method_class=method_class) - - class NonExistentFallbackFunction: @staticmethod def _raise_exception() -> NoReturn: diff --git a/web3/eth.py b/web3/eth.py index 30a5bfd3ac..f62d715b7a 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -68,7 +68,6 @@ from web3.contract import ( AsyncContract, AsyncContractCaller, - ConciseContract, Contract, ContractCaller, ) @@ -335,7 +334,7 @@ def contract( # noqa: F811 def set_contract_factory( self, contractFactory: Type[Union[Contract, AsyncContract, - ConciseContract, ContractCaller, AsyncContractCaller]] + ContractCaller, AsyncContractCaller]] ) -> None: self.defaultContractFactory = contractFactory @@ -580,7 +579,7 @@ async def get_storage_at( class Eth(BaseEth): account = Account() iban = Iban - defaultContractFactory: Type[Union[Contract, ConciseContract, ContractCaller]] = Contract + defaultContractFactory: Type[Union[Contract, ContractCaller]] = Contract def namereg(self) -> NoReturn: raise NotImplementedError()