add
This commit is contained in:
2
ccxt/static_dependencies/starknet/abi/v0/__init__.py
Normal file
2
ccxt/static_dependencies/starknet/abi/v0/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .model import Abi
|
||||
from .parser import AbiParser, AbiParsingError
|
||||
44
ccxt/static_dependencies/starknet/abi/v0/model.py
Normal file
44
ccxt/static_dependencies/starknet/abi/v0/model.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional, OrderedDict
|
||||
|
||||
from ...cairo.data_types import CairoType, StructType
|
||||
|
||||
|
||||
@dataclass
|
||||
class Abi:
|
||||
"""
|
||||
Dataclass representing class abi. Contains parsed functions, events and structures.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Function:
|
||||
"""
|
||||
Dataclass representing function's abi.
|
||||
"""
|
||||
|
||||
name: str
|
||||
inputs: OrderedDict[str, CairoType]
|
||||
outputs: OrderedDict[str, CairoType]
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
"""
|
||||
Dataclass representing event's abi.
|
||||
"""
|
||||
|
||||
name: str
|
||||
data: OrderedDict[str, CairoType]
|
||||
|
||||
defined_structures: Dict[
|
||||
str, StructType
|
||||
] #: Abi of structures defined by the class.
|
||||
functions: Dict[str, Function] #: Functions defined by the class.
|
||||
constructor: Optional[
|
||||
Function
|
||||
] #: Contract's constructor. It is None if class doesn't define one.
|
||||
l1_handler: Optional[
|
||||
Function
|
||||
] #: Handler of L1 messages. It is None if class doesn't define one.
|
||||
events: Dict[str, Event] #: Events defined by the class
|
||||
216
ccxt/static_dependencies/starknet/abi/v0/parser.py
Normal file
216
ccxt/static_dependencies/starknet/abi/v0/parser.py
Normal file
@@ -0,0 +1,216 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
from collections import OrderedDict, defaultdict
|
||||
from typing import DefaultDict, Dict, List, Optional, cast
|
||||
|
||||
from ....marshmallow import EXCLUDE
|
||||
|
||||
from .model import Abi
|
||||
from .schemas import ContractAbiEntrySchema
|
||||
from .shape import (
|
||||
CONSTRUCTOR_ENTRY,
|
||||
EVENT_ENTRY,
|
||||
FUNCTION_ENTRY,
|
||||
L1_HANDLER_ENTRY,
|
||||
STRUCT_ENTRY,
|
||||
EventDict,
|
||||
FunctionDict,
|
||||
StructMemberDict,
|
||||
TypedMemberDict,
|
||||
)
|
||||
from ...cairo.data_types import CairoType, StructType
|
||||
from ...cairo.type_parser import TypeParser
|
||||
|
||||
|
||||
class AbiParsingError(ValueError):
|
||||
"""
|
||||
Error raised when something wrong goes during abi parsing.
|
||||
"""
|
||||
|
||||
|
||||
class AbiParser:
|
||||
"""
|
||||
Utility class for parsing abi into a dataclass.
|
||||
"""
|
||||
|
||||
# Entries from ABI grouped by entry type
|
||||
_grouped: DefaultDict[str, List[Dict]]
|
||||
# lazy init property
|
||||
_type_parser: Optional[TypeParser] = None
|
||||
|
||||
def __init__(self, abi_list: List[Dict]):
|
||||
"""
|
||||
Abi parser constructor. Ensures that abi satisfies the abi schema.
|
||||
|
||||
:param abi_list: Contract's ABI as a list of dictionaries.
|
||||
"""
|
||||
abi = [
|
||||
ContractAbiEntrySchema().load(entry, unknown=EXCLUDE) for entry in abi_list
|
||||
]
|
||||
grouped = defaultdict(list)
|
||||
for entry in abi:
|
||||
assert isinstance(entry, dict)
|
||||
grouped[entry["type"]].append(entry)
|
||||
|
||||
self._grouped = grouped
|
||||
|
||||
def parse(self) -> Abi:
|
||||
"""
|
||||
Parse abi provided to constructor and return it as a dataclass. Ensures that there are no cycles in the abi.
|
||||
|
||||
:raises: AbiParsingError: on any parsing error.
|
||||
:return: Abi dataclass.
|
||||
"""
|
||||
structures = self._parse_structures()
|
||||
functions_dict = cast(
|
||||
Dict[str, FunctionDict],
|
||||
AbiParser._group_by_entry_name(
|
||||
self._grouped[FUNCTION_ENTRY], "defined functions"
|
||||
),
|
||||
)
|
||||
events_dict = cast(
|
||||
Dict[str, EventDict],
|
||||
AbiParser._group_by_entry_name(
|
||||
self._grouped[EVENT_ENTRY], "defined events"
|
||||
),
|
||||
)
|
||||
constructors = cast(List[FunctionDict], self._grouped[CONSTRUCTOR_ENTRY])
|
||||
l1_handlers = cast(List[FunctionDict], self._grouped[L1_HANDLER_ENTRY])
|
||||
|
||||
if len(l1_handlers) > 1:
|
||||
raise AbiParsingError("L1 handler in ABI must be defined at most once.")
|
||||
|
||||
if len(constructors) > 1:
|
||||
raise AbiParsingError("Constructor in ABI must be defined at most once.")
|
||||
|
||||
return Abi(
|
||||
defined_structures=structures,
|
||||
constructor=(
|
||||
self._parse_function(constructors[0]) if constructors else None
|
||||
),
|
||||
l1_handler=(self._parse_function(l1_handlers[0]) if l1_handlers else None),
|
||||
functions={
|
||||
name: self._parse_function(entry)
|
||||
for name, entry in functions_dict.items()
|
||||
},
|
||||
events={
|
||||
name: self._parse_event(entry) for name, entry in events_dict.items()
|
||||
},
|
||||
)
|
||||
|
||||
@property
|
||||
def type_parser(self) -> TypeParser:
|
||||
if self._type_parser:
|
||||
return self._type_parser
|
||||
|
||||
raise RuntimeError("Tried to get type_parser before it was set.")
|
||||
|
||||
def _parse_structures(self) -> Dict[str, StructType]:
|
||||
structs_dict = AbiParser._group_by_entry_name(
|
||||
self._grouped[STRUCT_ENTRY], "defined structures"
|
||||
)
|
||||
|
||||
# Contains sorted members of the struct
|
||||
struct_members: Dict[str, List[StructMemberDict]] = {}
|
||||
structs: Dict[str, StructType] = {}
|
||||
|
||||
# Example problem (with a simplified json structure):
|
||||
# [{name: User, fields: {id: Uint256}}, {name: "Uint256", ...}]
|
||||
# User refers to Uint256 even though it is not known yet (will be parsed next).
|
||||
# This is why it is important to create the structure types first. This way other types can already refer to
|
||||
# them when parsing types, even thought their fields are not filled yet.
|
||||
# At the end we will mutate those structures to contain the right fields. An alternative would be to use
|
||||
# topological sorting with an additional "unresolved type", so this flow is much easier.
|
||||
for name, struct in structs_dict.items():
|
||||
structs[name] = StructType(name, OrderedDict())
|
||||
without_offset = [
|
||||
member for member in struct["members"] if member.get("offset") is None
|
||||
]
|
||||
with_offset = [
|
||||
member for member in struct["members"] if member not in without_offset
|
||||
]
|
||||
struct_members[name] = sorted(
|
||||
with_offset, key=lambda member: member["offset"] # pyright: ignore
|
||||
)
|
||||
for member in without_offset:
|
||||
member["offset"] = (
|
||||
struct_members[name][-1].get("offset", 0) + 1
|
||||
if struct_members[name]
|
||||
else 0
|
||||
)
|
||||
struct_members[name].append(member)
|
||||
|
||||
# Now parse the types of members and save them.
|
||||
self._type_parser = TypeParser(structs)
|
||||
for name, struct in structs.items():
|
||||
members = self._parse_members(
|
||||
cast(List[TypedMemberDict], struct_members[name]),
|
||||
f"members of structure '{name}'",
|
||||
)
|
||||
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"],
|
||||
inputs=self._parse_members(function["inputs"], function["name"]),
|
||||
outputs=self._parse_members(function["outputs"], function["name"]),
|
||||
)
|
||||
|
||||
def _parse_event(self, event: EventDict) -> Abi.Event:
|
||||
return Abi.Event(
|
||||
name=event["name"],
|
||||
data=self._parse_members(event["data"], event["name"]),
|
||||
)
|
||||
|
||||
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"'
|
||||
members = AbiParser._group_by_entry_name(cast(List[Dict], params), entity_name)
|
||||
return OrderedDict(
|
||||
(name, self.type_parser.parse_inline_type(param["type"]))
|
||||
for name, param in members.items()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _group_by_entry_name(
|
||||
dicts: List[Dict], entity_name: str
|
||||
) -> OrderedDict[str, Dict]:
|
||||
grouped = OrderedDict()
|
||||
for entry in dicts:
|
||||
name = entry["name"]
|
||||
if name in grouped:
|
||||
raise AbiParsingError(
|
||||
f"Name '{name}' was used more than once in {entity_name}."
|
||||
)
|
||||
grouped[name] = entry
|
||||
return grouped
|
||||
|
||||
|
||||
def _to_json(value):
|
||||
class DataclassSupportingEncoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
# 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)
|
||||
72
ccxt/static_dependencies/starknet/abi/v0/schemas.py
Normal file
72
ccxt/static_dependencies/starknet/abi/v0/schemas.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from ....marshmallow import Schema, fields
|
||||
from ....marshmallow_oneofschema import OneOfSchema
|
||||
|
||||
from .shape import (
|
||||
CONSTRUCTOR_ENTRY,
|
||||
EVENT_ENTRY,
|
||||
FUNCTION_ENTRY,
|
||||
L1_HANDLER_ENTRY,
|
||||
STRUCT_ENTRY,
|
||||
)
|
||||
|
||||
|
||||
class TypedParameterSchema(Schema):
|
||||
name = fields.String(data_key="name", required=True)
|
||||
type = fields.String(data_key="type", required=True)
|
||||
|
||||
|
||||
class StructMemberSchema(TypedParameterSchema):
|
||||
offset = fields.Integer(data_key="offset", required=False)
|
||||
|
||||
|
||||
class FunctionBaseSchema(Schema):
|
||||
name = fields.String(data_key="name", required=True)
|
||||
inputs = fields.List(
|
||||
fields.Nested(TypedParameterSchema()), data_key="inputs", required=True
|
||||
)
|
||||
outputs = fields.List(
|
||||
fields.Nested(TypedParameterSchema()), data_key="outputs", required=True
|
||||
)
|
||||
|
||||
|
||||
class FunctionAbiEntrySchema(FunctionBaseSchema):
|
||||
type = fields.Constant(FUNCTION_ENTRY, data_key="type", required=True)
|
||||
|
||||
|
||||
class ConstructorAbiEntrySchema(FunctionBaseSchema):
|
||||
type = fields.Constant(CONSTRUCTOR_ENTRY, data_key="type", required=True)
|
||||
|
||||
|
||||
class L1HandlerAbiEntrySchema(FunctionBaseSchema):
|
||||
type = fields.Constant(L1_HANDLER_ENTRY, data_key="type", required=True)
|
||||
|
||||
|
||||
class EventAbiEntrySchema(Schema):
|
||||
type = fields.Constant(EVENT_ENTRY, data_key="type", required=True)
|
||||
name = fields.String(data_key="name", required=True)
|
||||
keys = fields.List(
|
||||
fields.Nested(TypedParameterSchema()), data_key="keys", required=True
|
||||
)
|
||||
data = fields.List(
|
||||
fields.Nested(TypedParameterSchema()), data_key="data", required=True
|
||||
)
|
||||
|
||||
|
||||
class StructAbiEntrySchema(Schema):
|
||||
type = fields.Constant(STRUCT_ENTRY, data_key="type", required=True)
|
||||
name = fields.String(data_key="name", required=True)
|
||||
size = fields.Integer(data_key="size", required=True)
|
||||
members = fields.List(
|
||||
fields.Nested(StructMemberSchema()), data_key="members", required=True
|
||||
)
|
||||
|
||||
|
||||
class ContractAbiEntrySchema(OneOfSchema):
|
||||
type_field_remove = False
|
||||
type_schemas = {
|
||||
FUNCTION_ENTRY: FunctionAbiEntrySchema,
|
||||
L1_HANDLER_ENTRY: L1HandlerAbiEntrySchema,
|
||||
CONSTRUCTOR_ENTRY: ConstructorAbiEntrySchema,
|
||||
EVENT_ENTRY: EventAbiEntrySchema,
|
||||
STRUCT_ENTRY: StructAbiEntrySchema,
|
||||
}
|
||||
63
ccxt/static_dependencies/starknet/abi/v0/shape.py
Normal file
63
ccxt/static_dependencies/starknet/abi/v0/shape.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# TODO (#1260): update pylint to 3.1.0 and remove pylint disable
|
||||
# pylint: disable=too-many-ancestors
|
||||
import sys
|
||||
from typing import List, Literal, Union
|
||||
|
||||
if sys.version_info < (3, 11):
|
||||
from typing_extensions import NotRequired, TypedDict
|
||||
else:
|
||||
from typing import NotRequired, TypedDict
|
||||
|
||||
STRUCT_ENTRY = "struct"
|
||||
FUNCTION_ENTRY = "function"
|
||||
CONSTRUCTOR_ENTRY = "constructor"
|
||||
L1_HANDLER_ENTRY = "l1_handler"
|
||||
EVENT_ENTRY = "event"
|
||||
|
||||
|
||||
class TypedMemberDict(TypedDict):
|
||||
name: str
|
||||
type: str
|
||||
|
||||
|
||||
class StructMemberDict(TypedMemberDict):
|
||||
offset: NotRequired[int]
|
||||
|
||||
|
||||
class StructDict(TypedDict):
|
||||
type: Literal["struct"]
|
||||
name: str
|
||||
size: int
|
||||
members: List[StructMemberDict]
|
||||
|
||||
|
||||
class FunctionBaseDict(TypedDict):
|
||||
name: str
|
||||
inputs: List[TypedMemberDict]
|
||||
outputs: List[TypedMemberDict]
|
||||
stateMutability: NotRequired[Literal["view"]]
|
||||
|
||||
|
||||
class FunctionDict(FunctionBaseDict):
|
||||
type: Literal["function"]
|
||||
|
||||
|
||||
class ConstructorDict(FunctionBaseDict):
|
||||
type: Literal["constructor"]
|
||||
|
||||
|
||||
class L1HandlerDict(FunctionBaseDict):
|
||||
type: Literal["l1_handler"]
|
||||
|
||||
|
||||
class EventDict(TypedDict):
|
||||
name: str
|
||||
type: Literal["event"]
|
||||
data: List[TypedMemberDict]
|
||||
keys: List[TypedMemberDict]
|
||||
|
||||
|
||||
AbiDictEntry = Union[
|
||||
StructDict, FunctionDict, ConstructorDict, L1HandlerDict, EventDict
|
||||
]
|
||||
AbiDictList = List[AbiDictEntry]
|
||||
Reference in New Issue
Block a user