diff --git a/tests/core/filtering/test_contract_get_logs.py b/tests/core/filtering/test_contract_get_logs.py index 7b06e5067c..11ccbc9daf 100644 --- a/tests/core/filtering/test_contract_get_logs.py +++ b/tests/core/filtering/test_contract_get_logs.py @@ -3,6 +3,9 @@ from web3._utils.contract_sources.contract_data._custom_contract_data import ( EMITTER_ENUM, ) +from web3.exceptions import ( + Web3ValidationError, +) def test_contract_get_available_events( @@ -166,6 +169,18 @@ def test_get_logs_argument_filters_indexed_and_non_indexed_args(emitter): assert logs_filter_non_indexed_uint256_and_string[0] == logs_no_filter[0] +def test_get_logs_argument_filters_key_validation( + emitter, +): + with pytest.raises( + Web3ValidationError, + match="all argument names must be present in the contract's event ABI", + ): + emitter.events.LogIndexedAndNotIndexed.get_logs( + argument_filters={"nonExistentKey": "Value shouldn't matter"}, + ) + + # --- async --- # @@ -364,3 +379,16 @@ async def test_async_get_logs_argument_filters_indexed_and_non_indexed_args( ) assert len(logs_filter_non_indexed_uint256_and_string) == 1 assert logs_filter_non_indexed_uint256_and_string[0] == logs_no_filter[0] + + +@pytest.mark.asyncio +async def test_async_get_logs_argument_filters_key_validation( + async_emitter, +): + with pytest.raises( + Web3ValidationError, + match="all argument names must be present in the contract's event ABI", + ): + await async_emitter.events.LogIndexedAndNotIndexed.get_logs( + argument_filters={"nonExistentKey": "Value shouldn't matter"}, + ) diff --git a/web3/contract/async_contract.py b/web3/contract/async_contract.py index 492657b153..232856ab27 100644 --- a/web3/contract/async_contract.py +++ b/web3/contract/async_contract.py @@ -83,6 +83,7 @@ ABIFunctionNotFound, NoABIFound, NoABIFunctionsFound, + Web3ValidationError, ) from web3.types import ( ABI, @@ -91,6 +92,9 @@ EventData, TxParams, ) +from web3.utils import ( + get_abi_input_names, +) if TYPE_CHECKING: from ens import AsyncENS # noqa: F401 @@ -156,7 +160,8 @@ async def get_logs( See also: :func:`web3.middleware.filter.local_filter_middleware`. - :param argument_filters: + :param argument_filters: Filter by argument values. Indexed arguments are + filtered by the node while non-indexed arguments are filtered by the library. :param fromBlock: block number or "latest", defaults to "latest" :param toBlock: block number or "latest". Defaults to "latest" :param block_hash: block hash. blockHash cannot be set at the @@ -165,12 +170,21 @@ async def get_logs( """ event_abi = self._get_event_abi() - # Call JSON-RPC API + # validate ``argument_filters`` if present + if argument_filters is not None: + event_arg_names = get_abi_input_names(event_abi) + if not all(arg in event_arg_names for arg in argument_filters.keys()): + raise Web3ValidationError( + "When filtering by argument names, all argument names must be " + "present in the contract's event ABI." + ) + _filter_params = self._get_event_filter_params( event_abi, argument_filters, fromBlock, toBlock, block_hash ) - + # call JSON-RPC API logs = await self.w3.eth.get_logs(_filter_params) + # convert raw binary data to Python proxy objects as described by ABI: all_event_logs = tuple( get_event_data(self.w3.codec, event_abi, entry) for entry in logs diff --git a/web3/contract/base_contract.py b/web3/contract/base_contract.py index d477f187b2..77cee3be27 100644 --- a/web3/contract/base_contract.py +++ b/web3/contract/base_contract.py @@ -115,10 +115,6 @@ TxReceipt, ) -from ..utils import ( - get_abi_input_names, -) - if TYPE_CHECKING: from web3 import ( # noqa: F401 AsyncWeb3, @@ -273,25 +269,18 @@ def _process_get_logs_argument_filters( event_logs: Sequence[EventData], argument_filters: Optional[Dict[str, Any]], ) -> Iterable[EventData]: - if argument_filters is None or len(event_logs) == 0: - return event_logs - - # handle argument filtering for un-indexed args - event_arg_names = get_abi_input_names(event_abi) - if not all(arg in event_arg_names for arg in argument_filters.keys()): - raise TypeError( - "When filtering by argument names, all argument names must be " - "present in the contract's event ABI." + if ( + argument_filters is None + or len(event_logs) == 0 + or + # if no non-indexed args in argument filters, since indexed args are + # filtered pre-call to ``eth_getLogs`` by building specific ``topics``. + not any( + not arg["indexed"] + for arg in event_abi["inputs"] + if arg["name"] in argument_filters ) - - if all( - input_arg["indexed"] - for input_arg in event_abi["inputs"] - if input_arg["name"] in argument_filters.keys() ): - # After validating args, if all args in ``argument_filters`` are indexed, - # then the logs are already filtered by the node in the ``eth_getLogs`` - # call. return event_logs filtered_logs_by_non_indexed_args = [] @@ -319,6 +308,7 @@ def _process_get_logs_argument_filters( break if match: break + return filtered_logs_by_non_indexed_args @combomethod diff --git a/web3/contract/contract.py b/web3/contract/contract.py index 6d49b900de..21522c6197 100644 --- a/web3/contract/contract.py +++ b/web3/contract/contract.py @@ -177,12 +177,12 @@ def get_logs( "present in the contract's event ABI." ) - # Call JSON-RPC API _filter_params = self._get_event_filter_params( event_abi, argument_filters, fromBlock, toBlock, block_hash ) - + # call JSON-RPC API logs = self.w3.eth.get_logs(_filter_params) + # convert raw binary data to Python proxy objects as described by ABI: all_event_logs = tuple( get_event_data(self.w3.codec, event_abi, entry) for entry in logs