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 Boxes Ops in Pyteal #438

Merged
merged 50 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5bb0d09
add box ops
barnjamin Jun 1, 2022
ff16945
full support on ops
ahangsu Jul 12, 2022
c6b4b91
first set of test, add versioning in multi
ahangsu Jul 12, 2022
9be38c3
remove some seemingly not necessary code?
ahangsu Jul 12, 2022
f6c63de
update testcase
ahangsu Jul 12, 2022
7389aeb
check invalid arguments
ahangsu Jul 13, 2022
8f41bad
finish testcase
ahangsu Jul 13, 2022
26aa362
move stuffs to app
ahangsu Jul 13, 2022
1305ea8
Merge remote-tracking branch 'origin/avm8' into feature/avm-box
ahangsu Jul 13, 2022
ec831e2
version check trick
ahangsu Jul 13, 2022
7dd22a6
Merge branch 'avm8' into feature/avm-box
ahangsu Jul 13, 2022
2944475
verifyTealVersion apply
ahangsu Jul 13, 2022
eec18ea
error message
ahangsu Jul 14, 2022
d6766d5
Merge branch 'avm8' into feature/avm-box
ahangsu Jul 14, 2022
949acd6
update docs structures
ahangsu Jul 14, 2022
ba77a22
period
ahangsu Jul 15, 2022
2767d12
update doc
ahangsu Jul 15, 2022
0a977b2
update doc
ahangsu Jul 18, 2022
28546dd
update doc
ahangsu Jul 18, 2022
3781fcb
per pr review on implementation
ahangsu Jul 19, 2022
75c08e3
Update docs/state.rst
ahangsu Jul 19, 2022
8a6738f
Update docs/state.rst
ahangsu Jul 19, 2022
abfffcf
Update docs/state.rst
ahangsu Jul 19, 2022
10c0730
Update docs/state.rst
ahangsu Jul 19, 2022
35d7e25
Update docs/state.rst
ahangsu Jul 19, 2022
1b95772
Update docs/state.rst
ahangsu Jul 19, 2022
198587e
Update docs/state.rst
ahangsu Jul 19, 2022
c819c78
Update docs/state.rst
ahangsu Jul 19, 2022
05d7b5c
Update docs/state.rst
ahangsu Jul 19, 2022
9285662
hex box size goes wild
ahangsu Jul 19, 2022
ae15c6f
Update docs/state.rst
ahangsu Jul 19, 2022
230894e
warning about MBR
ahangsu Jul 19, 2022
00767c2
Merge branch 'feature/avm-box' of github.com:algorand/pyteal into fea…
ahangsu Jul 19, 2022
eeb1b09
wording
ahangsu Jul 19, 2022
c1b2f15
Update docs/state.rst
ahangsu Jul 19, 2022
1aebe98
Merge branch 'feature/avm-box' of github.com:algorand/pyteal into fea…
ahangsu Jul 19, 2022
a5d6bf9
emphasize
ahangsu Jul 19, 2022
e384df6
Update docs/state.rst
ahangsu Jul 19, 2022
81e5f7a
Update docs/state.rst
ahangsu Jul 19, 2022
d4c3af8
Merge branch 'feature/avm-box' of github.com:algorand/pyteal into fea…
ahangsu Jul 19, 2022
65b404a
polishing
ahangsu Jul 19, 2022
a835be2
remove redundant box_put doc segment
ahangsu Jul 19, 2022
3d39d3f
per zeph pr review
ahangsu Jul 19, 2022
1185948
use note and warning
ahangsu Jul 19, 2022
16152ee
per zeph's pr review
ahangsu Jul 19, 2022
60439f6
Update docs/state.rst
ahangsu Jul 20, 2022
ef5e7f7
creating boxes
ahangsu Jul 20, 2022
ec5e8c4
Update docs/state.rst
ahangsu Jul 20, 2022
f4c581e
per pr review
ahangsu Jul 20, 2022
5481e8e
table for state types
ahangsu Jul 20, 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
165 changes: 155 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
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
---------------------

================== ==================== ====================== ==================== ======================
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,138 @@ 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.

:any:`App.box_create` makes a box with a specified name and byte length.
The first argument is the box name, and the second argument is the byte size to be allocated.

For example:

.. code-block:: python

# Allocate a box called "BoxA" of byte size 100
App.box_create(Bytes("BoxA"), Int(100))
# Allocate a box called "BoxB" of byte size 90
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 replacement length is **not identical** to the 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."))
ahangsu marked this conversation as resolved.
Show resolved Hide resolved

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 5'th 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`
App.box_get(Bytes("NoteBook")).value()


.. note::

:any:`App.box_get` can also be used to check the existence of a box. For example:

.. code-block:: python

# check existence of a box named `NoteBook`
App.box_get(Bytes("NoteBook")).hasValue()

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

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

.. code-block:: python

App.box_delete(Bytes("boxToRemove"))

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

# search for the box length for box `someBox`, and get the bool value for box existence
App.box_length(Bytes("someBox")).hasValue()
ahangsu marked this conversation as resolved.
Show resolved Hide resolved

.. 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 @@ -45,6 +45,13 @@ __all__ = [
"BitwiseOr",
"BitwiseXor",
"Block",
"BoxCreate",
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
"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 @@ -28,6 +28,16 @@
from pyteal.ast.app import App, AppField, OnComplete, AppParam
from pyteal.ast.asset import AssetHolding, AssetParam
from pyteal.ast.acct import AccountParam
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 @@ -160,6 +170,13 @@
"LeafExpr",
"Addr",
"Bytes",
"BoxCreate",
"BoxDelete",
"BoxReplace",
"BoxExtract",
"BoxLen",
"BoxGet",
"BoxPut",
"Int",
"EnumInt",
"MethodSignature",
Expand Down
87 changes: 86 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
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,82 @@ 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) -> BoxCreate:
"""
Create a box with a given name and 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) -> BoxDelete:
"""
Deletes a box given it's name.

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) -> BoxExtract:
"""
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) -> BoxReplace:
"""
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) -> BoxPut:
"""
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