Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f97d3b5
Add an unlimited global storage mechanism - Boxes.
jannotti Apr 29, 2022
455447f
Fixup tests that require complete evalparams
jannotti May 19, 2022
9e45d27
WellFormed check for Boxes
jannotti May 19, 2022
0139e54
Add --box argument to `goal app call`
jannotti May 22, 2022
efe16ed
Use 0 if needed as box ref index when caller specifies the app-id.
jannotti May 23, 2022
9fcc59a
Provide sort key for the new kvstore table
jannotti May 23, 2022
b142973
make tests obey the box ref rules
jannotti May 24, 2022
9d78c31
simplify closing prepared statements.
jannotti May 24, 2022
1fa7e55
test updates
jannotti May 24, 2022
4d24bf4
Add required (but useless) allocbound.
jannotti May 24, 2022
1aa9d90
Make "0 index" box refs on app calls available in group
jannotti May 24, 2022
4e57665
lints
jannotti May 25, 2022
2b32b6a
Fixup after rebase master
jannotti May 25, 2022
b357b7c
Michael's refactor
jannotti May 25, 2022
3d7c41e
Avoid confusing addition of Boxes in basics.AccountData
jannotti May 25, 2022
1302b5a
make msgp
jannotti May 25, 2022
47e560d
b64 encode the BoxRef names in a txn when marshaling to json
jannotti May 26, 2022
beec0ec
preliminary algod box access API
jannotti May 26, 2022
dd969ee
unify and imporve extract errors
jannotti May 26, 2022
ea0a98d
replace2 and replace3 opcodes
jannotti May 26, 2022
88ec16b
codegen and CR
jannotti May 27, 2022
e7b20e9
Add proper locking, and loop in case of db update.
jannotti May 31, 2022
beea6dd
Impose limits on reading and writing of box bytes
jannotti Jun 7, 2022
207bf1e
back to first values for box MBR
jannotti Jun 7, 2022
23c0346
Allow dry run test to pay for inner txn
jannotti Jun 8, 2022
686a1ab
moar! coverage!!11!!
jannotti Jun 8, 2022
7369e0a
book -> box
jannotti Jun 9, 2022
4171712
coverage for some box related goal parsing
jannotti Jun 9, 2022
8e7ff74
Outlaw zero length box names
jannotti Jun 10, 2022
b8d54d1
delete the kvstore table on reset
jannotti Jun 10, 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
196 changes: 131 additions & 65 deletions cmd/goal/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ var (
// platform seems not so far-fetched?
foreignApps []string
foreignAssets []string
appBoxes []string // parse these as we do app args, with optional number and comma in front
appStrAccounts []string

appArgs []string
Expand All @@ -97,8 +98,9 @@ func init() {
appCmd.PersistentFlags().StringArrayVar(&appArgs, "app-arg", nil, "Args to encode for application transactions (all will be encoded to a byte slice). For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.")
appCmd.PersistentFlags().StringSliceVar(&foreignApps, "foreign-app", nil, "Indexes of other apps whose global state is read in this transaction")
appCmd.PersistentFlags().StringSliceVar(&foreignAssets, "foreign-asset", nil, "Indexes of assets whose parameters are read in this transaction")
appCmd.PersistentFlags().StringArrayVar(&appBoxes, "box", nil, "Boxes that may be accessed by this transaction. Use the same form as app-arg to name the box, preceded by an optional app-id and comma. No app-id indicates the box is accessible by the app being called.")
appCmd.PersistentFlags().StringSliceVar(&appStrAccounts, "app-account", nil, "Accounts that may be accessed from application logic")
appCmd.PersistentFlags().StringVarP(&appInputFilename, "app-input", "i", "", "JSON file containing encoded arguments and inputs (mutually exclusive with app-arg-b64 and app-account)")
appCmd.PersistentFlags().StringVarP(&appInputFilename, "app-input", "i", "", "JSON file containing encoded arguments and inputs (mutually exclusive with app-arg, app-account, foreign-app, foreign-asset, and box)")

appCmd.PersistentFlags().StringVar(&approvalProgFile, "approval-prog", "", "(Uncompiled) TEAL assembly program filename for approving/rejecting transactions")
appCmd.PersistentFlags().StringVar(&clearProgFile, "clear-prog", "", "(Uncompiled) TEAL assembly program filename for updating application state when a user clears their local state")
Expand Down Expand Up @@ -193,16 +195,59 @@ func init() {
methodAppCmd.Flags().MarkHidden("app-arg") // nolint:errcheck
}

type appCallArg struct {
type appCallBytes struct {
Encoding string `codec:"encoding"`
Value string `codec:"value"`
}

func newAppCallBytes(arg string) appCallBytes {
parts := strings.SplitN(arg, ":", 2)
if len(parts) != 2 {
reportErrorf("all arguments and box names should be of the form 'encoding:value'")
}
return appCallBytes{
Encoding: parts[0],
Value: parts[1],
}
}

type appCallInputs struct {
Accounts []string `codec:"accounts"`
ForeignApps []uint64 `codec:"foreignapps"`
ForeignAssets []uint64 `codec:"foreignassets"`
Args []appCallArg `codec:"args"`
Accounts []string `codec:"accounts"`
ForeignApps []uint64 `codec:"foreignapps"`
ForeignAssets []uint64 `codec:"foreignassets"`
Boxes []boxRef `codec:"boxes"`
Args []appCallBytes `codec:"args"`
}

type boxRef struct {
appID uint64 `codec:"app"`
name appCallBytes `codec:"name"`
}

// newBoxRef parses a command-line box ref, which is an optional appId, a comma,
// and then the same format as an app call arg.
func newBoxRef(arg string) boxRef {
parts := strings.SplitN(arg, ":", 2)
if len(parts) != 2 {
reportErrorf("box refs should be of the form '[<app>,]encoding:value'")
}
encoding := parts[0] // tentative, may be <app>,<encoding>
value := parts[1]
parts = strings.SplitN(encoding, ",", 2)
appID := uint64(0)
if len(parts) == 2 {
// There was a comma in the part before the ":"
encoding = parts[1]
var err error
appID, err = strconv.ParseUint(parts[0], 10, 64)
if err != nil {
reportErrorf("Could not parse app id in box ref: %v", err)
}
}
return boxRef{
appID: appID,
name: newAppCallBytes(encoding + ":" + value),
}
}

func stringsToUint64(strs []string) []uint64 {
Expand All @@ -217,15 +262,15 @@ func stringsToUint64(strs []string) []uint64 {
return out
}

func getForeignAssets() []uint64 {
return stringsToUint64(foreignAssets)
}

func getForeignApps() []uint64 {
return stringsToUint64(foreignApps)
func stringsToBoxRefs(strs []string) []boxRef {
out := make([]boxRef, len(strs))
for i, brstr := range strs {
out[i] = newBoxRef(brstr)
}
return out
}

func parseAppArg(arg appCallArg) (rawValue []byte, parseErr error) {
func (arg appCallBytes) raw() (rawValue []byte, parseErr error) {
switch arg.Encoding {
case "str", "string":
rawValue = []byte(arg.Value)
Expand Down Expand Up @@ -282,13 +327,52 @@ func parseAppArg(arg appCallArg) (rawValue []byte, parseErr error) {
return
}

func parseAppInputs(inputs appCallInputs) (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) {
func translateBoxRefs(input []boxRef, foreignApps []uint64) []transactions.BoxRef {
output := make([]transactions.BoxRef, len(input))
for i, tbr := range input {
rawName, err := tbr.name.raw()
if err != nil {
reportErrorf("Could not decode box name %s: %v", tbr.name, err)
}

index := uint64(0)
if tbr.appID != 0 {
found := false
for a, id := range foreignApps {
if tbr.appID == id {
index = uint64(a + 1)
found = true
break
}
}
// Check appIdx after the foreignApps check. If the user actually
// put the appIdx in foreignApps, and then used the appIdx here
// (rather than 0), then maybe they really want to use it in the
// transaction as the full number. Though it's hard to see why.
if !found && tbr.appID == appIdx {
index = 0
found = true
}
if !found {
reportErrorf("Box ref with appId (%d) not in foreign-apps", tbr.appID)
}
}
output[i] = transactions.BoxRef{
Index: index,
Name: rawName,
}
}
return output
}

func parseAppInputs(inputs appCallInputs) (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) {
accounts = inputs.Accounts
foreignApps = inputs.ForeignApps
foreignAssets = inputs.ForeignAssets
boxes = translateBoxRefs(inputs.Boxes, foreignApps)
args = make([][]byte, len(inputs.Args))
for i, arg := range inputs.Args {
rawValue, err := parseAppArg(arg)
rawValue, err := arg.raw()
if err != nil {
reportErrorf("Could not decode input at index %d: %v", i, err)
}
Expand All @@ -297,7 +381,7 @@ func parseAppInputs(inputs appCallInputs) (args [][]byte, accounts []string, for
return
}

func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) {
func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) {
var inputs appCallInputs
f, err := os.Open(appInputFilename)
if err != nil {
Expand All @@ -313,49 +397,31 @@ func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint
return parseAppInputs(inputs)
}

// filterEmptyStrings filters out empty string parsed in by StringArrayVar
// this function is added to support abi argument parsing
// since parsing of `appArg` diverted from `StringSliceVar` to `StringArrayVar`
func filterEmptyStrings(strSlice []string) []string {
var newStrSlice []string

for _, str := range strSlice {
if len(str) > 0 {
newStrSlice = append(newStrSlice, str)
}
}
return newStrSlice
}

func getAppInputs() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) {
if (appArgs != nil || appStrAccounts != nil || foreignApps != nil) && appInputFilename != "" {
reportErrorf("Cannot specify both command-line arguments/accounts and JSON input filename")
}
func getAppInputs() (args [][]byte, accounts []string, apps []uint64, assets []uint64, boxes []transactions.BoxRef) {
if appInputFilename != "" {
if appArgs != nil || appStrAccounts != nil || foreignApps != nil || foreignAssets != nil {
reportErrorf("Cannot specify both command-line arguments/resources and JSON input filename")
}
return processAppInputFile()
}

var encodedArgs []appCallArg
// we need to ignore empty strings from appArgs because app-arg was
// previously a StringSliceVar, which also does that, and some test depend
// on it. appArgs became `StringArrayVar` in order to support abi arguments
// which contain commas.

// we need to filter out empty strings from appArgs first, caused by change to `StringArrayVar`
newAppArgs := filterEmptyStrings(appArgs)

for _, arg := range newAppArgs {
encodingValue := strings.SplitN(arg, ":", 2)
if len(encodingValue) != 2 {
reportErrorf("all arguments should be of the form 'encoding:value'")
}
encodedArg := appCallArg{
Encoding: encodingValue[0],
Value: encodingValue[1],
var encodedArgs []appCallBytes
for _, arg := range appArgs {
if len(arg) > 0 {
encodedArgs = append(encodedArgs, newAppCallBytes(arg))
}
encodedArgs = append(encodedArgs, encodedArg)
}

inputs := appCallInputs{
Accounts: appStrAccounts,
ForeignApps: getForeignApps(),
ForeignAssets: getForeignAssets(),
ForeignApps: stringsToUint64(foreignApps),
ForeignAssets: stringsToUint64(foreignAssets),
Boxes: stringsToBoxRefs(appBoxes),
Args: encodedArgs,
}

Expand Down Expand Up @@ -444,14 +510,14 @@ var createAppCmd = &cobra.Command{
// Parse transaction parameters
approvalProg, clearProg := mustParseProgArgs()
onCompletionEnum := mustParseOnCompletion(onCompletion)
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()

switch onCompletionEnum {
case transactions.CloseOutOC, transactions.ClearStateOC:
reportWarnf("'--on-completion %s' may be ill-formed for 'goal app create'", onCompletion)
}

tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, extraPages)
tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, boxes, extraPages)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
Expand Down Expand Up @@ -524,9 +590,9 @@ var updateAppCmd = &cobra.Command{

// Parse transaction parameters
approvalProg, clearProg := mustParseProgArgs()
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()

tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, approvalProg, clearProg)
tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, approvalProg, clearProg)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
Expand Down Expand Up @@ -594,9 +660,9 @@ var optInAppCmd = &cobra.Command{
dataDir, client := getDataDirAndClient()

// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()

tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets)
tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
Expand Down Expand Up @@ -664,9 +730,9 @@ var closeOutAppCmd = &cobra.Command{
dataDir, client := getDataDirAndClient()

// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()

tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets)
tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
Expand Down Expand Up @@ -734,9 +800,9 @@ var clearAppCmd = &cobra.Command{
dataDir, client := getDataDirAndClient()

// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()

tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets)
tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
Expand Down Expand Up @@ -804,9 +870,9 @@ var callAppCmd = &cobra.Command{
dataDir, client := getDataDirAndClient()

// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()

tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets)
tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
Expand Down Expand Up @@ -874,9 +940,9 @@ var deleteAppCmd = &cobra.Command{
dataDir, client := getDataDirAndClient()

// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()

tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets)
tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
Expand Down Expand Up @@ -1179,7 +1245,7 @@ var methodAppCmd = &cobra.Command{
dataDir, client := getDataDirAndClient()

// Parse transaction parameters
appArgsParsed, appAccounts, foreignApps, foreignAssets := getAppInputs()
appArgsParsed, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()
if len(appArgsParsed) > 0 {
reportErrorf("--arg and --app-arg are mutually exclusive, do not use --app-arg")
}
Expand Down Expand Up @@ -1296,7 +1362,7 @@ var methodAppCmd = &cobra.Command{
}

appCallTxn, err := client.MakeUnsignedApplicationCallTx(
appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets,
appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets, boxes,
onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, extraPages)

if err != nil {
Expand Down
13 changes: 12 additions & 1 deletion cmd/goal/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package main

import (
"flag"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -557,13 +558,23 @@ func reportErrorln(args ...interface{}) {
}
fmt.Fprintln(os.Stderr, line)
}
os.Exit(1)
exit(1)
}

func reportErrorf(format string, args ...interface{}) {
reportErrorln(fmt.Sprintf(format, args...))
}

func exit(code int) {
if flag.Lookup("test.v") == nil {
// normal run
os.Exit(code)
} else {
// testing run. panic, so we can require.Panic
panic(code)
}
}

// writeFile is a wrapper of ioutil.WriteFile which considers the special
// case of stdout filename
func writeFile(filename string, data []byte, perm os.FileMode) error {
Expand Down
Loading