From f141687d366e4538a3adae0aaea8473dac4283f9 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Sat, 24 Sep 2022 20:59:41 +0200 Subject: [PATCH 01/12] refactor: lint --- pyteal/ast/maybe.py | 4 ++-- pyteal/ast/multi.py | 8 ++++---- pyteal/ast/multi_test.py | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 43846384d..9d06a593c 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -16,8 +16,8 @@ def __init__( op: Op, type: TealType, *, - immediate_args: List[Union[int, str]] = None, - args: List[Expr] = None + immediate_args: List[Union[int, str]] | None = None, + args: List[Expr] | None = None ): """Create a new MaybeValue. diff --git a/pyteal/ast/multi.py b/pyteal/ast/multi.py index e61af4b4a..1996386a6 100644 --- a/pyteal/ast/multi.py +++ b/pyteal/ast/multi.py @@ -19,8 +19,8 @@ def __init__( op: Op, types: List[TealType], *, - immediate_args: List[Union[int, str]] = None, - args: List[Expr] = None, + immediate_args: List[Union[int, str]] | None = None, + args: List[Expr] | None = None, compile_check: Callable[["CompileOptions"], None] = lambda _: None, ): """Create a new MultiValue. @@ -41,8 +41,8 @@ def __init__( self.output_slots = [ScratchSlot() for _ in self.types] def outputReducer(self, reducer: Callable[..., Expr]) -> Expr: - input = [slot.load(self.types[i]) for i, slot in enumerate(self.output_slots)] - return Seq(self, reducer(*input)) + inputs = [slot.load(self.types[i]) for i, slot in enumerate(self.output_slots)] + return Seq(self, reducer(*inputs)) def __str__(self): ret_str = "(({}".format(self.op) diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index a85cebed7..912988cd2 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -6,6 +6,10 @@ options = pt.CompileOptions() +def TOO_MANY_ARGS(arg_len): + return ValueError("Too many arguments. Expected 0-2, got {}".format(len(arg_len))) + + def __test_single(expr: pt.MultiValue): assert expr.output_slots[0] != expr.output_slots[1] @@ -54,6 +58,8 @@ def __test_single_conditional( arg_2, after_arg_2 = args[1].__teal__(options) after_arg_1.setNextBlock(arg_2) after_arg_2.setNextBlock(expected_call) + else: + raise TOO_MANY_ARGS(len(args)) expected.addIncoming() expected = pt.TealBlock.NormalizeBlocks(expected) @@ -94,6 +100,8 @@ def __test_single_assert(expr: pt.MultiValue, op, args: List[pt.Expr], iargs, re arg_2, after_arg_2 = args[1].__teal__(options) after_arg_1.setNextBlock(arg_2) after_arg_2.setNextBlock(expected_call) + else: + raise TOO_MANY_ARGS(len(args)) expected.addIncoming() expected = pt.TealBlock.NormalizeBlocks(expected) @@ -136,6 +144,8 @@ def __test_single_with_vars( arg_2, after_arg_2 = args[1].__teal__(options) after_arg_1.setNextBlock(arg_2) after_arg_2.setNextBlock(expected_call) + else: + raise TOO_MANY_ARGS(len(args)) expected.addIncoming() expected = pt.TealBlock.NormalizeBlocks(expected) From 4ae19730bbb08ddcf61fc9162fc23f8794323d7c Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Sat, 24 Sep 2022 21:13:12 +0200 Subject: [PATCH 02/12] feat: add operator Addw with test --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 3 ++- pyteal/ast/multi.py | 20 +++++++++++++++++++- pyteal/ast/multi_test.py | 22 ++++++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 4431f2116..aa1c81079 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -37,6 +37,7 @@ __all__ = [ "AccountParam", "AccountParamObject", "Add", + "AddW", "Addr", "And", "App", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 77f66da5a..ec1bbed06 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -156,7 +156,7 @@ ) from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar from pyteal.ast.maybe import MaybeValue -from pyteal.ast.multi import MultiValue +from pyteal.ast.multi import MultiValue, AddW from pyteal.ast.opup import OpUp, OpUpMode from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover from pyteal.ast.router import ( @@ -330,4 +330,5 @@ "EcdsaRecover", "JsonRef", "VrfVerify", + "AddW", ] diff --git a/pyteal/ast/multi.py b/pyteal/ast/multi.py index 1996386a6..3efaced83 100644 --- a/pyteal/ast/multi.py +++ b/pyteal/ast/multi.py @@ -1,6 +1,6 @@ from typing import Callable, List, Union, TYPE_CHECKING -from pyteal.types import TealType +from pyteal.types import TealType, require_type from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr from pyteal.ast.leafexpr import LeafExpr @@ -81,3 +81,21 @@ def type_of(self): MultiValue.__module__ = "pyteal" + + +# Binary MultiValue operations +def AddW(adder: Expr, adder_: Expr) -> MultiValue: + """Add two 64-bit integers. + + Produces a MultiValue with two outputs: the sum and the carry bit. + + Args: + adder: Must evaluate to uint64. + adder_: Must evaluate to uint64. + """ + + # Should this be + require_type(adder, TealType.uint64) + require_type(adder_, TealType.uint64) + + return MultiValue(Op.addw, [TealType.uint64, TealType.uint64], args=[adder, adder_]) diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index 912988cd2..8be2fdf41 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -225,3 +225,25 @@ def always_fails(options): with pytest.raises(TestException): program_always_fails.__teal__(options) + + +def test_addw(): + expr = pt.AddW(pt.Int(1), pt.Int(2)) + __test_single(expr) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(expr, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.int, 2), + pt.TealOp(expr, pt.Op.addw), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected From d2ed2a184dbfac700c462beb57f25bb62d5185bc Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Sat, 24 Sep 2022 21:48:29 +0200 Subject: [PATCH 03/12] ref: split multiexpr to a new file --- pyteal/ast/__init__.py | 3 ++- pyteal/ast/multi.py | 18 ------------------ pyteal/ast/multi_test.py | 22 ---------------------- pyteal/ast/multiexpr.py | 29 +++++++++++++++++++++++++++++ pyteal/ast/multiexpr_test.py | 27 +++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 pyteal/ast/multiexpr.py create mode 100644 pyteal/ast/multiexpr_test.py diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index ec1bbed06..bcba8eb05 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -156,7 +156,8 @@ ) from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar from pyteal.ast.maybe import MaybeValue -from pyteal.ast.multi import MultiValue, AddW +from pyteal.ast.multi import MultiValue +from pyteal.ast.multiexpr import AddW from pyteal.ast.opup import OpUp, OpUpMode from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover from pyteal.ast.router import ( diff --git a/pyteal/ast/multi.py b/pyteal/ast/multi.py index 3efaced83..f1ea16341 100644 --- a/pyteal/ast/multi.py +++ b/pyteal/ast/multi.py @@ -81,21 +81,3 @@ def type_of(self): MultiValue.__module__ = "pyteal" - - -# Binary MultiValue operations -def AddW(adder: Expr, adder_: Expr) -> MultiValue: - """Add two 64-bit integers. - - Produces a MultiValue with two outputs: the sum and the carry bit. - - Args: - adder: Must evaluate to uint64. - adder_: Must evaluate to uint64. - """ - - # Should this be - require_type(adder, TealType.uint64) - require_type(adder_, TealType.uint64) - - return MultiValue(Op.addw, [TealType.uint64, TealType.uint64], args=[adder, adder_]) diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index 8be2fdf41..912988cd2 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -225,25 +225,3 @@ def always_fails(options): with pytest.raises(TestException): program_always_fails.__teal__(options) - - -def test_addw(): - expr = pt.AddW(pt.Int(1), pt.Int(2)) - __test_single(expr) - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(expr, pt.Op.int, 1), - pt.TealOp(expr, pt.Op.int, 2), - pt.TealOp(expr, pt.Op.addw), - pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), - pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), - ] - ) - - actual, _ = expr.__teal__(options) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) - - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected diff --git a/pyteal/ast/multiexpr.py b/pyteal/ast/multiexpr.py new file mode 100644 index 000000000..001399b49 --- /dev/null +++ b/pyteal/ast/multiexpr.py @@ -0,0 +1,29 @@ +from typing import TYPE_CHECKING +from pyteal.ast.multi import MultiValue + +from pyteal.types import TealType, require_type +from pyteal.ast.expr import Expr +from pyteal.ir import Op + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +"""Binary MultiValue operations""" + + +def AddW(adder: Expr, adder_: Expr) -> MultiValue: + """Add two 64-bit integers. + + Produces a MultiValue with two outputs: the sum and the carry bit. + + Args: + adder: Must evaluate to uint64. + adder_: Must evaluate to uint64. + """ + + # Should this be + require_type(adder, TealType.uint64) + require_type(adder_, TealType.uint64) + + return MultiValue(Op.addw, [TealType.uint64, TealType.uint64], args=[adder, adder_]) diff --git a/pyteal/ast/multiexpr_test.py b/pyteal/ast/multiexpr_test.py new file mode 100644 index 000000000..3e97843b5 --- /dev/null +++ b/pyteal/ast/multiexpr_test.py @@ -0,0 +1,27 @@ +import pytest +from typing import List + +import pyteal as pt + +options = pt.CompileOptions() + + +def test_addw(): + expr = pt.AddW(pt.Int(1), pt.Int(2)) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(expr, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.int, 2), + pt.TealOp(expr, pt.Op.addw), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected From 5f606591a4db74e338dadd62f2fd40a854677b3f Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Sun, 25 Sep 2022 22:43:13 +0200 Subject: [PATCH 04/12] ref: lint --- pyteal/ast/multi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/multi.py b/pyteal/ast/multi.py index f1ea16341..1996386a6 100644 --- a/pyteal/ast/multi.py +++ b/pyteal/ast/multi.py @@ -1,6 +1,6 @@ from typing import Callable, List, Union, TYPE_CHECKING -from pyteal.types import TealType, require_type +from pyteal.types import TealType from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr from pyteal.ast.leafexpr import LeafExpr From 345a71e3e05c8533004cd9c3049a34f3c9c31343 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Sun, 25 Sep 2022 22:45:55 +0200 Subject: [PATCH 05/12] test: add an invalid test case for addw --- pyteal/ast/multiexpr.py | 21 ++++++++++++++++----- pyteal/ast/multiexpr_test.py | 16 +++++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/multiexpr.py b/pyteal/ast/multiexpr.py index 001399b49..997c43f16 100644 --- a/pyteal/ast/multiexpr.py +++ b/pyteal/ast/multiexpr.py @@ -1,5 +1,6 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from pyteal.ast.multi import MultiValue +from pyteal.errors import verifyProgramVersion from pyteal.types import TealType, require_type from pyteal.ast.expr import Expr @@ -8,14 +9,15 @@ if TYPE_CHECKING: from pyteal.compiler import CompileOptions - """Binary MultiValue operations""" -def AddW(adder: Expr, adder_: Expr) -> MultiValue: +def AddW( + adder: Expr, adder_: Expr, _options: Union["CompileOptions", None] = None +) -> MultiValue: """Add two 64-bit integers. - Produces a MultiValue with two outputs: the sum and the carry bit. + Produces a MultiValue with two outputs: the sum and the carry-bit. Args: adder: Must evaluate to uint64. @@ -26,4 +28,13 @@ def AddW(adder: Expr, adder_: Expr) -> MultiValue: require_type(adder, TealType.uint64) require_type(adder_, TealType.uint64) - return MultiValue(Op.addw, [TealType.uint64, TealType.uint64], args=[adder, adder_]) + return MultiValue( + Op.addw, + [TealType.uint64, TealType.uint64], + args=[adder, adder_], + compile_check=lambda options: verifyProgramVersion( + Op.addw.min_version, + _options.version if _options else options.version, + "Program version too low to use op {}".format(Op.addw), + ), + ) diff --git a/pyteal/ast/multiexpr_test.py b/pyteal/ast/multiexpr_test.py index 3e97843b5..b33aec30e 100644 --- a/pyteal/ast/multiexpr_test.py +++ b/pyteal/ast/multiexpr_test.py @@ -1,9 +1,11 @@ import pytest -from typing import List import pyteal as pt -options = pt.CompileOptions() +avm2Options = pt.CompileOptions(version=2) +avm3Options = pt.CompileOptions(version=3) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) def test_addw(): @@ -19,9 +21,17 @@ def test_addw(): ] ) - actual, _ = expr.__teal__(options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected + + +def test_addw_invalid(): + with pytest.raises(pt.TealTypeError): + pt.AddW(pt.Int(2), pt.Txn.receiver()) + + with pytest.raises(pt.TealTypeError): + pt.AddW(pt.Txn.sender(), pt.Int(2)) From 0a2cdebd6e7a548d0ccae3498e7072eb2a712a67 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Sun, 25 Sep 2022 22:47:25 +0200 Subject: [PATCH 06/12] ref: rename multiexpr to wideexpr --- pyteal/ast/__init__.py | 2 +- pyteal/ast/{multiexpr.py => wideexpr.py} | 0 pyteal/ast/{multiexpr_test.py => wideexpr_test.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename pyteal/ast/{multiexpr.py => wideexpr.py} (100%) rename pyteal/ast/{multiexpr_test.py => wideexpr_test.py} (100%) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index bcba8eb05..abffa0661 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -157,7 +157,7 @@ from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar from pyteal.ast.maybe import MaybeValue from pyteal.ast.multi import MultiValue -from pyteal.ast.multiexpr import AddW +from pyteal.ast.wideexpr import AddW from pyteal.ast.opup import OpUp, OpUpMode from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover from pyteal.ast.router import ( diff --git a/pyteal/ast/multiexpr.py b/pyteal/ast/wideexpr.py similarity index 100% rename from pyteal/ast/multiexpr.py rename to pyteal/ast/wideexpr.py diff --git a/pyteal/ast/multiexpr_test.py b/pyteal/ast/wideexpr_test.py similarity index 100% rename from pyteal/ast/multiexpr_test.py rename to pyteal/ast/wideexpr_test.py From 64ab0399b924c6561d04141b6025d1f9d555716d Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 01:01:51 +0200 Subject: [PATCH 07/12] ref: rewrite `AddW` with `WideExpr` --- pyteal/ast/wideexpr.py | 68 ++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/pyteal/ast/wideexpr.py b/pyteal/ast/wideexpr.py index 997c43f16..5faa4621d 100644 --- a/pyteal/ast/wideexpr.py +++ b/pyteal/ast/wideexpr.py @@ -1,20 +1,58 @@ -from typing import TYPE_CHECKING, Union -from pyteal.ast.multi import MultiValue -from pyteal.errors import verifyProgramVersion +from typing import TYPE_CHECKING, List -from pyteal.types import TealType, require_type from pyteal.ast.expr import Expr +from pyteal.ast.multi import MultiValue +from pyteal.errors import verifyProgramVersion from pyteal.ir import Op +from pyteal.types import TealType, require_type if TYPE_CHECKING: from pyteal.compiler import CompileOptions + +class WideExpr(MultiValue): + """Base class for WideInt Operations + + This type of expression produces WideInt(MultiValue). + """ + + def __init__( + self, + op: Op, + args: List[Expr], + ): + """Create a new WideExpr, whose returned type is always a MultiValue of [TealType.uint64, TealType.uint64]. + + Args: + op: The operation that returns values. + args: Stack arguments for the op. + min_version: The minimum TEAL version required to use this expression. + """ + + super().__init__( + op=op, + types=[TealType.uint64, TealType.uint64], + args=args, + immediate_args=None, + ) + + for arg in args: + require_type(arg, TealType.uint64) + + def __teal__(self, options: "CompileOptions"): + verifyProgramVersion( + self.op.min_version, + options.version, + "Program version too low to use op {}".format(self.op), + ) + + return super().__teal__(options) + + """Binary MultiValue operations""" -def AddW( - adder: Expr, adder_: Expr, _options: Union["CompileOptions", None] = None -) -> MultiValue: +def AddW(adder: Expr, adder_: Expr) -> MultiValue: """Add two 64-bit integers. Produces a MultiValue with two outputs: the sum and the carry-bit. @@ -23,18 +61,4 @@ def AddW( adder: Must evaluate to uint64. adder_: Must evaluate to uint64. """ - - # Should this be - require_type(adder, TealType.uint64) - require_type(adder_, TealType.uint64) - - return MultiValue( - Op.addw, - [TealType.uint64, TealType.uint64], - args=[adder, adder_], - compile_check=lambda options: verifyProgramVersion( - Op.addw.min_version, - _options.version if _options else options.version, - "Program version too low to use op {}".format(Op.addw), - ), - ) + return WideExpr(Op.addw, [adder, adder_]) From a2f5085e62a372597b38518eeff889485fe755e2 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 01:50:08 +0200 Subject: [PATCH 08/12] feat: add `mulw`, `expw`, `divmodw` w/ their tests --- pyteal/__init__.pyi | 3 + pyteal/ast/__init__.py | 5 +- pyteal/ast/wideexpr.py | 57 +++++++++++++++ pyteal/ast/wideexpr_test.py | 136 +++++++++++++++++++++++++++++++++++- 4 files changed, 198 insertions(+), 3 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index aa1c81079..baa71eb8f 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -91,6 +91,7 @@ __all__ = [ "DEFAULT_PROGRAM_VERSION", "DEFAULT_TEAL_VERSION", "Div", + "DivModW", "Divw", "DynamicScratchVar", "EcdsaCurve", @@ -103,6 +104,7 @@ __all__ = [ "Eq", "Err", "Exp", + "ExpW", "Expr", "Extract", "ExtractUint16", @@ -152,6 +154,7 @@ __all__ = [ "Mod", "Mode", "Mul", + "MulW", "MultiValue", "NUM_SLOTS", "NaryExpr", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index abffa0661..62adbf5e0 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -157,7 +157,7 @@ from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar from pyteal.ast.maybe import MaybeValue from pyteal.ast.multi import MultiValue -from pyteal.ast.wideexpr import AddW +from pyteal.ast.wideexpr import AddW, MulW, ExpW, DivModW from pyteal.ast.opup import OpUp, OpUpMode from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover from pyteal.ast.router import ( @@ -332,4 +332,7 @@ "JsonRef", "VrfVerify", "AddW", + "MulW", + "ExpW", + "DivModW", ] diff --git a/pyteal/ast/wideexpr.py b/pyteal/ast/wideexpr.py index 5faa4621d..c5ac4d545 100644 --- a/pyteal/ast/wideexpr.py +++ b/pyteal/ast/wideexpr.py @@ -62,3 +62,60 @@ def AddW(adder: Expr, adder_: Expr) -> MultiValue: adder_: Must evaluate to uint64. """ return WideExpr(Op.addw, [adder, adder_]) + + +def MulW(factor: Expr, factor_: Expr) -> MultiValue: + """Multiply two 64-bit integers. + + Produces a MultiValue with two outputs: the product and the carry-bit. + + Args: + factor: Must evaluate to uint64. + factor_: Must evaluate to uint64. + """ + + return WideExpr(Op.mulw, [factor, factor_]) + + +def ExpW(base: Expr, exponent: Expr) -> MultiValue: + """Raise a 64-bit integer to a power. + + Produces a MultiValue with two outputs: the result and the carry-bit. + + Args: + base: Must evaluate to uint64. + exponent: Must evaluate to uint64. + """ + + return WideExpr(Op.expw, [base, exponent]) + + +def DivModW( + dividendHigh: Expr, dividendLow: Expr, divisorHigh: Expr, divisorLow: Expr +) -> MultiValue: + """Divide two wide-64-bit integers. + + Produces a MultiValue with four outputs: the quotient and its carry-bit, the remainder and its carry-bit. + + Stack: + ..., A: uint64, B: uint64, C: uint64, D: uint64 --> ..., W: uint64, X: uint64, Y: uint64, Z: uint64 + Where W,X = (A,B / C,D); Y,Z = (A,B modulo C,D) + + Example: + All ints should be initialized with Int(). For readability, we didn't use Int() in the example. + DivModW(0, 10, 0, 5) = (0, 2, 0, 0) # 10 / 5 = 2, 10 % 5 = 0 + DivModW(0, 10, 0, 3) = (0, 3, 0, 1) # 10 / 3 = 3, 10 % 3 = 1 + DivModW(5, 14, 0, 5) = (1, 2, 0, 4) # ((5<<64)+14) / 5 = (1<<64)+2, ((5<<64)+14) % 5 = 4 + DivModW(7, 29, 1, 3) = (0, 7, 0, 8) # ((7<<64)+29) / ((1<<64)+3) = 7, ((7<<64)+29) % ((1<<64)+3) = 8 + + Args: + dividendHigh: Must evaluate to uint64. + dividendLow: Must evaluate to uint64. + divisorHigh: Must evaluate to uint64. + divisorLow: Must evaluate to uint64. + """ + + return WideExpr( + Op.divmodw, + [dividendHigh, dividendLow, divisorHigh, divisorLow], + ) diff --git a/pyteal/ast/wideexpr_test.py b/pyteal/ast/wideexpr_test.py index b33aec30e..7b5ca3ed0 100644 --- a/pyteal/ast/wideexpr_test.py +++ b/pyteal/ast/wideexpr_test.py @@ -9,12 +9,13 @@ def test_addw(): + args = [pt.Int(2), pt.Int(3)] expr = pt.AddW(pt.Int(1), pt.Int(2)) expected = pt.TealSimpleBlock( [ - pt.TealOp(expr, pt.Op.int, 1), - pt.TealOp(expr, pt.Op.int, 2), + pt.TealOp(args[0], pt.Op.int, 1), + pt.TealOp(args[1], pt.Op.int, 2), pt.TealOp(expr, pt.Op.addw), pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), @@ -29,9 +30,140 @@ def test_addw(): assert actual == expected +# TODO: test: test_addw_overload() + + def test_addw_invalid(): with pytest.raises(pt.TealTypeError): pt.AddW(pt.Int(2), pt.Txn.receiver()) with pytest.raises(pt.TealTypeError): pt.AddW(pt.Txn.sender(), pt.Int(2)) + + +def test_mulw(): + args = [pt.Int(3), pt.Int(8)] + expr = pt.MulW(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 3), + pt.TealOp(args[1], pt.Op.int, 8), + pt.TealOp(expr, pt.Op.mulw), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + ] + ) + + actual, _ = expr.__teal__(avm2Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +# TODO: test: test_mulw_overload() + + +def test_mulw_invalid(): + with pytest.raises(pt.TealTypeError): + pt.MulW(pt.Int(2), pt.Txn.receiver()) + + with pytest.raises(pt.TealTypeError): + pt.MulW(pt.Txn.sender(), pt.Int(2)) + + +# TODO: ref: def test_divw(): +# TODO: ref: def test_divw_overload(): +# TODO: test: def test_modw_overload(): +# TODO: test: def test_modw_invalid(): + + +def test_expw(): + args = [pt.Int(2), pt.Int(9)] + expr = pt.ExpW(args[0], args[1]) + assert expr.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 2), + pt.TealOp(args[1], pt.Op.int, 9), + pt.TealOp(expr, pt.Op.expw), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + ] + ) + + actual, _ = expr.__teal__(avm4Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +# TODO: test: def test_expw_overload(): + + +def test_expw_invalid(): + with pytest.raises(pt.TealTypeError): + pt.ExpW(pt.Txn.receiver(), pt.Int(2)) + + with pytest.raises(pt.TealTypeError): + pt.ExpW(pt.Int(2), pt.Txn.sender()) + + +def test_expw_invalid_version(): + with pytest.raises(pt.TealInputError): + pt.ExpW(pt.Int(2), pt.Int(2)).__teal__(avm3Options) # needs >=4 + + +# TODO: ref: move test_divw() to here +def test_divw_invalid_version(): + with pytest.raises(pt.TealInputError): + pt.Divw(pt.Int(2), pt.Int(2), pt.Int(2)).__teal__(avm5Options) # needs >=5 + + +def test_divmodw(): + args = [pt.Int(7), pt.Int(29), pt.Int(1), pt.Int(3)] + expr = pt.DivModW(args[0], args[1], args[2], args[3]) + assert expr.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.int, 7), + pt.TealOp(args[1], pt.Op.int, 29), + pt.TealOp(args[2], pt.Op.int, 1), + pt.TealOp(args[3], pt.Op.int, 3), + pt.TealOp(expr, pt.Op.divmodw), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + ] + ) + + actual, _ = expr.__teal__(avm5Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +def test_divmodw_invalid(): + with pytest.raises(pt.TealTypeError): + pt.DivModW(pt.Int(2), pt.Txn.receiver(), pt.Int(2), pt.Int(2)) + + with pytest.raises(pt.TealTypeError): + pt.DivModW(pt.Int(2), pt.Int(2), pt.Txn.sender(), pt.Int(2)) + + with pytest.raises(pt.TealTypeError): + pt.DivModW(pt.Int(2), pt.Int(2), pt.Int(2), pt.Txn.sender()) + + +def test_divmodw_invalid_version(): + with pytest.raises(pt.TealInputError): + pt.DivModW(pt.Int(2), pt.Int(2), pt.Int(2), pt.Int(2)).__teal__( + avm3Options + ) # needs >=4 From ba4fb9f6e0fb0b8b24eea2237af3251d5d0ab314 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 01:54:28 +0200 Subject: [PATCH 09/12] ref: rename Divw to `DivW` --- pyteal/__init__.pyi | 2 +- pyteal/ast/__init__.py | 4 ++-- pyteal/ast/ternaryexpr.py | 2 +- pyteal/ast/ternaryexpr_test.py | 8 ++++---- pyteal/ast/wideexpr_test.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index baa71eb8f..e4bf54ada 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -92,7 +92,7 @@ __all__ = [ "DEFAULT_TEAL_VERSION", "Div", "DivModW", - "Divw", + "DivW", "DynamicScratchVar", "EcdsaCurve", "EcdsaDecompress", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 62adbf5e0..91e233e96 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -109,7 +109,7 @@ # ternary ops from pyteal.ast.ternaryexpr import ( - Divw, + DivW, Ed25519Verify, Ed25519Verify_Bare, SetBit, @@ -241,7 +241,7 @@ "Div", "Mod", "Exp", - "Divw", + "DivW", "BitwiseAnd", "BitwiseOr", "BitwiseXor", diff --git a/pyteal/ast/ternaryexpr.py b/pyteal/ast/ternaryexpr.py index a8a526cff..3ad219ed1 100644 --- a/pyteal/ast/ternaryexpr.py +++ b/pyteal/ast/ternaryexpr.py @@ -146,7 +146,7 @@ def SetByte(value: Expr, index: Expr, newByteValue: Expr) -> TernaryExpr: ) -def Divw(hi: Expr, lo: Expr, y: Expr) -> TernaryExpr: +def DivW(hi: Expr, lo: Expr, y: Expr) -> TernaryExpr: """ Performs wide division by interpreting `hi` and `lo` as a uint128 value. diff --git a/pyteal/ast/ternaryexpr_test.py b/pyteal/ast/ternaryexpr_test.py index 0530ec6b6..93e2fe970 100644 --- a/pyteal/ast/ternaryexpr_test.py +++ b/pyteal/ast/ternaryexpr_test.py @@ -176,7 +176,7 @@ def test_set_byte_invalid(): def test_divw(): args = [pt.Int(0), pt.Int(90), pt.Int(30)] - expr = pt.Divw(args[0], args[1], args[2]) + expr = pt.DivW(args[0], args[1], args[2]) assert expr.type_of() == pt.TealType.uint64 expected = pt.TealSimpleBlock( @@ -197,10 +197,10 @@ def test_divw(): def test_divw_invalid(): with pytest.raises(pt.TealTypeError): - pt.Divw(pt.Bytes("10"), pt.Int(0), pt.Int(1)) + pt.DivW(pt.Bytes("10"), pt.Int(0), pt.Int(1)) with pytest.raises(pt.TealTypeError): - pt.Divw(pt.Int(10), pt.Bytes("0"), pt.Int(1)) + pt.DivW(pt.Int(10), pt.Bytes("0"), pt.Int(1)) with pytest.raises(pt.TealTypeError): - pt.Divw(pt.Int(10), pt.Int(0), pt.Bytes("1")) + pt.DivW(pt.Int(10), pt.Int(0), pt.Bytes("1")) diff --git a/pyteal/ast/wideexpr_test.py b/pyteal/ast/wideexpr_test.py index 7b5ca3ed0..a6637b00d 100644 --- a/pyteal/ast/wideexpr_test.py +++ b/pyteal/ast/wideexpr_test.py @@ -123,7 +123,7 @@ def test_expw_invalid_version(): # TODO: ref: move test_divw() to here def test_divw_invalid_version(): with pytest.raises(pt.TealInputError): - pt.Divw(pt.Int(2), pt.Int(2), pt.Int(2)).__teal__(avm5Options) # needs >=5 + pt.DivW(pt.Int(2), pt.Int(2), pt.Int(2)).__teal__(avm5Options) # needs >=5 def test_divmodw(): From fae92235eece7096cad04f176634c8d7cd124ed9 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 01:55:44 +0200 Subject: [PATCH 10/12] ref: move test_divw_invalid_version --- pyteal/ast/ternaryexpr_test.py | 5 +++++ pyteal/ast/wideexpr_test.py | 8 -------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/ternaryexpr_test.py b/pyteal/ast/ternaryexpr_test.py index 93e2fe970..025b8905f 100644 --- a/pyteal/ast/ternaryexpr_test.py +++ b/pyteal/ast/ternaryexpr_test.py @@ -204,3 +204,8 @@ def test_divw_invalid(): with pytest.raises(pt.TealTypeError): pt.DivW(pt.Int(10), pt.Int(0), pt.Bytes("1")) + + +def test_divw_invalid_version(): + with pytest.raises(pt.TealInputError): + pt.DivW(pt.Int(2), pt.Int(2), pt.Int(2)).__teal__(avm5Options) # needs >=6 diff --git a/pyteal/ast/wideexpr_test.py b/pyteal/ast/wideexpr_test.py index a6637b00d..7acc9571b 100644 --- a/pyteal/ast/wideexpr_test.py +++ b/pyteal/ast/wideexpr_test.py @@ -75,8 +75,6 @@ def test_mulw_invalid(): pt.MulW(pt.Txn.sender(), pt.Int(2)) -# TODO: ref: def test_divw(): -# TODO: ref: def test_divw_overload(): # TODO: test: def test_modw_overload(): # TODO: test: def test_modw_invalid(): @@ -120,12 +118,6 @@ def test_expw_invalid_version(): pt.ExpW(pt.Int(2), pt.Int(2)).__teal__(avm3Options) # needs >=4 -# TODO: ref: move test_divw() to here -def test_divw_invalid_version(): - with pytest.raises(pt.TealInputError): - pt.DivW(pt.Int(2), pt.Int(2), pt.Int(2)).__teal__(avm5Options) # needs >=5 - - def test_divmodw(): args = [pt.Int(7), pt.Int(29), pt.Int(1), pt.Int(3)] expr = pt.DivModW(args[0], args[1], args[2], args[3]) From d04976d781e3fc8610866f346caf83a3f6c0c1d3 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 02:37:25 +0200 Subject: [PATCH 11/12] fix: hotfix --- pyteal/ast/wideexpr.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyteal/ast/wideexpr.py b/pyteal/ast/wideexpr.py index c5ac4d545..7b73d0080 100644 --- a/pyteal/ast/wideexpr.py +++ b/pyteal/ast/wideexpr.py @@ -49,6 +49,8 @@ def __teal__(self, options: "CompileOptions"): return super().__teal__(options) +WideExpr.__module__ = "pyteal" + """Binary MultiValue operations""" From edf2842afaa726fc0e60ff3731d23cc87698a263 Mon Sep 17 00:00:00 2001 From: Pablo LION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 22:41:27 +0200 Subject: [PATCH 12/12] Update pyteal/ast/multi_test.py Co-authored-by: Zeph Grunschlag --- pyteal/ast/multi_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index 912988cd2..7e511e667 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -7,7 +7,7 @@ def TOO_MANY_ARGS(arg_len): - return ValueError("Too many arguments. Expected 0-2, got {}".format(len(arg_len))) + return ValueError("Too many arguments. Expected 0-2, got {}".format(arg_len)) def __test_single(expr: pt.MultiValue):