summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Paulos <jasonpaulos@users.noreply.github.com>2021-12-29 12:54:27 -0800
committerGitHub <noreply@github.com>2021-12-29 15:54:27 -0500
commit14bf4643561fa20fb12c657e0eb89b58b1322309 (patch)
tree966d201f6c80690903b2d7e365a5a091c0aa5601
parent2e09ebfca1ca46231e43cd62c48a1025ce7c0a11 (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.go69
-rwxr-xr-xtest/scripts/e2e_subs/e2e-app-abi-method.sh23
-rw-r--r--test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal148
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