summaryrefslogtreecommitdiff
path: root/cmd/goal/application.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/goal/application.go')
-rw-r--r--cmd/goal/application.go179
1 files changed, 145 insertions, 34 deletions
diff --git a/cmd/goal/application.go b/cmd/goal/application.go
index 2ed7eed2d..49a2ce9c5 100644
--- a/cmd/goal/application.go
+++ b/cmd/goal/application.go
@@ -53,7 +53,7 @@ var (
extraPages uint32
- createOnCompletion string
+ onCompletion string
localSchemaUints uint64
localSchemaByteSlices uint64
@@ -106,7 +106,7 @@ func init() {
createAppCmd.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.")
createAppCmd.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.")
createAppCmd.Flags().StringVar(&appCreator, "creator", "", "Account to create the application")
- createAppCmd.Flags().StringVar(&createOnCompletion, "on-completion", "NoOp", "OnCompletion action for application transaction")
+ createAppCmd.Flags().StringVar(&onCompletion, "on-completion", "NoOp", "OnCompletion action for application transaction")
createAppCmd.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.")
callAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to call app from")
@@ -120,6 +120,7 @@ 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")
// Can't use PersistentFlags on the root because for some reason marking
// a root command as required with MarkPersistentFlagRequired isn't
@@ -432,15 +433,15 @@ var createAppCmd = &cobra.Command{
// Parse transaction parameters
approvalProg, clearProg := mustParseProgArgs()
- onCompletion := mustParseOnCompletion(createOnCompletion)
+ onCompletionEnum := mustParseOnCompletion(onCompletion)
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
- switch onCompletion {
+ switch onCompletionEnum {
case transactions.CloseOutOC, transactions.ClearStateOC:
- reportWarnf("'--on-completion %s' may be ill-formed for 'goal app create'", createOnCompletion)
+ reportWarnf("'--on-completion %s' may be ill-formed for 'goal app create'", onCompletion)
}
- tx, err := client.MakeUnsignedAppCreateTx(onCompletion, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, extraPages)
+ tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, extraPages)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
@@ -1046,6 +1047,43 @@ var infoAppCmd = &cobra.Command{
},
}
+// populateMethodCallTxnArgs parses and loads transactions from the files indicated by the values
+// slice. An error will occur if the transaction does not matched the expected type, it has a nonzero
+// group ID, or if it is signed by a normal signature or Msig signature (but not Lsig signature)
+func populateMethodCallTxnArgs(types []string, values []string) ([]transactions.SignedTxn, error) {
+ loadedTxns := make([]transactions.SignedTxn, len(values))
+
+ for i, txFilename := range values {
+ data, err := readFile(txFilename)
+ if err != nil {
+ return nil, fmt.Errorf(fileReadError, txFilename, err)
+ }
+
+ var txn transactions.SignedTxn
+ err = protocol.Decode(data, &txn)
+ if err != nil {
+ return nil, fmt.Errorf(txDecodeError, txFilename, err)
+ }
+
+ if !txn.Sig.Blank() || !txn.Msig.Blank() {
+ return nil, fmt.Errorf("Transaction from %s has already been signed", txFilename)
+ }
+
+ if !txn.Txn.Group.IsZero() {
+ return nil, fmt.Errorf("Transaction from %s already has a group ID: %s", txFilename, txn.Txn.Group)
+ }
+
+ expectedType := types[i]
+ if expectedType != "txn" && txn.Txn.Type != protocol.TxType(expectedType) {
+ return nil, fmt.Errorf("Transaction from %s does not match method argument type. Expected %s, got %s", txFilename, expectedType, txn.Txn.Type)
+ }
+
+ loadedTxns[i] = txn
+ }
+
+ return loadedTxns, nil
+}
+
var methodAppCmd = &cobra.Command{
Use: "method",
Short: "Invoke a method",
@@ -1060,14 +1098,14 @@ var methodAppCmd = &cobra.Command{
reportErrorf("in goal app method: --arg and --app-arg are mutually exclusive, do not use --app-arg")
}
- onCompletion := mustParseOnCompletion(createOnCompletion)
+ onCompletionEnum := mustParseOnCompletion(onCompletion)
if appIdx == 0 {
reportErrorf("app id == 0, goal app create not supported in goal app method")
}
var approvalProg, clearProg []byte
- if onCompletion == transactions.UpdateApplicationOC {
+ if onCompletionEnum == transactions.UpdateApplicationOC {
approvalProg, clearProg = mustParseProgArgs()
}
@@ -1078,26 +1116,60 @@ var methodAppCmd = &cobra.Command{
applicationArgs = append(applicationArgs, hash[0:4])
// parse down the ABI type from method signature
- argTupleTypeStr, retTypeStr, err := abi.ParseMethodSignature(method)
+ _, argTypes, retTypeStr, err := abi.ParseMethodSignature(method)
if err != nil {
reportErrorf("cannot parse method signature: %v", err)
}
- err = abi.ParseArgJSONtoByteSlice(argTupleTypeStr, methodArgs, &applicationArgs)
+
+ var retType *abi.Type
+ if retTypeStr != "void" {
+ theRetType, err := abi.TypeOf(retTypeStr)
+ if err != nil {
+ reportErrorf("cannot cast %s to abi type: %v", retTypeStr, err)
+ }
+ retType = &theRetType
+ }
+
+ if len(methodArgs) != len(argTypes) {
+ reportErrorf("incorrect number of arguments, method expected %d but got %d", len(argTypes), len(methodArgs))
+ }
+
+ var txnArgTypes []string
+ var txnArgValues []string
+ var basicArgTypes []string
+ var basicArgValues []string
+ for i, argType := range argTypes {
+ argValue := methodArgs[i]
+ if abi.IsTransactionType(argType) {
+ txnArgTypes = append(txnArgTypes, argType)
+ txnArgValues = append(txnArgValues, argValue)
+ } else {
+ basicArgTypes = append(basicArgTypes, argType)
+ basicArgValues = append(basicArgValues, argValue)
+ }
+ }
+
+ err = abi.ParseArgJSONtoByteSlice(basicArgTypes, basicArgValues, &applicationArgs)
if err != nil {
reportErrorf("cannot parse arguments to ABI encoding: %v", err)
}
- tx, err := client.MakeUnsignedApplicationCallTx(
+ txnArgs, err := populateMethodCallTxnArgs(txnArgTypes, txnArgValues)
+ if err != nil {
+ reportErrorf("error populating transaction arguments: %v", err)
+ }
+
+ appCallTxn, err := client.MakeUnsignedApplicationCallTx(
appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets,
- onCompletion, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0)
+ onCompletionEnum, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
// Fill in note and lease
- tx.Note = parseNoteField(cmd)
- tx.Lease = parseLease(cmd)
+ appCallTxn.Note = parseNoteField(cmd)
+ appCallTxn.Lease = parseLease(cmd)
// Fill in rounds, fee, etc.
fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds)
@@ -1105,23 +1177,65 @@ var methodAppCmd = &cobra.Command{
reportErrorf("Cannot determine last valid round: %s", err)
}
- tx, err = client.FillUnsignedTxTemplate(account, fv, lv, fee, tx)
+ appCallTxn, err = client.FillUnsignedTxTemplate(account, fv, lv, fee, appCallTxn)
if err != nil {
reportErrorf("Cannot construct transaction: %s", err)
}
explicitFee := cmd.Flags().Changed("fee")
if explicitFee {
- tx.Fee = basics.MicroAlgos{Raw: fee}
+ appCallTxn.Fee = basics.MicroAlgos{Raw: fee}
+ }
+
+ // Compile group
+ var txnGroup []transactions.Transaction
+ for i := range txnArgs {
+ txnGroup = append(txnGroup, txnArgs[i].Txn)
+ }
+ txnGroup = append(txnGroup, appCallTxn)
+ if len(txnGroup) > 1 {
+ // Only if transaction arguments are present, assign group ID
+ groupID, err := client.GroupID(txnGroup)
+ if err != nil {
+ reportErrorf("Cannot assign transaction group ID: %s", err)
+ }
+ for i := range txnGroup {
+ txnGroup[i].Group = groupID
+ }
}
+ // Sign transactions
+ var signedTxnGroup []transactions.SignedTxn
+ shouldSign := sign || outFilename == ""
+ for i, unsignedTxn := range txnGroup {
+ txnFromArgs := transactions.SignedTxn{}
+ if i < len(txnArgs) {
+ txnFromArgs = txnArgs[i]
+ }
+
+ if !txnFromArgs.Lsig.Blank() {
+ signedTxnGroup = append(signedTxnGroup, transactions.SignedTxn{
+ Lsig: txnFromArgs.Lsig,
+ AuthAddr: txnFromArgs.AuthAddr,
+ Txn: unsignedTxn,
+ })
+ continue
+ }
+
+ signedTxn, err := createSignedTransaction(client, shouldSign, dataDir, walletName, unsignedTxn, txnFromArgs.AuthAddr)
+ if err != nil {
+ reportErrorf(errorSigningTX, err)
+ }
+
+ signedTxnGroup = append(signedTxnGroup, signedTxn)
+ }
+
+ // Output to file
if outFilename != "" {
if dumpForDryrun {
- err = writeDryrunReqToFile(client, tx, outFilename)
+ err = writeDryrunReqToFile(client, signedTxnGroup, outFilename)
} else {
- // Write transaction to file
- err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
+ err = writeSignedTxnsToFile(signedTxnGroup, outFilename)
}
-
if err != nil {
reportErrorf(err.Error())
}
@@ -1129,19 +1243,19 @@ var methodAppCmd = &cobra.Command{
}
// Broadcast
- wh, pw := ensureWalletHandleMaybePassword(dataDir, walletName, true)
- signedTxn, err := client.SignTransactionWithWallet(wh, pw, tx)
- if err != nil {
- reportErrorf(errorSigningTX, err)
- }
-
- txid, err := client.BroadcastTransaction(signedTxn)
+ err = client.BroadcastTransactionGroup(signedTxnGroup)
if err != nil {
reportErrorf(errorBroadcastingTX, err)
}
// Report tx details to user
- reportInfof("Issued transaction from account %s, txid %s (fee %d)", tx.Sender, txid, tx.Fee.Raw)
+ 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)
+ }
if !noWaitAfterSend {
_, err := waitForCommit(client, txid, lv)
@@ -1154,7 +1268,8 @@ var methodAppCmd = &cobra.Command{
reportErrorf(err.Error())
}
- if retTypeStr == "void" {
+ if retType == nil {
+ fmt.Printf("method %s succeeded\n", method)
return
}
@@ -1179,10 +1294,6 @@ var methodAppCmd = &cobra.Command{
reportErrorf("cannot find return log for abi type %s", retTypeStr)
}
- retType, err := abi.TypeOf(retTypeStr)
- if err != nil {
- reportErrorf("cannot cast %s to abi type: %v", retTypeStr, err)
- }
decoded, err := retType.Decode(abiEncodedRet)
if err != nil {
reportErrorf("cannot decode return value %v: %v", abiEncodedRet, err)
@@ -1192,7 +1303,7 @@ var methodAppCmd = &cobra.Command{
if err != nil {
reportErrorf("cannot marshal returned bytes %v to JSON: %v", decoded, err)
}
- fmt.Printf("method %s output: %s\n", method, string(decodedJSON))
+ fmt.Printf("method %s succeeded with output: %s\n", method, string(decodedJSON))
}
},
}