From 63b8910dc506708fe4abd487355ed20bc421c7fe Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 29 Jul 2022 14:25:36 -0400 Subject: [PATCH 1/4] More details on spec (and unifiy the box opcodes a bit) --- cmd/goal/examples/boxes.teal | 3 + data/transactions/logic/README.md | 14 +-- data/transactions/logic/TEAL_opcodes.md | 18 ++-- data/transactions/logic/box.go | 39 ++++--- data/transactions/logic/box_test.go | 120 ++++++++++----------- data/transactions/logic/doc.go | 14 +-- data/transactions/logic/eval.go | 4 +- data/transactions/logic/langspec.json | 18 ++-- data/transactions/logic/ledger_test.go | 25 +++-- data/transactions/logic/opcodes.go | 4 +- ledger/internal/applications.go | 39 ++++--- ledger/internal/boxtxn_test.go | 20 ++-- test/e2e-go/restAPI/restClient_test.go | 3 + test/scripts/e2e_subs/tealprogs/boxes.teal | 3 + 14 files changed, 175 insertions(+), 149 deletions(-) diff --git a/cmd/goal/examples/boxes.teal b/cmd/goal/examples/boxes.teal index c785b51af1..7cef871812 100644 --- a/cmd/goal/examples/boxes.teal +++ b/cmd/goal/examples/boxes.teal @@ -17,7 +17,9 @@ btoi // [btoi(arg[2])] default: // [24] // NumAppArgs >= 3 txn ApplicationArgs 1 // [24, arg[1]] + swap // [arg[1], 24] box_create // [] // boxes: arg[1] -> [24]byte + assert b end del: // delete box arg[1] txn ApplicationArgs 0 // [arg[0]] @@ -26,6 +28,7 @@ del: // delete box arg[1] bz set // "delete" ? continue : goto set txn ApplicationArgs 1 // [arg[1]] box_del // del boxes[arg[1]] + assert b end set: // put arg[1] at start of box arg[0] ... so actually a _partial_ "set" txn ApplicationArgs 0 // [arg[0]] diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index c9a7b38bf6..b335e7ea5f 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -615,13 +615,13 @@ Account fields used in the `acct_params_get` opcode. | `app_params_get f` | X is field F from app A. Y is 1 if A exists, else 0 | | `acct_params_get f` | X is field F from account A. Y is 1 if A owns positive algos, else 0 | | `log` | write A to log state of the current application | -| `box_create` | make a box | -| `box_extract` | read from a box | -| `box_replace` | write to a box | -| `box_del` | delete a box | -| `box_len` | length of a box | -| `box_get` | full contents of a box | -| `box_put` | write contents of box | +| `box_create` | create a box named A, of length B. Fail if A is empty or B exceeds 32,384. | +| `box_extract` | read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size. | +| `box_replace` | write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. | +| `box_del` | delete box named A if it exists. Return 1 if A existed, 0 otherwise | +| `box_len` | X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. | +| `box_get` | X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) > 4096. | +| `box_put` | replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist. | ### Inner Transactions diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index b7d2cf48f8..42068533c6 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1352,8 +1352,8 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 ## box_create - Opcode: 0xb9 -- Stack: ..., A: uint64, B: []byte → ... -- make a box +- Stack: ..., A: []byte, B: uint64 → ..., uint64 +- create a box named A, of length B. Fail if A is empty or B exceeds 32,384. - Availability: v7 - Mode: Application @@ -1361,7 +1361,7 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Opcode: 0xba - Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte -- read from a box +- read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size. - Availability: v7 - Mode: Application @@ -1369,15 +1369,15 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Opcode: 0xbb - Stack: ..., A: []byte, B: uint64, C: []byte → ... -- write to a box +- write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. - Availability: v7 - Mode: Application ## box_del - Opcode: 0xbc -- Stack: ..., A: []byte → ... -- delete a box +- Stack: ..., A: []byte → ..., uint64 +- delete box named A if it exists. Return 1 if A existed, 0 otherwise - Availability: v7 - Mode: Application @@ -1385,7 +1385,7 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Opcode: 0xbd - Stack: ..., A: []byte → ..., X: uint64, Y: uint64 -- length of a box +- X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. - Availability: v7 - Mode: Application @@ -1393,7 +1393,7 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Opcode: 0xbe - Stack: ..., A: []byte → ..., X: []byte, Y: uint64 -- full contents of a box +- X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) > 4096. - Availability: v7 - Mode: Application @@ -1401,7 +1401,7 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Opcode: 0xbf - Stack: ..., A: []byte, B: []byte → ... -- write contents of box +- replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist. - Availability: v7 - Mode: Application diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index c2db0a736d..f900e3f9b2 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -50,40 +50,44 @@ func (cx *EvalContext) availableBox(name string, operation int, createSize uint6 return nil } -func createBox(cx *EvalContext, name string, value string, appAddr basics.Address) error { +func createBox(cx *EvalContext, name string, value string, appAddr basics.Address) (bool, error) { // Enforce length rules. Currently these are the same as enforced by // ledger. If these were ever to change in proto, we would need to isolate // changes to different program versions. (so a v7 app could not see a // bigger box than expected, for example) if len(name) == 0 { - return fmt.Errorf("box names may not be zero length") + return false, fmt.Errorf("box names may not be zero length") } if len(name) > cx.Proto.MaxAppKeyLen { - return fmt.Errorf("name too long: length was %d, maximum is %d", len(name), cx.Proto.MaxAppKeyLen) + return false, fmt.Errorf("name too long: length was %d, maximum is %d", len(name), cx.Proto.MaxAppKeyLen) } size := uint64(len(value)) if size > cx.Proto.MaxBoxSize { - return fmt.Errorf("box size too large: %d, maximum is %d", size, cx.Proto.MaxBoxSize) + return false, fmt.Errorf("box size too large: %d, maximum is %d", size, cx.Proto.MaxBoxSize) } err := cx.availableBox(name, boxCreate, size) // annotate size for write budget check if err != nil { - return err + return false, err } return cx.Ledger.NewBox(cx.appID, name, value, appAddr) } func opBoxCreate(cx *EvalContext) error { - last := len(cx.stack) - 1 // name - prev := last - 1 // size + last := len(cx.stack) - 1 // size + prev := last - 1 // name - name := string(cx.stack[last].Bytes) - size := cx.stack[prev].Uint + name := string(cx.stack[prev].Bytes) + size := cx.stack[last].Uint appAddr := cx.getApplicationAddress(cx.appID) - cx.stack = cx.stack[:prev] - return createBox(cx, name, string(make([]byte, size)), appAddr) + cx.stack = cx.stack[:last] + existed, err := createBox(cx, name, string(make([]byte, size)), appAddr) + cx.stack[prev].Bytes = nil + cx.stack[prev].Uint = boolToUint(existed) + return err + } func opBoxExtract(cx *EvalContext) error { @@ -151,9 +155,14 @@ func opBoxDel(cx *EvalContext) error { if err != nil { return err } - cx.stack = cx.stack[:last] appAddr := cx.getApplicationAddress(cx.appID) - return cx.Ledger.DelBox(cx.appID, name, appAddr) + existed, err := cx.Ledger.DelBox(cx.appID, name, appAddr) + if err != nil { + return err + } + cx.stack[last].Bytes = nil + cx.stack[last].Uint = boolToUint(existed) + return nil } func opBoxLen(cx *EvalContext) error { @@ -222,8 +231,8 @@ func opBoxPut(cx *EvalContext) error { /* The box did not exist, so create it. */ appAddr := cx.getApplicationAddress(cx.appID) - return createBox(cx, name, value, appAddr) - + _, err = createBox(cx, name, value, appAddr) + return err } // MakeBoxKey creates the key that a box named `name` under app `appIdx` should use. diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index cdb3bcf47e..7083641930 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -36,20 +36,20 @@ func TestBoxNewDel(t *testing.T) { ep, txn, ledger := logic.MakeSampleEnv() ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - logic.TestApp(t, `int 24; byte "self"; box_create; int 1`, ep) + logic.TestApp(t, `byte "self"; int 24; box_create`, ep) ledger.DeleteBox(888, "self") - logic.TestApp(t, `int 24; byte "self"; box_create; int 1`, ep) + logic.TestApp(t, `byte "self"; int 24; box_create`, ep) ledger.DeleteBox(888, "self") - logic.TestApp(t, `int 24; byte "self"; box_create; int 24; byte "self"; box_create; int 1`, ep, - `already exists`) + logic.TestApp(t, `byte "self"; int 24; box_create; assert; byte "self"; int 24; box_create; !`, ep) ledger.DeleteBox(888, "self") - logic.TestApp(t, `int 24; byte "self"; box_create; int 24; byte "other"; box_create; int 1`, ep) + logic.TestApp(t, `byte "self"; int 24; box_create; assert; byte "other"; int 24; box_create`, ep) ledger.DeleteBox(888, "self") - logic.TestApp(t, `int 24; byte "self"; box_create; byte "self"; box_del; int 1`, ep) - logic.TestApp(t, `int 24; byte "self"; box_del; int 1`, ep, `no such box`) - logic.TestApp(t, `int 24; byte "self"; box_create; byte "self"; box_del; byte "self"; box_del; int 1`, ep, - `no such box`) + logic.TestApp(t, `byte "self"; int 24; box_create; assert; byte "self"; box_del`, ep) + logic.TestApp(t, `byte "self"; box_del; !`, ep) + logic.TestApp(t, `byte "self"; int 24; box_create; assert + byte "self"; box_del; assert + byte "self"; box_del; !`, ep) } func TestBoxNewBad(t *testing.T) { @@ -59,26 +59,26 @@ func TestBoxNewBad(t *testing.T) { ep, txn, ledger := logic.MakeSampleEnv() ledger.NewApp(txn.Sender, 888, basics.AppParams{}) - logic.TestApp(t, `int 999; byte "self"; box_create; int 1`, ep, "write budget") + logic.TestApp(t, `byte "self"; int 999; box_create`, ep, "write budget") ledger.DeleteBox(888, "self") // In test proto, you get 100 I/O budget per boxref ten := [10]transactions.BoxRef{} txn.Boxes = append(txn.Boxes, ten[:]...) // write budget is now 11*100 = 1100 - logic.TestApp(t, `int 999; byte "self"; box_create; int 1`, ep) + logic.TestApp(t, `byte "self"; int 999; box_create`, ep) ledger.DeleteBox(888, "self") - logic.TestApp(t, `int 1000; byte "self"; box_create; int 1`, ep) + logic.TestApp(t, `byte "self"; int 1000; box_create`, ep) ledger.DeleteBox(888, "self") - logic.TestApp(t, `int 1001; byte "self"; box_create; int 1`, ep, "box size too large") + logic.TestApp(t, `byte "self"; int 1001; box_create`, ep, "box size too large") - logic.TestApp(t, `int 1000; byte "unknown"; box_create; int 1`, ep, "invalid Box reference") + logic.TestApp(t, `byte "unknown"; int 1000; box_create`, ep, "invalid Box reference") long := strings.Repeat("x", 65) txn.Boxes = []transactions.BoxRef{{Name: []byte(long)}} - logic.TestApp(t, fmt.Sprintf(`int 1000; byte "%s"; box_create; int 1`, long), ep, "name too long") + logic.TestApp(t, fmt.Sprintf(`byte "%s"; int 1000; box_create`, long), ep, "name too long") txn.Boxes = []transactions.BoxRef{{Name: []byte("")}} // irrelevant, zero check comes first anyway - logic.TestApp(t, `int 1000; byte ""; box_create; int 1`, ep, "zero length") + logic.TestApp(t, `byte ""; int 1000; box_create`, ep, "zero length") } func TestBoxReadWrite(t *testing.T) { @@ -90,7 +90,7 @@ func TestBoxReadWrite(t *testing.T) { ledger.NewApp(txn.Sender, 888, basics.AppParams{}) // extract some bytes until past the end, confirm the begin as zeros, and // when it fails. - logic.TestApp(t, `int 4; byte "self"; box_create; + logic.TestApp(t, `byte "self"; int 4; box_create; assert byte "self"; int 1; int 2; box_extract; byte 0x0000; ==; assert; byte "self"; int 1; int 3; box_extract; @@ -134,12 +134,12 @@ func TestBoxAcrossTxns(t *testing.T) { ledger.NewApp(basics.Address{}, 888, basics.AppParams{}) // After creation in first txn, second one can read it (though it's empty) logic.TestApps(t, []string{ - `int 64; byte "self"; box_create; int 1`, + `byte "self"; int 64; box_create`, `byte "self"; int 10; int 4; box_extract; byte 0x00000000; ==`, }, nil, 7, ledger) // after creation, modification, the third can read it logic.TestApps(t, []string{ - `int 64; byte "self"; box_create; int 1`, + `byte "self"; int 64; box_create`, `byte "self"; int 2; byte "hi"; box_replace; int 1`, `byte "self"; int 1; int 4; box_extract; byte 0x00686900; ==`, // "\0hi\0" }, nil, 7, ledger) @@ -154,7 +154,7 @@ func TestBoxAvailability(t *testing.T) { // B is not available (recall that "self" is set up by MakeSampleEnv, in TestApps) logic.TestApps(t, []string{ - `int 64; byte "self"; box_create; int 1`, + `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, }, nil, 7, ledger, logic.NewExpect(1, "invalid Box reference B")) @@ -166,7 +166,7 @@ func TestBoxAvailability(t *testing.T) { }.SignedTxn()) group[0].Txn.Type = protocol.ApplicationCallTx logic.TestApps(t, []string{ - `int 64; byte "self"; box_create; int 1`, + `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, }, group, 7, ledger, logic.NewExpect(1, "no such box")) @@ -179,7 +179,7 @@ func TestBoxAvailability(t *testing.T) { }.SignedTxn()) group[0].Txn.Type = protocol.ApplicationCallTx logic.TestApps(t, []string{ - `int 64; byte "self"; box_create; int 1`, + `byte "self"; int 64; box_create`, `byte "B"; int 10; int 4; box_extract; byte 0x00000000; ==`, }, group, 7, ledger, logic.NewExpect(1, "no such box")) @@ -203,58 +203,49 @@ func TestBoxWriteBudget(t *testing.T) { // Sample tx[0] has two box refs, so write budget is 2*100 // Test simple use of one box, less than, equal, or over budget - logic.TestApp(t, `int 4; byte "self"; box_create; - int 1`, ep) - logic.TestApp(t, `byte "self"; box_del; - int 199; byte "self"; box_create; - int 1`, ep) - logic.TestApp(t, `byte "self"; box_del; - int 200; byte "self"; box_create; - int 1`, ep) - logic.TestApp(t, `byte "self"; box_del; - int 201; byte "self"; box_create; - int 1`, ep, "write budget (200) exceeded") + logic.TestApp(t, `byte "self"; int 4; box_create`, ep) + logic.TestApp(t, `byte "self"; box_del; assert + byte "self"; int 199; box_create`, ep) + logic.TestApp(t, `byte "self"; box_del; assert + byte "self"; int 200; box_create`, ep) + logic.TestApp(t, `byte "self"; box_del; assert + byte "self"; int 201; box_create`, ep, "write budget (200) exceeded") ledger.DeleteBox(888, "self") // cleanup (doing it in a program would fail b/c the 201 len box exists) // Test interplay of two different boxes being created - logic.TestApp(t, `int 4; byte "self"; box_create; - int 4; byte "other"; box_create; - int 1`, ep) + logic.TestApp(t, `byte "self"; int 4; box_create; assert + byte "other"; int 4; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del; byte "other"; box_del; - int 4; byte "self"; box_create; - int 196; byte "other"; box_create; - int 1`, ep) + logic.TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del; assert + byte "self"; int 4; box_create; assert; + byte "other"; int 196; box_create`, ep) - logic.TestApp(t, `byte "self"; box_del; byte "other"; box_del; - int 6; byte "self"; box_create; - int 196; byte "other"; box_create; - int 1`, ep, "write budget (200) exceeded") + logic.TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del; assert + byte "self"; int 6; box_create; assert + byte "other"; int 196; box_create`, ep, + "write budget (200) exceeded") ledger.DeleteBox(888, "other") - logic.TestApp(t, `byte "self"; box_del; - int 6; byte "self"; box_create; - int 196; byte "other"; box_create; - byte "self"; box_del; // deletion means we don't pay for write bytes - int 1`, ep) - logic.TestApp(t, `byte "other"; box_del; int 1`, ep) // cleanup (self was already deleted in last test) - logic.TestApp(t, `byte "other"; box_del; int 1`, ep, "no such box") - logic.TestApp(t, `byte "junk"; box_del; int 1`, ep, "invalid Box reference") + logic.TestApp(t, `byte "self"; box_del; assert + byte "self"; int 6; box_create; assert + byte "other"; int 196; box_create; assert + byte "self"; box_del;`, ep) // deletion means we don't pay for write bytes + + logic.TestApp(t, `byte "other"; box_del`, ep) // cleanup (self was already deleted in last test) + logic.TestApp(t, `byte "other"; box_del; !`, ep) + logic.TestApp(t, `byte "junk"; box_del`, ep, "invalid Box reference") // Create two boxes, that sum to over budget, then test trying to use them together - logic.TestApp(t, `int 101; byte "self"; box_create; - int 1`, ep) + logic.TestApp(t, `byte "self"; int 101; box_create`, ep) logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; - int 101; byte "other"; box_create; - int 1`, ep, "write budget (200) exceeded") + byte "other"; int 101; box_create`, ep, "write budget (200) exceeded") // error was detected, but the TestLedger now has both boxes present logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep, "read budget (200) exceeded") ledger.DeleteBox(888, "other") logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; - int 10; byte "other"; box_create - int 1`, ep) + byte "other"; int 10; box_create`, ep) // They're now small enough to read and write logic.TestApp(t, `byte "self"; int 1; byte 0x3333; box_replace; byte "other"; int 1; byte 0x3333; box_replace; @@ -265,7 +256,7 @@ func TestBoxWriteBudget(t *testing.T) { byte "other"; int 1; byte 0x3333; box_replace; int 1`, ep) - logic.TestApp(t, `byte "self"; box_del; byte "other"; box_del; int 1`, ep) // cleanup + logic.TestApp(t, `byte "self"; box_del; assert; byte "other"; box_del`, ep) // cleanup } @@ -297,8 +288,7 @@ func TestIOBudgetGrow(t *testing.T) { // Here we read 202, and write a very different 350 (since we now have 4 brs) logic.TestApp(t, `byte "self"; int 1; int 7; box_extract; pop; byte "other"; int 1; int 7; box_extract; pop; - int 350; byte "another"; box_create; - int 1`, ep) + byte "another"; int 350; box_create`, ep) } func TestConveniences(t *testing.T) { @@ -316,8 +306,8 @@ func TestConveniences(t *testing.T) { // box_put creates the box with contents provided logic.TestApp(t, `byte "self"; byte 0x3132; box_put; - byte "self"; box_len; assert; int 2; ==; assert - byte "self"; box_get; assert; byte 0x3132; ==`, ep) + byte "self"; box_len; assert; int 2; ==; assert + byte "self"; box_get; assert; byte 0x3132; ==`, ep) // box_put fails if box exists and is wrong size (self exists from last test) logic.TestApp(t, `byte "self"; byte 0x313233; box_put; int 1`, ep, @@ -325,7 +315,7 @@ func TestConveniences(t *testing.T) { ledger.DeleteBox(888, "self") // put and get can interact with created boxes - logic.TestApp(t, `int 3; byte "self"; box_create; int 1`, ep) + logic.TestApp(t, `byte "self"; int 3; box_create`, ep) logic.TestApp(t, `byte "self"; box_get; assert; byte 0x000000; ==`, ep) logic.TestApp(t, `byte "self"; byte 0xAABBCC; box_put; int 1`, ep) logic.TestApp(t, `byte "self"; int 1; byte 0xDDEE; box_replace; int 1`, ep) @@ -335,7 +325,7 @@ func TestConveniences(t *testing.T) { // box_get panics if the box is too big ep.Proto.MaxBoxSize = 5000 ep.Proto.BytesPerBoxReference = 5000 // avoid write budget error - logic.TestApp(t, `int 4098; byte "self"; box_create; // bigger than maxStringSize + logic.TestApp(t, `byte "self"; int 4098; box_create; assert; // bigger than maxStringSize byte "self"; box_get; assert; len`, ep, "box_get produced a too big") diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 0983859e8b..834ecbce52 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -191,13 +191,13 @@ var opDocByName = map[string]string{ "itxn_field": "set field F of the current inner transaction to A", "itxn_submit": "execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.", - "box_create": "make a box", - "box_extract": "read from a box", - "box_replace": "write to a box", - "box_del": "delete a box", - "box_len": "length of a box", - "box_get": "full contents of a box", - "box_put": "write contents of box", + "box_create": "create a box named A, of length B. Fail if A is empty or B exceeds 32,384.", + "box_extract": "read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size.", + "box_replace": "write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", + "box_del": "delete box named A if it exists. Return 1 if A existed, 0 otherwise", + "box_len": "X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", + "box_get": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) > 4096.", + "box_put": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist.", } // OpDoc returns a description of the op diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index bd2619dd89..611904c04b 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -214,10 +214,10 @@ type LedgerForLogic interface { SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error DelGlobal(appIdx basics.AppIndex, key string) error - NewBox(appIdx basics.AppIndex, key string, value string, appAddr basics.Address) error + NewBox(appIdx basics.AppIndex, key string, value string, appAddr basics.Address) (bool, error) GetBox(appIdx basics.AppIndex, key string) (string, bool, error) SetBox(appIdx basics.AppIndex, key string, value string) error - DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) error + DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) (bool, error) Perform(gi int, ep *EvalParams) error Counter() uint64 diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index dd7769537f..193f7356aa 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -2206,9 +2206,10 @@ { "Opcode": 185, "Name": "box_create", - "Args": "UB", + "Args": "BU", + "Returns": "U", "Size": 1, - "Doc": "make a box", + "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,384.", "Groups": [ "State Access" ] @@ -2219,7 +2220,7 @@ "Args": "BUU", "Returns": "B", "Size": 1, - "Doc": "read from a box", + "Doc": "read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size.", "Groups": [ "State Access" ] @@ -2229,7 +2230,7 @@ "Name": "box_replace", "Args": "BUB", "Size": 1, - "Doc": "write to a box", + "Doc": "write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "Groups": [ "State Access" ] @@ -2238,8 +2239,9 @@ "Opcode": 188, "Name": "box_del", "Args": "B", + "Returns": "U", "Size": 1, - "Doc": "delete a box", + "Doc": "delete box named A if it exists. Return 1 if A existed, 0 otherwise", "Groups": [ "State Access" ] @@ -2250,7 +2252,7 @@ "Args": "B", "Returns": "UU", "Size": 1, - "Doc": "length of a box", + "Doc": "X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", "Groups": [ "State Access" ] @@ -2261,7 +2263,7 @@ "Args": "B", "Returns": "BU", "Size": 1, - "Doc": "full contents of a box", + "Doc": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) \u003e 4096.", "Groups": [ "State Access" ] @@ -2271,7 +2273,7 @@ "Name": "box_put", "Args": "BB", "Size": 1, - "Doc": "write contents of box", + "Doc": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist.", "Groups": [ "State Access" ] diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 4d4ea6816e..102d90a87e 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -345,28 +345,35 @@ func (l *Ledger) DelGlobal(appIdx basics.AppIndex, key string) error { return nil } +func errOnMismatch(x string, y string) error { + if len(x) != len(y) { + return fmt.Errorf("new box size mismatch %d %d", len(x), len(y)) + } + return nil +} + // NewBox makes a new box, through the boxMods mechanism. It can be Reset() -func (l *Ledger) NewBox(appIdx basics.AppIndex, key string, value string, appAddr basics.Address) error { +func (l *Ledger) NewBox(appIdx basics.AppIndex, key string, value string, appAddr basics.Address) (bool, error) { if appIdx.Address() != appAddr { panic(fmt.Sprintf("%d %v %v", appIdx, appIdx.Address(), appAddr)) } params, ok := l.applications[appIdx] if !ok { - return fmt.Errorf("no such app %d", appIdx) + return false, fmt.Errorf("no such app %d", appIdx) } if params.boxMods == nil { params.boxMods = make(map[string]*string) } if current, ok := params.boxMods[key]; ok { if current != nil { - return fmt.Errorf("box already exists 1 %#v %d", key, len(*current)) + return false, errOnMismatch(value, *current) } } else if current, ok := params.boxes[key]; ok { - return fmt.Errorf("box already exists 2 %#v %d", key, len(current)) + return false, errOnMismatch(value, current) } params.boxMods[key] = &value l.applications[appIdx] = params - return nil + return true, nil } func (l *Ledger) GetBox(appIdx basics.AppIndex, key string) (string, bool, error) { @@ -410,23 +417,23 @@ func (l *Ledger) SetBox(appIdx basics.AppIndex, key string, value string) error } // DelBox deletes a value through moxMods mechanism -func (l *Ledger) DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) error { +func (l *Ledger) DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) (bool, error) { if appIdx.Address() != appAddr { panic(fmt.Sprintf("%d %v %v", appIdx, appIdx.Address(), appAddr)) } _, ok, err := l.GetBox(appIdx, key) if err != nil { - return err + return false, err } if !ok { - return fmt.Errorf("no such box %d", appIdx) + return false, nil } params := l.applications[appIdx] // assured, based on above if params.boxMods == nil { params.boxMods = make(map[string]*string) } params.boxMods[key] = nil - return nil + return true, nil } // GetLocal returns the current value bound to a local key, taking diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index e705f8fa90..542a0ae943 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -589,10 +589,10 @@ var OpSpecs = []OpSpec{ {0xb8, "gitxna", opGitxna, proto(":a"), 6, immediates("t", "f", "i").field("f", &TxnArrayFields).only(modeApp)}, // Unlimited Global Storage - Boxes - {0xb9, "box_create", opBoxCreate, proto("ib:"), boxVersion, only(modeApp)}, + {0xb9, "box_create", opBoxCreate, proto("bi:i"), boxVersion, only(modeApp)}, {0xba, "box_extract", opBoxExtract, proto("bii:b"), boxVersion, only(modeApp)}, {0xbb, "box_replace", opBoxReplace, proto("bib:"), boxVersion, only(modeApp)}, - {0xbc, "box_del", opBoxDel, proto("b:"), boxVersion, only(modeApp)}, + {0xbc, "box_del", opBoxDel, proto("b:i"), boxVersion, only(modeApp)}, {0xbd, "box_len", opBoxLen, proto("b:ii"), boxVersion, only(modeApp)}, {0xbe, "box_get", opBoxGet, proto("b:bi"), boxVersion, only(modeApp)}, {0xbf, "box_put", opBoxPut, proto("bb:"), boxVersion, only(modeApp)}, diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index e89e5dbbb7..cb248e4e36 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -202,43 +202,50 @@ func (cs *roundCowState) kvDel(key string) error { return nil } -func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, value string, appAddr basics.Address) error { +func errOnMismatch(x string, y string) error { + if len(x) != len(y) { + return fmt.Errorf("new box size mismatch %d %d", len(x), len(y)) + } + return nil +} + +func (cs *roundCowState) NewBox(appIdx basics.AppIndex, key string, value string, appAddr basics.Address) (bool, error) { // Use same limit on key length as for global/local storage if len(key) > cs.proto.MaxAppKeyLen { - return fmt.Errorf("name too long: length was %d, maximum is %d", len(key), cs.proto.MaxAppKeyLen) + return false, fmt.Errorf("name too long: length was %d, maximum is %d", len(key), cs.proto.MaxAppKeyLen) } // This rule is NOT like global/local storage, but seems like it will limit // confusion, since these are standalone entities. if len(key) == 0 { - return fmt.Errorf("box names may not be zero length") + return false, fmt.Errorf("box names may not be zero length") } size := uint64(len(value)) if size > cs.proto.MaxBoxSize { - return fmt.Errorf("box size too large: %d, maximum is %d", size, cs.proto.MaxBoxSize) + return false, fmt.Errorf("box size too large: %d, maximum is %d", size, cs.proto.MaxBoxSize) } fullKey := logic.MakeBoxKey(appIdx, key) - _, ok, err := cs.kvGet(fullKey) + existing, ok, err := cs.kvGet(fullKey) if err != nil { - return err + return false, err } if ok { - return fmt.Errorf("box %s exists for %d", key, appIdx) + return false, errOnMismatch(existing, value) } record, err := cs.Get(appAddr, false) if err != nil { - return err + return false, err } record.TotalBoxes = basics.AddSaturate(record.TotalBoxes, 1) record.TotalBoxBytes = basics.AddSaturate(record.TotalBoxBytes, uint64(len(key))+size) err = cs.Put(appAddr, record) if err != nil { - return err + return false, err } - return cs.kvPut(fullKey, value) + return true, cs.kvPut(fullKey, value) } func (cs *roundCowState) GetBox(appIdx basics.AppIndex, key string) (string, bool, error) { @@ -262,29 +269,29 @@ func (cs *roundCowState) SetBox(appIdx basics.AppIndex, key string, value string return cs.kvPut(fullKey, value) } -func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) error { +func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string, appAddr basics.Address) (bool, error) { fullKey := logic.MakeBoxKey(appIdx, key) value, ok, err := cs.kvGet(fullKey) if err != nil { - return err + return false, err } if !ok { - return fmt.Errorf("box %s does not exist for %d", key, appIdx) + return false, nil } record, err := cs.Get(appAddr, false) if err != nil { - return err + return false, err } record.TotalBoxes = basics.SubSaturate(record.TotalBoxes, 1) record.TotalBoxBytes = basics.SubSaturate(record.TotalBoxBytes, uint64(len(key)+len(value))) err = cs.Put(appAddr, record) if err != nil { - return err + return false, err } - return cs.kvDel(fullKey) + return true, cs.kvDel(fullKey) } func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) error { diff --git a/ledger/internal/boxtxn_test.go b/ledger/internal/boxtxn_test.go index 14853c3974..9493ef655b 100644 --- a/ledger/internal/boxtxn_test.go +++ b/ledger/internal/boxtxn_test.go @@ -35,6 +35,7 @@ var appSource = main(` byte "create" // create box named arg[1] == bz del + txn ApplicationArgs 1 int 24 txn NumAppArgs int 2 @@ -44,8 +45,8 @@ var appSource = main(` txn ApplicationArgs 2 btoi default: - txn ApplicationArgs 1 box_create + assert b end del: // delete box arg[1] txn ApplicationArgs 0 @@ -54,6 +55,7 @@ var appSource = main(` bz set txn ApplicationArgs 1 box_del + assert b end set: // put arg[1] at start of box arg[0] txn ApplicationArgs 0 @@ -110,27 +112,27 @@ func TestBoxCreate(t *testing.T) { adam.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("adam")}} dl.txn(adam) dl.txn(adam.Args("check", "adam", "\x00\x00")) - dl.txgroup("exists", adam.Noted("one"), adam.Noted("two")) + dl.txgroup("box_create\nassert", adam.Noted("one"), adam.Noted("two")) bobo := call.Args("create", "bobo") dl.txn(bobo, "invalid Box reference bobo") bobo.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("bobo")}} dl.txn(bobo) - dl.txgroup("exists", bobo.Noted("one"), bobo.Noted("two")) + dl.txgroup("box_create\nassert", bobo.Noted("one"), bobo.Noted("two")) dl.beginBlock() chaz := call.Args("create", "chaz") chaz.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("chaz")}} dl.txn(chaz) - dl.txn(chaz.Noted("again"), "exists") + dl.txn(chaz.Noted("again"), "box_create\nassert") dl.endBlock() // new block - dl.txn(chaz.Noted("again"), "exists") + dl.txn(chaz.Noted("again"), "box_create\nassert") dogg := call.Args("create", "dogg") dogg.Boxes = []transactions.BoxRef{{Index: 0, Name: []byte("dogg")}} dl.txn(dogg, "below min") dl.txn(chaz.Args("delete", "chaz")) - dl.txn(chaz.Args("delete", "chaz").Noted("again"), "does not exist") + dl.txn(chaz.Args("delete", "chaz").Noted("again"), "box_del\nassert") dl.txn(dogg) dl.txn(bobo.Args("delete", "bobo")) @@ -159,10 +161,9 @@ func TestBoxCreateAvailability(t *testing.T) { ApplicationID: 0, // This is a create Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("hello")}}, ApprovalProgram: ` - int 10 byte "hello" + int 10 box_create - int 1 `, } @@ -195,9 +196,10 @@ func TestBoxCreateAvailability(t *testing.T) { Boxes: []transactions.BoxRef{{Index: 0, Name: []byte("hello")}}, // Note that main() wraps the program so it does not run at creation time. ApprovalProgram: main(` - int 10 byte "hello" + int 10 box_create + assert byte "we did it" log `), diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 8716134805..30c8a2a582 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1264,7 +1264,9 @@ func TestBoxNamesByAppID(t *testing.T) { bz del // "create" ? continue : goto del int 5 // [5] txn ApplicationArgs 1 // [5, arg[1]] + swap box_create // [] // boxes: arg[1] -> [5]byte + assert b end del: // delete box arg[1] txn ApplicationArgs 0 // [arg[0]] @@ -1273,6 +1275,7 @@ del: // delete box arg[1] bz set // "delete" ? continue : goto set txn ApplicationArgs 1 // [arg[1]] box_del // del boxes[arg[1]] + assert b end set: // put arg[1] at start of box arg[0] ... so actually a _partial_ "set" txn ApplicationArgs 0 // [arg[0]] diff --git a/test/scripts/e2e_subs/tealprogs/boxes.teal b/test/scripts/e2e_subs/tealprogs/boxes.teal index 2b56414ef5..d0fef9f17e 100644 --- a/test/scripts/e2e_subs/tealprogs/boxes.teal +++ b/test/scripts/e2e_subs/tealprogs/boxes.teal @@ -17,7 +17,9 @@ btoi // [btoi(arg[2])] default: // [24] // NumAppArgs >= 3 txn ApplicationArgs 1 // [24, arg[1]] + swap box_create // [] // boxes: arg[1] -> [24]byte + assert b end del: // delete box arg[1] txn ApplicationArgs 0 // [arg[0]] @@ -26,6 +28,7 @@ del: // delete box arg[1] bz set // "delete" ? continue : goto set txn ApplicationArgs 1 // [arg[1]] box_del // del boxes[arg[1]] + assert b end set: // put arg[1] at start of box arg[0] ... so actually a _partial_ "set" txn ApplicationArgs 0 // [arg[0]] From c6df2a586641d43840b6be38ee364422e43b19c5 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Sat, 30 Jul 2022 00:29:14 -0400 Subject: [PATCH 2/4] CR from Zeph --- data/transactions/logic/box.go | 4 ++-- data/transactions/logic/doc.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index f900e3f9b2..24950dac96 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -83,9 +83,9 @@ func opBoxCreate(cx *EvalContext) error { appAddr := cx.getApplicationAddress(cx.appID) cx.stack = cx.stack[:last] - existed, err := createBox(cx, name, string(make([]byte, size)), appAddr) + created, err := createBox(cx, name, string(make([]byte, size)), appAddr) cx.stack[prev].Bytes = nil - cx.stack[prev].Uint = boolToUint(existed) + cx.stack[prev].Uint = boolToUint(created) return err } diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 834ecbce52..061a6e742d 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -191,13 +191,13 @@ var opDocByName = map[string]string{ "itxn_field": "set field F of the current inner transaction to A", "itxn_submit": "execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.", - "box_create": "create a box named A, of length B. Fail if A is empty or B exceeds 32,384.", + "box_create": "create a box named A, of length B. Fail if A is empty or B exceeds 32,384. Returns 0 if A already existed, else 1", "box_extract": "read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size.", "box_replace": "write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "box_del": "delete box named A if it exists. Return 1 if A existed, 0 otherwise", "box_len": "X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", - "box_get": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) > 4096.", - "box_put": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist.", + "box_get": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0.", + "box_put": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist.", } // OpDoc returns a description of the op From 85abf4bcc0e5f80c16af8cf40ddb1c7ab187604a Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Sat, 30 Jul 2022 14:22:12 -0400 Subject: [PATCH 3/4] More detail, and remember to make the spec mds --- data/transactions/logic/README.md | 6 +++--- data/transactions/logic/TEAL_opcodes.md | 12 +++++++++--- data/transactions/logic/doc.go | 4 ++++ data/transactions/logic/langspec.json | 9 ++++++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index b335e7ea5f..8afb7cb037 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -615,13 +615,13 @@ Account fields used in the `acct_params_get` opcode. | `app_params_get f` | X is field F from app A. Y is 1 if A exists, else 0 | | `acct_params_get f` | X is field F from account A. Y is 1 if A owns positive algos, else 0 | | `log` | write A to log state of the current application | -| `box_create` | create a box named A, of length B. Fail if A is empty or B exceeds 32,384. | +| `box_create` | create a box named A, of length B. Fail if A is empty or B exceeds 32,384. Returns 0 if A already existed, else 1 | | `box_extract` | read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size. | | `box_replace` | write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. | | `box_del` | delete box named A if it exists. Return 1 if A existed, 0 otherwise | | `box_len` | X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. | -| `box_get` | X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) > 4096. | -| `box_put` | replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist. | +| `box_get` | X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. | +| `box_put` | replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist. | ### Inner Transactions diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 42068533c6..bb3c660f29 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1353,10 +1353,12 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Opcode: 0xb9 - Stack: ..., A: []byte, B: uint64 → ..., uint64 -- create a box named A, of length B. Fail if A is empty or B exceeds 32,384. +- create a box named A, of length B. Fail if A is empty or B exceeds 32,384. Returns 0 if A already existed, else 1 - Availability: v7 - Mode: Application +Newly created boxes are filled with 0 bytes. Boxes are unchanged by `box_create` if they already exist. + ## box_extract - Opcode: 0xba @@ -1393,18 +1395,22 @@ G1s are encoded by the concatenation of encoded G1 points, as described in `bn25 - Opcode: 0xbe - Stack: ..., A: []byte → ..., X: []byte, Y: uint64 -- X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) > 4096. +- X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. - Availability: v7 - Mode: Application +For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace` + ## box_put - Opcode: 0xbf - Stack: ..., A: []byte, B: []byte → ... -- replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist. +- replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist. - Availability: v7 - Mode: Application +For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace` + ## txnas f - Opcode: 0xc0 {uint8 transaction field index} diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 061a6e742d..4ddf0f023c 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -325,6 +325,10 @@ var opDocExtras = map[string]string{ "itxn_submit": "`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction.", "base64_decode": "Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See RFC 4648 (sections 4 and 5). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\\n` and `\\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\\r`, or `\\n`.", "json_ref": "specify the return type with an immediate arg either as JSONUint64 or JSONString or JSONObject.", + + "box_create": "Newly created boxes are filled with 0 bytes. Boxes are unchanged by `box_create` if they already exist.", + "box_get": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", + "box_put": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", } // OpDocExtra returns extra documentation text about an op diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 193f7356aa..09fa82314d 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -2209,7 +2209,8 @@ "Args": "BU", "Returns": "U", "Size": 1, - "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,384.", + "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,384. Returns 0 if A already existed, else 1", + "DocExtra": "Newly created boxes are filled with 0 bytes. Boxes are unchanged by `box_create` if they already exist.", "Groups": [ "State Access" ] @@ -2263,7 +2264,8 @@ "Args": "B", "Returns": "BU", "Size": 1, - "Doc": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. Fails if len(box A) \u003e 4096.", + "Doc": "X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0.", + "DocExtra": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", "Groups": [ "State Access" ] @@ -2273,7 +2275,8 @@ "Name": "box_put", "Args": "BB", "Size": 1, - "Doc": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does exist.", + "Doc": "replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist.", + "DocExtra": "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", "Groups": [ "State Access" ] From f055a26ed8e8dfc41cb815b99c0988e770775990 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 1 Aug 2022 13:08:47 -0400 Subject: [PATCH 4/4] Typo. Thanks @algoanne --- data/transactions/logic/README.md | 2 +- data/transactions/logic/TEAL_opcodes.md | 2 +- data/transactions/logic/doc.go | 2 +- data/transactions/logic/langspec.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 8afb7cb037..3a575a1d19 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -616,7 +616,7 @@ Account fields used in the `acct_params_get` opcode. | `acct_params_get f` | X is field F from account A. Y is 1 if A owns positive algos, else 0 | | `log` | write A to log state of the current application | | `box_create` | create a box named A, of length B. Fail if A is empty or B exceeds 32,384. Returns 0 if A already existed, else 1 | -| `box_extract` | read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size. | +| `box_extract` | read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. | | `box_replace` | write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. | | `box_del` | delete box named A if it exists. Return 1 if A existed, 0 otherwise | | `box_len` | X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. | diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index bb3c660f29..577c465e69 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1363,7 +1363,7 @@ Newly created boxes are filled with 0 bytes. Boxes are unchanged by `box_create` - Opcode: 0xba - Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte -- read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size. +- read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. - Availability: v7 - Mode: Application diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 4ddf0f023c..cf352634be 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -192,7 +192,7 @@ var opDocByName = map[string]string{ "itxn_submit": "execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.", "box_create": "create a box named A, of length B. Fail if A is empty or B exceeds 32,384. Returns 0 if A already existed, else 1", - "box_extract": "read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size.", + "box_extract": "read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "box_replace": "write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "box_del": "delete box named A if it exists. Return 1 if A existed, 0 otherwise", "box_len": "X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 09fa82314d..f1ae0a60e9 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -2221,7 +2221,7 @@ "Args": "BUU", "Returns": "B", "Size": 1, - "Doc": "read C bytes from box A, starting at offset B. Fail if B does not exist, or the byte range is outside A's size.", + "Doc": "read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "Groups": [ "State Access" ]