Skip to content

Commit 07a95de

Browse files
committed
bugfix(typing): Fix type for topics filtering
- Topics can be single topics or lists of AND / OR conditions. Here, we update to a better defined ``TopicFilter`` type to better define these cases.
1 parent c511443 commit 07a95de

File tree

5 files changed

+127
-3
lines changed

5 files changed

+127
-3
lines changed

newsfragments/3748.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``topics`` type for ``LogsSubscription`` to reflect AND / OR nested list conditions on log filters.

newsfragments/3748.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add the ``TopicFilter`` type to better describe the cases for filtering logs by topics.

web3/_utils/module_testing/persistent_connection_provider.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
Dict,
1010
Generator,
1111
List,
12+
Sequence,
1213
Tuple,
1314
Union,
1415
cast,
@@ -44,6 +45,7 @@
4445
LogReceipt,
4546
Nonce,
4647
RPCEndpoint,
48+
TopicFilter,
4749
TxData,
4850
Wei,
4951
)
@@ -580,6 +582,112 @@ async def test_async_eth_subscribe_creates_and_handles_logs_subscription_type(
580582
sub_manager.total_handler_calls = 0
581583
await clean_up_task(emit_event_task)
582584

585+
@pytest.mark.asyncio
586+
@pytest.mark.parametrize(
587+
"topics",
588+
[
589+
pytest.param(
590+
[
591+
HexStr(
592+
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" # noqa: E501
593+
)
594+
],
595+
id="Single specific topic at position 0",
596+
),
597+
pytest.param(
598+
[
599+
None,
600+
HexStr(
601+
"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" # noqa: E501
602+
),
603+
],
604+
id="Wildcard at position 0, specific topic at position 1",
605+
),
606+
pytest.param(
607+
[
608+
[
609+
HexStr(
610+
"0x1111111111111111111111111111111111111111111111111111111111111111" # noqa: E501
611+
),
612+
HexStr(
613+
"0x2222222222222222222222222222222222222222222222222222222222222222" # noqa: E501
614+
),
615+
]
616+
],
617+
id="OR pattern: topic A or B at position 0",
618+
),
619+
pytest.param(
620+
[
621+
[
622+
HexStr(
623+
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # noqa: E501
624+
),
625+
HexStr(
626+
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" # noqa: E501
627+
),
628+
],
629+
HexStr(
630+
"0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" # noqa: E501
631+
),
632+
],
633+
id="Complex: (A or B) at position 0 AND C at position 1",
634+
),
635+
pytest.param(
636+
[
637+
HexBytes(
638+
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" # noqa: E501
639+
)
640+
],
641+
id="Single specific topic at position 0 with HexBytes",
642+
),
643+
pytest.param(
644+
[
645+
[
646+
HexBytes(
647+
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # noqa: E501
648+
),
649+
HexStr(
650+
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" # noqa: E501
651+
),
652+
b"\xcc" * 32,
653+
]
654+
],
655+
id="OR pattern with mixed HexBytes, HexStr, and bytes at position 0",
656+
),
657+
],
658+
)
659+
async def test_async_logs_subscription_with_and_or_topic_patterns(
660+
self,
661+
async_w3: AsyncWeb3,
662+
async_emitter_contract: "AsyncContract",
663+
topics: Sequence[TopicFilter],
664+
) -> None:
665+
"""Test that LogsSubscription properly handles AND/OR topic patterns."""
666+
sub_manager = async_w3.subscription_manager
667+
668+
subscription = LogsSubscription(
669+
address=async_emitter_contract.address,
670+
topics=topics,
671+
handler=idle_handler,
672+
)
673+
674+
await sub_manager.subscribe(subscription)
675+
assert len(sub_manager.subscriptions) == 1
676+
assert isinstance(sub_manager.subscriptions[0], LogsSubscription)
677+
assert sub_manager.subscriptions[0].topics == topics
678+
679+
assert subscription.subscription_params == (
680+
"logs",
681+
{
682+
"address": async_emitter_contract.address,
683+
"topics": topics,
684+
},
685+
)
686+
687+
# clean up
688+
await sub_manager.unsubscribe(subscription)
689+
assert len(sub_manager.subscriptions) == 0
690+
583691
@pytest.mark.asyncio
584692
async def test_async_extradata_poa_middleware_on_eth_subscription(
585693
self,

web3/types.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@
6262

6363
# bytes, hexbytes, or hexstr representing a 32 byte hash
6464
_Hash32 = Union[Hash32, HexBytes, HexStr]
65+
66+
# --- ``TopicFilter`` type for event log filtering with AND/OR patterns --- #
67+
# - ``None``: wildcard, matches any value at this position
68+
# - ``_Hash32``: single topic that must match exactly
69+
# - ``Sequence[Union[None, _Hash32]]``: OR condition, at least one must match
70+
# - ``Sequence[Sequence[...]]``: nested OR conditions
71+
TopicFilter = Union[
72+
None,
73+
_Hash32,
74+
Sequence[Union[None, _Hash32]],
75+
Sequence["TopicFilter"],
76+
]
77+
6578
EnodeURI = NewType("EnodeURI", str)
6679
ENS = NewType("ENS", str)
6780
Nonce = NewType("Nonce", int)
@@ -345,7 +358,7 @@ class FilterParams(TypedDict, total=False):
345358
blockHash: HexBytes
346359
fromBlock: BlockIdentifier
347360
toBlock: BlockIdentifier
348-
topics: Sequence[Optional[Union[_Hash32, Sequence[_Hash32]]]]
361+
topics: Sequence[TopicFilter]
349362

350363

351364
class FeeHistory(TypedDict):
@@ -667,4 +680,4 @@ class LogsSubscriptionArg(TypedDict, total=False):
667680
ENS,
668681
Sequence[Union[Address, ChecksumAddress, ENS]],
669682
]
670-
topics: Sequence[Union[HexStr, Sequence[HexStr]]]
683+
topics: Sequence[TopicFilter]

web3/utils/subscriptions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
FilterParams,
3131
LogReceipt,
3232
SyncProgress,
33+
TopicFilter,
3334
TxData,
3435
)
3536

@@ -215,7 +216,7 @@ def __init__(
215216
address: Optional[
216217
Union[Address, ChecksumAddress, List[Address], List[ChecksumAddress]]
217218
] = None,
218-
topics: Optional[List[HexStr]] = None,
219+
topics: Optional[Sequence[TopicFilter]] = None,
219220
handler: LogsSubscriptionHandler = None,
220221
handler_context: Optional[Dict[str, Any]] = None,
221222
label: Optional[str] = None,

0 commit comments

Comments
 (0)