diff --git a/docs/state.rst b/docs/state.rst index adb585b26..b45a5a0c6 100644 --- a/docs/state.rst +++ b/docs/state.rst @@ -28,8 +28,8 @@ Current App Global :any:`App.globalPut` :any:`App.gl 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` +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 @@ -279,30 +279,35 @@ 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 :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 - App.box_create(Bytes("BoxA"), Int(100)) - # Allocate a box called "BoxB" of byte size 90 - App.box_create(Bytes("BoxB"), Int(90) + # 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 replacement length is **not identical** to the box's byte size); + (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.")) @@ -335,7 +340,7 @@ 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` + # 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. @@ -348,27 +353,29 @@ 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() + # 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. For example: +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 - App.box_delete(Bytes("boxToRemove")) + # 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -383,8 +390,12 @@ 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() + # 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:: diff --git a/pyteal/ast/app.py b/pyteal/ast/app.py index cf9993d46..e6f9d0de9 100644 --- a/pyteal/ast/app.py +++ b/pyteal/ast/app.py @@ -221,9 +221,15 @@ def globalDel(cls, key: Expr) -> "App": return cls(AppField.globalDel, [key]) @classmethod - def box_create(cls, name: Expr, size: Expr) -> BoxCreate: - """ - Create a box with a given name and size. + 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. @@ -232,9 +238,12 @@ def box_create(cls, name: Expr, size: Expr) -> BoxCreate: return BoxCreate(name, size) @classmethod - def box_delete(cls, name: Expr) -> BoxDelete: - """ - Deletes a box given it's name. + 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. @@ -242,9 +251,8 @@ def box_delete(cls, name: Expr) -> BoxDelete: 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. + 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. @@ -254,9 +262,8 @@ def box_extract(cls, name: Expr, start: Expr, length: Expr) -> BoxExtract: 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. + 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. @@ -267,8 +274,7 @@ def box_replace(cls, name: Expr, start: Expr, value: Expr) -> BoxReplace: @classmethod def box_length(cls, name: Expr) -> MaybeValue: - """ - Get the byte length of the box specified by its name. + """Get the byte length of the box specified by its name. Args: name: The key the box was created with. Must evaluate to bytes. @@ -277,8 +283,7 @@ def box_length(cls, name: Expr) -> MaybeValue: @classmethod def box_get(cls, name: Expr) -> MaybeValue: - """ - Get the full contents of a box given its name. + """Get the full contents of a box given its name. Args: name: The key the box was created with. Must evaluate to bytes. @@ -286,9 +291,8 @@ def box_get(cls, name: Expr) -> MaybeValue: return BoxGet(name) @classmethod - def box_put(cls, name: Expr, value: Expr) -> BoxPut: - """ - Write all contents to a box given its name. + 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. diff --git a/pyteal/ast/box_test.py b/pyteal/ast/box_test.py index c3ec419f3..008ffb580 100644 --- a/pyteal/ast/box_test.py +++ b/pyteal/ast/box_test.py @@ -131,10 +131,10 @@ def test_box_length(): expected = pt.TealSimpleBlock( [ - pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), - pt.TealOp(expr, pt.Op.box_len), - pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), - pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + pt.TealOp(None, pt.Op.byte, '"eineName"'), + pt.TealOp(None, pt.Op.box_len), + pt.TealOp(None, pt.Op.store, expr.output_slots[1]), + pt.TealOp(None, pt.Op.store, expr.output_slots[0]), ] ) actual, _ = expr.__teal__(avm8Options) @@ -151,10 +151,10 @@ def test_box_get(): expected = pt.TealSimpleBlock( [ - pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), - pt.TealOp(expr, pt.Op.box_get), - pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), - pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + pt.TealOp(None, pt.Op.byte, '"eineName"'), + pt.TealOp(None, pt.Op.box_get), + pt.TealOp(None, pt.Op.store, expr.output_slots[1]), + pt.TealOp(None, pt.Op.store, expr.output_slots[0]), ] ) actual, _ = expr.__teal__(avm8Options) diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 4d863e6ff..4c6091953 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -67,7 +67,7 @@ def value(self) -> ScratchLoad: def slotOk(self) -> ScratchSlot: """Get the scratch slot that stores hasValue. - Note: This is mainly added for backwards compatability and normally shouldn't be used + Note: This is mainly added for backwards compatibility and normally shouldn't be used directly in pyteal code. """ return self.output_slots[1] @@ -77,7 +77,7 @@ def slotValue(self) -> ScratchSlot: """Get the scratch slot that stores the value or the zero value for the type if the value doesn't exist. - Note: This is mainly added for backwards compatability and normally shouldn't be used + Note: This is mainly added for backwards compatibility and normally shouldn't be used directly in pyteal code. """ return self.output_slots[0]