add
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
from .array_serializer import ArraySerializer
|
||||
from .bool_serializer import BoolSerializer
|
||||
from .byte_array_serializer import ByteArraySerializer
|
||||
from .cairo_data_serializer import CairoDataSerializer
|
||||
from .felt_serializer import FeltSerializer
|
||||
from .named_tuple_serializer import NamedTupleSerializer
|
||||
from .payload_serializer import PayloadSerializer
|
||||
from .struct_serializer import StructSerializer
|
||||
from .tuple_serializer import TupleSerializer
|
||||
from .uint256_serializer import Uint256Serializer
|
||||
@@ -0,0 +1,82 @@
|
||||
# We have to use parametrised type from typing
|
||||
from collections import OrderedDict as _OrderedDict
|
||||
from typing import Dict, Generator, List, OrderedDict
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
# The actual serialization logic is very similar among all serializers: they either serialize data based on
|
||||
# position or their name. Having this logic reused adds indirection, but makes sure proper logic is used everywhere.
|
||||
|
||||
|
||||
def deserialize_to_list(
|
||||
deserializers: List[CairoDataSerializer], context: DeserializationContext
|
||||
) -> List:
|
||||
"""
|
||||
Deserializes data from context to list. This logic is used in every sequential type (arrays and tuples).
|
||||
"""
|
||||
result = []
|
||||
|
||||
for index, serializer in enumerate(deserializers):
|
||||
with context.push_entity(f"[{index}]"):
|
||||
result.append(serializer.deserialize_with_context(context))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def deserialize_to_dict(
|
||||
deserializers: OrderedDict[str, CairoDataSerializer],
|
||||
context: DeserializationContext,
|
||||
) -> OrderedDict:
|
||||
"""
|
||||
Deserializes data from context to dictionary. This logic is used in every type with named fields (structs,
|
||||
named tuples and payloads).
|
||||
"""
|
||||
result = _OrderedDict()
|
||||
|
||||
for key, serializer in deserializers.items():
|
||||
with context.push_entity(key):
|
||||
result[key] = serializer.deserialize_with_context(context)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def serialize_from_list(
|
||||
serializers: List[CairoDataSerializer], context: SerializationContext, values: List
|
||||
) -> Generator[int, None, None]:
|
||||
"""
|
||||
Serializes data from list. This logic is used in every sequential type (arrays and tuples).
|
||||
"""
|
||||
context.ensure_valid_value(
|
||||
len(serializers) == len(values),
|
||||
f"expected {len(serializers)} elements, {len(values)} provided",
|
||||
)
|
||||
|
||||
for index, (serializer, value) in enumerate(zip(serializers, values)):
|
||||
with context.push_entity(f"[{index}]"):
|
||||
yield from serializer.serialize_with_context(context, value)
|
||||
|
||||
|
||||
def serialize_from_dict(
|
||||
serializers: OrderedDict[str, CairoDataSerializer],
|
||||
context: SerializationContext,
|
||||
values: Dict,
|
||||
) -> Generator[int, None, None]:
|
||||
"""
|
||||
Serializes data from dict. This logic is used in every type with named fields (structs, named tuples and payloads).
|
||||
"""
|
||||
excessive_keys = set(values.keys()).difference(serializers.keys())
|
||||
context.ensure_valid_value(
|
||||
not excessive_keys,
|
||||
f"unexpected keys '{','.join(excessive_keys)}' were provided",
|
||||
)
|
||||
|
||||
for name, serializer in serializers.items():
|
||||
with context.push_entity(name):
|
||||
context.ensure_valid_value(name in values, f"key '{name}' is missing")
|
||||
yield from serializer.serialize_with_context(context, values[name])
|
||||
@@ -0,0 +1,43 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator, Iterable, List
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from ..data_serializers._common import (
|
||||
deserialize_to_list,
|
||||
serialize_from_list,
|
||||
)
|
||||
from ..data_serializers.cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArraySerializer(CairoDataSerializer[Iterable, List]):
|
||||
"""
|
||||
Serializer for arrays. In abi they are represented as a pointer to a type.
|
||||
Can serialize any iterable and prepends its length to resulting list.
|
||||
Deserializes data to a list.
|
||||
|
||||
Examples:
|
||||
[1,2,3] => [3,1,2,3]
|
||||
[] => [0]
|
||||
"""
|
||||
|
||||
inner_serializer: CairoDataSerializer
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> List:
|
||||
with context.push_entity("len"):
|
||||
[size] = context.reader.read(1)
|
||||
|
||||
return deserialize_to_list([self.inner_serializer] * size, context)
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: List
|
||||
) -> Generator[int, None, None]:
|
||||
yield len(value)
|
||||
yield from serialize_from_list(
|
||||
[self.inner_serializer] * len(value), context, value
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator
|
||||
|
||||
from .._context import (
|
||||
Context,
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BoolSerializer(CairoDataSerializer[bool, int]):
|
||||
"""
|
||||
Serializer for boolean.
|
||||
"""
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> bool:
|
||||
[val] = context.reader.read(1)
|
||||
self._ensure_bool(context, val)
|
||||
return bool(val)
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: bool
|
||||
) -> Generator[int, None, None]:
|
||||
context.ensure_valid_type(value, isinstance(value, bool), "bool")
|
||||
self._ensure_bool(context, value)
|
||||
yield int(value)
|
||||
|
||||
@staticmethod
|
||||
def _ensure_bool(context: Context, value: int):
|
||||
context.ensure_valid_value(
|
||||
value in [0, 1],
|
||||
f"invalid value '{value}' - must be in [0, 2) range",
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator
|
||||
|
||||
from ...cairo.felt import decode_shortstring, encode_shortstring
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from ._common import (
|
||||
deserialize_to_list,
|
||||
serialize_from_list,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
from .felt_serializer import FeltSerializer
|
||||
|
||||
BYTES_31_SIZE = 31
|
||||
|
||||
|
||||
@dataclass
|
||||
class ByteArraySerializer(CairoDataSerializer[str, str]):
|
||||
"""
|
||||
Serializer for ByteArrays. Serializes to and deserializes from str values.
|
||||
|
||||
Examples:
|
||||
"" => [0,0,0]
|
||||
"hello" => [0,448378203247,5]
|
||||
"""
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> str:
|
||||
with context.push_entity("data_array_len"):
|
||||
[size] = context.reader.read(1)
|
||||
|
||||
data = deserialize_to_list([FeltSerializer()] * size, context)
|
||||
|
||||
with context.push_entity("pending_word"):
|
||||
[pending_word] = context.reader.read(1)
|
||||
|
||||
with context.push_entity("pending_word_len"):
|
||||
[pending_word_len] = context.reader.read(1)
|
||||
|
||||
pending_word = decode_shortstring(pending_word)
|
||||
context.ensure_valid_value(
|
||||
len(pending_word) == pending_word_len,
|
||||
f"Invalid length {pending_word_len} for pending word {pending_word}",
|
||||
)
|
||||
|
||||
data_joined = "".join(map(decode_shortstring, data))
|
||||
return data_joined + pending_word
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: str
|
||||
) -> Generator[int, None, None]:
|
||||
context.ensure_valid_type(value, isinstance(value, str), "str")
|
||||
data = [
|
||||
value[i : i + BYTES_31_SIZE] for i in range(0, len(value), BYTES_31_SIZE)
|
||||
]
|
||||
pending_word = (
|
||||
"" if len(data) == 0 or len(data[-1]) == BYTES_31_SIZE else data.pop(-1)
|
||||
)
|
||||
|
||||
yield len(data)
|
||||
yield from serialize_from_list([FeltSerializer()] * len(data), context, data)
|
||||
yield encode_shortstring(pending_word)
|
||||
yield len(pending_word)
|
||||
@@ -0,0 +1,71 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generator, Generic, List, TypeVar
|
||||
|
||||
from .._calldata_reader import CairoData
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
|
||||
# Python type that is accepted by a serializer
|
||||
# pylint: disable=invalid-name
|
||||
SerializationType = TypeVar("SerializationType")
|
||||
|
||||
# Python type that will be returned from a serializer. Often same as SerializationType.
|
||||
# pylint: disable=invalid-name
|
||||
DeserializationType = TypeVar("DeserializationType")
|
||||
|
||||
|
||||
class CairoDataSerializer(ABC, Generic[SerializationType, DeserializationType]):
|
||||
"""
|
||||
Base class for serializing/deserializing data to/from calldata.
|
||||
"""
|
||||
|
||||
def deserialize(self, data: List[int]) -> DeserializationType:
|
||||
"""
|
||||
Transform calldata into python value.
|
||||
|
||||
:param data: calldata to deserialize.
|
||||
:return: defined DeserializationType.
|
||||
"""
|
||||
with DeserializationContext.create(data) as context:
|
||||
return self.deserialize_with_context(context)
|
||||
|
||||
def serialize(self, data: SerializationType) -> CairoData:
|
||||
"""
|
||||
Transform python data into calldata.
|
||||
|
||||
:param data: data to serialize.
|
||||
:return: calldata.
|
||||
"""
|
||||
with SerializationContext.create() as context:
|
||||
serialized_data = list(self.serialize_with_context(context, data))
|
||||
|
||||
return self.remove_units_from_serialized_data(serialized_data)
|
||||
|
||||
@abstractmethod
|
||||
def deserialize_with_context(
|
||||
self, context: DeserializationContext
|
||||
) -> DeserializationType:
|
||||
"""
|
||||
Transform calldata into python value.
|
||||
|
||||
:param context: context of this deserialization.
|
||||
:return: defined DeserializationType.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: SerializationType
|
||||
) -> Generator[int, None, None]:
|
||||
"""
|
||||
Transform python value into calldata.
|
||||
|
||||
:param context: context of this serialization.
|
||||
:param value: python value to serialize.
|
||||
:return: defined SerializationType.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def remove_units_from_serialized_data(serialized_data: List) -> List:
|
||||
return [x for x in serialized_data if x is not None]
|
||||
@@ -0,0 +1,71 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Generator, OrderedDict, Tuple, Union
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
from ..tuple_dataclass import TupleDataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnumSerializer(CairoDataSerializer[Union[Dict, TupleDataclass], TupleDataclass]):
|
||||
"""
|
||||
Serializer of enums.
|
||||
Can serialize a dictionary and TupleDataclass.
|
||||
Deserializes data to a TupleDataclass.
|
||||
|
||||
Example:
|
||||
enum MyEnum {
|
||||
a: u128,
|
||||
b: u128
|
||||
}
|
||||
|
||||
{"a": 1} => [0, 1]
|
||||
{"b": 100} => [1, 100]
|
||||
TupleDataclass(variant='a', value=100) => [0, 100]
|
||||
"""
|
||||
|
||||
serializers: OrderedDict[str, CairoDataSerializer]
|
||||
|
||||
def deserialize_with_context(
|
||||
self, context: DeserializationContext
|
||||
) -> TupleDataclass:
|
||||
[variant_index] = context.reader.read(1)
|
||||
variant_name, serializer = self._get_variant(variant_index)
|
||||
|
||||
with context.push_entity("enum.variant: " + variant_name):
|
||||
result_dict = {
|
||||
"variant": variant_name,
|
||||
"value": serializer.deserialize_with_context(context),
|
||||
}
|
||||
|
||||
return TupleDataclass.from_dict(result_dict)
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Union[Dict, TupleDataclass]
|
||||
) -> Generator[int, None, None]:
|
||||
if isinstance(value, Dict):
|
||||
items = list(value.items())
|
||||
if len(items) != 1:
|
||||
raise ValueError(
|
||||
"Can serialize only one enum variant, got: " + str(len(items))
|
||||
)
|
||||
|
||||
variant_name, variant_value = items[0]
|
||||
else:
|
||||
variant_name, variant_value = value
|
||||
|
||||
yield self._get_variant_index(variant_name)
|
||||
yield from self.serializers[variant_name].serialize_with_context(
|
||||
context, variant_value
|
||||
)
|
||||
|
||||
def _get_variant(self, variant_index: int) -> Tuple[str, CairoDataSerializer]:
|
||||
return list(self.serializers.items())[variant_index]
|
||||
|
||||
def _get_variant_index(self, variant_name: str) -> int:
|
||||
return list(self.serializers.keys()).index(variant_name)
|
||||
@@ -0,0 +1,50 @@
|
||||
import warnings
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator
|
||||
|
||||
from ...cairo.felt import encode_shortstring, is_in_felt_range
|
||||
from ...constants import FIELD_PRIME
|
||||
from .._context import (
|
||||
Context,
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FeltSerializer(CairoDataSerializer[int, int]):
|
||||
"""
|
||||
Serializer for field element. At the time of writing it is the only existing numeric type.
|
||||
"""
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> int:
|
||||
[val] = context.reader.read(1)
|
||||
self._ensure_felt(context, val)
|
||||
return val
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: int
|
||||
) -> Generator[int, None, None]:
|
||||
if isinstance(value, str):
|
||||
warnings.warn(
|
||||
"Serializing shortstrings in FeltSerializer is deprecated. "
|
||||
"Use starknet_py.cairo.felt.encode_shortstring instead.",
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
value = encode_shortstring(value)
|
||||
yield value
|
||||
return
|
||||
|
||||
context.ensure_valid_type(value, isinstance(value, int), "int")
|
||||
self._ensure_felt(context, value)
|
||||
yield value
|
||||
|
||||
@staticmethod
|
||||
def _ensure_felt(context: Context, value: int):
|
||||
context.ensure_valid_value(
|
||||
is_in_felt_range(value),
|
||||
f"invalid value '{value}' - must be in [0, {FIELD_PRIME}) range",
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Generator, NamedTuple, OrderedDict, Union
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from ._common import (
|
||||
deserialize_to_dict,
|
||||
serialize_from_dict,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
from ..tuple_dataclass import TupleDataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class NamedTupleSerializer(
|
||||
CairoDataSerializer[Union[Dict, NamedTuple, TupleDataclass], TupleDataclass]
|
||||
):
|
||||
"""
|
||||
Serializer for tuples with named fields.
|
||||
Can serialize a dictionary, a named tuple and TupleDataclass.
|
||||
Deserializes data to a TupleDataclass.
|
||||
|
||||
Example:
|
||||
{"a": 1, "b": 2} => [1,2]
|
||||
"""
|
||||
|
||||
serializers: OrderedDict[str, CairoDataSerializer]
|
||||
|
||||
def deserialize_with_context(
|
||||
self, context: DeserializationContext
|
||||
) -> TupleDataclass:
|
||||
as_dictionary = deserialize_to_dict(self.serializers, context)
|
||||
return TupleDataclass.from_dict(as_dictionary)
|
||||
|
||||
def serialize_with_context(
|
||||
self,
|
||||
context: SerializationContext,
|
||||
value: Union[Dict, NamedTuple, TupleDataclass],
|
||||
) -> Generator[int, None, None]:
|
||||
# We can't use isinstance(value, NamedTuple), because there is no NamedTuple type.
|
||||
context.ensure_valid_type(
|
||||
value,
|
||||
isinstance(value, (dict, TupleDataclass)) or self._is_namedtuple(value),
|
||||
"dict, NamedTuple or TupleDataclass",
|
||||
)
|
||||
|
||||
# noinspection PyUnresolvedReferences, PyProtectedMember
|
||||
values: Dict = value if isinstance(value, dict) else value._asdict()
|
||||
|
||||
yield from serialize_from_dict(self.serializers, context, values)
|
||||
|
||||
@staticmethod
|
||||
def _is_namedtuple(value) -> bool:
|
||||
return isinstance(value, tuple) and hasattr(value, "_fields")
|
||||
@@ -0,0 +1,43 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Generator, Optional
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class OptionSerializer(CairoDataSerializer[Optional[Any], Optional[Any]]):
|
||||
"""
|
||||
Serializer for Option type.
|
||||
Can serialize None and common CairoTypes.
|
||||
Deserializes data to None or CairoType.
|
||||
|
||||
Example:
|
||||
None => [1]
|
||||
{"option1": 123, "option2": None} => [0, 123, 1]
|
||||
"""
|
||||
|
||||
serializer: CairoDataSerializer
|
||||
|
||||
def deserialize_with_context(
|
||||
self, context: DeserializationContext
|
||||
) -> Optional[Any]:
|
||||
(is_none,) = context.reader.read(1)
|
||||
if is_none == 1:
|
||||
return None
|
||||
|
||||
return self.serializer.deserialize_with_context(context)
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Optional[Any]
|
||||
) -> Generator[int, None, None]:
|
||||
if value is None:
|
||||
yield 1
|
||||
else:
|
||||
yield 0
|
||||
yield from self.serializer.serialize_with_context(context, value)
|
||||
@@ -0,0 +1,40 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Generator, List, Tuple
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class OutputSerializer(CairoDataSerializer[List, Tuple]):
|
||||
"""
|
||||
Serializer for function output.
|
||||
Can't serialize anything.
|
||||
Deserializes data to a Tuple.
|
||||
|
||||
Example:
|
||||
[1, 1, 1] => (340282366920938463463374607431768211457)
|
||||
"""
|
||||
|
||||
serializers: List[CairoDataSerializer] = field(init=True)
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> Tuple:
|
||||
result = []
|
||||
|
||||
for index, serializer in enumerate(self.serializers):
|
||||
with context.push_entity("output[" + str(index) + "]"):
|
||||
result.append(serializer.deserialize_with_context(context))
|
||||
|
||||
return tuple(result)
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Dict
|
||||
) -> Generator[int, None, None]:
|
||||
raise ValueError(
|
||||
"Output serializer can't be used to transform python data into calldata."
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
from collections import OrderedDict as _OrderedDict
|
||||
from dataclasses import InitVar, dataclass, field
|
||||
from typing import Dict, Generator, OrderedDict
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from ._common import (
|
||||
deserialize_to_dict,
|
||||
serialize_from_dict,
|
||||
)
|
||||
from .array_serializer import ArraySerializer
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
from .felt_serializer import FeltSerializer
|
||||
from ..tuple_dataclass import TupleDataclass
|
||||
|
||||
SIZE_SUFFIX = "_len"
|
||||
SIZE_SUFFIX_LEN = len(SIZE_SUFFIX)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PayloadSerializer(CairoDataSerializer[Dict, TupleDataclass]):
|
||||
"""
|
||||
Serializer for payloads like function arguments/function outputs/events.
|
||||
Can serialize a dictionary.
|
||||
Deserializes data to a TupleDataclass.
|
||||
|
||||
Example:
|
||||
{"a": 1, "b": 2} => [1,2]
|
||||
"""
|
||||
|
||||
# Value present only in constructor.
|
||||
# We don't want to mutate the serializers received in constructor.
|
||||
input_serializers: InitVar[OrderedDict[str, CairoDataSerializer]]
|
||||
|
||||
serializers: OrderedDict[str, CairoDataSerializer] = field(init=False)
|
||||
|
||||
def __post_init__(self, input_serializers):
|
||||
"""
|
||||
ABI adds ARG_len for every argument ARG that is an array. We parse length as a part of ArraySerializer, so we
|
||||
need to remove those lengths from args.
|
||||
"""
|
||||
self.serializers = _OrderedDict(
|
||||
(key, serializer)
|
||||
for key, serializer in input_serializers.items()
|
||||
if not self._is_len_arg(key, input_serializers)
|
||||
)
|
||||
|
||||
def deserialize_with_context(
|
||||
self, context: DeserializationContext
|
||||
) -> TupleDataclass:
|
||||
as_dictionary = deserialize_to_dict(self.serializers, context)
|
||||
return TupleDataclass.from_dict(as_dictionary)
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Dict
|
||||
) -> Generator[int, None, None]:
|
||||
yield from serialize_from_dict(self.serializers, context, value)
|
||||
|
||||
@staticmethod
|
||||
def _is_len_arg(arg_name: str, serializers: Dict[str, CairoDataSerializer]) -> bool:
|
||||
return (
|
||||
arg_name.endswith(SIZE_SUFFIX)
|
||||
and isinstance(serializers[arg_name], FeltSerializer)
|
||||
# There is an ArraySerializer under key that is arg_name without the size suffix
|
||||
and isinstance(
|
||||
serializers.get(arg_name[:-SIZE_SUFFIX_LEN]), ArraySerializer
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Generator, OrderedDict
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from ._common import (
|
||||
deserialize_to_dict,
|
||||
serialize_from_dict,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StructSerializer(CairoDataSerializer[Dict, Dict]):
|
||||
"""
|
||||
Serializer of custom structures.
|
||||
Can serialize a dictionary.
|
||||
Deserializes data to a dictionary.
|
||||
|
||||
Example:
|
||||
{"a": 1, "b": 2} => [1,2]
|
||||
"""
|
||||
|
||||
serializers: OrderedDict[str, CairoDataSerializer]
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> Dict:
|
||||
return deserialize_to_dict(self.serializers, context)
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Dict
|
||||
) -> Generator[int, None, None]:
|
||||
yield from serialize_from_dict(self.serializers, context, value)
|
||||
@@ -0,0 +1,36 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator, Iterable, List, Tuple
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from ._common import (
|
||||
deserialize_to_list,
|
||||
serialize_from_list,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TupleSerializer(CairoDataSerializer[Iterable, Tuple]):
|
||||
"""
|
||||
Serializer for tuples without named fields.
|
||||
Can serialize any iterable.
|
||||
Deserializes data to a python tuple.
|
||||
|
||||
Example:
|
||||
(1,2,(3,4)) => [1,2,3,4]
|
||||
"""
|
||||
|
||||
serializers: List[CairoDataSerializer]
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> Tuple:
|
||||
return tuple(deserialize_to_list(self.serializers, context))
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Iterable
|
||||
) -> Generator[int, None, None]:
|
||||
yield from serialize_from_list(self.serializers, context, [*value])
|
||||
@@ -0,0 +1,76 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator, TypedDict, Union
|
||||
|
||||
from ...cairo.felt import uint256_range_check
|
||||
from .._context import (
|
||||
Context,
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
U128_UPPER_BOUND = 2**128
|
||||
|
||||
|
||||
class Uint256Dict(TypedDict):
|
||||
low: int
|
||||
high: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class Uint256Serializer(CairoDataSerializer[Union[int, Uint256Dict], int]):
|
||||
"""
|
||||
Serializer of Uint256. In Cairo it is represented by structure {low: Uint128, high: Uint128}.
|
||||
Can serialize an int.
|
||||
Deserializes data to an int.
|
||||
|
||||
Examples:
|
||||
0 => [0,0]
|
||||
1 => [1,0]
|
||||
2**128 => [0,1]
|
||||
3 + 2**128 => [3,1]
|
||||
"""
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> int:
|
||||
[low, high] = context.reader.read(2)
|
||||
|
||||
# Checking if resulting value is in [0, 2**256) range is not enough. Uint256 should be made of two uint128.
|
||||
with context.push_entity("low"):
|
||||
self._ensure_valid_uint128(low, context)
|
||||
with context.push_entity("high"):
|
||||
self._ensure_valid_uint128(high, context)
|
||||
|
||||
return (high << 128) + low
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Union[int, Uint256Dict]
|
||||
) -> Generator[int, None, None]:
|
||||
context.ensure_valid_type(value, isinstance(value, (int, dict)), "int or dict")
|
||||
if isinstance(value, int):
|
||||
yield from self._serialize_from_int(value)
|
||||
else:
|
||||
yield from self._serialize_from_dict(context, value)
|
||||
|
||||
@staticmethod
|
||||
def _serialize_from_int(value: int) -> Generator[int, None, None]:
|
||||
uint256_range_check(value)
|
||||
result = (value % 2**128, value // 2**128)
|
||||
yield from result
|
||||
|
||||
def _serialize_from_dict(
|
||||
self, context: SerializationContext, value: Uint256Dict
|
||||
) -> Generator[int, None, None]:
|
||||
with context.push_entity("low"):
|
||||
self._ensure_valid_uint128(value["low"], context)
|
||||
yield value["low"]
|
||||
with context.push_entity("high"):
|
||||
self._ensure_valid_uint128(value["high"], context)
|
||||
yield value["high"]
|
||||
|
||||
@staticmethod
|
||||
def _ensure_valid_uint128(value: int, context: Context):
|
||||
context.ensure_valid_value(
|
||||
0 <= value < U128_UPPER_BOUND, "expected value in range [0;2**128)"
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator, TypedDict, Union
|
||||
|
||||
from ...cairo.felt import uint256_range_check
|
||||
from .._context import (
|
||||
Context,
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
class Uint256Dict(TypedDict):
|
||||
low: int
|
||||
high: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class UintSerializer(CairoDataSerializer[Union[int, Uint256Dict], int]):
|
||||
"""
|
||||
Serializer of uint. In Cairo there are few uints (u8, ..., u128 and u256).
|
||||
u256 is represented by structure {low: u128, high: u128}.
|
||||
Can serialize an int and dict.
|
||||
Deserializes data to an int.
|
||||
|
||||
Examples:
|
||||
if bits < 256:
|
||||
0 => [0]
|
||||
1 => [1]
|
||||
2**128-1 => [2**128-1]
|
||||
else:
|
||||
0 => [0,0]
|
||||
1 => [1,0]
|
||||
2**128 => [0,1]
|
||||
3 + 2**128 => [3,1]
|
||||
"""
|
||||
|
||||
bits: int
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> int:
|
||||
if self.bits < 256:
|
||||
(uint,) = context.reader.read(1)
|
||||
with context.push_entity("uint" + str(self.bits)):
|
||||
self._ensure_valid_uint(uint, context, self.bits)
|
||||
|
||||
return uint
|
||||
|
||||
[low, high] = context.reader.read(2)
|
||||
|
||||
# Checking if resulting value is in [0, 2**256) range is not enough. Uint256 should be made of two uint128.
|
||||
with context.push_entity("low"):
|
||||
self._ensure_valid_uint(low, context, bits=128)
|
||||
with context.push_entity("high"):
|
||||
self._ensure_valid_uint(high, context, bits=128)
|
||||
|
||||
return (high << 128) + low
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Union[int, Uint256Dict]
|
||||
) -> Generator[int, None, None]:
|
||||
context.ensure_valid_type(value, isinstance(value, (int, dict)), "int or dict")
|
||||
if isinstance(value, int):
|
||||
yield from self._serialize_from_int(value, context, self.bits)
|
||||
else:
|
||||
yield from self._serialize_from_dict(context, value)
|
||||
|
||||
@staticmethod
|
||||
def _serialize_from_int(
|
||||
value: int, context: SerializationContext, bits: int
|
||||
) -> Generator[int, None, None]:
|
||||
if bits < 256:
|
||||
UintSerializer._ensure_valid_uint(value, context, bits)
|
||||
|
||||
yield value
|
||||
else:
|
||||
uint256_range_check(value)
|
||||
|
||||
result = (value % 2**128, value >> 128)
|
||||
yield from result
|
||||
|
||||
def _serialize_from_dict(
|
||||
self, context: SerializationContext, value: Uint256Dict
|
||||
) -> Generator[int, None, None]:
|
||||
with context.push_entity("low"):
|
||||
self._ensure_valid_uint(value["low"], context, bits=128)
|
||||
yield value["low"]
|
||||
with context.push_entity("high"):
|
||||
self._ensure_valid_uint(value["high"], context, bits=128)
|
||||
yield value["high"]
|
||||
|
||||
@staticmethod
|
||||
def _ensure_valid_uint(value: int, context: Context, bits: int):
|
||||
"""
|
||||
Ensures that value is a valid uint on `bits` bits.
|
||||
"""
|
||||
context.ensure_valid_value(
|
||||
0 <= value < 2**bits, "expected value in range [0;2**" + str(bits) + ")"
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Generator, Optional
|
||||
|
||||
from .._context import (
|
||||
DeserializationContext,
|
||||
SerializationContext,
|
||||
)
|
||||
from .cairo_data_serializer import (
|
||||
CairoDataSerializer,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class UnitSerializer(CairoDataSerializer[None, None]):
|
||||
"""
|
||||
Serializer for unit type.
|
||||
Can only serialize None.
|
||||
Deserializes data to None.
|
||||
|
||||
Example:
|
||||
[] => None
|
||||
"""
|
||||
|
||||
def deserialize_with_context(self, context: DeserializationContext) -> None:
|
||||
return None
|
||||
|
||||
def serialize_with_context(
|
||||
self, context: SerializationContext, value: Optional[Any]
|
||||
) -> Generator[None, None, None]:
|
||||
if value is not None:
|
||||
raise ValueError("Can only serialize `None`.")
|
||||
yield None
|
||||
Reference in New Issue
Block a user