diff --git a/cmd/goal/application.go b/cmd/goal/application.go
index da142e3e08..c92bab2b91 100644
--- a/cmd/goal/application.go
+++ b/cmd/goal/application.go
@@ -17,6 +17,8 @@
package main
import (
+ "bytes"
+ "crypto/sha512"
"encoding/base32"
"encoding/base64"
"encoding/binary"
@@ -28,9 +30,11 @@ import (
"github.com/spf13/cobra"
"github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/abi"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/libgoal"
"github.com/algorand/go-algorand/protocol"
)
@@ -41,6 +45,9 @@ var (
approvalProgFile string
clearProgFile string
+ method string
+ methodArgs []string
+
approvalProgRawFile string
clearProgRawFile string
@@ -79,9 +86,10 @@ func init() {
appCmd.AddCommand(clearAppCmd)
appCmd.AddCommand(readStateAppCmd)
appCmd.AddCommand(infoAppCmd)
+ appCmd.AddCommand(methodAppCmd)
appCmd.PersistentFlags().StringVarP(&walletName, "wallet", "w", "", "Set the wallet to be used for the selected operation")
- appCmd.PersistentFlags().StringSliceVar(&appArgs, "app-arg", nil, "Args to encode for application transactions (all will be encoded to a byte slice). For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.")
+ appCmd.PersistentFlags().StringArrayVar(&appArgs, "app-arg", nil, "Args to encode for application transactions (all will be encoded to a byte slice). For ints, use the form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'.")
appCmd.PersistentFlags().StringSliceVar(&foreignApps, "foreign-app", nil, "Indexes of other apps whose global state is read in this transaction")
appCmd.PersistentFlags().StringSliceVar(&foreignAssets, "foreign-asset", nil, "Indexes of assets whose parameters are read in this transaction")
appCmd.PersistentFlags().StringSliceVar(&appStrAccounts, "app-account", nil, "Accounts that may be accessed from application logic")
@@ -108,6 +116,10 @@ func init() {
deleteAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to send delete transaction from")
readStateAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to fetch state from")
updateAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to send update transaction from")
+ methodAppCmd.Flags().StringVarP(&account, "from", "f", "", "Account to call method from")
+
+ methodAppCmd.Flags().StringVar(&method, "method", "", "Method to be called")
+ methodAppCmd.Flags().StringArrayVar(&methodArgs, "arg", nil, "Args to pass in for calling a method")
// Can't use PersistentFlags on the root because for some reason marking
// a root command as required with MarkPersistentFlagRequired isn't
@@ -120,6 +132,7 @@ func init() {
readStateAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
updateAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
infoAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
+ methodAppCmd.Flags().Uint64Var(&appIdx, "app-id", 0, "Application ID")
// Add common transaction flags to all txn-generating app commands
addTxnFlags(createAppCmd)
@@ -129,6 +142,7 @@ func init() {
addTxnFlags(optInAppCmd)
addTxnFlags(closeOutAppCmd)
addTxnFlags(clearAppCmd)
+ addTxnFlags(methodAppCmd)
readStateAppCmd.Flags().BoolVar(&fetchLocal, "local", false, "Fetch account-specific state for this application. `--from` address is required when using this flag")
readStateAppCmd.Flags().BoolVar(&fetchGlobal, "global", false, "Fetch global state for this application.")
@@ -161,6 +175,13 @@ func init() {
readStateAppCmd.MarkFlagRequired("app-id")
infoAppCmd.MarkFlagRequired("app-id")
+
+ methodAppCmd.MarkFlagRequired("method") // nolint:errcheck // follow previous required flag format
+ methodAppCmd.MarkFlagRequired("app-id") // nolint:errcheck
+ methodAppCmd.MarkFlagRequired("from") // nolint:errcheck
+ methodAppCmd.Flags().MarkHidden("app-arg") // nolint:errcheck
+ methodAppCmd.Flags().MarkHidden("app-input") // nolint:errcheck
+ methodAppCmd.Flags().MarkHidden("i") // nolint:errcheck
}
type appCallArg struct {
@@ -229,6 +250,23 @@ func parseAppArg(arg appCallArg) (rawValue []byte, parseErr error) {
return
}
rawValue = data
+ case "abi":
+ typeAndValue := strings.SplitN(arg.Value, ":", 2)
+ if len(typeAndValue) != 2 {
+ parseErr = fmt.Errorf("Could not decode abi string (%s): should split abi-type and abi-value with colon", arg.Value)
+ return
+ }
+ abiType, err := abi.TypeOf(typeAndValue[0])
+ if err != nil {
+ parseErr = fmt.Errorf("Could not decode abi type string (%s): %v", typeAndValue[0], err)
+ return
+ }
+ value, err := abiType.UnmarshalFromJSON([]byte(typeAndValue[1]))
+ if err != nil {
+ parseErr = fmt.Errorf("Could not decode abi value string (%s):%v ", typeAndValue[1], err)
+ return
+ }
+ return abiType.Encode(value)
default:
parseErr = fmt.Errorf("Unknown encoding: %s", arg.Encoding)
}
@@ -266,6 +304,20 @@ func processAppInputFile() (args [][]byte, accounts []string, foreignApps []uint
return parseAppInputs(inputs)
}
+// filterEmptyStrings filters out empty string parsed in by StringArrayVar
+// this function is added to support abi argument parsing
+// since parsing of `appArg` diverted from `StringSliceVar` to `StringArrayVar`
+func filterEmptyStrings(strSlice []string) []string {
+ var newStrSlice []string
+
+ for _, str := range strSlice {
+ if len(str) > 0 {
+ newStrSlice = append(newStrSlice, str)
+ }
+ }
+ return newStrSlice
+}
+
func getAppInputs() (args [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64) {
if (appArgs != nil || appStrAccounts != nil || foreignApps != nil) && appInputFilename != "" {
reportErrorf("Cannot specify both command-line arguments/accounts and JSON input filename")
@@ -275,7 +327,11 @@ func getAppInputs() (args [][]byte, accounts []string, foreignApps []uint64, for
}
var encodedArgs []appCallArg
- for _, arg := range appArgs {
+
+ // we need to filter out empty strings from appArgs first, caused by change to `StringArrayVar`
+ newAppArgs := filterEmptyStrings(appArgs)
+
+ for _, arg := range newAppArgs {
encodingValue := strings.SplitN(arg, ":", 2)
if len(encodingValue) != 2 {
reportErrorf("all arguments should be of the form 'encoding:value'")
@@ -327,6 +383,12 @@ func mustParseOnCompletion(ocString string) (oc transactions.OnCompletion) {
}
}
+func getDataDirAndClient() (dataDir string, client libgoal.Client) {
+ dataDir = ensureSingleDataDir()
+ client = ensureFullClient(dataDir)
+ return
+}
+
func mustParseProgArgs() (approval []byte, clear []byte) {
// Ensure we don't have ambiguous or all empty args
if (approvalProgFile == "") == (approvalProgRawFile == "") {
@@ -357,9 +419,7 @@ var createAppCmd = &cobra.Command{
Long: `Issue a transaction that creates an application`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
-
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Construct schemas from args
localSchema := basics.StateSchema{
@@ -451,8 +511,7 @@ var updateAppCmd = &cobra.Command{
Long: `Issue a transaction that updates an application's ApprovalProgram and ClearStateProgram`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
approvalProg, clearProg := mustParseProgArgs()
@@ -523,8 +582,7 @@ var optInAppCmd = &cobra.Command{
Long: `Opt an account in to an application, allocating local state in your account`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -594,8 +652,7 @@ var closeOutAppCmd = &cobra.Command{
Long: `Close an account out of an application, removing local state from your account. The application must still exist. If it doesn't, use 'goal app clear'.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -665,8 +722,7 @@ var clearAppCmd = &cobra.Command{
Long: `Remove any local state from your account associated with an application. The application does not need to exist anymore.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -736,8 +792,7 @@ var callAppCmd = &cobra.Command{
Long: `Call an application, invoking application-specific functionality`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -807,8 +862,7 @@ var deleteAppCmd = &cobra.Command{
Long: `Delete an application, removing the global state and other application parameters from the creator's account`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ dataDir, client := getDataDirAndClient()
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets := getAppInputs()
@@ -879,8 +933,7 @@ var readStateAppCmd = &cobra.Command{
Long: `Read global or local (account-specific) state for an application`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ _, client := getDataDirAndClient()
// Ensure exactly one of --local or --global is specified
if fetchLocal == fetchGlobal {
@@ -961,8 +1014,7 @@ var infoAppCmd = &cobra.Command{
Long: `Look up application information stored on the network, such as program hash.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, _ []string) {
- dataDir := ensureSingleDataDir()
- client := ensureFullClient(dataDir)
+ _, client := getDataDirAndClient()
meta, err := client.ApplicationInformation(appIdx)
if err != nil {
@@ -995,3 +1047,140 @@ var infoAppCmd = &cobra.Command{
}
},
}
+
+var methodAppCmd = &cobra.Command{
+ Use: "method",
+ Short: "Invoke a method",
+ Long: `Invoke a method in an App (stateful contract) with an application call transaction`,
+ Args: validateNoPosArgsFn,
+ Run: func(cmd *cobra.Command, args []string) {
+ dataDir, client := getDataDirAndClient()
+
+ // Parse transaction parameters
+ appArgsParsed, appAccounts, foreignApps, foreignAssets := getAppInputs()
+ if len(appArgsParsed) > 0 {
+ reportErrorf("in goal app method: --arg and --app-arg are mutually exclusive, do not use --app-arg")
+ }
+
+ onCompletion := mustParseOnCompletion(createOnCompletion)
+
+ if appIdx == 0 {
+ reportErrorf("app id == 0, goal app create not supported in goal app method")
+ }
+
+ var approvalProg, clearProg []byte
+ if onCompletion == transactions.UpdateApplicationOC {
+ approvalProg, clearProg = mustParseProgArgs()
+ }
+
+ var applicationArgs [][]byte
+
+ // insert the method selector hash
+ hash := sha512.Sum512_256([]byte(method))
+ applicationArgs = append(applicationArgs, hash[0:4])
+
+ // parse down the ABI type from method signature
+ argTupleTypeStr, retTypeStr, err := abi.ParseMethodSignature(method)
+ if err != nil {
+ reportErrorf("cannot parse method signature: %v", err)
+ }
+ err = abi.ParseArgJSONtoByteSlice(argTupleTypeStr, methodArgs, &applicationArgs)
+ if err != nil {
+ reportErrorf("cannot parse arguments to ABI encoding: %v", err)
+ }
+
+ tx, err := client.MakeUnsignedApplicationCallTx(
+ appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets,
+ onCompletion, 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)
+
+ // Fill in rounds, fee, etc.
+ fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds)
+ if err != nil {
+ reportErrorf("Cannot determine last valid round: %s", err)
+ }
+
+ tx, err = client.FillUnsignedTxTemplate(account, fv, lv, fee, tx)
+ if err != nil {
+ reportErrorf("Cannot construct transaction: %s", err)
+ }
+ explicitFee := cmd.Flags().Changed("fee")
+ if explicitFee {
+ tx.Fee = basics.MicroAlgos{Raw: fee}
+ }
+
+ // 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)
+ 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)
+
+ if !noWaitAfterSend {
+ _, err := waitForCommit(client, txid, lv)
+ if err != nil {
+ reportErrorf(err.Error())
+ }
+
+ resp, err := client.PendingTransactionInformationV2(txid)
+ if err != nil {
+ reportErrorf(err.Error())
+ }
+
+ if retTypeStr == "void" {
+ return
+ }
+
+ // specify the return hash prefix
+ hashRet := sha512.Sum512_256([]byte("return"))
+ hashRetPrefix := hashRet[:4]
+
+ var abiEncodedRet []byte
+ foundRet := false
+ if resp.Logs != nil {
+ for i := len(*resp.Logs) - 1; i >= 0; i-- {
+ retLog := (*resp.Logs)[i]
+ if bytes.HasPrefix(retLog, hashRetPrefix) {
+ abiEncodedRet = retLog[4:]
+ foundRet = true
+ break
+ }
+ }
+ }
+
+ if !foundRet {
+ 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)
+ }
+
+ decodedJSON, err := retType.MarshalToJSON(decoded)
+ if err != nil {
+ reportErrorf("cannot marshal returned bytes %v to JSON: %v", decoded, err)
+ }
+ fmt.Printf("method %s output: %s", method, string(decodedJSON))
+ }
+ },
+}
diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go
index fc4790c139..fa5dbd57c8 100644
--- a/data/abi/abi_encode.go
+++ b/data/abi/abi_encode.go
@@ -21,6 +21,7 @@ import (
"fmt"
"math/big"
"reflect"
+ "strings"
)
// typeCastToTuple cast an array-like ABI type into an ABI tuple type.
@@ -58,7 +59,7 @@ func (t Type) typeCastToTuple(tupLen ...int) (Type, error) {
return Type{}, fmt.Errorf("type cannot support conversion to tuple")
}
- tuple, err := makeTupleType(childT)
+ tuple, err := MakeTupleType(childT)
if err != nil {
return Type{}, err
}
@@ -476,3 +477,73 @@ func decodeTuple(encoded []byte, childT []Type) ([]interface{}, error) {
}
return values, nil
}
+
+// 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
+ }
+ if len(abiTupleT.childTypes) != len(jsonArgs) {
+ return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTupleT.childTypes))
+ }
+
+ // change the input args to be 1 - 14 + 15 (compacting everything together)
+ if len(jsonArgs) > 14 {
+ compactedType, err := MakeTupleType(abiTupleT.childTypes[14:])
+ if err != nil {
+ return err
+ }
+ abiTupleT.childTypes = abiTupleT.childTypes[:14]
+ abiTupleT.childTypes = append(abiTupleT.childTypes, compactedType)
+ abiTupleT.staticLength = 15
+
+ remainingJSON := "[" + strings.Join(jsonArgs[14:], ",") + "]"
+ jsonArgs = jsonArgs[:14]
+ jsonArgs = append(jsonArgs, remainingJSON)
+ }
+
+ // parse JSON value to ABI encoded bytes
+ for i := 0; i < len(jsonArgs); i++ {
+ interfaceVal, err := abiTupleT.childTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i]))
+ if err != nil {
+ return err
+ }
+ abiEncoded, err := abiTupleT.childTypes[i].Encode(interfaceVal)
+ if err != nil {
+ return err
+ }
+ *applicationArgs = append(*applicationArgs, abiEncoded)
+ }
+ 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
+ }
+ 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
+ }
+ }
+ }
+ return "", "", fmt.Errorf("unpaired parentheses: %s", methodSig)
+}
diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go
index fc7fbd8411..c585564c61 100644
--- a/data/abi/abi_encode_test.go
+++ b/data/abi/abi_encode_test.go
@@ -462,14 +462,13 @@ func TestDecodeValid(t *testing.T) {
t.Run("static uint array decode", func(t *testing.T) {
staticUintArrT, err := TypeOf("uint64[8]")
require.NoError(t, err, "make static uint array type failure")
- inputUint := []interface{}{1, 2, 3, 4, 5, 6, 7, 8}
expected := []interface{}{
uint64(1), uint64(2),
uint64(3), uint64(4),
uint64(5), uint64(6),
uint64(7), uint64(8),
}
- arrayEncoded, err := staticUintArrT.Encode(inputUint)
+ arrayEncoded, err := staticUintArrT.Encode(expected)
require.NoError(t, err, "uint64 static array encode should not return error")
actual, err := staticUintArrT.Decode(arrayEncoded)
require.NoError(t, err, "uint64 static array decode should not return error")
@@ -835,6 +834,11 @@ func categorySelfRoundTripTest(t *testing.T, category []testUnit) {
actual, err := abiType.Decode(encodedValue)
require.NoError(t, err, "failure to decode value")
require.Equal(t, testObj.value, actual, "decoded value not equal to expected")
+ jsonEncodedValue, err := abiType.MarshalToJSON(testObj.value)
+ require.NoError(t, err, "failure to encode value to JSON type")
+ jsonActual, err := abiType.UnmarshalFromJSON(jsonEncodedValue)
+ require.NoError(t, err, "failure to decode JSON value back")
+ require.Equal(t, testObj.value, jsonActual, "decode JSON value not equal to expected")
}
}
@@ -856,18 +860,8 @@ func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
randVal, err := rand.Int(rand.Reader, max)
require.NoError(t, err, "generate random uint, should be no error")
- var narrowest interface{}
- if bitSize <= 64 && bitSize > 32 {
- narrowest = randVal.Uint64()
- } else if bitSize <= 32 && bitSize > 16 {
- narrowest = uint32(randVal.Uint64())
- } else if bitSize == 16 {
- narrowest = uint16(randVal.Uint64())
- } else if bitSize == 8 {
- narrowest = uint8(randVal.Uint64())
- } else {
- narrowest = randVal
- }
+ narrowest, err := castBigIntToNearestPrimitive(randVal, uint16(bitSize))
+ require.NoError(t, err, "cast random uint to nearest primitive failure")
(*pool)[Uint][uintIndex] = testUnit{serializedType: uintTstr, value: narrowest}
uintIndex++
@@ -877,18 +871,8 @@ func addPrimitiveRandomValues(t *testing.T, pool *map[BaseType][]testUnit) {
randVal, err := rand.Int(rand.Reader, max)
require.NoError(t, err, "generate random ufixed, should be no error")
- var narrowest interface{}
- if bitSize <= 64 && bitSize > 32 {
- narrowest = randVal.Uint64()
- } else if bitSize <= 32 && bitSize > 16 {
- narrowest = uint32(randVal.Uint64())
- } else if bitSize == 16 {
- narrowest = uint16(randVal.Uint64())
- } else if bitSize == 8 {
- narrowest = uint8(randVal.Uint64())
- } else {
- narrowest = randVal
- }
+ narrowest, err := castBigIntToNearestPrimitive(randVal, uint16(bitSize))
+ require.NoError(t, err, "cast random uint to nearest primitive failure")
ufixedT, err := makeUfixedType(bitSize, precision)
require.NoError(t, err, "make ufixed type failure")
@@ -999,7 +983,7 @@ func addTupleRandomValues(t *testing.T, slotRange BaseType, pool *map[BaseType][
require.NoError(t, err, "deserialize type failure for tuple elements")
elemTypes[index] = abiT
}
- tupleT, err := makeTupleType(elemTypes)
+ tupleT, err := MakeTupleType(elemTypes)
require.NoError(t, err, "make tuple type failure")
(*pool)[Tuple] = append((*pool)[Tuple], testUnit{
serializedType: tupleT.String(),
diff --git a/data/abi/abi_json.go b/data/abi/abi_json.go
new file mode 100644
index 0000000000..482419e6b9
--- /dev/null
+++ b/data/abi/abi_json.go
@@ -0,0 +1,254 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package abi
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "github.com/algorand/go-algorand/data/basics"
+ "math/big"
+)
+
+func castBigIntToNearestPrimitive(num *big.Int, bitSize uint16) (interface{}, error) {
+ if num.BitLen() > int(bitSize) {
+ return nil, fmt.Errorf("cast big int to nearest primitive failure: %v >= 2^%d", num, bitSize)
+ } else if num.Sign() < 0 {
+ return nil, fmt.Errorf("cannot cast big int to near primitive: %v < 0", num)
+ }
+
+ switch bitSize / 8 {
+ case 1:
+ return uint8(num.Uint64()), nil
+ case 2:
+ return uint16(num.Uint64()), nil
+ case 3, 4:
+ return uint32(num.Uint64()), nil
+ case 5, 6, 7, 8:
+ return num.Uint64(), nil
+ default:
+ return num, nil
+ }
+}
+
+// MarshalToJSON convert golang value to JSON format from ABI type
+func (t Type) MarshalToJSON(value interface{}) ([]byte, error) {
+ switch t.abiTypeID {
+ case Uint:
+ bytesUint, err := encodeInt(value, t.bitSize)
+ if err != nil {
+ return nil, err
+ }
+ return new(big.Int).SetBytes(bytesUint).MarshalJSON()
+ case Ufixed:
+ denom := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(t.precision)), nil)
+ encodedUint, err := encodeInt(value, t.bitSize)
+ if err != nil {
+ return nil, err
+ }
+ return []byte(new(big.Rat).SetFrac(new(big.Int).SetBytes(encodedUint), denom).FloatString(int(t.precision))), nil
+ case Bool:
+ boolValue, ok := value.(bool)
+ if !ok {
+ return nil, fmt.Errorf("cannot infer to bool for marshal to JSON")
+ }
+ return json.Marshal(boolValue)
+ case Byte:
+ byteValue, ok := value.(byte)
+ if !ok {
+ return nil, fmt.Errorf("cannot infer to byte for marshal to JSON")
+ }
+ return json.Marshal(byteValue)
+ case Address:
+ var addressInternal basics.Address
+ switch valueCasted := value.(type) {
+ case []byte:
+ copy(addressInternal[:], valueCasted[:])
+ return json.Marshal(addressInternal.String())
+ case [addressByteSize]byte:
+ addressInternal = valueCasted
+ return json.Marshal(addressInternal.String())
+ default:
+ return nil, fmt.Errorf("cannot infer to byte slice/array for marshal to JSON")
+ }
+ case ArrayStatic, ArrayDynamic:
+ values, err := inferToSlice(value)
+ if err != nil {
+ return nil, err
+ }
+ if t.abiTypeID == ArrayStatic && int(t.staticLength) != len(values) {
+ return nil, fmt.Errorf("length of slice %d != type specific length %d", len(values), t.staticLength)
+ }
+ if t.childTypes[0].abiTypeID == Byte {
+ byteArr := make([]byte, len(values))
+ for i := 0; i < len(values); i++ {
+ tempByte, ok := values[i].(byte)
+ if !ok {
+ return nil, fmt.Errorf("cannot infer byte element from slice")
+ }
+ byteArr[i] = tempByte
+ }
+ return json.Marshal(byteArr)
+ }
+ rawMsgSlice := make([]json.RawMessage, len(values))
+ for i := 0; i < len(values); i++ {
+ rawMsgSlice[i], err = t.childTypes[0].MarshalToJSON(values[i])
+ if err != nil {
+ return nil, err
+ }
+ }
+ return json.Marshal(rawMsgSlice)
+ case String:
+ stringVal, ok := value.(string)
+ if !ok {
+ return nil, fmt.Errorf("cannot infer to string for marshal to JSON")
+ }
+ return json.Marshal(stringVal)
+ case Tuple:
+ values, err := inferToSlice(value)
+ if err != nil {
+ return nil, err
+ }
+ if len(values) != int(t.staticLength) {
+ return nil, fmt.Errorf("tuple element number != value slice length")
+ }
+ rawMsgSlice := make([]json.RawMessage, len(values))
+ for i := 0; i < len(values); i++ {
+ rawMsgSlice[i], err = t.childTypes[i].MarshalToJSON(values[i])
+ if err != nil {
+ return nil, err
+ }
+ }
+ return json.Marshal(rawMsgSlice)
+ default:
+ return nil, fmt.Errorf("cannot infer ABI type for marshalling value to JSON")
+ }
+}
+
+// UnmarshalFromJSON convert bytes to golang value following ABI type and encoding rules
+func (t Type) UnmarshalFromJSON(jsonEncoded []byte) (interface{}, error) {
+ switch t.abiTypeID {
+ case Uint:
+ num := new(big.Int)
+ if err := num.UnmarshalJSON(jsonEncoded); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to uint: %v", string(jsonEncoded), err)
+ }
+ return castBigIntToNearestPrimitive(num, t.bitSize)
+ case Ufixed:
+ floatTemp := new(big.Rat)
+ if err := floatTemp.UnmarshalText(jsonEncoded); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to ufixed: %v", string(jsonEncoded), err)
+ }
+ denom := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(t.precision)), nil)
+ denomRat := new(big.Rat).SetInt(denom)
+ numeratorRat := new(big.Rat).Mul(denomRat, floatTemp)
+ if !numeratorRat.IsInt() {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to ufixed: precision out of range", string(jsonEncoded))
+ }
+ return castBigIntToNearestPrimitive(numeratorRat.Num(), t.bitSize)
+ case Bool:
+ var elem bool
+ if err := json.Unmarshal(jsonEncoded, &elem); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to bool: %v", string(jsonEncoded), err)
+ }
+ return elem, nil
+ case Byte:
+ var elem byte
+ if err := json.Unmarshal(jsonEncoded, &elem); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded to byte: %v", err)
+ }
+ return elem, nil
+ case Address:
+ var addrStr string
+ if err := json.Unmarshal(jsonEncoded, &addrStr); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded to string: %v", err)
+ }
+ addr, err := basics.UnmarshalChecksumAddress(addrStr)
+ if err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to address: %v", string(jsonEncoded), err)
+ }
+ return addr[:], nil
+ case ArrayStatic, ArrayDynamic:
+ if t.childTypes[0].abiTypeID == Byte && bytes.HasPrefix(jsonEncoded, []byte{'"'}) {
+ var byteArr []byte
+ err := json.Unmarshal(jsonEncoded, &byteArr)
+ if err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to bytes: %v", string(jsonEncoded), err)
+ }
+ if t.abiTypeID == ArrayStatic && len(byteArr) != int(t.staticLength) {
+ return nil, fmt.Errorf("length of slice %d != type specific length %d", len(byteArr), t.staticLength)
+ }
+ outInterface := make([]interface{}, len(byteArr))
+ for i := 0; i < len(byteArr); i++ {
+ outInterface[i] = byteArr[i]
+ }
+ return outInterface, nil
+ }
+ var elems []json.RawMessage
+ if err := json.Unmarshal(jsonEncoded, &elems); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to array: %v", string(jsonEncoded), err)
+ }
+ if t.abiTypeID == ArrayStatic && len(elems) != int(t.staticLength) {
+ return nil, fmt.Errorf("JSON array element number != ABI array elem number")
+ }
+ values := make([]interface{}, len(elems))
+ for i := 0; i < len(elems); i++ {
+ tempValue, err := t.childTypes[0].UnmarshalFromJSON(elems[i])
+ if err != nil {
+ return nil, err
+ }
+ values[i] = tempValue
+ }
+ return values, nil
+ case String:
+ stringEncoded := string(jsonEncoded)
+ if bytes.HasPrefix(jsonEncoded, []byte{'"'}) {
+ var stringVar string
+ if err := json.Unmarshal(jsonEncoded, &stringVar); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to string: %v", stringEncoded, err)
+ }
+ return stringVar, nil
+ } else if bytes.HasPrefix(jsonEncoded, []byte{'['}) {
+ var elems []byte
+ if err := json.Unmarshal(jsonEncoded, &elems); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to string: %v", stringEncoded, err)
+ }
+ return string(elems), nil
+ } else {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to string", stringEncoded)
+ }
+ case Tuple:
+ var elems []json.RawMessage
+ if err := json.Unmarshal(jsonEncoded, &elems); err != nil {
+ return nil, fmt.Errorf("cannot cast JSON encoded (%s) to array for tuple: %v", string(jsonEncoded), err)
+ }
+ if len(elems) != int(t.staticLength) {
+ return nil, fmt.Errorf("JSON array element number != ABI tuple elem number")
+ }
+ values := make([]interface{}, len(elems))
+ for i := 0; i < len(elems); i++ {
+ tempValue, err := t.childTypes[i].UnmarshalFromJSON(elems[i])
+ if err != nil {
+ return nil, err
+ }
+ values[i] = tempValue
+ }
+ return values, nil
+ default:
+ return nil, fmt.Errorf("cannot cast JSON encoded %s to ABI encoding stuff", string(jsonEncoded))
+ }
+}
diff --git a/data/abi/abi_json_test.go b/data/abi/abi_json_test.go
new file mode 100644
index 0000000000..d65e3c10af
--- /dev/null
+++ b/data/abi/abi_json_test.go
@@ -0,0 +1,123 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package abi
+
+import (
+ "testing"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+func TestJSONtoInterfaceValid(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ var testCases = []struct {
+ input string
+ typeStr string
+ expected interface{}
+ }{
+ {
+ input: `[true, [0, 1, 2], 17]`,
+ typeStr: `(bool,byte[],uint64)`,
+ expected: []interface{}{
+ true,
+ []interface{}{byte(0), byte(1), byte(2)},
+ uint64(17),
+ },
+ },
+ {
+ input: `[true, "AAEC", 17]`,
+ typeStr: `(bool,byte[],uint64)`,
+ expected: []interface{}{
+ true,
+ []interface{}{byte(0), byte(1), byte(2)},
+ uint64(17),
+ },
+ },
+ {
+ input: `"AQEEBQEE"`,
+ typeStr: `byte[6]`,
+ expected: []interface{}{byte(1), byte(1), byte(4), byte(5), byte(1), byte(4)},
+ },
+ {
+ input: `[[0, [true, false], "utf-8"], [18446744073709551615, [false, true], "pistachio"]]`,
+ typeStr: `(uint64,bool[2],string)[]`,
+ expected: []interface{}{
+ []interface{}{uint64(0), []interface{}{true, false}, "utf-8"},
+ []interface{}{^uint64(0), []interface{}{false, true}, "pistachio"},
+ },
+ },
+ {
+ input: `[]`,
+ typeStr: `(uint64,bool[2],string)[]`,
+ expected: []interface{}{},
+ },
+ {
+ input: "[]",
+ typeStr: "()",
+ expected: []interface{}{},
+ },
+ {
+ input: "[65, 66, 67]",
+ typeStr: "string",
+ expected: "ABC",
+ },
+ {
+ input: "[]",
+ typeStr: "string",
+ expected: "",
+ },
+ {
+ input: "123.456",
+ typeStr: "ufixed64x3",
+ expected: uint64(123456),
+ },
+ {
+ input: `"optin"`,
+ typeStr: "string",
+ expected: "optin",
+ },
+ {
+ input: `"AAEC"`,
+ typeStr: "byte[3]",
+ expected: []interface{}{byte(0), byte(1), byte(2)},
+ },
+ {
+ input: `["uwu",["AAEC",12.34]]`,
+ typeStr: "(string,(byte[3],ufixed64x3))",
+ expected: []interface{}{"uwu", []interface{}{[]interface{}{byte(0), byte(1), byte(2)}, uint64(12340)}},
+ },
+ {
+ input: `[399,"should pass",[true,false,false,true]]`,
+ typeStr: "(uint64,string,bool[])",
+ expected: []interface{}{uint64(399), "should pass", []interface{}{true, false, false, true}},
+ },
+ }
+
+ for _, testCase := range testCases {
+ abiT, err := TypeOf(testCase.typeStr)
+ require.NoError(t, err, "fail to construct ABI type (%s): %v", testCase.typeStr, err)
+ res, err := abiT.UnmarshalFromJSON([]byte(testCase.input))
+ require.NoError(t, err, "fail to unmarshal JSON to interface: (%s): %v", testCase.input, err)
+ require.Equal(t, testCase.expected, res, "%v not matching with expected value %v", res, testCase.expected)
+ resEncoded, err := abiT.Encode(res)
+ require.NoError(t, err, "fail to encode %v to ABI bytes: %v", res, err)
+ resDecoded, err := abiT.Decode(resEncoded)
+ require.NoError(t, err, "fail to decode ABI bytes of %v: %v", res, err)
+ require.Equal(t, res, resDecoded, "ABI encode-decode round trip: %v not match with expected %v", resDecoded, res)
+ }
+}
diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go
index 654a2fba46..eb93f9eea1 100644
--- a/data/abi/abi_type.go
+++ b/data/abi/abi_type.go
@@ -185,7 +185,7 @@ func TypeOf(str string) (Type, error) {
}
tupleTypes[i] = ti
}
- return makeTupleType(tupleTypes)
+ return MakeTupleType(tupleTypes)
default:
return Type{}, fmt.Errorf("cannot convert a string %s to an ABI type", str)
}
@@ -333,8 +333,8 @@ func makeDynamicArrayType(argumentType Type) Type {
}
}
-// makeTupleType makes tuple ABI type by taking an array of tuple element types as argument.
-func makeTupleType(argumentTypes []Type) (Type, error) {
+// MakeTupleType makes tuple ABI type by taking an array of tuple element types as argument.
+func MakeTupleType(argumentTypes []Type) (Type, error) {
if len(argumentTypes) >= math.MaxUint16 {
return Type{}, fmt.Errorf("tuple type child type number larger than maximum uint16 error")
}
diff --git a/go.mod b/go.mod
index 0299aa610e..b9dafd4bc3 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,6 @@ require (
github.com/gopherjs/gopherwasm v1.0.1 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2
- github.com/gorilla/schema v1.0.2
github.com/gorilla/websocket v1.4.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmoiron/sqlx v1.2.0
diff --git a/test/scripts/e2e_subs/e2e-app-abi-add.sh b/test/scripts/e2e_subs/e2e-app-abi-add.sh
new file mode 100755
index 0000000000..60e1c1f3eb
--- /dev/null
+++ b/test/scripts/e2e_subs/e2e-app-abi-add.sh
@@ -0,0 +1,39 @@
+#!/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-arg.sh b/test/scripts/e2e_subs/e2e-app-abi-arg.sh
new file mode 100755
index 0000000000..c6f719a479
--- /dev/null
+++ b/test/scripts/e2e_subs/e2e-app-abi-arg.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+date '+app-abi-arg-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-arg.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 with string "optin"
+${gcmd} app optin --app-id $APPID --from $ACCOUNT --app-arg 'abi:string:"optin"'
+
+# Call should now succeed
+${gcmd} app call --app-id $APPID --from $ACCOUNT --app-arg 'abi:uint64:0'
+${gcmd} app call --app-id $APPID --from $ACCOUNT --app-arg 'abi:byte[3]:"AAEC"'
+${gcmd} app call --app-id $APPID --from $ACCOUNT --app-arg 'abi:(string,(byte[3],ufixed64x3)):["uwu",["AAEC",12.34]]'
+${gcmd} app call --app-id $APPID --from $ACCOUNT --app-arg 'abi:(uint64,string,bool[]):[399,"should pass",[true,false,false,true]]'
+
+# 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/tealprogs/app-abi-add-example.teal b/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal
new file mode 100644
index 0000000000..18d3b3e6e7
--- /dev/null
+++ b/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal
@@ -0,0 +1,87 @@
+#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-arg.teal b/test/scripts/e2e_subs/tealprogs/app-abi-arg.teal
new file mode 100644
index 0000000000..900ee0e541
--- /dev/null
+++ b/test/scripts/e2e_subs/tealprogs/app-abi-arg.teal
@@ -0,0 +1,73 @@
+#pragma version 5
+intcblock 1 0
+txn ApplicationID
+intc_1 // 0
+==
+bnz main_l14
+txn OnCompletion
+pushint 5 // DeleteApplication
+==
+bnz main_l13
+txn OnCompletion
+intc_0 // OptIn
+==
+txna ApplicationArgs 0
+pushbytes 0x00056f7074696e // 0x00056f7074696e
+==
+&&
+bnz main_l12
+txn OnCompletion
+intc_1 // NoOp
+==
+txna ApplicationArgs 0
+pushbytes 0x0000000000000000 // 0x0000000000000000
+==
+&&
+bnz main_l11
+txn OnCompletion
+intc_1 // NoOp
+==
+txna ApplicationArgs 0
+pushbytes 0x000102 // 0x000102
+==
+&&
+bnz main_l10
+txn OnCompletion
+intc_1 // NoOp
+==
+txna ApplicationArgs 0
+pushbytes 0x000d00010200000000000030340003757775 // 0x000d00010200000000000030340003757775
+==
+&&
+bnz main_l9
+txn OnCompletion
+intc_1 // NoOp
+==
+txna ApplicationArgs 0
+pushbytes 0x000000000000018f000c0019000b73686f756c642070617373000490 // 0x000000000000018f000c0019000b73686f756c642070617373000490
+==
+&&
+bnz main_l8
+intc_1 // 0
+return
+main_l8:
+intc_0 // 1
+return
+main_l9:
+intc_0 // 1
+return
+main_l10:
+intc_0 // 1
+return
+main_l11:
+intc_0 // 1
+return
+main_l12:
+intc_0 // 1
+return
+main_l13:
+intc_0 // 1
+return
+main_l14:
+intc_0 // 1
+return
\ No newline at end of file