Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AVM 7: Address integration branch feedback #452

Merged
merged 5 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions docs/accessing_transaction_field.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,9 @@ Operator
:any:`Txn.application_args <TxnObject.application_args>` :code:`TealType.bytes[]` 2 Array of application arguments
:any:`Txn.created_application_id() <TxnObject.created_application_id>` :code:`TealType.uint64` 5 The ID of the newly created application in this transaction. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions.
:any:`Txn.logs <TxnObject.logs>` :code:`TealType.bytes[]` 5 Array of application logged items. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions.
:any:`Txn.last_log() <TxnObject.last_log>` :code:`TealType.bytes[]` 6 The last message emitted. Empty bytes if none were emitted. Application mode only.
:any:`Txn.last_log() <TxnObject.last_log>` :code:`TealType.bytes` 6 The last message emitted. Empty bytes if none were emitted. Application mode only.
:any:`Txn.approval_program_pages() <TxnObject.approval_program_pages>` :code:`TealType.bytes[]` 7 The pages of the approval program as an array
:any:`Txn.num_approval_program_pages() <TxnObject.num_approval_program_pages>` :code:`TealType.uint64` 7 The number of approval program pages
:any:`Txn.clear_state_program_pages() <TxnObject.clear_state_program_pages>` :code:`TealType.bytes[]` 7 The pages of a clear state program as an array
:any:`Txn.num_clear_state_program_pages() <TxnObject.num_clear_state_program_pages>` :code:`TealType.uint64` 7 The number of clear state program pages
==================================================================================== ========================= ================ ============================================================================

Asset Config
Expand Down
12 changes: 8 additions & 4 deletions pyteal/ast/base64decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pyteal.errors import verifyFieldVersion
from pyteal.ir import TealOp, Op, TealBlock
from pyteal.ast.expr import Expr
from pyteal.ast.leafexpr import LeafExpr

if TYPE_CHECKING:
from pyteal.compiler import CompileOptions
Expand All @@ -27,7 +26,7 @@ def __init__(self, id: int, name: str, min_version: int) -> None:
Base64Encoding.__module__ = "pyteal"


class Base64Decode(LeafExpr):
class Base64Decode(Expr):
"""An expression that decodes a base64-encoded byte string according to a specific encoding.

See [RFC 4648](https://rfc-editor.org/rfc/rfc4648.html#section-4) (sections 4 and 5) for information on specifications.
Expand All @@ -36,6 +35,8 @@ class Base64Decode(LeafExpr):
When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail.
The special cases of \\n and \\r are allowed but completely ignored. An error will result when attempting
to decode a string with a character that is not in the encoding alphabet or not one of =, \\r, or \\n.

NOTE: Base64Decode usage is not intended for introducing constants. Instead, use :any:`Bytes`.
"""

def __init__(self, encoding: Base64Encoding, base64: Expr) -> None:
Expand All @@ -59,8 +60,11 @@ def __str__(self):
def type_of(self):
return TealType.bytes

def has_return(self):
return False

@classmethod
def url(cls, base64: Expr) -> "Base64Decode":
def url(cls, base64: Expr) -> Expr:
"""Decode a base64-encoded byte string according to the URL encoding.

Refer to the `Base64Decode` class documentation for more information.
Expand All @@ -71,7 +75,7 @@ def url(cls, base64: Expr) -> "Base64Decode":
return cls(Base64Encoding.url, base64)

@classmethod
def std(cls, base64: Expr) -> "Base64Decode":
def std(cls, base64: Expr) -> Expr:
"""Decode a base64-encoded byte string according to the Standard encoding.

Refer to the `Base64Decode` class documentation for more information.
Expand Down
4 changes: 2 additions & 2 deletions pyteal/ast/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def type_of(self):
return self.field.type_of()

@classmethod
def seed(cls, block: Expr) -> "Block":
def seed(cls, block: Expr) -> Expr:
"""Get the seed of a block.

Args:
Expand All @@ -66,7 +66,7 @@ def seed(cls, block: Expr) -> "Block":
return cls(BlockField.block_seed, block)

@classmethod
def timestamp(cls, block: Expr) -> "Block":
def timestamp(cls, block: Expr) -> Expr:
"""Get the timestamp of a block.

Args:
Expand Down
12 changes: 10 additions & 2 deletions pyteal/ast/ecdsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
from typing import Tuple, TYPE_CHECKING

