summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Lee <64482439+algojohnlee@users.noreply.github.com>2021-12-02 11:25:13 -0500
committerGitHub <noreply@github.com>2021-12-02 11:25:13 -0500
commit2baf39b72b6db24c9e0cbdecfe8325dcfffaa30c (patch)
treec5395e40afe20937e23bdb51ff433c9e19047a72
parent8aa0728a8be0fe56402b1b1a9007014d8bb39183 (diff)
parent305d7ab6bece6f78608f0c282db1f728454ef79d (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.dat2
-rw-r--r--cmd/goal/application.go179
-rw-r--r--cmd/goal/clerk.go50
-rw-r--r--data/abi/abi_encode.go85
-rw-r--r--data/abi/abi_type.go11
-rw-r--r--ledger/internal/eval.go2
-rw-r--r--libgoal/libgoal.go27
-rwxr-xr-xscripts/install_linux_deps.sh2
-rw-r--r--scripts/release/mule/Makefile.mule4
-rw-r--r--test/README.md9
-rw-r--r--test/e2e-go/features/devmode/devmode_test.go5
-rw-r--r--test/e2e-go/features/participation/accountParticipationTransitions_test.go64
-rwxr-xr-xtest/scripts/e2e.sh25
-rwxr-xr-xtest/scripts/e2e_subs/e2e-app-abi-add.sh39
-rwxr-xr-xtest/scripts/e2e_subs/e2e-app-abi-method.sh79
-rwxr-xr-xtest/scripts/e2e_subs/rest-participation-key.sh32
-rw-r--r--test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal87
-rw-r--r--test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal176
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