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

More details on spec (and unifiy the box opcodes a bit) #4323

Merged
merged 4 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions cmd/goal/examples/boxes.teal
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand All @@ -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]]
Expand Down
14 changes: 7 additions & 7 deletions data/transactions/logic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
jannotti marked this conversation as resolved.
Show resolved Hide resolved
| `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. Returns 0 if A already existed, else 1 |
| `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. |
| `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

Expand Down
24 changes: 15 additions & 9 deletions data/transactions/logic/TEAL_opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1352,59 +1352,65 @@ 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. 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
- Stack: ..., A: []byte, B: uint64, C: uint64 → ..., []byte
- read from a box
- 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

## box_replace

- 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

## box_len

- 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

## box_get

- 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.
- 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 → ...
- 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 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}
Expand Down
39 changes: 24 additions & 15 deletions data/transactions/logic/box.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
created, err := createBox(cx, name, string(make([]byte, size)), appAddr)
cx.stack[prev].Bytes = nil
cx.stack[prev].Uint = boolToUint(created)
return err

}

func opBoxExtract(cx *EvalContext) error {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
Loading