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

Add set on address and string, change array init to accept typespec #289

Merged
merged 31 commits into from
May 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
efbe1e5
adding string and address types
barnjamin Apr 14, 2022
7db17ca
fix type spec, added get and thoughts on set
barnjamin Apr 14, 2022
4055424
move address to its own file, starting to add tests
barnjamin Apr 15, 2022
12a36a6
adding tests for string
barnjamin Apr 15, 2022
8a54f14
Adding slicing for a string
barnjamin Apr 15, 2022
af7967d
removed unused tests
barnjamin Apr 15, 2022
0dcc792
merged up
barnjamin Apr 15, 2022
41b10c3
merged up
barnjamin Apr 15, 2022
3e7fdfd
cr
barnjamin Apr 15, 2022
6f4a540
Merge branch 'strings' into slicey
barnjamin Apr 16, 2022
bc95806
add util function to prefix strlen
barnjamin Apr 16, 2022
9c3b5a3
fix set on address
barnjamin Apr 17, 2022
190ec16
merge feature/abi
barnjamin Apr 21, 2022
9a33018
cr from original strings pr
barnjamin Apr 21, 2022
7964aa4
maybe fix linter stuff
barnjamin Apr 21, 2022
86199ef
pass type spec to init for tuple, static array, and dynamic array
barnjamin Apr 22, 2022
c0e921e
merged feature/abi
barnjamin Apr 22, 2022
c5815a4
address mypy issues
barnjamin Apr 22, 2022
2cf6699
fmt
barnjamin Apr 22, 2022
50848df
adding tests for string set
barnjamin Apr 27, 2022
044a76c
starting to try slice testing
barnjamin Apr 27, 2022
e0f3147
remove slicing for now
barnjamin Apr 27, 2022
85cbdad
adding tests for address set
barnjamin Apr 27, 2022
50068c9
make linter happy
barnjamin Apr 27, 2022
5baead2
Merge branch 'feature/abi' into slicey
barnjamin Apr 28, 2022
c5b13c4
adding handlers for other cases, adding testing for new handlers
barnjamin Apr 28, 2022
d4b6ecf
change typespec checking
barnjamin Apr 28, 2022
58ccd76
use match for typechecks
barnjamin Apr 29, 2022
a8f8d29
adding sequence check from collections package
barnjamin Apr 29, 2022
017a7ff
partial cr
barnjamin May 2, 2022
dcf504b
use IntEnum, replace hardcoded 32 with enum value
barnjamin May 4, 2022
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
8 changes: 6 additions & 2 deletions pyteal/ast/abi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
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,
AddressLength,
)
from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue
from pyteal.ast.abi.bool import BoolTypeSpec, Bool
from pyteal.ast.abi.uint import (
Expand Down Expand Up @@ -38,7 +42,7 @@
"StringTypeSpec",
"Address",
"AddressTypeSpec",
"ADDRESS_LENGTH",
"AddressLength",
"TypeSpec",
"BaseType",
"ComputedValue",
Expand Down
81 changes: 76 additions & 5 deletions pyteal/ast/abi/address.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,104 @@
from enum import IntEnum
from typing import Union, Sequence, Literal, cast
from collections.abc import Sequence as CollectionSequence

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.abi.uint import ByteTypeSpec, Byte
from pyteal.ast.expr import Expr

ADDRESS_LENGTH = 32

class AddressLength(IntEnum):
String = 58
Bytes = 32


AddressLength.__module__ = "pyteal"


class AddressTypeSpec(StaticArrayTypeSpec):
def __init__(self) -> None:
super().__init__(ByteTypeSpec(), ADDRESS_LENGTH)
super().__init__(ByteTypeSpec(), AddressLength.Bytes)

def new_instance(self) -> "Address":
return Address()

def __str__(self) -> str:
return "address"

def __eq__(self, other: object) -> bool:
return isinstance(other, AddressTypeSpec)


AddressTypeSpec.__module__ = "pyteal"


class Address(StaticArray):
class Address(StaticArray[Byte, Literal[AddressLength.Bytes]]):
def __init__(self) -> None:
super().__init__(AddressTypeSpec(), ADDRESS_LENGTH)
super().__init__(AddressTypeSpec())

def type_spec(self) -> AddressTypeSpec:
return AddressTypeSpec()

def get(self) -> Expr:
return self.stored_value.load()

def set(
self,
value: Union[
Sequence[Byte],
StaticArray[Byte, Literal[AddressLength.Bytes]],
ComputedValue[StaticArray[Byte, Literal[AddressLength.Bytes]]],
"Address",
str,
bytes,
Expr,
],
):

match value:
case ComputedValue():
pts = value.produced_type_spec()
if pts == AddressTypeSpec() or pts == StaticArrayTypeSpec(
ByteTypeSpec(), AddressLength.Bytes
):
return value.store_into(self)

raise TealInputError(
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)
):
return self.stored_value.store(value.stored_value.load())