from pyteal.ast import Expr, MultiValue
from pyteal.errors import TealTypeError, verifyFieldVersion, verifyTealVersion
from pyteal.errors import (
TealTypeError,
verifyFieldVersion,
verifyTealVersion,
TealInputError,
)
from pyteal.ir import Op, TealBlock, TealOp
from pyteal.types import TealType, require_type

Expand Down Expand Up @@ -142,7 +147,7 @@ def EcdsaDecompress(curve: EcdsaCurve, compressed_pk: Expr) -> MultiValue:
def EcdsaRecover(
curve: EcdsaCurve, data: Expr, recovery_id: Expr, sigA: Expr, sigB: Expr
) -> MultiValue:
"""Reover an ECDSA public key from a signature.
"""Recover an ECDSA public key from a signature.
All byte arguments must be big endian encoded.
Args:
curve: Enum representing the ECDSA curve used for the public key
Expand All @@ -158,6 +163,9 @@ def EcdsaRecover(
if not isinstance(curve, EcdsaCurve):
raise TealTypeError(curve, EcdsaCurve)

if curve != EcdsaCurve.Secp256k1:
raise TealInputError("Recover only supports Secp256k1")

require_type(data, TealType.bytes)
require_type(recovery_id, TealType.uint64)
require_type(sigA, TealType.bytes)
Expand Down
93 changes: 50 additions & 43 deletions pyteal/ast/ecdsa_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,45 +54,51 @@ def test_ecdsa_decompress(curve: pt.EcdsaCurve):

@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1])
def test_ecdsa_recover(curve: pt.EcdsaCurve):
args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")]
pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3])
assert pubkey.type_of() == pt.TealType.none

expected = pt.TealSimpleBlock(
[
pt.TealOp(args[0], pt.Op.byte, '"data"'),
pt.TealOp(args[1], pt.Op.int, 1),
pt.TealOp(args[2], pt.Op.byte, '"sigA"'),
pt.TealOp(args[3], pt.Op.byte, '"sigB"'),
pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, curve.arg_name),
pt.TealOp(
pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1]
),
pt.TealOp(
pubkey.output_slots[0].store(), pt.Op.store, pubkey.output_slots[0]
),
]
)
if curve != pt.EcdsaCurve.Secp256k1:
with pytest.raises(pt.TealInputError):
pt.EcdsaRecover(
curve, pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")
)
else:
args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")]
pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3])
assert pubkey.type_of() == pt.TealType.none

expected = pt.TealSimpleBlock(
[
pt.TealOp(args[0], pt.Op.byte, '"data"'),
pt.TealOp(args[1], pt.Op.int, 1),
pt.TealOp(args[2], pt.Op.byte, '"sigA"'),
pt.TealOp(args[3], pt.Op.byte, '"sigB"'),
pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, curve.arg_name),
pt.TealOp(
pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1]
),
pt.TealOp(
pubkey.output_slots[0].store(), pt.Op.store, pubkey.output_slots[0]
),
]
)

actual, _ = pubkey.__teal__(curve_options_map[curve])
actual.addIncoming()
actual = pt.TealBlock.NormalizeBlocks(actual)
actual, _ = pubkey.__teal__(curve_options_map[curve])
actual.addIncoming()
actual = pt.TealBlock.NormalizeBlocks(actual)

with pt.TealComponent.Context.ignoreExprEquality():
assert actual == expected
with pt.TealComponent.Context.ignoreExprEquality():
assert actual == expected

# compile without errors this is necessary so assembly is also tested
pt.compileTeal(
pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version
)

with pytest.raises(pt.TealInputError):
# compile without errors this is necessary so assembly is also tested
pt.compileTeal(
pt.Seq(pubkey, pt.Approve()),
pt.Mode.Application,
version=curve.min_version - 1,
pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version
)

with pytest.raises(pt.TealInputError):
pt.compileTeal(
pt.Seq(pubkey, pt.Approve()),
pt.Mode.Application,
version=curve.min_version - 1,
)


@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1])
def test_ecdsa_verify_basic(curve: pt.EcdsaCurve):
Expand Down Expand Up @@ -186,8 +192,8 @@ def test_ecdsa_verify_compressed_pk(curve: pt.EcdsaCurve):
)


@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1])
def test_ecdsa_verify_recovered_pk(curve: pt.EcdsaCurve):
def test_ecdsa_verify_recovered_pk():
curve = pt.EcdsaCurve.Secp256k1
args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")]
pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3])
expr = pt.EcdsaVerify(curve, args[0], args[2], args[3], pubkey)
Expand Down Expand Up @@ -243,14 +249,15 @@ def test_ecdsa_verify_recovered_pk(curve: pt.EcdsaCurve):

