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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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 4b96a4c415fa1690acdbae761f328c82a109c291 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 7 Mar 2022 15:37:42 -0500 Subject: [PATCH 11/31] update on u128 add/mul arithmetic --- pyteal/ast/widemath.py | 161 +++++++++++++++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 32 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index b70c4c2e8..3d7a76c43 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -1,5 +1,6 @@ from typing import List, Tuple, TYPE_CHECKING - +from pyteal.ast.multi import MultiValue +from pyteal.ir.tealblock import TealBlock from pyteal.types import TealType from pyteal.errors import TealInternalError, TealCompileError from pyteal.ir import TealOp, Op, TealSimpleBlock @@ -9,6 +10,32 @@ from pyteal.compiler import CompileOptions +def multU128U64( + expr: Expr, factor: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + facSrt, facEnd = factor.__teal__(options) + # stack is [..., A, B, C], where C is current factor + # need to pop all A,B,C from stack and push X,Y, where X and Y are: + # X * 2**64 + Y = (A * 2**64 + B) * C + # <=> X * 2**64 + Y = A * C * 2**64 + B * C + # <=> X = A * C + highword(B * C) + # Y = lowword(B * C) + multiply = TealSimpleBlock( + [ + TealOp(expr, Op.uncover, 2), # stack: [..., B, C, A] + TealOp(expr, Op.dig, 1), # stack: [..., B, C, A, C] + TealOp(expr, Op.mul), # stack: [..., B, C, A*C] + TealOp(expr, Op.cover, 2), # stack: [..., A*C, B, C] + TealOp(expr, Op.mulw), # stack: [..., A*C, highword(B*C), lowword(B*C)] + TealOp(expr, Op.cover, 2), # stack: [..., lowword(B*C), A*C, highword(B*C)] + TealOp(expr, Op.add), # stack: [..., lowword(B*C), A*C+highword(B*C)] + TealOp(expr, Op.swap), # stack: [..., A*C+highword(B*C), lowword(B*C)] + ] + ) + facEnd.setNextBlock(multiply) + return facSrt, multiply + + def multiplyFactors( expr: Expr, factors: List[Expr], options: "CompileOptions" ) -> Tuple[TealSimpleBlock, TealSimpleBlock]: @@ -38,42 +65,70 @@ def multiplyFactors( end = multiplyFirst2 for factor in factors[2:]: - facXStart, facXEnd = factor.__teal__(options) - end.setNextBlock(facXStart) - - # stack is [..., A, B, C], where C is current factor - # need to pop all A,B,C from stack and push X,Y, where X and Y are: - # X * 2**64 + Y = (A * 2**64 + B) * C - # <=> X * 2**64 + Y = A * C * 2**64 + B * C - # <=> X = A * C + highword(B * C) - # Y = lowword(B * C) - multiply = TealSimpleBlock( - [ - TealOp(expr, Op.uncover, 2), # stack: [..., B, C, A] - TealOp(expr, Op.dig, 1), # stack: [..., B, C, A, C] - TealOp(expr, Op.mul), # stack: [..., B, C, A*C] - TealOp(expr, Op.cover, 2), # stack: [..., A*C, B, C] - TealOp( - expr, Op.mulw - ), # stack: [..., A*C, highword(B*C), lowword(B*C)] - TealOp( - expr, Op.cover, 2 - ), # stack: [..., lowword(B*C), A*C, highword(B*C)] - TealOp( - expr, Op.add - ), # stack: [..., lowword(B*C), A*C+highword(B*C)] - TealOp( - expr, Op.swap - ), # stack: [..., A*C+highword(B*C), lowword(B*C)] - ] - ) + facSrt, multed = multU128U64(expr, factor, options) + end.setNextBlock(facSrt) + end = multed + + return start, end + + +def addU128U64( + expr: Expr, term: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + termSrt, termEnd = term.__teal__(options) + addition = TealSimpleBlock( + [ + TealOp(expr, Op.addw), + TealOp(expr, Op.cover, 2), + TealOp(expr, Op.add), + TealOp(expr, Op.uncover, 1), + ] + ) + termEnd.setNextBlock(addition) + return termSrt, addition + + +def addTerms( + expr: Expr, terms: List[Expr], options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + if len(terms) == 0: + raise TealInternalError("Received 0 terms") + + start = TealSimpleBlock([]) + + term0srt, term0end = terms[0].__teal__(options) + + if len(terms) == 1: + highword = TealSimpleBlock([TealOp(expr, Op.int, 0)]) + + start.setNextBlock(highword) + highword.setNextBlock(term0srt) + end = term0end + else: + start.setNextBlock(term0srt) + + term1srt, term1end = terms[1].__teal__(options) + term0end.setNextBlock(term1srt) + addFirst2 = TealSimpleBlock([TealOp(expr, Op.addw)]) + term1end.setNextBlock(addFirst2) + end = addFirst2 - facXEnd.setNextBlock(multiply) - end = multiply + for term in terms[2:]: + termSrt, added = addU128U64(expr, term, options) + end.setNextBlock(termSrt) + end = added return start, end +""" +def substractU128U64( + expr: Expr, term: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + pass +""" + + class WideRatio(Expr): """A class used to calculate expressions of the form :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)` @@ -157,3 +212,45 @@ def has_return(self): WideRatio.__module__ = "pyteal" + + +class WideUint128(MultiValue): + @staticmethod + def addw(*terms: Expr): + if len(terms) < 2: + pass + return WideUint128( + Op.addw, [TealType.uint64, TealType.uint64], args=list(terms) + ) + + @staticmethod + def mulw(*factors: Expr): + if len(factors) < 2: + pass + return WideUint128( + Op.addw, [TealType.uint64, TealType.uint64], args=list(factors) + ) + + @staticmethod + def expw(base: Expr, _pow: Expr): + return WideUint128( + Op.expw, [TealType.uint64, TealType.uint64], args=[base, _pow] + ) + + def __init__(self, op: Op, types: List[TealType], *, args: List[Expr]): + super().__init__(op, types, args=args) + + def __add__(self, other: Expr): + return super().__add__(other) + + def __sub__(self, other: Expr): + return super().__sub__(other) + + def __divmod__(self, other: "WideUint128"): + pass + + def __truediv__(self, other: Expr): + return super().__truediv__(other) + + def __teal__(self, options: "CompileOptions"): + return super().__teal__(options) From 0bebacbce53ddbb8a3d967d3f30d8f13a1e3d60b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 9 Mar 2022 13:45:44 -0500 Subject: [PATCH 12/31] push what I have local --- pyteal/ast/widemath.py | 201 ++++++++++++++++++++++++++++++++++------- 1 file changed, 167 insertions(+), 34 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 3d7a76c43..34ee0420a 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -1,3 +1,4 @@ +from abc import ABCMeta, abstractmethod from typing import List, Tuple, TYPE_CHECKING from pyteal.ast.multi import MultiValue from pyteal.ir.tealblock import TealBlock @@ -22,14 +23,22 @@ def multU128U64( # Y = lowword(B * C) multiply = TealSimpleBlock( [ - TealOp(expr, Op.uncover, 2), # stack: [..., B, C, A] - TealOp(expr, Op.dig, 1), # stack: [..., B, C, A, C] - TealOp(expr, Op.mul), # stack: [..., B, C, A*C] - TealOp(expr, Op.cover, 2), # stack: [..., A*C, B, C] - TealOp(expr, Op.mulw), # stack: [..., A*C, highword(B*C), lowword(B*C)] - TealOp(expr, Op.cover, 2), # stack: [..., lowword(B*C), A*C, highword(B*C)] - TealOp(expr, Op.add), # stack: [..., lowword(B*C), A*C+highword(B*C)] - TealOp(expr, Op.swap), # stack: [..., A*C+highword(B*C), lowword(B*C)] + # stack: [..., B, C, A] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, C, A, C] + TealOp(expr, Op.dig, 1), + # stack: [..., B, C, A*C] + TealOp(expr, Op.mul), + # stack: [..., A*C, B, C] + TealOp(expr, Op.cover, 2), + # stack: [..., A*C, highword(B*C), lowword(B*C)] + TealOp(expr, Op.mulw), + # stack: [..., lowword(B*C), A*C, highword(B*C)] + TealOp(expr, Op.cover, 2), + # stack: [..., lowword(B*C), A*C+highword(B*C)] + TealOp(expr, Op.add), + # stack: [..., A*C+highword(B*C), lowword(B*C)] + TealOp(expr, Op.swap), ] ) facEnd.setNextBlock(multiply) @@ -42,6 +51,9 @@ def multiplyFactors( if len(factors) == 0: raise TealInternalError("Received 0 factors") + for factor in factors: + require_type(factor, TealType.uint64) + start = TealSimpleBlock([]) fac0Start, fac0End = factors[0].__teal__(options) @@ -76,11 +88,21 @@ def addU128U64( expr: Expr, term: Expr, options: "CompileOptions" ) -> Tuple[TealBlock, TealSimpleBlock]: termSrt, termEnd = term.__teal__(options) + # stack is [..., A, B, C], where C is current term + # need to pop all A, B, C from stack and push X, Y, where X and Y are: + # X * 2 ** 64 + Y = (A * 2 ** 64) + (B + C) + # <=> X * 2 ** 64 + Y = (A + highword(B + C)) * 2 ** 64 + lowword(B + C) + # <=> X = A + highword(B + C) + # Y = lowword(B + C) addition = TealSimpleBlock( [ + # stack: [..., A, highword(B + C), lowword(B + C)] TealOp(expr, Op.addw), + # stack: [..., lowword(B + C), A, highword(B + C)] TealOp(expr, Op.cover, 2), + # stack: [..., lowword(B + C), A + highword(B + C)] TealOp(expr, Op.add), + # stack: [..., A + highword(B + C), lowword(B + C)] TealOp(expr, Op.uncover, 1), ] ) @@ -94,6 +116,9 @@ def addTerms( if len(terms) == 0: raise TealInternalError("Received 0 terms") + for term in terms: + require_type(term, TealType.uint64) + start = TealSimpleBlock([]) term0srt, term0end = terms[0].__teal__(options) @@ -121,12 +146,31 @@ def addTerms( return start, end -""" -def substractU128U64( +def substractU128( expr: Expr, term: Expr, options: "CompileOptions" ) -> Tuple[TealBlock, TealSimpleBlock]: - pass -""" + termSrt, termEnd = term.__teal__(options) + substract = TealSimpleBlock( + [ + TealOp(expr, Op.uncover, 3), + TealOp(expr, Op.uncover, 2), + TealOp(expr, Op.minus), + TealOp(expr, Op.neq), + TealOp(expr, Op.assert_), + TealOp(expr, Op.minus), + ] + ) + termEnd.setNextBlock(substract) + return termSrt, substract + + """ +uncover 3 // [B, C, D, A] +uncover 2 // [B, D, A, C] +- // [B, D, A-C] +! // [B, D, A-C == 0] +assert // [B, D] +- // [B-D], aka [X] + """ class WideRatio(Expr): @@ -214,43 +258,132 @@ def has_return(self): WideRatio.__module__ = "pyteal" -class WideUint128(MultiValue): +class WideUint128(LeafExpr, metaclass=ABCMeta): @staticmethod - def addw(*terms: Expr): + def sumW(*terms: Expr): if len(terms) < 2: - pass - return WideUint128( - Op.addw, [TealType.uint64, TealType.uint64], args=list(terms) - ) + raise TealInternalError("received term number less than 2") + + for term in terms: + require_type(term, TealType.uint64) + + class WideUint128Sum(MultiValue, WideUint128): + def __init__(self, *args: Expr): + WideUint128.__init__(self, *args) + MultiValue.__init__( + self, Op.addw, [TealType.uint64, TealType.uint64], args=list(args) + ) + self.terms = list(args) + + def __teal__(self, options: "CompileOptions"): + return addTerms(self, self.terms, options) + + def __str__(self) -> str: + return MultiValue.__str__(self) + + return WideUint128Sum(*terms) @staticmethod - def mulw(*factors: Expr): + def prodW(*factors: Expr): if len(factors) < 2: - pass - return WideUint128( - Op.addw, [TealType.uint64, TealType.uint64], args=list(factors) - ) + raise TealInternalError("received factor number less than 2") + + for factor in factors: + require_type(factor, TealType.uint64) + + class WideUint128Prod(MultiValue, WideUint128): + def __init__(self, *args: Expr): + WideUint128.__init__(self, *args) + MultiValue.__init__( + self, Op.mulw, [TealType.uint64, TealType.uint64], args=list(args) + ) + self.factors = list(args) + + def __teal__(self, options: "CompileOptions"): + return multiplyFactors(self, self.factors, options) + + def __str__(self) -> str: + return MultiValue.__str__(self) + + return WideUint128Prod(*factors) @staticmethod def expw(base: Expr, _pow: Expr): - return WideUint128( - Op.expw, [TealType.uint64, TealType.uint64], args=[base, _pow] - ) - - def __init__(self, op: Op, types: List[TealType], *, args: List[Expr]): - super().__init__(op, types, args=args) + require_type(base, TealType.uint64) + require_type(_pow, TealType.uint64) + + class WideUint128Exp(MultiValue, WideUint128): + def __init__(self, *args: Expr): + WideUint128.__init__(self, *args) + MultiValue.__init__( + self, Op.expw, [TealType.uint64, TealType.uint64], args=list(args) + ) + self.base = args[0] + self.power = args[1] + + def __teal__(self, options: "CompileOptions"): + return TealBlock.FromOp( + options, TealOp(self, Op.expw), self.base, self.power + ) + + def __str__(self) -> str: + return MultiValue.__str__(self) + + return WideUint128Exp(base, _pow) + + @abstractmethod + def __init__(self, *args: Expr): + pass def __add__(self, other: Expr): - return super().__add__(other) + if isinstance(other, WideUint128): + return self.add128w(other) + else: + require_type(other, TealType.uint64) + return self.add(other) + + def add128w(self, other: "WideUint128"): + pass + + def add(self, other: Expr): + pass def __sub__(self, other: Expr): - return super().__sub__(other) + if isinstance(other, WideUint128): + return self.minus128w(other) + else: + require_type(other, TealType.uint64) + return self.minus(other) + + def minus128w(self, other: "WideUint128"): + pass + + def minus(self, other: Expr): + pass + + def __div__(self, other: Expr): + pass + + def div128w(self, other: "WideUint128"): + if isinstance(other, WideUint128): + return self.div128w(other) + else: + require_type(other, TealType.uint64) + return self.div(other) + + def div(self, other: Expr): + # returns u64 + pass def __divmod__(self, other: "WideUint128"): pass - def __truediv__(self, other: Expr): - return super().__truediv__(other) + def __mod__(self, other: "WideUint128"): + pass + @abstractmethod def __teal__(self, options: "CompileOptions"): - return super().__teal__(options) + pass + + def type_of(self) -> TealType: + return TealType.none From 80b1765f31bc9138a66347fc2b1d87e7950012b8 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 9 Mar 2022 14:58:32 -0500 Subject: [PATCH 13/31] update 128b minus --- pyteal/ast/widemath.py | 104 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 34ee0420a..e9a502021 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -146,7 +146,7 @@ def addTerms( return start, end -def substractU128( +def substractU128toU64( expr: Expr, term: Expr, options: "CompileOptions" ) -> Tuple[TealBlock, TealSimpleBlock]: termSrt, termEnd = term.__teal__(options) @@ -173,6 +173,106 @@ def substractU128( """ +def __substrctU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + # stack: [..., A, B, C], where A * 2 ** 64 + B is 128 bit uint, C is uint64 + substractPrep = TealSimpleBlock( + [ + # stack: [..., A, B, C, B, C] + TealOp(expr, Op.dup2), + # stack: [..., A, B, C, B >= C] + TealOp(expr, Op.gt), + ] + ) + substractCond = TealConditionalBlock([]) + substractTrueBlock = TealSimpleBlock( + [ + # stack: [..., A, B - C] + TealOp(expr, Op.minus) + ] + ) + + substractFalseBlock = TealSimpleBlock( + [ + # stack: [..., B, C, A] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, C, A, A] + TealOp(expr, Op.dup), + # stack: [..., B, C, A, A, 1] + TealOp(expr, Op.int, 1), + # stack: [..., B, C, A, A >= 1], + TealOp(expr, Op.gt), + # stack: [..., B, C, A] + TealOp(expr, Op.assert_), + # stack: [..., B, C, A, 1] + TealOp(expr, Op.int, 1), + # stack: [..., B, C, A - 1] + TealOp(expr, Op.minus), + # stack: [..., A - 1, B, C] + TealOp(expr, Op.cover, 2), + # stack: [..., A - 1, C, B] + TealOp(expr, Op.cover, 1), + # stack: [..., A - 1, C - B] + TealOp(expr, Op.minus), + # stack: [..., A - 1, 2^64 - 1 - (C - B)] + TealOp(expr, Op.bitwise_not), + # stack: [..., A - 1, 2^64 - (C - B), 1] + TealOp(expr, Op.int, 1), + # stack: [..., A - 1, 2^64 - (C - B)] + TealOp(expr, Op.add), + ] + ) + substractPrep.setNextBlock(substractCond) + substractCond.setTrueBlock(substractTrueBlock) + substractCond.setFalseBlock(substractFalseBlock) + + end = TealSimpleBlock([]) + substractTrueBlock.setNextBlock(end) + substractFalseBlock.setNextBlock(end) + return substractPrep, end + + +def substractU128U64( + expr: Expr, rhsTerm: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + termSrt, termEnd = rhsTerm.__teal__(options) + subsSrt, subsEnd = __substrctU128U64(expr) + termEnd.setNextBlock(subsSrt) + return termSrt, subsEnd + + +def substractU128( + expr: Expr, lhsTerm: Expr, rhsTerm: Expr, options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + start = TealSimpleBlock([]) + lhsSrt, lhsEnd = lhsTerm.__teal__(options) + rhsSrt, rhsEnd = rhsTerm.__teal__(options) + start.setNextBlock(lhsSrt) + lhsEnd.setNextBlock(rhsSrt) + # stack: [..., A, B, C, D] + highwordPrep = TealSimpleBlock( + [ + # stack: [..., B, C, D, A] + TealOp(expr, Op.uncover, 3), + # stack: [..., B, D, A, C] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, D, A, C, A, C] + TealOp(expr, Op.dup2), + # stack: [..., B, D, A, C, A >= C] + TealOp(expr, Op.gt), + # stack: [..., B, D, A, C] + TealOp(expr, Op.assert_), + # stack: [..., B, D, A - C] + TealOp(expr, Op.minus), + # stack: [..., A - C, B, D] + TealOp(expr, Op.cover, 2), + ] + ) + rhsEnd.setNextBlock(highwordPrep) + subsSrt, subsEnd = __substrctU128U64(expr) + highwordPrep.setNextBlock(subsSrt) + return start, subsEnd + + class WideRatio(Expr): """A class used to calculate expressions of the form :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)` @@ -361,7 +461,7 @@ def minus128w(self, other: "WideUint128"): def minus(self, other: Expr): pass - def __div__(self, other: Expr): + def __truediv__(self, other: Expr): pass def div128w(self, other: "WideUint128"): From 496f8d5ff0ea713640f009f3b288b8db7fcbe428 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 9 Mar 2022 15:09:08 -0500 Subject: [PATCH 14/31] minor --- pyteal/ast/widemath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index e9a502021..3d093cdda 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -215,7 +215,7 @@ def __substrctU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: TealOp(expr, Op.minus), # stack: [..., A - 1, 2^64 - 1 - (C - B)] TealOp(expr, Op.bitwise_not), - # stack: [..., A - 1, 2^64 - (C - B), 1] + # stack: [..., A - 1, 2^64 - 1 - (C - B), 1] TealOp(expr, Op.int, 1), # stack: [..., A - 1, 2^64 - (C - B)] TealOp(expr, Op.add), From 13933466d927862c7bc5ba0cf6a1581d01bc70be Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 9 Mar 2022 16:24:24 -0500 Subject: [PATCH 15/31] per Jason comments --- pyteal/ast/widemath.py | 152 ++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 72 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 3d093cdda..521d44d08 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -359,78 +359,6 @@ def has_return(self): class WideUint128(LeafExpr, metaclass=ABCMeta): - @staticmethod - def sumW(*terms: Expr): - if len(terms) < 2: - raise TealInternalError("received term number less than 2") - - for term in terms: - require_type(term, TealType.uint64) - - class WideUint128Sum(MultiValue, WideUint128): - def __init__(self, *args: Expr): - WideUint128.__init__(self, *args) - MultiValue.__init__( - self, Op.addw, [TealType.uint64, TealType.uint64], args=list(args) - ) - self.terms = list(args) - - def __teal__(self, options: "CompileOptions"): - return addTerms(self, self.terms, options) - - def __str__(self) -> str: - return MultiValue.__str__(self) - - return WideUint128Sum(*terms) - - @staticmethod - def prodW(*factors: Expr): - if len(factors) < 2: - raise TealInternalError("received factor number less than 2") - - for factor in factors: - require_type(factor, TealType.uint64) - - class WideUint128Prod(MultiValue, WideUint128): - def __init__(self, *args: Expr): - WideUint128.__init__(self, *args) - MultiValue.__init__( - self, Op.mulw, [TealType.uint64, TealType.uint64], args=list(args) - ) - self.factors = list(args) - - def __teal__(self, options: "CompileOptions"): - return multiplyFactors(self, self.factors, options) - - def __str__(self) -> str: - return MultiValue.__str__(self) - - return WideUint128Prod(*factors) - - @staticmethod - def expw(base: Expr, _pow: Expr): - require_type(base, TealType.uint64) - require_type(_pow, TealType.uint64) - - class WideUint128Exp(MultiValue, WideUint128): - def __init__(self, *args: Expr): - WideUint128.__init__(self, *args) - MultiValue.__init__( - self, Op.expw, [TealType.uint64, TealType.uint64], args=list(args) - ) - self.base = args[0] - self.power = args[1] - - def __teal__(self, options: "CompileOptions"): - return TealBlock.FromOp( - options, TealOp(self, Op.expw), self.base, self.power - ) - - def __str__(self) -> str: - return MultiValue.__str__(self) - - return WideUint128Exp(base, _pow) - @abstractmethod def __init__(self, *args: Expr): pass @@ -487,3 +415,83 @@ def __teal__(self, options: "CompileOptions"): def type_of(self) -> TealType: return TealType.none + + +# (A + B - C) * D +# sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Seq(Assert(Not(high)), low)) # alias as .to64Bits() +# sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Concat(Itob(high), Itob(low))) # alias as .toBinary() + +# (A + B) - (C * D) +# sumW(A, B).minus(mulW(C, D)).to64Bits() + + +def sumW(*terms: Expr): + if len(terms) < 2: + raise TealInternalError("received term number less than 2") + + for term in terms: + require_type(term, TealType.uint64) + + class WideUint128Sum(MultiValue, WideUint128): + def __init__(self, *args: Expr): + WideUint128.__init__(self, *args) + MultiValue.__init__( + self, Op.addw, [TealType.uint64, TealType.uint64], args=list(args) + ) + self.terms = list(args) + + def __teal__(self, options: "CompileOptions"): + return addTerms(self, self.terms, options) + + def __str__(self) -> str: + return MultiValue.__str__(self) + + return WideUint128Sum(*terms) + + +def prodW(*factors: Expr): + if len(factors) < 2: + raise TealInternalError("received factor number less than 2") + + for factor in factors: + require_type(factor, TealType.uint64) + + class WideUint128Prod(MultiValue, WideUint128): + def __init__(self, *args: Expr): + WideUint128.__init__(self, *args) + MultiValue.__init__( + self, Op.mulw, [TealType.uint64, TealType.uint64], args=list(args) + ) + self.factors = list(args) + + def __teal__(self, options: "CompileOptions"): + return multiplyFactors(self, self.factors, options) + + def __str__(self) -> str: + return MultiValue.__str__(self) + + return WideUint128Prod(*factors) + + +def expW(base: Expr, _pow: Expr): + require_type(base, TealType.uint64) + require_type(_pow, TealType.uint64) + + class WideUint128Exp(MultiValue, WideUint128): + def __init__(self, *args: Expr): + WideUint128.__init__(self, *args) + MultiValue.__init__( + self, Op.expw, [TealType.uint64, TealType.uint64], args=list(args) + ) + self.base = args[0] + self.power = args[1] + + def __teal__(self, options: "CompileOptions"): + return TealBlock.FromOp( + options, TealOp(self, Op.expw), self.base, self.power + ) + + def __str__(self) -> str: + return MultiValue.__str__(self) + + return WideUint128Exp(base, _pow) From f8fe8afeaeb63d0a94c9c667a99d74bff6144888 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 10 Mar 2022 13:40:53 -0500 Subject: [PATCH 16/31] update full arith support --- pyteal/ast/widemath.py | 634 +++++++++++++++++++++++++++++++++-------- 1 file changed, 514 insertions(+), 120 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 521d44d08..4f962abeb 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -11,79 +11,6 @@ from pyteal.compiler import CompileOptions -def multU128U64( - expr: Expr, factor: Expr, options: "CompileOptions" -) -> Tuple[TealBlock, TealSimpleBlock]: - facSrt, facEnd = factor.__teal__(options) - # stack is [..., A, B, C], where C is current factor - # need to pop all A,B,C from stack and push X,Y, where X and Y are: - # X * 2**64 + Y = (A * 2**64 + B) * C - # <=> X * 2**64 + Y = A * C * 2**64 + B * C - # <=> X = A * C + highword(B * C) - # Y = lowword(B * C) - multiply = TealSimpleBlock( - [ - # stack: [..., B, C, A] - TealOp(expr, Op.uncover, 2), - # stack: [..., B, C, A, C] - TealOp(expr, Op.dig, 1), - # stack: [..., B, C, A*C] - TealOp(expr, Op.mul), - # stack: [..., A*C, B, C] - TealOp(expr, Op.cover, 2), - # stack: [..., A*C, highword(B*C), lowword(B*C)] - TealOp(expr, Op.mulw), - # stack: [..., lowword(B*C), A*C, highword(B*C)] - TealOp(expr, Op.cover, 2), - # stack: [..., lowword(B*C), A*C+highword(B*C)] - TealOp(expr, Op.add), - # stack: [..., A*C+highword(B*C), lowword(B*C)] - TealOp(expr, Op.swap), - ] - ) - facEnd.setNextBlock(multiply) - return facSrt, multiply - - -def multiplyFactors( - expr: Expr, factors: List[Expr], options: "CompileOptions" -) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - if len(factors) == 0: - raise TealInternalError("Received 0 factors") - - for factor in factors: - require_type(factor, TealType.uint64) - - start = TealSimpleBlock([]) - - fac0Start, fac0End = factors[0].__teal__(options) - - if len(factors) == 1: - # need to use 0 as high word - highword = TealSimpleBlock([TealOp(expr, Op.int, 0)]) - - start.setNextBlock(highword) - highword.setNextBlock(fac0Start) - - end = fac0End - else: - start.setNextBlock(fac0Start) - - fac1Start, fac1End = factors[1].__teal__(options) - fac0End.setNextBlock(fac1Start) - - multiplyFirst2 = TealSimpleBlock([TealOp(expr, Op.mulw)]) - fac1End.setNextBlock(multiplyFirst2) - - end = multiplyFirst2 - for factor in factors[2:]: - facSrt, multed = multU128U64(expr, factor, options) - end.setNextBlock(facSrt) - end = multed - - return start, end - - def addU128U64( expr: Expr, term: Expr, options: "CompileOptions" ) -> Tuple[TealBlock, TealSimpleBlock]: @@ -146,31 +73,35 @@ def addTerms( return start, end -def substractU128toU64( +def addU128( expr: Expr, term: Expr, options: "CompileOptions" -) -> Tuple[TealBlock, TealSimpleBlock]: +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + start = TealSimpleBlock([]) termSrt, termEnd = term.__teal__(options) - substract = TealSimpleBlock( + # stack: [..., A, B, C, D] + addition = TealSimpleBlock( [ - TealOp(expr, Op.uncover, 3), + # stack: [..., A, C, D, B] TealOp(expr, Op.uncover, 2), - TealOp(expr, Op.minus), - TealOp(expr, Op.neq), - TealOp(expr, Op.assert_), - TealOp(expr, Op.minus), + # stack: [..., A, C, B, D] + TealOp(expr, Op.swap), + # stack: [..., A, C, highword(B + D), lowword(B + D)] + TealOp(expr, Op.addw), + # stack: [..., lowword(B + D), A, C, highword(B + D)] + TealOp(expr, Op.cover, 3), + # stack: [..., lowword(B + D), highword(B + D), A, C] + TealOp(expr, Op.cover, 2), + # stack: [..., lowword(B + D), highword(B + D), A + C] + TealOp(expr, Op.add), + # stack: [..., lowword(B + D), highword(B + D) + A + C] + TealOp(expr, Op.add), + # stack: [..., highword(B + D) + A + C, lowword(B + D)] + TealOp(expr, Op.swap), ] ) - termEnd.setNextBlock(substract) - return termSrt, substract - - """ -uncover 3 // [B, C, D, A] -uncover 2 // [B, D, A, C] -- // [B, D, A-C] -! // [B, D, A-C == 0] -assert // [B, D] -- // [B-D], aka [X] - """ + start.setNextBlock(termSrt) + termEnd.setNextBlock(addition) + return start, addition def __substrctU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: @@ -241,13 +172,12 @@ def substractU128U64( def substractU128( - expr: Expr, lhsTerm: Expr, rhsTerm: Expr, options: "CompileOptions" + expr: Expr, rhsTerm: Expr, options: "CompileOptions" ) -> Tuple[TealSimpleBlock, TealSimpleBlock]: start = TealSimpleBlock([]) - lhsSrt, lhsEnd = lhsTerm.__teal__(options) rhsSrt, rhsEnd = rhsTerm.__teal__(options) - start.setNextBlock(lhsSrt) - lhsEnd.setNextBlock(rhsSrt) + start.setNextBlock(rhsSrt) + rhsEnd.setNextBlock(rhsSrt) # stack: [..., A, B, C, D] highwordPrep = TealSimpleBlock( [ @@ -273,6 +203,158 @@ def substractU128( return start, subsEnd +def __multU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + srt = TealSimpleBlock([]) + # stack: [..., A, B, C] + multiply = TealSimpleBlock( + [ + # stack: [..., B, C, A] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, C, A, C] + TealOp(expr, Op.dig, 1), + # stack: [..., B, C, A*C] + TealOp(expr, Op.mul), + # stack: [..., A*C, B, C] + TealOp(expr, Op.cover, 2), + # stack: [..., A*C, highword(B*C), lowword(B*C)] + TealOp(expr, Op.mulw), + # stack: [..., lowword(B*C), A*C, highword(B*C)] + TealOp(expr, Op.cover, 2), + # stack: [..., lowword(B*C), A*C+highword(B*C)] + TealOp(expr, Op.add), + # stack: [..., A*C+highword(B*C), lowword(B*C)] + TealOp(expr, Op.swap), + ] + ) + end = TealSimpleBlock([]) + srt.setNextBlock(multiply) + multiply.setNextBlock(end) + return srt, end + + +def multU128U64( + expr: Expr, factor: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + facSrt, facEnd = factor.__teal__(options) + # stack is [..., A, B, C], where C is current factor + # need to pop all A,B,C from stack and push X,Y, where X and Y are: + # X * 2**64 + Y = (A * 2**64 + B) * C + # <=> X * 2**64 + Y = A * C * 2**64 + B * C + # <=> X = A * C + highword(B * C) + # Y = lowword(B * C) + multSrt, multEnd = __multU128U64(expr) + facEnd.setNextBlock(multSrt) + return facSrt, multEnd + + +def multiplyFactors( + expr: Expr, factors: List[Expr], options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + if len(factors) == 0: + raise TealInternalError("Received 0 factors") + + for factor in factors: + require_type(factor, TealType.uint64) + + start = TealSimpleBlock([]) + + fac0Start, fac0End = factors[0].__teal__(options) + + if len(factors) == 1: + # need to use 0 as high word + highword = TealSimpleBlock([TealOp(expr, Op.int, 0)]) + + start.setNextBlock(highword) + highword.setNextBlock(fac0Start) + + end = fac0End + else: + start.setNextBlock(fac0Start) + + fac1Start, fac1End = factors[1].__teal__(options) + fac0End.setNextBlock(fac1Start) + + multiplyFirst2 = TealSimpleBlock([TealOp(expr, Op.mulw)]) + fac1End.setNextBlock(multiplyFirst2) + + end = multiplyFirst2 + for factor in factors[2:]: + facSrt, multed = multU128U64(expr, factor, options) + end.setNextBlock(facSrt) + end = multed + + return start, end + + +def multU128( + expr: Expr, rhsFactor: Expr, options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + start = TealSimpleBlock([]) + rhsSrt, rhsEnd = rhsFactor.__teal__(options) + start.setNextBlock(rhsSrt) + # stack: [..., A, B, C, D] + multPrep = TealSimpleBlock( + [ + # stack; [..., B, C, D, A] + TealOp(expr, Op.uncover, 3), + # stack: [..., B, D, A, C] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, D, A, C, A, C] + TealOp(expr, Op.dup2), + # stack: [..., B, D, A, C, A * C], if mul overflow, then u128 mult will also overflow + TealOp(expr, Op.mul), + # stack: [..., B, D, A, C, A * C != 0] + TealOp(expr, Op.logic_not), + # stack: [..., B, D, A, C], at least one of A and C is 0 + TealOp(expr, Op.assert_), + # stack: [..., B, D, A, C, C] + TealOp(expr, Op.dup), + # stack: [..., B, D, C, C, A] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, D, C, C + A] + TealOp(expr, Op.add), + # stack: [..., B, D, C, C + A, C + A] + TealOp(expr, Op.dup), + # stack: [..., B, D, C + A, C, C + A] + TealOp(expr, Op.cover, 2), + # stack: [..., B, D, C + A, C == C + A] decide C + A should be swapped to before B or D + TealOp(expr, Op.eq), + ] + ) + rhsEnd.setNextBlock(multPrep) + + multCond = TealConditionalBlock([]) + multPrep.setNextBlock(multCond) + + # stack: [..., B, D, C] + multCondTrue = TealSimpleBlock( + [ + # stack: [..., B, C, D] + TealOp(expr, Op.swap), + # stack: [..., D, B, C] + TealOp(expr, Op.cover, 2), + # stack: [..., C, D, B] + TealOp(expr, Op.cover, 2), + ] + ) + # stack: [..., B, D, A] + multCondFalse = TealSimpleBlock( + [ + # stack: [..., A, B, D] + TealOp(expr, Op.cover, 2) + ] + ) + multCond.setTrueBlock(multCondTrue) + multCond.setFalseBlock(multCondFalse) + + # stack: [..., C, D, B] or [..., A, B, D] + multSrt, multEnd = __multU128U64(expr) + multCondTrue.setNextBlock(multSrt) + multCondFalse.setNextBlock(multSrt) + + return start, multEnd + + class WideRatio(Expr): """A class used to calculate expressions of the form :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)` @@ -365,57 +447,369 @@ def __init__(self, *args: Expr): def __add__(self, other: Expr): if isinstance(other, WideUint128): - return self.add128w(other) + return self.addU128(other) else: require_type(other, TealType.uint64) - return self.add(other) + return self.addU64(other) - def add128w(self, other: "WideUint128"): - pass + def addU128(self, other: "WideUint128"): + if not isinstance(other, WideUint128): + raise TealInputError("expected WideUint128 input for addU128") - def add(self, other: Expr): - pass + class WideUint128AddU128(WideUint128): + def __init__(self, lhs: WideUint128, arg: WideUint128): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + addSrt, addEnd = addU128(self, self.term, options) + lhsEnd.setNextBlock(addSrt) + return lhsSrt, addEnd + + def __str__(self) -> str: + return "(addw {} {})".format(self.lhs, self.term) + + return WideUint128AddU128(self, other) + + def addU64(self, other: Expr): + require_type(other, TealType.uint64) + + class WideUint128AddU64(WideUint128): + def __init__(self, lhs: WideUint128, arg: Expr): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + addSrt, addEnd = addU128U64(self, self.term, options) + lhsEnd.setNextBlock(addSrt) + return lhsSrt, addEnd + + def __str__(self) -> str: + return "(addw {} {})".format(self.lhs, self.term) + + return WideUint128AddU64(self, other) def __sub__(self, other: Expr): if isinstance(other, WideUint128): - return self.minus128w(other) + return self.minusU128(other) else: require_type(other, TealType.uint64) - return self.minus(other) + return self.minusU64(other) - def minus128w(self, other: "WideUint128"): - pass + def minusU128(self, other: "WideUint128"): + if not isinstance(other, WideUint128): + raise TealInputError("expected WideUint128 input for minusU128") - def minus(self, other: Expr): - pass + class WideUint128MinusU128(WideUint128): + def __init__(self, lhs: WideUint128, arg: WideUint128): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs - def __truediv__(self, other: Expr): - pass + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + subSrt, subEnd = substractU128(self, self.term, options) + lhsEnd.setNextBlock(subSrt) + return lhsSrt, subEnd + + def __str__(self) -> str: + return "(minusW {} {})".format(self.lhs, self.term) + + return WideUint128MinusU128(self, other) + + def minusU64(self, other: Expr): + require_type(other, TealType.uint64) + + class WideUint128MinusU64(WideUint128): + def __init__(self, lhs: WideUint128, arg: Expr): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs - def div128w(self, other: "WideUint128"): + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + subSrt, subEnd = substractU128U64(self, self.term, options) + lhsEnd.setNextBlock(subSrt) + return lhsSrt, subEnd + + def __str__(self) -> str: + return "(minusW {} {})".format(self.lhs, self.term) + + return WideUint128MinusU64(self, other) + + def __mul__(self, other): if isinstance(other, WideUint128): - return self.div128w(other) + return self.mulU128(other) else: require_type(other, TealType.uint64) - return self.div(other) + return self.mulU64(other) - def div(self, other: Expr): - # returns u64 - pass + def mulU128(self, other: "WideUint128"): + if not isinstance(other, WideUint128): + raise TealInputError("expected WideUint128 input for mulU128") - def __divmod__(self, other: "WideUint128"): - pass + class WideUint128MulU128(WideUint128): + def __init__(self, lhs: WideUint128, arg: WideUint128): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + mulSrt, mulEnd = multU128(self, self.term, options) + lhsEnd.setNextBlock(mulSrt) + return lhsSrt, mulEnd + + def __str__(self) -> str: + return "(mulw {} {})".format(self.lhs, self.term) + + return WideUint128MulU128(self, other) + + def mulU64(self, other: Expr): + require_type(other, TealType.uint64) + + class WideUint128MulU64(WideUint128): + def __init__(self, lhs: WideUint128, arg: Expr): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + mulSrt, mulEnd = multU128U64(self, self.term, options) + lhsEnd.setNextBlock(mulSrt) + return lhsSrt, mulEnd + + def __str__(self) -> str: + return "(mulw {} {})".format(self.lhs, self.term) + + return WideUint128MulU64(self, other) + + def __truediv__(self, other: Expr): + if isinstance(other, WideUint128): + return self.divU128(other) + else: + require_type(other, TealType.uint64) + return self.divU64(other) + + def divU128(self, other: "WideUint128"): + if not isinstance(other, WideUint128): + raise TealInputError("expected WideUint128 input for divU128") + + class WideUint128DivU128(WideUint128): + def __init__(self, lhs: WideUint128, arg: WideUint128): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + termSrt, termEnd = self.term.__teal__(options) + lhsEnd.setNextBlock(termSrt) + divFromDivmodW = TealSimpleBlock( + [ + TealOp(self, Op.divmodw), + TealOp(self, Op.pop), + TealOp(self, Op.pop), + ] + ) + termEnd.setNextBlock(divFromDivmodW) + return lhsSrt, divFromDivmodW + + def __str__(self) -> str: + return "(divW {} {})".format(self.lhs, self.term) + + return WideUint128DivU128(self, other) + + def divU64(self, other: Expr): + require_type(other, TealType.uint64) + + class WideUint128DivU64(WideUint128): + def __init__(self, lhs: WideUint128, arg: Expr): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + termSrt, termEnd = self.term.__teal__(options) + lhsEnd.setNextBlock(termSrt) + divFromDivW = TealSimpleBlock( + [ + TealOp(self, Op.divw), + ] + ) + termEnd.setNextBlock(divFromDivW) + return lhsSrt, divFromDivW + + def __str__(self) -> str: + return "(divW {} {})".format(self.lhs, self.term) + + def type_of(self) -> TealType: + return TealType.uint64 + + return WideUint128DivU64(self, other) def __mod__(self, other: "WideUint128"): - pass + if isinstance(other, WideUint128): + return self.modU128(other) + else: + require_type(other, TealType.uint64) + return self.modU64(other) + + def modU128(self, other: "WideUint128"): + if not isinstance(other, WideUint128): + raise TealInputError("expected WideUint128 input for modU128") + + class WideUint128ModU128(WideUint128): + def __init__(self, lhs: WideUint128, arg: WideUint128): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + termSrt, termEnd = self.term.__teal__(options) + lhsEnd.setNextBlock(termSrt) + modFromDivModW = TealSimpleBlock( + [ + # stack: [..., divH, divL, modH, modL] + TealOp(self, Op.divmodw), + # stack: [..., divL, modH, modL, divH] + TealOp(self, Op.uncover, 3), + # stack: [..., modH, modL, divH, divL] + TealOp(self, Op.uncover, 3), + TealOp(self, Op.pop), + TealOp(self, Op.pop), + ] + ) + termEnd.setNextBlock(modFromDivModW) + return lhsSrt, modFromDivModW + + def __str__(self) -> str: + return "(modW {} {})".format(self.lhs, self.term) + + return WideUint128ModU128(self, other) + + def modU64(self, other: Expr): + require_type(other, TealType.uint64) + # returns u64 + class WideUint128ModU64(WideUint128): + def __init__(self, lhs: WideUint128, arg: Expr): + WideUint128.__init__(self, arg) + self.term = arg + self.lhs = lhs + + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + termSrt, termEnd = self.term.__teal__(options) + lhsEnd.setNextBlock(termSrt) + divFromDivW = TealSimpleBlock( + [ + # stack: [..., A, B, C, 0] + TealOp(self, Op.int, 0), + # stack: [..., A, B, 0, C] + TealOp(self, Op.swap), + # stack: [..., divH, divL, modH, modL] + TealOp(self, Op.divmodw), + # stack: [..., divL, modH, modL, divH] + TealOp(self, Op.uncover, 3), + # stack: [..., modH, modL, divH, divL] + TealOp(self, Op.uncover, 3), + TealOp(self, Op.pop), + TealOp(self, Op.pop), + ] + ) + termEnd.setNextBlock(divFromDivW) + return lhsSrt, divFromDivW + + def __str__(self) -> str: + return "(divW {} {})".format(self.lhs, self.term) + + def type_of(self) -> TealType: + return TealType.uint64 + + return WideUint128ModU64(self, other) @abstractmethod - def __teal__(self, options: "CompileOptions"): + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealSimpleBlock, TealSimpleBlock]: pass def type_of(self) -> TealType: return TealType.none + def toUint64(self) -> Expr: + if self.type_of() == TealType.uint64: + raise TealInternalError("expression is already evaluated to uint64") + + class WideUint128ToUint64(Expr): + def __init__(self, wideArith: WideUint128): + super().__init__() + self.wideArith = wideArith + + def type_of(self) -> TealType: + return TealType.uint64 + + def has_return(self) -> bool: + return False + + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealBlock, TealSimpleBlock]: + arithSrt, arithEnd = self.wideArith.__teal__(options) + reducer = TealSimpleBlock([TealOp(self, Op.swap), TealOp(self, Op.pop)]) + arithEnd.setNextBlock(reducer) + return arithSrt, reducer + + def __str__(self) -> str: + return "(toUint64 {})".format(self.wideArith) + + return WideUint128ToUint64(self) + + def toBinary(self) -> Expr: + if self.type_of() == TealType.uint64: + raise TealInternalError("expression is already evaluated to uint64") + + class WideUint128ToBinary(Expr): + def __init__(self, wideArith: WideUint128): + super().__init__() + self.wideArith = wideArith + + def type_of(self) -> TealType: + return TealType.uint64 + + def has_return(self) -> bool: + return False + + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealBlock, TealSimpleBlock]: + arithSrt, arithEnd = self.wideArith.__teal__(options) + reducer = TealSimpleBlock( + [ + TealOp(self, Op.itob), + TealOp(self, Op.swap), + TealOp(self, Op.itob), + TealOp(self, Op.swap), + TealOp(self, Op.concat), + ] + ) + arithEnd.setNextBlock(reducer) + return arithSrt, reducer + + def __str__(self) -> str: + return "(toBinary {})".format(self.wideArith) + + return WideUint128ToBinary(self) + + +WideUint128.__module__ = "pyteal" # (A + B - C) * D # sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Seq(Assert(Not(high)), low)) # alias as .to64Bits() From cadaba83f7cadfff29ec5824a9d594933714328d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 10 Mar 2022 13:48:52 -0500 Subject: [PATCH 17/31] minor --- pyteal/ast/widemath.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 4f962abeb..dfebbefce 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -826,19 +826,16 @@ def sumW(*terms: Expr): for term in terms: require_type(term, TealType.uint64) - class WideUint128Sum(MultiValue, WideUint128): + class WideUint128Sum(WideUint128): def __init__(self, *args: Expr): WideUint128.__init__(self, *args) - MultiValue.__init__( - self, Op.addw, [TealType.uint64, TealType.uint64], args=list(args) - ) self.terms = list(args) def __teal__(self, options: "CompileOptions"): return addTerms(self, self.terms, options) def __str__(self) -> str: - return MultiValue.__str__(self) + return "(addw {})".format(" ".join([term.__str__() for term in self.terms])) return WideUint128Sum(*terms) @@ -850,19 +847,18 @@ def prodW(*factors: Expr): for factor in factors: require_type(factor, TealType.uint64) - class WideUint128Prod(MultiValue, WideUint128): + class WideUint128Prod(WideUint128): def __init__(self, *args: Expr): WideUint128.__init__(self, *args) - MultiValue.__init__( - self, Op.mulw, [TealType.uint64, TealType.uint64], args=list(args) - ) self.factors = list(args) def __teal__(self, options: "CompileOptions"): return multiplyFactors(self, self.factors, options) def __str__(self) -> str: - return MultiValue.__str__(self) + return "(mulw {})".format( + " ".join([factor.__str__() for factor in self.factors]) + ) return WideUint128Prod(*factors) @@ -871,12 +867,9 @@ def expW(base: Expr, _pow: Expr): require_type(base, TealType.uint64) require_type(_pow, TealType.uint64) - class WideUint128Exp(MultiValue, WideUint128): + class WideUint128Exp(WideUint128): def __init__(self, *args: Expr): WideUint128.__init__(self, *args) - MultiValue.__init__( - self, Op.expw, [TealType.uint64, TealType.uint64], args=list(args) - ) self.base = args[0] self.power = args[1] @@ -886,6 +879,6 @@ def __teal__(self, options: "CompileOptions"): ) def __str__(self) -> str: - return MultiValue.__str__(self) + return "(expw {} {})".format(self.base.__str__(), self.power.__str__()) return WideUint128Exp(base, _pow) From 0f4abb211c99c7a81e2ae0ff99731d70a3a45b07 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 10 Mar 2022 14:13:44 -0500 Subject: [PATCH 18/31] minor --- pyteal/__init__.pyi | 6 +++++- pyteal/ast/__init__.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index e4bf54ada..01a41193e 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -222,7 +222,11 @@ __all__ = [ "VrfVerify", "While", "WideRatio", - "abi", + "WideUint128", "compileTeal", + "expW", + "prodW", + "sumW", + "abi", "pragma", ] diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 91e233e96..331d9654f 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -267,6 +267,10 @@ "Or", "Concat", "WideRatio", + "sumW", + "prodW", + "expW", + "WideUint128", "If", "Cond", "Seq", From 81d3808f51f92ec5cc68d1da37bd6ec1d24eee7c Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 10 Mar 2022 14:49:22 -0500 Subject: [PATCH 19/31] minor --- pyteal/ast/widemath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index dfebbefce..d817030e4 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -697,7 +697,7 @@ def __str__(self) -> str: def modU64(self, other: Expr): require_type(other, TealType.uint64) - # returns u64 + class WideUint128ModU64(WideUint128): def __init__(self, lhs: WideUint128, arg: Expr): WideUint128.__init__(self, arg) @@ -733,7 +733,7 @@ def __str__(self) -> str: def type_of(self) -> TealType: return TealType.uint64 - return WideUint128ModU64(self, other) + return WideUint128ModU64(self, other).toUint64() @abstractmethod def __teal__( From 5586a7a0bcd87eaaa1679090ff35f042d9bb8a46 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 10 Mar 2022 15:56:34 -0500 Subject: [PATCH 20/31] comply with ide hints: --- pyteal/ast/widemath.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index d817030e4..d91ab2272 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -443,7 +443,7 @@ def has_return(self): class WideUint128(LeafExpr, metaclass=ABCMeta): @abstractmethod def __init__(self, *args: Expr): - pass + super().__init__() def __add__(self, other: Expr): if isinstance(other, WideUint128): @@ -835,7 +835,7 @@ def __teal__(self, options: "CompileOptions"): return addTerms(self, self.terms, options) def __str__(self) -> str: - return "(addw {})".format(" ".join([term.__str__() for term in self.terms])) + return "(addw {})".format(" ".join([t.__str__() for t in self.terms])) return WideUint128Sum(*terms) @@ -857,7 +857,7 @@ def __teal__(self, options: "CompileOptions"): def __str__(self) -> str: return "(mulw {})".format( - " ".join([factor.__str__() for factor in self.factors]) + " ".join([f.__str__() for f in self.factors]) ) return WideUint128Prod(*factors) From 76efcb14e01cf8de2e9700f64c3992db9eb50930 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 10 Mar 2022 16:56:38 -0500 Subject: [PATCH 21/31] fmt --- pyteal/ast/widemath.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index d91ab2272..6d3bca7c2 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -856,9 +856,7 @@ def __teal__(self, options: "CompileOptions"): return multiplyFactors(self, self.factors, options) def __str__(self) -> str: - return "(mulw {})".format( - " ".join([f.__str__() for f in self.factors]) - ) + return "(mulw {})".format(" ".join([f.__str__() for f in self.factors])) return WideUint128Prod(*factors) From 8ef55631b66b6ce1250869f5660dcf725b10aa4c Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 11 Mar 2022 10:57:23 -0500 Subject: [PATCH 22/31] minor fixes here n there --- pyteal/ast/widemath.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 6d3bca7c2..3a88c8efe 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -469,7 +469,7 @@ def __teal__(self, options: "CompileOptions"): return lhsSrt, addEnd def __str__(self) -> str: - return "(addw {} {})".format(self.lhs, self.term) + return "(addW {} {})".format(self.lhs, self.term) return WideUint128AddU128(self, other) @@ -489,7 +489,7 @@ def __teal__(self, options: "CompileOptions"): return lhsSrt, addEnd def __str__(self) -> str: - return "(addw {} {})".format(self.lhs, self.term) + return "(addW {} {})".format(self.lhs, self.term) return WideUint128AddU64(self, other) @@ -541,7 +541,7 @@ def __str__(self) -> str: return WideUint128MinusU64(self, other) - def __mul__(self, other): + def __mul__(self, other: Expr): if isinstance(other, WideUint128): return self.mulU128(other) else: @@ -565,7 +565,7 @@ def __teal__(self, options: "CompileOptions"): return lhsSrt, mulEnd def __str__(self) -> str: - return "(mulw {} {})".format(self.lhs, self.term) + return "(mulW {} {})".format(self.lhs, self.term) return WideUint128MulU128(self, other) @@ -585,7 +585,7 @@ def __teal__(self, options: "CompileOptions"): return lhsSrt, mulEnd def __str__(self) -> str: - return "(mulw {} {})".format(self.lhs, self.term) + return "(mulW {} {})".format(self.lhs, self.term) return WideUint128MulU64(self, other) @@ -654,7 +654,7 @@ def type_of(self) -> TealType: return WideUint128DivU64(self, other) - def __mod__(self, other: "WideUint128"): + def __mod__(self, other: Expr): if isinstance(other, WideUint128): return self.modU128(other) else: @@ -720,7 +720,13 @@ def __teal__(self, options: "CompileOptions"): TealOp(self, Op.uncover, 3), # stack: [..., modH, modL, divH, divL] TealOp(self, Op.uncover, 3), + # stack: [..., modH, modL, divH] TealOp(self, Op.pop), + # stack: [..., modH, modL] + TealOp(self, Op.pop), + # stack: [..., modL, modH] + TealOp(self, Op.swap), + # stack: [..., modL] TealOp(self, Op.pop), ] ) @@ -728,7 +734,7 @@ def __teal__(self, options: "CompileOptions"): return lhsSrt, divFromDivW def __str__(self) -> str: - return "(divW {} {})".format(self.lhs, self.term) + return "(modW {} {})".format(self.lhs, self.term) def type_of(self) -> TealType: return TealType.uint64 @@ -782,7 +788,7 @@ def __init__(self, wideArith: WideUint128): self.wideArith = wideArith def type_of(self) -> TealType: - return TealType.uint64 + return TealType.bytes def has_return(self) -> bool: return False From d891c4da47581eca050e8e21b592f2bad2426caf Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 11 Mar 2022 13:43:26 -0500 Subject: [PATCH 23/31] update reduce To method with lowest scratch use --- pyteal/ast/widemath.py | 82 ++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 3a88c8efe..491290bac 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -2,7 +2,7 @@ from typing import List, Tuple, TYPE_CHECKING from pyteal.ast.multi import MultiValue from pyteal.ir.tealblock import TealBlock -from pyteal.types import TealType +from pyteal.types import TealType, require_type from pyteal.errors import TealInternalError, TealCompileError from pyteal.ir import TealOp, Op, TealSimpleBlock from pyteal.ast.expr import Expr @@ -442,8 +442,9 @@ def has_return(self): class WideUint128(LeafExpr, metaclass=ABCMeta): @abstractmethod - def __init__(self, *args: Expr): + def __init__(self, outputNum: int): super().__init__() + self.output_slots = [ScratchSlot() for _ in range(outputNum)] def __add__(self, other: Expr): if isinstance(other, WideUint128): @@ -458,7 +459,7 @@ def addU128(self, other: "WideUint128"): class WideUint128AddU128(WideUint128): def __init__(self, lhs: WideUint128, arg: WideUint128): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -478,7 +479,7 @@ def addU64(self, other: Expr): class WideUint128AddU64(WideUint128): def __init__(self, lhs: WideUint128, arg: Expr): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -506,7 +507,7 @@ def minusU128(self, other: "WideUint128"): class WideUint128MinusU128(WideUint128): def __init__(self, lhs: WideUint128, arg: WideUint128): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -526,7 +527,7 @@ def minusU64(self, other: Expr): class WideUint128MinusU64(WideUint128): def __init__(self, lhs: WideUint128, arg: Expr): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -554,7 +555,7 @@ def mulU128(self, other: "WideUint128"): class WideUint128MulU128(WideUint128): def __init__(self, lhs: WideUint128, arg: WideUint128): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -574,7 +575,7 @@ def mulU64(self, other: Expr): class WideUint128MulU64(WideUint128): def __init__(self, lhs: WideUint128, arg: Expr): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -602,7 +603,7 @@ def divU128(self, other: "WideUint128"): class WideUint128DivU128(WideUint128): def __init__(self, lhs: WideUint128, arg: WideUint128): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -630,7 +631,7 @@ def divU64(self, other: Expr): class WideUint128DivU64(WideUint128): def __init__(self, lhs: WideUint128, arg: Expr): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 1) self.term = arg self.lhs = lhs @@ -649,9 +650,6 @@ def __teal__(self, options: "CompileOptions"): def __str__(self) -> str: return "(divW {} {})".format(self.lhs, self.term) - def type_of(self) -> TealType: - return TealType.uint64 - return WideUint128DivU64(self, other) def __mod__(self, other: Expr): @@ -667,7 +665,7 @@ def modU128(self, other: "WideUint128"): class WideUint128ModU128(WideUint128): def __init__(self, lhs: WideUint128, arg: WideUint128): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -700,7 +698,7 @@ def modU64(self, other: Expr): class WideUint128ModU64(WideUint128): def __init__(self, lhs: WideUint128, arg: Expr): - WideUint128.__init__(self, arg) + WideUint128.__init__(self, 2) self.term = arg self.lhs = lhs @@ -736,9 +734,6 @@ def __teal__(self, options: "CompileOptions"): def __str__(self) -> str: return "(modW {} {})".format(self.lhs, self.term) - def type_of(self) -> TealType: - return TealType.uint64 - return WideUint128ModU64(self, other).toUint64() @abstractmethod @@ -748,11 +743,13 @@ def __teal__( pass def type_of(self) -> TealType: - return TealType.none + return TealType.uint64 if len(self.output_slots) == 1 else TealType.none def toUint64(self) -> Expr: if self.type_of() == TealType.uint64: raise TealInternalError("expression is already evaluated to uint64") + elif len(self.output_slots) > 2: + raise TealInternalError("expression is only appliable for uint128") class WideUint128ToUint64(Expr): def __init__(self, wideArith: WideUint128): @@ -781,6 +778,8 @@ def __str__(self) -> str: def toBinary(self) -> Expr: if self.type_of() == TealType.uint64: raise TealInternalError("expression is already evaluated to uint64") + elif len(self.output_slots) > 2: + raise TealInternalError("expression is only appliable for uint128") class WideUint128ToBinary(Expr): def __init__(self, wideArith: WideUint128): @@ -814,6 +813,45 @@ def __str__(self) -> str: return WideUint128ToBinary(self) + def reduceTo(self, reducer: Callable[..., Expr]): + if self.type_of() == TealType.uint64: + raise TealInternalError("expression is already evaluated to uint64") + + class WideUint128Reduced(Expr): + def __init__(self, wideArith: WideUint128): + super().__init__() + self.wideArith = wideArith + + argsLoaded = [ + slot.load(TealType.uint64) for slot in self.wideArith.output_slots + ] + self.reduceExpr = reducer(argsLoaded) + + def __str__(self) -> str: + return "(reduced {})".format(self.wideArith) + + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealBlock, TealSimpleBlock]: + srt = TealSimpleBlock([]) + curEnd = srt + for slot in reversed(self.wideArith.output_slots): + store = slot.store() + storeSrt, storeEnd = store.__teal__(options) + curEnd.setNextBlock(storeSrt) + curEnd = storeEnd + reduceSrt, reduceEnd = self.reduceExpr.__teal__(options) + curEnd.setNextBlock(reduceSrt) + return srt, reduceEnd + + def type_of(self) -> TealType: + return self.reduceExpr.type_of() + + def has_return(self) -> bool: + return False + + return WideUint128Reduced(self) + WideUint128.__module__ = "pyteal" @@ -834,7 +872,7 @@ def sumW(*terms: Expr): class WideUint128Sum(WideUint128): def __init__(self, *args: Expr): - WideUint128.__init__(self, *args) + WideUint128.__init__(self, 2) self.terms = list(args) def __teal__(self, options: "CompileOptions"): @@ -855,7 +893,7 @@ def prodW(*factors: Expr): class WideUint128Prod(WideUint128): def __init__(self, *args: Expr): - WideUint128.__init__(self, *args) + WideUint128.__init__(self, 2) self.factors = list(args) def __teal__(self, options: "CompileOptions"): @@ -873,7 +911,7 @@ def expW(base: Expr, _pow: Expr): class WideUint128Exp(WideUint128): def __init__(self, *args: Expr): - WideUint128.__init__(self, *args) + WideUint128.__init__(self, 2) self.base = args[0] self.power = args[1] From 78f038cf937c9123312d0a7a315603078d7d2946 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 11 Mar 2022 13:47:14 -0500 Subject: [PATCH 24/31] remove redundant abstract teal method --- pyteal/ast/widemath.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 491290bac..b7ee221b5 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -736,12 +736,6 @@ def __str__(self) -> str: return WideUint128ModU64(self, other).toUint64() - @abstractmethod - def __teal__( - self, options: "CompileOptions" - ) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - pass - def type_of(self) -> TealType: return TealType.uint64 if len(self.output_slots) == 1 else TealType.none From 0fa2dc89e4f1b129513edf4014bbe706b98eaf5b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 11 Mar 2022 13:55:25 -0500 Subject: [PATCH 25/31] update divmodw support --- pyteal/ast/widemath.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index b7ee221b5..cd577d77b 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -736,6 +736,31 @@ def __str__(self) -> str: return WideUint128ModU64(self, other).toUint64() + def __divmod__(self, other: "WideUint128"): + if not isinstance(other, WideUint128): + raise TealInputError("expected WideUint128 input for divmodW") + + class WideUint128DivmodW(WideUint128): + def __init__(self, lhs: WideUint128, rhs: WideUint128): + WideUint128.__init__(self, 4) + self.lhs = lhs + self.term = rhs + + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealBlock, TealSimpleBlock]: + lhsSrt, lhsEnd = self.lhs.__teal__(options) + termSrt, termEnd = self.term.__teal__(options) + lhsEnd.setNextBlock(termSrt) + divmodW = TealSimpleBlock([TealOp(self, Op.divmodw)]) + termEnd.setNextBlock(divmodW) + return lhsSrt, divmodW + + def __str__(self) -> str: + return "(divmodW {} {})".format(self.lhs, self.term) + + return WideUint128DivmodW(self, other) + def type_of(self) -> TealType: return TealType.uint64 if len(self.output_slots) == 1 else TealType.none From 0de4d3e6b1818bd1abdd4620aca8124263e014a3 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 11 Mar 2022 14:11:57 -0500 Subject: [PATCH 26/31] update toUint64 check highword == 0 --- pyteal/ast/widemath.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index cd577d77b..90b9bdcfa 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -785,7 +785,13 @@ def __teal__( self, options: "CompileOptions" ) -> Tuple[TealBlock, TealSimpleBlock]: arithSrt, arithEnd = self.wideArith.__teal__(options) - reducer = TealSimpleBlock([TealOp(self, Op.swap), TealOp(self, Op.pop)]) + reducer = TealSimpleBlock( + [ + TealOp(self, Op.swap), + TealOp(self, Op.logic_not), + TealOp(self, Op.assert_), + ] + ) arithEnd.setNextBlock(reducer) return arithSrt, reducer @@ -879,7 +885,8 @@ def has_return(self) -> bool: # sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Concat(Itob(high), Itob(low))) # alias as .toBinary() # (A + B) - (C * D) -# sumW(A, B).minus(mulW(C, D)).to64Bits() +# sumW(A, B).minus(mulW(C, D)).toUint64() +# (sumW(A, B) - prodW(C, D)).toUint64() def sumW(*terms: Expr): From d01dd386e3e96b546efdbfbfa638cc77894989d2 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 14 Mar 2022 13:24:08 -0400 Subject: [PATCH 27/31] minor --- pyteal/ast/widemath.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 90b9bdcfa..8a5455610 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -444,6 +444,8 @@ class WideUint128(LeafExpr, metaclass=ABCMeta): @abstractmethod def __init__(self, outputNum: int): super().__init__() + if outputNum <= 0: + raise TealInputError("number of output slot should be positive") self.output_slots = [ScratchSlot() for _ in range(outputNum)] def __add__(self, other: Expr): @@ -454,7 +456,7 @@ def __add__(self, other: Expr): return self.addU64(other) def addU128(self, other: "WideUint128"): - if not isinstance(other, WideUint128): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: raise TealInputError("expected WideUint128 input for addU128") class WideUint128AddU128(WideUint128): @@ -502,7 +504,7 @@ def __sub__(self, other: Expr): return self.minusU64(other) def minusU128(self, other: "WideUint128"): - if not isinstance(other, WideUint128): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: raise TealInputError("expected WideUint128 input for minusU128") class WideUint128MinusU128(WideUint128): @@ -550,7 +552,7 @@ def __mul__(self, other: Expr): return self.mulU64(other) def mulU128(self, other: "WideUint128"): - if not isinstance(other, WideUint128): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: raise TealInputError("expected WideUint128 input for mulU128") class WideUint128MulU128(WideUint128): @@ -598,7 +600,7 @@ def __truediv__(self, other: Expr): return self.divU64(other) def divU128(self, other: "WideUint128"): - if not isinstance(other, WideUint128): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: raise TealInputError("expected WideUint128 input for divU128") class WideUint128DivU128(WideUint128): @@ -660,7 +662,7 @@ def __mod__(self, other: Expr): return self.modU64(other) def modU128(self, other: "WideUint128"): - if not isinstance(other, WideUint128): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: raise TealInputError("expected WideUint128 input for modU128") class WideUint128ModU128(WideUint128): @@ -737,7 +739,7 @@ def __str__(self) -> str: return WideUint128ModU64(self, other).toUint64() def __divmod__(self, other: "WideUint128"): - if not isinstance(other, WideUint128): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: raise TealInputError("expected WideUint128 input for divmodW") class WideUint128DivmodW(WideUint128): From bc83f881a288947077ccb9b302e0e163070d2443 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Fri, 23 Sep 2022 22:59:23 +0200 Subject: [PATCH 28/31] ref: rebase and import for test --- pyteal/ast/__init__.py | 2 +- pyteal/ast/widemath.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 331d9654f..dfd982c32 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -124,7 +124,7 @@ # more ops from pyteal.ast.naryexpr import NaryExpr, Add, And, Mul, Or, Concat -from pyteal.ast.widemath import WideRatio +from pyteal.ast.widemath import WideRatio, sumW, prodW, expW, WideUint128 # control flow from pyteal.ast.if_ import If diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 8a5455610..3194e2391 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -1,11 +1,13 @@ from abc import ABCMeta, abstractmethod -from typing import List, Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING, Callable, List, Tuple + +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr from pyteal.ast.multi import MultiValue -from pyteal.ir.tealblock import TealBlock +from pyteal.ast.scratch import ScratchSlot +from pyteal.errors import TealCompileError, TealInputError, TealInternalError +from pyteal.ir import Op, TealBlock, TealConditionalBlock, TealOp, TealSimpleBlock from pyteal.types import TealType, require_type -from pyteal.errors import TealInternalError, TealCompileError -from pyteal.ir import TealOp, Op, TealSimpleBlock -from pyteal.ast.expr import Expr if TYPE_CHECKING: from pyteal.compiler import CompileOptions From 876d78a15e5f4df3e3bdeda3f7da2c079606061f Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Sat, 24 Sep 2022 01:49:09 +0200 Subject: [PATCH 29/31] chore: regenerate init --- pyteal/__init__.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 01a41193e..3fb09d616 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -223,10 +223,10 @@ __all__ = [ "While", "WideRatio", "WideUint128", + "abi", "compileTeal", "expW", + "pragma", "prodW", "sumW", - "abi", - "pragma", ] From 8bd36f2065aab692c176698a37b3e5d841a33cb9 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 02:12:29 +0200 Subject: [PATCH 30/31] ref: rearrange some codes before rebase --- pyteal/ast/widemath.py | 1268 ++++++++++++++++++++-------------------- 1 file changed, 638 insertions(+), 630 deletions(-) diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 3194e2391..d2d2b6a21 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -1,4 +1,9 @@ -from abc import ABCMeta, abstractmethod +""" +!!IMPORTANT!! +#FIX: This commit is not finished yet, finish it before pushing. + +""" +from abc import abstractmethod from typing import TYPE_CHECKING, Callable, List, Tuple from pyteal.ast.expr import Expr @@ -13,455 +18,122 @@ from pyteal.compiler import CompileOptions -def addU128U64( - expr: Expr, term: Expr, options: "CompileOptions" -) -> Tuple[TealBlock, TealSimpleBlock]: - termSrt, termEnd = term.__teal__(options) - # stack is [..., A, B, C], where C is current term - # need to pop all A, B, C from stack and push X, Y, where X and Y are: - # X * 2 ** 64 + Y = (A * 2 ** 64) + (B + C) - # <=> X * 2 ** 64 + Y = (A + highword(B + C)) * 2 ** 64 + lowword(B + C) - # <=> X = A + highword(B + C) - # Y = lowword(B + C) - addition = TealSimpleBlock( - [ - # stack: [..., A, highword(B + C), lowword(B + C)] - TealOp(expr, Op.addw), - # stack: [..., lowword(B + C), A, highword(B + C)] - TealOp(expr, Op.cover, 2), - # stack: [..., lowword(B + C), A + highword(B + C)] - TealOp(expr, Op.add), - # stack: [..., A + highword(B + C), lowword(B + C)] - TealOp(expr, Op.uncover, 1), - ] - ) - termEnd.setNextBlock(addition) - return termSrt, addition +class WideUint128(LeafExpr): + @abstractmethod + def __init__(self, outputNum: int): + super().__init__() + if outputNum <= 0: + raise TealInputError("number of output slot should be positive") + self.output_slots = [ScratchSlot() for _ in range(outputNum)] + def __add__(self, other: Expr): + if isinstance(other, WideUint128): + return self.addU128(other) + else: + require_type(other, TealType.uint64) + return self.addU64(other) -def addTerms( - expr: Expr, terms: List[Expr], options: "CompileOptions" -) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - if len(terms) == 0: - raise TealInternalError("Received 0 terms") + def addU128(self, other: "WideUint128"): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: + raise TealInputError("expected WideUint128 input for addU128") - for term in terms: - require_type(term, TealType.uint64) + class WideUint128AddU128(WideUint128): + def __init__(self, lhs: WideUint128, arg: WideUint128): + WideUint128.__init__(self, 2) + self.term = arg + self.lhs = lhs - start = TealSimpleBlock([]) + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + addSrt, addEnd = addU128(self, self.term, options) + lhsEnd.setNextBlock(addSrt) + return lhsSrt, addEnd - term0srt, term0end = terms[0].__teal__(options) + def __str__(self) -> str: + return "(addW {} {})".format(self.lhs, self.term) - if len(terms) == 1: - highword = TealSimpleBlock([TealOp(expr, Op.int, 0)]) + return WideUint128AddU128(self, other) - start.setNextBlock(highword) - highword.setNextBlock(term0srt) - end = term0end - else: - start.setNextBlock(term0srt) + def addU64(self, other: Expr): + require_type(other, TealType.uint64) - term1srt, term1end = terms[1].__teal__(options) - term0end.setNextBlock(term1srt) - addFirst2 = TealSimpleBlock([TealOp(expr, Op.addw)]) - term1end.setNextBlock(addFirst2) - end = addFirst2 + class WideUint128AddU64(WideUint128): + def __init__(self, lhs: WideUint128, arg: Expr): + WideUint128.__init__(self, 2) + self.term = arg + self.lhs = lhs - for term in terms[2:]: - termSrt, added = addU128U64(expr, term, options) - end.setNextBlock(termSrt) - end = added + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + addSrt, addEnd = addU128U64(self, self.term, options) + lhsEnd.setNextBlock(addSrt) + return lhsSrt, addEnd - return start, end + def __str__(self) -> str: + return "(addW {} {})".format(self.lhs, self.term) + return WideUint128AddU64(self, other) -def addU128( - expr: Expr, term: Expr, options: "CompileOptions" -) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - start = TealSimpleBlock([]) - termSrt, termEnd = term.__teal__(options) - # stack: [..., A, B, C, D] - addition = TealSimpleBlock( - [ - # stack: [..., A, C, D, B] - TealOp(expr, Op.uncover, 2), - # stack: [..., A, C, B, D] - TealOp(expr, Op.swap), - # stack: [..., A, C, highword(B + D), lowword(B + D)] - TealOp(expr, Op.addw), - # stack: [..., lowword(B + D), A, C, highword(B + D)] - TealOp(expr, Op.cover, 3), - # stack: [..., lowword(B + D), highword(B + D), A, C] - TealOp(expr, Op.cover, 2), - # stack: [..., lowword(B + D), highword(B + D), A + C] - TealOp(expr, Op.add), - # stack: [..., lowword(B + D), highword(B + D) + A + C] - TealOp(expr, Op.add), - # stack: [..., highword(B + D) + A + C, lowword(B + D)] - TealOp(expr, Op.swap), - ] - ) - start.setNextBlock(termSrt) - termEnd.setNextBlock(addition) - return start, addition + def __sub__(self, other: Expr): + if isinstance(other, WideUint128): + return self.minusU128(other) + else: + require_type(other, TealType.uint64) + return self.minusU64(other) + def minusU128(self, other: "WideUint128"): + if not isinstance(other, WideUint128) or len(other.output_slots) != 2: + raise TealInputError("expected WideUint128 input for minusU128") -def __substrctU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - # stack: [..., A, B, C], where A * 2 ** 64 + B is 128 bit uint, C is uint64 - substractPrep = TealSimpleBlock( - [ - # stack: [..., A, B, C, B, C] - TealOp(expr, Op.dup2), - # stack: [..., A, B, C, B >= C] - TealOp(expr, Op.gt), - ] - ) - substractCond = TealConditionalBlock([]) - substractTrueBlock = TealSimpleBlock( - [ - # stack: [..., A, B - C] - TealOp(expr, Op.minus) - ] - ) + class WideUint128MinusU128(WideUint128): + def __init__(self, lhs: WideUint128, arg: WideUint128): + WideUint128.__init__(self, 2) + self.term = arg + self.lhs = lhs - substractFalseBlock = TealSimpleBlock( - [ - # stack: [..., B, C, A] - TealOp(expr, Op.uncover, 2), - # stack: [..., B, C, A, A] - TealOp(expr, Op.dup), - # stack: [..., B, C, A, A, 1] - TealOp(expr, Op.int, 1), - # stack: [..., B, C, A, A >= 1], - TealOp(expr, Op.gt), - # stack: [..., B, C, A] - TealOp(expr, Op.assert_), - # stack: [..., B, C, A, 1] - TealOp(expr, Op.int, 1), - # stack: [..., B, C, A - 1] - TealOp(expr, Op.minus), - # stack: [..., A - 1, B, C] - TealOp(expr, Op.cover, 2), - # stack: [..., A - 1, C, B] - TealOp(expr, Op.cover, 1), - # stack: [..., A - 1, C - B] - TealOp(expr, Op.minus), - # stack: [..., A - 1, 2^64 - 1 - (C - B)] - TealOp(expr, Op.bitwise_not), - # stack: [..., A - 1, 2^64 - 1 - (C - B), 1] - TealOp(expr, Op.int, 1), - # stack: [..., A - 1, 2^64 - (C - B)] - TealOp(expr, Op.add), - ] - ) - substractPrep.setNextBlock(substractCond) - substractCond.setTrueBlock(substractTrueBlock) - substractCond.setFalseBlock(substractFalseBlock) + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + subSrt, subEnd = subtractU128(self, self.term, options) + lhsEnd.setNextBlock(subSrt) + return lhsSrt, subEnd - end = TealSimpleBlock([]) - substractTrueBlock.setNextBlock(end) - substractFalseBlock.setNextBlock(end) - return substractPrep, end + def __str__(self) -> str: + return "(minusW {} {})".format(self.lhs, self.term) + return WideUint128MinusU128(self, other) -def substractU128U64( - expr: Expr, rhsTerm: Expr, options: "CompileOptions" -) -> Tuple[TealBlock, TealSimpleBlock]: - termSrt, termEnd = rhsTerm.__teal__(options) - subsSrt, subsEnd = __substrctU128U64(expr) - termEnd.setNextBlock(subsSrt) - return termSrt, subsEnd + def minusU64(self, other: Expr): + require_type(other, TealType.uint64) + class WideUint128MinusU64(WideUint128): + def __init__(self, lhs: WideUint128, arg: Expr): + WideUint128.__init__(self, 2) + self.term = arg + self.lhs = lhs -def substractU128( - expr: Expr, rhsTerm: Expr, options: "CompileOptions" -) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - start = TealSimpleBlock([]) - rhsSrt, rhsEnd = rhsTerm.__teal__(options) - start.setNextBlock(rhsSrt) - rhsEnd.setNextBlock(rhsSrt) - # stack: [..., A, B, C, D] - highwordPrep = TealSimpleBlock( - [ - # stack: [..., B, C, D, A] - TealOp(expr, Op.uncover, 3), - # stack: [..., B, D, A, C] - TealOp(expr, Op.uncover, 2), - # stack: [..., B, D, A, C, A, C] - TealOp(expr, Op.dup2), - # stack: [..., B, D, A, C, A >= C] - TealOp(expr, Op.gt), - # stack: [..., B, D, A, C] - TealOp(expr, Op.assert_), - # stack: [..., B, D, A - C] - TealOp(expr, Op.minus), - # stack: [..., A - C, B, D] - TealOp(expr, Op.cover, 2), - ] - ) - rhsEnd.setNextBlock(highwordPrep) - subsSrt, subsEnd = __substrctU128U64(expr) - highwordPrep.setNextBlock(subsSrt) - return start, subsEnd + def __teal__(self, options: "CompileOptions"): + lhsSrt, lhsEnd = self.lhs.__teal__(options) + subSrt, subEnd = subtractU128U64(self, self.term, options) + lhsEnd.setNextBlock(subSrt) + return lhsSrt, subEnd + def __str__(self) -> str: + return "(minusW {} {})".format(self.lhs, self.term) -def __multU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - srt = TealSimpleBlock([]) - # stack: [..., A, B, C] - multiply = TealSimpleBlock( - [ - # stack: [..., B, C, A] - TealOp(expr, Op.uncover, 2), - # stack: [..., B, C, A, C] - TealOp(expr, Op.dig, 1), - # stack: [..., B, C, A*C] - TealOp(expr, Op.mul), - # stack: [..., A*C, B, C] - TealOp(expr, Op.cover, 2), - # stack: [..., A*C, highword(B*C), lowword(B*C)] - TealOp(expr, Op.mulw), - # stack: [..., lowword(B*C), A*C, highword(B*C)] - TealOp(expr, Op.cover, 2), - # stack: [..., lowword(B*C), A*C+highword(B*C)] - TealOp(expr, Op.add), - # stack: [..., A*C+highword(B*C), lowword(B*C)] - TealOp(expr, Op.swap), - ] - ) - end = TealSimpleBlock([]) - srt.setNextBlock(multiply) - multiply.setNextBlock(end) - return srt, end - - -def multU128U64( - expr: Expr, factor: Expr, options: "CompileOptions" -) -> Tuple[TealBlock, TealSimpleBlock]: - facSrt, facEnd = factor.__teal__(options) - # stack is [..., A, B, C], where C is current factor - # need to pop all A,B,C from stack and push X,Y, where X and Y are: - # X * 2**64 + Y = (A * 2**64 + B) * C - # <=> X * 2**64 + Y = A * C * 2**64 + B * C - # <=> X = A * C + highword(B * C) - # Y = lowword(B * C) - multSrt, multEnd = __multU128U64(expr) - facEnd.setNextBlock(multSrt) - return facSrt, multEnd - - -def multiplyFactors( - expr: Expr, factors: List[Expr], options: "CompileOptions" -) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - if len(factors) == 0: - raise TealInternalError("Received 0 factors") - - for factor in factors: - require_type(factor, TealType.uint64) - - start = TealSimpleBlock([]) - - fac0Start, fac0End = factors[0].__teal__(options) - - if len(factors) == 1: - # need to use 0 as high word - highword = TealSimpleBlock([TealOp(expr, Op.int, 0)]) - - start.setNextBlock(highword) - highword.setNextBlock(fac0Start) - - end = fac0End - else: - start.setNextBlock(fac0Start) - - fac1Start, fac1End = factors[1].__teal__(options) - fac0End.setNextBlock(fac1Start) - - multiplyFirst2 = TealSimpleBlock([TealOp(expr, Op.mulw)]) - fac1End.setNextBlock(multiplyFirst2) - - end = multiplyFirst2 - for factor in factors[2:]: - facSrt, multed = multU128U64(expr, factor, options) - end.setNextBlock(facSrt) - end = multed - - return start, end - - -def multU128( - expr: Expr, rhsFactor: Expr, options: "CompileOptions" -) -> Tuple[TealSimpleBlock, TealSimpleBlock]: - start = TealSimpleBlock([]) - rhsSrt, rhsEnd = rhsFactor.__teal__(options) - start.setNextBlock(rhsSrt) - # stack: [..., A, B, C, D] - multPrep = TealSimpleBlock( - [ - # stack; [..., B, C, D, A] - TealOp(expr, Op.uncover, 3), - # stack: [..., B, D, A, C] - TealOp(expr, Op.uncover, 2), - # stack: [..., B, D, A, C, A, C] - TealOp(expr, Op.dup2), - # stack: [..., B, D, A, C, A * C], if mul overflow, then u128 mult will also overflow - TealOp(expr, Op.mul), - # stack: [..., B, D, A, C, A * C != 0] - TealOp(expr, Op.logic_not), - # stack: [..., B, D, A, C], at least one of A and C is 0 - TealOp(expr, Op.assert_), - # stack: [..., B, D, A, C, C] - TealOp(expr, Op.dup), - # stack: [..., B, D, C, C, A] - TealOp(expr, Op.uncover, 2), - # stack: [..., B, D, C, C + A] - TealOp(expr, Op.add), - # stack: [..., B, D, C, C + A, C + A] - TealOp(expr, Op.dup), - # stack: [..., B, D, C + A, C, C + A] - TealOp(expr, Op.cover, 2), - # stack: [..., B, D, C + A, C == C + A] decide C + A should be swapped to before B or D - TealOp(expr, Op.eq), - ] - ) - rhsEnd.setNextBlock(multPrep) - - multCond = TealConditionalBlock([]) - multPrep.setNextBlock(multCond) - - # stack: [..., B, D, C] - multCondTrue = TealSimpleBlock( - [ - # stack: [..., B, C, D] - TealOp(expr, Op.swap), - # stack: [..., D, B, C] - TealOp(expr, Op.cover, 2), - # stack: [..., C, D, B] - TealOp(expr, Op.cover, 2), - ] - ) - # stack: [..., B, D, A] - multCondFalse = TealSimpleBlock( - [ - # stack: [..., A, B, D] - TealOp(expr, Op.cover, 2) - ] - ) - multCond.setTrueBlock(multCondTrue) - multCond.setFalseBlock(multCondFalse) - - # stack: [..., C, D, B] or [..., A, B, D] - multSrt, multEnd = __multU128U64(expr) - multCondTrue.setNextBlock(multSrt) - multCondFalse.setNextBlock(multSrt) - - return start, multEnd - - -class WideRatio(Expr): - """A class used to calculate expressions of the form :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)` - - Use this class if all inputs to the expression are uint64s, the output fits in a uint64, and all - intermediate values fit in a uint128. - """ - - def __init__( - self, numeratorFactors: List[Expr], denominatorFactors: List[Expr] - ) -> None: - """Create a new WideRatio expression with the given numerator and denominator factors. - - This will calculate :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)`, where each - :code:`N_i` represents an element in :code:`numeratorFactors` and each :code:`D_i` - represents an element in :code:`denominatorFactors`. - - Requires program version 5 or higher. - - Args: - numeratorFactors: The factors in the numerator of the ratio. This list must have at - least 1 element. If this list has exactly 1 element, then denominatorFactors must - have more than 1 element (otherwise basic division should be used). - denominatorFactors: The factors in the denominator of the ratio. This list must have at - least 1 element. - """ - super().__init__() - if len(numeratorFactors) == 0 or len(denominatorFactors) == 0: - raise TealInternalError( - "At least 1 factor must be present in the numerator and denominator" - ) - if len(numeratorFactors) == 1 and len(denominatorFactors) == 1: - raise TealInternalError( - "There is only a single factor in the numerator and denominator. Use basic division instead." - ) - self.numeratorFactors = numeratorFactors - self.denominatorFactors = denominatorFactors - - def __teal__(self, options: "CompileOptions"): - if options.version < Op.cover.min_version: - raise TealCompileError( - "WideRatio requires program version {} or higher".format( - Op.cover.min_version - ), - self, - ) - - numStart, numEnd = multiplyFactors(self, self.numeratorFactors, options) - denomStart, denomEnd = multiplyFactors(self, self.denominatorFactors, options) - numEnd.setNextBlock(denomStart) - - combine = TealSimpleBlock( - [ - TealOp(self, Op.divmodw), - TealOp(self, Op.pop), # pop remainder low word - TealOp(self, Op.pop), # pop remainder high word - TealOp(self, Op.swap), # swap quotient high and low words - TealOp(self, Op.logic_not), - TealOp(self, Op.assert_), # assert quotient high word is 0 - # end with quotient low word remaining on the stack - ] - ) - denomEnd.setNextBlock(combine) - - return numStart, combine - - def __str__(self): - ret_str = "(WideRatio (*" - for f in self.numeratorFactors: - ret_str += " " + str(f) - ret_str += ") (*" - for f in self.denominatorFactors: - ret_str += " " + str(f) - ret_str += ")" - return ret_str - - def type_of(self): - return TealType.uint64 - - def has_return(self): - return False - - -WideRatio.__module__ = "pyteal" - - -class WideUint128(LeafExpr, metaclass=ABCMeta): - @abstractmethod - def __init__(self, outputNum: int): - super().__init__() - if outputNum <= 0: - raise TealInputError("number of output slot should be positive") - self.output_slots = [ScratchSlot() for _ in range(outputNum)] + return WideUint128MinusU64(self, other) - def __add__(self, other: Expr): + def __mul__(self, other: Expr): if isinstance(other, WideUint128): - return self.addU128(other) + return self.mulU128(other) else: require_type(other, TealType.uint64) - return self.addU64(other) + return self.mulU64(other) - def addU128(self, other: "WideUint128"): + def mulU128(self, other: "WideUint128"): if not isinstance(other, WideUint128) or len(other.output_slots) != 2: - raise TealInputError("expected WideUint128 input for addU128") + raise TealInputError("expected WideUint128 input for mulU128") - class WideUint128AddU128(WideUint128): + class WideUint128MulU128(WideUint128): def __init__(self, lhs: WideUint128, arg: WideUint128): WideUint128.__init__(self, 2) self.term = arg @@ -469,19 +141,19 @@ def __init__(self, lhs: WideUint128, arg: WideUint128): def __teal__(self, options: "CompileOptions"): lhsSrt, lhsEnd = self.lhs.__teal__(options) - addSrt, addEnd = addU128(self, self.term, options) - lhsEnd.setNextBlock(addSrt) - return lhsSrt, addEnd + mulSrt, mulEnd = multU128(self, self.term, options) + lhsEnd.setNextBlock(mulSrt) + return lhsSrt, mulEnd def __str__(self) -> str: - return "(addW {} {})".format(self.lhs, self.term) + return "(mulW {} {})".format(self.lhs, self.term) - return WideUint128AddU128(self, other) + return WideUint128MulU128(self, other) - def addU64(self, other: Expr): + def mulU64(self, other: Expr): require_type(other, TealType.uint64) - class WideUint128AddU64(WideUint128): + class WideUint128MulU64(WideUint128): def __init__(self, lhs: WideUint128, arg: Expr): WideUint128.__init__(self, 2) self.term = arg @@ -489,105 +161,9 @@ def __init__(self, lhs: WideUint128, arg: Expr): def __teal__(self, options: "CompileOptions"): lhsSrt, lhsEnd = self.lhs.__teal__(options) - addSrt, addEnd = addU128U64(self, self.term, options) - lhsEnd.setNextBlock(addSrt) - return lhsSrt, addEnd - - def __str__(self) -> str: - return "(addW {} {})".format(self.lhs, self.term) - - return WideUint128AddU64(self, other) - - def __sub__(self, other: Expr): - if isinstance(other, WideUint128): - return self.minusU128(other) - else: - require_type(other, TealType.uint64) - return self.minusU64(other) - - def minusU128(self, other: "WideUint128"): - if not isinstance(other, WideUint128) or len(other.output_slots) != 2: - raise TealInputError("expected WideUint128 input for minusU128") - - class WideUint128MinusU128(WideUint128): - def __init__(self, lhs: WideUint128, arg: WideUint128): - WideUint128.__init__(self, 2) - self.term = arg - self.lhs = lhs - - def __teal__(self, options: "CompileOptions"): - lhsSrt, lhsEnd = self.lhs.__teal__(options) - subSrt, subEnd = substractU128(self, self.term, options) - lhsEnd.setNextBlock(subSrt) - return lhsSrt, subEnd - - def __str__(self) -> str: - return "(minusW {} {})".format(self.lhs, self.term) - - return WideUint128MinusU128(self, other) - - def minusU64(self, other: Expr): - require_type(other, TealType.uint64) - - class WideUint128MinusU64(WideUint128): - def __init__(self, lhs: WideUint128, arg: Expr): - WideUint128.__init__(self, 2) - self.term = arg - self.lhs = lhs - - def __teal__(self, options: "CompileOptions"): - lhsSrt, lhsEnd = self.lhs.__teal__(options) - subSrt, subEnd = substractU128U64(self, self.term, options) - lhsEnd.setNextBlock(subSrt) - return lhsSrt, subEnd - - def __str__(self) -> str: - return "(minusW {} {})".format(self.lhs, self.term) - - return WideUint128MinusU64(self, other) - - def __mul__(self, other: Expr): - if isinstance(other, WideUint128): - return self.mulU128(other) - else: - require_type(other, TealType.uint64) - return self.mulU64(other) - - def mulU128(self, other: "WideUint128"): - if not isinstance(other, WideUint128) or len(other.output_slots) != 2: - raise TealInputError("expected WideUint128 input for mulU128") - - class WideUint128MulU128(WideUint128): - def __init__(self, lhs: WideUint128, arg: WideUint128): - WideUint128.__init__(self, 2) - self.term = arg - self.lhs = lhs - - def __teal__(self, options: "CompileOptions"): - lhsSrt, lhsEnd = self.lhs.__teal__(options) - mulSrt, mulEnd = multU128(self, self.term, options) - lhsEnd.setNextBlock(mulSrt) - return lhsSrt, mulEnd - - def __str__(self) -> str: - return "(mulW {} {})".format(self.lhs, self.term) - - return WideUint128MulU128(self, other) - - def mulU64(self, other: Expr): - require_type(other, TealType.uint64) - - class WideUint128MulU64(WideUint128): - def __init__(self, lhs: WideUint128, arg: Expr): - WideUint128.__init__(self, 2) - self.term = arg - self.lhs = lhs - - def __teal__(self, options: "CompileOptions"): - lhsSrt, lhsEnd = self.lhs.__teal__(options) - mulSrt, mulEnd = multU128U64(self, self.term, options) - lhsEnd.setNextBlock(mulSrt) - return lhsSrt, mulEnd + mulSrt, mulEnd = multU128U64(self, self.term, options) + lhsEnd.setNextBlock(mulSrt) + return lhsSrt, mulEnd def __str__(self) -> str: return "(mulW {} {})".format(self.lhs, self.term) @@ -760,137 +336,569 @@ def __teal__( termEnd.setNextBlock(divmodW) return lhsSrt, divmodW - def __str__(self) -> str: - return "(divmodW {} {})".format(self.lhs, self.term) + def __str__(self) -> str: + return "(divmodW {} {})".format(self.lhs, self.term) + + return WideUint128DivmodW(self, other) + + def type_of(self) -> TealType: + return TealType.uint64 if len(self.output_slots) == 1 else TealType.none + + def toUint64(self) -> Expr: + if self.type_of() == TealType.uint64: + raise TealInternalError("expression is already evaluated to uint64") + elif len(self.output_slots) > 2: + raise TealInternalError("expression is only appliable for uint128") + + class WideUint128ToUint64(Expr): + def __init__(self, wideArith: WideUint128): + super().__init__() + self.wideArith = wideArith + + def type_of(self) -> TealType: + return TealType.uint64 + + def has_return(self) -> bool: + return False + + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealBlock, TealSimpleBlock]: + arithSrt, arithEnd = self.wideArith.__teal__(options) + reducer = TealSimpleBlock( + [ + TealOp(self, Op.swap), + TealOp(self, Op.logic_not), + TealOp(self, Op.assert_), + ] + ) + arithEnd.setNextBlock(reducer) + return arithSrt, reducer + + def __str__(self) -> str: + return "(toUint64 {})".format(self.wideArith) + + return WideUint128ToUint64(self) + + def toBinary(self) -> Expr: + if self.type_of() == TealType.uint64: + raise TealInternalError("expression is already evaluated to uint64") + elif len(self.output_slots) > 2: + raise TealInternalError("expression is only applicable for uint128") + + class WideUint128ToBinary(Expr): + def __init__(self, wideArith: WideUint128): + super().__init__() + self.wideArith = wideArith + + def type_of(self) -> TealType: + return TealType.bytes + + def has_return(self) -> bool: + return False + + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealBlock, TealSimpleBlock]: + arithSrt, arithEnd = self.wideArith.__teal__(options) + reducer = TealSimpleBlock( + [ + TealOp(self, Op.itob), + TealOp(self, Op.swap), + TealOp(self, Op.itob), + TealOp(self, Op.swap), + TealOp(self, Op.concat), + ] + ) + arithEnd.setNextBlock(reducer) + return arithSrt, reducer + + def __str__(self) -> str: + return "(toBinary {})".format(self.wideArith) + + return WideUint128ToBinary(self) + + def reduceTo(self, reducer: Callable[..., Expr]): + if self.type_of() == TealType.uint64: + raise TealInternalError("expression is already evaluated to uint64") + + class WideUint128Reduced(Expr): + def __init__(self, wideArith: WideUint128): + super().__init__() + self.wideArith = wideArith + + argsLoaded = [ + slot.load(TealType.uint64) for slot in self.wideArith.output_slots + ] + self.reduceExpr = reducer(argsLoaded) + + def __str__(self) -> str: + return "(reduced {})".format(self.wideArith) + + def __teal__( + self, options: "CompileOptions" + ) -> Tuple[TealBlock, TealSimpleBlock]: + srt = TealSimpleBlock([]) + curEnd = srt + for slot in reversed(self.wideArith.output_slots): + store = slot.store() + storeSrt, storeEnd = store.__teal__(options) + curEnd.setNextBlock(storeSrt) + curEnd = storeEnd + reduceSrt, reduceEnd = self.reduceExpr.__teal__(options) + curEnd.setNextBlock(reduceSrt) + return srt, reduceEnd + + def type_of(self) -> TealType: + return self.reduceExpr.type_of() + + def has_return(self) -> bool: + return False + + return WideUint128Reduced(self) + + +WideUint128.__module__ = "pyteal" + +# (A + B - C) * D +# sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Seq(Assert(Not(high)), low)) # alias as .to64Bits() +# sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Concat(Itob(high), Itob(low))) # alias as .toBinary() + +# (A + B) - (C * D) +# sumW(A, B).minus(mulW(C, D)).toUint64() +# (sumW(A, B) - prodW(C, D)).toUint64() + +""" THIS PART GOES TO """ +""" THIS PART IS NOT TOUCHED """ + + +def addU128U64( + expr: Expr, term: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + termSrt, termEnd = term.__teal__(options) + # stack is [..., A, B, C], where C is current term + # need to pop all A, B, C from stack and push X, Y, where X and Y are: + # X * 2 ** 64 + Y = (A * 2 ** 64) + (B + C) + # <=> X * 2 ** 64 + Y = (A + highword(B + C)) * 2 ** 64 + lowword(B + C) + # <=> X = A + highword(B + C) + # Y = lowword(B + C) + addition = TealSimpleBlock( + [ + # stack: [..., A, highword(B + C), lowword(B + C)] + TealOp(expr, Op.addw), + # stack: [..., lowword(B + C), A, highword(B + C)] + TealOp(expr, Op.cover, 2), + # stack: [..., lowword(B + C), A + highword(B + C)] + TealOp(expr, Op.add), + # stack: [..., A + highword(B + C), lowword(B + C)] + TealOp(expr, Op.uncover, 1), + ] + ) + termEnd.setNextBlock(addition) + return termSrt, addition + + +def addTerms( + expr: Expr, terms: List[Expr], options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + if len(terms) == 0: + raise TealInternalError("Received 0 terms") + + for term in terms: + require_type(term, TealType.uint64) + + start = TealSimpleBlock([]) + + term0srt, term0end = terms[0].__teal__(options) + + if len(terms) == 1: + highword = TealSimpleBlock([TealOp(expr, Op.int, 0)]) + + start.setNextBlock(highword) + highword.setNextBlock(term0srt) + end = term0end + else: + start.setNextBlock(term0srt) + + term1srt, term1end = terms[1].__teal__(options) + term0end.setNextBlock(term1srt) + addFirst2 = TealSimpleBlock([TealOp(expr, Op.addw)]) + term1end.setNextBlock(addFirst2) + end = addFirst2 + + for term in terms[2:]: + termSrt, added = addU128U64(expr, term, options) + end.setNextBlock(termSrt) + end = added + + return start, end + + +def addU128( + expr: Expr, term: Expr, options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + start = TealSimpleBlock([]) + termSrt, termEnd = term.__teal__(options) + # stack: [..., A, B, C, D] + addition = TealSimpleBlock( + [ + # stack: [..., A, C, D, B] + TealOp(expr, Op.uncover, 2), + # stack: [..., A, C, B, D] + TealOp(expr, Op.swap), + # stack: [..., A, C, highword(B + D), lowword(B + D)] + TealOp(expr, Op.addw), + # stack: [..., lowword(B + D), A, C, highword(B + D)] + TealOp(expr, Op.cover, 3), + # stack: [..., lowword(B + D), highword(B + D), A, C] + TealOp(expr, Op.cover, 2), + # stack: [..., lowword(B + D), highword(B + D), A + C] + TealOp(expr, Op.add), + # stack: [..., lowword(B + D), highword(B + D) + A + C] + TealOp(expr, Op.add), + # stack: [..., highword(B + D) + A + C, lowword(B + D)] + TealOp(expr, Op.swap), + ] + ) + start.setNextBlock(termSrt) + termEnd.setNextBlock(addition) + return start, addition + + +def __substrctU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + # stack: [..., A, B, C], where A * 2 ** 64 + B is 128 bit uint, C is uint64 + subtractPrep = TealSimpleBlock( + [ + # stack: [..., A, B, C, B, C] + TealOp(expr, Op.dup2), + # stack: [..., A, B, C, B >= C] + TealOp(expr, Op.gt), + ] + ) + subtractCond = TealConditionalBlock([]) + subtractTrueBlock = TealSimpleBlock( + [ + # stack: [..., A, B - C] + TealOp(expr, Op.minus) + ] + ) + + subtractFalseBlock = TealSimpleBlock( + [ + # stack: [..., B, C, A] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, C, A, A] + TealOp(expr, Op.dup), + # stack: [..., B, C, A, A, 1] + TealOp(expr, Op.int, 1), + # stack: [..., B, C, A, A >= 1], + TealOp(expr, Op.gt), + # stack: [..., B, C, A] + TealOp(expr, Op.assert_), + # stack: [..., B, C, A, 1] + TealOp(expr, Op.int, 1), + # stack: [..., B, C, A - 1] + TealOp(expr, Op.minus), + # stack: [..., A - 1, B, C] + TealOp(expr, Op.cover, 2), + # stack: [..., A - 1, C, B] + TealOp(expr, Op.cover, 1), + # stack: [..., A - 1, C - B] + TealOp(expr, Op.minus), + # stack: [..., A - 1, 2^64 - 1 - (C - B)] + TealOp(expr, Op.bitwise_not), + # stack: [..., A - 1, 2^64 - 1 - (C - B), 1] + TealOp(expr, Op.int, 1), + # stack: [..., A - 1, 2^64 - (C - B)] + TealOp(expr, Op.add), + ] + ) + subtractPrep.setNextBlock(subtractCond) + subtractCond.setTrueBlock(subtractTrueBlock) + subtractCond.setFalseBlock(subtractFalseBlock) + + end = TealSimpleBlock([]) + subtractTrueBlock.setNextBlock(end) + subtractFalseBlock.setNextBlock(end) + return subtractPrep, end + + +def subtractU128U64( + expr: Expr, rhsTerm: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + termSrt, termEnd = rhsTerm.__teal__(options) + subsSrt, subsEnd = __substrctU128U64(expr) + termEnd.setNextBlock(subsSrt) + return termSrt, subsEnd + + +def subtractU128( + expr: Expr, rhsTerm: Expr, options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + start = TealSimpleBlock([]) + rhsSrt, rhsEnd = rhsTerm.__teal__(options) + start.setNextBlock(rhsSrt) + rhsEnd.setNextBlock(rhsSrt) + # stack: [..., A, B, C, D] + highwordPrep = TealSimpleBlock( + [ + # stack: [..., B, C, D, A] + TealOp(expr, Op.uncover, 3), + # stack: [..., B, D, A, C] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, D, A, C, A, C] + TealOp(expr, Op.dup2), + # stack: [..., B, D, A, C, A >= C] + TealOp(expr, Op.gt), + # stack: [..., B, D, A, C] + TealOp(expr, Op.assert_), + # stack: [..., B, D, A - C] + TealOp(expr, Op.minus), + # stack: [..., A - C, B, D] + TealOp(expr, Op.cover, 2), + ] + ) + rhsEnd.setNextBlock(highwordPrep) + subsSrt, subsEnd = __substrctU128U64(expr) + highwordPrep.setNextBlock(subsSrt) + return start, subsEnd + + +def __multU128U64(expr: Expr) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + srt = TealSimpleBlock([]) + # stack: [..., A, B, C] + multiply = TealSimpleBlock( + [ + # stack: [..., B, C, A] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, C, A, C] + TealOp(expr, Op.dig, 1), + # stack: [..., B, C, A*C] + TealOp(expr, Op.mul), + # stack: [..., A*C, B, C] + TealOp(expr, Op.cover, 2), + # stack: [..., A*C, highword(B*C), lowword(B*C)] + TealOp(expr, Op.mulw), + # stack: [..., lowword(B*C), A*C, highword(B*C)] + TealOp(expr, Op.cover, 2), + # stack: [..., lowword(B*C), A*C+highword(B*C)] + TealOp(expr, Op.add), + # stack: [..., A*C+highword(B*C), lowword(B*C)] + TealOp(expr, Op.swap), + ] + ) + end = TealSimpleBlock([]) + srt.setNextBlock(multiply) + multiply.setNextBlock(end) + return srt, end + + +def multU128U64( + expr: Expr, factor: Expr, options: "CompileOptions" +) -> Tuple[TealBlock, TealSimpleBlock]: + facSrt, facEnd = factor.__teal__(options) + # stack is [..., A, B, C], where C is current factor + # need to pop all A,B,C from stack and push X,Y, where X and Y are: + # X * 2**64 + Y = (A * 2**64 + B) * C + # <=> X * 2**64 + Y = A * C * 2**64 + B * C + # <=> X = A * C + highword(B * C) + # Y = lowword(B * C) + multSrt, multEnd = __multU128U64(expr) + facEnd.setNextBlock(multSrt) + return facSrt, multEnd + + +def multiplyFactors( + expr: Expr, factors: List[Expr], options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + if len(factors) == 0: + raise TealInternalError("Received 0 factors") + + for factor in factors: + require_type(factor, TealType.uint64) + + start = TealSimpleBlock([]) + + fac0Start, fac0End = factors[0].__teal__(options) - return WideUint128DivmodW(self, other) + if len(factors) == 1: + # need to use 0 as high word + highword = TealSimpleBlock([TealOp(expr, Op.int, 0)]) - def type_of(self) -> TealType: - return TealType.uint64 if len(self.output_slots) == 1 else TealType.none + start.setNextBlock(highword) + highword.setNextBlock(fac0Start) - def toUint64(self) -> Expr: - if self.type_of() == TealType.uint64: - raise TealInternalError("expression is already evaluated to uint64") - elif len(self.output_slots) > 2: - raise TealInternalError("expression is only appliable for uint128") + end = fac0End + else: + start.setNextBlock(fac0Start) - class WideUint128ToUint64(Expr): - def __init__(self, wideArith: WideUint128): - super().__init__() - self.wideArith = wideArith + fac1Start, fac1End = factors[1].__teal__(options) + fac0End.setNextBlock(fac1Start) - def type_of(self) -> TealType: - return TealType.uint64 + multiplyFirst2 = TealSimpleBlock([TealOp(expr, Op.mulw)]) + fac1End.setNextBlock(multiplyFirst2) - def has_return(self) -> bool: - return False + end = multiplyFirst2 + for factor in factors[2:]: + facSrt, multed = multU128U64(expr, factor, options) + end.setNextBlock(facSrt) + end = multed - def __teal__( - self, options: "CompileOptions" - ) -> Tuple[TealBlock, TealSimpleBlock]: - arithSrt, arithEnd = self.wideArith.__teal__(options) - reducer = TealSimpleBlock( - [ - TealOp(self, Op.swap), - TealOp(self, Op.logic_not), - TealOp(self, Op.assert_), - ] - ) - arithEnd.setNextBlock(reducer) - return arithSrt, reducer + return start, end - def __str__(self) -> str: - return "(toUint64 {})".format(self.wideArith) - return WideUint128ToUint64(self) +def multU128( + expr: Expr, rhsFactor: Expr, options: "CompileOptions" +) -> Tuple[TealSimpleBlock, TealSimpleBlock]: + start = TealSimpleBlock([]) + rhsSrt, rhsEnd = rhsFactor.__teal__(options) + start.setNextBlock(rhsSrt) + # stack: [..., A, B, C, D] + multPrep = TealSimpleBlock( + [ + # stack: [..., B, C, D, A] + TealOp(expr, Op.uncover, 3), + # stack: [..., B, D, A, C] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, D, A, C, A, C] + TealOp(expr, Op.dup2), + # stack: [..., B, D, A, C, A * C], if mul overflow, then u128 mult will also overflow + TealOp(expr, Op.mul), + # stack: [..., B, D, A, C, A * C != 0] + TealOp(expr, Op.logic_not), + # stack: [..., B, D, A, C], at least one of A and C is 0 + TealOp(expr, Op.assert_), + # stack: [..., B, D, A, C, C] + TealOp(expr, Op.dup), + # stack: [..., B, D, C, C, A] + TealOp(expr, Op.uncover, 2), + # stack: [..., B, D, C, C + A] + TealOp(expr, Op.add), + # stack: [..., B, D, C, C + A, C + A] + TealOp(expr, Op.dup), + # stack: [..., B, D, C + A, C, C + A] + TealOp(expr, Op.cover, 2), + # stack: [..., B, D, C + A, C == C + A] decide C + A should be swapped to before B or D + TealOp(expr, Op.eq), + ] + ) + rhsEnd.setNextBlock(multPrep) - def toBinary(self) -> Expr: - if self.type_of() == TealType.uint64: - raise TealInternalError("expression is already evaluated to uint64") - elif len(self.output_slots) > 2: - raise TealInternalError("expression is only appliable for uint128") + multCond = TealConditionalBlock([]) + multPrep.setNextBlock(multCond) - class WideUint128ToBinary(Expr): - def __init__(self, wideArith: WideUint128): - super().__init__() - self.wideArith = wideArith + # stack: [..., B, D, C] + multCondTrue = TealSimpleBlock( + [ + # stack: [..., B, C, D] + TealOp(expr, Op.swap), + # stack: [..., D, B, C] + TealOp(expr, Op.cover, 2), + # stack: [..., C, D, B] + TealOp(expr, Op.cover, 2), + ] + ) + # stack: [..., B, D, A] + multCondFalse = TealSimpleBlock( + [ + # stack: [..., A, B, D] + TealOp(expr, Op.cover, 2) + ] + ) + multCond.setTrueBlock(multCondTrue) + multCond.setFalseBlock(multCondFalse) - def type_of(self) -> TealType: - return TealType.bytes + # stack: [..., C, D, B] or [..., A, B, D] + multSrt, multEnd = __multU128U64(expr) + multCondTrue.setNextBlock(multSrt) + multCondFalse.setNextBlock(multSrt) - def has_return(self) -> bool: - return False + return start, multEnd - def __teal__( - self, options: "CompileOptions" - ) -> Tuple[TealBlock, TealSimpleBlock]: - arithSrt, arithEnd = self.wideArith.__teal__(options) - reducer = TealSimpleBlock( - [ - TealOp(self, Op.itob), - TealOp(self, Op.swap), - TealOp(self, Op.itob), - TealOp(self, Op.swap), - TealOp(self, Op.concat), - ] - ) - arithEnd.setNextBlock(reducer) - return arithSrt, reducer - def __str__(self) -> str: - return "(toBinary {})".format(self.wideArith) +class WideRatio(Expr): + """A class used to calculate expressions of the form :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)` - return WideUint128ToBinary(self) + Use this class if all inputs to the expression are uint64s, the output fits in a uint64, and all + intermediate values fit in a uint128. + """ - def reduceTo(self, reducer: Callable[..., Expr]): - if self.type_of() == TealType.uint64: - raise TealInternalError("expression is already evaluated to uint64") + def __init__( + self, numeratorFactors: List[Expr], denominatorFactors: List[Expr] + ) -> None: + """Create a new WideRatio expression with the given numerator and denominator factors. - class WideUint128Reduced(Expr): - def __init__(self, wideArith: WideUint128): - super().__init__() - self.wideArith = wideArith + This will calculate :code:`(N_1 * N_2 * N_3 * ...) / (D_1 * D_2 * D_3 * ...)`, where each + :code:`N_i` represents an element in :code:`numeratorFactors` and each :code:`D_i` + represents an element in :code:`denominatorFactors`. - argsLoaded = [ - slot.load(TealType.uint64) for slot in self.wideArith.output_slots - ] - self.reduceExpr = reducer(argsLoaded) + Requires program version 5 or higher. - def __str__(self) -> str: - return "(reduced {})".format(self.wideArith) + Args: + numeratorFactors: The factors in the numerator of the ratio. This list must have at + least 1 element. If this list has exactly 1 element, then denominatorFactors must + have more than 1 element (otherwise basic division should be used). + denominatorFactors: The factors in the denominator of the ratio. This list must have at + least 1 element. + """ + super().__init__() + if len(numeratorFactors) == 0 or len(denominatorFactors) == 0: + raise TealInternalError( + "At least 1 factor must be present in the numerator and denominator" + ) + if len(numeratorFactors) == 1 and len(denominatorFactors) == 1: + raise TealInternalError( + "There is only a single factor in the numerator and denominator. Use basic division instead." + ) + self.numeratorFactors = numeratorFactors + self.denominatorFactors = denominatorFactors - def __teal__( - self, options: "CompileOptions" - ) -> Tuple[TealBlock, TealSimpleBlock]: - srt = TealSimpleBlock([]) - curEnd = srt - for slot in reversed(self.wideArith.output_slots): - store = slot.store() - storeSrt, storeEnd = store.__teal__(options) - curEnd.setNextBlock(storeSrt) - curEnd = storeEnd - reduceSrt, reduceEnd = self.reduceExpr.__teal__(options) - curEnd.setNextBlock(reduceSrt) - return srt, reduceEnd + def __teal__(self, options: "CompileOptions"): + if options.version < Op.cover.min_version: + raise TealCompileError( + "WideRatio requires program version {} or higher".format( + Op.cover.min_version + ), + self, + ) - def type_of(self) -> TealType: - return self.reduceExpr.type_of() + numStart, numEnd = multiplyFactors(self, self.numeratorFactors, options) + denomStart, denomEnd = multiplyFactors(self, self.denominatorFactors, options) + numEnd.setNextBlock(denomStart) - def has_return(self) -> bool: - return False + combine = TealSimpleBlock( + [ + TealOp(self, Op.divmodw), + TealOp(self, Op.pop), # pop remainder low word + TealOp(self, Op.pop), # pop remainder high word + TealOp(self, Op.swap), # swap quotient high and low words + TealOp(self, Op.logic_not), + TealOp(self, Op.assert_), # assert quotient high word is 0 + # end with quotient low word remaining on the stack + ] + ) + denomEnd.setNextBlock(combine) - return WideUint128Reduced(self) + return numStart, combine + + def __str__(self): + ret_str = "(WideRatio (*" + for f in self.numeratorFactors: + ret_str += " " + str(f) + ret_str += ") (*" + for f in self.denominatorFactors: + ret_str += " " + str(f) + ret_str += ")" + return ret_str + def type_of(self): + return TealType.uint64 -WideUint128.__module__ = "pyteal" + def has_return(self): + return False -# (A + B - C) * D -# sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Seq(Assert(Not(high)), low)) # alias as .to64Bits() -# sumW(A, B).minus(C).mul(D).reduce(lambda high, low: Concat(Itob(high), Itob(low))) # alias as .toBinary() -# (A + B) - (C * D) -# sumW(A, B).minus(mulW(C, D)).toUint64() -# (sumW(A, B) - prodW(C, D)).toUint64() +WideRatio.__module__ = "pyteal" def sumW(*terms: Expr): From 7f542382bf19aed944a7236c368ae3ee19fb12b4 Mon Sep 17 00:00:00 2001 From: PabloLION <36828324+PabloLION@users.noreply.github.com> Date: Mon, 26 Sep 2022 02:14:36 +0200 Subject: [PATCH 31/31] tag: rebase to PR#543