5353
5454 extraPages uint32
5555
56- createOnCompletion string
56+ onCompletion string
5757
5858 localSchemaUints uint64
5959 localSchemaByteSlices uint64
@@ -106,7 +106,7 @@ func init() {
106106 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." )
107107 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." )
108108 createAppCmd .Flags ().StringVar (& appCreator , "creator" , "" , "Account to create the application" )
109- createAppCmd .Flags ().StringVar (& createOnCompletion , "on-completion" , "NoOp" , "OnCompletion action for application transaction" )
109+ createAppCmd .Flags ().StringVar (& onCompletion , "on-completion" , "NoOp" , "OnCompletion action for application transaction" )
110110 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." )
111111
112112 callAppCmd .Flags ().StringVarP (& account , "from" , "f" , "" , "Account to call app from" )
@@ -120,6 +120,7 @@ func init() {
120120
121121 methodAppCmd .Flags ().StringVar (& method , "method" , "" , "Method to be called" )
122122 methodAppCmd .Flags ().StringArrayVar (& methodArgs , "arg" , nil , "Args to pass in for calling a method" )
123+ methodAppCmd .Flags ().StringVar (& onCompletion , "on-completion" , "NoOp" , "OnCompletion action for application transaction" )
123124
124125 // Can't use PersistentFlags on the root because for some reason marking
125126 // a root command as required with MarkPersistentFlagRequired isn't
@@ -176,12 +177,10 @@ func init() {
176177
177178 infoAppCmd .MarkFlagRequired ("app-id" )
178179
179- methodAppCmd .MarkFlagRequired ("method" ) // nolint:errcheck // follow previous required flag format
180- methodAppCmd .MarkFlagRequired ("app-id" ) // nolint:errcheck
181- methodAppCmd .MarkFlagRequired ("from" ) // nolint:errcheck
182- methodAppCmd .Flags ().MarkHidden ("app-arg" ) // nolint:errcheck
183- methodAppCmd .Flags ().MarkHidden ("app-input" ) // nolint:errcheck
184- methodAppCmd .Flags ().MarkHidden ("i" ) // nolint:errcheck
180+ methodAppCmd .MarkFlagRequired ("method" ) // nolint:errcheck // follow previous required flag format
181+ methodAppCmd .MarkFlagRequired ("app-id" ) // nolint:errcheck
182+ methodAppCmd .MarkFlagRequired ("from" ) // nolint:errcheck
183+ methodAppCmd .Flags ().MarkHidden ("app-arg" ) // nolint:errcheck
185184}
186185
187186type appCallArg struct {
@@ -434,15 +433,15 @@ var createAppCmd = &cobra.Command{
434433
435434 // Parse transaction parameters
436435 approvalProg , clearProg := mustParseProgArgs ()
437- onCompletion := mustParseOnCompletion (createOnCompletion )
436+ onCompletionEnum := mustParseOnCompletion (onCompletion )
438437 appArgs , appAccounts , foreignApps , foreignAssets := getAppInputs ()
439438
440- switch onCompletion {
439+ switch onCompletionEnum {
441440 case transactions .CloseOutOC , transactions .ClearStateOC :
442- reportWarnf ("'--on-completion %s' may be ill-formed for 'goal app create'" , createOnCompletion )
441+ reportWarnf ("'--on-completion %s' may be ill-formed for 'goal app create'" , onCompletion )
443442 }
444443
445- tx , err := client .MakeUnsignedAppCreateTx (onCompletion , approvalProg , clearProg , globalSchema , localSchema , appArgs , appAccounts , foreignApps , foreignAssets , extraPages )
444+ tx , err := client .MakeUnsignedAppCreateTx (onCompletionEnum , approvalProg , clearProg , globalSchema , localSchema , appArgs , appAccounts , foreignApps , foreignAssets , extraPages )
446445 if err != nil {
447446 reportErrorf ("Cannot create application txn: %v" , err )
448447 }
@@ -1048,6 +1047,43 @@ var infoAppCmd = &cobra.Command{
10481047 },
10491048}
10501049
1050+ // populateMethodCallTxnArgs parses and loads transactions from the files indicated by the values
1051+ // slice. An error will occur if the transaction does not matched the expected type, it has a nonzero
1052+ // group ID, or if it is signed by a normal signature or Msig signature (but not Lsig signature)
1053+ func populateMethodCallTxnArgs (types []string , values []string ) ([]transactions.SignedTxn , error ) {
1054+ loadedTxns := make ([]transactions.SignedTxn , len (values ))
1055+
1056+ for i , txFilename := range values {
1057+ data , err := readFile (txFilename )
1058+ if err != nil {
1059+ return nil , fmt .Errorf (fileReadError , txFilename , err )
1060+ }
1061+
1062+ var txn transactions.SignedTxn
1063+ err = protocol .Decode (data , & txn )
1064+ if err != nil {
1065+ return nil , fmt .Errorf (txDecodeError , txFilename , err )
1066+ }
1067+
1068+ if ! txn .Sig .Blank () || ! txn .Msig .Blank () {
1069+ return nil , fmt .Errorf ("Transaction from %s has already been signed" , txFilename )
1070+ }
1071+
1072+ if ! txn .Txn .Group .IsZero () {
1073+ return nil , fmt .Errorf ("Transaction from %s already has a group ID: %s" , txFilename , txn .Txn .Group )
1074+ }
1075+
1076+ expectedType := types [i ]
1077+ if expectedType != "txn" && txn .Txn .Type != protocol .TxType (expectedType ) {
1078+ return nil , fmt .Errorf ("Transaction from %s does not match method argument type. Expected %s, got %s" , txFilename , expectedType , txn .Txn .Type )
1079+ }
1080+
1081+ loadedTxns [i ] = txn
1082+ }
1083+
1084+ return loadedTxns , nil
1085+ }
1086+
10511087var methodAppCmd = & cobra.Command {
10521088 Use : "method" ,
10531089 Short : "Invoke a method" ,
@@ -1062,14 +1098,14 @@ var methodAppCmd = &cobra.Command{
10621098 reportErrorf ("in goal app method: --arg and --app-arg are mutually exclusive, do not use --app-arg" )
10631099 }
10641100
1065- onCompletion := mustParseOnCompletion (createOnCompletion )
1101+ onCompletionEnum := mustParseOnCompletion (onCompletion )
10661102
10671103 if appIdx == 0 {
10681104 reportErrorf ("app id == 0, goal app create not supported in goal app method" )
10691105 }
10701106
10711107 var approvalProg , clearProg []byte
1072- if onCompletion == transactions .UpdateApplicationOC {
1108+ if onCompletionEnum == transactions .UpdateApplicationOC {
10731109 approvalProg , clearProg = mustParseProgArgs ()
10741110 }
10751111
@@ -1080,56 +1116,146 @@ var methodAppCmd = &cobra.Command{
10801116 applicationArgs = append (applicationArgs , hash [0 :4 ])
10811117
10821118 // parse down the ABI type from method signature
1083- argTupleTypeStr , retTypeStr , err := abi .ParseMethodSignature (method )
1119+ _ , argTypes , retTypeStr , err := abi .ParseMethodSignature (method )
10841120 if err != nil {
10851121 reportErrorf ("cannot parse method signature: %v" , err )
10861122 }
1087- err = abi .ParseArgJSONtoByteSlice (argTupleTypeStr , methodArgs , & applicationArgs )
1123+
1124+ var retType * abi.Type
1125+ if retTypeStr != "void" {
1126+ theRetType , err := abi .TypeOf (retTypeStr )
1127+ if err != nil {
1128+ reportErrorf ("cannot cast %s to abi type: %v" , retTypeStr , err )
1129+ }
1130+ retType = & theRetType
1131+ }
1132+
1133+ if len (methodArgs ) != len (argTypes ) {
1134+ reportErrorf ("incorrect number of arguments, method expected %d but got %d" , len (argTypes ), len (methodArgs ))
1135+ }
1136+
1137+ var txnArgTypes []string
1138+ var txnArgValues []string
1139+ var basicArgTypes []string
1140+ var basicArgValues []string
1141+ for i , argType := range argTypes {
1142+ argValue := methodArgs [i ]
1143+ if abi .IsTransactionType (argType ) {
1144+ txnArgTypes = append (txnArgTypes , argType )
1145+ txnArgValues = append (txnArgValues , argValue )
1146+ } else {
1147+ basicArgTypes = append (basicArgTypes , argType )
1148+ basicArgValues = append (basicArgValues , argValue )
1149+ }
1150+ }
1151+
1152+ err = abi .ParseArgJSONtoByteSlice (basicArgTypes , basicArgValues , & applicationArgs )
10881153 if err != nil {
10891154 reportErrorf ("cannot parse arguments to ABI encoding: %v" , err )
10901155 }
10911156
1092- tx , err := client .MakeUnsignedApplicationCallTx (
1157+ txnArgs , err := populateMethodCallTxnArgs (txnArgTypes , txnArgValues )
1158+ if err != nil {
1159+ reportErrorf ("error populating transaction arguments: %v" , err )
1160+ }
1161+
1162+ appCallTxn , err := client .MakeUnsignedApplicationCallTx (
10931163 appIdx , applicationArgs , appAccounts , foreignApps , foreignAssets ,
1094- onCompletion , approvalProg , clearProg , basics.StateSchema {}, basics.StateSchema {}, 0 )
1164+ onCompletionEnum , approvalProg , clearProg , basics.StateSchema {}, basics.StateSchema {}, 0 )
10951165
10961166 if err != nil {
10971167 reportErrorf ("Cannot create application txn: %v" , err )
10981168 }
10991169
11001170 // Fill in note and lease
1101- tx .Note = parseNoteField (cmd )
1102- tx .Lease = parseLease (cmd )
1171+ appCallTxn .Note = parseNoteField (cmd )
1172+ appCallTxn .Lease = parseLease (cmd )
11031173
11041174 // Fill in rounds, fee, etc.
11051175 fv , lv , err := client .ComputeValidityRounds (firstValid , lastValid , numValidRounds )
11061176 if err != nil {
11071177 reportErrorf ("Cannot determine last valid round: %s" , err )
11081178 }
11091179
1110- tx , err = client .FillUnsignedTxTemplate (account , fv , lv , fee , tx )
1180+ appCallTxn , err = client .FillUnsignedTxTemplate (account , fv , lv , fee , appCallTxn )
11111181 if err != nil {
11121182 reportErrorf ("Cannot construct transaction: %s" , err )
11131183 }
11141184 explicitFee := cmd .Flags ().Changed ("fee" )
11151185 if explicitFee {
1116- tx .Fee = basics.MicroAlgos {Raw : fee }
1186+ appCallTxn .Fee = basics.MicroAlgos {Raw : fee }
11171187 }
11181188
1119- // Broadcast
1120- wh , pw := ensureWalletHandleMaybePassword (dataDir , walletName , true )
1121- signedTxn , err := client .SignTransactionWithWallet (wh , pw , tx )
1122- if err != nil {
1123- reportErrorf (errorSigningTX , err )
1189+ // Compile group
1190+ var txnGroup []transactions.Transaction
1191+ for i := range txnArgs {
1192+ txnGroup = append (txnGroup , txnArgs [i ].Txn )
1193+ }
1194+ txnGroup = append (txnGroup , appCallTxn )
1195+ if len (txnGroup ) > 1 {
1196+ // Only if transaction arguments are present, assign group ID
1197+ groupID , err := client .GroupID (txnGroup )
1198+ if err != nil {
1199+ reportErrorf ("Cannot assign transaction group ID: %s" , err )
1200+ }
1201+ for i := range txnGroup {
1202+ txnGroup [i ].Group = groupID
1203+ }
11241204 }
11251205
1126- txid , err := client .BroadcastTransaction (signedTxn )
1206+ // Sign transactions
1207+ var signedTxnGroup []transactions.SignedTxn
1208+ shouldSign := sign || outFilename == ""
1209+ for i , unsignedTxn := range txnGroup {
1210+ txnFromArgs := transactions.SignedTxn {}
1211+ if i < len (txnArgs ) {
1212+ txnFromArgs = txnArgs [i ]
1213+ }
1214+
1215+ if ! txnFromArgs .Lsig .Blank () {
1216+ signedTxnGroup = append (signedTxnGroup , transactions.SignedTxn {
1217+ Lsig : txnFromArgs .Lsig ,
1218+ AuthAddr : txnFromArgs .AuthAddr ,
1219+ Txn : unsignedTxn ,
1220+ })
1221+ continue
1222+ }
1223+
1224+ signedTxn , err := createSignedTransaction (client , shouldSign , dataDir , walletName , unsignedTxn , txnFromArgs .AuthAddr )
1225+ if err != nil {
1226+ reportErrorf (errorSigningTX , err )
1227+ }
1228+
1229+ signedTxnGroup = append (signedTxnGroup , signedTxn )
1230+ }
1231+
1232+ // Output to file
1233+ if outFilename != "" {
1234+ if dumpForDryrun {
1235+ err = writeDryrunReqToFile (client , signedTxnGroup , outFilename )
1236+ } else {
1237+ err = writeSignedTxnsToFile (signedTxnGroup , outFilename )
1238+ }
1239+ if err != nil {
1240+ reportErrorf (err .Error ())
1241+ }
1242+ return
1243+ }
1244+
1245+ // Broadcast
1246+ err = client .BroadcastTransactionGroup (signedTxnGroup )
11271247 if err != nil {
11281248 reportErrorf (errorBroadcastingTX , err )
11291249 }
11301250
11311251 // Report tx details to user
1132- reportInfof ("Issued transaction from account %s, txid %s (fee %d)" , tx .Sender , txid , tx .Fee .Raw )
1252+ reportInfof ("Issued %d transaction(s):" , len (signedTxnGroup ))
1253+ // remember the final txid in this variable
1254+ var txid string
1255+ for _ , stxn := range signedTxnGroup {
1256+ txid = stxn .Txn .ID ().String ()
1257+ reportInfof ("\t Issued transaction from account %s, txid %s (fee %d)" , stxn .Txn .Sender , txid , stxn .Txn .Fee .Raw )
1258+ }
11331259
11341260 if ! noWaitAfterSend {
11351261 _ , err := waitForCommit (client , txid , lv )
@@ -1142,7 +1268,8 @@ var methodAppCmd = &cobra.Command{
11421268 reportErrorf (err .Error ())
11431269 }
11441270
1145- if retTypeStr == "void" {
1271+ if retType == nil {
1272+ fmt .Printf ("method %s succeeded\n " , method )
11461273 return
11471274 }
11481275
@@ -1167,10 +1294,6 @@ var methodAppCmd = &cobra.Command{
11671294 reportErrorf ("cannot find return log for abi type %s" , retTypeStr )
11681295 }
11691296
1170- retType , err := abi .TypeOf (retTypeStr )
1171- if err != nil {
1172- reportErrorf ("cannot cast %s to abi type: %v" , retTypeStr , err )
1173- }
11741297 decoded , err := retType .Decode (abiEncodedRet )
11751298 if err != nil {
11761299 reportErrorf ("cannot decode return value %v: %v" , abiEncodedRet , err )
@@ -1180,7 +1303,7 @@ var methodAppCmd = &cobra.Command{
11801303 if err != nil {
11811304 reportErrorf ("cannot marshal returned bytes %v to JSON: %v" , decoded , err )
11821305 }
1183- fmt .Printf ("method %s output: %s" , method , string (decodedJSON ))
1306+ fmt .Printf ("method %s succeeded with output: %s\n " , method , string (decodedJSON ))
11841307 }
11851308 },
11861309}
0 commit comments