diff --git a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py index b79789f27..5cab68aa8 100644 --- a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py +++ b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py @@ -36,6 +36,11 @@ class BasicValue(int, SSZValue, metaclass=BasicType): class Bit(BasicValue): # can't subclass bool. + def __new__(cls, value, *args, **kwargs): + if value < 0 or value > 1: + raise ValueError(f"value {value} out of bounds for bit") + return super().__new__(cls, value) + @classmethod def default(cls): return cls(False) @@ -49,7 +54,7 @@ class uint(BasicValue, metaclass=BasicType): def __new__(cls, value, *args, **kwargs): if value < 0: raise ValueError("unsigned types must not be negative") - if cls.byte_len and (value.bit_length() >> 3) > cls.byte_len: + if cls.byte_len and value.bit_length() > (cls.byte_len << 3): raise ValueError("value out of bounds for uint{}".format(cls.byte_len)) return super().__new__(cls, value) @@ -142,7 +147,7 @@ class Container(Series, metaclass=SSZType): @classmethod def get_fields(cls) -> Tuple[Tuple[str, SSZType], ...]: - return tuple((f, SSZType(t)) for f, t in dict(cls.__annotations__).items()) + return tuple((f, t) for f, t in cls.__annotations__.items()) def get_typed_values(self): return tuple(zip(self.get_field_values(), self.get_field_types())) @@ -190,6 +195,12 @@ class ParamsMeta(SSZType): o._bare = False return o + def __str__(self): + return f"{self.__name__}~{self.__class__.__name__}" + + def __repr__(self): + return self, self.__class__ + def attr_from_params(self, p): # single key params are valid too. Wrap them in a tuple. params = p if isinstance(p, tuple) else (p,) @@ -215,7 +226,7 @@ class ParamsMeta(SSZType): def __instancecheck__(self, obj): if obj.__class__.__name__ != self.__name__: return False - for name, typ in self.__annotations__: + for name, typ in self.__annotations__.items(): if hasattr(self, name) and hasattr(obj.__class__, name) \ and getattr(obj.__class__, name) != getattr(self, name): return False @@ -233,7 +244,7 @@ class ElementsBase(ParamsBase, metaclass=Elements): items = self.extract_args(*args) if not self.value_check(items): - raise ValueCheckError("Bad input for class {}: {}".format(self.__class__, items)) + raise ValueCheckError(f"Bad input for class {self.__class__}: {items}") self.items = items @classmethod @@ -245,7 +256,7 @@ class ElementsBase(ParamsBase, metaclass=Elements): x = list(args) if len(x) == 1 and isinstance(x[0], GeneratorType): x = list(x[0]) - return x if len(x) > 0 else cls.default() + return x def __str__(self): cls = self.__class__ diff --git a/test_libs/pyspec/eth2spec/utils/ssz/test_ssz_typing.py b/test_libs/pyspec/eth2spec/utils/ssz/test_ssz_typing.py new file mode 100644 index 000000000..a0705f8d3 --- /dev/null +++ b/test_libs/pyspec/eth2spec/utils/ssz/test_ssz_typing.py @@ -0,0 +1,131 @@ +from .ssz_typing import ( + SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bit, Container, List, Vector, Bytes, BytesN, + uint, uint8, uint16, uint32, uint64, uint128, uint256 +) + + +def test_subclasses(): + for u in [uint, uint8, uint16, uint32, uint64, uint128, uint256]: + assert issubclass(u, uint) + assert issubclass(u, int) + assert issubclass(u, BasicValue) + assert issubclass(u, SSZValue) + assert isinstance(u, SSZType) + assert isinstance(u, BasicType) + assert issubclass(Bit, BasicValue) + assert isinstance(Bit, BasicType) + + for c in [Container, List, Vector, Bytes, BytesN]: + assert issubclass(c, Series) + assert issubclass(c, SSZValue) + assert isinstance(c, SSZType) + assert not issubclass(c, BasicValue) + assert not isinstance(c, BasicType) + + for c in [List, Vector, Bytes, BytesN]: + assert isinstance(c, Elements) + + +def test_basic_instances(): + for u in [uint, uint8, uint16, uint32, uint64, uint128, uint256]: + v = u(123) + assert isinstance(v, uint) + assert isinstance(v, int) + assert isinstance(v, BasicValue) + assert isinstance(v, SSZValue) + + assert isinstance(Bit(True), BasicValue) + assert isinstance(Bit(False), BasicValue) + + +def test_basic_value_bounds(): + max = { + Bit: 2**1, + uint8: 2**(8 * 1), + uint16: 2**(8 * 2), + uint32: 2**(8 * 4), + uint64: 2**(8 * 8), + uint128: 2**(8 * 16), + uint256: 2**(8 * 32), + } + for k, v in max.items(): + # this should work + assert k(v - 1) == v - 1 + # but we do not allow overflows + try: + k(v) + assert False + except ValueError: + pass + + for k, _ in max.items(): + # this should work + assert k(0) == 0 + # but we do not allow underflows + try: + k(-1) + assert False + except ValueError: + pass + + +def test_container(): + class Foo(Container): + a: uint8 + b: uint32 + + assert issubclass(Foo, Container) + assert issubclass(Foo, SSZValue) + assert issubclass(Foo, Series) + + assert Foo.is_fixed_size() + x = Foo(a=uint8(123), b=uint32(45)) + assert x.a == 123 + assert x.b == 45 + assert isinstance(x.a, uint8) + assert isinstance(x.b, uint32) + assert x.type().is_fixed_size() + + class Bar(Container): + a: uint8 + b: List[uint8, 1024] + + assert not Bar.is_fixed_size() + + y = Bar(a=uint8(123), b=List[uint8, 1024](uint8(1), uint8(2))) + assert y.a == 123 + assert len(y.b) == 2 + assert isinstance(y.a, uint8) + assert isinstance(y.b, List[uint8, 1024]) + assert not y.type().is_fixed_size() + assert y.b[0] == 1 + v: List = y.b + assert v.type().elem_type == uint8 + assert v.type().length == 1024 + + +def test_list(): + typ = List[uint64, 128] + assert issubclass(typ, List) + assert issubclass(typ, SSZValue) + assert issubclass(typ, Series) + assert isinstance(typ, Elements) + + assert not typ.is_fixed_size() + + assert len(typ()) == 0 # empty + assert len(typ(uint64(0))) == 1 # single arg + assert len(typ(uint64(i) for i in range(10))) == 10 # generator + assert len(typ(uint64(0), uint64(1), uint64(2))) == 3 # args + + v = typ(uint64(0)) + v[0] = uint64(123) + assert v[0] == 123 + assert isinstance(v[0], uint64) + + assert isinstance(v, List) + assert isinstance(v, List[uint64, 128]) + assert isinstance(v, typ) + assert isinstance(v, SSZValue) + assert isinstance(v, Series) + assert isinstance(v.type(), Elements)