Skip to content

Commit 8c0445e

Browse files
authored
Support for AVM v10 (#714)
1 parent 0d6c1f2 commit 8c0445e

25 files changed

+889
-208
lines changed

.flake8

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ per-file-ignores =
2323
examples/signature/recurring_swap.py: F403, F405
2424
examples/signature/split.py: F403, F405
2525
pyteal/__init__.py: F401, F403
26+
pyteal/ast/ec.py: E222
2627
pyteal/compiler/flatten.py: F821
2728
pyteal/compiler/optimizer/__init__.py: F401
2829
pyteal/ir/ops.py: E221

CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
## Added
44

5+
* Support for AVM v10 programs. ([#714](https://github.com/algorand/pyteal/pull/714))
6+
* New box operations:
7+
* `App.box_resize`
8+
* `App.box_splice`
9+
* New `Global` fields:
10+
* `Global.asset_create_min_balance()`
11+
* `Global.asset_opt_in_min_balance()`
12+
* `Global.genesis_hash()`
13+
* New elliptic curve operations:
14+
* `EcAdd`
15+
* `EcScalarMul`
16+
* `EcPairingCheck`
17+
* `EcMultiScalarMul`
18+
* `EcSubgroupCheck`
19+
* `EcMapTo`
520
* Support for Python 3.12. ([#713](https://github.com/algorand/pyteal/pull/713))
621

722
## Fixed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ algod-stop:
8585
docker compose stop algod
8686

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

9090
# Run tests w/ @pytest.mark.serial under ~/tests/integration each in its own proc:
9191
test-integ-sync:

docker-compose.yml

+8-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ version: '3'
22

33
services:
44
algod:
5-
image: wwinder/algod:dev
5+
image: algorand/algod:master
66
platform: linux/amd64
77
ports:
88
- "4001:8080" # algod
99
- "4160:4160" # gossip
1010
- "9100:9100" # prometheus
1111
environment:
12-
- DEV_MODE=true
13-
- TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
12+
- DEV_MODE=1
13+
- PROFILE=development
14+
# Can remove ADMIN_TOKEN if the code stops passing an auth token. The development profile
15+
# enables the DisableAPIAuth config option, however an unfortunate side effect of this
16+
# option is that requests will error if *any* auth token is passed. Issue: https://github.com/algorand/go-algorand/issues/5883
17+
- ADMIN_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
1418
healthcheck:
15-
test: goal node status
19+
test: curl -f http://localhost:8080/health
1620
interval: 2s
1721
retries: 30

docs/accessing_transaction_field.rst

+9-3
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ Global Parameters
222222
Information about the current state of the blockchain can be obtained using the following
223223
:any:`Global` expressions:
224224

225-
=========================================== ======================= ==================== =============================================================
225+
=========================================== ======================= ==================== ===================================================================================================================
226226
Operator Type Min Program Version Notes
227-
=========================================== ======================= ==================== =============================================================
227+
=========================================== ======================= ==================== ===================================================================================================================
228228
:any:`Global.min_txn_fee()` :code:`TealType.uint64` 2 in microAlgos
229229
:any:`Global.min_balance()` :code:`TealType.uint64` 2 in microAlgos
230230
:any:`Global.max_txn_life()` :code:`TealType.uint64` 2 number of rounds
@@ -237,4 +237,10 @@ Operator Type Min Program
237237
:any:`Global.creator_address()` :code:`TealType.bytes` 3 32 byte address of the creator of the current application
238238
:any:`Global.current_application_address()` :code:`TealType.bytes` 5 32 byte address of the current application controlled account
239239
:any:`Global.group_id()` :code:`TealType.bytes` 5 32 byte ID of the current transaction group
240-
=========================================== ======================= ==================== =============================================================
240+
:any:`Global.opcode_budget()` :code:`TealType.uint64` 6 The remaining cost that can be spent by opcodes in this program
241+
:any:`Global.caller_app_id()` :code:`TealType.uint64` 6 The ID of the application that called the current application, or zero. Application mode only
242+
: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.
243+
:any:`Global.asset_create_min_balance()` :code:`TealType.uint64` 10 The minimum balance required to create and opt into an asset
244+
:any:`Global.asset_opt_in_min_balance()` :code:`TealType.uint64` 10 The minimum balance required to opt in to an asset
245+
:any:`Global.genesis_hash()` :code:`TealType.bytes` 10 The genesis hash for the network
246+
=========================================== ======================= ==================== ===================================================================================================================

docs/state.rst

+56-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Other App Global :any:`App.gl
3030
Other App Local :any:`App.localGetEx` :any:`App.localGetEx`
3131
Current App Boxes :any:`App.box_create` :any:`App.box_put` :any:`App.box_extract` :any:`App.box_delete` :any:`App.box_length`
3232
:any:`App.box_put` :any:`App.box_replace` :any:`App.box_get` :any:`App.box_get`
33+
:any:`App.box_splice`
34+
:any:`App.box_resize`
3335
================== ======================= ======================== ======================== ===================== =======================
3436

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

270-
Box sizes and names cannot be changed after initial allocation, but they can be deleted and re-allocated.
271272
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.
272273

274+
Boxes are fixed-length structures, though they can be resized with the :any:`App.box_resize` method (or by deleting and recreating the box).
275+
273276
The following sections explain how to work with boxes.
274277

275278
.. _Creating Boxes:
@@ -311,19 +314,67 @@ For :any:`App.box_put`, the first argument is the box name to create or to write
311314
# write to box `poemLine` with new value
312315
App.box_put(Bytes("poemLine"), Bytes("The lone and level sands stretch far away."))
313316
317+
Resizing Boxes
318+
~~~~~~~~~~~~~~
319+
320+
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.
321+
322+
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.
323+
324+
.. note::
325+
If the new size is smaller than the existing box's byte size, then the box will lose the bytes at the end.
326+
If the new size is larger than the existing box's byte size, then the box will be padded with zeros at the end.
327+
328+
For all size changes, the app account's minimum balance requirement (MBR) will be updated accordingly.
329+
330+
For example:
331+
332+
.. code-block:: python
333+
334+
# resize a box called "BoxA" to byte size 200
335+
App.box_resize(Bytes("BoxA"), Int(200))
336+
314337
Writing to a Box
315338
~~~~~~~~~~~~~~~~
316339
317-
To write to a box, use :any:`App.box_replace`, or :any:`App.box_put` method.
340+
To write to a box, use :any:`App.box_replace`, :any:`App.box_splice` , or :any:`App.box_put` method.
318341
319-
:any:`App.box_replace` writes bytes of certain length from a start index in a box.
342+
:any:`App.box_replace` replaces a range of bytes in a box.
320343
The first argument is the box name to write into, the second argument is the starting index to write,
321344
and the third argument is the replacement bytes. For example:
322345
323346
.. code-block:: python
324347
325-
# replace 2 bytes starting from the 0'th byte by `Ne` in the box named `wordleBox`
326-
App.box_replace(Bytes("wordleBox"), Int(0), Bytes("Ne"))
348+
# Assume the box named "wordleBox" initially contains the bytes "cones"
349+
350+
# Replace 2 bytes starting from index 1 with "ap" in the box named "wordleBox"
351+
App.box_replace(Bytes("wordleBox"), Int(1), Bytes("ap"))
352+
353+
# The result is that the box named "wordleBox" now contains the bytes "capes"
354+
355+
:any:`App.box_splice` is a more general version of :any:`App.box_replace`. This operation takes an
356+
additional argument, which is the length of the bytes in the box to be replaced. By specifying a
357+
different length than the bytes you are inserting, you can shift contents of the box instead of just
358+
replacing a range of bytes.
359+
360+
For example:
361+
362+
.. code-block:: python
363+
364+
# Assume the box named "flavors" initially contains the bytes "banana_apple_cherry_______"
365+
366+
# Insert "grape_" at index 7 in the box named "flavors". By specifying a length of 0, the
367+
# following bytes will be shifted to the right.
368+
App.box_splice(Bytes("flavors"), Int(7), Int(0), Bytes("grape_"))
369+
370+
# The result is that the box named "flavors" now contains the bytes "banana_grape_apple_cherry_"
371+
372+
# If we want to zero the box, we can replace the entire contents with an empty string.
373+
App.box_splice(Bytes("flavors"), Int(0), Int(26), Bytes(""))
374+
# The "flavors" box now contains "00000000000000000000000000". Ready for reuse!
375+
376+
Recall that boxes are fixed length, so shifting bytes can cause the box to truncate or pad with zeros.
377+
More information is available in the docstring for :any:`App.box_splice`.
327378
328379
:any:`App.box_put` writes the full contents to a pre-existing box, as is mentioned in `Creating Boxes`_.
329380

docs/versions.rst

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ AVM Version PyTeal Version
1919
7 >= 0.15.0
2020
8 >= 0.20.0
2121
9 >= 0.25.0
22+
10 >= 0.26.0
2223
============ ==============
2324

2425
.. _version pragmas:

pyteal/__init__.pyi

+9
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ __all__ = [
7676
"BoxLen",
7777
"BoxPut",
7878
"BoxReplace",
79+
"BoxResize",
80+
"BoxSplice",
7981
"Break",
8082
"Btoi",
8183
"Bytes",
@@ -109,12 +111,19 @@ __all__ = [
109111
"Div",
110112
"Divw",
111113
"DynamicScratchVar",
114+
"EcAdd",
115+
"EcMapTo",
116+
"EcMultiScalarMul",
117+
"EcPairingCheck",
118+
"EcScalarMul",
119+
"EcSubgroupCheck",
112120
"EcdsaCurve",
113121
"EcdsaDecompress",
114122
"EcdsaRecover",
115123
"EcdsaVerify",
116124
"Ed25519Verify",
117125
"Ed25519Verify_Bare",
126+
"EllipticCurve",
118127
"EnumInt",
119128
"Eq",
120129
"Err",

pyteal/ast/__init__.py

+20
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
from pyteal.ast.acct import AccountParam, AccountParamObject
3737
from pyteal.ast.box import (
3838
BoxCreate,
39+
BoxResize,
3940
BoxDelete,
4041
BoxExtract,
4142
BoxReplace,
43+
BoxSplice,
4244
BoxLen,
4345
BoxGet,
4446
BoxPut,
@@ -169,6 +171,15 @@
169171
from pyteal.ast.multi import MultiValue
170172
from pyteal.ast.opup import OpUp, OpUpMode, OpUpFeeSource
171173
from pyteal.ast.ecdsa import EcdsaCurve, EcdsaVerify, EcdsaDecompress, EcdsaRecover
174+
from pyteal.ast.ec import (
175+
EllipticCurve,
176+
EcAdd,
177+
EcScalarMul,
178+
EcPairingCheck,
179+
EcMultiScalarMul,
180+
EcSubgroupCheck,
181+
EcMapTo,
182+
)
172183
from pyteal.ast.router import (
173184
BareCallActions,
174185
CallConfig,
@@ -212,8 +223,10 @@
212223
"BitwiseXor",
213224
"Block",
214225
"BoxCreate",
226+
"BoxResize",
215227
"BoxDelete",
216228
"BoxExtract",
229+
"BoxSplice",
217230
"BoxGet",
218231
"BoxLen",
219232
"BoxPut",
@@ -252,6 +265,13 @@
252265
"EcdsaVerify",
253266
"Ed25519Verify_Bare",
254267
"Ed25519Verify",
268+
"EllipticCurve",
269+
"EcAdd",
270+
"EcScalarMul",
271+
"EcPairingCheck",
272+
"EcMultiScalarMul",
273+
"EcSubgroupCheck",
274+
"EcMapTo",
255275
"EnumInt",
256276
"Eq",
257277
"Err",

pyteal/ast/abstractvar.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def alloc_abstract_var(stack_type: TealType) -> AbstractVar:
5252
stack_type: TealType that represents stack type.
5353
"""
5454

55-
from pyteal.ast import ScratchVar
55+
from pyteal.ast.scratchvar import ScratchVar
5656
from pyteal.ast.subroutine import SubroutineEval
5757
from pyteal.ast.frame import FrameVar, MAX_FRAME_LOCAL_VARS
5858

pyteal/ast/app.py

+39
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
from enum import Enum
33
from pyteal.ast.box import (
44
BoxCreate,
5+
BoxResize,
56
BoxDelete,
67
BoxExtract,
78
BoxReplace,
9+
BoxSplice,
810
BoxLen,
911
BoxGet,
1012
BoxPut,
@@ -237,6 +239,19 @@ def box_create(cls, name: Expr, size: Expr) -> Expr:
237239
"""
238240
return BoxCreate(name, size)
239241

242+
@classmethod
243+
def box_resize(cls, name: Expr, size: Expr) -> Expr:
244+
"""Resize an existing box.
245+
246+
If the new size is larger than the old size, zero bytes will be added to the end of the box.
247+
If the new size is smaller than the old size, the box will be truncated from the end.
248+
249+
Args:
250+
name: The key used to reference this box. Must evaluate to a bytes.
251+
size: The new number of bytes to reserve for this box. Must evaluate to a uint64.
252+
"""
253+
return BoxResize(name, size)
254+
240255
@classmethod
241256
def box_delete(cls, name: Expr) -> Expr:
242257
"""Deletes a box given it's name.
@@ -272,6 +287,30 @@ def box_replace(cls, name: Expr, start: Expr, value: Expr) -> Expr:
272287
"""
273288
return BoxReplace(name, start, value)
274289

290+
@classmethod
291+
def box_splice(
292+
cls, name: Expr, start: Expr, length: Expr, new_content: Expr
293+
) -> Expr:
294+
"""
295+
Replaces the range of bytes from `start` through `start + length` with `new_content`.
296+
297+
Bytes after `start + length` will be shifted to the right.
298+
299+
Recall that boxes are constant length, and this operation will not change the length of the
300+
box. Instead content may be adjusted as so:
301+
302+
* 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.
303+
304+
* 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.
305+
306+
Args:
307+
name: The name of the box to modify. Must evaluate to bytes.
308+
start: The byte index into the box to start writing. Must evaluate to uint64.
309+
length: The length of the bytes to be replaced. Must evaluate to uint64.
310+
new_content: The new content to write into the box. Must evaluate to bytes.
311+
"""
312+
return BoxSplice(name, start, length, new_content)
313+
275314
@classmethod
276315
def box_length(cls, name: Expr) -> MaybeValue:
277316
"""Get the byte length of the box specified by its name.

0 commit comments

Comments
 (0)