diff options
author | John Lee <64482439+algojohnlee@users.noreply.github.com> | 2021-12-02 11:25:13 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-02 11:25:13 -0500 |
commit | 2baf39b72b6db24c9e0cbdecfe8325dcfffaa30c (patch) | |
tree | c5395e40afe20937e23bdb51ff433c9e19047a72 | |
parent | 8aa0728a8be0fe56402b1b1a9007014d8bb39183 (diff) | |
parent | 305d7ab6bece6f78608f0c282db1f728454ef79d (diff) |
Merge pull request #3260 from Algo-devops-service/relbeta3.2.1v3.2.1-beta
go-algorand 3.2.1-beta
-rw-r--r-- | buildnumber.dat | 2 | ||||
-rw-r--r-- | cmd/goal/application.go | 179 | ||||
-rw-r--r-- | cmd/goal/clerk.go | 50 | ||||
-rw-r--r-- | data/abi/abi_encode.go | 85 | ||||
-rw-r--r-- | data/abi/abi_type.go | 11 | ||||
-rw-r--r-- | ledger/internal/eval.go | 2 | ||||
-rw-r--r-- | libgoal/libgoal.go | 27 | ||||
-rwxr-xr-x | scripts/install_linux_deps.sh | 2 | ||||
-rw-r--r-- | scripts/release/mule/Makefile.mule | 4 | ||||
-rw-r--r-- | test/README.md | 9 | ||||
-rw-r--r-- | test/e2e-go/features/devmode/devmode_test.go | 5 | ||||
-rw-r--r-- | test/e2e-go/features/participation/accountParticipationTransitions_test.go | 64 | ||||
-rwxr-xr-x | test/scripts/e2e.sh | 25 | ||||
-rwxr-xr-x | test/scripts/e2e_subs/e2e-app-abi-add.sh | 39 | ||||
-rwxr-xr-x | test/scripts/e2e_subs/e2e-app-abi-method.sh | 79 | ||||
-rwxr-xr-x | test/scripts/e2e_subs/rest-participation-key.sh | 32 | ||||
-rw-r--r-- | test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal | 87 | ||||
-rw-r--r-- | test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal | 176 |
18 files changed, 596 insertions, 282 deletions
diff --git a/buildnumber.dat b/buildnumber.dat index 573541ac9..d00491fd7 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -0 +1 diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 2ed7eed2d..49a2ce9c5 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -53,7 +53,7 @@ var ( extraPages uint32 - createOnCompletion string + onCompletion string localSchemaUints uint64 localSchemaByteSlices uint64 @@ -106,7 +106,7 @@ func init() { createAppCmd.Flags().Uint64Var(&localSchemaUints, "local-ints", 0, "Maximum number of integer values that may be stored in local (per-account) key/value stores for this app. Immutable.") createAppCmd.Flags().Uint64Var(&localSchemaByteSlices, "local-byteslices", 0, "Maximum number of byte slices that may be stored in local (per-account) key/value stores for this app. Immutable.") createAppCmd.Flags().StringVar(&appCreator, "creator", "", "Account to create the application") - createAppCmd.Flags().StringVar(&createOnCompletion, "on-completion", "NoOp", "OnCompletion action for application transaction") + createAppCmd.Flags().StringVar(&onCompletion, "on-completion", "NoOp", "OnCompletion action for application transaction") createAppCmd.Flags().Uint32Var(&extraPages, "extra-pages", 0, "Additional program space for supporting larger TEAL assembly program. A maximum of 3 extra pages is allowed. A page is 1024 bytes.") callAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to call app from") @@ -120,6 +120,7 @@ func init() { methodAppCmd.Flags().StringVar(&method, "method", "", "Method to be called") methodAppCmd.Flags().StringArrayVar(&methodArgs, "arg", nil, "Args to pass in for calling a method") + methodAppCmd.Flags().StringVar(&onCompletion, "on-completion", "NoOp", "OnCompletion action for application transaction") // Can't use PersistentFlags on the root because for some reason marking // a root command as required with MarkPersistentFlagRequired isn't @@ -432,15 +433,15 @@ var createAppCmd = &cobra.Command{ // Parse transaction parameters approvalProg, clearProg := mustParseProgArgs() - onCompletion := mustParseOnCompletion(createOnCompletion) + onCompletionEnum := mustParseOnCompletion(onCompletion) appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs() - switch onCompletion { + switch onCompletionEnum { case transactions.CloseOutOC, transactions.ClearStateOC: - reportWarnf("'--on-completion %s' may be ill-formed for 'goal app create'", createOnCompletion) + reportWarnf("'--on-completion %s' may be ill-formed for 'goal app create'", onCompletion) } - tx, err := client.MakeUnsignedAppCreateTx(onCompletion, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, extraPages) + tx, err := client.MakeUnsignedAppCreateTx(onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, appArgs, appAccounts, foreignApps, foreignAssets, extraPages) if err != nil { reportErrorf("Cannot create application txn: %v", err) } @@ -1046,6 +1047,43 @@ var infoAppCmd = &cobra.Command{ }, } +// populateMethodCallTxnArgs parses and loads transactions from the files indicated by the values +// slice. An error will occur if the transaction does not matched the expected type, it has a nonzero +// group ID, or if it is signed by a normal signature or Msig signature (but not Lsig signature) +func populateMethodCallTxnArgs(types []string, values []string) ([]transactions.SignedTxn, error) { + loadedTxns := make([]transactions.SignedTxn, len(values)) + + for i, txFilename := range values { + data, err := readFile(txFilename) + if err != nil { + return nil, fmt.Errorf(fileReadError, txFilename, err) + } + + var txn transactions.SignedTxn + err = protocol.Decode(data, &txn) + if err != nil { + return nil, fmt.Errorf(txDecodeError, txFilename, err) + } + + if !txn.Sig.Blank() || !txn.Msig.Blank() { + return nil, fmt.Errorf("Transaction from %s has already been signed", txFilename) + } + + if !txn.Txn.Group.IsZero() { + return nil, fmt.Errorf("Transaction from %s already has a group ID: %s", txFilename, txn.Txn.Group) + } + + expectedType := types[i] + if expectedType != "txn" && txn.Txn.Type != protocol.TxType(expectedType) { + return nil, fmt.Errorf("Transaction from %s does not match method argument type. Expected %s, got %s", txFilename, expectedType, txn.Txn.Type) + } + + loadedTxns[i] = txn + } + + return loadedTxns, nil +} + var methodAppCmd = &cobra.Command{ Use: "method", Short: "Invoke a method", @@ -1060,14 +1098,14 @@ var methodAppCmd = &cobra.Command{ reportErrorf("in goal app method: --arg and --app-arg are mutually exclusive, do not use --app-arg") } - onCompletion := mustParseOnCompletion(createOnCompletion) + onCompletionEnum := mustParseOnCompletion(onCompletion) if appIdx == 0 { reportErrorf("app id == 0, goal app create not supported in goal app method") } var approvalProg, clearProg []byte - if onCompletion == transactions.UpdateApplicationOC { + if onCompletionEnum == transactions.UpdateApplicationOC { approvalProg, clearProg = mustParseProgArgs() } @@ -1078,26 +1116,60 @@ var methodAppCmd = &cobra.Command{ applicationArgs = append(applicationArgs, hash[0:4]) // parse down the ABI type from method signature - argTupleTypeStr, retTypeStr, err := abi.ParseMethodSignature(method) + _, argTypes, retTypeStr, err := abi.ParseMethodSignature(method) if err != nil { reportErrorf("cannot parse method signature: %v", err) } - err = abi.ParseArgJSONtoByteSlice(argTupleTypeStr, methodArgs, &applicationArgs) + + var retType *abi.Type + if retTypeStr != "void" { + theRetType, err := abi.TypeOf(retTypeStr) + if err != nil { + reportErrorf("cannot cast %s to abi type: %v", retTypeStr, err) + } + retType = &theRetType + } + + if len(methodArgs) != len(argTypes) { + reportErrorf("incorrect number of arguments, method expected %d but got %d", len(argTypes), len(methodArgs)) + } + + var txnArgTypes []string + var txnArgValues []string + var basicArgTypes []string + var basicArgValues []string + for i, argType := range argTypes { + argValue := methodArgs[i] + if abi.IsTransactionType(argType) { + txnArgTypes = append(txnArgTypes, argType) + txnArgValues = append(txnArgValues, argValue) + } else { + basicArgTypes = append(basicArgTypes, argType) + basicArgValues = append(basicArgValues, argValue) + } + } + + err = abi.ParseArgJSONtoByteSlice(basicArgTypes, basicArgValues, &applicationArgs) if err != nil { reportErrorf("cannot parse arguments to ABI encoding: %v", err) } - tx, err := client.MakeUnsignedApplicationCallTx( + txnArgs, err := populateMethodCallTxnArgs(txnArgTypes, txnArgValues) + if err != nil { + reportErrorf("error populating transaction arguments: %v", err) + } + + appCallTxn, err := client.MakeUnsignedApplicationCallTx( appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets, - onCompletion, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0) + onCompletionEnum, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0) if err != nil { reportErrorf("Cannot create application txn: %v", err) } // Fill in note and lease - tx.Note = parseNoteField(cmd) - tx.Lease = parseLease(cmd) + appCallTxn.Note = parseNoteField(cmd) + appCallTxn.Lease = parseLease(cmd) // Fill in rounds, fee, etc. fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) @@ -1105,23 +1177,65 @@ var methodAppCmd = &cobra.Command{ reportErrorf("Cannot determine last valid round: %s", err) } - tx, err = client.FillUnsignedTxTemplate(account, fv, lv, fee, tx) + appCallTxn, err = client.FillUnsignedTxTemplate(account, fv, lv, fee, appCallTxn) if err != nil { reportErrorf("Cannot construct transaction: %s", err) } explicitFee := cmd.Flags().Changed("fee") if explicitFee { - tx.Fee = basics.MicroAlgos{Raw: fee} + appCallTxn.Fee = basics.MicroAlgos{Raw: fee} + } + + // Compile group + var txnGroup []transactions.Transaction + for i := range txnArgs { + txnGroup = append(txnGroup, txnArgs[i].Txn) + } + txnGroup = append(txnGroup, appCallTxn) + if len(txnGroup) > 1 { + // Only if transaction arguments are present, assign group ID + groupID, err := client.GroupID(txnGroup) + if err != nil { + reportErrorf("Cannot assign transaction group ID: %s", err) + } + for i := range txnGroup { + txnGroup[i].Group = groupID + } } + // Sign transactions + var signedTxnGroup []transactions.SignedTxn + shouldSign := sign || outFilename == "" + for i, unsignedTxn := range txnGroup { + txnFromArgs := transactions.SignedTxn{} + if i < len(txnArgs) { + txnFromArgs = txnArgs[i] + } + + if !txnFromArgs.Lsig.Blank() { + signedTxnGroup = append(signedTxnGroup, transactions.SignedTxn{ + Lsig: txnFromArgs.Lsig, + AuthAddr: txnFromArgs.AuthAddr, + Txn: unsignedTxn, + }) + continue + } + + signedTxn, err := createSignedTransaction(client, shouldSign, dataDir, walletName, unsignedTxn, txnFromArgs.AuthAddr) + if err != nil { + reportErrorf(errorSigningTX, err) + } + + signedTxnGroup = append(signedTxnGroup, signedTxn) + } + + // Output to file if outFilename != "" { if dumpForDryrun { - err = writeDryrunReqToFile(client, tx, outFilename) + err = writeDryrunReqToFile(client, signedTxnGroup, outFilename) } else { - // Write transaction to file - err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename) + err = writeSignedTxnsToFile(signedTxnGroup, outFilename) } - if err != nil { reportErrorf(err.Error()) } @@ -1129,19 +1243,19 @@ var methodAppCmd = &cobra.Command{ } // Broadcast - wh, pw := ensureWalletHandleMaybePassword(dataDir, walletName, true) - signedTxn, err := client.SignTransactionWithWallet(wh, pw, tx) - if err != nil { - reportErrorf(errorSigningTX, err) - } - - txid, err := client.BroadcastTransaction(signedTxn) + err = client.BroadcastTransactionGroup(signedTxnGroup) if err != nil { reportErrorf(errorBroadcastingTX, err) } // Report tx details to user - reportInfof("Issued transaction from account %s, txid %s (fee %d)", tx.Sender, txid, tx.Fee.Raw) + reportInfof("Issued %d transaction(s):", len(signedTxnGroup)) + // remember the final txid in this variable + var txid string + for _, stxn := range signedTxnGroup { + txid = stxn.Txn.ID().String() + reportInfof("\tIssued transaction from account %s, txid %s (fee %d)", stxn.Txn.Sender, txid, stxn.Txn.Fee.Raw) + } if !noWaitAfterSend { _, err := waitForCommit(client, txid, lv) @@ -1154,7 +1268,8 @@ var methodAppCmd = &cobra.Command{ reportErrorf(err.Error()) } - if retTypeStr == "void" { + if retType == nil { + fmt.Printf("method %s succeeded\n", method) return } @@ -1179,10 +1294,6 @@ var methodAppCmd = &cobra.Command{ reportErrorf("cannot find return log for abi type %s", retTypeStr) } - retType, err := abi.TypeOf(retTypeStr) - if err != nil { - reportErrorf("cannot cast %s to abi type: %v", retTypeStr, err) - } decoded, err := retType.Decode(abiEncodedRet) if err != nil { reportErrorf("cannot decode return value %v: %v", abiEncodedRet, err) @@ -1192,7 +1303,7 @@ var methodAppCmd = &cobra.Command{ if err != nil { reportErrorf("cannot marshal returned bytes %v to JSON: %v", decoded, err) } - fmt.Printf("method %s output: %s\n", method, string(decodedJSON)) + fmt.Printf("method %s succeeded with output: %s\n", method, string(decodedJSON)) } }, } 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/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 { 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/scripts/install_linux_deps.sh b/scripts/install_linux_deps.sh index 453bbaf70..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-all-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 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 && \ 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/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..5b2c3ea0c 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" @@ -70,17 +73,27 @@ 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() } - 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 +105,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 +124,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]") + } + } } 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" 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 60e1c1f3e..000000000 --- a/test/scripts/e2e_subs/e2e-app-abi-add.sh +++ /dev/null @@ -1,39 +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}" - -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 }') - -# Should succeed to opt in -${gcmd} app optin --app-id $APPID --from $ACCOUNT - -# Call should now succeed -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" -if [[ $RES != *"${EXPECTED}"* ]]; then - date '+app-abi-add-test FAIL the application creation should not fail %Y%m%d_%H%M%S' - false -fi - -# Delete application should still succeed -${gcmd} app delete --app-id $APPID --from $ACCOUNT - -# Clear should still succeed -${gcmd} app clear --app-id $APPID --from $ACCOUNT 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/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 - - 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 18d3b3e6e..000000000 --- a/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal +++ /dev/null @@ -1,87 +0,0 @@ -#pragma version 5 -intcblock 1 0 -bytecblock 0x151f7c75 -txn ApplicationID -intc_1 // 0 -== -bnz main_l12 -txn OnCompletion -intc_0 // OptIn -== -bnz main_l11 -txn OnCompletion -pushint 5 // DeleteApplication -== -bnz main_l10 -txn OnCompletion -intc_1 // NoOp -== -txna ApplicationArgs 0 -pushbytes 0xfe6bdf69 // 0xfe6bdf69 -== -&& -bnz main_l9 -txn OnCompletion -intc_1 // NoOp -== -txna ApplicationArgs 0 -pushbytes 0xa88c26a5 // 0xa88c26a5 -== -&& -bnz main_l8 -txn OnCompletion -intc_1 // NoOp -== -txna ApplicationArgs 0 -pushbytes 0x535a47ba // 0x535a47ba -== -&& -bnz main_l7 -intc_1 // 0 -return -main_l7: -txna ApplicationArgs 1 -callsub sub2 -intc_0 // 1 -return -main_l8: -callsub sub1 -intc_0 // 1 -return -main_l9: -txna ApplicationArgs 1 -txna ApplicationArgs 2 -callsub sub0 -intc_0 // 1 -return -main_l10: -intc_0 // 1 -return -main_l11: -intc_0 // 1 -return -main_l12: -intc_0 // 1 -return -sub0: // add -store 1 -store 0 -bytec_0 // 0x151f7c75 -load 0 -btoi -load 1 -btoi -+ -itob -concat -log -retsub -sub1: // empty -bytec_0 // 0x151f7c75 -log -retsub -sub2: // payment -store 2 -pushbytes 0x151f7c7580 // 0x151f7c7580 -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 |