From efbe1e55f7c7f50129103a3147a955b1238829f1 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 14 Apr 2022 15:32:41 -0400 Subject: [PATCH 01/26] adding string and address types --- pyteal/ast/abi/__init__.py | 5 ++++ pyteal/ast/abi/string.py | 55 ++++++++++++++++++++++++++++++++++++++ pyteal/ast/abi/util.py | 11 ++++++++ 3 files changed, 71 insertions(+) create mode 100644 pyteal/ast/abi/string.py diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index e2d57bf29..2fecd481d 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,3 +1,4 @@ +from .string import String, Address, StringTypeSpec, AddressTypeSpec from .type import TypeSpec, BaseType, ComputedValue from .bool import BoolTypeSpec, Bool from .uint import ( @@ -32,6 +33,10 @@ from .method_return import MethodReturn __all__ = [ + "String", + "StringTypeSpec", + "Address", + "AddressTypeSpec", "TypeSpec", "BaseType", "ComputedValue", diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py new file mode 100644 index 000000000..3b806b7c2 --- /dev/null +++ b/pyteal/ast/abi/string.py @@ -0,0 +1,55 @@ +from .array_static import StaticArray, StaticArrayTypeSpec +from .array_dynamic import DynamicArray, DynamicArrayTypeSpec +from .uint import ByteTypeSpec + +address_length = 32 + + +class AddressTypeSpec(StaticArrayTypeSpec): + def __init__(self) -> None: + super().__init__(ByteTypeSpec, address_length) + + def new_instance(self) -> "Address": + return Address() + + def __str__(self) -> str: + return "address" + + +AddressTypeSpec.__module__ = "pyteal" + + +class Address(StaticArray): + def __init__(self) -> None: + super().__init__(AddressTypeSpec(), address_length) + + def type_spec(self) -> AddressTypeSpec: + return AddressTypeSpec() + + +Address.__module__ = "pyteal" + + +class StringTypeSpec(DynamicArrayTypeSpec): + def __init__(self) -> None: + super().__init__(ByteTypeSpec) + + def new_instance(self) -> "String": + return String() + + def __str__(self) -> str: + return "string" + + +StringTypeSpec.__module__ = "pyteal" + + +class String(DynamicArray): + def __init__(self) -> None: + super().__init__(StringTypeSpec()) + + def type_spec(self) -> StringTypeSpec: + return StringTypeSpec() + + +String.__module__ = "pyteal" diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index e9077ded6..89876e751 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -107,6 +107,7 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: Tuple4, Tuple5, ) + from .string import String, Address, StringTypeSpec, AddressTypeSpec origin = get_origin(annotation) if origin is None: @@ -144,6 +145,16 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: raise TypeError("Uint64 expects 0 type arguments. Got: {}".format(args)) return Uint64TypeSpec() + if origin is String: + if len(args) != 0: + raise TypeError("String expects 0 arguments. Got: {}".format(args)) + return StringTypeSpec() + + if origin is Address: + if len(args) != 0: + raise TypeError("Address expects 0 arguments. Got: {}".format(args)) + return AddressTypeSpec() + if origin is DynamicArray: if len(args) != 1: raise TypeError( From 7db17ca402fcf9551028ce4183748ce3897d6309 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 14 Apr 2022 16:30:28 -0400 Subject: [PATCH 02/26] fix type spec, added get and thoughts on set --- pyteal/ast/abi/string.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 3b806b7c2..043ae12f0 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,13 +1,17 @@ from .array_static import StaticArray, StaticArrayTypeSpec from .array_dynamic import DynamicArray, DynamicArrayTypeSpec from .uint import ByteTypeSpec +from .util import substringForDecoding + +from ..int import Int +from ..expr import Expr address_length = 32 class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec, address_length) + super().__init__(ByteTypeSpec(), address_length) def new_instance(self) -> "Address": return Address() @@ -26,13 +30,16 @@ def __init__(self) -> None: def type_spec(self) -> AddressTypeSpec: return AddressTypeSpec() + def get(self) -> Expr: + return self.stored_value.load() + Address.__module__ = "pyteal" class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec) + super().__init__(ByteTypeSpec()) def new_instance(self) -> "String": return String() @@ -51,5 +58,8 @@ def __init__(self) -> None: def type_spec(self) -> StringTypeSpec: return StringTypeSpec() + def get(self) -> Expr: + return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) + String.__module__ = "pyteal" From 40554243f48ed3455684dd399a7ffff3d5fd680b Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 15 Apr 2022 08:33:09 -0400 Subject: [PATCH 03/26] move address to its own file, starting to add tests --- pyteal/ast/abi/__init__.py | 3 +- pyteal/ast/abi/address.py | 33 ++++++++++++++ pyteal/ast/abi/address_test.py | 78 ++++++++++++++++++++++++++++++++++ pyteal/ast/abi/string.py | 17 ++++++++ pyteal/ast/abi/string_test.py | 33 ++++++++++++++ pyteal/ast/abi/util.py | 3 +- 6 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 pyteal/ast/abi/address.py create mode 100644 pyteal/ast/abi/address_test.py create mode 100644 pyteal/ast/abi/string_test.py diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 2fecd481d..aa670ce4f 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,5 @@ -from .string import String, Address, StringTypeSpec, AddressTypeSpec +from .string import String, StringTypeSpec +from .address import AddressTypeSpec, Address from .type import TypeSpec, BaseType, ComputedValue from .bool import BoolTypeSpec, Bool from .uint import ( diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py new file mode 100644 index 000000000..83bf9682e --- /dev/null +++ b/pyteal/ast/abi/address.py @@ -0,0 +1,33 @@ +from .array_static import StaticArray, StaticArrayTypeSpec +from .uint import ByteTypeSpec +from ..expr import Expr + +address_length = 32 + + +class AddressTypeSpec(StaticArrayTypeSpec): + def __init__(self) -> None: + super().__init__(ByteTypeSpec(), address_length) + + def new_instance(self) -> "Address": + return Address() + + def __str__(self) -> str: + return "address" + + +AddressTypeSpec.__module__ = "pyteal" + + +class Address(StaticArray): + def __init__(self) -> None: + super().__init__(AddressTypeSpec(), address_length) + + def type_spec(self) -> AddressTypeSpec: + return AddressTypeSpec() + + def get(self) -> Expr: + return self.stored_value.load() + + +Address.__module__ = "pyteal" diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py new file mode 100644 index 000000000..8f4e2ff29 --- /dev/null +++ b/pyteal/ast/abi/address_test.py @@ -0,0 +1,78 @@ +from .type_test import ContainerType +from os import urandom + +from ... import * + +options = CompileOptions(version=5) + +def test_AddressTypeSpec_str(): + assert str(abi.AddressTypeSpec()) == "address" + +def test_AddressTypeSpec_is_dynamic(): + assert not (abi.AddressTypeSpec()).is_dynamic() + +def test_AddressTypeSpec_is_dynamic(): + assert (abi.AddressTypeSpec()).byte_length_static() == 32 + +def test_AddressTypeSpec_new_instance(): + assert isinstance(abi.AddressTypeSpec().new_instance(), abi.Address) + +def test_AddressTypeSpec_eq(): + assert abi.AddressTypeSpec() == abi.AddressTypeSpec() + + for otherType in ( + abi.ByteTypeSpec, + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 31), + abi.DynamicArrayTypeSpec(abi.ByteTypeSpec()), + ): + assert abi.AddressTypeSpec() != otherType + +def test_Address_encode(): + value = abi.Address() + expr = value.encode() + assert expr.type_of() == TealType.bytes + assert not expr.has_return() + + expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + actual, _ = expr.__teal__(options) + assert actual == expected + +def test_Address_decode(): + value = abi.Address() + for value_to_set in [urandom(32) for x in range(10)]: + expr = value.decode(Bytes(value_to_set)) + + assert expr.type_of() == TealType.none + assert not expr.has_return() + + expected = TealSimpleBlock( + [ + TealOp(None, Op.byte, f"0x{value_to_set.hex()}"), + TealOp(None, Op.store, value.stored_value.slot) + ] + ) + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = TealBlock.NormalizeBlocks(actual) + + with TealComponent.Context.ignoreExprEquality(): + assert actual == expected + +#def test_Address_set_static(): +# value = abi.Address() +# for value_to_set in [urandom(32) for x in range(10)]: +# print(value_to_set) +# expr = value.set(Bytes(value_to_set)) +# +# assert expr.type_of() == TealType.none +# assert not expr.has_return() + +def test_Address_get(): + value = abi.Address() + expr = value.get() + assert expr.type_of() == TealType.bytes + assert not expr.has_return() + + expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + actual, _ = expr.__teal__(options) + assert actual == expected \ No newline at end of file diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 043ae12f0..8a146df16 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,3 +1,4 @@ +from typing import Union from .array_static import StaticArray, StaticArrayTypeSpec from .array_dynamic import DynamicArray, DynamicArrayTypeSpec from .uint import ByteTypeSpec @@ -61,5 +62,21 @@ def type_spec(self) -> StringTypeSpec: def get(self) -> Expr: return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) + def __getslice__(self, low: Union[int, Int], high: Union[int, Int]): + if type(low) is int: + low = Int(low) + + if type(high) is int: + high = Int(high) + + if not isinstance(low, Int): + raise TypeError("low expected int or Int, got {low}") + if not isinstance(high, Int): + raise TypeError("high expected int or Int, got {high}") + + + return substringForDecoding(self.stored_value.load(), startIndex=Int(2)+low, endIndex=Int(2)+high) + + String.__module__ = "pyteal" diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py new file mode 100644 index 000000000..12d584b19 --- /dev/null +++ b/pyteal/ast/abi/string_test.py @@ -0,0 +1,33 @@ +from .type_test import ContainerType +from os import urandom + +from ... import * + +import pytest + +options = CompileOptions(version=5) + + +def test_String_encode(): + value = abi.String() + + tspec = value.type_spec() + assert tspec.is_dynamic() + assert str(tspec) == "string" + + expr = value.encode() + assert expr.type_of() == TealType.bytes + assert not expr.has_return() + + + expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + actual, _ = expr.__teal__(options) + assert actual == expected + assert expr == value.get() + + +def test_String_decode(): + pass + +def test_String_get(): + pass diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 89876e751..4ebee5301 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -107,7 +107,8 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: Tuple4, Tuple5, ) - from .string import String, Address, StringTypeSpec, AddressTypeSpec + from .string import StringTypeSpec, String + from .address import AddressTypeSpec, Address origin = get_origin(annotation) if origin is None: From 12a36a687e8622753cc06ed3d2a52a4f1ebd4542 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 15 Apr 2022 08:45:11 -0400 Subject: [PATCH 04/26] adding tests for string --- pyteal/ast/abi/address_test.py | 36 +++++++++------ pyteal/ast/abi/string.py | 36 ++------------- pyteal/ast/abi/string_test.py | 84 ++++++++++++++++++++++++++++------ pyteal/ast/abi/util.py | 2 +- 4 files changed, 97 insertions(+), 61 deletions(-) diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 8f4e2ff29..806050fa7 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -1,22 +1,26 @@ from .type_test import ContainerType -from os import urandom from ... import * options = CompileOptions(version=5) + def test_AddressTypeSpec_str(): assert str(abi.AddressTypeSpec()) == "address" - + + def test_AddressTypeSpec_is_dynamic(): assert not (abi.AddressTypeSpec()).is_dynamic() -def test_AddressTypeSpec_is_dynamic(): + +def test_AddressTypeSpec_byte_length_static(): assert (abi.AddressTypeSpec()).byte_length_static() == 32 + def test_AddressTypeSpec_new_instance(): assert isinstance(abi.AddressTypeSpec().new_instance(), abi.Address) + def test_AddressTypeSpec_eq(): assert abi.AddressTypeSpec() == abi.AddressTypeSpec() @@ -27,6 +31,7 @@ def test_AddressTypeSpec_eq(): ): assert abi.AddressTypeSpec() != otherType + def test_Address_encode(): value = abi.Address() expr = value.encode() @@ -37,7 +42,10 @@ def test_Address_encode(): actual, _ = expr.__teal__(options) assert actual == expected + def test_Address_decode(): + from os import urandom + value = abi.Address() for value_to_set in [urandom(32) for x in range(10)]: expr = value.decode(Bytes(value_to_set)) @@ -48,7 +56,7 @@ def test_Address_decode(): expected = TealSimpleBlock( [ TealOp(None, Op.byte, f"0x{value_to_set.hex()}"), - TealOp(None, Op.store, value.stored_value.slot) + TealOp(None, Op.store, value.stored_value.slot), ] ) actual, _ = expr.__teal__(options) @@ -58,14 +66,6 @@ def test_Address_decode(): with TealComponent.Context.ignoreExprEquality(): assert actual == expected -#def test_Address_set_static(): -# value = abi.Address() -# for value_to_set in [urandom(32) for x in range(10)]: -# print(value_to_set) -# expr = value.set(Bytes(value_to_set)) -# -# assert expr.type_of() == TealType.none -# assert not expr.has_return() def test_Address_get(): value = abi.Address() @@ -75,4 +75,14 @@ def test_Address_get(): expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) actual, _ = expr.__teal__(options) - assert actual == expected \ No newline at end of file + assert actual == expected + + +# def test_Address_set_static(): +# value = abi.Address() +# for value_to_set in [urandom(32) for x in range(10)]: +# print(value_to_set) +# expr = value.set(Bytes(value_to_set)) +# +# assert expr.type_of() == TealType.none +# assert not expr.has_return() diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 8a146df16..4d9883bef 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -7,36 +7,6 @@ from ..int import Int from ..expr import Expr -address_length = 32 - - -class AddressTypeSpec(StaticArrayTypeSpec): - def __init__(self) -> None: - super().__init__(ByteTypeSpec(), address_length) - - def new_instance(self) -> "Address": - return Address() - - def __str__(self) -> str: - return "address" - - -AddressTypeSpec.__module__ = "pyteal" - - -class Address(StaticArray): - def __init__(self) -> None: - super().__init__(AddressTypeSpec(), address_length) - - def type_spec(self) -> AddressTypeSpec: - return AddressTypeSpec() - - def get(self) -> Expr: - return self.stored_value.load() - - -Address.__module__ = "pyteal" - class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: @@ -74,9 +44,9 @@ def __getslice__(self, low: Union[int, Int], high: Union[int, Int]): if not isinstance(high, Int): raise TypeError("high expected int or Int, got {high}") - - return substringForDecoding(self.stored_value.load(), startIndex=Int(2)+low, endIndex=Int(2)+high) - + return substringForDecoding( + self.stored_value.load(), startIndex=Int(2) + low, endIndex=Int(2) + high + ) String.__module__ = "pyteal" diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index 12d584b19..c59f40b4d 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -1,33 +1,89 @@ -from .type_test import ContainerType -from os import urandom - from ... import * -import pytest - options = CompileOptions(version=5) -def test_String_encode(): - value = abi.String() +def test_StringTypeSpec_str(): + assert str(abi.StringTypeSpec()) == "string" + + +def test_StringTypeSpec_is_dynamic(): + assert (abi.StringTypeSpec()).is_dynamic() + - tspec = value.type_spec() - assert tspec.is_dynamic() - assert str(tspec) == "string" +def test_StringTypeSpec_new_instance(): + assert isinstance(abi.StringTypeSpec().new_instance(), abi.String) + +def test_StringTypeSpec_eq(): + assert abi.StringTypeSpec() == abi.StringTypeSpec() + + for otherType in ( + abi.ByteTypeSpec, + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 1), + abi.DynamicArrayTypeSpec(abi.Uint8TypeSpec()), + ): + assert abi.StringTypeSpec() != otherType + + +def test_String_encode(): + value = abi.String() expr = value.encode() assert expr.type_of() == TealType.bytes assert not expr.has_return() - expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) actual, _ = expr.__teal__(options) assert actual == expected - assert expr == value.get() def test_String_decode(): - pass + import random + from os import urandom + + value = abi.String() + for value_to_set in [urandom(random.randint(0, 50)) for x in range(10)]: + expr = value.decode(Bytes(value_to_set)) + + assert expr.type_of() == TealType.none + assert not expr.has_return() + + expected = TealSimpleBlock( + [ + TealOp(None, Op.byte, f"0x{value_to_set.hex()}"), + TealOp(None, Op.store, value.stored_value.slot), + ] + ) + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = TealBlock.NormalizeBlocks(actual) + + with TealComponent.Context.ignoreExprEquality(): + assert actual == expected + def test_String_get(): - pass + value = abi.String() + expr = value.get() + assert expr.type_of() == TealType.bytes + assert not expr.has_return() + + expected = TealSimpleBlock( + [TealOp(expr, Op.load, value.stored_value.slot), TealOp(None, Op.extract, 2, 0)] + ) + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = TealBlock.NormalizeBlocks(actual) + + with TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +# def test_String_set_static(): +# value = abi.String() +# for value_to_set in [urandom(32) for x in range(10)]: +# print(value_to_set) +# expr = value.set(Bytes(value_to_set)) +# +# assert expr.type_of() == TealType.none +# assert not expr.has_return() diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 4ebee5301..f16a03900 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -108,7 +108,7 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: Tuple5, ) from .string import StringTypeSpec, String - from .address import AddressTypeSpec, Address + from .address import AddressTypeSpec, Address origin = get_origin(annotation) if origin is None: From 8a54f14073804d4e6cd614950af5a11a630f5340 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 15 Apr 2022 11:32:10 -0400 Subject: [PATCH 05/26] Adding slicing for a string --- pyteal/ast/abi/string.py | 101 ++++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 4d9883bef..f9bc1f40a 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,11 +1,20 @@ from typing import Union -from .array_static import StaticArray, StaticArrayTypeSpec + +from .type import ComputedValue, TypeSpec +from pyteal.types import require_type from .array_dynamic import DynamicArray, DynamicArrayTypeSpec from .uint import ByteTypeSpec from .util import substringForDecoding +from ..bytes import Bytes +from ..unaryexpr import Itob +from ..substring import Suffix +from ...types import TealType, require_type +from ...errors import TealInputError from ..int import Int from ..expr import Expr +from ..seq import Seq +from ..naryexpr import Concat class StringTypeSpec(DynamicArrayTypeSpec): @@ -32,21 +41,87 @@ def type_spec(self) -> StringTypeSpec: def get(self) -> Expr: return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) - def __getslice__(self, low: Union[int, Int], high: Union[int, Int]): - if type(low) is int: - low = Int(low) + def set(self, value: Union[str, "String", ComputedValue["String"], Expr]): + if isinstance(value, ComputedValue): + return value.store_into(self) - if type(high) is int: - high = Int(high) + if isinstance(value, String): + return self.decode(value.encode()) - if not isinstance(low, Int): - raise TypeError("low expected int or Int, got {low}") - if not isinstance(high, Int): - raise TypeError("high expected int or Int, got {high}") + if type(value) is str: + # Assume not prefixed with length + value = Concat( + Suffix(Itob(Int(len(value))), Int(6)), + Bytes(value) + ) - return substringForDecoding( - self.stored_value.load(), startIndex=Int(2) + low, endIndex=Int(2) + high - ) + if not isinstance(value, Expr): + raise TealInputError("Expected Expr, got {}".format(value)) + + # TODO: should we prefix this with length? + return self.stored_value.store(value) + + def __getitem__(self, index: Union[int, slice, Expr]) -> "ComputedValue[String]": + + if type(index) is int or isinstance(index, Expr): + return super().__getitem__(index) + + if type(index) is not slice: + raise TealInputError("Index expected slice, got {index}") + + if index.step is not None: + raise TealInputError("Slice step is not supported") + + start = index.start + stop = index.stop + + if stop is not None and type(stop) is int and stop <= 0: + raise TealInputError("Negative slice indicies are not supported") + + if start is not None and type(start) is int and start <= 0: + raise TealInputError("Negative slice indicies are not supported") + + if stop is None: + stop = self.length() + + if start is None: + start = Int(0) + + if type(stop) is int: + stop = Int(stop) + + if type(start) is int: + start = Int(start) + + return SubstringValue(self, start, stop) String.__module__ = "pyteal" + + +class SubstringValue(ComputedValue): + def __init__(self, string: String, start: Expr, stop: Expr) -> None: + super().__init__() + + require_type(start, TealType.uint64) + require_type(stop, TealType.uint64) + + self.string = string + self.start = start + self.stop = stop + + def produced_type_spec(cls) -> TypeSpec: + return StringTypeSpec() + + def store_into(self, output: String) -> Expr: + return output.decode( + Concat( + Suffix(Itob(self.stop - self.start), Int(6)), + substringForDecoding( + self.string.get(), startIndex=self.start, endIndex=self.stop + ), + ) + ) + + +SubstringValue.__module__ = "pyteal" From af7967d530238c4c0fe2b4ceb9a4a38353a0f3a6 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 15 Apr 2022 12:14:59 -0400 Subject: [PATCH 06/26] removed unused tests --- pyteal/ast/abi/address_test.py | 10 ---------- pyteal/ast/abi/string.py | 21 +++------------------ pyteal/ast/abi/string_test.py | 10 ---------- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 806050fa7..345bb2b2e 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -76,13 +76,3 @@ def test_Address_get(): expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) actual, _ = expr.__teal__(options) assert actual == expected - - -# def test_Address_set_static(): -# value = abi.Address() -# for value_to_set in [urandom(32) for x in range(10)]: -# print(value_to_set) -# expr = value.set(Bytes(value_to_set)) -# -# assert expr.type_of() == TealType.none -# assert not expr.has_return() diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 4d9883bef..6560bed22 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,7 +1,5 @@ -from typing import Union -from .array_static import StaticArray, StaticArrayTypeSpec from .array_dynamic import DynamicArray, DynamicArrayTypeSpec -from .uint import ByteTypeSpec +from .uint import ByteTypeSpec, Uint16TypeSpec from .util import substringForDecoding from ..int import Int @@ -30,22 +28,9 @@ def type_spec(self) -> StringTypeSpec: return StringTypeSpec() def get(self) -> Expr: - return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) - - def __getslice__(self, low: Union[int, Int], high: Union[int, Int]): - if type(low) is int: - low = Int(low) - - if type(high) is int: - high = Int(high) - - if not isinstance(low, Int): - raise TypeError("low expected int or Int, got {low}") - if not isinstance(high, Int): - raise TypeError("high expected int or Int, got {high}") - return substringForDecoding( - self.stored_value.load(), startIndex=Int(2) + low, endIndex=Int(2) + high + self.stored_value.load(), + startIndex=Int(Uint16TypeSpec().byte_length_static()), ) diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index c59f40b4d..45da165fc 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -77,13 +77,3 @@ def test_String_get(): with TealComponent.Context.ignoreExprEquality(): assert actual == expected - - -# def test_String_set_static(): -# value = abi.String() -# for value_to_set in [urandom(32) for x in range(10)]: -# print(value_to_set) -# expr = value.set(Bytes(value_to_set)) -# -# assert expr.type_of() == TealType.none -# assert not expr.has_return() From 41b10c34d152ae234b0d80f7a2c79333890ed773 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 15 Apr 2022 12:48:51 -0400 Subject: [PATCH 07/26] merged up --- pyteal/ast/abi/string.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 44d7e7882..d31bb0226 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,10 +1,7 @@ -<<<<<<< HEAD from typing import Union from .type import ComputedValue, TypeSpec from pyteal.types import require_type -======= ->>>>>>> strings from .array_dynamic import DynamicArray, DynamicArrayTypeSpec from .uint import ByteTypeSpec, Uint16TypeSpec from .util import substringForDecoding @@ -42,7 +39,6 @@ def type_spec(self) -> StringTypeSpec: return StringTypeSpec() def get(self) -> Expr: -<<<<<<< HEAD return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) def set(self, value: Union[str, "String", ComputedValue["String"], Expr]): @@ -98,12 +94,6 @@ def __getitem__(self, index: Union[int, slice, Expr]) -> "ComputedValue[String]" start = Int(start) return SubstringValue(self, start, stop) -======= - return substringForDecoding( - self.stored_value.load(), - startIndex=Int(Uint16TypeSpec().byte_length_static()), - ) ->>>>>>> strings String.__module__ = "pyteal" From 3e7fdfdcad5cf79097ce687873d121a4810fda8e Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 15 Apr 2022 12:51:46 -0400 Subject: [PATCH 08/26] cr --- pyteal/ast/abi/__init__.py | 3 ++- pyteal/ast/abi/address.py | 6 +++--- pyteal/ast/abi/address_test.py | 6 ++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index aa670ce4f..0dfefb026 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,5 +1,5 @@ from .string import String, StringTypeSpec -from .address import AddressTypeSpec, Address +from .address import AddressTypeSpec, Address, ADDRESS_LENGTH from .type import TypeSpec, BaseType, ComputedValue from .bool import BoolTypeSpec, Bool from .uint import ( @@ -38,6 +38,7 @@ "StringTypeSpec", "Address", "AddressTypeSpec", + "ADDRESS_LENGTH", "TypeSpec", "BaseType", "ComputedValue", diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 83bf9682e..0a95ab6f7 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -2,12 +2,12 @@ from .uint import ByteTypeSpec from ..expr import Expr -address_length = 32 +ADDRESS_LENGTH = 32 class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec(), address_length) + super().__init__(ByteTypeSpec(), ADDRESS_LENGTH) def new_instance(self) -> "Address": return Address() @@ -21,7 +21,7 @@ def __str__(self) -> str: class Address(StaticArray): def __init__(self) -> None: - super().__init__(AddressTypeSpec(), address_length) + super().__init__(AddressTypeSpec(), ADDRESS_LENGTH) def type_spec(self) -> AddressTypeSpec: return AddressTypeSpec() diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 345bb2b2e..e7267a7e8 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -1,5 +1,3 @@ -from .type_test import ContainerType - from ... import * options = CompileOptions(version=5) @@ -14,7 +12,7 @@ def test_AddressTypeSpec_is_dynamic(): def test_AddressTypeSpec_byte_length_static(): - assert (abi.AddressTypeSpec()).byte_length_static() == 32 + assert (abi.AddressTypeSpec()).byte_length_static() == abi.ADDRESS_LENGTH def test_AddressTypeSpec_new_instance(): @@ -47,7 +45,7 @@ def test_Address_decode(): from os import urandom value = abi.Address() - for value_to_set in [urandom(32) for x in range(10)]: + for value_to_set in [urandom(abi.ADDRESS_LENGTH) for x in range(10)]: expr = value.decode(Bytes(value_to_set)) assert expr.type_of() == TealType.none From bc958065f4dea1d33e754d8f82af825f3e7c3ade Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Sat, 16 Apr 2022 07:34:18 -0400 Subject: [PATCH 09/26] add util function to prefix strlen --- pyteal/ast/abi/string.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index d31bb0226..7756ae5bf 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -7,7 +7,7 @@ from .util import substringForDecoding from ..bytes import Bytes -from ..unaryexpr import Itob +from ..unaryexpr import Itob, Len from ..substring import Suffix from ...types import TealType, require_type from ...errors import TealInputError @@ -16,6 +16,8 @@ from ..seq import Seq from ..naryexpr import Concat +def encoded_string(s: Expr): + return Concat(Suffix(Itob(Len(s)), Int(6)), s) class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: @@ -42,24 +44,22 @@ def get(self) -> Expr: return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) def set(self, value: Union[str, "String", ComputedValue["String"], Expr]): + + # Assume length prefixed if isinstance(value, ComputedValue): return value.store_into(self) if isinstance(value, String): return self.decode(value.encode()) + # Assume not length prefixed if type(value) is str: - # Assume not prefixed with length - value = Concat( - Suffix(Itob(Int(len(value))), Int(6)), - Bytes(value) - ) + return self.stored_value.store(encoded_string(Bytes(value)) ) if not isinstance(value, Expr): raise TealInputError("Expected Expr, got {}".format(value)) - # TODO: should we prefix this with length? - return self.stored_value.store(value) + return self.stored_value.store(encoded_string(value)) def __getitem__(self, index: Union[int, slice, Expr]) -> "ComputedValue[String]": From 9c3b5a36ba20a43c2d51b21b79a288b14cd55b69 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Sun, 17 Apr 2022 10:14:41 -0400 Subject: [PATCH 10/26] fix set on address --- pyteal/ast/abi/address.py | 22 ++++++++++++++++++++++ pyteal/ast/abi/string.py | 8 +++++--- pyteal/ast/abi/tuple.py | 2 +- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 0a95ab6f7..94556491c 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -1,5 +1,12 @@ +from typing import Union + +from pyteal.errors import TealInputError + +from .type import ComputedValue from .array_static import StaticArray, StaticArrayTypeSpec from .uint import ByteTypeSpec + +from ..bytes import Bytes from ..expr import Expr ADDRESS_LENGTH = 32 @@ -29,5 +36,20 @@ def type_spec(self) -> AddressTypeSpec: def get(self) -> Expr: return self.stored_value.load() + def set(self, value: Union[str, "Address", ComputedValue["Address"], Expr]) -> Expr: + if isinstance(value, ComputedValue): + return value.store_into(self) + + if isinstance(value, Address): + return self.decode(self.encode()) + + if isinstance(value, str): + return self.stored_value.store(Bytes(str)) + + if isinstance(value, Expr): + return self.stored_value.store(value) + + raise TealInputError(f"Expected str, Address or Expr got {value}") + Address.__module__ = "pyteal" diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 7756ae5bf..37d828df3 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -3,7 +3,7 @@ from .type import ComputedValue, TypeSpec from pyteal.types import require_type from .array_dynamic import DynamicArray, DynamicArrayTypeSpec -from .uint import ByteTypeSpec, Uint16TypeSpec +from .uint import ByteTypeSpec from .util import substringForDecoding from ..bytes import Bytes @@ -16,9 +16,11 @@ from ..seq import Seq from ..naryexpr import Concat + def encoded_string(s: Expr): return Concat(Suffix(Itob(Len(s)), Int(6)), s) + class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: super().__init__(ByteTypeSpec()) @@ -43,7 +45,7 @@ def type_spec(self) -> StringTypeSpec: def get(self) -> Expr: return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) - def set(self, value: Union[str, "String", ComputedValue["String"], Expr]): + def set(self, value: Union[str, "String", ComputedValue["String"], Expr]) -> Expr: # Assume length prefixed if isinstance(value, ComputedValue): @@ -54,7 +56,7 @@ def set(self, value: Union[str, "String", ComputedValue["String"], Expr]): # Assume not length prefixed if type(value) is str: - return self.stored_value.store(encoded_string(Bytes(value)) ) + return self.stored_value.store(encoded_string(Bytes(value))) if not isinstance(value, Expr): raise TealInputError("Expected Expr, got {}".format(value)) diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index 08b81aabb..7ffa3a53e 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -303,7 +303,7 @@ def length(self) -> Expr: def __getitem__(self, index: int) -> "TupleElement": if not (0 <= index < self.type_spec().length_static()): - raise TealInputError("Index out of bounds") + raise TealInputError(f"Index out of bounds: {index}") return TupleElement(self, index) From 9a3301846254abf77945a2402bd1117a66a8b953 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 21 Apr 2022 15:16:56 -0400 Subject: [PATCH 11/26] cr from original strings pr --- pyteal/ast/abi/address.py | 3 ++ pyteal/ast/abi/address_test.py | 84 +++++++++++++++++++----------- pyteal/ast/abi/string.py | 8 ++- pyteal/ast/abi/string_test.py | 93 ++++++++++++++++++++++------------ 4 files changed, 125 insertions(+), 63 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 975ed4001..4f1a0006e 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -24,6 +24,9 @@ def new_instance(self) -> "Address": def __str__(self) -> str: return "address" + def __eq__(self, other: object) -> bool: + return isinstance(other, AddressTypeSpec) + AddressTypeSpec.__module__ = "pyteal" diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 1227df77b..47961f35c 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -1,6 +1,10 @@ -from ... import * +import pytest -options = CompileOptions(version=5) +import pyteal as pt +from pyteal import abi +from .util import substringForDecoding + +options = pt.CompileOptions(version=5) def test_AddressTypeSpec_str(): @@ -23,8 +27,8 @@ def test_AddressTypeSpec_eq(): assert abi.AddressTypeSpec() == abi.AddressTypeSpec() for otherType in ( - abi.ByteTypeSpec, - abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 31), + abi.ByteTypeSpec(), + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 32), abi.DynamicArrayTypeSpec(abi.ByteTypeSpec()), ): assert abi.AddressTypeSpec() != otherType @@ -33,44 +37,66 @@ def test_AddressTypeSpec_eq(): def test_Address_encode(): value = abi.Address() expr = value.encode() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert expr.has_return() is False - expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) actual, _ = expr.__teal__(options) assert actual == expected def test_Address_decode(): - from os import urandom - - value = abi.Address() - for value_to_set in [urandom(abi.ADDRESS_LENGTH) for x in range(10)]: - expr = value.decode(Bytes(value_to_set)) - - assert expr.type_of() == TealType.none - assert expr.has_return() is False - - expected = TealSimpleBlock( - [ - TealOp(None, Op.byte, f"0x{value_to_set.hex()}"), - TealOp(None, Op.store, value.stored_value.slot), - ] - ) - actual, _ = expr.__teal__(options) - actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) - - with TealComponent.Context.ignoreExprEquality(): - assert actual == expected + address = bytes([0] * abi.ADDRESS_LENGTH) + encoded = pt.Bytes(address) + + for startIndex in (None, pt.Int(0)): + for endIndex in (None, pt.Int(1)): + for length in (None, pt.Int(2)): + value = abi.Address() + + if endIndex is not None and length is not None: + with pytest.raises(pt.TealInputError): + value.decode( + encoded, + startIndex=startIndex, + endIndex=endIndex, + length=length, + ) + continue + + expr = value.decode( + encoded, startIndex=startIndex, endIndex=endIndex, length=length + ) + assert expr.type_of() == pt.TealType.none + assert expr.has_return() is False + + expectedExpr = value.stored_value.store( + substringForDecoding( + encoded, startIndex=startIndex, endIndex=endIndex, length=length + ) + ) + expected, _ = expectedExpr.__teal__(options) + expected.addIncoming() + expected = pt.TealBlock.NormalizeBlocks(expected) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected def test_Address_get(): value = abi.Address() expr = value.get() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert expr.has_return() is False - expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) actual, _ = expr.__teal__(options) assert actual == expected diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index ca87d06b7..7bd9a7daf 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -25,6 +25,7 @@ def encoded_string(s: Expr): from .uint import ByteTypeSpec, Uint16TypeSpec from .util import substringForDecoding +from ..substring import Suffix from ..int import Int from ..expr import Expr @@ -39,6 +40,9 @@ def new_instance(self) -> "String": def __str__(self) -> str: return "string" + def __eq__(self, other: object) -> bool: + return isinstance(other, StringTypeSpec) + StringTypeSpec.__module__ = "pyteal" @@ -51,7 +55,9 @@ def type_spec(self) -> StringTypeSpec: return StringTypeSpec() def get(self) -> Expr: - return substringForDecoding(self.stored_value.load(), startIndex=Int(2)) + return Suffix( + self.stored_value.load(), Int(Uint16TypeSpec().byte_length_static()) + ) def set(self, value: Union[str, "String", ComputedValue["String"], Expr]) -> Expr: diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index 856f48ddf..9a4bdb76e 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -1,6 +1,11 @@ -from ... import * +import pytest -options = CompileOptions(version=5) +import pyteal as pt +from pyteal import abi +from .util import substringForDecoding + + +options = pt.CompileOptions(version=5) def test_StringTypeSpec_str(): @@ -19,9 +24,10 @@ def test_StringTypeSpec_eq(): assert abi.StringTypeSpec() == abi.StringTypeSpec() for otherType in ( - abi.ByteTypeSpec, + abi.ByteTypeSpec(), abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 1), abi.DynamicArrayTypeSpec(abi.Uint8TypeSpec()), + abi.DynamicArrayTypeSpec(abi.ByteTypeSpec()), ): assert abi.StringTypeSpec() != otherType @@ -29,51 +35,72 @@ def test_StringTypeSpec_eq(): def test_String_encode(): value = abi.String() expr = value.encode() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert expr.has_return() is False - expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)]) + expected = pt.TealSimpleBlock( + [pt.TealOp(expr, pt.Op.load, value.stored_value.slot)] + ) actual, _ = expr.__teal__(options) assert actual == expected -def test_String_decode(): - import random - from os import urandom - - value = abi.String() - for value_to_set in [urandom(random.randint(0, 50)) for x in range(10)]: - expr = value.decode(Bytes(value_to_set)) - - assert expr.type_of() == TealType.none - assert expr.has_return() is False - - expected = TealSimpleBlock( - [ - TealOp(None, Op.byte, f"0x{value_to_set.hex()}"), - TealOp(None, Op.store, value.stored_value.slot), - ] - ) - actual, _ = expr.__teal__(options) - actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) - - with TealComponent.Context.ignoreExprEquality(): - assert actual == expected +def test_DynamicArray_decode(): + encoded = pt.Bytes("encoded") + stringType = abi.StringTypeSpec() + for startIndex in (None, pt.Int(1)): + for endIndex in (None, pt.Int(2)): + for length in (None, pt.Int(3)): + value = stringType.new_instance() + + if endIndex is not None and length is not None: + with pytest.raises(pt.TealInputError): + value.decode( + encoded, + startIndex=startIndex, + endIndex=endIndex, + length=length, + ) + continue + + expr = value.decode( + encoded, startIndex=startIndex, endIndex=endIndex, length=length + ) + assert expr.type_of() == pt.TealType.none + assert expr.has_return() is False + + expectedExpr = value.stored_value.store( + substringForDecoding( + encoded, startIndex=startIndex, endIndex=endIndex, length=length + ) + ) + expected, _ = expectedExpr.__teal__(options) + expected.addIncoming() + expected = pt.TealBlock.NormalizeBlocks(expected) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected def test_String_get(): value = abi.String() expr = value.get() - assert expr.type_of() == TealType.bytes + assert expr.type_of() == pt.TealType.bytes assert expr.has_return() is False - expected = TealSimpleBlock( - [TealOp(expr, Op.load, value.stored_value.slot), TealOp(None, Op.extract, 2, 0)] + expected = pt.TealSimpleBlock( + [ + pt.TealOp(expr, pt.Op.load, value.stored_value.slot), + pt.TealOp(None, pt.Op.extract, 2, 0), + ] ) actual, _ = expr.__teal__(options) actual.addIncoming() - actual = TealBlock.NormalizeBlocks(actual) + actual = pt.TealBlock.NormalizeBlocks(actual) - with TealComponent.Context.ignoreExprEquality(): + with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected From 7964aa49899a8e992ce6a4307d908553375822f1 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 21 Apr 2022 15:59:33 -0400 Subject: [PATCH 12/26] maybe fix linter stuff --- pyteal/ast/abi/address.py | 3 --- pyteal/ast/abi/string.py | 13 +------------ 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 4f1a0006e..dd2e7f74f 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -5,10 +5,7 @@ from .type import ComputedValue from .array_static import StaticArray, StaticArrayTypeSpec from .uint import ByteTypeSpec - from ..bytes import Bytes -from .array_static import StaticArray, StaticArrayTypeSpec -from .uint import ByteTypeSpec from ..expr import Expr ADDRESS_LENGTH = 32 diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 7bd9a7daf..9b6b9cdd7 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,9 +1,8 @@ from typing import Union from .type import ComputedValue, TypeSpec -from pyteal.types import require_type from .array_dynamic import DynamicArray, DynamicArrayTypeSpec -from .uint import ByteTypeSpec +from .uint import ByteTypeSpec, Uint16TypeSpec from .util import substringForDecoding from ..bytes import Bytes @@ -13,7 +12,6 @@ from ...errors import TealInputError from ..int import Int from ..expr import Expr -from ..seq import Seq from ..naryexpr import Concat @@ -21,15 +19,6 @@ def encoded_string(s: Expr): return Concat(Suffix(Itob(Len(s)), Int(6)), s) -from .array_dynamic import DynamicArray, DynamicArrayTypeSpec -from .uint import ByteTypeSpec, Uint16TypeSpec -from .util import substringForDecoding - -from ..substring import Suffix -from ..int import Int -from ..expr import Expr - - class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: super().__init__(ByteTypeSpec()) From 86199efec585436720155d7b735078b5273b8f60 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 22 Apr 2022 08:15:51 -0400 Subject: [PATCH 13/26] pass type spec to init for tuple, static array, and dynamic array --- pyteal/ast/abi/address.py | 2 +- pyteal/ast/abi/array_base_test.py | 4 ++-- pyteal/ast/abi/array_dynamic.py | 6 +++--- pyteal/ast/abi/array_dynamic_test.py | 2 +- pyteal/ast/abi/array_static.py | 8 ++++--- pyteal/ast/abi/array_static_test.py | 22 ++++++++++--------- pyteal/ast/abi/method_return_test.py | 2 +- pyteal/ast/abi/string.py | 3 ++- pyteal/ast/abi/tuple.py | 32 +++++++++++++++++----------- pyteal/ast/abi/tuple_test.py | 26 ++++++++++++---------- pyteal/ast/subroutine_test.py | 12 ++++++++--- 11 files changed, 70 insertions(+), 49 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index dd2e7f74f..d364545fd 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -30,7 +30,7 @@ def __eq__(self, other: object) -> bool: class Address(StaticArray): def __init__(self) -> None: - super().__init__(AddressTypeSpec(), ADDRESS_LENGTH) + super().__init__(AddressTypeSpec()) def type_spec(self) -> AddressTypeSpec: return AddressTypeSpec() diff --git a/pyteal/ast/abi/array_base_test.py b/pyteal/ast/abi/array_base_test.py index 0c480183a..7b515cbc6 100644 --- a/pyteal/ast/abi/array_base_test.py +++ b/pyteal/ast/abi/array_base_test.py @@ -107,7 +107,7 @@ def test_ArrayElement_store_into(): assert actual == expected with pytest.raises(pt.TealInputError): - element.store_into(abi.Tuple(elementType)) + element.store_into(abi.Tuple(abi.TupleTypeSpec(elementType))) for elementType in STATIC_TYPES + DYNAMIC_TYPES: dynamicArrayType = abi.DynamicArrayTypeSpec(elementType) @@ -158,4 +158,4 @@ def test_ArrayElement_store_into(): ) with pytest.raises(pt.TealInputError): - element.store_into(abi.Tuple(elementType)) + element.store_into(abi.Tuple(abi.TupleTypeSpec(elementType))) diff --git a/pyteal/ast/abi/array_dynamic.py b/pyteal/ast/abi/array_dynamic.py index bc0526a86..a35285a1b 100644 --- a/pyteal/ast/abi/array_dynamic.py +++ b/pyteal/ast/abi/array_dynamic.py @@ -20,7 +20,7 @@ class DynamicArrayTypeSpec(ArrayTypeSpec[T]): def new_instance(self) -> "DynamicArray[T]": - return DynamicArray(self.value_type_spec()) + return DynamicArray(DynamicArrayTypeSpec(self.value_type_spec())) def is_length_dynamic(self) -> bool: return True @@ -47,8 +47,8 @@ def __str__(self) -> str: class DynamicArray(Array[T]): """The class that represents ABI dynamic array type.""" - def __init__(self, value_type_spec: TypeSpec) -> None: - super().__init__(DynamicArrayTypeSpec(value_type_spec)) + def __init__(self, array_type_spec: DynamicArrayTypeSpec) -> None: + super().__init__(array_type_spec) def type_spec(self) -> DynamicArrayTypeSpec[T]: return cast(DynamicArrayTypeSpec[T], super().type_spec()) diff --git a/pyteal/ast/abi/array_dynamic_test.py b/pyteal/ast/abi/array_dynamic_test.py index f5034b598..c0ee66560 100644 --- a/pyteal/ast/abi/array_dynamic_test.py +++ b/pyteal/ast/abi/array_dynamic_test.py @@ -170,7 +170,7 @@ def test_DynamicArray_set_copy(): def test_DynamicArray_set_computed(): - value = abi.DynamicArray(abi.ByteTypeSpec()) + value = abi.DynamicArray(abi.DynamicArrayTypeSpec(abi.ByteTypeSpec())) computed = ContainerType( value.type_spec(), pt.Bytes("this should be a dynamic array") ) diff --git a/pyteal/ast/abi/array_static.py b/pyteal/ast/abi/array_static.py index 3632933a9..be6d73e0a 100644 --- a/pyteal/ast/abi/array_static.py +++ b/pyteal/ast/abi/array_static.py @@ -28,7 +28,9 @@ def __init__(self, value_type_spec: TypeSpec, array_length: int) -> None: self.array_length: Final = array_length def new_instance(self) -> "StaticArray[T, N]": - return StaticArray(self.value_type_spec(), self.length_static()) + return StaticArray( + StaticArrayTypeSpec(self.value_type_spec(), self.length_static()) + ) def length_static(self) -> int: """Get the size of this static array type. @@ -72,8 +74,8 @@ def __str__(self) -> str: class StaticArray(Array[T], Generic[T, N]): """The class that represents ABI static array type.""" - def __init__(self, value_type_spec: TypeSpec, array_length: int) -> None: - super().__init__(StaticArrayTypeSpec(value_type_spec, array_length)) + def __init__(self, array_type_spec: StaticArrayTypeSpec) -> None: + super().__init__(array_type_spec) def type_spec(self) -> StaticArrayTypeSpec[T, N]: return cast(StaticArrayTypeSpec[T, N], super().type_spec()) diff --git a/pyteal/ast/abi/array_static_test.py b/pyteal/ast/abi/array_static_test.py index 0e8625b68..dafd79dec 100644 --- a/pyteal/ast/abi/array_static_test.py +++ b/pyteal/ast/abi/array_static_test.py @@ -104,7 +104,9 @@ def test_StaticArray_decode(): for startIndex in (None, pt.Int(1)): for endIndex in (None, pt.Int(2)): for length in (None, pt.Int(3)): - value = abi.StaticArray(abi.Uint64TypeSpec(), 10) + value = abi.StaticArray( + abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), 10) + ) if endIndex is not None and length is not None: with pytest.raises(pt.TealInputError): @@ -140,7 +142,7 @@ def test_StaticArray_decode(): def test_StaticArray_set_values(): - value = abi.StaticArray(abi.Uint64TypeSpec(), 10) + value = abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), 10)) with pytest.raises(pt.TealInputError): value.set([]) @@ -176,14 +178,14 @@ def test_StaticArray_set_values(): def test_StaticArray_set_copy(): - value = abi.StaticArray(abi.Uint64TypeSpec(), 10) - otherArray = abi.StaticArray(abi.Uint64TypeSpec(), 10) + value = abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), 10)) + otherArray = abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), 10)) with pytest.raises(pt.TealInputError): - value.set(abi.StaticArray(abi.Uint64TypeSpec(), 11)) + value.set(abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), 11))) with pytest.raises(pt.TealInputError): - value.set(abi.StaticArray(abi.Uint8TypeSpec(), 10)) + value.set(abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint8TypeSpec(), 10))) with pytest.raises(pt.TealInputError): value.set(abi.Uint64()) @@ -208,7 +210,7 @@ def test_StaticArray_set_copy(): def test_StaticArray_set_computed(): - value = abi.StaticArray(abi.Uint64TypeSpec(), 10) + value = abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), 10)) computed = ContainerType( value.type_spec(), pt.Bytes("indeed this is hard to simulate") ) @@ -239,7 +241,7 @@ def test_StaticArray_set_computed(): def test_StaticArray_encode(): - value = abi.StaticArray(abi.Uint64TypeSpec(), 10) + value = abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), 10)) expr = value.encode() assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() @@ -258,7 +260,7 @@ def test_StaticArray_encode(): def test_StaticArray_length(): for length in (0, 1, 2, 3, 1000): - value = abi.StaticArray(abi.Uint64TypeSpec(), length) + value = abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), length)) expr = value.length() assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() @@ -275,7 +277,7 @@ def test_StaticArray_length(): def test_StaticArray_getitem(): for length in (0, 1, 2, 3, 1000): - value = abi.StaticArray(abi.Uint64TypeSpec(), length) + value = abi.StaticArray(abi.StaticArrayTypeSpec(abi.Uint64TypeSpec(), length)) for index in range(length): # dynamic indexes diff --git a/pyteal/ast/abi/method_return_test.py b/pyteal/ast/abi/method_return_test.py index d834ddf2a..00657b6be 100644 --- a/pyteal/ast/abi/method_return_test.py +++ b/pyteal/ast/abi/method_return_test.py @@ -7,7 +7,7 @@ POSITIVE_CASES = [ abi.Uint16(), abi.Uint32(), - abi.StaticArray(abi.BoolTypeSpec(), 12), + abi.StaticArray(abi.StaticArrayTypeSpec(abi.BoolTypeSpec(), 12)), ] diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 9b6b9cdd7..e56402590 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,6 +1,7 @@ from typing import Union from .type import ComputedValue, TypeSpec +from .array_base import ArrayElement from .array_dynamic import DynamicArray, DynamicArrayTypeSpec from .uint import ByteTypeSpec, Uint16TypeSpec from .util import substringForDecoding @@ -66,7 +67,7 @@ def set(self, value: Union[str, "String", ComputedValue["String"], Expr]) -> Exp return self.stored_value.store(encoded_string(value)) - def __getitem__(self, index: Union[int, slice, Expr]) -> "ComputedValue[String]": + def __getitem__(self, index: Union[int, slice, Expr]) -> "ArrayElement[String]": if type(index) is int or isinstance(index, Expr): return super().__getitem__(index) diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index d6f5a8882..7933eb8c0 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -218,7 +218,7 @@ def length_static(self) -> int: return len(self.value_specs) def new_instance(self) -> "Tuple": - return Tuple(*self.value_specs) + return Tuple(TupleTypeSpec(*self.value_specs)) def is_dynamic(self) -> bool: return any(type_spec.is_dynamic() for type_spec in self.value_type_specs()) @@ -248,8 +248,8 @@ def __str__(self) -> str: class Tuple(BaseType): - def __init__(self, *value_type_specs: TypeSpec) -> None: - super().__init__(TupleTypeSpec(*value_type_specs)) + def __init__(self, tuple_type_spec: TupleTypeSpec) -> None: + super().__init__(tuple_type_spec) def type_spec(self) -> TupleTypeSpec: return cast(TupleTypeSpec, super().type_spec()) @@ -340,7 +340,7 @@ class Tuple0(Tuple): """A Tuple with 0 values.""" def __init__(self) -> None: - super().__init__() + super().__init__(TupleTypeSpec()) Tuple0.__module__ = "pyteal" @@ -352,7 +352,7 @@ class Tuple1(Tuple, Generic[T1]): """A Tuple with 1 value.""" def __init__(self, value1_type_spec: TypeSpec) -> None: - super().__init__(value1_type_spec) + super().__init__(TupleTypeSpec(value1_type_spec)) Tuple1.__module__ = "pyteal" @@ -364,7 +364,7 @@ class Tuple2(Tuple, Generic[T1, T2]): """A Tuple with 2 values.""" def __init__(self, value1_type_spec: TypeSpec, value2_type_spec: TypeSpec) -> None: - super().__init__(value1_type_spec, value2_type_spec) + super().__init__(TupleTypeSpec(value1_type_spec, value2_type_spec)) Tuple2.__module__ = "pyteal" @@ -381,7 +381,9 @@ def __init__( value2_type_spec: TypeSpec, value3_type_spec: TypeSpec, ) -> None: - super().__init__(value1_type_spec, value2_type_spec, value3_type_spec) + super().__init__( + TupleTypeSpec(value1_type_spec, value2_type_spec, value3_type_spec) + ) Tuple3.__module__ = "pyteal" @@ -400,7 +402,9 @@ def __init__( value4_type_spec: TypeSpec, ) -> None: super().__init__( - value1_type_spec, value2_type_spec, value3_type_spec, value4_type_spec + TupleTypeSpec( + value1_type_spec, value2_type_spec, value3_type_spec, value4_type_spec + ) ) @@ -421,11 +425,13 @@ def __init__( value5_type_spec: TypeSpec, ) -> None: super().__init__( - value1_type_spec, - value2_type_spec, - value3_type_spec, - value4_type_spec, - value5_type_spec, + TupleTypeSpec( + value1_type_spec, + value2_type_spec, + value3_type_spec, + value4_type_spec, + value5_type_spec, + ) ) diff --git a/pyteal/ast/abi/tuple_test.py b/pyteal/ast/abi/tuple_test.py index 5c3651e75..555f4ba0a 100644 --- a/pyteal/ast/abi/tuple_test.py +++ b/pyteal/ast/abi/tuple_test.py @@ -23,10 +23,10 @@ class EncodeTest(NamedTuple): uint16_b = abi.Uint16() bool_a = abi.Bool() bool_b = abi.Bool() - tuple_a = abi.Tuple(abi.BoolTypeSpec(), abi.BoolTypeSpec()) - dynamic_array_a = abi.DynamicArray(abi.Uint64TypeSpec()) - dynamic_array_b = abi.DynamicArray(abi.Uint16TypeSpec()) - dynamic_array_c = abi.DynamicArray(abi.BoolTypeSpec()) + tuple_a = abi.Tuple(abi.TupleTypeSpec(abi.BoolTypeSpec(), abi.BoolTypeSpec())) + dynamic_array_a = abi.DynamicArray(abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec())) + dynamic_array_b = abi.DynamicArray(abi.DynamicArrayTypeSpec(abi.Uint16TypeSpec())) + dynamic_array_c = abi.DynamicArray(abi.DynamicArrayTypeSpec(abi.BoolTypeSpec())) tail_holder = pt.ScratchVar() encoded_tail = pt.ScratchVar() @@ -599,7 +599,7 @@ def test_TupleTypeSpec_byte_length_static(): def test_Tuple_decode(): encoded = pt.Bytes("encoded") - tupleValue = abi.Tuple(abi.Uint64TypeSpec()) + tupleValue = abi.Tuple(abi.TupleTypeSpec(abi.Uint64TypeSpec())) for startIndex in (None, pt.Int(1)): for endIndex in (None, pt.Int(2)): for length in (None, pt.Int(3)): @@ -638,7 +638,9 @@ def test_Tuple_decode(): def test_Tuple_set(): tupleValue = abi.Tuple( - abi.Uint8TypeSpec(), abi.Uint16TypeSpec(), abi.Uint32TypeSpec() + abi.TupleTypeSpec( + abi.Uint8TypeSpec(), abi.Uint16TypeSpec(), abi.Uint32TypeSpec() + ) ) uint8 = abi.Uint8() uint16 = abi.Uint16() @@ -678,7 +680,9 @@ def test_Tuple_set(): def test_Tuple_set_Computed(): tupleValue = abi.Tuple( - abi.Uint8TypeSpec(), abi.Uint16TypeSpec(), abi.Uint32TypeSpec() + abi.TupleTypeSpec( + abi.Uint8TypeSpec(), abi.Uint16TypeSpec(), abi.Uint32TypeSpec() + ) ) computed = ContainerType( tupleValue.type_spec(), pt.Bytes("internal representation") @@ -710,7 +714,7 @@ def test_Tuple_set_Computed(): def test_Tuple_encode(): - tupleValue = abi.Tuple(abi.Uint64TypeSpec()) + tupleValue = abi.Tuple(abi.TupleTypeSpec(abi.Uint64TypeSpec())) expr = tupleValue.encode() assert expr.type_of() == pt.TealType.bytes assert not expr.has_return() @@ -739,7 +743,7 @@ def test_Tuple_length(): ] for i, test in enumerate(tests): - tupleValue = abi.Tuple(*test) + tupleValue = abi.Tuple(abi.TupleTypeSpec(*test)) expr = tupleValue.length() assert expr.type_of() == pt.TealType.uint64 assert not expr.has_return() @@ -767,7 +771,7 @@ def test_Tuple_getitem(): ] for i, test in enumerate(tests): - tupleValue = abi.Tuple(*test) + tupleValue = abi.Tuple(abi.TupleTypeSpec(*test)) for j in range(len(test)): element = tupleValue[j] assert type(element) is TupleElement, "Test at index {} failed".format(i) @@ -793,7 +797,7 @@ def test_TupleElement_store_into(): ] for i, test in enumerate(tests): - tupleValue = abi.Tuple(*test) + tupleValue = abi.Tuple(abi.TupleTypeSpec(*test)) for j in range(len(test)): element = TupleElement(tupleValue, j) output = test[j].new_instance() diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 21857f1dd..7b5d7b09e 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -110,8 +110,12 @@ def fnWithMixedAnns4(a: pt.ScratchVar, b, c: pt.abi.Uint16) -> pt.Expr: x = pt.Int(42) s = pt.Bytes("hello") av_u16 = pt.abi.Uint16() - av_bool_dym_arr = pt.abi.DynamicArray(pt.abi.BoolTypeSpec()) - av_u32_static_arr = pt.abi.StaticArray(pt.abi.Uint32TypeSpec(), 10) + av_bool_dym_arr = pt.abi.DynamicArray( + pt.abi.DynamicArrayTypeSpec(pt.abi.BoolTypeSpec()) + ) + av_u32_static_arr = pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.Uint32TypeSpec(), 10) + ) av_bool = pt.abi.Bool() av_byte = pt.abi.Byte() @@ -265,7 +269,9 @@ def fnWithMixedAnns4AndBytesReturn(a: pt.Expr, b: pt.ScratchVar) -> pt.Bytes: def fnWithMixedAnnsABIRet1( a: pt.Expr, b: pt.ScratchVar, c: pt.abi.Uint16 ) -> pt.abi.StaticArray[pt.abi.Uint32, Literal[10]]: - return pt.abi.StaticArray(pt.abi.Uint32TypeSpec(), 10) + return pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.Uint32TypeSpec(), 10) + ) def fnWithMixedAnnsABIRet2( a: pt.Expr, b: pt.abi.Byte, c: pt.ScratchVar From c5815a4a9c9043c3eaa2a1550e898be5175fa898 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 22 Apr 2022 09:00:49 -0400 Subject: [PATCH 14/26] address mypy issues --- pyteal/ast/abi/address.py | 17 ++++++++++++----- pyteal/ast/abi/string.py | 20 +++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 9f8383317..cd4a6159c 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -1,8 +1,8 @@ -from typing import Union +from typing import Union, Sequence, TypeVar from pyteal.errors import TealInputError -from pyteal.ast.abi.type import ComputedValue +from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.array_static import StaticArray, StaticArrayTypeSpec from pyteal.ast.abi.uint import ByteTypeSpec from pyteal.ast.expr import Expr @@ -10,6 +10,8 @@ ADDRESS_LENGTH = 32 +T = TypeVar("T", bound=BaseType) +N = TypeVar("N", bound=int) class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: @@ -38,15 +40,20 @@ def type_spec(self) -> AddressTypeSpec: def get(self) -> Expr: return self.stored_value.load() - def set(self, value: Union[str, "Address", ComputedValue["Address"], Expr]) -> Expr: + def set(self, value: Union[Sequence[T], StaticArray[T, N], ComputedValue[StaticArray[T, N]], "Address", str, Expr]): + if isinstance(value, ComputedValue): + if value.produced_type_spec() is not AddressTypeSpec(): + raise TealInputError(f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec") return value.store_into(self) - if isinstance(value, Address): + if isinstance(value, BaseType): + if value.type_spec() is not AddressTypeSpec(): + raise TealInputError(f"Got {value.__class__} with type spec {value.type_spec()}, expected AddressTypeSpec") return self.decode(self.encode()) if isinstance(value, str): - return self.stored_value.store(Bytes(str)) + return self.stored_value.store(Bytes(value)) if isinstance(value, Expr): return self.stored_value.store(value) diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 256544e0d..2b3b990b0 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,6 +1,6 @@ -from typing import Union +from typing import Union, TypeVar, cast, Sequence -from pyteal.ast.abi.type import ComputedValue, TypeSpec +from pyteal.ast.abi.type import ComputedValue, TypeSpec, BaseType from pyteal.ast.abi.array_base import ArrayElement from pyteal.ast.abi.array_dynamic import DynamicArray, DynamicArrayTypeSpec from pyteal.ast.abi.uint import ByteTypeSpec, Uint16TypeSpec @@ -22,6 +22,7 @@ def encoded_string(s: Expr): return Concat(Suffix(Itob(Len(s)), Int(6)), s) +T = TypeVar("T", bound=BaseType) class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: @@ -52,14 +53,18 @@ def get(self) -> Expr: self.stored_value.load(), Int(Uint16TypeSpec().byte_length_static()) ) - def set(self, value: Union[str, "String", ComputedValue["String"], Expr]) -> Expr: + def set(self, value: Union[Sequence[T], DynamicArray[T], ComputedValue[DynamicArray[T]], "String", str, Expr]) -> Expr: # Assume length prefixed if isinstance(value, ComputedValue): + if value.produced_type_spec() is not StringTypeSpec(): + raise TealInputError(f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec") return value.store_into(self) - if isinstance(value, String): - return self.decode(value.encode()) + if isinstance(value, BaseType): + if value.type_spec() is not StringTypeSpec(): + raise TealInputError(f"Got {value.__class__} with type spec {value.type_spec()}, expected StringTypeSpec") + return value.decode(value.encode()) # Assume not length prefixed if type(value) is str: @@ -70,7 +75,7 @@ def set(self, value: Union[str, "String", ComputedValue["String"], Expr]) -> Exp return self.stored_value.store(encoded_string(value)) - def __getitem__(self, index: Union[int, slice, Expr]) -> "ArrayElement[String]": + def __getitem__(self, index: Union[int, slice, Expr]) -> "ArrayElement[T]": if type(index) is int or isinstance(index, Expr): return super().__getitem__(index) @@ -102,7 +107,8 @@ def __getitem__(self, index: Union[int, slice, Expr]) -> "ArrayElement[String]": if type(start) is int: start = Int(start) - return SubstringValue(self, start, stop) + # TODO: ????? + return ArrayElement(self, cast(Expr, SubstringValue(self, start, stop))) String.__module__ = "pyteal" From 2cf669983aa256b96e96673891dbcc6f53390aea Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 22 Apr 2022 09:03:11 -0400 Subject: [PATCH 15/26] fmt --- pyteal/ast/abi/address.py | 23 +++++++++++++++++++---- pyteal/ast/abi/address_test.py | 3 ++- pyteal/ast/abi/array_dynamic.py | 2 +- pyteal/ast/abi/string.py | 24 +++++++++++++++++++----- pyteal/ast/abi/string_test.py | 3 +-- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index cd4a6159c..f541d71a9 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -13,6 +13,7 @@ T = TypeVar("T", bound=BaseType) N = TypeVar("N", bound=int) + class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: super().__init__(ByteTypeSpec(), ADDRESS_LENGTH) @@ -40,16 +41,30 @@ def type_spec(self) -> AddressTypeSpec: def get(self) -> Expr: return self.stored_value.load() - def set(self, value: Union[Sequence[T], StaticArray[T, N], ComputedValue[StaticArray[T, N]], "Address", str, Expr]): - + def set( + self, + value: Union[ + Sequence[T], + StaticArray[T, N], + ComputedValue[StaticArray[T, N]], + "Address", + str, + Expr, + ], + ): + if isinstance(value, ComputedValue): if value.produced_type_spec() is not AddressTypeSpec(): - raise TealInputError(f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec") + raise TealInputError( + f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec" + ) return value.store_into(self) if isinstance(value, BaseType): if value.type_spec() is not AddressTypeSpec(): - raise TealInputError(f"Got {value.__class__} with type spec {value.type_spec()}, expected AddressTypeSpec") + raise TealInputError( + f"Got {value.__class__} with type spec {value.type_spec()}, expected AddressTypeSpec" + ) return self.decode(self.encode()) if isinstance(value, str): diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 44aa466dc..8aef6720b 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -2,7 +2,8 @@ import pyteal as pt from pyteal import abi -from .util import substringForDecoding +from pyteal.ast.abi.util import substringForDecoding + options = pt.CompileOptions(version=5) diff --git a/pyteal/ast/abi/array_dynamic.py b/pyteal/ast/abi/array_dynamic.py index 9387f9877..3eb97c7ad 100644 --- a/pyteal/ast/abi/array_dynamic.py +++ b/pyteal/ast/abi/array_dynamic.py @@ -10,7 +10,7 @@ from pyteal.ast.expr import Expr from pyteal.ast.seq import Seq -from pyteal.ast.abi.type import ComputedValue, TypeSpec, BaseType +from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.uint import Uint16 from pyteal.ast.abi.array_base import ArrayTypeSpec, Array diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 2b3b990b0..aab05681b 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -11,8 +11,6 @@ from pyteal.ast.bytes import Bytes from pyteal.ast.unaryexpr import Itob, Len from pyteal.ast.substring import Suffix -from pyteal.ast.int import Int -from pyteal.ast.expr import Expr from pyteal.ast.naryexpr import Concat from pyteal.types import TealType, require_type @@ -22,8 +20,10 @@ def encoded_string(s: Expr): return Concat(Suffix(Itob(Len(s)), Int(6)), s) + T = TypeVar("T", bound=BaseType) + class StringTypeSpec(DynamicArrayTypeSpec): def __init__(self) -> None: super().__init__(ByteTypeSpec()) @@ -53,17 +53,31 @@ def get(self) -> Expr: self.stored_value.load(), Int(Uint16TypeSpec().byte_length_static()) ) - def set(self, value: Union[Sequence[T], DynamicArray[T], ComputedValue[DynamicArray[T]], "String", str, Expr]) -> Expr: + def set( + self, + value: Union[ + Sequence[T], + DynamicArray[T], + ComputedValue[DynamicArray[T]], + "String", + str, + Expr, + ], + ) -> Expr: # Assume length prefixed if isinstance(value, ComputedValue): if value.produced_type_spec() is not StringTypeSpec(): - raise TealInputError(f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec") + raise TealInputError( + f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec" + ) return value.store_into(self) if isinstance(value, BaseType): if value.type_spec() is not StringTypeSpec(): - raise TealInputError(f"Got {value.__class__} with type spec {value.type_spec()}, expected StringTypeSpec") + raise TealInputError( + f"Got {value.__class__} with type spec {value.type_spec()}, expected StringTypeSpec" + ) return value.decode(value.encode()) # Assume not length prefixed diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index 9a4bdb76e..8e0d38e31 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -2,8 +2,7 @@ import pyteal as pt from pyteal import abi -from .util import substringForDecoding - +from pyteal.ast.abi.util import substringForDecoding options = pt.CompileOptions(version=5) From 50848df83144ccb7764045eaddcb1c728dc5d51a Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 27 Apr 2022 11:21:38 -0400 Subject: [PATCH 16/26] adding tests for string set --- pyteal/ast/__init__.py | 1 + pyteal/ast/abi/string.py | 8 +-- pyteal/ast/abi/string_test.py | 113 ++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 7d951442e..c4f606fcc 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -139,6 +139,7 @@ # abi from pyteal.ast import abi +#from pyteal.ast.abi import * __all__ = [ "Expr", diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index aab05681b..038b7bcad 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -67,18 +67,18 @@ def set( # Assume length prefixed if isinstance(value, ComputedValue): - if value.produced_type_spec() is not StringTypeSpec(): + if not isinstance(value.produced_type_spec(), StringTypeSpec): raise TealInputError( f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec" ) return value.store_into(self) if isinstance(value, BaseType): - if value.type_spec() is not StringTypeSpec(): + if not isinstance(value.type_spec(), StringTypeSpec): raise TealInputError( - f"Got {value.__class__} with type spec {value.type_spec()}, expected StringTypeSpec" + f"Got {value} with type spec {value.type_spec()}, expected {StringTypeSpec}" ) - return value.decode(value.encode()) + return self.decode(value.encode()) # Assume not length prefixed if type(value) is str: diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index 8e0d38e31..80912a56e 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -3,6 +3,8 @@ import pyteal as pt from pyteal import abi from pyteal.ast.abi.util import substringForDecoding +from pyteal.ast.abi.type_test import ContainerType +from pyteal.util import escapeStr options = pt.CompileOptions(version=5) @@ -103,3 +105,114 @@ def test_String_get(): with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected + + +def test_String_set_static(): + for value_to_set in ("stringy", "😀", "0xDEADBEEF"): + value = abi.String() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.byte, escapeStr(value_to_set)), + pt.TealOp(None, pt.Op.len), + pt.TealOp(None, pt.Op.itob), + pt.TealOp(None, pt.Op.extract, 6, 0), + pt.TealOp(None, pt.Op.byte, escapeStr(value_to_set)), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(bytes(32)) + + +def test_String_set_expr(): + for value_to_set in (pt.Bytes("hi"), pt.Bytes("base16", "0xdeadbeef")): + value = abi.String() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + vts, _ = value_to_set.__teal__(options) + expected = pt.TealSimpleBlock( + [ + vts.ops[0], + pt.TealOp(None, pt.Op.len), + pt.TealOp(None, pt.Op.itob), + pt.TealOp(None, pt.Op.extract, 6, 0), + vts.ops[0], + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +def test_String_set_copy(): + value = abi.String() + other = abi.String() + expr = value.set(other) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.load, other.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(abi.Address()) + + +def test_String_set_computed(): + bv = pt.Bytes("base16", "0x0004DEADBEEF") + computed_value = ContainerType(abi.StringTypeSpec(), bv) + + value = abi.String() + expr = value.set(computed_value) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + _, byte_ops = bv.__teal__(options) + expected = pt.TealSimpleBlock( + [ + byte_ops.ops[0], + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(ContainerType(abi.ByteTypeSpec(), pt.Int(0x01))) From 044a76c504451c45f6f990c5bf051ebb52209e14 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 27 Apr 2022 12:24:13 -0400 Subject: [PATCH 17/26] starting to try slice testing --- pyteal/ast/__init__.py | 3 ++- pyteal/ast/abi/__init__.py | 3 ++- pyteal/ast/abi/string.py | 3 ++- pyteal/ast/abi/string_test.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index c4f606fcc..1c1e63aa4 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -139,7 +139,8 @@ # abi from pyteal.ast import abi -#from pyteal.ast.abi import * + +# from pyteal.ast.abi import * __all__ = [ "Expr", diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index ba495f0ac..bb72e1705 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from pyteal.ast.abi.string import String, StringTypeSpec +from pyteal.ast.abi.string import String, StringTypeSpec, SubstringValue from pyteal.ast.abi.address import AddressTypeSpec, Address, ADDRESS_LENGTH from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue from pyteal.ast.abi.bool import BoolTypeSpec, Bool @@ -36,6 +36,7 @@ __all__ = [ "String", "StringTypeSpec", + "SubstringValue", "Address", "AddressTypeSpec", "ADDRESS_LENGTH", diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 038b7bcad..c19714597 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -122,7 +122,8 @@ def __getitem__(self, index: Union[int, slice, Expr]) -> "ArrayElement[T]": start = Int(start) # TODO: ????? - return ArrayElement(self, cast(Expr, SubstringValue(self, start, stop))) + # return ArrayElement(self, cast(Expr, SubstringValue(self, start, stop))) + return SubstringValue(self, start, stop) String.__module__ = "pyteal" diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index 80912a56e..04d3dff52 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -216,3 +216,36 @@ def test_String_set_computed(): with pytest.raises(pt.TealInputError): value.set(ContainerType(abi.ByteTypeSpec(), pt.Int(0x01))) + + +def test_String_slice(): + test_vals = [ + "this is a long string but its not really that long but its not super short either so lets just stick with it", + "", + "medium string", + ] + + for val_to_set in test_vals: + value = abi.String() + value.set(val_to_set) + + substr = value[1:] + assert isinstance(substr, abi.SubstringValue) + + substr = value[:1] + assert isinstance(substr, abi.SubstringValue) + + substr = value[:] + assert isinstance(substr, abi.SubstringValue) + + +def test_SubstringValue_type_spec(): + pass + + +def test_SubstringValue_store_into(): + pass + + +def test_SubstringValue_use(): + pass From e0f314731c9da37ae53abc0c7fa63cb0cbdacac4 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 27 Apr 2022 12:50:25 -0400 Subject: [PATCH 18/26] remove slicing for now --- pyteal/ast/abi/__init__.py | 2 +- pyteal/ast/abi/string.py | 71 +---------------------------------- pyteal/ast/abi/string_test.py | 33 ---------------- 3 files changed, 3 insertions(+), 103 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index bb72e1705..390b935ed 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from pyteal.ast.abi.string import String, StringTypeSpec, SubstringValue +from pyteal.ast.abi.string import String, StringTypeSpec from pyteal.ast.abi.address import AddressTypeSpec, Address, ADDRESS_LENGTH from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue from pyteal.ast.abi.bool import BoolTypeSpec, Bool diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index c19714597..9591bd81d 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,10 +1,8 @@ -from typing import Union, TypeVar, cast, Sequence +from typing import Union, TypeVar, Sequence -from pyteal.ast.abi.type import ComputedValue, TypeSpec, BaseType -from pyteal.ast.abi.array_base import ArrayElement +from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.array_dynamic import DynamicArray, DynamicArrayTypeSpec from pyteal.ast.abi.uint import ByteTypeSpec, Uint16TypeSpec -from pyteal.ast.abi.util import substringForDecoding from pyteal.ast.int import Int from pyteal.ast.expr import Expr @@ -13,7 +11,6 @@ from pyteal.ast.substring import Suffix from pyteal.ast.naryexpr import Concat -from pyteal.types import TealType, require_type from pyteal.errors import TealInputError @@ -89,69 +86,5 @@ def set( return self.stored_value.store(encoded_string(value)) - def __getitem__(self, index: Union[int, slice, Expr]) -> "ArrayElement[T]": - - if type(index) is int or isinstance(index, Expr): - return super().__getitem__(index) - - if type(index) is not slice: - raise TealInputError("Index expected slice, got {index}") - - if index.step is not None: - raise TealInputError("Slice step is not supported") - - start = index.start - stop = index.stop - - if stop is not None and type(stop) is int and stop <= 0: - raise TealInputError("Negative slice indicies are not supported") - - if start is not None and type(start) is int and start <= 0: - raise TealInputError("Negative slice indicies are not supported") - - if stop is None: - stop = self.length() - - if start is None: - start = Int(0) - - if type(stop) is int: - stop = Int(stop) - - if type(start) is int: - start = Int(start) - - # TODO: ????? - # return ArrayElement(self, cast(Expr, SubstringValue(self, start, stop))) - return SubstringValue(self, start, stop) - String.__module__ = "pyteal" - - -class SubstringValue(ComputedValue): - def __init__(self, string: String, start: Expr, stop: Expr) -> None: - super().__init__() - - require_type(start, TealType.uint64) - require_type(stop, TealType.uint64) - - self.string = string - self.start = start - self.stop = stop - - def produced_type_spec(cls) -> TypeSpec: - return StringTypeSpec() - - def store_into(self, output: String) -> Expr: - return output.decode( - Concat( - Suffix(Itob(self.stop - self.start), Int(6)), - substringForDecoding( - self.string.get(), startIndex=self.start, endIndex=self.stop - ), - ) - ) - - -SubstringValue.__module__ = "pyteal" diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index 04d3dff52..80912a56e 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -216,36 +216,3 @@ def test_String_set_computed(): with pytest.raises(pt.TealInputError): value.set(ContainerType(abi.ByteTypeSpec(), pt.Int(0x01))) - - -def test_String_slice(): - test_vals = [ - "this is a long string but its not really that long but its not super short either so lets just stick with it", - "", - "medium string", - ] - - for val_to_set in test_vals: - value = abi.String() - value.set(val_to_set) - - substr = value[1:] - assert isinstance(substr, abi.SubstringValue) - - substr = value[:1] - assert isinstance(substr, abi.SubstringValue) - - substr = value[:] - assert isinstance(substr, abi.SubstringValue) - - -def test_SubstringValue_type_spec(): - pass - - -def test_SubstringValue_store_into(): - pass - - -def test_SubstringValue_use(): - pass From 85cbdadb864e9147d8bc7029d46ce3b7c96d924e Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 27 Apr 2022 13:20:38 -0400 Subject: [PATCH 19/26] adding tests for address set --- pyteal/ast/abi/__init__.py | 1 - pyteal/ast/abi/address.py | 11 ++-- pyteal/ast/abi/address_test.py | 104 +++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 390b935ed..ba495f0ac 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -36,7 +36,6 @@ __all__ = [ "String", "StringTypeSpec", - "SubstringValue", "Address", "AddressTypeSpec", "ADDRESS_LENGTH", diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index f541d71a9..5ca99348e 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -2,6 +2,7 @@ from pyteal.errors import TealInputError +from pyteal.ast.addr import Addr from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.array_static import StaticArray, StaticArrayTypeSpec from pyteal.ast.abi.uint import ByteTypeSpec @@ -54,21 +55,21 @@ def set( ): if isinstance(value, ComputedValue): - if value.produced_type_spec() is not AddressTypeSpec(): + if not isinstance(value.produced_type_spec(), AddressTypeSpec): raise TealInputError( f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec" ) return value.store_into(self) if isinstance(value, BaseType): - if value.type_spec() is not AddressTypeSpec(): + if not isinstance(value.type_spec(), AddressTypeSpec): raise TealInputError( - f"Got {value.__class__} with type spec {value.type_spec()}, expected AddressTypeSpec" + f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" ) - return self.decode(self.encode()) + return self.decode(value.encode()) if isinstance(value, str): - return self.stored_value.store(Bytes(value)) + return self.stored_value.store(Addr(value)) if isinstance(value, Expr): return self.stored_value.store(value) diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 8aef6720b..4f34c6055 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -2,6 +2,9 @@ import pyteal as pt from pyteal import abi +from pyteal.ast.global_ import Global, GlobalField +from pyteal.ast.abi.type_test import ContainerType +from pyteal.util import escapeStr from pyteal.ast.abi.util import substringForDecoding @@ -101,3 +104,104 @@ def test_Address_get(): ) actual, _ = expr.__teal__(options) assert actual == expected + + +def test_Address_set_static(): + for value_to_set in ("CEZZTYHNTVIZFZWT6X2R474Z2P3Q2DAZAKIRTPBAHL3LZ7W4O6VBROVRQA",): + value = abi.Address() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.addr, value_to_set), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(bytes(32)) + + +def test_Address_set_expr(): + for value_to_set in [Global(GlobalField.zero_address)]: + value = abi.Address() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + vts, _ = value_to_set.__teal__(options) + expected = pt.TealSimpleBlock( + [ + vts.ops[0], + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +def test_Address_set_copy(): + value = abi.Address() + other = abi.Address() + expr = value.set(other) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.load, other.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(abi.String()) + + +def test_Address_set_computed(): + av = pt.Addr("MDDKJUCTY57KA2PBFI44CLTJ5YHY5YVS4SVQUPZAWSRV2ZAVFKI33O6YPE") + computed_value = ContainerType(abi.AddressTypeSpec(), av) + + value = abi.Address() + expr = value.set(computed_value) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + _, byte_ops = av.__teal__(options) + expected = pt.TealSimpleBlock( + [ + byte_ops.ops[0], + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(ContainerType(abi.ByteTypeSpec(), pt.Int(0x01))) From 50068c96c8ce76cdcf39ff652cf79aa59cbff41f Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 27 Apr 2022 13:49:00 -0400 Subject: [PATCH 20/26] make linter happy --- pyteal/ast/abi/address.py | 1 - pyteal/ast/abi/address_test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 5ca99348e..68196d3df 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -7,7 +7,6 @@ from pyteal.ast.abi.array_static import StaticArray, StaticArrayTypeSpec from pyteal.ast.abi.uint import ByteTypeSpec from pyteal.ast.expr import Expr -from pyteal.ast.bytes import Bytes ADDRESS_LENGTH = 32 diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 4f34c6055..373aac860 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -4,7 +4,6 @@ from pyteal.ast.global_ import Global, GlobalField from pyteal.ast.abi.type_test import ContainerType -from pyteal.util import escapeStr from pyteal.ast.abi.util import substringForDecoding From c5b13c492e11abb44a9249f5bb75593883823d76 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 28 Apr 2022 13:21:03 -0400 Subject: [PATCH 21/26] adding handlers for other cases, adding testing for new handlers --- pyteal/ast/__init__.py | 2 -- pyteal/ast/abi/__init__.py | 10 ++++-- pyteal/ast/abi/address.py | 42 +++++++++++++++++------ pyteal/ast/abi/address_test.py | 63 +++++++++++++++++++++++++++++++--- pyteal/ast/abi/string.py | 30 +++++++++++----- pyteal/ast/abi/string_test.py | 32 +++++++++++++++-- 6 files changed, 149 insertions(+), 30 deletions(-) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index f0c83d265..d131e7344 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -140,8 +140,6 @@ # abi import pyteal.ast.abi as abi # noqa: I250 -# from pyteal.ast.abi import * - __all__ = [ "Expr", "LeafExpr", diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index ba495f0ac..a48c58d51 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,5 +1,10 @@ from pyteal.ast.abi.string import String, StringTypeSpec -from pyteal.ast.abi.address import AddressTypeSpec, Address, ADDRESS_LENGTH +from pyteal.ast.abi.address import ( + AddressTypeSpec, + Address, + ADDRESS_LENGTH_BYTES, + ADDRESS_LENGTH_STR, +) from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue from pyteal.ast.abi.bool import BoolTypeSpec, Bool from pyteal.ast.abi.uint import ( @@ -38,7 +43,8 @@ "StringTypeSpec", "Address", "AddressTypeSpec", - "ADDRESS_LENGTH", + "ADDRESS_LENGTH_BYTES", + "ADDRESS_LENGTH_STR", "TypeSpec", "BaseType", "ComputedValue", diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 68196d3df..57b82b343 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -2,13 +2,15 @@ from pyteal.errors import TealInputError +from pyteal.ast.bytes import Bytes from pyteal.ast.addr import Addr from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.array_static import StaticArray, StaticArrayTypeSpec from pyteal.ast.abi.uint import ByteTypeSpec from pyteal.ast.expr import Expr -ADDRESS_LENGTH = 32 +ADDRESS_LENGTH_STR = 58 +ADDRESS_LENGTH_BYTES = 32 T = TypeVar("T", bound=BaseType) N = TypeVar("N", bound=int) @@ -16,7 +18,7 @@ class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec(), ADDRESS_LENGTH) + super().__init__(ByteTypeSpec(), ADDRESS_LENGTH_BYTES) def new_instance(self) -> "Address": return Address() @@ -49,6 +51,7 @@ def set( ComputedValue[StaticArray[T, N]], "Address", str, + bytes, Expr, ], ): @@ -60,20 +63,37 @@ def set( ) return value.store_into(self) - if isinstance(value, BaseType): - if not isinstance(value.type_spec(), AddressTypeSpec): - raise TealInputError( - f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" - ) - return self.decode(value.encode()) + elif isinstance(value, BaseType): + + if isinstance(value.type_spec(), AddressTypeSpec): + return self.decode(value.encode()) + + if ( + isinstance(value.type_spec(), StaticArrayTypeSpec) + and isinstance(value.type_spec().value_type_spec(), ByteTypeSpec) + and value.type_spec().length_static() == ADDRESS_LENGTH_BYTES + ): + return self.decode(value.encode()) - if isinstance(value, str): + raise TealInputError( + f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" + ) + + elif isinstance(value, str) and len(value) == ADDRESS_LENGTH_STR: return self.stored_value.store(Addr(value)) - if isinstance(value, Expr): + elif isinstance(value, bytes) and len(value) == ADDRESS_LENGTH_BYTES: + return self.stored_value.store(Bytes(value)) + + elif isinstance(value, Expr): return self.stored_value.store(value) - raise TealInputError(f"Expected str, Address or Expr got {value}") + elif isinstance(value, Sequence): + return super().set(value) + + raise TealInputError( + f"Got {value}, expected str, bytes, Expr, Sequence, Address, or ComputedType" + ) Address.__module__ = "pyteal" diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 373aac860..a8ceb3681 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -1,6 +1,7 @@ import pytest import pyteal as pt from pyteal import abi +from pyteal.ast.abi.address import ADDRESS_LENGTH_BYTES from pyteal.ast.global_ import Global, GlobalField from pyteal.ast.abi.type_test import ContainerType @@ -19,7 +20,7 @@ def test_AddressTypeSpec_is_dynamic(): def test_AddressTypeSpec_byte_length_static(): - assert (abi.AddressTypeSpec()).byte_length_static() == abi.ADDRESS_LENGTH + assert (abi.AddressTypeSpec()).byte_length_static() == ADDRESS_LENGTH_BYTES def test_AddressTypeSpec_new_instance(): @@ -51,7 +52,7 @@ def test_Address_encode(): def test_Address_decode(): - address = bytes([0] * abi.ADDRESS_LENGTH) + address = bytes([0] * ADDRESS_LENGTH_BYTES) encoded = pt.Bytes(address) for startIndex in (None, pt.Int(0)): @@ -105,7 +106,36 @@ def test_Address_get(): assert actual == expected -def test_Address_set_static(): +def test_Address_set_StaticArray(): + sa = abi.StaticArray( + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), ADDRESS_LENGTH_BYTES) + ) + for value_to_set in (sa,): + value = abi.Address() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.load, value_to_set.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + bogus = abi.StaticArray(abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 10)) + value.set(bogus) + + +def test_Address_set_str(): for value_to_set in ("CEZZTYHNTVIZFZWT6X2R474Z2P3Q2DAZAKIRTPBAHL3LZ7W4O6VBROVRQA",): value = abi.Address() expr = value.set(value_to_set) @@ -127,7 +157,32 @@ def test_Address_set_static(): assert actual == expected with pytest.raises(pt.TealInputError): - value.set(bytes(32)) + value.set(" " * 16) + + +def test_Address_set_bytes(): + for value_to_set in (bytes(32),): + value = abi.Address() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.byte, f"0x{value_to_set.hex()}"), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(bytes(16)) def test_Address_set_expr(): diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 9591bd81d..f69aaf28b 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -58,6 +58,7 @@ def set( ComputedValue[DynamicArray[T]], "String", str, + bytes, Expr, ], ) -> Expr: @@ -71,20 +72,31 @@ def set( return value.store_into(self) if isinstance(value, BaseType): - if not isinstance(value.type_spec(), StringTypeSpec): - raise TealInputError( - f"Got {value} with type spec {value.type_spec()}, expected {StringTypeSpec}" - ) - return self.decode(value.encode()) + if isinstance(value.type_spec(), StringTypeSpec): + return self.decode(value.encode()) + + if isinstance(value.type_spec(), DynamicArrayTypeSpec) and isinstance( + value.type_spec().value_type_spec(), ByteTypeSpec + ): + return self.decode(value.encode()) + + raise TealInputError( + f"Got {value} with type spec {value.type_spec()}, expected {StringTypeSpec}" + ) # Assume not length prefixed - if type(value) is str: + if isinstance(value, str) or isinstance(value, bytes): return self.stored_value.store(encoded_string(Bytes(value))) - if not isinstance(value, Expr): - raise TealInputError("Expected Expr, got {}".format(value)) + if isinstance(value, Expr): + return self.stored_value.store(encoded_string(value)) + + if isinstance(value, Sequence): + return super().set(value) - return self.stored_value.store(encoded_string(value)) + raise TealInputError( + f"Got {value}, expected Sequence, DynamicArray, ComputedValue, String, str, bytes, Expr" + ) String.__module__ = "pyteal" diff --git a/pyteal/ast/abi/string_test.py b/pyteal/ast/abi/string_test.py index 80912a56e..a652d2768 100644 --- a/pyteal/ast/abi/string_test.py +++ b/pyteal/ast/abi/string_test.py @@ -108,6 +108,7 @@ def test_String_get(): def test_String_set_static(): + for value_to_set in ("stringy", "😀", "0xDEADBEEF"): value = abi.String() expr = value.set(value_to_set) @@ -133,8 +134,35 @@ def test_String_set_static(): with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected - with pytest.raises(pt.TealInputError): - value.set(bytes(32)) + for value_to_set in (bytes(32), b"alphabet_soup"): + value = abi.String() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() + + teal_val = f"0x{value_to_set.hex()}" + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.byte, teal_val), + pt.TealOp(None, pt.Op.len), + pt.TealOp(None, pt.Op.itob), + pt.TealOp(None, pt.Op.extract, 6, 0), + pt.TealOp(None, pt.Op.byte, teal_val), + pt.TealOp(None, pt.Op.concat), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + value.set(42) def test_String_set_expr(): From d4b6ecfbc92d020083f431510a1954b3e05e9b67 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 28 Apr 2022 14:33:29 -0400 Subject: [PATCH 22/26] change typespec checking --- pyteal/ast/abi/address.py | 23 ++++++++++------------- pyteal/ast/abi/string.py | 19 +++++++++---------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 57b82b343..7b6189afe 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -57,23 +57,20 @@ def set( ): if isinstance(value, ComputedValue): - if not isinstance(value.produced_type_spec(), AddressTypeSpec): - raise TealInputError( - f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec" - ) - return value.store_into(self) + if value.produced_type_spec() == AddressTypeSpec(): + return value.store_into(self) - elif isinstance(value, BaseType): - - if isinstance(value.type_spec(), AddressTypeSpec): - return self.decode(value.encode()) + raise TealInputError( + f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec" + ) + elif isinstance(value, BaseType): if ( - isinstance(value.type_spec(), StaticArrayTypeSpec) - and isinstance(value.type_spec().value_type_spec(), ByteTypeSpec) - and value.type_spec().length_static() == ADDRESS_LENGTH_BYTES + value.type_spec() == AddressTypeSpec() + or value.type_spec() + == StaticArrayTypeSpec(ByteTypeSpec(), ADDRESS_LENGTH_BYTES) ): - return self.decode(value.encode()) + return self.stored_value.store(value.stored_value.load()) raise TealInputError( f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index f69aaf28b..b8d003dab 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -65,20 +65,19 @@ def set( # Assume length prefixed if isinstance(value, ComputedValue): - if not isinstance(value.produced_type_spec(), StringTypeSpec): - raise TealInputError( - f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec" - ) - return value.store_into(self) + if value.produced_type_spec() == StringTypeSpec(): + return value.store_into(self) + + raise TealInputError( + f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec" + ) if isinstance(value, BaseType): - if isinstance(value.type_spec(), StringTypeSpec): - return self.decode(value.encode()) - if isinstance(value.type_spec(), DynamicArrayTypeSpec) and isinstance( - value.type_spec().value_type_spec(), ByteTypeSpec + if value.type_spec() == StringTypeSpec() or ( + value.type_spec() == DynamicArrayTypeSpec(ByteTypeSpec()) ): - return self.decode(value.encode()) + return self.stored_value.store(value.stored_value.load()) raise TealInputError( f"Got {value} with type spec {value.type_spec()}, expected {StringTypeSpec}" From 58ccd76349589c2eeb484ee7aa33f6bcceb4a4c4 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 29 Apr 2022 13:29:48 -0400 Subject: [PATCH 23/26] use match for typechecks --- pyteal/ast/abi/address.py | 66 ++++++++++++++++++++------------------- pyteal/ast/abi/string.py | 54 ++++++++++++++------------------ 2 files changed, 57 insertions(+), 63 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 7b6189afe..e6b4edc52 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -56,40 +56,42 @@ def set( ], ): - if isinstance(value, ComputedValue): - if value.produced_type_spec() == AddressTypeSpec(): - return value.store_into(self) - - raise TealInputError( - f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec" - ) - - elif isinstance(value, BaseType): - if ( - value.type_spec() == AddressTypeSpec() - or value.type_spec() - == StaticArrayTypeSpec(ByteTypeSpec(), ADDRESS_LENGTH_BYTES) - ): - return self.stored_value.store(value.stored_value.load()) - - raise TealInputError( - f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" - ) - - elif isinstance(value, str) and len(value) == ADDRESS_LENGTH_STR: - return self.stored_value.store(Addr(value)) - - elif isinstance(value, bytes) and len(value) == ADDRESS_LENGTH_BYTES: - return self.stored_value.store(Bytes(value)) - - elif isinstance(value, Expr): - return self.stored_value.store(value) - - elif isinstance(value, Sequence): - return super().set(value) + match value: + case ComputedValue(): + if value.produced_type_spec() == AddressTypeSpec(): + return value.store_into(self) + + raise TealInputError( + f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec" + ) + case BaseType(): + if ( + value.type_spec() == AddressTypeSpec() + or value.type_spec() + == StaticArrayTypeSpec(ByteTypeSpec(), ADDRESS_LENGTH_BYTES) + ): + return self.stored_value.store(value.stored_value.load()) + + raise TealInputError( + f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" + ) + case str(): + if len(value) == ADDRESS_LENGTH_STR: + return self.stored_value.store(Addr(value)) + raise TealInputError( + f"Got string with length {len(value)}, expected {ADDRESS_LENGTH_STR}" + ) + case bytes(): + if len(value) == ADDRESS_LENGTH_BYTES: + return self.stored_value.store(Bytes(value)) + raise TealInputError( + f"Got bytes with length {len(value)}, expected {ADDRESS_LENGTH_BYTES}" + ) + case Expr(): + return self.stored_value.store(value) raise TealInputError( - f"Got {value}, expected str, bytes, Expr, Sequence, Address, or ComputedType" + f"Got {type(value)}, expected StaticArray, ComputedValue, String, str, bytes, Expr" ) diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index b8d003dab..4e508ece4 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,4 +1,4 @@ -from typing import Union, TypeVar, Sequence +from typing import Iterable, Union, TypeVar, Sequence, get_type_hints from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.array_dynamic import DynamicArray, DynamicArrayTypeSpec @@ -63,38 +63,30 @@ def set( ], ) -> Expr: - # Assume length prefixed - if isinstance(value, ComputedValue): - if value.produced_type_spec() == StringTypeSpec(): - return value.store_into(self) - - raise TealInputError( - f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec" - ) - - if isinstance(value, BaseType): - - if value.type_spec() == StringTypeSpec() or ( - value.type_spec() == DynamicArrayTypeSpec(ByteTypeSpec()) - ): - return self.stored_value.store(value.stored_value.load()) - - raise TealInputError( - f"Got {value} with type spec {value.type_spec()}, expected {StringTypeSpec}" - ) - - # Assume not length prefixed - if isinstance(value, str) or isinstance(value, bytes): - return self.stored_value.store(encoded_string(Bytes(value))) - - if isinstance(value, Expr): - return self.stored_value.store(encoded_string(value)) - - if isinstance(value, Sequence): - return super().set(value) + match value: + case ComputedValue(): + if value.produced_type_spec() == StringTypeSpec(): + return value.store_into(self) + + raise TealInputError( + f"Got ComputedValue with type spec {value.produced_type_spec()}, expected StringTypeSpec" + ) + case BaseType(): + if value.type_spec() == StringTypeSpec() or ( + value.type_spec() == DynamicArrayTypeSpec(ByteTypeSpec()) + ): + return self.stored_value.store(value.stored_value.load()) + + raise TealInputError( + f"Got {value} with type spec {value.type_spec()}, expected {StringTypeSpec}" + ) + case str() | bytes(): + return self.stored_value.store(encoded_string(Bytes(value))) + case Expr(): + return self.stored_value.store(encoded_string(value)) raise TealInputError( - f"Got {value}, expected Sequence, DynamicArray, ComputedValue, String, str, bytes, Expr" + f"Got {type(value)}, expected DynamicArray, ComputedValue, String, str, bytes, Expr" ) From a8f8d2901c6de16837100c5b0725df8248c642b7 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 29 Apr 2022 13:35:28 -0400 Subject: [PATCH 24/26] adding sequence check from collections package --- pyteal/ast/abi/address.py | 3 +++ pyteal/ast/abi/string.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index e6b4edc52..58d77be6c 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -1,4 +1,5 @@ from typing import Union, Sequence, TypeVar +from collections.abc import Sequence as CollectionSequence from pyteal.errors import TealInputError @@ -89,6 +90,8 @@ def set( ) case Expr(): return self.stored_value.store(value) + case CollectionSequence(): + return super().set(value) raise TealInputError( f"Got {type(value)}, expected StaticArray, ComputedValue, String, str, bytes, Expr" diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index 4e508ece4..cddaa4e7d 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -1,4 +1,5 @@ -from typing import Iterable, Union, TypeVar, Sequence, get_type_hints +from typing import Union, TypeVar, Sequence +from collections.abc import Sequence as CollectionSequence from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.array_dynamic import DynamicArray, DynamicArrayTypeSpec @@ -84,6 +85,8 @@ def set( return self.stored_value.store(encoded_string(Bytes(value))) case Expr(): return self.stored_value.store(encoded_string(value)) + case CollectionSequence(): + return super().set(value) raise TealInputError( f"Got {type(value)}, expected DynamicArray, ComputedValue, String, str, bytes, Expr" From 017a7ffc98e13d95b1d4bc91a1752fef2a30cda9 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 2 May 2022 08:50:09 -0400 Subject: [PATCH 25/26] partial cr --- pyteal/ast/abi/__init__.py | 6 ++-- pyteal/ast/abi/address.py | 57 ++++++++++++++++++++++----------- pyteal/ast/abi/address_test.py | 52 +++++++++++++++--------------- pyteal/ast/abi/array_dynamic.py | 4 +-- pyteal/ast/abi/array_static.py | 6 ++-- pyteal/ast/abi/tuple.py | 57 +++++++++++++-------------------- 6 files changed, 93 insertions(+), 89 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index a48c58d51..89629e612 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -2,8 +2,7 @@ from pyteal.ast.abi.address import ( AddressTypeSpec, Address, - ADDRESS_LENGTH_BYTES, - ADDRESS_LENGTH_STR, + AddressLength, ) from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue from pyteal.ast.abi.bool import BoolTypeSpec, Bool @@ -43,8 +42,7 @@ "StringTypeSpec", "Address", "AddressTypeSpec", - "ADDRESS_LENGTH_BYTES", - "ADDRESS_LENGTH_STR", + "AddressLength", "TypeSpec", "BaseType", "ComputedValue", diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 58d77be6c..4bcaa6387 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -1,4 +1,5 @@ -from typing import Union, Sequence, TypeVar +from enum import Enum +from typing import Union, Sequence, Literal from collections.abc import Sequence as CollectionSequence from pyteal.errors import TealInputError @@ -7,19 +8,21 @@ from pyteal.ast.addr import Addr from pyteal.ast.abi.type import ComputedValue, BaseType from pyteal.ast.abi.array_static import StaticArray, StaticArrayTypeSpec -from pyteal.ast.abi.uint import ByteTypeSpec +from pyteal.ast.abi.uint import ByteTypeSpec, Byte from pyteal.ast.expr import Expr -ADDRESS_LENGTH_STR = 58 -ADDRESS_LENGTH_BYTES = 32 -T = TypeVar("T", bound=BaseType) -N = TypeVar("N", bound=int) +class AddressLength(Enum): + String = 58 + Bytes = 32 + + +AddressLength.__module__ = "pyteal" class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec(), ADDRESS_LENGTH_BYTES) + super().__init__(ByteTypeSpec(), AddressLength.Bytes.value) def new_instance(self) -> "Address": return Address() @@ -34,7 +37,7 @@ def __eq__(self, other: object) -> bool: AddressTypeSpec.__module__ = "pyteal" -class Address(StaticArray): +class Address(StaticArray[Byte, Literal[32]]): def __init__(self) -> None: super().__init__(AddressTypeSpec()) @@ -47,9 +50,9 @@ def get(self) -> Expr: def set( self, value: Union[ - Sequence[T], - StaticArray[T, N], - ComputedValue[StaticArray[T, N]], + Sequence[Byte], + StaticArray[Byte, Literal[32]], + ComputedValue[StaticArray[Byte, Literal[32]]], "Address", str, bytes, @@ -59,17 +62,20 @@ def set( match value: case ComputedValue(): - if value.produced_type_spec() == AddressTypeSpec(): + pts = value.produced_type_spec() + if pts == AddressTypeSpec() or pts == StaticArrayTypeSpec( + ByteTypeSpec(), AddressLength.Bytes.value + ): return value.store_into(self) raise TealInputError( - f"Got ComputedValue with type spec {value.produced_type_spec()}, expected AddressTypeSpec" + f"Got ComputedValue with type spec {pts}, expected AddressTypeSpec or StaticArray[Byte, Literal[32]]" ) case BaseType(): if ( value.type_spec() == AddressTypeSpec() or value.type_spec() - == StaticArrayTypeSpec(ByteTypeSpec(), ADDRESS_LENGTH_BYTES) + == StaticArrayTypeSpec(ByteTypeSpec(), AddressLength.Bytes.value) ): return self.stored_value.store(value.stored_value.load()) @@ -77,24 +83,37 @@ def set( f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" ) case str(): - if len(value) == ADDRESS_LENGTH_STR: + if len(value) == AddressLength.String.value: return self.stored_value.store(Addr(value)) raise TealInputError( - f"Got string with length {len(value)}, expected {ADDRESS_LENGTH_STR}" + f"Got string with length {len(value)}, expected {AddressLength.String.value}" ) case bytes(): - if len(value) == ADDRESS_LENGTH_BYTES: + if len(value) == AddressLength.Bytes.value: return self.stored_value.store(Bytes(value)) raise TealInputError( - f"Got bytes with length {len(value)}, expected {ADDRESS_LENGTH_BYTES}" + f"Got bytes with length {len(value)}, expected {AddressLength.Bytes.value}" ) case Expr(): return self.stored_value.store(value) case CollectionSequence(): + # TODO: mypy thinks its possible for the type of `value` here to be + # Union[Sequence[Byte], str, bytes] even though we check above? + if isinstance(value, str) or isinstance(value, bytes): + return return super().set(value) + # TODO: wdyt? something like this maybe should be in utils? or just manually update the args each time? + # from inspect import signature + # from typing import get_args + # sig = signature(self.set) + # expected = ", ".join([repr(a) for a in get_args(sig.parameters['value'].annotation)]) + # raise TealInputError( + # f"Got {type(value)}, expected {expected}" + # ) + raise TealInputError( - f"Got {type(value)}, expected StaticArray, ComputedValue, String, str, bytes, Expr" + f"Got {type(value)}, expected Sequence, StaticArray, ComputedValue, Address, str, bytes, Expr" ) diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index a8ceb3681..21019fb11 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -1,9 +1,7 @@ import pytest import pyteal as pt from pyteal import abi -from pyteal.ast.abi.address import ADDRESS_LENGTH_BYTES -from pyteal.ast.global_ import Global, GlobalField from pyteal.ast.abi.type_test import ContainerType from pyteal.ast.abi.util import substringForDecoding @@ -20,7 +18,7 @@ def test_AddressTypeSpec_is_dynamic(): def test_AddressTypeSpec_byte_length_static(): - assert (abi.AddressTypeSpec()).byte_length_static() == ADDRESS_LENGTH_BYTES + assert (abi.AddressTypeSpec()).byte_length_static() == abi.AddressLength.Bytes.value def test_AddressTypeSpec_new_instance(): @@ -52,7 +50,7 @@ def test_Address_encode(): def test_Address_decode(): - address = bytes([0] * ADDRESS_LENGTH_BYTES) + address = bytes([0] * abi.AddressLength.Bytes.value) encoded = pt.Bytes(address) for startIndex in (None, pt.Int(0)): @@ -107,32 +105,31 @@ def test_Address_get(): def test_Address_set_StaticArray(): - sa = abi.StaticArray( - abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), ADDRESS_LENGTH_BYTES) + value_to_set = abi.StaticArray( + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), abi.AddressLength.Bytes.value) ) - for value_to_set in (sa,): - value = abi.Address() - expr = value.set(value_to_set) - assert expr.type_of() == pt.TealType.none - assert not expr.has_return() + value = abi.Address() + expr = value.set(value_to_set) + assert expr.type_of() == pt.TealType.none + assert not expr.has_return() - expected = pt.TealSimpleBlock( - [ - pt.TealOp(None, pt.Op.load, value_to_set.stored_value.slot), - pt.TealOp(None, pt.Op.store, value.stored_value.slot), - ] - ) + expected = pt.TealSimpleBlock( + [ + pt.TealOp(None, pt.Op.load, value_to_set.stored_value.slot), + pt.TealOp(None, pt.Op.store, value.stored_value.slot), + ] + ) - actual, _ = expr.__teal__(options) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected - with pytest.raises(pt.TealInputError): - bogus = abi.StaticArray(abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 10)) - value.set(bogus) + with pytest.raises(pt.TealInputError): + bogus = abi.StaticArray(abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 10)) + value.set(bogus) def test_Address_set_str(): @@ -184,9 +181,12 @@ def test_Address_set_bytes(): with pytest.raises(pt.TealInputError): value.set(bytes(16)) + with pytest.raises(pt.TealInputError): + value.set(16) + def test_Address_set_expr(): - for value_to_set in [Global(GlobalField.zero_address)]: + for value_to_set in [pt.Global(pt.GlobalField.zero_address)]: value = abi.Address() expr = value.set(value_to_set) assert expr.type_of() == pt.TealType.none diff --git a/pyteal/ast/abi/array_dynamic.py b/pyteal/ast/abi/array_dynamic.py index 3eb97c7ad..99d0ffffe 100644 --- a/pyteal/ast/abi/array_dynamic.py +++ b/pyteal/ast/abi/array_dynamic.py @@ -20,7 +20,7 @@ class DynamicArrayTypeSpec(ArrayTypeSpec[T]): def new_instance(self) -> "DynamicArray[T]": - return DynamicArray(DynamicArrayTypeSpec(self.value_type_spec())) + return DynamicArray(self) def is_length_dynamic(self) -> bool: return True @@ -47,7 +47,7 @@ def __str__(self) -> str: class DynamicArray(Array[T]): """The class that represents ABI dynamic array type.""" - def __init__(self, array_type_spec: DynamicArrayTypeSpec) -> None: + def __init__(self, array_type_spec: DynamicArrayTypeSpec[T]) -> None: super().__init__(array_type_spec) def type_spec(self) -> DynamicArrayTypeSpec[T]: diff --git a/pyteal/ast/abi/array_static.py b/pyteal/ast/abi/array_static.py index c4a153dca..6f8ba4468 100644 --- a/pyteal/ast/abi/array_static.py +++ b/pyteal/ast/abi/array_static.py @@ -28,9 +28,7 @@ def __init__(self, value_type_spec: TypeSpec, array_length: int) -> None: self.array_length: Final = array_length def new_instance(self) -> "StaticArray[T, N]": - return StaticArray( - StaticArrayTypeSpec(self.value_type_spec(), self.length_static()) - ) + return StaticArray(self) def length_static(self) -> int: """Get the size of this static array type. @@ -74,7 +72,7 @@ def __str__(self) -> str: class StaticArray(Array[T], Generic[T, N]): """The class that represents ABI static array type.""" - def __init__(self, array_type_spec: StaticArrayTypeSpec) -> None: + def __init__(self, array_type_spec: StaticArrayTypeSpec[T, N]) -> None: super().__init__(array_type_spec) def type_spec(self) -> StaticArrayTypeSpec[T, N]: diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index de7fac7e3..3824e701e 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -218,7 +218,7 @@ def length_static(self) -> int: return len(self.value_specs) def new_instance(self) -> "Tuple": - return Tuple(TupleTypeSpec(*self.value_specs)) + return Tuple(self) def is_dynamic(self) -> bool: return any(type_spec.is_dynamic() for type_spec in self.value_type_specs()) @@ -336,6 +336,13 @@ def store_into(self, output: BaseType) -> Expr: # sizes. +def _tuple_raise_arg_mismatch(expected: int, typespec: TupleTypeSpec): + if len(typespec.value_specs) != expected: + raise TealInputError( + f"Expected TupleTypeSpec with {expected} elements, Got {len(typespec.value_specs)}" + ) + + class Tuple0(Tuple): """A Tuple with 0 values.""" @@ -351,8 +358,9 @@ def __init__(self) -> None: class Tuple1(Tuple, Generic[T1]): """A Tuple with 1 value.""" - def __init__(self, value1_type_spec: TypeSpec) -> None: - super().__init__(TupleTypeSpec(value1_type_spec)) + def __init__(self, value_type_spec: TupleTypeSpec) -> None: + _tuple_raise_arg_mismatch(1, value_type_spec) + super().__init__(value_type_spec) Tuple1.__module__ = "pyteal" @@ -363,8 +371,9 @@ def __init__(self, value1_type_spec: TypeSpec) -> None: class Tuple2(Tuple, Generic[T1, T2]): """A Tuple with 2 values.""" - def __init__(self, value1_type_spec: TypeSpec, value2_type_spec: TypeSpec) -> None: - super().__init__(TupleTypeSpec(value1_type_spec, value2_type_spec)) + def __init__(self, value_type_spec: TupleTypeSpec) -> None: + _tuple_raise_arg_mismatch(2, value_type_spec) + super().__init__(value_type_spec) Tuple2.__module__ = "pyteal" @@ -377,13 +386,10 @@ class Tuple3(Tuple, Generic[T1, T2, T3]): def __init__( self, - value1_type_spec: TypeSpec, - value2_type_spec: TypeSpec, - value3_type_spec: TypeSpec, + value_type_spec: TupleTypeSpec, ) -> None: - super().__init__( - TupleTypeSpec(value1_type_spec, value2_type_spec, value3_type_spec) - ) + _tuple_raise_arg_mismatch(3, value_type_spec) + super().__init__(value_type_spec) Tuple3.__module__ = "pyteal" @@ -396,16 +402,10 @@ class Tuple4(Tuple, Generic[T1, T2, T3, T4]): def __init__( self, - value1_type_spec: TypeSpec, - value2_type_spec: TypeSpec, - value3_type_spec: TypeSpec, - value4_type_spec: TypeSpec, + value_type_spec: TupleTypeSpec, ) -> None: - super().__init__( - TupleTypeSpec( - value1_type_spec, value2_type_spec, value3_type_spec, value4_type_spec - ) - ) + _tuple_raise_arg_mismatch(4, value_type_spec) + super().__init__(value_type_spec) Tuple4.__module__ = "pyteal" @@ -418,21 +418,10 @@ class Tuple5(Tuple, Generic[T1, T2, T3, T4, T5]): def __init__( self, - value1_type_spec: TypeSpec, - value2_type_spec: TypeSpec, - value3_type_spec: TypeSpec, - value4_type_spec: TypeSpec, - value5_type_spec: TypeSpec, + value_type_spec: TupleTypeSpec, ) -> None: - super().__init__( - TupleTypeSpec( - value1_type_spec, - value2_type_spec, - value3_type_spec, - value4_type_spec, - value5_type_spec, - ) - ) + _tuple_raise_arg_mismatch(5, value_type_spec) + super().__init__(value_type_spec) Tuple5.__module__ = "pyteal" From dcf504b0fd95575ca2b554e090897de5584c93f4 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 4 May 2022 13:41:40 -0400 Subject: [PATCH 26/26] use IntEnum, replace hardcoded 32 with enum value --- pyteal/ast/abi/address.py | 46 +++++++++++----------------------- pyteal/ast/abi/address_test.py | 6 ++--- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 4bcaa6387..db21b1adc 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -1,5 +1,5 @@ -from enum import Enum -from typing import Union, Sequence, Literal +from enum import IntEnum +from typing import Union, Sequence, Literal, cast from collections.abc import Sequence as CollectionSequence from pyteal.errors import TealInputError @@ -12,7 +12,7 @@ from pyteal.ast.expr import Expr -class AddressLength(Enum): +class AddressLength(IntEnum): String = 58 Bytes = 32 @@ -22,7 +22,7 @@ class AddressLength(Enum): class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec(), AddressLength.Bytes.value) + super().__init__(ByteTypeSpec(), AddressLength.Bytes) def new_instance(self) -> "Address": return Address() @@ -37,7 +37,7 @@ def __eq__(self, other: object) -> bool: AddressTypeSpec.__module__ = "pyteal" -class Address(StaticArray[Byte, Literal[32]]): +class Address(StaticArray[Byte, Literal[AddressLength.Bytes]]): def __init__(self) -> None: super().__init__(AddressTypeSpec()) @@ -51,8 +51,8 @@ def set( self, value: Union[ Sequence[Byte], - StaticArray[Byte, Literal[32]], - ComputedValue[StaticArray[Byte, Literal[32]]], + StaticArray[Byte, Literal[AddressLength.Bytes]], + ComputedValue[StaticArray[Byte, Literal[AddressLength.Bytes]]], "Address", str, bytes, @@ -64,18 +64,18 @@ def set( case ComputedValue(): pts = value.produced_type_spec() if pts == AddressTypeSpec() or pts == StaticArrayTypeSpec( - ByteTypeSpec(), AddressLength.Bytes.value + ByteTypeSpec(), AddressLength.Bytes ): return value.store_into(self) raise TealInputError( - f"Got ComputedValue with type spec {pts}, expected AddressTypeSpec or StaticArray[Byte, Literal[32]]" + f"Got ComputedValue with type spec {pts}, expected AddressTypeSpec or StaticArray[Byte, Literal[AddressLength.Bytes]]" ) case BaseType(): if ( value.type_spec() == AddressTypeSpec() or value.type_spec() - == StaticArrayTypeSpec(ByteTypeSpec(), AddressLength.Bytes.value) + == StaticArrayTypeSpec(ByteTypeSpec(), AddressLength.Bytes) ): return self.stored_value.store(value.stored_value.load()) @@ -83,34 +83,18 @@ def set( f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec" ) case str(): - if len(value) == AddressLength.String.value: - return self.stored_value.store(Addr(value)) - raise TealInputError( - f"Got string with length {len(value)}, expected {AddressLength.String.value}" - ) + # Addr throws if value is invalid address + return self.stored_value.store(Addr(value)) case bytes(): - if len(value) == AddressLength.Bytes.value: + if len(value) == AddressLength.Bytes: return self.stored_value.store(Bytes(value)) raise TealInputError( - f"Got bytes with length {len(value)}, expected {AddressLength.Bytes.value}" + f"Got bytes with length {len(value)}, expected {AddressLength.Bytes}" ) case Expr(): return self.stored_value.store(value) case CollectionSequence(): - # TODO: mypy thinks its possible for the type of `value` here to be - # Union[Sequence[Byte], str, bytes] even though we check above? - if isinstance(value, str) or isinstance(value, bytes): - return - return super().set(value) - - # TODO: wdyt? something like this maybe should be in utils? or just manually update the args each time? - # from inspect import signature - # from typing import get_args - # sig = signature(self.set) - # expected = ", ".join([repr(a) for a in get_args(sig.parameters['value'].annotation)]) - # raise TealInputError( - # f"Got {type(value)}, expected {expected}" - # ) + return super().set(cast(Sequence[Byte], value)) raise TealInputError( f"Got {type(value)}, expected Sequence, StaticArray, ComputedValue, Address, str, bytes, Expr" diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 21019fb11..c3ce39510 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -18,7 +18,7 @@ def test_AddressTypeSpec_is_dynamic(): def test_AddressTypeSpec_byte_length_static(): - assert (abi.AddressTypeSpec()).byte_length_static() == abi.AddressLength.Bytes.value + assert (abi.AddressTypeSpec()).byte_length_static() == abi.AddressLength.Bytes def test_AddressTypeSpec_new_instance(): @@ -50,7 +50,7 @@ def test_Address_encode(): def test_Address_decode(): - address = bytes([0] * abi.AddressLength.Bytes.value) + address = bytes([0] * abi.AddressLength.Bytes) encoded = pt.Bytes(address) for startIndex in (None, pt.Int(0)): @@ -106,7 +106,7 @@ def test_Address_get(): def test_Address_set_StaticArray(): value_to_set = abi.StaticArray( - abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), abi.AddressLength.Bytes.value) + abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), abi.AddressLength.Bytes) ) value = abi.Address() expr = value.set(value_to_set)