Skip to content

Commit

Permalink
Add unindexed event arg filtering support for get_logs
Browse files Browse the repository at this point in the history
- Contract event ``get_logs()`` has support for ``argument_filters`` but it only works on indexed event arguments. This feature works on unindexed args for ``create_filter()`` / ``build_filter()``. It is ideal to achieve some parity in the same argument for ``get_logs()`` by allowing it here as well.
  • Loading branch information
fselmo committed Aug 15, 2023
1 parent 88011b2 commit 570d38b
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/web3.contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ Each Contract Factory exposes the following methods.
- ``fromBlock`` is a mandatory field. Defines the starting block (exclusive) filter block range. It can be either the starting block number, or 'latest' for the last mined block, or 'pending' for unmined transactions. In the case of ``fromBlock``, 'latest' and 'pending' set the 'latest' or 'pending' block as a static value for the starting filter block.
- ``toBlock`` optional. Defaults to 'latest'. Defines the ending block (inclusive) in the filter block range. Special values 'latest' and 'pending' set a dynamic range that always includes the 'latest' or 'pending' blocks for the filter's upper block range.
- ``address`` optional. Defaults to the contract address. The filter matches the event logs emanating from ``address``.
- ``argument_filters``, optional. Expects a dictionary of argument names and values. When provided event logs are filtered for the event argument values. Event arguments can be both indexed or unindexed. Indexed values with be translated to their corresponding topic arguments. Unindexed arguments will be filtered using a regular expression.
- ``argument_filters``, optional. Expects a dictionary of argument names and values. When provided event logs are filtered for the event argument values. Event arguments can be both indexed or unindexed. Indexed values will be translated to their corresponding topic arguments. Unindexed arguments will be filtered using a regular expression.
- ``topics`` optional, accepts the standard JSON-RPC topics argument. See the JSON-RPC documentation for `eth_newFilter <https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter>`_ more information on the ``topics`` parameters.

.. py:classmethod:: Contract.events.your_event_name.build_filter()
Expand Down
6 changes: 5 additions & 1 deletion web3/_utils/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ def construct_event_filter_params(
if is_list_like(address):
filter_params["address"] = [address] + [contract_address]
elif is_string(address):
filter_params["address"] = [address, contract_address]
filter_params["address"] = (
[address, contract_address]
if address != contract_address
else [address]
)
else:
raise ValueError(
f"Unsupported type for `address` parameter: {type(address)}"
Expand Down
2 changes: 1 addition & 1 deletion web3/contract/base_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def _get_event_filter_params(

# Construct JSON-RPC raw filter presentation based on human readable
# Python descriptions. Namely, convert event names to their keccak signatures
data_filter_set, event_filter_params = construct_event_filter_params(
_, event_filter_params = construct_event_filter_params(
abi,
self.w3.codec,
contract_address=self.address,
Expand Down
67 changes: 60 additions & 7 deletions web3/contract/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from eth_utils import (
combomethod,
is_list_like,
)
from eth_utils.toolz import (
partial,
Expand Down Expand Up @@ -89,6 +90,9 @@
EventData,
TxParams,
)
from web3.utils import (
get_abi_input_names,
)

if TYPE_CHECKING:
from ens import ENS # noqa: F401
Expand Down Expand Up @@ -161,16 +165,65 @@ def get_logs(
same time as fromBlock or toBlock
:yield: Tuple of :class:`AttributeDict` instances
"""
abi = self._get_event_abi()
event_abi = self._get_event_abi()

# Call JSON-RPC API
logs = self.w3.eth.get_logs(
self._get_event_filter_params(
abi, argument_filters, fromBlock, toBlock, block_hash
)
_filter_params = self._get_event_filter_params(
event_abi, argument_filters, fromBlock, toBlock, block_hash
)

logs = cast(Sequence[EventData], self.w3.eth.get_logs(_filter_params))
# convert raw binary data to Python proxy objects as described by ABI:
event_logs = tuple(
get_event_data(self.w3.codec, event_abi, entry) for entry in logs
)

# Convert raw binary data to Python proxy objects as described by ABI
return tuple(get_event_data(self.w3.codec, abi, entry) for entry in logs)
event_arg_names = get_abi_input_names(event_abi)

if argument_filters is not None:
# handle argument filtering for un-indexed args
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 (
len(event_logs) > 0
# if all args in ``argument_filters`` are indexed, then the logs
# are already filtered by the node in the ``eth_getLogs`` call above.
and not all(
input_arg["indexed"]
for input_arg in event_abi["inputs"]
if input_arg["name"] in argument_filters.keys()
)
):
filtered_logs_by_non_indexed_args = []

for log in event_logs:
for arg, match_values in argument_filters.items():
if not is_list_like(match_values):
match_values = [match_values]

for abi_arg in event_abi["inputs"]:
if abi_arg["name"] == arg:
if (
# isolate ``string`` values to support substrings
abi_arg["type"] == "string"
and any(
val in log["args"][arg] for val in match_values
)
or (
# otherwise, do direct value comparison
abi_arg["type"] != "string"
and log["args"][arg] in match_values
)
):
filtered_logs_by_non_indexed_args.append(log)

return filtered_logs_by_non_indexed_args

return event_logs

@combomethod
def create_filter(
Expand Down

0 comments on commit 570d38b

Please sign in to comment.