Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ABI Strings #278

Merged
merged 8 commits into from
Apr 21, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pyteal/ast/abi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .string import String, StringTypeSpec
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
from .address import AddressTypeSpec, Address
from .type import TypeSpec, BaseType, ComputedValue
from .bool import BoolTypeSpec, Bool
from .uint import (
Expand Down Expand Up @@ -32,6 +34,10 @@
from .method_return import MethodReturn

__all__ = [
"String",
"StringTypeSpec",
"Address",
"AddressTypeSpec",
"TypeSpec",
"BaseType",
"ComputedValue",
Expand Down
33 changes: 33 additions & 0 deletions pyteal/ast/abi/address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from .array_static import StaticArray, StaticArrayTypeSpec
from .uint import ByteTypeSpec
from ..expr import Expr

address_length = 32
barnjamin marked this conversation as resolved.
Show resolved Hide resolved


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"
78 changes: 78 additions & 0 deletions pyteal/ast/abi/address_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from .type_test import ContainerType

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()
barnjamin marked this conversation as resolved.
Show resolved Hide resolved


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()

for otherType in (
abi.ByteTypeSpec,
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), 31),
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
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()
tzaffi marked this conversation as resolved.
Show resolved Hide resolved

expected = TealSimpleBlock([TealOp(expr, 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(32) for x in range(10)]:
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
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_get():
value = abi.Address()
expr = value.get()
assert expr.type_of() == TealType.bytes
assert not expr.has_return()
tzaffi marked this conversation as resolved.
Show resolved Hide resolved

expected = TealSimpleBlock([TealOp(expr, Op.load, value.stored_value.slot)])
actual, _ = expr.__teal__(options)
assert actual == expected
37 changes: 37 additions & 0 deletions pyteal/ast/abi/string.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from .array_dynamic import DynamicArray, DynamicArrayTypeSpec
from .uint import ByteTypeSpec, Uint16TypeSpec
from .util import substringForDecoding

from ..int import Int
from ..expr import Expr


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()

def get(self) -> Expr:
return substringForDecoding(
self.stored_value.load(),
startIndex=Int(Uint16TypeSpec().byte_length_static()),
)
barnjamin marked this conversation as resolved.
Show resolved Hide resolved


String.__module__ = "pyteal"
79 changes: 79 additions & 0 deletions pyteal/ast/abi/string_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from ... import *

options = CompileOptions(version=5)


def test_StringTypeSpec_str():
assert str(abi.StringTypeSpec()) == "string"


def test_StringTypeSpec_is_dynamic():
assert (abi.StringTypeSpec()).is_dynamic()
tzaffi marked this conversation as resolved.
Show resolved Hide resolved


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()),
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
):
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()
tzaffi marked this conversation as resolved.
Show resolved Hide resolved

expected = TealSimpleBlock([TealOp(expr, 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)]:
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
expr = value.decode(Bytes(value_to_set))

assert expr.type_of() == TealType.none
assert not expr.has_return()
tzaffi marked this conversation as resolved.
Show resolved Hide resolved

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():
value = abi.String()
expr = value.get()
assert expr.type_of() == TealType.bytes
assert not expr.has_return()
tzaffi marked this conversation as resolved.
Show resolved Hide resolved

expected = TealSimpleBlock(
[TealOp(expr, Op.load, value.stored_value.slot), TealOp(None, Op.extract, 2, 0)]
)
actual, _ = expr.__teal__(options)
jasonpaulos marked this conversation as resolved.
Show resolved Hide resolved
actual.addIncoming()
actual = TealBlock.NormalizeBlocks(actual)

with TealComponent.Context.ignoreExprEquality():
assert actual == expected
12 changes: 12 additions & 0 deletions pyteal/ast/abi/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec:
Tuple4,
Tuple5,
)
from .string import StringTypeSpec, String
from .address import AddressTypeSpec, Address

origin = get_origin(annotation)
if origin is None:
Expand Down Expand Up @@ -144,6 +146,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(
Expand Down