From 99054210c0a777ec0016e84728672a0f22865271 Mon Sep 17 00:00:00 2001 From: Jack Smith <87339414+algojack@users.noreply.github.com> Date: Fri, 19 Nov 2021 09:52:05 -0700 Subject: Adding-clean-target-to-ci-build-target (#2948) --- scripts/release/mule/Makefile.mule | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release/mule/Makefile.mule b/scripts/release/mule/Makefile.mule index 627e7fe21..95cc465eb 100644 --- a/scripts/release/mule/Makefile.mule +++ b/scripts/release/mule/Makefile.mule @@ -4,7 +4,7 @@ PKG_DIR = $(SRCPATH)/tmp/node_pkgs/$(OS_TYPE)/$(ARCH) .PHONY: ci-clean ci-setup ci-build -ci-clean: +ci-clean: clean rm -rf tmp ci-setup: @@ -28,7 +28,7 @@ ci-integration: SRCROOT=$(SRCPATH) \ test/scripts/e2e.sh -c $(CHANNEL) -n -ci-build: buildsrc gen ci-setup +ci-build: ci-clean buildsrc gen ci-setup CHANNEL=$(CHANNEL) PKG_ROOT=$(PKG_DIR) NO_BUILD=True VARIATIONS=$(OS_TYPE)-$(ARCH) \ scripts/build_packages.sh $(OS_TYPE)/$(ARCH) && \ mkdir -p $(PKG_DIR)/data && \ -- cgit v1.2.3 From 0b0f97b8883773f57a7b574b1ce2979951d3fe83 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 19 Nov 2021 09:43:25 -0800 Subject: Fix OnCompletion flag for `goal app method` (#3228) --- cmd/goal/application.go | 22 +-- test/scripts/e2e_subs/e2e-app-abi-add.sh | 44 ++++-- .../e2e_subs/tealprogs/app-abi-add-example.teal | 159 +++++++++++++++------ 3 files changed, 163 insertions(+), 62 deletions(-) diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 2ed7eed2d..2758a2712 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) } @@ -1060,14 +1061,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() } @@ -1089,7 +1090,7 @@ var methodAppCmd = &cobra.Command{ tx, 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) @@ -1155,6 +1156,7 @@ var methodAppCmd = &cobra.Command{ } if retTypeStr == "void" { + fmt.Printf("method %s succeeded", method) return } @@ -1192,7 +1194,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", method, string(decodedJSON)) } }, } diff --git a/test/scripts/e2e_subs/e2e-app-abi-add.sh b/test/scripts/e2e_subs/e2e-app-abi-add.sh index 60e1c1f3e..4ee0494b6 100755 --- a/test/scripts/e2e_subs/e2e-app-abi-add.sh +++ b/test/scripts/e2e_subs/e2e-app-abi-add.sh @@ -14,26 +14,48 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" gcmd="goal -w ${WALLET}" -GLOBAL_INTS=2 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-add-example.teal --clear-prog ${TEMPDIR}/simple.teal --global-byteslices 0 --global-ints ${GLOBAL_INTS} --local-byteslices 0 --local-ints 0 | grep Created | awk '{ print $6 }') +APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/app-abi-add-example.teal --clear-prog ${TEMPDIR}/simple.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 | grep Created | awk '{ print $6 }') -# Should succeed to opt in -${gcmd} app optin --app-id $APPID --from $ACCOUNT +# Opt in +RES=$(${gcmd} app method --method "optIn(string)string" --arg "\"Algorand Fan\"" --on-completion optin --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method optIn(string)string succeeded with output: \"hello Algorand Fan\"" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-add-test FAIL the method call to optIn(string)string should not fail %Y%m%d_%H%M%S' + false +fi -# Call should now succeed +# 1 + 2 = 3 RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 1 --arg 2 --app-id $APPID --from $ACCOUNT 2>&1 || true) -EXPECTED="method add(uint64,uint64)uint64 output: 3" +EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 3" if [[ $RES != *"${EXPECTED}"* ]]; then - date '+app-abi-add-test FAIL the application creation should not fail %Y%m%d_%H%M%S' + date '+app-abi-add-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S' false fi -# Delete application should still succeed -${gcmd} app delete --app-id $APPID --from $ACCOUNT +# 18446744073709551614 + 1 = 18446744073709551615 +RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 18446744073709551614 --arg 1 --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 18446744073709551615" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-add-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S' + false +fi -# Clear should still succeed -${gcmd} app clear --app-id $APPID --from $ACCOUNT +# Close out +RES=$(${gcmd} app method --method "closeOut()string" --on-completion closeout --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method closeOut()string succeeded with output: \"goodbye Algorand Fan\"" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-add-test FAIL the method call to closeOut()string 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" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-add-test FAIL the method call to delete()void should not fail %Y%m%d_%H%M%S' + false +fi diff --git a/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal b/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal index 18d3b3e6e..a2c816876 100644 --- a/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal +++ b/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal @@ -1,87 +1,164 @@ +// generated from https://gist.github.com/jasonpaulos/99e4f8a75f2fc2ec9b8073c064530359 #pragma version 5 -intcblock 1 0 -bytecblock 0x151f7c75 txn ApplicationID -intc_1 // 0 +int 0 == -bnz main_l12 +bnz main_l14 txn OnCompletion -intc_0 // OptIn +int OptIn == -bnz main_l11 +txna ApplicationArgs 0 +byte 0xcfa68e36 +== +&& +bnz main_l13 txn OnCompletion -pushint 5 // DeleteApplication +int CloseOut == -bnz main_l10 +txna ApplicationArgs 0 +byte 0xa9f42b3d +== +&& +bnz main_l12 txn OnCompletion -intc_1 // NoOp +int DeleteApplication == txna ApplicationArgs 0 -pushbytes 0xfe6bdf69 // 0xfe6bdf69 +byte 0x24378d3c == && -bnz main_l9 +bnz main_l11 txn OnCompletion -intc_1 // NoOp +int NoOp == txna ApplicationArgs 0 -pushbytes 0xa88c26a5 // 0xa88c26a5 +byte 0xfe6bdf69 == && -bnz main_l8 +bnz main_l10 txn OnCompletion -intc_1 // NoOp +int NoOp == txna ApplicationArgs 0 -pushbytes 0x535a47ba // 0x535a47ba +byte 0xa88c26a5 == && -bnz main_l7 -intc_1 // 0 -return -main_l7: -txna ApplicationArgs 1 -callsub sub2 -intc_0 // 1 +bnz main_l9 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0x535a47ba +== +&& +bnz main_l8 +int 0 return main_l8: -callsub sub1 -intc_0 // 1 +txna ApplicationArgs 1 +callsub sub5 +int 1 return main_l9: -txna ApplicationArgs 1 -txna ApplicationArgs 2 -callsub sub0 -intc_0 // 1 +callsub sub4 +int 1 return main_l10: -intc_0 // 1 +txna ApplicationArgs 1 +txna ApplicationArgs 2 +callsub sub3 +int 1 return main_l11: -intc_0 // 1 +callsub sub2 +int 1 return main_l12: -intc_0 // 1 +callsub sub1 +int 1 return -sub0: // add -store 1 +main_l13: +txna ApplicationArgs 1 +callsub sub0 +int 1 +return +main_l14: +int 1 +return +sub0: // optIn store 0 -bytec_0 // 0x151f7c75 +int 0 +byte "name" load 0 -btoi +extract 2 0 +app_local_put +byte "hello " +int 0 +byte "name" +app_local_get +concat +store 1 +byte 0x151f7c75 load 1 +len +itob +extract 6 2 +concat +load 1 +concat +log +retsub +sub1: // closeOut +byte "goodbye " +int 0 +byte "name" +app_local_get +concat +store 2 +byte 0x151f7c75 +load 2 +len +itob +extract 6 2 +concat +load 2 +concat +log +retsub +sub2: // deleteApp +txn Sender +global CreatorAddress +== +assert +retsub +sub3: // add +store 4 +store 3 +byte 0x151f7c75 +load 3 +btoi +load 4 btoi + itob concat log retsub -sub1: // empty -bytec_0 // 0x151f7c75 +sub4: // empty +byte "random inconsequential log" log retsub -sub2: // payment -store 2 -pushbytes 0x151f7c7580 // 0x151f7c7580 +sub5: // payment +store 5 +txn GroupIndex +int 1 +- +gtxns TypeEnum +int pay +== +assert +byte 0x151f7c75 +byte 0x80 +concat log retsub \ No newline at end of file -- cgit v1.2.3 From 116c06e00ad4f0dfa0c22577b733d8b0d933be0a Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 22 Nov 2021 06:16:32 -0800 Subject: Support transaction arguments for `goal app method` (#3233) * Implement transactions as arguments * Fix indexing and dryrun issue * Add docstring * Satisfy review dog * Fix pointer issue * Fix group command * Rename e2e test * Fix filename variable * Add e2e test * Use tab --- cmd/goal/application.go | 161 ++++++++++++++++--- cmd/goal/clerk.go | 50 +++--- data/abi/abi_encode.go | 85 +++++----- data/abi/abi_type.go | 11 ++ libgoal/libgoal.go | 27 ++-- test/scripts/e2e_subs/e2e-app-abi-add.sh | 61 ------- test/scripts/e2e_subs/e2e-app-abi-method.sh | 79 +++++++++ .../e2e_subs/tealprogs/app-abi-add-example.teal | 164 ------------------- .../e2e_subs/tealprogs/app-abi-method-example.teal | 176 +++++++++++++++++++++ 9 files changed, 497 insertions(+), 317 deletions(-) delete mode 100755 test/scripts/e2e_subs/e2e-app-abi-add.sh create mode 100755 test/scripts/e2e_subs/e2e-app-abi-method.sh delete mode 100644 test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal create mode 100644 test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 2758a2712..49a2ce9c5 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -1047,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", @@ -1079,16 +1116,50 @@ 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, onCompletionEnum, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0) @@ -1097,8 +1168,8 @@ var methodAppCmd = &cobra.Command{ } // 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) @@ -1106,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()) } @@ -1130,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) @@ -1155,8 +1268,8 @@ var methodAppCmd = &cobra.Command{ reportErrorf(err.Error()) } - if retTypeStr == "void" { - fmt.Printf("method %s succeeded", method) + if retType == nil { + fmt.Printf("method %s succeeded\n", method) return } @@ -1181,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) @@ -1194,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 succeeded with output: %s", method, string(decodedJSON)) + fmt.Printf("method %s succeeded with output: %s\n", method, string(decodedJSON)) } }, } diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index ed5392f5a..c5d69854a 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -194,34 +194,45 @@ func waitForCommit(client libgoal.Client, txid string, transactionLastValidRound return } -func createSignedTransaction(client libgoal.Client, signTx bool, dataDir string, walletName string, tx transactions.Transaction) (stxn transactions.SignedTxn, err error) { +func createSignedTransaction(client libgoal.Client, signTx bool, dataDir string, walletName string, tx transactions.Transaction, signer basics.Address) (stxn transactions.SignedTxn, err error) { if signTx { // Sign the transaction wh, pw := ensureWalletHandleMaybePassword(dataDir, walletName, true) - stxn, err = client.SignTransactionWithWallet(wh, pw, tx) - if err != nil { - return - } - } else { - // Wrap in a transactions.SignedTxn with an empty sig. - // This way protocol.Encode will encode the transaction type - stxn, err = transactions.AssembleSignedTxn(tx, crypto.Signature{}, crypto.MultisigSig{}) - if err != nil { - return + if signer.IsZero() { + stxn, err = client.SignTransactionWithWallet(wh, pw, tx) + } else { + stxn, err = client.SignTransactionWithWalletAndSigner(wh, pw, signer.String(), tx) } + return + } - stxn = populateBlankMultisig(client, dataDir, walletName, stxn) + // Wrap in a transactions.SignedTxn with an empty sig. + // This way protocol.Encode will encode the transaction type + stxn, err = transactions.AssembleSignedTxn(tx, crypto.Signature{}, crypto.MultisigSig{}) + if err != nil { + return } + + stxn = populateBlankMultisig(client, dataDir, walletName, stxn) return } +func writeSignedTxnsToFile(stxns []transactions.SignedTxn, filename string) error { + var outData []byte + for _, stxn := range stxns { + outData = append(outData, protocol.Encode(&stxn)...) + } + + return writeFile(filename, outData, 0600) +} + func writeTxnToFile(client libgoal.Client, signTx bool, dataDir string, walletName string, tx transactions.Transaction, filename string) error { - stxn, err := createSignedTransaction(client, signTx, dataDir, walletName, tx) + stxn, err := createSignedTransaction(client, signTx, dataDir, walletName, tx, basics.Address{}) if err != nil { return err } // Write the SignedTxn to the output file - return writeFile(filename, protocol.Encode(&stxn), 0600) + return writeSignedTxnsToFile([]transactions.SignedTxn{stxn}, filename) } func getB64Args(args []string) [][]byte { @@ -419,7 +430,7 @@ var sendCmd = &cobra.Command{ } } else { signTx := sign || (outFilename == "") - stx, err = createSignedTransaction(client, signTx, dataDir, walletName, payment) + stx, err = createSignedTransaction(client, signTx, dataDir, walletName, payment, basics.Address{}) if err != nil { reportErrorf(errorSigningTX, err) } @@ -854,13 +865,12 @@ var groupCmd = &cobra.Command{ transactionIdx++ } - var outData []byte - for _, stxn := range stxns { - stxn.Txn.Group = crypto.HashObj(group) - outData = append(outData, protocol.Encode(&stxn)...) + groupHash := crypto.HashObj(group) + for i := range stxns { + stxns[i].Txn.Group = groupHash } - err = writeFile(outFilename, outData, 0600) + err = writeSignedTxnsToFile(stxns, outFilename) if err != nil { reportErrorf(fileWriteError, outFilename, err) } diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go index fa5dbd57c..397e66856 100644 --- a/data/abi/abi_encode.go +++ b/data/abi/abi_encode.go @@ -481,37 +481,39 @@ func decodeTuple(encoded []byte, childT []Type) ([]interface{}, error) { // ParseArgJSONtoByteSlice convert input method arguments to ABI encoded bytes // it converts funcArgTypes into a tuple type and apply changes over input argument string (in JSON format) // if there are greater or equal to 15 inputs, then we compact the tailing inputs into one tuple -func ParseArgJSONtoByteSlice(funcArgTypes string, jsonArgs []string, applicationArgs *[][]byte) error { - abiTupleT, err := TypeOf(funcArgTypes) - if err != nil { - return err +func ParseArgJSONtoByteSlice(argTypes []string, jsonArgs []string, applicationArgs *[][]byte) error { + abiTypes := make([]Type, len(argTypes)) + for i, typeString := range argTypes { + abiType, err := TypeOf(typeString) + if err != nil { + return err + } + abiTypes[i] = abiType } - if len(abiTupleT.childTypes) != len(jsonArgs) { - return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTupleT.childTypes)) + + if len(abiTypes) != len(jsonArgs) { + return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTypes)) } // change the input args to be 1 - 14 + 15 (compacting everything together) if len(jsonArgs) > 14 { - compactedType, err := MakeTupleType(abiTupleT.childTypes[14:]) + compactedType, err := MakeTupleType(abiTypes[14:]) if err != nil { return err } - abiTupleT.childTypes = abiTupleT.childTypes[:14] - abiTupleT.childTypes = append(abiTupleT.childTypes, compactedType) - abiTupleT.staticLength = 15 + abiTypes = append(abiTypes[:14], compactedType) remainingJSON := "[" + strings.Join(jsonArgs[14:], ",") + "]" - jsonArgs = jsonArgs[:14] - jsonArgs = append(jsonArgs, remainingJSON) + jsonArgs = append(jsonArgs[:14], remainingJSON) } // parse JSON value to ABI encoded bytes for i := 0; i < len(jsonArgs); i++ { - interfaceVal, err := abiTupleT.childTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i])) + interfaceVal, err := abiTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i])) if err != nil { return err } - abiEncoded, err := abiTupleT.childTypes[i].Encode(interfaceVal) + abiEncoded, err := abiTypes[i].Encode(interfaceVal) if err != nil { return err } @@ -520,30 +522,41 @@ func ParseArgJSONtoByteSlice(funcArgTypes string, jsonArgs []string, application return nil } -// ParseMethodSignature parses a method of format `method(...argTypes...)retType` -// into `(...argTypes)` and `retType` -func ParseMethodSignature(methodSig string) (string, string, error) { - var stack []int - - for index, chr := range methodSig { - if chr == '(' { - stack = append(stack, index) - } else if chr == ')' { - if len(stack) == 0 { - break +// ParseMethodSignature parses a method of format `method(argType1,argType2,...)retType` +// into `method` {`argType1`,`argType2`,..} and `retType` +func ParseMethodSignature(methodSig string) (name string, argTypes []string, returnType string, err error) { + argsStart := strings.Index(methodSig, "(") + if argsStart == -1 { + err = fmt.Errorf("Invalid method signature: %s", methodSig) + return + } + + argsEnd := -1 + depth := 0 + for index, char := range methodSig { + switch char { + case '(': + depth++ + case ')': + if depth == 0 { + err = fmt.Errorf("Unpaired parenthesis in method signature: %s", methodSig) + return } - leftParenIndex := stack[len(stack)-1] - stack = stack[:len(stack)-1] - if len(stack) == 0 { - returnType := methodSig[index+1:] - if _, err := TypeOf(returnType); err != nil { - if returnType != "void" { - return "", "", fmt.Errorf("cannot infer return type: %s", returnType) - } - } - return methodSig[leftParenIndex : index+1], methodSig[index+1:], nil + depth-- + if depth == 0 { + argsEnd = index + break } } } - return "", "", fmt.Errorf("unpaired parentheses: %s", methodSig) + + if argsEnd == -1 { + err = fmt.Errorf("Invalid method signature: %s", methodSig) + return + } + + name = methodSig[:argsStart] + argTypes, err = parseTupleContent(methodSig[argsStart+1 : argsEnd]) + returnType = methodSig[argsEnd+1:] + return } diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go index eb93f9eea..353517027 100644 --- a/data/abi/abi_type.go +++ b/data/abi/abi_type.go @@ -457,3 +457,14 @@ func (t Type) ByteLen() (int, error) { return -1, fmt.Errorf("%s is a dynamic type", t.String()) } } + +// IsTransactionType checks if a type string represents a transaction type +// argument, such as "txn", "pay", "keyreg", etc. +func IsTransactionType(s string) bool { + switch s { + case "txn", "pay", "keyreg", "acfg", "axfer", "afrz", "appl": + return true + default: + return false + } +} diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index b7f8cf519..2563adf6e 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1037,17 +1037,24 @@ func MakeDryrunState(client Client, txnOrStxn interface{}, otherTxns []transacti } // MakeDryrunStateGenerated function creates generatedV2.DryrunRequest data structure -func MakeDryrunStateGenerated(client Client, txnOrStxn interface{}, otherTxns []transactions.SignedTxn, otherAccts []basics.Address, proto string) (dr generatedV2.DryrunRequest, err error) { +func MakeDryrunStateGenerated(client Client, txnOrStxnOrSlice interface{}, otherTxns []transactions.SignedTxn, otherAccts []basics.Address, proto string) (dr generatedV2.DryrunRequest, err error) { var txns []transactions.SignedTxn - if txnOrStxn == nil { - // empty input do nothing - } else if txn, ok := txnOrStxn.(transactions.Transaction); ok { - txns = append(txns, transactions.SignedTxn{Txn: txn}) - } else if stxn, ok := txnOrStxn.(transactions.SignedTxn); ok { - txns = append(txns, stxn) - } else { - err = fmt.Errorf("unsupported txn type") - return + if txnOrStxnOrSlice != nil { + switch txnType := txnOrStxnOrSlice.(type) { + case transactions.Transaction: + txns = append(txns, transactions.SignedTxn{Txn: txnType}) + case []transactions.Transaction: + for _, t := range txnType { + txns = append(txns, transactions.SignedTxn{Txn: t}) + } + case transactions.SignedTxn: + txns = append(txns, txnType) + case []transactions.SignedTxn: + txns = append(txns, txnType...) + default: + err = fmt.Errorf("unsupported txn type") + return + } } txns = append(txns, otherTxns...) diff --git a/test/scripts/e2e_subs/e2e-app-abi-add.sh b/test/scripts/e2e_subs/e2e-app-abi-add.sh deleted file mode 100755 index 4ee0494b6..000000000 --- a/test/scripts/e2e_subs/e2e-app-abi-add.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -date '+app-abi-add-test start %Y%m%d_%H%M%S' - -set -e -set -x -set -o pipefail -export SHELLOPTS - -WALLET=$1 - -# Directory of this bash program -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -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-add-example.teal --clear-prog ${TEMPDIR}/simple.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 | 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) -EXPECTED="method optIn(string)string succeeded with output: \"hello Algorand Fan\"" -if [[ $RES != *"${EXPECTED}"* ]]; then - date '+app-abi-add-test FAIL the method call to optIn(string)string should not fail %Y%m%d_%H%M%S' - false -fi - -# 1 + 2 = 3 -RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 1 --arg 2 --app-id $APPID --from $ACCOUNT 2>&1 || true) -EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 3" -if [[ $RES != *"${EXPECTED}"* ]]; then - date '+app-abi-add-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S' - false -fi - -# 18446744073709551614 + 1 = 18446744073709551615 -RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 18446744073709551614 --arg 1 --app-id $APPID --from $ACCOUNT 2>&1 || true) -EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 18446744073709551615" -if [[ $RES != *"${EXPECTED}"* ]]; then - date '+app-abi-add-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S' - false -fi - -# Close out -RES=$(${gcmd} app method --method "closeOut()string" --on-completion closeout --app-id $APPID --from $ACCOUNT 2>&1 || true) -EXPECTED="method closeOut()string succeeded with output: \"goodbye Algorand Fan\"" -if [[ $RES != *"${EXPECTED}"* ]]; then - date '+app-abi-add-test FAIL the method call to closeOut()string 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" -if [[ $RES != *"${EXPECTED}"* ]]; then - date '+app-abi-add-test FAIL the method call to delete()void should not fail %Y%m%d_%H%M%S' - false -fi diff --git a/test/scripts/e2e_subs/e2e-app-abi-method.sh b/test/scripts/e2e_subs/e2e-app-abi-method.sh new file mode 100755 index 000000000..ec3a0d71e --- /dev/null +++ b/test/scripts/e2e_subs/e2e-app-abi-method.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +date '+app-abi-method-test start %Y%m%d_%H%M%S' + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +# Directory of this bash program +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +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 }') + +# Opt in +RES=$(${gcmd} app method --method "optIn(string)string" --arg "\"Algorand Fan\"" --on-completion optin --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method optIn(string)string succeeded with output: \"hello Algorand Fan\"" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to optIn(string)string should not fail %Y%m%d_%H%M%S' + false +fi + +# 1 + 2 = 3 +RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 1 --arg 2 --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 3" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S' + false +fi + +# 18446744073709551614 + 1 = 18446744073709551615 +RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 18446744073709551614 --arg 1 --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 18446744073709551615" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S' + false +fi + +goal clerk send --from $ACCOUNT --to $ACCOUNT --amount 1000000 -o "${TEMPDIR}/pay-txn-arg.tx" + +# Payment with return true +RES=$(${gcmd} app method --method "payment(pay,uint64)bool" --arg ${TEMPDIR}/pay-txn-arg.tx --arg 1000000 --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method payment(pay,uint64)bool succeeded with output: true" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to payment(pay,uint64)bool should not fail %Y%m%d_%H%M%S' + false +fi + +# Payment with return false +RES=$(${gcmd} app method --method "payment(pay,uint64)bool" --arg ${TEMPDIR}/pay-txn-arg.tx --arg 1000001 --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method payment(pay,uint64)bool succeeded with output: false" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to payment(pay,uint64)bool should not fail %Y%m%d_%H%M%S' + false +fi + +# Close out +RES=$(${gcmd} app method --method "closeOut()string" --on-completion closeout --app-id $APPID --from $ACCOUNT 2>&1 || true) +EXPECTED="method closeOut()string succeeded with output: \"goodbye Algorand Fan\"" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to closeOut()string 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" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-abi-method-test FAIL the method call to delete()void should not fail %Y%m%d_%H%M%S' + false +fi diff --git a/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal b/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal deleted file mode 100644 index a2c816876..000000000 --- a/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal +++ /dev/null @@ -1,164 +0,0 @@ -// generated from https://gist.github.com/jasonpaulos/99e4f8a75f2fc2ec9b8073c064530359 -#pragma version 5 -txn ApplicationID -int 0 -== -bnz main_l14 -txn OnCompletion -int OptIn -== -txna ApplicationArgs 0 -byte 0xcfa68e36 -== -&& -bnz main_l13 -txn OnCompletion -int CloseOut -== -txna ApplicationArgs 0 -byte 0xa9f42b3d -== -&& -bnz main_l12 -txn OnCompletion -int DeleteApplication -== -txna ApplicationArgs 0 -byte 0x24378d3c -== -&& -bnz main_l11 -txn OnCompletion -int NoOp -== -txna ApplicationArgs 0 -byte 0xfe6bdf69 -== -&& -bnz main_l10 -txn OnCompletion -int NoOp -== -txna ApplicationArgs 0 -byte 0xa88c26a5 -== -&& -bnz main_l9 -txn OnCompletion -int NoOp -== -txna ApplicationArgs 0 -byte 0x535a47ba -== -&& -bnz main_l8 -int 0 -return -main_l8: -txna ApplicationArgs 1 -callsub sub5 -int 1 -return -main_l9: -callsub sub4 -int 1 -return -main_l10: -txna ApplicationArgs 1 -txna ApplicationArgs 2 -callsub sub3 -int 1 -return -main_l11: -callsub sub2 -int 1 -return -main_l12: -callsub sub1 -int 1 -return -main_l13: -txna ApplicationArgs 1 -callsub sub0 -int 1 -return -main_l14: -int 1 -return -sub0: // optIn -store 0 -int 0 -byte "name" -load 0 -extract 2 0 -app_local_put -byte "hello " -int 0 -byte "name" -app_local_get -concat -store 1 -byte 0x151f7c75 -load 1 -len -itob -extract 6 2 -concat -load 1 -concat -log -retsub -sub1: // closeOut -byte "goodbye " -int 0 -byte "name" -app_local_get -concat -store 2 -byte 0x151f7c75 -load 2 -len -itob -extract 6 2 -concat -load 2 -concat -log -retsub -sub2: // deleteApp -txn Sender -global CreatorAddress -== -assert -retsub -sub3: // add -store 4 -store 3 -byte 0x151f7c75 -load 3 -btoi -load 4 -btoi -+ -itob -concat -log -retsub -sub4: // empty -byte "random inconsequential log" -log -retsub -sub5: // payment -store 5 -txn GroupIndex -int 1 -- -gtxns TypeEnum -int pay -== -assert -byte 0x151f7c75 -byte 0x80 -concat -log -retsub \ No newline at end of file diff --git a/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal b/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal new file mode 100644 index 000000000..dbc831d7a --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal @@ -0,0 +1,176 @@ +// generated from https://gist.github.com/jasonpaulos/99e4f8a75f2fc2ec9b8073c064530359 +#pragma version 5 +txn ApplicationID +int 0 +== +bnz main_l14 +txn OnCompletion +int OptIn +== +txna ApplicationArgs 0 +byte 0xcfa68e36 +== +&& +bnz main_l13 +txn OnCompletion +int CloseOut +== +txna ApplicationArgs 0 +byte 0xa9f42b3d +== +&& +bnz main_l12 +txn OnCompletion +int DeleteApplication +== +txna ApplicationArgs 0 +byte 0x24378d3c +== +&& +bnz main_l11 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0xfe6bdf69 +== +&& +bnz main_l10 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0xa88c26a5 +== +&& +bnz main_l9 +txn OnCompletion +int NoOp +== +txna ApplicationArgs 0 +byte 0x3e3b3d28 +== +&& +bnz main_l8 +int 0 +return +main_l8: +txna ApplicationArgs 1 +callsub sub5 +int 1 +return +main_l9: +callsub sub4 +int 1 +return +main_l10: +txna ApplicationArgs 1 +txna ApplicationArgs 2 +callsub sub3 +int 1 +return +main_l11: +callsub sub2 +int 1 +return +main_l12: +callsub sub1 +int 1 +return +main_l13: +txna ApplicationArgs 1 +callsub sub0 +int 1 +return +main_l14: +int 1 +return +sub0: // optIn +store 0 +int 0 +byte "name" +load 0 +extract 2 0 +app_local_put +byte "hello " +int 0 +byte "name" +app_local_get +concat +store 1 +byte 0x151f7c75 +load 1 +len +itob +extract 6 2 +concat +load 1 +concat +log +retsub +sub1: // closeOut +byte "goodbye " +int 0 +byte "name" +app_local_get +concat +store 2 +byte 0x151f7c75 +load 2 +len +itob +extract 6 2 +concat +load 2 +concat +log +retsub +sub2: // deleteApp +txn Sender +global CreatorAddress +== +assert +retsub +sub3: // add +store 4 +store 3 +byte 0x151f7c75 +load 3 +btoi +load 4 +btoi ++ +itob +concat +log +retsub +sub4: // empty +byte "random inconsequential log" +log +retsub +sub5: // payment +store 5 +txn GroupIndex +int 1 +- +gtxns TypeEnum +int pay +== +assert +byte 0x151f7c75 +txn GroupIndex +int 1 +- +gtxns Amount +load 5 +btoi +== +bnz sub5_l2 +byte 0x00 +b sub5_l3 +sub5_l2: +byte 0x80 +sub5_l3: +concat +log +retsub -- cgit v1.2.3 From 9807eab88a30eceffa9e540cc3b31dcaca239691 Mon Sep 17 00:00:00 2001 From: chris erway <51567+cce@users.noreply.github.com> Date: Mon, 22 Nov 2021 12:28:24 -0500 Subject: CI: use libboost-math-dev instead of libboost-all-dev (#3223) ## Summary Small change: libboost-math-dev requires just 4 packages to install, while libboost-all-dev requires > 100. Only Debian/Ubuntu distributions provide fine-grained boost packages like this, but should shave a little time off the CI builds. (Our only boost include is boost/math/distributions/binomial.hpp.) ## Test Plan Builds should pass as before. Now that we are no longer using Travis for Linux builds, the side effect of libboost-all-dev installing make and other missing build tools on Travis encountered in #2717 is no longer a concern. --- scripts/install_linux_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install_linux_deps.sh b/scripts/install_linux_deps.sh index 453bbaf70..d016843c7 100755 --- a/scripts/install_linux_deps.sh +++ b/scripts/install_linux_deps.sh @@ -5,7 +5,7 @@ set -e DISTRIB=$ID ARCH_DEPS="boost boost-libs expect jq autoconf shellcheck sqlite python-virtualenv" -UBUNTU_DEPS="libboost-all-dev expect jq autoconf shellcheck sqlite3 python3-venv" +UBUNTU_DEPS="libboost-math-dev expect jq autoconf shellcheck sqlite3 python3-venv" FEDORA_DEPS="boost-devel expect jq autoconf ShellCheck sqlite python-virtualenv" if [ "${DISTRIB}" = "arch" ]; then -- cgit v1.2.3 From 6cb3a52417318b97c10542742fab8d21f0c54adc Mon Sep 17 00:00:00 2001 From: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Date: Tue, 23 Nov 2021 13:39:26 -0500 Subject: testing: fixes to rest-participation-key e2e test (#3238) ## Summary - Test to make sure RES has the right input before counting line numbers for result size. - Rest RES to empty so that the same output is not recycled in case of an error. - exit 1 in case of an error - Reduce LAST_ROUND from 1200000 to 120 - "Get List of Keys" before getting NUM_IDS_3 otherwise it will recycle old RES value. --- test/scripts/e2e_subs/rest-participation-key.sh | 32 +++++-------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/test/scripts/e2e_subs/rest-participation-key.sh b/test/scripts/e2e_subs/rest-participation-key.sh index 30557ff2a..e5f7a30b3 100755 --- a/test/scripts/e2e_subs/rest-participation-key.sh +++ b/test/scripts/e2e_subs/rest-participation-key.sh @@ -9,38 +9,28 @@ date "+$0 start %Y%m%d_%H%M%S" # Use admin token for both get and post export USE_ADMIN=true -pushd "${TEMPDIR}" || exit +pushd "${TEMPDIR}" || exit 1 FIRST_ROUND=0 # A really large (but arbitrary) last valid round -LAST_ROUND=1200000 +LAST_ROUND=120 NAME_OF_TEMP_PARTKEY="tmp.${FIRST_ROUND}.${LAST_ROUND}.partkey" algokey part generate --first ${FIRST_ROUND} --last ${LAST_ROUND} --keyfile ${NAME_OF_TEMP_PARTKEY} --parent ${ACCOUNT} -popd || exit +popd || exit 1 -call_and_verify "Get List of Keys" "/v2/participation" 200 'address' - -# Find out how many keys there are installed so far -NUM_IDS_1=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(len(o))') +call_and_verify "Get List of Keys" "/v2/participation" 200 'address' 'effective-first-valid' +RES="" call_post_and_verify "Install a basic participation key" "/v2/participation" 200 ${NAME_OF_TEMP_PARTKEY} 'partId' # Get the returned participation id from the RESULT (aka $RES) variable INSTALLED_ID=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(o["partId"])') # Should contain the installed id -call_and_verify "Get List of Keys" "/v2/participation" 200 'address' "${INSTALLED_ID}" - -# Get list of keys -NUM_IDS_2=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(len(o))') - -if [[ $((NUM_IDS_1 + 1)) -ne $NUM_IDS_2 ]]; then - printf "\n\nFailed test. New number of IDs (%s) is not one more than old ID count(%s)\n\n" "${NUM_IDS_2}" "${NUM_IDS_1}" - exit 1 -fi +call_and_verify "Get List of Keys" "/v2/participation" 200 'address' "${INSTALLED_ID}" 'address' 'effective-first-valid' call_and_verify "Get a specific ID" "/v2/participation/${INSTALLED_ID}" 200 "${INSTALLED_ID}" @@ -49,13 +39,3 @@ call_delete_and_verify "Delete the specific ID" "/v2/participation/${INSTALLED_I # Verify that it got called previously and now returns an error message saying that no key was found call_delete_and_verify "Delete the specific ID" "/v2/participation/${INSTALLED_ID}" 404 true 'participation id not found' - -# Get list of keys -NUM_IDS_3=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(len(o))') - -if [[ "$NUM_IDS_3" -ne "$NUM_IDS_1" ]]; then - printf "\n\nFailed test. New number of IDs (%s) is not equal to original ID count (%s)\n\n" "${NUM_IDS_3}" "${NUM_IDS_1}" - exit 1 -fi - - -- cgit v1.2.3 From 52b528b2853e32081ade545790a538454eb4f14f Mon Sep 17 00:00:00 2001 From: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:37:03 -0500 Subject: testing: interactive mode for e2e testing (#3227) ## Summary Some e2e tests require a python environment for testing. Unfortunately, setting up that environment adequately similar to the testing environment may not be trivial. This change introduces an interactive mode to the e2e.sh script which stops at the point of running the tests, and allows the user to run the tests from the same testing environment. ## Test Plan No tests needed. Tested the script locally. --- test/README.md | 9 ++------- test/scripts/e2e.sh | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/test/README.md b/test/README.md index 2d8936460..e35a21251 100644 --- a/test/README.md +++ b/test/README.md @@ -46,14 +46,9 @@ optional arguments: --version Future|vXX selects the network template file ``` -To run a specific test: +To run a specific test, run e2e.sh with -i interactive flag, and follow the instructions: ``` -~$ ./e2e_client_runner.py /full/path/to/e2e_subs/test_script.sh -``` - -Make sure to install the Algorand Python SDK before running: -``` -pip install py-algorand-sdk +test/scripts/e2e.sh -i ``` Tests in the `e2e_subs/serial` directory are executed serially instead of in parallel. This should only be used when absolutely necessary. diff --git a/test/scripts/e2e.sh b/test/scripts/e2e.sh index 9de5eaace..571d11781 100755 --- a/test/scripts/e2e.sh +++ b/test/scripts/e2e.sh @@ -24,7 +24,7 @@ Options: -n Run tests without building binaries (Binaries are expected in PATH) " NO_BUILD=false -while getopts ":c:nh" opt; do +while getopts ":c:nhi" opt; do case ${opt} in c ) CHANNEL=$OPTARG ;; @@ -33,7 +33,11 @@ while getopts ":c:nh" opt; do ;; h ) echo "${HELP}" exit 0 - ;; + ;; + i ) echo " Interactive session" + echo "######################################################################" + INTERACTIVE=true + ;; \? ) echo "${HELP}" exit 2 ;; @@ -122,6 +126,23 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then "${TEMPDIR}/ve/bin/pip3" install --upgrade py-algorand-sdk cryptography duration "e2e client setup" + if [ $INTERACTIVE ]; then + echo "********** READY **********" + echo "The test environment is now set. Run the tests using the following command on a different terminal after setting the path." + echo "" + echo "export VIRTUAL_ENV=\"${TEMPDIR}/ve\"" + echo "export PATH=\"\$VIRTUAL_ENV/bin:\$PATH\"" + echo "" + echo "${TEMPDIR}/ve/bin/python3" test/scripts/e2e_client_runner.py ${RUN_KMD_WITH_UNSAFE_SCRYPT} "$SRCROOT"/test/scripts/e2e_subs/SCRIPT_FILE_NAME + echo "" + echo "Press enter to shut down the test environment..." + read a + echo -n "deactivating..." + deactivate + echo "done" + exit + fi + "${TEMPDIR}/ve/bin/python3" e2e_client_runner.py ${RUN_KMD_WITH_UNSAFE_SCRYPT} "$SRCROOT"/test/scripts/e2e_subs/*.{sh,py} duration "parallel client runner" -- cgit v1.2.3 From 597fc630704fca33e224ad072364e1dedd22fd86 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Mon, 29 Nov 2021 18:02:53 -0500 Subject: Make dev-mode tests less flaky. (#3252) ## Summary Fix a couple flaws in the new go-e2e tests built ontop of DevMode: * Shutdown the fixture when finished. * Don't run in parallel. * Longer delays / better algorithms to wait for data flushing to complete. * Check for "out of order" keys. ## Test Plan N/A, this is a test. --- test/e2e-go/features/devmode/devmode_test.go | 5 +- .../accountParticipationTransitions_test.go | 63 +++++++++++++++++----- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/test/e2e-go/features/devmode/devmode_test.go b/test/e2e-go/features/devmode/devmode_test.go index 95801258d..ee741fb8b 100644 --- a/test/e2e-go/features/devmode/devmode_test.go +++ b/test/e2e-go/features/devmode/devmode_test.go @@ -37,12 +37,11 @@ func TestDevMode(t *testing.T) { t.Skip() } - t.Parallel() - // Start devmode network, and make sure everything is primed by sending a transaction. var fixture fixtures.RestClientFixture fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeNetwork.json")) fixture.Start() + defer fixture.Shutdown() sender, err := fixture.GetRichestAccount() require.NoError(t, err) key := crypto.GenerateSignatureSecrets(crypto.Seed{}) @@ -56,7 +55,7 @@ func TestDevMode(t *testing.T) { txn = fixture.SendMoneyAndWait(firstRound+i, 100000, 1000, sender.Address, receiver.String(), "") require.Equal(t, firstRound+i, txn.FirstRound) } - require.True(t, time.Since(start) < 2*time.Second, "Transactions should be quickly confirmed.") + require.True(t, time.Since(start) < 8*time.Second, "Transactions should be quickly confirmed faster than usual.") // Without transactions there should be no rounds even after a normal confirmation time. time.Sleep(10 * time.Second) diff --git a/test/e2e-go/features/participation/accountParticipationTransitions_test.go b/test/e2e-go/features/participation/accountParticipationTransitions_test.go index 606ae7ce7..f096859ad 100644 --- a/test/e2e-go/features/participation/accountParticipationTransitions_test.go +++ b/test/e2e-go/features/participation/accountParticipationTransitions_test.go @@ -21,11 +21,14 @@ package participation // deterministic. import ( + "fmt" "io/ioutil" "os" "path/filepath" "testing" + "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" @@ -75,12 +78,21 @@ func TestKeyRegistration(t *testing.T) { t.Skip() } - t.Parallel() + checkKey := func(key generated.ParticipationKey, firstValid, lastValid, lastProposal uint64, msg string) { + require.NotNil(t, key.EffectiveFirstValid, fmt.Sprintf("%s.EffectiveFirstValid", msg)) + require.NotNil(t, key.EffectiveLastValid, fmt.Sprintf("%s.EffectiveLastValid", msg)) + require.NotNil(t, key.LastBlockProposal, fmt.Sprintf("%s.LastBlockProposal", msg)) + + assert.Equal(t, int(*(key.EffectiveFirstValid)), int(firstValid), fmt.Sprintf("%s.EffectiveFirstValid", msg)) + assert.Equal(t, int(*(key.EffectiveLastValid)), int(lastValid), fmt.Sprintf("%s.EffectiveLastValid", msg)) + assert.Equal(t, int(*(key.LastBlockProposal)), int(lastProposal), fmt.Sprintf("%s.LastBlockProposal", msg)) + } // Start devmode network and initialize things for the test. var fixture fixtures.RestClientFixture fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeOneWallet.json")) fixture.Start() + defer fixture.Shutdown() sClient := fixture.GetLibGoalClientForNamedNode("Node") minTxnFee, _, err := fixture.MinFeeAndBalance(0) require.NoError(t, err) @@ -92,7 +104,7 @@ func TestKeyRegistration(t *testing.T) { last := uint64(6_000_000) numNew := 2 for i := 0; i < numNew; i++ { - response, part, err := installParticipationKey(t, sClient, sAccount, 0, last) + response, part, err := installParticipationKey(t, sClient, sAccount, 0, last+uint64(i)) require.NoError(t, err) require.NotNil(t, response) registerParticipationAndWait(t, sClient, part) @@ -111,16 +123,41 @@ func TestKeyRegistration(t *testing.T) { fixture.SendMoneyAndWait(2+i, 0, minTxnFee, sAccount, sAccount, "") } - keys, err = fixture.LibGoalClient.GetParticipationKeys() - require.Equal(t, *(keys[0].EffectiveFirstValid), uint64(1)) - require.Equal(t, *(keys[0].EffectiveLastValid), lookback) - require.Equal(t, *(keys[0].LastBlockProposal), lookback) - - require.Equal(t, *(keys[1].EffectiveFirstValid), lookback+1) - require.Equal(t, *(keys[1].EffectiveLastValid), lookback+1) - require.Equal(t, *(keys[1].LastBlockProposal), lookback+1) + // Wait until data has been persisted + ready := false + waitfor := time.After(1 * time.Minute) + for !ready { + select { + case <-waitfor: + ready = true + default: + keys, err = fixture.LibGoalClient.GetParticipationKeys() + ready = (len(keys) >= 3) && + (keys[2].LastBlockProposal != nil) && + (keys[2].EffectiveFirstValid != nil) && + (keys[2].EffectiveLastValid != nil) && + (keys[1].LastBlockProposal != nil) && + (keys[1].EffectiveFirstValid != nil) && + (keys[1].EffectiveLastValid != nil) && + (keys[0].LastBlockProposal != nil) && + (keys[0].EffectiveFirstValid != nil) && + (keys[0].EffectiveLastValid != nil) + if !ready { + time.Sleep(100 * time.Millisecond) + } + } + } - require.Equal(t, *(keys[2].EffectiveFirstValid), lookback+2) - require.Equal(t, *(keys[2].EffectiveLastValid), last) - require.Equal(t, *(keys[2].LastBlockProposal), lookback+2) + // Verify results, order may vary, key off of the last valid field + require.Len(t, keys, 3) + for _, k := range keys { + switch k.Key.VoteLastValid { + case 3_000_000: + checkKey(k, 1, lookback, lookback, "keys[0]") + case last: + checkKey(k, lookback+1, lookback+1, lookback+1, "keys[1]") + case last + 1: + checkKey(k, lookback+2, last+1, lookback+2, "keys[2]") + } + } } -- cgit v1.2.3 From 5339cf0da179900ee380c8a95ae20d1265ff5297 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 29 Nov 2021 18:10:04 -0500 Subject: adding libtool to ubuntu deps (#3251) ## Summary The sandbox is not building with dev config using master branch https://github.com/algorand/sandbox/issues/85, complains about libtool not being installed Guessing from this change https://github.com/algorand/go-algorand/pull/3223 Adding libtool to UBUNTU_DEPS in install scripts ## Test Plan Set config in sandbox to my branch `sandbox up dev` It built --- scripts/install_linux_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install_linux_deps.sh b/scripts/install_linux_deps.sh index d016843c7..78c42cd35 100755 --- a/scripts/install_linux_deps.sh +++ b/scripts/install_linux_deps.sh @@ -5,7 +5,7 @@ set -e DISTRIB=$ID ARCH_DEPS="boost boost-libs expect jq autoconf shellcheck sqlite python-virtualenv" -UBUNTU_DEPS="libboost-math-dev expect jq autoconf shellcheck sqlite3 python3-venv" +UBUNTU_DEPS="libtool libboost-math-dev expect jq autoconf shellcheck sqlite3 python3-venv" FEDORA_DEPS="boost-devel expect jq autoconf ShellCheck sqlite python-virtualenv" if [ "${DISTRIB}" = "arch" ]; then -- cgit v1.2.3 From 715f511146da1e4518abea8ade9f100404a940fd Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 30 Nov 2021 14:15:36 -0500 Subject: Fix error shadowing in Eval (#3258) ## Summary Error from account preloading was shadowed by returning a wrong err variable. This caused subsequent problems in account updates and masked the original failure. ## Test Plan Use existing tests --- ledger/internal/eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go index 264e8d1df..1cb050bf6 100644 --- a/ledger/internal/eval.go +++ b/ledger/internal/eval.go @@ -1403,7 +1403,7 @@ transactionGroupLoop: if !ok { break transactionGroupLoop } else if txgroup.err != nil { - return ledgercore.StateDelta{}, err + return ledgercore.StateDelta{}, txgroup.err } for _, br := range txgroup.balances { -- cgit v1.2.3 From 54db1c7f0954802fdf0b1db35fdc4f3deadede79 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 30 Nov 2021 14:32:21 -0500 Subject: Disable flaky test. (#3256) ## Summary This test doesn't work properly, disable it until #3255 addresses any underlying problems. --- .../features/participation/accountParticipationTransitions_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e-go/features/participation/accountParticipationTransitions_test.go b/test/e2e-go/features/participation/accountParticipationTransitions_test.go index f096859ad..5b2c3ea0c 100644 --- a/test/e2e-go/features/participation/accountParticipationTransitions_test.go +++ b/test/e2e-go/features/participation/accountParticipationTransitions_test.go @@ -73,6 +73,7 @@ func registerParticipationAndWait(t *testing.T, client libgoal.Client, part acco func TestKeyRegistration(t *testing.T) { partitiontest.PartitionTest(t) + t.Skipf("Skipping flaky test. Re-enable with #3255") if testing.Short() { t.Skip() -- cgit v1.2.3 From 305d7ab6bece6f78608f0c282db1f728454ef79d Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Tue, 30 Nov 2021 20:58:14 +0000 Subject: Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildnumber.dat b/buildnumber.dat index 573541ac9..d00491fd7 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -0 +1 -- cgit v1.2.3