@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1])
def test_ecdsa_invalid(curve: pt.EcdsaCurve):
with pytest.raises(pt.TealTypeError):
args: List[Union[pt.Bytes, pt.Int]] = [
pt.Bytes("data"),
pt.Bytes("1"),
pt.Bytes("sigA"),
pt.Bytes("sigB"),
]
pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3])
if curve == pt.EcdsaCurve.Secp256k1:
with pytest.raises(pt.TealTypeError):
args: List[Union[pt.Bytes, pt.Int]] = [
pt.Bytes("data"),
pt.Bytes("1"),
pt.Bytes("sigA"),
pt.Bytes("sigB"),
]
pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3])

with pytest.raises(pt.TealTypeError):
pt.EcdsaDecompress(curve, pt.Int(1))
Expand Down
12 changes: 7 additions & 5 deletions pyteal/ast/jsonref.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pyteal.errors import verifyFieldVersion, verifyTealVersion
from pyteal.ir import TealOp, Op, TealBlock
from pyteal.ast.expr import Expr
from pyteal.ast.leafexpr import LeafExpr

if TYPE_CHECKING:
from pyteal.compiler import CompileOptions
Expand All @@ -32,7 +31,7 @@ def type_of(self) -> TealType:
JsonRefType.__module__ = "pyteal"


class JsonRef(LeafExpr):
class JsonRef(Expr):
"""An expression that accesses the value associated with a given key from a supported utf-8 encoded json object.

The json object must satisfy a `particular specification <https://github.com/algorand/go-algorand/blob/master/data/transactions/logic/jsonspec.md>`_.
Expand Down Expand Up @@ -62,13 +61,16 @@ def __teal__(self, options: "CompileOptions"):
return TealBlock.FromOp(options, op, self.json_obj, self.key)

def __str__(self):
return "(JsonRef {})".format(self.type.arg_name)
return "(JsonRef {} {} {})".format(self.type.arg_name, self.json_obj, self.key)

def type_of(self):
return self.type.type_of()

def has_return(self):
return False

@classmethod
def as_string(cls, json_obj: Expr, key: Expr) -> "JsonRef":
def as_string(cls, json_obj: Expr, key: Expr) -> Expr:
"""Access the value of a given key as a string.

Refer to the `JsonRef` class documentation for valid json specification.
Expand All @@ -80,7 +82,7 @@ def as_string(cls, json_obj: Expr, key: Expr) -> "JsonRef":
return cls(JsonRefType.string, json_obj, key)

@classmethod
def as_uint64(cls, json_obj: Expr, key: Expr) -> "JsonRef":
def as_uint64(cls, json_obj: Expr, key: Expr) -> Expr:
"""Access the value of a given key as a uint64.

Refer to the `JsonRef` class documentation for valid json specification.
Expand Down
14 changes: 0 additions & 14 deletions pyteal/ast/txn.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,20 +751,6 @@ def state_proof_pk(self) -> TxnExpr:
"""
return self.makeTxnExpr(TxnField.state_proof_pk)

def num_approval_program_pages(self) -> TxnExpr:
"""Get the number of pages in the approval program.

Requires TEAL version 7 or higher.
"""
return self.makeTxnExpr(TxnField.num_approval_program_pages)

def num_clear_state_program_pages(self) -> TxnExpr:
"""Get the number of pages in the clear state program.

Requires TEAL version 7 or higher.
"""
return self.makeTxnExpr(TxnField.num_clear_state_program_pages)

@property
def application_args(self) -> TxnArray:
"""Application call arguments array.
Expand Down
2 changes: 0 additions & 2 deletions pyteal/ast/txn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@
pt.TxnField.created_application_id: lambda txn: txn.created_application_id(),
pt.TxnField.last_log: lambda txn: txn.last_log(),
pt.TxnField.state_proof_pk: lambda txn: txn.state_proof_pk(),
pt.TxnField.num_approval_program_pages: lambda txn: txn.num_approval_program_pages(),
pt.TxnField.num_clear_state_program_pages: lambda txn: txn.num_clear_state_program_pages(),
}

arrayFieldToProperty: Dict[pt.TxnField, Callable[[pt.TxnObject], pt.TxnArray]] = {
Expand Down