Skip to content

Commit

Permalink
Support for AVM v10 (#714)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonpaulos authored Jan 2, 2024
1 parent 0d6c1f2 commit 8c0445e
Show file tree
Hide file tree
Showing 25 changed files with 889 additions and 208 deletions.
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ per-file-ignores =
examples/signature/recurring_swap.py: F403, F405
examples/signature/split.py: F403, F405
pyteal/__init__.py: F401, F403
pyteal/ast/ec.py: E222
pyteal/compiler/flatten.py: F821
pyteal/compiler/optimizer/__init__.py: F401
pyteal/ir/ops.py: E221
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

## Added

* Support for AVM v10 programs. ([#714](https://github.com/algorand/pyteal/pull/714))
* New box operations:
* `App.box_resize`
* `App.box_splice`
* New `Global` fields:
* `Global.asset_create_min_balance()`
* `Global.asset_opt_in_min_balance()`
* `Global.genesis_hash()`
* New elliptic curve operations:
* `EcAdd`
* `EcScalarMul`
* `EcPairingCheck`
* `EcMultiScalarMul`
* `EcSubgroupCheck`
* `EcMapTo`
* Support for Python 3.12. ([#713](https://github.com/algorand/pyteal/pull/713))

## Fixed
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ algod-stop:
docker compose stop algod

test-integ-async:
pytest -n auto --durations=10 -sv tests/integration -m "not serial"
pytest -n auto --durations=10 tests/integration -m "not serial"

# Run tests w/ @pytest.mark.serial under ~/tests/integration each in its own proc:
test-integ-sync:
Expand Down
12 changes: 8 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ version: '3'

services:
algod:
image: wwinder/algod:dev
image: algorand/algod:master
platform: linux/amd64
ports:
- "4001:8080" # algod
- "4160:4160" # gossip
- "9100:9100" # prometheus
environment:
- DEV_MODE=true
- TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- DEV_MODE=1
- PROFILE=development
# Can remove ADMIN_TOKEN if the code stops passing an auth token. The development profile
# enables the DisableAPIAuth config option, however an unfortunate side effect of this
# option is that requests will error if *any* auth token is passed. Issue: https://github.com/algorand/go-algorand/issues/5883
- ADMIN_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
healthcheck:
test: goal node status
test: curl -f http://localhost:8080/health
interval: 2s
retries: 30
12 changes: 9 additions & 3 deletions docs/accessing_transaction_field.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ Global Parameters
Information about the current state of the blockchain can be obtained using the following
:any:`Global` expressions:

=========================================== ======================= ==================== =============================================================
=========================================== ======================= ==================== ===================================================================================================================
Operator Type Min Program Version Notes
=========================================== ======================= ==================== =============================================================
=========================================== ======================= ==================== ===================================================================================================================
:any:`Global.min_txn_fee()` :code:`TealType.uint64` 2 in microAlgos
:any:`Global.min_balance()` :code:`TealType.uint64` 2 in microAlgos
:any:`Global.max_txn_life()` :code:`TealType.uint64` 2 number of rounds
Expand All @@ -237,4 +237,10 @@ Operator Type Min Program
:any:`Global.creator_address()` :code:`TealType.bytes` 3 32 byte address of the creator of the current application
:any:`Global.current_application_address()` :code:`TealType.bytes` 5 32 byte address of the current application controlled account
:any:`Global.group_id()` :code:`TealType.bytes` 5 32 byte ID of the current transaction group
=========================================== ======================= ==================== =============================================================
:any:`Global.opcode_budget()` :code:`TealType.uint64` 6 The remaining cost that can be spent by opcodes in this program
:any:`Global.caller_app_id()` :code:`TealType.uint64` 6 The ID of the application that called the current application, or zero. Application mode only
:any:`Global.caller_app_address()` :code:`TealType.bytes` 6 32 byte address of the application that called the current application, or the zero address. Application mode only.
:any:`Global.asset_create_min_balance()` :code:`TealType.uint64` 10 The minimum balance required to create and opt into an asset
:any:`Global.asset_opt_in_min_balance()` :code:`TealType.uint64` 10 The minimum balance required to opt in to an asset
:any:`Global.genesis_hash()` :code:`TealType.bytes` 10 The genesis hash for the network
=========================================== ======================= ==================== ===================================================================================================================
61 changes: 56 additions & 5 deletions docs/state.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Other App Global :any:`App.gl
Other App Local :any:`App.localGetEx` :any:`App.localGetEx`
Current App Boxes :any:`App.box_create` :any:`App.box_put` :any:`App.box_extract` :any:`App.box_delete` :any:`App.box_length`
:any:`App.box_put` :any:`App.box_replace` :any:`App.box_get` :any:`App.box_get`
:any:`App.box_splice`
:any:`App.box_resize`
================== ======================= ======================== ======================== ===================== =======================

Global State
Expand Down Expand Up @@ -267,9 +269,10 @@ The app account's minimum balance requirement (MBR) is increased with each addit
If one deletes an application with outstanding boxes, the MBR is not recoverable from the deleted app account.
It is recommended that *before* app deletion, all box storage be deleted, and funds previously allocated to the MBR be withdrawn.

Box sizes and names cannot be changed after initial allocation, but they can be deleted and re-allocated.
Boxes are only visible to the application itself; in other words, an application cannot read from or write to another application's boxes on-chain.

Boxes are fixed-length structures, though they can be resized with the :any:`App.box_resize` method (or by deleting and recreating the box).

The following sections explain how to work with boxes.

.. _Creating Boxes:
Expand Down Expand Up @@ -311,19 +314,67 @@ For :any:`App.box_put`, the first argument is the box name to create or to write
# write to box `poemLine` with new value
App.box_put(Bytes("poemLine"), Bytes("The lone and level sands stretch far away."))
Resizing Boxes
~~~~~~~~~~~~~~
Boxes that already exist can be resized using the :any:`App.box_resize` method. This is the only way to resize a box, besides deleting it and recreating it.
For :any:`App.box_resize`, the first argument is the box name to resize, and the second argument is the new byte size to be allocated.
.. note::
If the new size is smaller than the existing box's byte size, then the box will lose the bytes at the end.
If the new size is larger than the existing box's byte size, then the box will be padded with zeros at the end.
For all size changes, the app account's minimum balance requirement (MBR) will be updated accordingly.
For example:
.. code-block:: python
# resize a box called "BoxA" to byte size 200
App.box_resize(Bytes("BoxA"), Int(200))
Writing to a Box
~~~~~~~~~~~~~~~~
To write to a box, use :any:`App.box_replace`, or :any:`App.box_put` method.
To write to a box, use :any:`App.box_replace`, :any:`App.box_splice` , or :any:`App.box_put` method.
:any:`App.box_replace` writes bytes of certain length from a start index in a box.
:any:`App.box_replace` replaces a range of bytes in a box.
The first argument is the box name to write into, the second argument is the starting index to write,
and the third argument is the replacement bytes. For example:
.. code-block:: python
# replace 2 bytes starting from the 0'th byte by `Ne` in the box named `wordleBox`
App.box_replace(Bytes("wordleBox"), Int(0), Bytes("Ne"))
# Assume the box named "wordleBox" initially contains the bytes "cones"
# Replace 2 bytes starting from index 1 with "ap" in the box named "wordleBox"
App.box_replace(Bytes("wordleBox"), Int(1), Bytes("ap"))
# The result is that the box named "wordleBox" now contains the bytes "capes"
:any:`App.box_splice` is a more general version of :any:`App.box_replace`. This operation takes an
additional argument, which is the length of the bytes in the box to be replaced. By specifying a
different length than the bytes you are inserting, you can shift contents of the box instead of just
replacing a range of bytes.
For example:
.. code-block:: python
# Assume the box named "flavors" initially contains the bytes "banana_apple_cherry_______"
# Insert "grape_" at index 7 in the box named "flavors". By specifying a length of 0, the
# following bytes will be shifted to the right.
App.box_splice(Bytes("flavors"), Int(7), Int(0), Bytes("grape_"))
# The result is that the box named "flavors" now contains the bytes "banana_grape_apple_cherry_"
# If we want to zero the box, we can replace the entire contents with an empty string.
App.box_splice(Bytes("flavors"), Int(0), Int(26), Bytes(""))
# The "flavors" box now contains "00000000000000000000000000". Ready for reuse!
Recall that boxes are fixed length, so shifting bytes can cause the box to truncate or pad with zeros.
More information is available in the docstring for :any:`App.box_splice`.
:any:`App.box_put` writes the full contents to a pre-existing box, as is mentioned in `Creating Boxes`_.
Expand Down
1 change: 1 addition & 0 deletions docs/versions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ AVM Version PyTeal Version
7 >= 0.15.0
8 >= 0.20.0
9 >= 0.25.0
10 >= 0.26.0
============ ==============

.. _version pragmas:
Expand Down
9 changes: 9 additions & 0 deletions pyteal/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ __all__ = [
"BoxLen",
"BoxPut",
"BoxReplace",
"BoxResize",
"BoxSplice",
"Break",
"Btoi",
"Bytes",
Expand Down Expand Up @@ -109,12 +111,19 @@ __all__ = [
"Div",
"Divw",
"DynamicScratchVar",
"EcAdd",
"EcMapTo",
"EcMultiScalarMul",
"EcPairingCheck",
"EcScalarMul",
"EcSubgroupCheck",
"EcdsaCurve",
"EcdsaDecompress",
"EcdsaRecover",
"EcdsaVerify",
"Ed25519Verify",
"Ed25519Verify_Bare",
"EllipticCurve",
"EnumInt",
"Eq",
"Err",
Expand Down
20 changes: 20 additions & 0 deletions pyteal/ast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@
from pyteal.ast.acct import AccountParam, AccountParamObject
from pyteal.ast.box import (
BoxCreate,
BoxResize,
BoxDelete,
BoxExtract,
BoxReplace,
BoxSplice,
BoxLen,
BoxGet,
BoxPut,
Expand Down Expand Up @@ -169,6 +171,15 @@
from pyteal.ast.multi import MultiValue
from pyteal.ast.opup import OpUp, OpUpMode, OpUpFeeSource
from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover
from pyteal.ast.ec import (
EllipticCurve,
EcAdd,
EcScalarMul,
EcPairingCheck,
EcMultiScalarMul,
EcSubgroupCheck,
EcMapTo,
)
from pyteal.ast.router import (
BareCallActions,
CallConfig,
Expand Down Expand Up @@ -212,8 +223,10 @@
"BitwiseXor",
"Block",
"BoxCreate",
"BoxResize",
"BoxDelete",
"BoxExtract",
"BoxSplice",
"BoxGet",
"BoxLen",
"BoxPut",
Expand Down Expand Up @@ -252,6 +265,13 @@
"EcdsaVerify",
"Ed25519Verify_Bare",
"Ed25519Verify",
"EllipticCurve",
"EcAdd",
"EcScalarMul",
"EcPairingCheck",
"EcMultiScalarMul",
"EcSubgroupCheck",
"EcMapTo",
"EnumInt",
"Eq",
"Err",
Expand Down
2 changes: 1 addition & 1 deletion pyteal/ast/abstractvar.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def alloc_abstract_var(stack_type: TealType) -> AbstractVar:
stack_type: TealType that represents stack type.
"""

from pyteal.ast import ScratchVar
from pyteal.ast.scratchvar import ScratchVar
from pyteal.ast.subroutine import SubroutineEval
from pyteal.ast.frame import FrameVar, MAX_FRAME_LOCAL_VARS

Expand Down
39 changes: 39 additions & 0 deletions pyteal/ast/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
from enum import Enum
from pyteal.ast.box import (
BoxCreate,
BoxResize,
BoxDelete,
BoxExtract,
BoxReplace,
BoxSplice,
BoxLen,
BoxGet,
BoxPut,
Expand Down Expand Up @@ -237,6 +239,19 @@ def box_create(cls, name: Expr, size: Expr) -> Expr:
"""
return BoxCreate(name, size)

@classmethod
def box_resize(cls, name: Expr, size: Expr) -> Expr:
"""Resize an existing box.
If the new size is larger than the old size, zero bytes will be added to the end of the box.
If the new size is smaller than the old size, the box will be truncated from the end.
Args:
name: The key used to reference this box. Must evaluate to a bytes.
size: The new number of bytes to reserve for this box. Must evaluate to a uint64.
"""
return BoxResize(name, size)

@classmethod
def box_delete(cls, name: Expr) -> Expr:
"""Deletes a box given it's name.
Expand Down Expand Up @@ -272,6 +287,30 @@ def box_replace(cls, name: Expr, start: Expr, value: Expr) -> Expr:
"""
return BoxReplace(name, start, value)

@classmethod
def box_splice(
cls, name: Expr, start: Expr, length: Expr, new_content: Expr
) -> Expr:
"""
Replaces the range of bytes from `start` through `start + length` with `new_content`.
Bytes after `start + length` will be shifted to the right.
Recall that boxes are constant length, and this operation will not change the length of the
box. Instead content may be adjusted as so:
* If the length of the new content is less than `length`, the bytes following `start + length` will be shifted to the left, and the end of the box will be padded with zeros.
* If the length of the new content is greater than `length`, the bytes following `start + length` will be shifted to the right and bytes exceeding the length of the box will be truncated.
Args:
name: The name of the box to modify. Must evaluate to bytes.
start: The byte index into the box to start writing. Must evaluate to uint64.
length: The length of the bytes to be replaced. Must evaluate to uint64.
new_content: The new content to write into the box. Must evaluate to bytes.
"""
return BoxSplice(name, start, length, new_content)

@classmethod
def box_length(cls, name: Expr) -> MaybeValue:
"""Get the byte length of the box specified by its name.
Expand Down
Loading

0 comments on commit 8c0445e

Please sign in to comment.