Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support complex types in Starknet ABI #1171

Merged
merged 6 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/dipdup/abi/cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,42 @@ class CairoAbi(TypedDict):
events: list[CairoEventAbi]


def _convert_type(type_: CairoType) -> str:
def _convert_type(type_: CairoType) -> dict[str, Any]:
# TODO: Support all types
return {
if type_.__class__.__name__ in {'EventType', 'StructType'}:
if type_.name == 'Uint256':
return {'type': 'integer'}
if type_.name == 'core::byte_array::ByteArray':
return {'type': 'string'}
return {
'type': 'object',
'properties': {
key: _convert_type(value)
for key, value in type_.types.items()
},
'required': tuple(type_.types.keys()),
'additionalProperties': False,
}

if type_.__class__.__name__ == 'ArrayType':
return {
'type': 'array',
'items': _convert_type(type_.inner_type),
}

simple_type = {
'FeltType': 'integer',
'UintType': 'integer',
'BoolType': 'boolean',
}[type_.__class__.__name__]
return {'type': simple_type}


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


Expand Down
3 changes: 2 additions & 1 deletion src/dipdup/config/starknet.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from abc import ABC
from typing import Annotated
from typing import Literal
from typing import TypeAlias

from pydantic import AfterValidator
from pydantic import ConfigDict
Expand All @@ -17,7 +18,7 @@
from dipdup.config.starknet_subsquid import StarknetSubsquidDatasourceConfig
from dipdup.exceptions import ConfigurationError

StarknetDatasourceConfigU = StarknetSubsquidDatasourceConfig | StarknetNodeDatasourceConfig
StarknetDatasourceConfigU: TypeAlias = StarknetSubsquidDatasourceConfig | StarknetNodeDatasourceConfig

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

Expand Down
2 changes: 2 additions & 0 deletions src/dipdup/config/starknet_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from dipdup.config import Alias
from dipdup.config import HandlerConfig
from dipdup.config.starknet import StarknetContractConfig
from dipdup.config.starknet import StarknetDatasourceConfigU
from dipdup.config.starknet import StarknetIndexConfig
from dipdup.models.starknet import StarknetSubscription
from dipdup.subscriptions import Subscription
Expand Down Expand Up @@ -61,6 +62,7 @@ class StarknetEventsIndexConfig(StarknetIndexConfig):
"""

kind: Literal['starknet.events']
datasources: tuple[Alias[StarknetDatasourceConfigU], ...]
handlers: tuple[StarknetEventsHandlerConfig, ...]

def get_subscriptions(self) -> set[Subscription]:
Expand Down
2 changes: 2 additions & 0 deletions src/dipdup/datasources/starknet_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@


class StarknetNodeDatasource(IndexDatasource[StarknetNodeDatasourceConfig]):
NODE_LAST_MILE = 128

_default_http_config = HttpConfig(
batch_size=1000,
)
Expand Down
2 changes: 1 addition & 1 deletion src/dipdup/indexes/starknet_events/matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def prepare_event_handler_args(
typename=typename,
name=handler_config.name,
)['serializer']
data = [int(s, 16) for s in matched_event.data]
data = [int(x, 16) for x in matched_event.keys[1:] + matched_event.data]

# holding context for error building
with DeserializationContext.create(data) as context:
Expand Down
27 changes: 27 additions & 0 deletions tests/configs/demo_starknet_events.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
spec_version: 3.0
package: demo_starknet_events

datasources:
subsquid:
kind: starknet.subsquid
url: https://v2.archive.subsquid.io/network/starknet-mainnet
node:
kind: starknet.node
url: https://starknet-mainnet.g.alchemy.com/v2

contracts:
stark_usdt:
kind: starknet
address: '0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8'
typename: stark_usdt

indexes:
starknet_usdt_events:
kind: starknet.events
datasources:
- subsquid
- node
handlers:
- callback: on_transfer
contract: stark_usdt
name: Transfer
21 changes: 14 additions & 7 deletions tests/test_config/test_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import tempfile
from collections.abc import Generator
from pathlib import Path

import pytest
from _pytest.fixtures import SubRequest
from pydantic import ValidationError

from dipdup.config import DipDupConfig
Expand All @@ -19,9 +21,16 @@
from dipdup.models.tezos_tzkt import TransactionSubscription
from dipdup.yaml import DipDupYAMLConfig

TEST_CONFIGS = Path(__file__).parent.parent / 'configs'


@pytest.fixture(params=list(Path.glob(TEST_CONFIGS, 'demo_*.yml')))
def demo_dipdup_config(request: SubRequest) -> Generator[Path, None, None]:
yield request.param


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

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

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


Expand Down Expand Up @@ -148,5 +155,5 @@ async def test_http_config() -> None:
)


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