diff options
author | Jason Paulos <jasonpaulos@users.noreply.github.com> | 2021-12-29 12:54:27 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-29 15:54:27 -0500 |
commit | 14bf4643561fa20fb12c657e0eb89b58b1322309 (patch) | |
tree | 966d201f6c80690903b2d7e365a5a091c0aa5601 | |
parent | 2e09ebfca1ca46231e43cd62c48a1025ce7c0a11 (diff) |
Support app creation in `goal app method` (#3353)
* Support app creation in `goal app method`
* Don't use nonprintable tab character
* Link to specific gist version
* Fix error messages
* Rename `methodCreation` to `methodCreatesApp`
-rw-r--r-- | cmd/goal/application.go | 69 | ||||
-rwxr-xr-x | test/scripts/e2e_subs/e2e-app-abi-method.sh | 23 | ||||
-rw-r--r-- | test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal | 148 |
3 files changed, 171 insertions, 69 deletions
diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 8ecb5289a..d770c43c2 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -46,8 +46,9 @@ var ( approvalProgFile string clearProgFile string - method string - methodArgs []string + method string + methodArgs []string + methodCreatesApp bool approvalProgRawFile string clearProgRawFile string @@ -122,6 +123,12 @@ func init() { methodAppCmd.Flags().StringVar(&method, "method", "", "Method to be called") methodAppCmd.Flags().StringArrayVar(&methodArgs, "arg", nil, "Args to pass in for calling a method") methodAppCmd.Flags().StringVar(&onCompletion, "on-completion", "NoOp", "OnCompletion action for application transaction") + methodAppCmd.Flags().BoolVar(&methodCreatesApp, "create", false, "Create an application in this method call") + methodAppCmd.Flags().Uint64Var(&globalSchemaUints, "global-ints", 0, "Maximum number of integer values that may be stored in the global key/value store. Immutable, only valid when passed with --create.") + methodAppCmd.Flags().Uint64Var(&globalSchemaByteSlices, "global-byteslices", 0, "Maximum number of byte slices that may be stored in the global key/value store. Immutable, only valid when passed with --create.") + methodAppCmd.Flags().Uint64Var(&localSchemaUints, "local-ints", 0, "Maximum number of integer values that may be stored in local (per-account) key/value stores for this app. Immutable, only valid when passed with --create.") + methodAppCmd.Flags().Uint64Var(&localSchemaByteSlices, "local-byteslices", 0, "Maximum number of byte slices that may be stored in local (per-account) key/value stores for this app. Immutable, only valid when passed with --create.") + methodAppCmd.Flags().Uint32Var(&extraPages, "extra-pages", 0, "Additional program space for supporting larger TEAL assembly program. A maximum of 3 extra pages is allowed. A page is 1024 bytes. Only valid when passed with --create.") // Can't use PersistentFlags on the root because for some reason marking // a root command as required with MarkPersistentFlagRequired isn't @@ -179,7 +186,6 @@ func init() { infoAppCmd.MarkFlagRequired("app-id") methodAppCmd.MarkFlagRequired("method") // nolint:errcheck // follow previous required flag format - methodAppCmd.MarkFlagRequired("app-id") // nolint:errcheck methodAppCmd.MarkFlagRequired("from") // nolint:errcheck methodAppCmd.Flags().MarkHidden("app-arg") // nolint:errcheck } @@ -1172,17 +1178,47 @@ var methodAppCmd = &cobra.Command{ // Parse transaction parameters appArgsParsed, appAccounts, foreignApps, foreignAssets := getAppInputs() if len(appArgsParsed) > 0 { - reportErrorf("in goal app method: --arg and --app-arg are mutually exclusive, do not use --app-arg") + reportErrorf("--arg and --app-arg are mutually exclusive, do not use --app-arg") + } + + // Construct schemas from args + localSchema := basics.StateSchema{ + NumUint: localSchemaUints, + NumByteSlice: localSchemaByteSlices, + } + + globalSchema := basics.StateSchema{ + NumUint: globalSchemaUints, + NumByteSlice: globalSchemaByteSlices, } onCompletionEnum := mustParseOnCompletion(onCompletion) - if appIdx == 0 { - reportErrorf("app id == 0, goal app create not supported in goal app method") + if methodCreatesApp { + if appIdx != 0 { + reportErrorf("--app-id and --create are mutually exclusive, only provide one") + } + + switch onCompletionEnum { + case transactions.CloseOutOC, transactions.ClearStateOC: + reportWarnf("'--on-completion %s' may be ill-formed for use with --create", onCompletion) + } + } else { + if appIdx == 0 { + reportErrorf("one of --app-id or --create must be provided") + } + + if localSchema != (basics.StateSchema{}) || globalSchema != (basics.StateSchema{}) { + reportErrorf("--global-ints, --global-byteslices, --local-ints, and --local-byteslices must only be provided with --create") + } + + if extraPages != 0 { + reportErrorf("--extra-pages must only be provided with --create") + } } var approvalProg, clearProg []byte - if onCompletionEnum == transactions.UpdateApplicationOC { + if methodCreatesApp || onCompletionEnum == transactions.UpdateApplicationOC { approvalProg, clearProg = mustParseProgArgs() } @@ -1258,7 +1294,7 @@ var methodAppCmd = &cobra.Command{ appCallTxn, err := client.MakeUnsignedApplicationCallTx( appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets, - onCompletionEnum, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0) + onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, extraPages) if err != nil { reportErrorf("Cannot create application txn: %v", err) @@ -1346,12 +1382,19 @@ var methodAppCmd = &cobra.Command{ } // Report tx details to user + if methodCreatesApp { + reportInfof("Attempting to create app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + } else if onCompletionEnum == transactions.UpdateApplicationOC { + reportInfof("Attempting to update app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + } + reportInfof("Issued %d transaction(s):", len(signedTxnGroup)) + // remember the final txid in this variable var txid string for _, stxn := range signedTxnGroup { txid = stxn.Txn.ID().String() - reportInfof("\tIssued transaction from account %s, txid %s (fee %d)", stxn.Txn.Sender, txid, stxn.Txn.Fee.Raw) + reportInfof("Issued transaction from account %s, txid %s (fee %d)", stxn.Txn.Sender, txid, stxn.Txn.Fee.Raw) } if !noWaitAfterSend { @@ -1365,8 +1408,12 @@ var methodAppCmd = &cobra.Command{ reportErrorf(err.Error()) } + if methodCreatesApp && resp.ApplicationIndex != nil && *resp.ApplicationIndex != 0 { + reportInfof("Created app with app index %d", *resp.ApplicationIndex) + } + if retType == nil { - fmt.Printf("method %s succeeded\n", method) + reportInfof("method %s succeeded", method) return } @@ -1393,7 +1440,7 @@ var methodAppCmd = &cobra.Command{ reportErrorf("method %s succeed but its return value could not be converted to JSON.\nThe raw return value in hex is:%s\nThe error is: %s", method, hex.EncodeToString(rawReturnValue), err) } - fmt.Printf("method %s succeeded with output: %s\n", method, string(decodedJSON)) + reportInfof("method %s succeeded with output: %s", method, string(decodedJSON)) } }, } diff --git a/test/scripts/e2e_subs/e2e-app-abi-method.sh b/test/scripts/e2e_subs/e2e-app-abi-method.sh index 210b54318..46cc5406a 100755 --- a/test/scripts/e2e_subs/e2e-app-abi-method.sh +++ b/test/scripts/e2e_subs/e2e-app-abi-method.sh @@ -16,9 +16,18 @@ gcmd="goal -w ${WALLET}" ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') -printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple.teal" -PROGRAM=($(${gcmd} clerk compile "${TEMPDIR}/simple.teal")) -APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 | grep Created | awk '{ print $6 }') +printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple-v2.teal" +printf '#pragma version 3\nint 1' > "${TEMPDIR}/simple-v3.teal" + +# Create +RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 --extra-pages 0 --from $ACCOUNT 2>&1 || true) +EXPECTED="method create(uint64)uint64 succeeded with output: 2468" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to create(uint64)uint64 should not fail %Y%m%d_%H%M%S' + false +fi + +APPID=$(echo "$RES" | grep Created | awk '{ print $6 }') # Opt in RES=$(${gcmd} app method --method "optIn(string)string" --arg "\"Algorand Fan\"" --on-completion optin --app-id $APPID --from $ACCOUNT 2>&1 || true) @@ -86,6 +95,14 @@ if [[ $RES != *"${EXPECTED}"* ]]; then false fi +# Update +RES=$(${gcmd} app method --method "update()void" --on-completion updateapplication --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v3.teal --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method update()void succeeded" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to update()void should not fail %Y%m%d_%H%M%S' + false +fi + # Delete RES=$(${gcmd} app method --method "delete()void" --on-completion deleteapplication --app-id $APPID --from $ACCOUNT 2>&1 || true) EXPECTED="method delete()void succeeded" diff --git a/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal b/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal index 2711cd333..83c4f0fdd 100644 --- a/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal +++ b/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal @@ -1,9 +1,17 @@ -// generated from https://gist.github.com/jasonpaulos/99e4f8a75f2fc2ec9b8073c064530359 +// generated from https://gist.github.com/jasonpaulos/99e4f8a75f2fc2ec9b8073c064530359/b4d37519ccc67383042b6c0fb8b7b26a2e538738 #pragma version 5 txn ApplicationID int 0 == -bnz main_l16 +bnz main_l18 +txn OnCompletion +int UpdateApplication +== +txna ApplicationArgs 0 +byte 0xa0e81872 +== +&& +bnz main_l17 txn OnCompletion int OptIn == @@ -11,7 +19,7 @@ txna ApplicationArgs 0 byte 0xcfa68e36 == && -bnz main_l15 +bnz main_l16 txn OnCompletion int CloseOut == @@ -19,7 +27,7 @@ txna ApplicationArgs 0 byte 0xa9f42b3d == && -bnz main_l14 +bnz main_l15 txn OnCompletion int DeleteApplication == @@ -27,7 +35,7 @@ txna ApplicationArgs 0 byte 0x24378d3c == && -bnz main_l13 +bnz main_l14 txn OnCompletion int NoOp == @@ -35,7 +43,7 @@ txna ApplicationArgs 0 byte 0xfe6bdf69 == && -bnz main_l12 +bnz main_l13 txn OnCompletion int NoOp == @@ -43,7 +51,7 @@ txna ApplicationArgs 0 byte 0xa88c26a5 == && -bnz main_l11 +bnz main_l12 txn OnCompletion int NoOp == @@ -51,7 +59,7 @@ txna ApplicationArgs 0 byte 0x3e3b3d28 == && -bnz main_l10 +bnz main_l11 txn OnCompletion int NoOp == @@ -59,10 +67,10 @@ txna ApplicationArgs 0 byte 0x0df0050f == && -bnz main_l9 +bnz main_l10 int 0 return -main_l9: +main_l10: txna ApplicationArgs 1 txna ApplicationArgs 2 txna ApplicationArgs 3 @@ -72,45 +80,75 @@ txna ApplicationArgs 6 txna ApplicationArgs 7 txna ApplicationArgs 8 txna ApplicationArgs 9 +callsub sub8 +int 1 +return +main_l11: +txna ApplicationArgs 1 +callsub sub7 +int 1 +return +main_l12: callsub sub6 int 1 return -main_l10: +main_l13: txna ApplicationArgs 1 +txna ApplicationArgs 2 callsub sub5 int 1 return -main_l11: +main_l14: callsub sub4 int 1 return -main_l12: -txna ApplicationArgs 1 -txna ApplicationArgs 2 +main_l15: callsub sub3 int 1 return -main_l13: +main_l16: +txna ApplicationArgs 1 callsub sub2 int 1 return -main_l14: +main_l17: callsub sub1 int 1 return -main_l15: -txna ApplicationArgs 1 -callsub sub0 -int 1 -return -main_l16: +main_l18: +txn NumAppArgs +int 0 +> +bnz main_l20 +main_l19: int 1 return -sub0: // optIn +main_l20: +txna ApplicationArgs 0 +byte 0x43464101 +== +assert +txna ApplicationArgs 1 +callsub sub0 +b main_l19 +sub0: // create store 0 +byte 0x151f7c75 +load 0 +btoi +int 2 +* +itob +concat +log +retsub +sub1: // update +retsub +sub2: // optIn +store 1 int 0 byte "name" -load 0 +load 1 extract 2 0 app_local_put byte "hello " @@ -118,59 +156,59 @@ int 0 byte "name" app_local_get concat -store 1 +store 2 byte 0x151f7c75 -load 1 +load 2 len itob extract 6 2 concat -load 1 +load 2 concat log retsub -sub1: // closeOut +sub3: // closeOut byte "goodbye " int 0 byte "name" app_local_get concat -store 2 +store 3 byte 0x151f7c75 -load 2 +load 3 len itob extract 6 2 concat -load 2 +load 3 concat log retsub -sub2: // deleteApp +sub4: // deleteApp txn Sender global CreatorAddress == assert retsub -sub3: // add +sub5: // add +store 5 store 4 -store 3 byte 0x151f7c75 -load 3 -btoi load 4 btoi +load 5 +btoi + itob concat log retsub -sub4: // empty +sub6: // empty byte "random inconsequential log" log retsub -sub5: // payment -store 5 +sub7: // payment +store 6 txn GroupIndex int 1 - @@ -183,19 +221,20 @@ txn GroupIndex int 1 - gtxns Amount -load 5 +load 6 btoi == -bnz sub5_l2 +bnz sub7_l2 byte 0x00 -b sub5_l3 -sub5_l2: +b sub7_l3 +sub7_l2: byte 0x80 -sub5_l3: +sub7_l3: concat log retsub -sub6: // referenceTest +sub8: // referenceTest +store 15 store 14 store 13 store 12 @@ -204,25 +243,24 @@ store 10 store 9 store 8 store 7 -store 6 byte 0x151f7c75 -load 6 -concat -load 8 +load 7 concat -load 10 +load 9 concat -load 7 +load 11 concat -load 13 +load 8 concat load 14 concat -load 9 +load 15 concat -load 11 +load 10 concat load 12 concat +load 13 +concat log retsub |