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

Add cycle check in parser #630

Merged
merged 1 commit into from Jan 4, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 27 additions & 2 deletions starknet_py/net/models/abi/parser.py
@@ -1,5 +1,7 @@
from __future__ import annotations

import dataclasses
import json
from collections import OrderedDict, defaultdict
from typing import Dict, List, Optional, cast, DefaultDict

Expand Down Expand Up @@ -119,8 +121,20 @@ def _parse_structures(self) -> Dict[str, StructType]:
struct.types.update(members)

# All types have their members assigned now

self._check_for_cycles(structs)

return structs

@staticmethod
def _check_for_cycles(structs: Dict[str, StructType]):
# We want to avoid creating our own cycle checker as it would make it more complex. json module has a built-in
# checker for cycles.
try:
_to_json(structs)
except ValueError as err:
raise AbiParsingError(err) from ValueError

def _parse_function(self, function: FunctionDict) -> Abi.Function:
return Abi.Function(
name=function["name"],
Expand All @@ -137,8 +151,7 @@ def _parse_event(self, event: EventDict) -> Abi.Event:
def _parse_members(
self, params: List[TypedMemberDict], entity_name: str
) -> OrderedDict[str, CairoType]:
# Without cast it complains that
# 'Type "TypedMemberDict" cannot be assigned to type "T@_group_by_name"'
# Without cast it complains that 'Type "TypedMemberDict" cannot be assigned to type "T@_group_by_name"'
members = AbiParser._group_by_entry_name(cast(List[Dict], params), entity_name)
return OrderedDict(
(name, self.type_parser.parse_inline_type(param["type"]))
Expand All @@ -158,3 +171,15 @@ def _group_by_entry_name(
)
grouped[name] = entry
return grouped


def _to_json(value):
class DataclassSupportingEncoder(json.JSONEncoder):
def default(self, o):
war-in marked this conversation as resolved.
Show resolved Hide resolved
# Dataclasses are not supported by json. Additionally, dataclasses.asdict() works recursively and doesn't
# check for cycles, so we need to flatten dataclasses (by ONE LEVEL) ourselves.
if dataclasses.is_dataclass(o):
return tuple(getattr(o, field.name) for field in dataclasses.fields(o))
return super().default(o)

return json.dumps(value, cls=DataclassSupportingEncoder)
43 changes: 43 additions & 0 deletions starknet_py/net/models/abi/parser_test.py
Expand Up @@ -37,6 +37,49 @@ def test_parsing_types_abi():
}


def test_self_cycle():
self_referencing_struct = {
"type": "struct",
"name": "Infinite",
"size": 1,
"members": [
{"name": "value", "offset": 0, "type": "Infinite"},
],
}
with pytest.raises(
AbiParsingError,
match="Circular reference detected",
):
AbiParser([self_referencing_struct]).parse()


def test_bigger_cycle():
# first -> seconds -> third -> first...
first = {
"type": "struct",
"name": "First",
"size": 1,
"members": [{"name": "value", "offset": 0, "type": "Second"}],
}
second = {
"type": "struct",
"name": "Second",
"size": 1,
"members": [{"name": "value", "offset": 0, "type": "Third"}],
}
third = {
"type": "struct",
"name": "Third",
"size": 1,
"members": [{"name": "value", "offset": 0, "type": "First"}],
}
with pytest.raises(
AbiParsingError,
match="Circular reference detected",
):
AbiParser([first, second, third]).parse()


def test_duplicated_structure():
with pytest.raises(
AbiParsingError,
Expand Down