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.go464
1 files changed, 367 insertions, 97 deletions
diff --git a/cmd/goal/application.go b/cmd/goal/application.go
index 5a1bed482..49a2ce9c5 100644
--- a/cmd/goal/application.go
+++ b/cmd/goal/application.go
@@ -17,6 +17,8 @@
package main
import (
+ "bytes"
+ "crypto/sha512"
"encoding/base32"
"encoding/base64"
"encoding/binary"
@@ -28,6 +30,7 @@ import (
"github.com/spf13/cobra"
"github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/abi"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
@@ -42,12 +45,15 @@ var (
approvalProgFile string
clearProgFile string
+ method string
+ methodArgs []string
+
approvalProgRawFile string
clearProgRawFile string
extraPages uint32
- createOnCompletion string
+ onCompletion string
localSchemaUints uint64
localSchemaByteSlices uint64
@@ -80,9 +86,10 @@ func init() {
appCmd.AddCommand(clearAppCmd)
appCmd.AddCommand(readStateAppCmd)
appCmd.AddCommand(infoAppCmd)
+ appCmd.AddCommand(methodAppCmd)
appCmd.PersistentFlags().StringVarP(&walletName, "wallet", "w", "", "Set the wallet to be used for the selected operation")
- appCmd.PersistentFlags().StringSliceVar(&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().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().StringSliceVar(&appStrAccounts, "app-account", nil, "Accounts that may be accessed from application logic")
@@ -99,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")
@@ -109,6 +116,11 @@ func init() {
deleteAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to send delete transaction from")
readStateAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to fetch state from")
updateAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to send update transaction from")
+ methodAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to call method from")
+
+ 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
@@ -121,6 +133,7 @@ func init() {
readStateAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
updateAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
infoAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
+ methodAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
// Add common transaction flags to all txn-generating app commands
addTxnFlags(createAppCmd)
@@ -130,6 +143,7 @@ func init() {
addTxnFlags(optInAppCmd)
addTxnFlags(closeOutAppCmd)
addTxnFlags(clearAppCmd)
+ addTxnFlags(methodAppCmd)
readStateAppCmd.Flags().BoolVar(&fetchLocal, "local", false, "Fetch account-specific state for this application. `--from` address is required when using this flag")
readStateAppCmd.Flags().BoolVar(&fetchGlobal, "global", false, "Fetch global state for this application.")
@@ -162,6 +176,11 @@ func init() {
readStateAppCmd.MarkFlagRequired("app-id")
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
}
type appCallArg struct {
@@ -230,6 +249,23 @@ func parseAppArg(arg appCallArg) (rawValue []byte, parseErr error) {
return
}
rawValue = data
+ case "abi":
+ typeAndValue := strings.SplitN(arg.Value, ":", 2)
+ if len(typeAndValue) != 2 {
+ parseErr = fmt.Errorf("Could not decode abi string (%s): should split abi-type and abi-value with colon", arg.Value)
+ return
+ }
+ abiType, err := abi.TypeOf(typeAndValue[0])
+ if err != nil {
+ parseErr = fmt.Errorf("Could not decode abi type string (%s): %v", typeAndValue[0], err)
+ return
+ }
+ value, err := abiType.UnmarshalFromJSON([]byte(typeAndValue[1]))
+ if err != nil {
+ parseErr = fmt.Errorf("Could not decode abi value string (%s):%v ", typeAndValue[1], err)
+ return
+ }
+ return abiType.Encode(value)
default:
parseErr = fmt.Errorf("Unknown encoding: %s", arg.Encoding)
}
@@ -267,6 +303,20 @@ 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")
@@ -276,7 +326,11 @@ func getAppInputs() (args [][]byte, accounts []string, foreignApps []uint64, for
}
var encodedArgs []appCallArg
- for _, arg := range appArgs {
+
+ // 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'")
@@ -328,6 +382,12 @@ func mustParseOnCompletion(ocString string) (oc transactions.OnCompletion) {
}
}
+func getDataDirAndClient() (dataDir string, client libgoal.Client) {
+ dataDir = ensureSingleDataDir()
+ client = ensureFullClient(dataDir)
+ return
+}
+
func mustParseProgArgs() (approval []byte, clear []byte) {
// Ensure we don't have ambiguous or all empty args
if (approvalProgFile == "") == (approvalProgRawFile == "") {
@@ -358,9 +418,7 @@ var createAppCmd = &cobra.Command{
Long: `Issue a transaction that creates an application`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
-
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Construct schemas from args
localSchema := basics.StateSchema{
@@ -375,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)
}
@@ -434,19 +492,13 @@ var createAppCmd = &cobra.Command{
}
} else {
if dumpForDryrun {
- // Write dryrun data to file
- proto, _ := getProto(protoVersion)
- data, err := libgoal.MakeDryrunStateBytes(client, tx, []transactions.SignedTxn{}, string(proto), dumpForDryrunFormat.String())
- if err != nil {
- reportErrorf(err.Error())
- }
- writeFile(outFilename, data, 0600)
+ err = writeDryrunReqToFile(client, tx, outFilename)
} else {
// Write transaction to file
err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
- if err != nil {
- reportErrorf(err.Error())
- }
+ }
+ if err != nil {
+ reportErrorf(err.Error())
}
}
},
@@ -458,8 +510,7 @@ var updateAppCmd = &cobra.Command{
Long: `Issue a transaction that updates an application's ApprovalProgram and ClearStateProgram`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
approvalProg, clearProg := mustParseProgArgs()
@@ -513,18 +564,12 @@ var updateAppCmd = &cobra.Command{
}
} else {
if dumpForDryrun {
- // Write dryrun data to file
- proto, _ := getProto(protoVersion)
- data, err := libgoal.MakeDryrunStateBytes(client, tx, []transactions.SignedTxn{}, string(proto), dumpForDryrunFormat.String())
- if err != nil {
- reportErrorf(err.Error())
- }
- writeFile(outFilename, data, 0600)
+ err = writeDryrunReqToFile(client, tx, outFilename)
} else {
err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
- if err != nil {
- reportErrorf(err.Error())
- }
+ }
+ if err != nil {
+ reportErrorf(err.Error())
}
}
},
@@ -536,8 +581,7 @@ var optInAppCmd = &cobra.Command{
Long: `Opt an account in to an application, allocating local state in your account`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -590,18 +634,12 @@ var optInAppCmd = &cobra.Command{
}
} else {
if dumpForDryrun {
- // Write dryrun data to file
- proto, _ := getProto(protoVersion)
- data, err := libgoal.MakeDryrunStateBytes(client, tx, []transactions.SignedTxn{}, string(proto), dumpForDryrunFormat.String())
- if err != nil {
- reportErrorf(err.Error())
- }
- writeFile(outFilename, data, 0600)
+ err = writeDryrunReqToFile(client, tx, outFilename)
} else {
err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
- if err != nil {
- reportErrorf(err.Error())
- }
+ }
+ if err != nil {
+ reportErrorf(err.Error())
}
}
},
@@ -613,8 +651,7 @@ var closeOutAppCmd = &cobra.Command{
Long: `Close an account out of an application, removing local state from your account. The application must still exist. If it doesn't, use 'goal app clear'.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -667,18 +704,12 @@ var closeOutAppCmd = &cobra.Command{
}
} else {
if dumpForDryrun {
- // Write dryrun data to file
- proto, _ := getProto(protoVersion)
- data, err := libgoal.MakeDryrunStateBytes(client, tx, []transactions.SignedTxn{}, string(proto), dumpForDryrunFormat.String())
- if err != nil {
- reportErrorf(err.Error())
- }
- writeFile(outFilename, data, 0600)
+ err = writeDryrunReqToFile(client, tx, outFilename)
} else {
err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
- if err != nil {
- reportErrorf(err.Error())
- }
+ }
+ if err != nil {
+ reportErrorf(err.Error())
}
}
},
@@ -690,8 +721,7 @@ var clearAppCmd = &cobra.Command{
Long: `Remove any local state from your account associated with an application. The application does not need to exist anymore.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -744,18 +774,12 @@ var clearAppCmd = &cobra.Command{
}
} else {
if dumpForDryrun {
- // Write dryrun data to file
- proto, _ := getProto(protoVersion)
- data, err := libgoal.MakeDryrunStateBytes(client, tx, []transactions.SignedTxn{}, string(proto), dumpForDryrunFormat.String())
- if err != nil {
- reportErrorf(err.Error())
- }
- writeFile(outFilename, data, 0600)
+ err = writeDryrunReqToFile(client, tx, outFilename)
} else {
err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
- if err != nil {
- reportErrorf(err.Error())
- }
+ }
+ if err != nil {
+ reportErrorf(err.Error())
}
}
},
@@ -767,8 +791,7 @@ var callAppCmd = &cobra.Command{
Long: `Call an application, invoking application-specific functionality`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -821,18 +844,12 @@ var callAppCmd = &cobra.Command{
}
} else {
if dumpForDryrun {
- // Write dryrun data to file
- proto, _ := getProto(protoVersion)
- data, err := libgoal.MakeDryrunStateBytes(client, tx, []transactions.SignedTxn{}, string(proto), dumpForDryrunFormat.String())
- if err != nil {
- reportErrorf(err.Error())
- }
- writeFile(outFilename, data, 0600)
+ err = writeDryrunReqToFile(client, tx, outFilename)
} else {
err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
- if err != nil {
- reportErrorf(err.Error())
- }
+ }
+ if err != nil {
+ reportErrorf(err.Error())
}
}
},
@@ -844,8 +861,7 @@ var deleteAppCmd = &cobra.Command{
Long: `Delete an application, removing the global state and other application parameters from the creator's account`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -898,18 +914,13 @@ var deleteAppCmd = &cobra.Command{
}
} else {
if dumpForDryrun {
- // Write dryrun data to file
- proto, _ := getProto(protoVersion)
- data, err := libgoal.MakeDryrunStateBytes(client, tx, []transactions.SignedTxn{}, string(proto), dumpForDryrunFormat.String())
- if err != nil {
- reportErrorf(err.Error())
- }
- writeFile(outFilename, data, 0600)
+ err = writeDryrunReqToFile(client, tx, outFilename)
} else {
err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
- if err != nil {
- reportErrorf(err.Error())
- }
+
+ }
+ if err != nil {
+ reportErrorf(err.Error())
}
}
},
@@ -921,8 +932,7 @@ var readStateAppCmd = &cobra.Command{
Long: `Read global or local (account-specific) state for an application`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ _, client := getDataDirAndClient()
// Ensure exactly one of --local or --global is specified
if fetchLocal == fetchGlobal {
@@ -1003,8 +1013,7 @@ var infoAppCmd = &cobra.Command{
Long: `Look up application information stored on the network, such as program hash.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ _, client := getDataDirAndClient()
meta, err := client.ApplicationInformation(appIdx)
if err != nil {
@@ -1037,3 +1046,264 @@ 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",
+ Long: `Invoke a method in an App (stateful contract) with an application call transaction`,
+ Args: validateNoPosArgsFn,
+ Run: func(cmd *cobra.Command, args []string) {
+ dataDir, client := getDataDirAndClient()
+
+ // 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")
+ }
+
+ onCompletionEnum := mustParseOnCompletion(onCompletion)
+
+ if appIdx == 0 {
+ reportErrorf("app id == 0, goal app create not supported in goal app method")
+ }
+
+ var approvalProg, clearProg []byte
+ if onCompletionEnum == transactions.UpdateApplicationOC {
+ approvalProg, clearProg = mustParseProgArgs()
+ }
+
+ var applicationArgs [][]byte
+
+ // insert the method selector hash
+ hash := sha512.Sum512_256([]byte(method))
+ applicationArgs = append(applicationArgs, hash[0:4])
+
+ // parse down the ABI type from method signature
+ _, argTypes, retTypeStr, err := abi.ParseMethodSignature(method)
+ if err != nil {
+ reportErrorf("cannot parse method signature: %v", err)
+ }
+
+ 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)
+ }
+
+ txnArgs, err := populateMethodCallTxnArgs(txnArgTypes, txnArgValues)
+ if err != nil {
+ reportErrorf("error populating transaction arguments: %v", err)
+ }
+
+ appCallTxn, err := client.MakeUnsignedApplicationCallTx(
+ appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets,
+ onCompletionEnum, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0)
+
+ if err != nil {
+ reportErrorf("Cannot create application txn: %v", err)
+ }
+
+ // Fill in note and lease
+ appCallTxn.Note = parseNoteField(cmd)
+ appCallTxn.Lease = parseLease(cmd)
+
+ // Fill in rounds, fee, etc.
+ fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds)
+ if err != nil {
+ reportErrorf("Cannot determine last valid round: %s", err)
+ }
+
+ 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 {
+ 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, signedTxnGroup, outFilename)
+ } else {
+ err = writeSignedTxnsToFile(signedTxnGroup, outFilename)
+ }
+ if err != nil {
+ reportErrorf(err.Error())
+ }
+ return
+ }
+
+ // Broadcast
+ err = client.BroadcastTransactionGroup(signedTxnGroup)
+ if err != nil {
+ reportErrorf(errorBroadcastingTX, err)
+ }
+
+ // Report tx details to user
+ 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)
+ if err != nil {
+ reportErrorf(err.Error())
+ }
+
+ resp, err := client.PendingTransactionInformationV2(txid)
+ if err != nil {
+ reportErrorf(err.Error())
+ }
+
+ if retType == nil {
+ fmt.Printf("method %s succeeded\n", method)
+ return
+ }
+
+ // specify the return hash prefix
+ hashRet := sha512.Sum512_256([]byte("return"))
+ hashRetPrefix := hashRet[:4]
+
+ var abiEncodedRet []byte
+ foundRet := false
+ if resp.Logs != nil {
+ for i := len(*resp.Logs) - 1; i >= 0; i-- {
+ retLog := (*resp.Logs)[i]
+ if bytes.HasPrefix(retLog, hashRetPrefix) {
+ abiEncodedRet = retLog[4:]
+ foundRet = true
+ break
+ }
+ }
+ }
+
+ if !foundRet {
+ reportErrorf("cannot find return log for abi type %s", retTypeStr)
+ }
+
+ decoded, err := retType.Decode(abiEncodedRet)
+ if err != nil {
+ reportErrorf("cannot decode return value %v: %v", abiEncodedRet, err)
+ }
+
+ decodedJSON, err := retType.MarshalToJSON(decoded)
+ if err != nil {
+ reportErrorf("cannot marshal returned bytes %v to JSON: %v", decoded, err)
+ }
+ fmt.Printf("method %s succeeded with output: %s\n", method, string(decodedJSON))
+ }
+ },
+}