Skip to content

Refactor Uint8.set to make data size a first class concept #238

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

Closed
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions pyteal/ast/abi/sized.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from dataclasses import dataclass
from typing import Union
from .. import Expr
from ..assert_ import Assert
from pyteal.ast import Int, Seq, ScratchVar


@dataclass(frozen=True)
class SizedExpr:
"""Provides a wrapper class to tag `Expr`s with known ABI data sizes.

Knowing the data size removes the need for a runtime `Assert` confirming it fits into the ScratchVar."""

underlying: Expr

@staticmethod
def store_into(
u: Union[Expr, "SizedExpr"], s: ScratchVar, supported_bit_size: int
) -> Expr:
return (
s.store(u.underlying)
if isinstance(u, SizedExpr)
else Seq(s.store(u), Assert(s.load() < Int(2 ** supported_bit_size)))
)
45 changes: 22 additions & 23 deletions pyteal/ast/abi/uint.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Union, cast
from abc import abstractmethod

from .sized import SizedExpr
from ...types import TealType
from ...errors import TealInputError
from ..expr import Expr
Expand Down Expand Up @@ -56,36 +57,34 @@ def new_instance(self) -> "Uint8":
return Uint8()

def set(self, value: Union[int, Expr, "Uint8", "Byte"]) -> Expr:
checked = False
if type(value) is int:
if value >= 2 ** self.bit_size:
raise TealInputError(
"Value exceeds {} maximum: {}".format(
self.__class__.__name__, value
def resolve_storage_value() -> Union[Expr, SizedExpr]:
if type(value) is int:
if value >= 2 ** self.bit_size:
raise TealInputError(
"Value exceeds {} maximum: {}".format(
self.__class__.__name__, value
)
)
)
value = Int(value)
checked = True

if type(value) is Uint8 or type(value) is Byte:
value = value.get()
checked = True
return SizedExpr(Int(value))

if checked:
return self.stored_value.store(cast(Expr, value))
elif isinstance(value, Uint8):
return SizedExpr(value.get())
elif isinstance(value, Expr):
# There's insufficient evidence to attempt resolution.
return cast(Expr, value)
else:
raise TypeError(f"Unsupported type: {type(value)}")

return Seq(
self.stored_value.store(cast(Expr, value)),
Assert(self.stored_value.load() < Int(2 ** self.bit_size)),
)
r = resolve_storage_value()
return SizedExpr.store_into(r, self.stored_value, self.bit_size)

def decode(
self,
encoded: Expr,
*,
startIndex: Expr = None,
endIndex: Expr = None,
length: Expr = None
length: Expr = None,
) -> Expr:
if startIndex is None:
startIndex = Int(0)
Expand Down Expand Up @@ -147,7 +146,7 @@ def decode(
*,
startIndex: Expr = None,
endIndex: Expr = None,
length: Expr = None
length: Expr = None,
) -> Expr:
if startIndex is None:
startIndex = Int(0)
Expand Down Expand Up @@ -195,7 +194,7 @@ def decode(
*,
startIndex: Expr = None,
endIndex: Expr = None,
length: Expr = None
length: Expr = None,
) -> Expr:
if startIndex is None:
startIndex = Int(0)
Expand Down Expand Up @@ -228,7 +227,7 @@ def decode(
*,
startIndex: Expr = None,
endIndex: Expr = None,
length: Expr = None
length: Expr = None,
) -> Expr:
if startIndex is None:
if endIndex is None and length is None:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ mypy==0.931
pytest
pytest-timeout
py-algorand-sdk
types-dataclasses
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewers: Without installing types-dataclasses, Python 3.6 lacks @dataclass support.

Here's the error prior to installing the dependency: https://github.com/algorand/pyteal/runs/5471035129?check_suite_focus=true.