From a3637566ceb9926aa1dca1d583f5bc64b073a2e5 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 12:32:23 -0500 Subject: [PATCH 01/18] update method, need some improvements --- pyteal/ast/__init__.py | 2 ++ pyteal/ast/method.py | 37 +++++++++++++++++++++++++++++++++++++ pyteal/ast/method_test.py | 11 +++++++++++ pyteal/ast/subroutine.py | 3 +++ pyteal/ir/ops.py | 3 ++- 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 pyteal/ast/method.py create mode 100644 pyteal/ast/method_test.py diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 9712dc76d..35b109646 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -6,6 +6,7 @@ from .addr import Addr from .bytes import Bytes from .int import Int, EnumInt +from .method import Method # properties from .arg import Arg @@ -126,6 +127,7 @@ "Bytes", "Int", "EnumInt", + "Method", "Arg", "TxnType", "TxnField", diff --git a/pyteal/ast/method.py b/pyteal/ast/method.py new file mode 100644 index 000000000..fac09a32f --- /dev/null +++ b/pyteal/ast/method.py @@ -0,0 +1,37 @@ +from typing import TYPE_CHECKING + +from pyteal.types import TealType + +from ..types import TealType +from ..ir import TealOp, Op, TealBlock +from .leafexpr import LeafExpr + +if TYPE_CHECKING: + from ..compiler import CompileOptions + + +class Method(LeafExpr): + """An expression that represents an ABI method selector""" + + def __init__(self, methodName: str) -> None: + """Create a new method selector for ABI method call. + + Args: + methodName: A string containing a valid ABI method signature + """ + super().__init__() + # TODO do we need to check method name validity? + self.methodName = methodName + + def __teal__(self, options: "CompileOptions"): + op = TealOp(self, Op.method, self.methodName) + return TealBlock.FromOp(options, op) + + def __str__(self) -> str: + return "(method: {})".format(self.methodName) + + def type_of(self) -> TealType: + return TealType.bytes + + +Method.__module__ = "pyteal" diff --git a/pyteal/ast/method_test.py b/pyteal/ast/method_test.py new file mode 100644 index 000000000..492bef32e --- /dev/null +++ b/pyteal/ast/method_test.py @@ -0,0 +1,11 @@ +import pytest + +from .. import * + + +def test_method(): + pass + + +def test_method_invalid(): + pass diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 5c1a2111e..4765c1e27 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -14,6 +14,9 @@ from ..compiler import CompileOptions +# TODO also need to support method here, see first argument is hash of four bytes. + + class SubroutineDefinition: nextSubroutineId = 0 diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 19f3b5457..317a1cf07 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -1,4 +1,4 @@ -from typing import NamedTuple +from typing import NamedTuple, Optional from enum import Enum, Flag, auto @@ -74,6 +74,7 @@ def min_version(self) -> int: bytec_3 = OpType("bytec_3", Mode.Signature | Mode.Application, 2) byte = OpType("byte", Mode.Signature | Mode.Application, 2) addr = OpType("addr", Mode.Signature | Mode.Application, 2) + method = OpType("method", Mode.Signature | Mode.Application, 2) arg = OpType("arg", Mode.Signature, 2) txn = OpType("txn", Mode.Signature | Mode.Application, 2) global_ = OpType("global", Mode.Signature | Mode.Application, 2) From 4286afc3ea81474fc09033faa8f40320ee171c99 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 12:54:14 -0500 Subject: [PATCH 02/18] cleanup --- pyteal/ir/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 317a1cf07..4773f6f59 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -1,4 +1,4 @@ -from typing import NamedTuple, Optional +from typing import NamedTuple from enum import Enum, Flag, auto From f5467e182a9a529ee92d718402fb684eae6cffe7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 14:25:39 -0500 Subject: [PATCH 03/18] update method impl and test --- pyteal/ast/method.py | 8 +++++++- pyteal/ast/method_test.py | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/method.py b/pyteal/ast/method.py index fac09a32f..38baafc64 100644 --- a/pyteal/ast/method.py +++ b/pyteal/ast/method.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING +from pyteal.errors import TealInputError from pyteal.types import TealType @@ -20,7 +21,12 @@ def __init__(self, methodName: str) -> None: methodName: A string containing a valid ABI method signature """ super().__init__() - # TODO do we need to check method name validity? + if type(methodName) is not str: + raise TealInputError( + "invalid input type {} to Method".format(type(methodName)) + ) + elif len(methodName) == 0: + raise TealInputError("invalid input empty string to Method") self.methodName = methodName def __teal__(self, options: "CompileOptions"): diff --git a/pyteal/ast/method_test.py b/pyteal/ast/method_test.py index 492bef32e..1d5c11fe0 100644 --- a/pyteal/ast/method_test.py +++ b/pyteal/ast/method_test.py @@ -1,11 +1,25 @@ import pytest +from pyteal.ast.method import Method + from .. import * def test_method(): - pass + expr = Method("add(uint64,uint64)uint64") + assert expr.type_of() == TealType.bytes + + expected = TealSimpleBlock([TealOp(expr, Op.method, "add(uint64,uint64)uint64")]) + actual, _ = expr.__teal__(CompileOptions()) + assert expected == actual def test_method_invalid(): - pass + with pytest.raises(TealInputError): + Method(114514) + + with pytest.raises(TealInputError): + Method(["m0()void", "m1()uint64"]) + + with pytest.raises(TealInputError): + Method("") From a111aa0ee9cde84040ffac826c0cbbed2c9b2100 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 15:26:25 -0500 Subject: [PATCH 04/18] update subroutine, support replacing labels with method sig --- pyteal/ast/subroutine.py | 18 ++++++++++++------ pyteal/ir/tealop.py | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 4765c1e27..6f3ba9088 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -14,15 +14,15 @@ from ..compiler import CompileOptions -# TODO also need to support method here, see first argument is hash of four bytes. - - class SubroutineDefinition: nextSubroutineId = 0 def __init__( - self, implementation: Callable[..., Expr], returnType: TealType + self, + implementation: Callable[..., Expr], + returnType: TealType, + methodSignature: str = None, ) -> None: super().__init__() self.id = SubroutineDefinition.nextSubroutineId @@ -56,6 +56,7 @@ def __init__( self.returnType = returnType self.declaration: Optional["SubroutineDeclaration"] = None + self.methodSignature = methodSignature def getDeclaration(self) -> "SubroutineDeclaration": if self.declaration is None: @@ -64,7 +65,11 @@ def getDeclaration(self) -> "SubroutineDeclaration": return self.declaration def name(self) -> str: - return self.implementation.__name__ + return ( + self.implementation.__name__ + if self.methodSignature is None + else self.methodSignature + ) def argumentCount(self) -> int: return len(self.implementationParams) @@ -184,7 +189,7 @@ def mySubroutine(a: Expr, b: Expr) -> Expr: ]) """ - def __init__(self, returnType: TealType) -> None: + def __init__(self, returnType: TealType, methodSignature: str = None) -> None: """Define a new subroutine with the given return type. Args: @@ -192,6 +197,7 @@ def __init__(self, returnType: TealType) -> None: TealType.none indicates that this subroutine does not return any value. """ self.returnType = returnType + self.methodSignature = methodSignature def __call__(self, fnImplementation: Callable[..., Expr]) -> Callable[..., Expr]: subroutine = SubroutineDefinition(fnImplementation, self.returnType) diff --git a/pyteal/ir/tealop.py b/pyteal/ir/tealop.py index 920723181..b136023ed 100644 --- a/pyteal/ir/tealop.py +++ b/pyteal/ir/tealop.py @@ -1,4 +1,4 @@ -from typing import cast, Union, List, Optional, TYPE_CHECKING +from typing import Union, List, Optional, TYPE_CHECKING from .tealcomponent import TealComponent from .labelref import LabelReference From f54013e638d5d1cc3518bc0edcad24829af48e0c Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 15:36:27 -0500 Subject: [PATCH 05/18] minor --- pyteal/ast/subroutine.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 6f3ba9088..73614fe5a 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -10,7 +10,6 @@ from .scratchvar import ScratchVar if TYPE_CHECKING: - from ..ir import TealSimpleBlock from ..compiler import CompileOptions From 091dafe81496d2c8e4d827ede8add4b30918eaa4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 15:48:51 -0500 Subject: [PATCH 06/18] change teal comments to method signatures if subroutine is specified as abi --- pyteal/ast/subroutine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 73614fe5a..cc6660cdf 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -199,7 +199,7 @@ def __init__(self, returnType: TealType, methodSignature: str = None) -> None: self.methodSignature = methodSignature def __call__(self, fnImplementation: Callable[..., Expr]) -> Callable[..., Expr]: - subroutine = SubroutineDefinition(fnImplementation, self.returnType) + subroutine = SubroutineDefinition(fnImplementation, self.returnType, self.methodSignature) @wraps(fnImplementation) def subroutineCall(*args: Expr, **kwargs) -> Expr: From 003771c613f43682710be6ba39786e9258de4f57 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 15:54:29 -0500 Subject: [PATCH 07/18] refmt --- pyteal/ast/subroutine.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index cc6660cdf..38fd92a50 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -199,7 +199,9 @@ def __init__(self, returnType: TealType, methodSignature: str = None) -> None: self.methodSignature = methodSignature def __call__(self, fnImplementation: Callable[..., Expr]) -> Callable[..., Expr]: - subroutine = SubroutineDefinition(fnImplementation, self.returnType, self.methodSignature) + subroutine = SubroutineDefinition( + fnImplementation, self.returnType, self.methodSignature + ) @wraps(fnImplementation) def subroutineCall(*args: Expr, **kwargs) -> Expr: From 45e641493763bea85d8ed18b354a326725cbd023 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 17:52:16 -0500 Subject: [PATCH 08/18] fix method gen-code --- pyteal/ast/method.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/method.py b/pyteal/ast/method.py index 38baafc64..e17a40d2e 100644 --- a/pyteal/ast/method.py +++ b/pyteal/ast/method.py @@ -30,7 +30,7 @@ def __init__(self, methodName: str) -> None: self.methodName = methodName def __teal__(self, options: "CompileOptions"): - op = TealOp(self, Op.method, self.methodName) + op = TealOp(self, Op.method, "\"{}\"".format(self.methodName)) return TealBlock.FromOp(options, op) def __str__(self) -> str: From e743eba323828040907c2fb484cffc925e87c45e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 17:53:31 -0500 Subject: [PATCH 09/18] minor --- pyteal/ast/method_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/method_test.py b/pyteal/ast/method_test.py index 1d5c11fe0..c36ee0892 100644 --- a/pyteal/ast/method_test.py +++ b/pyteal/ast/method_test.py @@ -9,7 +9,7 @@ def test_method(): expr = Method("add(uint64,uint64)uint64") assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.method, "add(uint64,uint64)uint64")]) + expected = TealSimpleBlock([TealOp(expr, Op.method, "\"add(uint64,uint64)uint64\"")]) actual, _ = expr.__teal__(CompileOptions()) assert expected == actual @@ -19,7 +19,7 @@ def test_method_invalid(): Method(114514) with pytest.raises(TealInputError): - Method(["m0()void", "m1()uint64"]) + Method(["\"m0()void\"", "\"m1()uint64\""]) with pytest.raises(TealInputError): Method("") From 4b0397df1136dfd9d5ef1ce7cbe63e8c68d9beb6 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 30 Dec 2021 17:56:11 -0500 Subject: [PATCH 10/18] fmt... --- pyteal/ast/method.py | 2 +- pyteal/ast/method_test.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/method.py b/pyteal/ast/method.py index e17a40d2e..427921397 100644 --- a/pyteal/ast/method.py +++ b/pyteal/ast/method.py @@ -30,7 +30,7 @@ def __init__(self, methodName: str) -> None: self.methodName = methodName def __teal__(self, options: "CompileOptions"): - op = TealOp(self, Op.method, "\"{}\"".format(self.methodName)) + op = TealOp(self, Op.method, '"{}"'.format(self.methodName)) return TealBlock.FromOp(options, op) def __str__(self) -> str: diff --git a/pyteal/ast/method_test.py b/pyteal/ast/method_test.py index c36ee0892..615429adf 100644 --- a/pyteal/ast/method_test.py +++ b/pyteal/ast/method_test.py @@ -9,7 +9,7 @@ def test_method(): expr = Method("add(uint64,uint64)uint64") assert expr.type_of() == TealType.bytes - expected = TealSimpleBlock([TealOp(expr, Op.method, "\"add(uint64,uint64)uint64\"")]) + expected = TealSimpleBlock([TealOp(expr, Op.method, '"add(uint64,uint64)uint64"')]) actual, _ = expr.__teal__(CompileOptions()) assert expected == actual @@ -19,7 +19,7 @@ def test_method_invalid(): Method(114514) with pytest.raises(TealInputError): - Method(["\"m0()void\"", "\"m1()uint64\""]) + Method(['"m0()void"', '"m1()uint64"']) with pytest.raises(TealInputError): Method("") From 9c1825abfacb897455ae872eb47495647d7ebf80 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 13:54:21 -0500 Subject: [PATCH 11/18] update optimization for constants assemble --- pyteal/compiler/constants.py | 39 +++++++++++++++++++++++-------- pyteal/compiler/constants_test.py | 35 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index 6d54e6607..52818673c 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -7,11 +7,7 @@ from ..ir import ( Op, TealOp, - TealLabel, TealComponent, - TealBlock, - TealSimpleBlock, - TealConditionalBlock, ) from ..util import unescapeStr, correctBase32Padding from ..errors import TealInternalError @@ -94,6 +90,20 @@ def extractAddrValue(op: TealOp) -> Union[str, bytes]: return value +def extractMethodValue(op: TealOp) -> bytes: + """Extract the constant value being loaded by a TealOp whose op is Op.method. + + Returns: + The bytes of method selector computed from the method signature that the op is loading. + """ + if len(op.args) != 1 or type(op.args[0]) != str: + raise TealInternalError("Unexpected args in method opcode: {}".format(op.args)) + + methodSignature = cast(str, op.args[0]) + methodSelector = encoding.checksum(bytes(methodSignature, "utf-8"))[:4] + return methodSelector + + def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]: """Convert TEAL code from using pseudo-ops for constants to using assembled constant blocks. @@ -124,6 +134,9 @@ def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]: elif basicOp == Op.addr: addrValue = extractAddrValue(op) byteFreqs[addrValue] = byteFreqs.get(addrValue, 0) + 1 + elif basicOp == Op.method: + methodValue = extractMethodValue(op) + byteFreqs[methodValue] = byteFreqs.get(methodValue, 0) + 1 assembled: List[TealComponent] = [] @@ -177,12 +190,18 @@ def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]: assembled.append(TealOp(op.expr, Op.intc, index, "//", *op.args)) continue - if basicOp == Op.byte or basicOp == Op.addr: - byteValue = ( - extractBytesValue(op) - if basicOp == Op.byte - else extractAddrValue(op) - ) + if basicOp == Op.byte or basicOp == Op.addr or basicOp == Op.method: + if basicOp == Op.byte: + byteValue = extractBytesValue(op) + elif basicOp == Op.addr: + byteValue = extractAddrValue(op) + elif basicOp == Op.method: + byteValue = extractMethodValue(op) + else: + raise TealInternalError( + "Expect a byte-like constant opcode, get {}".format(op) + ) + if byteFreqs[byteValue] == 1: encodedValue = ( ("0x" + byteValue.hex()) diff --git a/pyteal/compiler/constants_test.py b/pyteal/compiler/constants_test.py index 12d97b328..de5426241 100644 --- a/pyteal/compiler/constants_test.py +++ b/pyteal/compiler/constants_test.py @@ -5,6 +5,7 @@ extractBytesValue, extractAddrValue, createConstantBlocks, + extractMethodValue, ) @@ -63,6 +64,31 @@ def test_extractAddrValue(): assert actual == expected +def test_extractMethodValue(): + tests = [ + (TealOp(None, Op.method, "create(uint64)uint64"), b"\x43\x46\x41\x01"), + (TealOp(None, Op.method, "update()void"), b"\xa0\xe8\x18\x72"), + (TealOp(None, Op.method, "optIn(string)string"), b"\xcf\xa6\x8e\x36"), + (TealOp(None, Op.method, "closeOut()string"), b"\xa9\xf4\x2b\x3d"), + (TealOp(None, Op.method, "delete()void"), b"\x24\x37\x8d\x3c"), + (TealOp(None, Op.method, "add(uint64,uint64)uint64"), b"\xfe\x6b\xdf\x69"), + (TealOp(None, Op.method, "empty()void"), b"\xa8\x8c\x26\xa5"), + (TealOp(None, Op.method, "payment(pay,uint64)bool"), b"\x3e\x3b\x3d\x28"), + ( + TealOp( + None, + Op.method, + "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]", + ), + b"\x0d\xf0\x05\x0f", + ), + ] + + for op, expected in tests: + actual = extractMethodValue(op) + assert actual == expected + + def test_createConstantBlocks_empty(): ops = [] @@ -184,12 +210,14 @@ def test_createConstantBlocks_pushbytes(): ops = [ TealOp(None, Op.byte, "0x0102"), TealOp(None, Op.byte, "0x0103"), + TealOp(None, Op.method, "empty()void"), TealOp(None, Op.concat), ] expected = [ TealOp(None, Op.pushbytes, "0x0102", "//", "0x0102"), TealOp(None, Op.pushbytes, "0x0103", "//", "0x0103"), + TealOp(None, Op.pushbytes, "0xa88c26a5", "//", "empty()void"), TealOp(None, Op.concat), ] @@ -240,6 +268,9 @@ def test_createConstantBlocks_byteblock_multiple(): None, Op.addr, "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M" ), TealOp(None, Op.concat), + TealOp(None, Op.method, "closeOut()string"), + TealOp(None, Op.concat), + TealOp(None, Op.byte, "base64(qfQrPQ==)"), ] expected = [ @@ -249,6 +280,7 @@ def test_createConstantBlocks_byteblock_multiple(): "0x0102", "0x74657374", "0xb49276bd3ec0977eab86a321c449ead802c96c0bd97c2956131511d2f11eebec", + "0xa9f42b3d", ), TealOp(None, Op.bytec_0, "//", "0x0102"), TealOp(None, Op.bytec_0, "//", "base64(AQI=)"), @@ -273,6 +305,9 @@ def test_createConstantBlocks_byteblock_multiple(): "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), TealOp(None, Op.concat), + TealOp(None, Op.bytec_3, "//", "closeOut()string"), + TealOp(None, Op.concat), + TealOp(None, Op.bytec_3, "//", "base64(qfQrPQ==)"), ] actual = createConstantBlocks(ops) From 20505c942f714d1c81b6863096d38f4ad494eb45 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 14:23:15 -0500 Subject: [PATCH 12/18] rename methodSignature to nameStr --- pyteal/ast/subroutine.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 38fd92a50..e1532b1f0 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -21,7 +21,7 @@ def __init__( self, implementation: Callable[..., Expr], returnType: TealType, - methodSignature: str = None, + nameStr: str = None, ) -> None: super().__init__() self.id = SubroutineDefinition.nextSubroutineId @@ -55,7 +55,7 @@ def __init__( self.returnType = returnType self.declaration: Optional["SubroutineDeclaration"] = None - self.methodSignature = methodSignature + self.nameStr = nameStr def getDeclaration(self) -> "SubroutineDeclaration": if self.declaration is None: @@ -64,11 +64,7 @@ def getDeclaration(self) -> "SubroutineDeclaration": return self.declaration def name(self) -> str: - return ( - self.implementation.__name__ - if self.methodSignature is None - else self.methodSignature - ) + return self.implementation.__name__ if self.nameStr is None else self.nameStr def argumentCount(self) -> int: return len(self.implementationParams) @@ -188,7 +184,7 @@ def mySubroutine(a: Expr, b: Expr) -> Expr: ]) """ - def __init__(self, returnType: TealType, methodSignature: str = None) -> None: + def __init__(self, returnType: TealType, name: str = None) -> None: """Define a new subroutine with the given return type. Args: @@ -196,12 +192,10 @@ def __init__(self, returnType: TealType, methodSignature: str = None) -> None: TealType.none indicates that this subroutine does not return any value. """ self.returnType = returnType - self.methodSignature = methodSignature + self.name = name def __call__(self, fnImplementation: Callable[..., Expr]) -> Callable[..., Expr]: - subroutine = SubroutineDefinition( - fnImplementation, self.returnType, self.methodSignature - ) + subroutine = SubroutineDefinition(fnImplementation, self.returnType, self.name) @wraps(fnImplementation) def subroutineCall(*args: Expr, **kwargs) -> Expr: From 9f7ec76a167cb6a130da0e11b77bd4de0fa2f55e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 14:27:17 -0500 Subject: [PATCH 13/18] minor, change subroutine definition name mechanism --- pyteal/ast/subroutine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index e1532b1f0..69dd54e8c 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -55,7 +55,7 @@ def __init__( self.returnType = returnType self.declaration: Optional["SubroutineDeclaration"] = None - self.nameStr = nameStr + self.__name = self.implementation.__name__ if nameStr is None else nameStr def getDeclaration(self) -> "SubroutineDeclaration": if self.declaration is None: @@ -64,7 +64,7 @@ def getDeclaration(self) -> "SubroutineDeclaration": return self.declaration def name(self) -> str: - return self.implementation.__name__ if self.nameStr is None else self.nameStr + return self.__name def argumentCount(self) -> int: return len(self.implementationParams) From 95390890dc1caf2721611a6de1ee5a9ae69eece4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 14:58:18 -0500 Subject: [PATCH 14/18] per review --- pyteal/ast/__init__.py | 4 ++-- pyteal/ast/method_test.py | 25 -------------------- pyteal/ast/{method.py => methodsig.py} | 6 ++--- pyteal/ast/methodsig_test.py | 27 ++++++++++++++++++++++ pyteal/compiler/constants.py | 10 +++++--- pyteal/compiler/constants_test.py | 32 +++++++++++++++++--------- pyteal/ir/ops.py | 2 +- 7 files changed, 61 insertions(+), 45 deletions(-) delete mode 100644 pyteal/ast/method_test.py rename pyteal/ast/{method.py => methodsig.py} (88%) create mode 100644 pyteal/ast/methodsig_test.py diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 35b109646..fbc12cdde 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -6,7 +6,7 @@ from .addr import Addr from .bytes import Bytes from .int import Int, EnumInt -from .method import Method +from .methodsig import MethodSignature # properties from .arg import Arg @@ -127,7 +127,7 @@ "Bytes", "Int", "EnumInt", - "Method", + "MethodSignature", "Arg", "TxnType", "TxnField", diff --git a/pyteal/ast/method_test.py b/pyteal/ast/method_test.py deleted file mode 100644 index 615429adf..000000000 --- a/pyteal/ast/method_test.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest - -from pyteal.ast.method import Method - -from .. import * - - -def test_method(): - expr = Method("add(uint64,uint64)uint64") - assert expr.type_of() == TealType.bytes - - expected = TealSimpleBlock([TealOp(expr, Op.method, '"add(uint64,uint64)uint64"')]) - actual, _ = expr.__teal__(CompileOptions()) - assert expected == actual - - -def test_method_invalid(): - with pytest.raises(TealInputError): - Method(114514) - - with pytest.raises(TealInputError): - Method(['"m0()void"', '"m1()uint64"']) - - with pytest.raises(TealInputError): - Method("") diff --git a/pyteal/ast/method.py b/pyteal/ast/methodsig.py similarity index 88% rename from pyteal/ast/method.py rename to pyteal/ast/methodsig.py index 427921397..de9bd9748 100644 --- a/pyteal/ast/method.py +++ b/pyteal/ast/methodsig.py @@ -11,7 +11,7 @@ from ..compiler import CompileOptions -class Method(LeafExpr): +class MethodSignature(LeafExpr): """An expression that represents an ABI method selector""" def __init__(self, methodName: str) -> None: @@ -30,7 +30,7 @@ def __init__(self, methodName: str) -> None: self.methodName = methodName def __teal__(self, options: "CompileOptions"): - op = TealOp(self, Op.method, '"{}"'.format(self.methodName)) + op = TealOp(self, Op.method_signature, '"{}"'.format(self.methodName)) return TealBlock.FromOp(options, op) def __str__(self) -> str: @@ -40,4 +40,4 @@ def type_of(self) -> TealType: return TealType.bytes -Method.__module__ = "pyteal" +MethodSignature.__module__ = "pyteal" diff --git a/pyteal/ast/methodsig_test.py b/pyteal/ast/methodsig_test.py new file mode 100644 index 000000000..6a941ea27 --- /dev/null +++ b/pyteal/ast/methodsig_test.py @@ -0,0 +1,27 @@ +import pytest + +from pyteal.ast.methodsig import MethodSignature + +from .. import * + + +def test_method(): + expr = MethodSignature("add(uint64,uint64)uint64") + assert expr.type_of() == TealType.bytes + + expected = TealSimpleBlock( + [TealOp(expr, Op.method_signature, '"add(uint64,uint64)uint64"')] + ) + actual, _ = expr.__teal__(CompileOptions()) + assert expected == actual + + +def test_method_invalid(): + with pytest.raises(TealInputError): + MethodSignature(114514) + + with pytest.raises(TealInputError): + MethodSignature(['"m0()void"', '"m1()uint64"']) + + with pytest.raises(TealInputError): + MethodSignature("") diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index 52818673c..1b11bdab9 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -134,7 +134,7 @@ def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]: elif basicOp == Op.addr: addrValue = extractAddrValue(op) byteFreqs[addrValue] = byteFreqs.get(addrValue, 0) + 1 - elif basicOp == Op.method: + elif basicOp == Op.method_signature: methodValue = extractMethodValue(op) byteFreqs[methodValue] = byteFreqs.get(methodValue, 0) + 1 @@ -190,12 +190,16 @@ def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]: assembled.append(TealOp(op.expr, Op.intc, index, "//", *op.args)) continue - if basicOp == Op.byte or basicOp == Op.addr or basicOp == Op.method: + if ( + basicOp == Op.byte + or basicOp == Op.addr + or basicOp == Op.method_signature + ): if basicOp == Op.byte: byteValue = extractBytesValue(op) elif basicOp == Op.addr: byteValue = extractAddrValue(op) - elif basicOp == Op.method: + elif basicOp == Op.method_signature: byteValue = extractMethodValue(op) else: raise TealInternalError( diff --git a/pyteal/compiler/constants_test.py b/pyteal/compiler/constants_test.py index de5426241..166f48370 100644 --- a/pyteal/compiler/constants_test.py +++ b/pyteal/compiler/constants_test.py @@ -64,20 +64,30 @@ def test_extractAddrValue(): assert actual == expected +# test case came from: https://gist.github.com/jasonpaulos/99e4f8a75f2fc2ec9b8073c064530359 def test_extractMethodValue(): tests = [ - (TealOp(None, Op.method, "create(uint64)uint64"), b"\x43\x46\x41\x01"), - (TealOp(None, Op.method, "update()void"), b"\xa0\xe8\x18\x72"), - (TealOp(None, Op.method, "optIn(string)string"), b"\xcf\xa6\x8e\x36"), - (TealOp(None, Op.method, "closeOut()string"), b"\xa9\xf4\x2b\x3d"), - (TealOp(None, Op.method, "delete()void"), b"\x24\x37\x8d\x3c"), - (TealOp(None, Op.method, "add(uint64,uint64)uint64"), b"\xfe\x6b\xdf\x69"), - (TealOp(None, Op.method, "empty()void"), b"\xa8\x8c\x26\xa5"), - (TealOp(None, Op.method, "payment(pay,uint64)bool"), b"\x3e\x3b\x3d\x28"), + ( + TealOp(None, Op.method_signature, "create(uint64)uint64"), + b"\x43\x46\x41\x01", + ), + (TealOp(None, Op.method_signature, "update()void"), b"\xa0\xe8\x18\x72"), + (TealOp(None, Op.method_signature, "optIn(string)string"), b"\xcf\xa6\x8e\x36"), + (TealOp(None, Op.method_signature, "closeOut()string"), b"\xa9\xf4\x2b\x3d"), + (TealOp(None, Op.method_signature, "delete()void"), b"\x24\x37\x8d\x3c"), + ( + TealOp(None, Op.method_signature, "add(uint64,uint64)uint64"), + b"\xfe\x6b\xdf\x69", + ), + (TealOp(None, Op.method_signature, "empty()void"), b"\xa8\x8c\x26\xa5"), + ( + TealOp(None, Op.method_signature, "payment(pay,uint64)bool"), + b"\x3e\x3b\x3d\x28", + ), ( TealOp( None, - Op.method, + Op.method_signature, "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]", ), b"\x0d\xf0\x05\x0f", @@ -210,7 +220,7 @@ def test_createConstantBlocks_pushbytes(): ops = [ TealOp(None, Op.byte, "0x0102"), TealOp(None, Op.byte, "0x0103"), - TealOp(None, Op.method, "empty()void"), + TealOp(None, Op.method_signature, "empty()void"), TealOp(None, Op.concat), ] @@ -268,7 +278,7 @@ def test_createConstantBlocks_byteblock_multiple(): None, Op.addr, "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M" ), TealOp(None, Op.concat), - TealOp(None, Op.method, "closeOut()string"), + TealOp(None, Op.method_signature, "closeOut()string"), TealOp(None, Op.concat), TealOp(None, Op.byte, "base64(qfQrPQ==)"), ] diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 4773f6f59..bb5d4043f 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -74,7 +74,7 @@ def min_version(self) -> int: bytec_3 = OpType("bytec_3", Mode.Signature | Mode.Application, 2) byte = OpType("byte", Mode.Signature | Mode.Application, 2) addr = OpType("addr", Mode.Signature | Mode.Application, 2) - method = OpType("method", Mode.Signature | Mode.Application, 2) + method_signature = OpType("method", Mode.Signature | Mode.Application, 2) arg = OpType("arg", Mode.Signature, 2) txn = OpType("txn", Mode.Signature | Mode.Application, 2) global_ = OpType("global", Mode.Signature | Mode.Application, 2) From 3327bd5f654b0b1b6ab3a74468410af9dd3a8241 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 15:10:47 -0500 Subject: [PATCH 15/18] minor rename in constants --- pyteal/compiler/constants.py | 6 +++--- pyteal/compiler/constants_test.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index 1b11bdab9..f03672d2d 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -90,7 +90,7 @@ def extractAddrValue(op: TealOp) -> Union[str, bytes]: return value -def extractMethodValue(op: TealOp) -> bytes: +def extractMethodSigValue(op: TealOp) -> bytes: """Extract the constant value being loaded by a TealOp whose op is Op.method. Returns: @@ -135,7 +135,7 @@ def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]: addrValue = extractAddrValue(op) byteFreqs[addrValue] = byteFreqs.get(addrValue, 0) + 1 elif basicOp == Op.method_signature: - methodValue = extractMethodValue(op) + methodValue = extractMethodSigValue(op) byteFreqs[methodValue] = byteFreqs.get(methodValue, 0) + 1 assembled: List[TealComponent] = [] @@ -200,7 +200,7 @@ def createConstantBlocks(ops: List[TealComponent]) -> List[TealComponent]: elif basicOp == Op.addr: byteValue = extractAddrValue(op) elif basicOp == Op.method_signature: - byteValue = extractMethodValue(op) + byteValue = extractMethodSigValue(op) else: raise TealInternalError( "Expect a byte-like constant opcode, get {}".format(op) diff --git a/pyteal/compiler/constants_test.py b/pyteal/compiler/constants_test.py index 166f48370..8e2804858 100644 --- a/pyteal/compiler/constants_test.py +++ b/pyteal/compiler/constants_test.py @@ -5,7 +5,7 @@ extractBytesValue, extractAddrValue, createConstantBlocks, - extractMethodValue, + extractMethodSigValue, ) @@ -95,7 +95,7 @@ def test_extractMethodValue(): ] for op, expected in tests: - actual = extractMethodValue(op) + actual = extractMethodSigValue(op) assert actual == expected From c02731125fbb24d5cb44951ff8560cd4ae795b3d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 15:20:21 -0500 Subject: [PATCH 16/18] per review --- examples/abi_method_call.teal | 265 ++++++++++++++++++++++++++++++ examples/stuff.py | 229 ++++++++++++++++++++++++++ pyteal/compiler/constants.py | 2 + pyteal/compiler/constants_test.py | 19 ++- 4 files changed, 507 insertions(+), 8 deletions(-) create mode 100644 examples/abi_method_call.teal create mode 100644 examples/stuff.py diff --git a/examples/abi_method_call.teal b/examples/abi_method_call.teal new file mode 100644 index 000000000..2ec57eb7d --- /dev/null +++ b/examples/abi_method_call.teal @@ -0,0 +1,265 @@ +#pragma version 5 +txn ApplicationID +int 0 +== +bnz main_l18 +txn OnCompletion +int UpdateApplication +== +txna ApplicationArgs 0 +byte 0xa0e81872 +== +&& +bnz main_l17 +txn OnCompletion +int OptIn +== +txna ApplicationArgs 0 +byte 0xcfa68e36 +== +&& +bnz main_l16 +txn OnCompletion +int CloseOut +== +txna ApplicationArgs 0 +byte 0xa9f42b3d +== +&& +bnz main_l15 +txn OnCompletion +int DeleteApplication +== +txna ApplicationArgs 0 +byte 0x24378d3c +== +&& +bnz main_l14 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0xfe6bdf69 +== +&& +bnz main_l13 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0xa88c26a5 +== +&& +bnz main_l12 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0x3e3b3d28 +== +&& +bnz main_l11 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0x0df0050f +== +&& +bnz main_l10 +int 0 +return +main_l10: +txna ApplicationArgs 1 +txna ApplicationArgs 2 +txna ApplicationArgs 3 +txna ApplicationArgs 4 +txna ApplicationArgs 5 +txna ApplicationArgs 6 +txna ApplicationArgs 7 +txna ApplicationArgs 8 +txna ApplicationArgs 9 +callsub sub8 +int 1 +return +main_l11: +txna ApplicationArgs 1 +callsub sub7 +int 1 +return +main_l12: +callsub sub6 +int 1 +return +main_l13: +txna ApplicationArgs 1 +txna ApplicationArgs 2 +callsub sub5 +int 1 +return +main_l14: +callsub sub4 +int 1 +return +main_l15: +callsub sub3 +int 1 +return +main_l16: +txna ApplicationArgs 1 +callsub sub2 +int 1 +return +main_l17: +callsub sub1 +int 1 +return +main_l18: +txn NumAppArgs +int 0 +> +bnz main_l20 +main_l19: +int 1 +return +main_l20: +txna ApplicationArgs 0 +byte 0x43464101 +== +assert +txna ApplicationArgs 1 +callsub sub0 +b main_l19 +sub0: // create(uint64)uint64 +store 0 +byte 0x151f7c75 +load 0 +btoi +int 2 +* +itob +concat +log +retsub +sub1: // update()void +retsub +sub2: // optIn(string)string +store 1 +int 0 +byte "name" +load 1 +extract 2 0 +app_local_put +byte "hello " +int 0 +byte "name" +app_local_get +concat +store 2 +byte 0x151f7c75 +load 2 +len +itob +extract 6 2 +concat +load 2 +concat +log +retsub +sub3: // closeOut()string +byte "goodbye " +int 0 +byte "name" +app_local_get +concat +store 3 +byte 0x151f7c75 +load 3 +len +itob +extract 6 2 +concat +load 3 +concat +log +retsub +sub4: // delete()void +txn Sender +global CreatorAddress +== +assert +retsub +sub5: // add(uint64,uint64)uint64 +store 5 +store 4 +byte 0x151f7c75 +load 4 +btoi +load 5 +btoi ++ +itob +concat +log +retsub +sub6: // empty()void +byte "random inconsequential log" +log +retsub +sub7: // payment(pay,uint64)bool +store 6 +txn GroupIndex +int 1 +- +gtxns TypeEnum +int pay +== +assert +byte 0x151f7c75 +txn GroupIndex +int 1 +- +gtxns Amount +load 6 +btoi +== +bnz sub7_l2 +byte 0x00 +b sub7_l3 +sub7_l2: +byte 0x80 +sub7_l3: +concat +log +retsub +sub8: // referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] +store 15 +store 14 +store 13 +store 12 +store 11 +store 10 +store 9 +store 8 +store 7 +byte 0x151f7c75 +load 7 +concat +load 9 +concat +load 11 +concat +load 8 +concat +load 14 +concat +load 15 +concat +load 10 +concat +load 12 +concat +load 13 +concat +log +retsub \ No newline at end of file diff --git a/examples/stuff.py b/examples/stuff.py new file mode 100644 index 000000000..bab8df31f --- /dev/null +++ b/examples/stuff.py @@ -0,0 +1,229 @@ +# using pyteal version 0.9.1 + +from pyteal import * + +# Method selectors +create_method_selector = Bytes("base16", "43464101") +update_method_selector = Bytes("base16", "a0e81872") +optin_method_selector = Bytes("base16", "cfa68e36") +closeout_method_selector = Bytes("base16", "a9f42b3d") +delete_method_selector = Bytes("base16", "24378d3c") +add_method_selector = Bytes("base16", "fe6bdf69") +empty_method_selector = Bytes("base16", "a88c26a5") +payment_method_selector = Bytes("base16", "3e3b3d28") +reference_test_method_selector = Bytes("base16", "0df0050f") + +return_event_selector = Bytes("base16", "151f7c75") + +# Method signature should be: create(uint64)uint64 +@Subroutine(TealType.none, "create(uint64)uint64") +def create(arg): + return Log(Concat(return_event_selector, Itob(Btoi(arg) * Int(2)))) + + +# Method signature should be: update()void +@Subroutine(TealType.none, "update()void") +def update(): + return Seq() + + +# Method signature should be: optIn(string)string +@Subroutine(TealType.none, "optIn(string)string") +def optIn(name): + message = ScratchVar(TealType.bytes) + return Seq( + App.localPut(Int(0), Bytes("name"), Suffix(name, Int(2))), + message.store(Concat(Bytes("hello "), App.localGet(Int(0), Bytes("name")))), + Log( + Concat( + return_event_selector, + Extract(Itob(Len(message.load())), Int(6), Int(2)), + message.load(), + ) + ), + ) + + +# Method signature should be: closeOut()string +@Subroutine(TealType.none, "closeOut()string") +def closeOut(): + message = ScratchVar(TealType.bytes) + return Seq( + message.store(Concat(Bytes("goodbye "), App.localGet(Int(0), Bytes("name")))), + Log( + Concat( + return_event_selector, + Extract(Itob(Len(message.load())), Int(6), Int(2)), + message.load(), + ) + ), + ) + + +# Method signature should be: delete()void +@Subroutine(TealType.none, "delete()void") +def deleteApp(): + return Assert(Txn.sender() == Global.creator_address()) + + +# Method signature should be: add(uint64,uint64)uint64 +@Subroutine(TealType.none, "add(uint64,uint64)uint64") +def add(a, b): + # The app should log the first four bytes of the SHA512/256 hash of the word + # "return" (0x151f7c75), followed by the method return argument. + return Log(Concat(return_event_selector, Itob(Add(Btoi(a), Btoi(b))))) + + +# Method signature should be: empty()void +@Subroutine(TealType.none, "empty()void") +def empty(): + return Log(Bytes("random inconsequential log")) + + +# Method signature should be: payment(pay,uint64)bool +@Subroutine(TealType.none, "payment(pay,uint64)bool") +def payment(amount): + prev = Txn.group_index() - Int(1) # index of the pay transaction + return Seq( + Assert(Gtxn[prev].type_enum() == TxnType.Payment), + Log( + Concat( + return_event_selector, + If(Gtxn[prev].amount() == Btoi(amount)) + .Then(Bytes("base16", "80")) + .Else(Bytes("base16", "00")), + ) + ), + ) + + +# Method signature should be: referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] +@Subroutine( + TealType.none, + "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]", +) +def referenceTest( + account1, + application1, + account2, + asset1, + account3, + asset2, + asset3, + application2, + application3, +): + return Log( + Concat( + return_event_selector, + account1, + account2, + account3, + application1, + application2, + application3, + asset1, + asset2, + asset3, + ) + ) + + +def approval_program(): + program = ( + If(Txn.application_id() == Int(0)) + .Then( + Seq( + If(Txn.application_args.length() > Int(0)).Then( + Seq( + Assert(Txn.application_args[0] == create_method_selector), + create(Txn.application_args[1]), + ) + ), + Approve(), + ) + ) + .ElseIf( + And( + Txn.on_completion() == OnComplete.UpdateApplication, + Txn.application_args[0] == update_method_selector, + ) + ) + .Then(Seq(update(), Approve())) + .ElseIf( + And( + Txn.on_completion() == OnComplete.OptIn, + Txn.application_args[0] == optin_method_selector, + ) + ) + .Then(Seq(optIn(Txn.application_args[1]), Approve())) + .ElseIf( + And( + Txn.on_completion() == OnComplete.CloseOut, + Txn.application_args[0] == closeout_method_selector, + ) + ) + .Then(Seq(closeOut(), Approve())) + .ElseIf( + And( + Txn.on_completion() == OnComplete.DeleteApplication, + Txn.application_args[0] == delete_method_selector, + ) + ) + .Then(Seq(deleteApp(), Approve())) + .ElseIf( + And( # calling the add method + Txn.on_completion() == OnComplete.NoOp, + Txn.application_args[0] == add_method_selector, + ) + ) + .Then(Seq(add(Txn.application_args[1], Txn.application_args[2]), Approve())) + .ElseIf( + And( # calling the empty method + Txn.on_completion() == OnComplete.NoOp, + Txn.application_args[0] == empty_method_selector, + ) + ) + .Then(Seq(empty(), Approve())) + .ElseIf( + And( # calling the payment method + Txn.on_completion() == OnComplete.NoOp, + Txn.application_args[0] == payment_method_selector, + ) + ) + .Then(Seq(payment(Txn.application_args[1]), Approve())) + .ElseIf( + And( # calling the accountReference method + Txn.on_completion() == OnComplete.NoOp, + Txn.application_args[0] == reference_test_method_selector, + ) + ) + .Then( + Seq( + referenceTest( + Txn.application_args[1], + Txn.application_args[2], + Txn.application_args[3], + Txn.application_args[4], + Txn.application_args[5], + Txn.application_args[6], + Txn.application_args[7], + Txn.application_args[8], + Txn.application_args[9], + ), + Approve(), + ) + ) + .Else(Reject()) + ) + return program + + +if __name__ == "__main__": + import os + + path = os.path.dirname(__file__) + + with open(os.path.join(path, "abi_method_call.teal"), "w") as f: + compiled = compileTeal(approval_program(), mode=Mode.Application, version=5) + f.write(compiled) diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index f03672d2d..dfe8439c2 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -100,6 +100,8 @@ def extractMethodSigValue(op: TealOp) -> bytes: raise TealInternalError("Unexpected args in method opcode: {}".format(op.args)) methodSignature = cast(str, op.args[0]) + if methodSignature[0] == methodSignature[-1] and methodSignature.startswith('"'): + methodSignature = methodSignature[1:-1] methodSelector = encoding.checksum(bytes(methodSignature, "utf-8"))[:4] return methodSelector diff --git a/pyteal/compiler/constants_test.py b/pyteal/compiler/constants_test.py index 8e2804858..cef814d39 100644 --- a/pyteal/compiler/constants_test.py +++ b/pyteal/compiler/constants_test.py @@ -71,24 +71,27 @@ def test_extractMethodValue(): TealOp(None, Op.method_signature, "create(uint64)uint64"), b"\x43\x46\x41\x01", ), - (TealOp(None, Op.method_signature, "update()void"), b"\xa0\xe8\x18\x72"), - (TealOp(None, Op.method_signature, "optIn(string)string"), b"\xcf\xa6\x8e\x36"), - (TealOp(None, Op.method_signature, "closeOut()string"), b"\xa9\xf4\x2b\x3d"), - (TealOp(None, Op.method_signature, "delete()void"), b"\x24\x37\x8d\x3c"), + (TealOp(None, Op.method_signature, '"update()void"'), b"\xa0\xe8\x18\x72"), ( - TealOp(None, Op.method_signature, "add(uint64,uint64)uint64"), + TealOp(None, Op.method_signature, '"optIn(string)string"'), + b"\xcf\xa6\x8e\x36", + ), + (TealOp(None, Op.method_signature, '"closeOut()string"'), b"\xa9\xf4\x2b\x3d"), + (TealOp(None, Op.method_signature, '"delete()void"'), b"\x24\x37\x8d\x3c"), + ( + TealOp(None, Op.method_signature, '"add(uint64,uint64)uint64"'), b"\xfe\x6b\xdf\x69", ), - (TealOp(None, Op.method_signature, "empty()void"), b"\xa8\x8c\x26\xa5"), + (TealOp(None, Op.method_signature, '"empty()void"'), b"\xa8\x8c\x26\xa5"), ( - TealOp(None, Op.method_signature, "payment(pay,uint64)bool"), + TealOp(None, Op.method_signature, '"payment(pay,uint64)bool"'), b"\x3e\x3b\x3d\x28", ), ( TealOp( None, Op.method_signature, - "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]", + '"referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]"', ), b"\x0d\xf0\x05\x0f", ), From cbbf13ec88ca2472f62da9cd925c5a87400bc546 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 15:21:40 -0500 Subject: [PATCH 17/18] house cleaning --- examples/abi_method_call.teal | 265 ---------------------------------- examples/stuff.py | 229 ----------------------------- 2 files changed, 494 deletions(-) delete mode 100644 examples/abi_method_call.teal delete mode 100644 examples/stuff.py diff --git a/examples/abi_method_call.teal b/examples/abi_method_call.teal deleted file mode 100644 index 2ec57eb7d..000000000 --- a/examples/abi_method_call.teal +++ /dev/null @@ -1,265 +0,0 @@ -#pragma version 5 -txn ApplicationID -int 0 -== -bnz main_l18 -txn OnCompletion -int UpdateApplication -== -txna ApplicationArgs 0 -byte 0xa0e81872 -== -&& -bnz main_l17 -txn OnCompletion -int OptIn -== -txna ApplicationArgs 0 -byte 0xcfa68e36 -== -&& -bnz main_l16 -txn OnCompletion -int CloseOut -== -txna ApplicationArgs 0 -byte 0xa9f42b3d -== -&& -bnz main_l15 -txn OnCompletion -int DeleteApplication -== -txna ApplicationArgs 0 -byte 0x24378d3c -== -&& -bnz main_l14 -txn OnCompletion -int NoOp -== -txna ApplicationArgs 0 -byte 0xfe6bdf69 -== -&& -bnz main_l13 -txn OnCompletion -int NoOp -== -txna ApplicationArgs 0 -byte 0xa88c26a5 -== -&& -bnz main_l12 -txn OnCompletion -int NoOp -== -txna ApplicationArgs 0 -byte 0x3e3b3d28 -== -&& -bnz main_l11 -txn OnCompletion -int NoOp -== -txna ApplicationArgs 0 -byte 0x0df0050f -== -&& -bnz main_l10 -int 0 -return -main_l10: -txna ApplicationArgs 1 -txna ApplicationArgs 2 -txna ApplicationArgs 3 -txna ApplicationArgs 4 -txna ApplicationArgs 5 -txna ApplicationArgs 6 -txna ApplicationArgs 7 -txna ApplicationArgs 8 -txna ApplicationArgs 9 -callsub sub8 -int 1 -return -main_l11: -txna ApplicationArgs 1 -callsub sub7 -int 1 -return -main_l12: -callsub sub6 -int 1 -return -main_l13: -txna ApplicationArgs 1 -txna ApplicationArgs 2 -callsub sub5 -int 1 -return -main_l14: -callsub sub4 -int 1 -return -main_l15: -callsub sub3 -int 1 -return -main_l16: -txna ApplicationArgs 1 -callsub sub2 -int 1 -return -main_l17: -callsub sub1 -int 1 -return -main_l18: -txn NumAppArgs -int 0 -> -bnz main_l20 -main_l19: -int 1 -return -main_l20: -txna ApplicationArgs 0 -byte 0x43464101 -== -assert -txna ApplicationArgs 1 -callsub sub0 -b main_l19 -sub0: // create(uint64)uint64 -store 0 -byte 0x151f7c75 -load 0 -btoi -int 2 -* -itob -concat -log -retsub -sub1: // update()void -retsub -sub2: // optIn(string)string -store 1 -int 0 -byte "name" -load 1 -extract 2 0 -app_local_put -byte "hello " -int 0 -byte "name" -app_local_get -concat -store 2 -byte 0x151f7c75 -load 2 -len -itob -extract 6 2 -concat -load 2 -concat -log -retsub -sub3: // closeOut()string -byte "goodbye " -int 0 -byte "name" -app_local_get -concat -store 3 -byte 0x151f7c75 -load 3 -len -itob -extract 6 2 -concat -load 3 -concat -log -retsub -sub4: // delete()void -txn Sender -global CreatorAddress -== -assert -retsub -sub5: // add(uint64,uint64)uint64 -store 5 -store 4 -byte 0x151f7c75 -load 4 -btoi -load 5 -btoi -+ -itob -concat -log -retsub -sub6: // empty()void -byte "random inconsequential log" -log -retsub -sub7: // payment(pay,uint64)bool -store 6 -txn GroupIndex -int 1 -- -gtxns TypeEnum -int pay -== -assert -byte 0x151f7c75 -txn GroupIndex -int 1 -- -gtxns Amount -load 6 -btoi -== -bnz sub7_l2 -byte 0x00 -b sub7_l3 -sub7_l2: -byte 0x80 -sub7_l3: -concat -log -retsub -sub8: // referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] -store 15 -store 14 -store 13 -store 12 -store 11 -store 10 -store 9 -store 8 -store 7 -byte 0x151f7c75 -load 7 -concat -load 9 -concat -load 11 -concat -load 8 -concat -load 14 -concat -load 15 -concat -load 10 -concat -load 12 -concat -load 13 -concat -log -retsub \ No newline at end of file diff --git a/examples/stuff.py b/examples/stuff.py deleted file mode 100644 index bab8df31f..000000000 --- a/examples/stuff.py +++ /dev/null @@ -1,229 +0,0 @@ -# using pyteal version 0.9.1 - -from pyteal import * - -# Method selectors -create_method_selector = Bytes("base16", "43464101") -update_method_selector = Bytes("base16", "a0e81872") -optin_method_selector = Bytes("base16", "cfa68e36") -closeout_method_selector = Bytes("base16", "a9f42b3d") -delete_method_selector = Bytes("base16", "24378d3c") -add_method_selector = Bytes("base16", "fe6bdf69") -empty_method_selector = Bytes("base16", "a88c26a5") -payment_method_selector = Bytes("base16", "3e3b3d28") -reference_test_method_selector = Bytes("base16", "0df0050f") - -return_event_selector = Bytes("base16", "151f7c75") - -# Method signature should be: create(uint64)uint64 -@Subroutine(TealType.none, "create(uint64)uint64") -def create(arg): - return Log(Concat(return_event_selector, Itob(Btoi(arg) * Int(2)))) - - -# Method signature should be: update()void -@Subroutine(TealType.none, "update()void") -def update(): - return Seq() - - -# Method signature should be: optIn(string)string -@Subroutine(TealType.none, "optIn(string)string") -def optIn(name): - message = ScratchVar(TealType.bytes) - return Seq( - App.localPut(Int(0), Bytes("name"), Suffix(name, Int(2))), - message.store(Concat(Bytes("hello "), App.localGet(Int(0), Bytes("name")))), - Log( - Concat( - return_event_selector, - Extract(Itob(Len(message.load())), Int(6), Int(2)), - message.load(), - ) - ), - ) - - -# Method signature should be: closeOut()string -@Subroutine(TealType.none, "closeOut()string") -def closeOut(): - message = ScratchVar(TealType.bytes) - return Seq( - message.store(Concat(Bytes("goodbye "), App.localGet(Int(0), Bytes("name")))), - Log( - Concat( - return_event_selector, - Extract(Itob(Len(message.load())), Int(6), Int(2)), - message.load(), - ) - ), - ) - - -# Method signature should be: delete()void -@Subroutine(TealType.none, "delete()void") -def deleteApp(): - return Assert(Txn.sender() == Global.creator_address()) - - -# Method signature should be: add(uint64,uint64)uint64 -@Subroutine(TealType.none, "add(uint64,uint64)uint64") -def add(a, b): - # The app should log the first four bytes of the SHA512/256 hash of the word - # "return" (0x151f7c75), followed by the method return argument. - return Log(Concat(return_event_selector, Itob(Add(Btoi(a), Btoi(b))))) - - -# Method signature should be: empty()void -@Subroutine(TealType.none, "empty()void") -def empty(): - return Log(Bytes("random inconsequential log")) - - -# Method signature should be: payment(pay,uint64)bool -@Subroutine(TealType.none, "payment(pay,uint64)bool") -def payment(amount): - prev = Txn.group_index() - Int(1) # index of the pay transaction - return Seq( - Assert(Gtxn[prev].type_enum() == TxnType.Payment), - Log( - Concat( - return_event_selector, - If(Gtxn[prev].amount() == Btoi(amount)) - .Then(Bytes("base16", "80")) - .Else(Bytes("base16", "00")), - ) - ), - ) - - -# Method signature should be: referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] -@Subroutine( - TealType.none, - "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]", -) -def referenceTest( - account1, - application1, - account2, - asset1, - account3, - asset2, - asset3, - application2, - application3, -): - return Log( - Concat( - return_event_selector, - account1, - account2, - account3, - application1, - application2, - application3, - asset1, - asset2, - asset3, - ) - ) - - -def approval_program(): - program = ( - If(Txn.application_id() == Int(0)) - .Then( - Seq( - If(Txn.application_args.length() > Int(0)).Then( - Seq( - Assert(Txn.application_args[0] == create_method_selector), - create(Txn.application_args[1]), - ) - ), - Approve(), - ) - ) - .ElseIf( - And( - Txn.on_completion() == OnComplete.UpdateApplication, - Txn.application_args[0] == update_method_selector, - ) - ) - .Then(Seq(update(), Approve())) - .ElseIf( - And( - Txn.on_completion() == OnComplete.OptIn, - Txn.application_args[0] == optin_method_selector, - ) - ) - .Then(Seq(optIn(Txn.application_args[1]), Approve())) - .ElseIf( - And( - Txn.on_completion() == OnComplete.CloseOut, - Txn.application_args[0] == closeout_method_selector, - ) - ) - .Then(Seq(closeOut(), Approve())) - .ElseIf( - And( - Txn.on_completion() == OnComplete.DeleteApplication, - Txn.application_args[0] == delete_method_selector, - ) - ) - .Then(Seq(deleteApp(), Approve())) - .ElseIf( - And( # calling the add method - Txn.on_completion() == OnComplete.NoOp, - Txn.application_args[0] == add_method_selector, - ) - ) - .Then(Seq(add(Txn.application_args[1], Txn.application_args[2]), Approve())) - .ElseIf( - And( # calling the empty method - Txn.on_completion() == OnComplete.NoOp, - Txn.application_args[0] == empty_method_selector, - ) - ) - .Then(Seq(empty(), Approve())) - .ElseIf( - And( # calling the payment method - Txn.on_completion() == OnComplete.NoOp, - Txn.application_args[0] == payment_method_selector, - ) - ) - .Then(Seq(payment(Txn.application_args[1]), Approve())) - .ElseIf( - And( # calling the accountReference method - Txn.on_completion() == OnComplete.NoOp, - Txn.application_args[0] == reference_test_method_selector, - ) - ) - .Then( - Seq( - referenceTest( - Txn.application_args[1], - Txn.application_args[2], - Txn.application_args[3], - Txn.application_args[4], - Txn.application_args[5], - Txn.application_args[6], - Txn.application_args[7], - Txn.application_args[8], - Txn.application_args[9], - ), - Approve(), - ) - ) - .Else(Reject()) - ) - return program - - -if __name__ == "__main__": - import os - - path = os.path.dirname(__file__) - - with open(os.path.join(path, "abi_method_call.teal"), "w") as f: - compiled = compileTeal(approval_program(), mode=Mode.Application, version=5) - f.write(compiled) From 7856987b47684c12bf5d0a6ce476424ee8a81cf9 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 3 Jan 2022 15:37:30 -0500 Subject: [PATCH 18/18] per review --- pyteal/compiler/constants.py | 6 ++++++ pyteal/compiler/constants_test.py | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index dfe8439c2..36421940f 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -102,6 +102,12 @@ def extractMethodSigValue(op: TealOp) -> bytes: methodSignature = cast(str, op.args[0]) if methodSignature[0] == methodSignature[-1] and methodSignature.startswith('"'): methodSignature = methodSignature[1:-1] + else: + raise TealInternalError( + "Method signature opcode error: signatue {} not wrapped with double-quotes".format( + methodSignature + ) + ) methodSelector = encoding.checksum(bytes(methodSignature, "utf-8"))[:4] return methodSelector diff --git a/pyteal/compiler/constants_test.py b/pyteal/compiler/constants_test.py index cef814d39..bd69d463f 100644 --- a/pyteal/compiler/constants_test.py +++ b/pyteal/compiler/constants_test.py @@ -68,7 +68,7 @@ def test_extractAddrValue(): def test_extractMethodValue(): tests = [ ( - TealOp(None, Op.method_signature, "create(uint64)uint64"), + TealOp(None, Op.method_signature, '"create(uint64)uint64"'), b"\x43\x46\x41\x01", ), (TealOp(None, Op.method_signature, '"update()void"'), b"\xa0\xe8\x18\x72"), @@ -223,14 +223,14 @@ def test_createConstantBlocks_pushbytes(): ops = [ TealOp(None, Op.byte, "0x0102"), TealOp(None, Op.byte, "0x0103"), - TealOp(None, Op.method_signature, "empty()void"), + TealOp(None, Op.method_signature, '"empty()void"'), TealOp(None, Op.concat), ] expected = [ TealOp(None, Op.pushbytes, "0x0102", "//", "0x0102"), TealOp(None, Op.pushbytes, "0x0103", "//", "0x0103"), - TealOp(None, Op.pushbytes, "0xa88c26a5", "//", "empty()void"), + TealOp(None, Op.pushbytes, "0xa88c26a5", "//", '"empty()void"'), TealOp(None, Op.concat), ] @@ -281,7 +281,7 @@ def test_createConstantBlocks_byteblock_multiple(): None, Op.addr, "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M" ), TealOp(None, Op.concat), - TealOp(None, Op.method_signature, "closeOut()string"), + TealOp(None, Op.method_signature, '"closeOut()string"'), TealOp(None, Op.concat), TealOp(None, Op.byte, "base64(qfQrPQ==)"), ] @@ -318,7 +318,7 @@ def test_createConstantBlocks_byteblock_multiple(): "WSJHNPJ6YCLX5K4GUMQ4ISPK3ABMS3AL3F6CSVQTCUI5F4I65PWEMCWT3M", ), TealOp(None, Op.concat), - TealOp(None, Op.bytec_3, "//", "closeOut()string"), + TealOp(None, Op.bytec_3, "//", '"closeOut()string"'), TealOp(None, Op.concat), TealOp(None, Op.bytec_3, "//", "base64(qfQrPQ==)"), ]