raise TealInputError(
f"Got {value} with type spec {value.type_spec()}, expected AddressTypeSpec"
)
case str():
# Addr throws if value is invalid address
return self.stored_value.store(Addr(value))
case bytes():
if len(value) == AddressLength.Bytes:
return self.stored_value.store(Bytes(value))
raise TealInputError(
f"Got bytes with length {len(value)}, expected {AddressLength.Bytes}"
)
case Expr():
return self.stored_value.store(value)
case CollectionSequence():
return super().set(cast(Sequence[Byte], value))

raise TealInputError(
f"Got {type(value)}, expected Sequence, StaticArray, ComputedValue, Address, str, bytes, Expr"
)


Address.__module__ = "pyteal"
206 changes: 193 additions & 13 deletions pyteal/ast/abi/address_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import pytest
import pyteal as pt
from pyteal import abi

from pyteal.ast.abi.type_test import ContainerType
from pyteal.ast.abi.util import substringForDecoding


options = pt.CompileOptions(version=5)


Expand All @@ -13,7 +18,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() == abi.AddressLength.Bytes


def test_AddressTypeSpec_new_instance():
Expand All @@ -24,8 +29,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
Expand All @@ -45,37 +50,212 @@ def test_Address_encode():


def test_Address_decode():
from os import urandom
address = bytes([0] * abi.AddressLength.Bytes)
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() == pt.TealType.bytes
assert expr.has_return() is False

expected = pt.TealSimpleBlock(
[pt.TealOp(expr, pt.Op.load, value.stored_value.slot)]
)
actual, _ = expr.__teal__(options)
assert actual == expected


def test_Address_set_StaticArray():
value_to_set = abi.StaticArray(
abi.StaticArrayTypeSpec(abi.ByteTypeSpec(), abi.AddressLength.Bytes)
)
value = abi.Address()
for value_to_set in [urandom(abi.ADDRESS_LENGTH) for x in range(10)]:
expr = value.decode(pt.Bytes(value_to_set))
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)
assert expr.type_of() == pt.TealType.none
assert expr.has_return() is False
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(" " * 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_get():
with pytest.raises(pt.TealInputError):
value.set(16)


def test_Address_set_expr():
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
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()
expr = value.get()
assert expr.type_of() == pt.TealType.bytes
assert expr.has_return() is False
other = abi.Address()
expr = value.set(other)
assert expr.type_of() == pt.TealType.none
assert not expr.has_return()

expected = pt.TealSimpleBlock(
[pt.TealOp(expr, pt.Op.load, value.stored_value.slot)]
[
pt.TealOp(None, pt.Op.load, other.stored_value.slot),
pt.TealOp(None, pt.Op.store, value.stored_value.slot),
]
)

actual, _ = expr.__teal__(options)
assert actual == expected
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)))
4 changes: 2 additions & 2 deletions pyteal/ast/abi/array_base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)))
Loading