Skip to content

Commit 53b8c2b

Browse files
m-kusigorsereda
andauthored
Support complex types in Starknet ABI (#1171)
* Support complex types in abi * Add array type & fix config validation * Fixups * Fix missing node var & add special handling for u256/bytearray * Temporary workaround for event parsing * Add test for config validation fix --------- Co-authored-by: Igor Sereda <[email protected]>
1 parent 13a1e34 commit 53b8c2b

File tree

7 files changed

+73
-15
lines changed

7 files changed

+73
-15
lines changed

src/dipdup/abi/cairo.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,42 @@ class CairoAbi(TypedDict):
3232
events: list[CairoEventAbi]
3333

3434

35-
def _convert_type(type_: CairoType) -> str:
35+
def _convert_type(type_: CairoType) -> dict[str, Any]:
3636
# TODO: Support all types
37-
return {
37+
if type_.__class__.__name__ in {'EventType', 'StructType'}:
38+
if type_.name == 'Uint256':
39+
return {'type': 'integer'}
40+
if type_.name == 'core::byte_array::ByteArray':
41+
return {'type': 'string'}
42+
return {
43+
'type': 'object',
44+
'properties': {
45+
key: _convert_type(value)
46+
for key, value in type_.types.items()
47+
},
48+
'required': tuple(type_.types.keys()),
49+
'additionalProperties': False,
50+
}
51+
52+
if type_.__class__.__name__ == 'ArrayType':
53+
return {
54+
'type': 'array',
55+
'items': _convert_type(type_.inner_type),
56+
}
57+
58+
simple_type = {
3859
'FeltType': 'integer',
3960
'UintType': 'integer',
4061
'BoolType': 'boolean',
4162
}[type_.__class__.__name__]
63+
return {'type': simple_type}
4264

4365

4466
def _jsonschema_from_event(event: EventType) -> dict[str, Any]:
4567
# TODO: Unpack nested types (starknet.py could do that)
4668
return {
4769
'$schema': 'http://json-schema.org/draft/2019-09/schema#',
48-
'type': 'object',
49-
'properties': {key: {'type': _convert_type(value)} for key, value in event.types.items()},
50-
'required': tuple(event.types.keys()),
51-
'additionalProperties': False,
70+
**_convert_type(event)
5271
}
5372

5473

src/dipdup/config/starknet.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from abc import ABC
55
from typing import Annotated
66
from typing import Literal
7+
from typing import TypeAlias
78

89
from pydantic import AfterValidator
910
from pydantic import ConfigDict
@@ -17,7 +18,7 @@
1718
from dipdup.config.starknet_subsquid import StarknetSubsquidDatasourceConfig
1819
from dipdup.exceptions import ConfigurationError
1920

20-
StarknetDatasourceConfigU = StarknetSubsquidDatasourceConfig | StarknetNodeDatasourceConfig
21+
StarknetDatasourceConfigU: TypeAlias = StarknetSubsquidDatasourceConfig | StarknetNodeDatasourceConfig
2122

2223
_HEX_ADDRESS_REGEXP = re.compile(r'(0x)?[0-9a-f]{1,64}', re.IGNORECASE | re.ASCII)
2324

src/dipdup/config/starknet_events.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from dipdup.config import Alias
1010
from dipdup.config import HandlerConfig
1111
from dipdup.config.starknet import StarknetContractConfig
12+
from dipdup.config.starknet import StarknetDatasourceConfigU
1213
from dipdup.config.starknet import StarknetIndexConfig
1314
from dipdup.models.starknet import StarknetSubscription
1415
from dipdup.subscriptions import Subscription
@@ -61,6 +62,7 @@ class StarknetEventsIndexConfig(StarknetIndexConfig):
6162
"""
6263

6364
kind: Literal['starknet.events']
65+
datasources: tuple[Alias[StarknetDatasourceConfigU], ...]
6466
handlers: tuple[StarknetEventsHandlerConfig, ...]
6567

6668
def get_subscriptions(self) -> set[Subscription]:

src/dipdup/datasources/starknet_node.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313

1414
class StarknetNodeDatasource(IndexDatasource[StarknetNodeDatasourceConfig]):
15+
NODE_LAST_MILE = 128
16+
1517
_default_http_config = HttpConfig(
1618
batch_size=1000,
1719
)

src/dipdup/indexes/starknet_events/matcher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def prepare_event_handler_args(
7373
typename=typename,
7474
name=handler_config.name,
7575
)['serializer']
76-
data = [int(s, 16) for s in matched_event.data]
76+
data = [int(x, 16) for x in matched_event.keys[1:] + matched_event.data]
7777

7878
# holding context for error building
7979
with DeserializationContext.create(data) as context:
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
spec_version: 3.0
2+
package: demo_starknet_events
3+
4+
datasources:
5+
subsquid:
6+
kind: starknet.subsquid
7+
url: https://v2.archive.subsquid.io/network/starknet-mainnet
8+
node:
9+
kind: starknet.node
10+
url: https://starknet-mainnet.g.alchemy.com/v2
11+
12+
contracts:
13+
stark_usdt:
14+
kind: starknet
15+
address: '0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8'
16+
typename: stark_usdt
17+
18+
indexes:
19+
starknet_usdt_events:
20+
kind: starknet.events
21+
datasources:
22+
- subsquid
23+
- node
24+
handlers:
25+
- callback: on_transfer
26+
contract: stark_usdt
27+
name: Transfer

tests/test_config/test_config.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import tempfile
2+
from collections.abc import Generator
23
from pathlib import Path
34

45
import pytest
6+
from _pytest.fixtures import SubRequest
57
from pydantic import ValidationError
68

79
from dipdup.config import DipDupConfig
@@ -19,9 +21,16 @@
1921
from dipdup.models.tezos_tzkt import TransactionSubscription
2022
from dipdup.yaml import DipDupYAMLConfig
2123

24+
TEST_CONFIGS = Path(__file__).parent.parent / 'configs'
25+
26+
27+
@pytest.fixture(params=list(Path.glob(TEST_CONFIGS, 'demo_*.yml')))
28+
def demo_dipdup_config(request: SubRequest) -> Generator[Path, None, None]:
29+
yield request.param
30+
2231

2332
def create_config(merge_subs: bool = False, origs: bool = False) -> DipDupConfig:
24-
path = Path(__file__).parent.parent / 'configs' / 'dipdup.yaml'
33+
path = TEST_CONFIGS / 'dipdup.yaml'
2534
config = DipDupConfig.load([path])
2635
if origs:
2736
config.indexes['hen_mainnet'].types += (TezosOperationType.origination,) # type: ignore
@@ -88,12 +97,10 @@ async def test_reserved_keywords() -> None:
8897
)
8998

9099
# FIXME: Can't use `from_` field alias in dataclasses
91-
raw_config, _ = DipDupYAMLConfig.load(
92-
paths=[Path(__file__).parent.parent / 'configs' / 'demo_tezos_token_transfers_4.yml']
93-
)
100+
raw_config, _ = DipDupYAMLConfig.load(paths=[TEST_CONFIGS / 'demo_tezos_token_transfers_4.yml'])
94101
assert raw_config['indexes']['tzbtc_holders_mainnet']['handlers'][1]['from_'] == 'tzbtc_mainnet'
95102

96-
config = DipDupConfig.load([Path(__file__).parent.parent / 'configs' / 'demo_tezos_token_transfers_4.yml'])
103+
config = DipDupConfig.load([TEST_CONFIGS / 'demo_tezos_token_transfers_4.yml'])
97104
assert config.indexes['tzbtc_holders_mainnet'].handlers[1].from_ == 'tzbtc_mainnet' # type: ignore[misc,union-attr]
98105

99106

@@ -148,5 +155,5 @@ async def test_http_config() -> None:
148155
)
149156

150157

151-
# async def test_evm() -> None:
152-
# DipDupConfig.load([Path(__file__).parent.parent / 'configs' / 'evm_subsquid.yml'])
158+
async def test_load_demo_config(demo_dipdup_config: Path) -> None:
159+
DipDupConfig.load([demo_dipdup_config])

0 commit comments

Comments
 (0)