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 8 #439

Merged
merged 27 commits into from
Nov 3, 2022
Merged

AVM 8 #439

Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d0f9d8d
upping max teal version
barnjamin Jun 24, 2022
749fb8c
adding program page related ops (#412)
barnjamin Jun 30, 2022
39a11c2
Add Replace (#413)
jdtzmn Jun 30, 2022
dd2c7ec
Add Block (#415)
jdtzmn Jun 30, 2022
8c3d2a0
Add JsonRef (#417)
jdtzmn Jun 30, 2022
839b985
Add Base64Decode (#418)
jdtzmn Jun 30, 2022
7cccdcb
Support Secp256r1 curve (#423)
jdtzmn Jun 30, 2022
9a26fec
Add VrfVerify (#419)
jdtzmn Jun 30, 2022
cc544aa
Add `Sha3_256` (#425)
jdtzmn Jun 30, 2022
6e83ae6
Support `FirstValidTime` transaction field (#424)
jdtzmn Jun 30, 2022
594e8e4
Add `Ed25519Verify_Bare` (#426)
jdtzmn Jun 30, 2022
0ba95e9
Merge branch 'master' into avm8
ahangsu Jul 13, 2022
e9af25f
Merge branch 'master' into avm8
ahangsu Jul 14, 2022
7c24013
AVM Boxes Ops in Pyteal (#438)
ahangsu Jul 20, 2022
ba260e6
Merge Teal7 to AVM8, and consolidate Teal to AVM versioning (#470)
ahangsu Jul 22, 2022
ed516f7
Merge branch 'master' into avm8
ahangsu Jul 22, 2022
055c981
change according to https://github.com/algorand/go-algorand/pull/4323…
ahangsu Aug 3, 2022
bc78e39
Merge branch 'master' into avm8-merge-master
ahangsu Aug 4, 2022
2c2b7b2
Merge branch 'master' into avm8
ahangsu Aug 15, 2022
61af648
Merge branch 'master' into avm8
ahangsu Aug 31, 2022
5282726
Merge branch 'master' into avm8
ahangsu Sep 15, 2022
76010b2
Changes to avm8 docs (#546)
jasonpaulos Sep 29, 2022
d767c66
Merge branch 'master' into avm8
ahangsu Oct 25, 2022
cc49a55
Support new AVM 8 account parameters (#555)
jdtzmn Oct 26, 2022
c241cfa
CHANGELOG.md
ahangsu Nov 2, 2022
dd687c7
Frame Ops to `avm8` branch PR (#585)
ahangsu Nov 2, 2022
9cdcb30
CHANGELOG 0.20.0
ahangsu Nov 3, 2022
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
176 changes: 166 additions & 10 deletions docs/state.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,30 @@ PyTeal can be used to write `Stateful Algorand Smart Contracts <https://develope
as well. Stateful contracts, also known as applications, can access and manipulate state on the
Algorand blockchain.

State consists of key-value pairs, where keys are byte slices and values can be integers or byte
slices. There are multiple types of state that an application can use.
There are multiple types of state that an application can use.
State consists of key-value pairs, where keys are byte slices and values vary by type of state.

=================== ===================== ===============================================
Type of State Type of Key Type of Value
=================== ===================== ===============================================
Global State :any:`TealType.bytes` :any:`TealType.uint64` or :any:`TealType.bytes`
Local State :any:`TealType.bytes` :any:`TealType.uint64` or :any:`TealType.bytes`
Boxes :any:`TealType.bytes` :any:`TealType.bytes`
=================== ===================== ===============================================

State Operation Table
---------------------

================== ==================== ====================== ==================== ======================
Context Write Read Delete Check If Exists
================== ==================== ====================== ==================== ======================
Current App Global :any:`App.globalPut` :any:`App.globalGet` :any:`App.globalDel` :any:`App.globalGetEx`
Current App Local :any:`App.localPut` :any:`App.localGet` :any:`App.localDel` :any:`App.localGetEx`
Other App Global :any:`App.globalGetEx` :any:`App.globalGetEx`
Other App Local :any:`App.localGetEx` :any:`App.localGetEx`
================== ==================== ====================== ==================== ======================
================== ======================= ======================== ======================== ===================== =======================
Context Create Write Read Delete Check If Exists
================== ======================= ======================== ======================== ===================== =======================
Current App Global :any:`App.globalPut` :any:`App.globalGet` :any:`App.globalDel` :any:`App.globalGetEx`
Current App Local :any:`App.localPut` :any:`App.localGet` :any:`App.localDel` :any:`App.localGetEx`
Other App Global :any:`App.globalGetEx` :any:`App.globalGetEx`
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`
================== ======================= ======================== ======================== ===================== =======================

Global State
------------
Expand Down Expand Up @@ -244,3 +254,149 @@ For example:
otherAppOtherAccountRole,
If(otherAppOtherAccountRole.hasValue(), otherAppOtherAccountRole.value(), Bytes("none"))
])

Box Storage
-----------

Box storage consists of key-value pairs that are stored in an application's local context.

The app account's minimum balance requirement (MBR) is increased with each additional box, and each additional byte in the box's name and allocated size.

.. warning::

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.

The following sections explain how to work with boxes.

.. _Creating Boxes:

Creating Boxes
~~~~~~~~~~~~~~

To create a box, use :any:`App.box_create`, or :any:`App.box_put` method.

For :any:`App.box_create`, the first argument is the box name, and the second argument is the byte size to be allocated.

:any:`App.box_create` creates a new box with the specified name and byte length. New boxes will contain a byte string of all zeros. Performing this operation on a box that already exists will not change its contents.

If successful, :any:`App.box_create` will return :code:`0` if the box already existed, otherwise it will return :code:`1`. A failure will occur if you attempt to create a box that already exists with a different size.

For example:

.. code-block:: python

# Allocate a box called "BoxA" of byte size 100 and ignore the return value
Pop(App.box_create(Bytes("BoxA"), Int(100)))

# Allocate a box called "BoxB" of byte size 90, asserting that it didn't exist before.
Assert(App.box_create(Bytes("BoxB"), Int(90))

For :any:`App.box_put`, the first argument is the box name to create or to write to, and the second argument is the bytes to write.

.. note::

If the box exists, then :any:`App.box_put` will write the contents to the box
(fails when the content length is **not identical** to the existing box's byte size);
otherwise, it will create a box containing exactly the same input bytes.

.. code-block:: python

# create a 42 bytes length box called `poemLine` with content
App.box_put(Bytes("poemLine"), Bytes("Of that colossal wreck, boundless and bare"))

# write to box `poemLine` with new value
App.box_put(Bytes("poemLine"), Bytes("The lone and level sands stretch far away."))

Writing to a Box
~~~~~~~~~~~~~~~~

To write to a box, use :any:`App.box_replace`, or :any:`App.box_put` method.

:any:`App.box_replace` writes bytes of certain length from a start index 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"))

:any:`App.box_put` writes the full contents to a pre-existing box, as is mentioned in `Creating Boxes`_.

.. _Reading from a Box:

Reading from a Box
~~~~~~~~~~~~~~~~~~

To read from a box, use :any:`App.box_extract`, or :any:`App.box_get` method.

:any:`App.box_extract` reads bytes of a certain length from a start index in a Box.
The first argument is the box name to read from, the second argument is the starting index to read,
and the third argument is the length of bytes to extract. For example:

.. code-block:: python

# extract a segment of length 10 starting at the 5th byte in a box named `NoteBook`
App.box_extract(Bytes("NoteBook"), Int(5), Int(10))

:any:`App.box_get` gets the full contents of a box.
The only argument is the box name, and it returns a :any:`MaybeValue` containing:

- a boolean value indicating if the box exists
- the full contents of the box.

For example:

.. code-block:: python

# get the full contents from a box named `NoteBook`, asserting that it exists
Seq(
contents := App.box_get(Bytes("NoteBook")),
Assert(contents.hasValue()),
contents.value()
)

Deleting a Box
~~~~~~~~~~~~~~

To delete a box, use :any:`App.box_delete` method. The only argument is the box name.

:any:`App.box_delete` will return :code:`1` if the box already existed, otherwise it will return :code:`0`. Deleting a nonexistent box is allowed, but has no effect.

For example:

.. code-block:: python

# delete the box `boxToRemove`, asserting that it existed prior to this
Assert(App.box_delete(Bytes("boxToRemove")))

# delete the box `mightExist` and ignore the return value
Pop(App.box_delete(Bytes("mightExist")))

Checking if a Box Exists and Reads its Length
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To check the existence of a box, use the :any:`App.box_length` method.
The only argument is the box name, and it returns a :any:`MaybeValue` containing:

- a boolean value indicating if the box exists
- the actual byte size of the box.

For example:

.. code-block:: python

# get the length of the box `someBox`, and assert that the box exists
Seq(
length := App.box_length(Bytes("someBox")),
Assert(length.hasValue()),
length.value()
)

.. note::

:any:`App.box_get` can also check the existence of a box as mentioned in `Reading from a Box`_.
7 changes: 7 additions & 0 deletions pyteal/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ __all__ = [
"BitwiseOr",
"BitwiseXor",
"Block",
"BoxCreate",
"BoxDelete",
"BoxExtract",
"BoxGet",
"BoxLen",
"BoxPut",
"BoxReplace",
"Break",
"Btoi",
"Bytes",
Expand Down
17 changes: 17 additions & 0 deletions pyteal/ast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from pyteal.ast.gitxn import Gitxn, GitxnExpr, GitxnaExpr, InnerTxnGroup
from pyteal.ast.gload import ImportScratchValue
from pyteal.ast.global_ import Global, GlobalField

from pyteal.ast.app import App, AppField, OnComplete, AppParam, AppParamObject
from pyteal.ast.asset import (
AssetHolding,
Expand All @@ -33,6 +34,15 @@
AssetParamObject,
)
from pyteal.ast.acct import AccountParam, AccountParamObject
from pyteal.ast.box import (
BoxCreate,
BoxDelete,
BoxExtract,
BoxReplace,
BoxLen,
BoxGet,
BoxPut,
)

# inner txns
from pyteal.ast.itxn import InnerTxnBuilder, InnerTxn, InnerTxnAction
Expand Down Expand Up @@ -175,6 +185,13 @@
"LeafExpr",
"Addr",
"Bytes",
"BoxCreate",
"BoxDelete",
"BoxReplace",
"BoxExtract",
"BoxLen",
"BoxGet",
"BoxPut",
"Int",
"EnumInt",
"MethodSignature",
Expand Down
91 changes: 90 additions & 1 deletion pyteal/ast/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
from typing import TYPE_CHECKING, Final
from enum import Enum
from pyteal.ast.box import (
BoxCreate,
BoxDelete,
BoxExtract,
BoxReplace,
BoxLen,
BoxGet,
BoxPut,
)

from pyteal.types import TealType, require_type
from pyteal.ir import TealOp, Op, TealBlock
Expand Down Expand Up @@ -181,7 +190,7 @@ def globalPut(cls, key: Expr, value: Expr) -> "App":

Args:
key: The key to write in the global application state. Must evaluate to bytes.
value: THe value to write in the global application state. Can evaluate to any type.
value: The value to write in the global application state. Can evaluate to any type.
"""
require_type(key, TealType.bytes)
require_type(value, TealType.anytype)
Expand Down Expand Up @@ -211,6 +220,86 @@ def globalDel(cls, key: Expr) -> "App":
require_type(key, TealType.bytes)
return cls(AppField.globalDel, [key])

@classmethod
def box_create(cls, name: Expr, size: Expr) -> Expr:
"""Create a box with a given name and size.

New boxes will contain a byte string of all zeros. Performing this operation on a box that
already exists will not change its contents.

If successful, this expression returns 0 if the box already existed, otherwise it returns 1.

A failure will occur if you attempt to create a box that already exists with a different size.

Args:
name: The key used to reference this box. Must evaluate to a bytes.
size: The number of bytes to reserve for this box. Must evaluate to a uint64.
"""
return BoxCreate(name, size)

@classmethod
def box_delete(cls, name: Expr) -> Expr:
"""Deletes a box given it's name.

This expression returns 1 if the box existed, otherwise it returns 0.

Deleting a nonexistent box is allowed, but has no effect.

Args:
name: The key the box was created with. Must evaluate to bytes.
"""
return BoxDelete(name)

@classmethod
def box_extract(cls, name: Expr, start: Expr, length: Expr) -> Expr:
"""Extracts bytes in a box given its name, start index and stop index.

Args:
name: The key the box was created with. Must evaluate to bytes.
start: The byte index into the box to start reading. Must evaluate to uint64.
length: The byte length into the box from start to stop reading. Must evaluate to uint64.
"""
return BoxExtract(name, start, length)

@classmethod
def box_replace(cls, name: Expr, start: Expr, value: Expr) -> Expr:
"""Replaces bytes in a box given its name, start index, and value.

Args:
name: The key the box was created with. Must evaluate to bytes.
start: The byte index into the box to start writing. Must evaluate to uint64.
value: The value to start writing at start index. Must evaluate to bytes.
"""
return BoxReplace(name, start, value)

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

Args:
name: The key the box was created with. Must evaluate to bytes.
"""
return BoxLen(name)

@classmethod
def box_get(cls, name: Expr) -> MaybeValue:
"""Get the full contents of a box given its name.

Args:
name: The key the box was created with. Must evaluate to bytes.
"""
return BoxGet(name)

@classmethod
def box_put(cls, name: Expr, value: Expr) -> Expr:
"""Write all contents to a box given its name.

Args:
name: The key the box was created with. Must evaluate to bytes.
value: The value to write to the box. Must evaluate to bytes.
"""
return BoxPut(name, value)


App.__module__ = "pyteal"

Expand Down
Loading