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
@@ -432,15 +433,15 @@ var createAppCmd = &cobra.Command{
432433
433434 // Parse transaction parameters
434435 approvalProg , clearProg := mustParseProgArgs ()
435- onCompletion := mustParseOnCompletion (createOnCompletion )
436+ onCompletionEnum := mustParseOnCompletion (onCompletion )
436437 appArgs , appAccounts , foreignApps , foreignAssets := getAppInputs ()
437438
438- switch onCompletion {
439+ switch onCompletionEnum {
439440 case transactions .CloseOutOC , transactions .ClearStateOC :
440- 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 )
441442 }
442443
443- 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 )
444445 if err != nil {
445446 reportErrorf ("Cannot create application txn: %v" , err )
446447 }
@@ -1046,6 +1047,43 @@ var infoAppCmd = &cobra.Command{
10461047 },
10471048}
10481049
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+
10491087var methodAppCmd = & cobra.Command {
10501088 Use : "method" ,
10511089 Short : "Invoke a method" ,
@@ -1060,14 +1098,14 @@ var methodAppCmd = &cobra.Command{
10601098 reportErrorf ("in goal app method: --arg and --app-arg are mutually exclusive, do not use --app-arg" )
10611099 }
10621100
1063- onCompletion := mustParseOnCompletion (createOnCompletion )
1101+ onCompletionEnum := mustParseOnCompletion (onCompletion )
10641102
10651103 if appIdx == 0 {
10661104 reportErrorf ("app id == 0, goal app create not supported in goal app method" )
10671105 }
10681106
10691107 var approvalProg , clearProg []byte
1070- if onCompletion == transactions .UpdateApplicationOC {
1108+ if onCompletionEnum == transactions .UpdateApplicationOC {
10711109 approvalProg , clearProg = mustParseProgArgs ()
10721110 }
10731111
@@ -1078,70 +1116,146 @@ var methodAppCmd = &cobra.Command{
10781116 applicationArgs = append (applicationArgs , hash [0 :4 ])
10791117
10801118 // parse down the ABI type from method signature
1081- argTupleTypeStr , retTypeStr , err := abi .ParseMethodSignature (method )
1119+ _ , argTypes , retTypeStr , err := abi .ParseMethodSignature (method )
10821120 if err != nil {
10831121 reportErrorf ("cannot parse method signature: %v" , err )
10841122 }
1085- 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 )
10861153 if err != nil {
10871154 reportErrorf ("cannot parse arguments to ABI encoding: %v" , err )
10881155 }
10891156
1090- 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 (
10911163 appIdx , applicationArgs , appAccounts , foreignApps , foreignAssets ,
1092- onCompletion , approvalProg , clearProg , basics.StateSchema {}, basics.StateSchema {}, 0 )
1164+ onCompletionEnum , approvalProg , clearProg , basics.StateSchema {}, basics.StateSchema {}, 0 )
10931165
10941166 if err != nil {
10951167 reportErrorf ("Cannot create application txn: %v" , err )
10961168 }
10971169
10981170 // Fill in note and lease
1099- tx .Note = parseNoteField (cmd )
1100- tx .Lease = parseLease (cmd )
1171+ appCallTxn .Note = parseNoteField (cmd )
1172+ appCallTxn .Lease = parseLease (cmd )
11011173
11021174 // Fill in rounds, fee, etc.
11031175 fv , lv , err := client .ComputeValidityRounds (firstValid , lastValid , numValidRounds )
11041176 if err != nil {
11051177 reportErrorf ("Cannot determine last valid round: %s" , err )
11061178 }
11071179
1108- tx , err = client .FillUnsignedTxTemplate (account , fv , lv , fee , tx )
1180+ appCallTxn , err = client .FillUnsignedTxTemplate (account , fv , lv , fee , appCallTxn )
11091181 if err != nil {
11101182 reportErrorf ("Cannot construct transaction: %s" , err )
11111183 }
11121184 explicitFee := cmd .Flags ().Changed ("fee" )
11131185 if explicitFee {
1114- tx .Fee = basics.MicroAlgos {Raw : fee }
1186+ appCallTxn .Fee = basics.MicroAlgos {Raw : fee }
1187+ }
1188+
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+ }
11151204 }
11161205
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
11171233 if outFilename != "" {
11181234 if dumpForDryrun {
1119- err = writeDryrunReqToFile (client , tx , outFilename )
1235+ err = writeDryrunReqToFile (client , signedTxnGroup , outFilename )
11201236 } else {
1121- // Write transaction to file
1122- err = writeTxnToFile (client , sign , dataDir , walletName , tx , outFilename )
1237+ err = writeSignedTxnsToFile (signedTxnGroup , outFilename )
11231238 }
1124-
11251239 if err != nil {
11261240 reportErrorf (err .Error ())
11271241 }
11281242 return
11291243 }
11301244
11311245 // Broadcast
1132- wh , pw := ensureWalletHandleMaybePassword (dataDir , walletName , true )
1133- signedTxn , err := client .SignTransactionWithWallet (wh , pw , tx )
1134- if err != nil {
1135- reportErrorf (errorSigningTX , err )
1136- }
1137-
1138- txid , err := client .BroadcastTransaction (signedTxn )
1246+ err = client .BroadcastTransactionGroup (signedTxnGroup )
11391247 if err != nil {
11401248 reportErrorf (errorBroadcastingTX , err )
11411249 }
11421250
11431251 // Report tx details to user
1144- 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+ }
11451259
11461260 if ! noWaitAfterSend {
11471261 _ , err := waitForCommit (client , txid , lv )
@@ -1154,7 +1268,8 @@ var methodAppCmd = &cobra.Command{
11541268 reportErrorf (err .Error ())
11551269 }
11561270
1157- if retTypeStr == "void" {
1271+ if retType == nil {
1272+ fmt .Printf ("method %s succeeded\n " , method )
11581273 return
11591274 }
11601275
@@ -1179,10 +1294,6 @@ var methodAppCmd = &cobra.Command{
11791294 reportErrorf ("cannot find return log for abi type %s" , retTypeStr )
11801295 }
11811296
1182- retType , err := abi .TypeOf (retTypeStr )
1183- if err != nil {
1184- reportErrorf ("cannot cast %s to abi type: %v" , retTypeStr , err )
1185- }
11861297 decoded , err := retType .Decode (abiEncodedRet )
11871298 if err != nil {
11881299 reportErrorf ("cannot decode return value %v: %v" , abiEncodedRet , err )
@@ -1192,7 +1303,7 @@ var methodAppCmd = &cobra.Command{
11921303 if err != nil {
11931304 reportErrorf ("cannot marshal returned bytes %v to JSON: %v" , decoded , err )
11941305 }
1195- fmt .Printf ("method %s output: %s\n " , method , string (decodedJSON ))
1306+ fmt .Printf ("method %s succeeded with output: %s\n " , method , string (decodedJSON ))
11961307 }
11971308 },
11981309}
0 commit comments