github.com/algorand/go-algorand-sdk@v1.24.0/future/atomicTransactionComposer.go (about)

     1  package future
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/algorand/go-algorand-sdk/abi"
    10  	"github.com/algorand/go-algorand-sdk/client/v2/algod"
    11  	"github.com/algorand/go-algorand-sdk/client/v2/common/models"
    12  	"github.com/algorand/go-algorand-sdk/crypto"
    13  	"github.com/algorand/go-algorand-sdk/types"
    14  )
    15  
    16  // abiReturnHash is the 4-byte prefix for logged return values, from https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0004.md#standard-format
    17  var abiReturnHash = []byte{0x15, 0x1f, 0x7c, 0x75}
    18  
    19  // maxAppArgs is the maximum number of arguments for an application call transaction at the time
    20  // ARC-4 was created
    21  const maxAppArgs = 16
    22  
    23  // The tuple threshold is maxAppArgs, minus 1 for the method selector in the first app arg,
    24  // minus 1 for the final app argument becoming a tuple of the remaining method args
    25  const methodArgsTupleThreshold = maxAppArgs - 2
    26  
    27  // TransactionWithSigner represents an unsigned transactions and a signer that can authorize that
    28  // transaction.
    29  type TransactionWithSigner struct {
    30  	// An unsigned transaction
    31  	Txn types.Transaction
    32  	// A transaction signer that can authorize the transaction
    33  	Signer TransactionSigner
    34  }
    35  
    36  // ABIMethodResult represents the output from a successful ABI method call.
    37  type ABIMethodResult struct {
    38  	// The TxID of the transaction that invoked the ABI method call.
    39  	TxID string
    40  	// Information about the confirmed transaction that invoked the ABI method call.
    41  	TransactionInfo models.PendingTransactionInfoResponse
    42  	// Method that was called for this ABIMethodResult
    43  	Method abi.Method
    44  	// The raw bytes of the return value from the ABI method call. This will be empty if the method
    45  	// does not return a value (return type "void").
    46  	RawReturnValue []byte
    47  	// The return value from the ABI method call. This will be nil if the method does not return
    48  	// a value (return type "void"), or if the SDK was unable to decode the returned value.
    49  	ReturnValue interface{}
    50  	// If the SDK was unable to decode a return value, the error will be here. Make sure to check
    51  	// this before examinging ReturnValue
    52  	DecodeError error
    53  }
    54  
    55  // AddMethodCallParams contains the parameters for the method AtomicTransactionComposer.AddMethodCall
    56  type AddMethodCallParams struct {
    57  	// The ID of the smart contract to call. Set this to 0 to indicate an application creation call.
    58  	AppID uint64
    59  	// The method to call on the smart contract
    60  	Method abi.Method
    61  	// The arguments to include in the method call. If omitted, no arguments will be passed to the
    62  	// method.
    63  	MethodArgs []interface{}
    64  	// The address of the sender of this application call
    65  	Sender types.Address
    66  	// Transactions params to use for this application call
    67  	SuggestedParams types.SuggestedParams
    68  	// The OnComplete action to take for this application call
    69  	OnComplete types.OnCompletion
    70  	// The approval program for this application call. Only set this if this is an application
    71  	// creation call, or if onComplete is UpdateApplicationOC.
    72  	ApprovalProgram []byte
    73  	// The clear program for this application call. Only set this if this is an application creation
    74  	// call, or if onComplete is UpdateApplicationOC.
    75  	ClearProgram []byte
    76  	// The global schema sizes. Only set this if this is an application creation call.
    77  	GlobalSchema types.StateSchema
    78  	// The local schema sizes. Only set this if this is an application creation call.
    79  	LocalSchema types.StateSchema
    80  	// The number of extra pages to allocate for the application's programs. Only set this if this
    81  	// is an application creation call.
    82  	ExtraPages uint32
    83  	// The note value for this application call
    84  	Note []byte
    85  	// The lease value for this application call
    86  	Lease [32]byte
    87  	// If provided, the address that the sender will be rekeyed to at the conclusion of this application call
    88  	RekeyTo types.Address
    89  	// A transaction Signer that can authorize this application call from sender
    90  	Signer TransactionSigner
    91  	// Any foreign apps to be passed that aren't part of the method signature.
    92  	// If apps are provided here, the apps specified in the method args will appear after these
    93  	ForeignApps []uint64
    94  	// Any foreign assets to be passed that aren't part of the method signature
    95  	// If assets are provided here, the assets specified in the method args will appear after these
    96  	ForeignAssets []uint64
    97  	// Any foreign accounts to be passed that aren't part of the method signature
    98  	// If accounts are provided here, the accounts specified in the method args will appear after these
    99  	ForeignAccounts []string
   100  
   101  	// References of the boxes to be accessed by this method call.
   102  	BoxReferences []types.AppBoxReference
   103  }
   104  
   105  // ExecuteResult contains the results of successfully calling the Execute method on an
   106  // AtomicTransactionComposer object.
   107  type ExecuteResult struct {
   108  	// The round in which the executed transaction group was confirmed on chain
   109  	ConfirmedRound uint64
   110  	// A list of the TxIDs for each transaction in the executed group
   111  	TxIDs []string
   112  	// For each ABI method call in the executed group (created by the AddMethodCall method), this
   113  	// slice contains information about the method call's return value
   114  	MethodResults []ABIMethodResult
   115  }
   116  
   117  // AtomicTransactionComposerStatus represents the status of an AtomicTransactionComposer
   118  type AtomicTransactionComposerStatus = int
   119  
   120  const (
   121  	// The atomic group is still under construction.
   122  	BUILDING AtomicTransactionComposerStatus = iota
   123  
   124  	// The atomic group has been finalized, but not yet signed.
   125  	BUILT
   126  
   127  	// The atomic group has been finalized and signed, but not yet submitted to the network.
   128  	SIGNED
   129  
   130  	// The atomic group has been finalized, signed, and submitted to the network.
   131  	SUBMITTED
   132  
   133  	// The atomic group has been finalized, signed, submitted, and successfully committed to a block.
   134  	COMMITTED
   135  )
   136  
   137  type transactionContext struct {
   138  	// The main transaction.
   139  	txn types.Transaction
   140  
   141  	// The corresponding signer responsible for producing the signed transaction.
   142  	signer TransactionSigner
   143  
   144  	// The corresponding Method constructed from information passed into atc.AddMethodCall().
   145  	method *abi.Method
   146  
   147  	// The raw signed transaction populated after invocation of atc.GatherSignatures().
   148  	stxBytes []byte
   149  
   150  	// The txid of the transaction, empty until populated by a call to txContext.txID()
   151  	txid string
   152  }
   153  
   154  func (txContext *transactionContext) txID() string {
   155  	if txContext.txid == "" {
   156  		txContext.txid = crypto.GetTxID(txContext.txn)
   157  	}
   158  	return txContext.txid
   159  }
   160  
   161  func (txContext *transactionContext) isMethodCallTx() bool {
   162  	return txContext.method != nil
   163  }
   164  
   165  // The maximum size of an atomic transaction group.
   166  const MaxAtomicGroupSize = 16
   167  
   168  // AtomicTransactionComposer is a helper class used to construct and execute atomic transaction groups
   169  type AtomicTransactionComposer struct {
   170  	// The current status of the composer. The status increases monotonically.
   171  	status AtomicTransactionComposerStatus
   172  
   173  	// The transaction contexts in the group with their respective signers.
   174  	// If status is greater than BUILDING, then this slice cannot change.
   175  	txContexts []transactionContext
   176  }
   177  
   178  // GetStatus returns the status of this composer's transaction group.
   179  func (atc *AtomicTransactionComposer) GetStatus() AtomicTransactionComposerStatus {
   180  	return atc.status
   181  }
   182  
   183  // Count returns the number of transactions currently in this atomic group.
   184  func (atc *AtomicTransactionComposer) Count() int {
   185  	return len(atc.txContexts)
   186  }
   187  
   188  // Clone creates a new composer with the same underlying transactions. The new composer's status
   189  // will be BUILDING, so additional transactions may be added to it.
   190  func (atc *AtomicTransactionComposer) Clone() AtomicTransactionComposer {
   191  	newTxContexts := make([]transactionContext, len(atc.txContexts))
   192  	copy(newTxContexts, atc.txContexts)
   193  	for i := range newTxContexts {
   194  		newTxContexts[i].txn.Group = types.Digest{}
   195  	}
   196  
   197  	if len(newTxContexts) == 0 {
   198  		newTxContexts = nil
   199  	}
   200  
   201  	return AtomicTransactionComposer{
   202  		status:     BUILDING,
   203  		txContexts: newTxContexts,
   204  	}
   205  }
   206  
   207  func (atc *AtomicTransactionComposer) validateTransaction(txn types.Transaction, expectedType string) error {
   208  	emtpyGroup := types.Digest{}
   209  	if txn.Group != emtpyGroup {
   210  		return fmt.Errorf("expected empty group id")
   211  	}
   212  
   213  	if expectedType != abi.AnyTransactionType && expectedType != string(txn.Type) {
   214  		return fmt.Errorf("expected transaction with type %s, but got type %s", expectedType, string(txn.Type))
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // AddTransaction adds a transaction to this atomic group.
   221  //
   222  // An error will be thrown if the composer's status is not BUILDING, or if adding this transaction
   223  // causes the current group to exceed MaxAtomicGroupSize.
   224  func (atc *AtomicTransactionComposer) AddTransaction(txnAndSigner TransactionWithSigner) error {
   225  	if atc.status != BUILDING {
   226  		return errors.New("status must be BUILDING in order to add tranactions")
   227  	}
   228  
   229  	if atc.Count() == MaxAtomicGroupSize {
   230  		return fmt.Errorf("reached max group size: %d", MaxAtomicGroupSize)
   231  	}
   232  
   233  	err := atc.validateTransaction(txnAndSigner.Txn, abi.AnyTransactionType)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	txContext := transactionContext{
   239  		txn:    txnAndSigner.Txn,
   240  		signer: txnAndSigner.Signer,
   241  	}
   242  	atc.txContexts = append(atc.txContexts, txContext)
   243  	return nil
   244  }
   245  
   246  // AddMethodCall adds a smart contract method call to this atomic group.
   247  //
   248  // An error will be thrown if the composer's status is not BUILDING, if adding this transaction
   249  // causes the current group to exceed MaxAtomicGroupSize, or if the provided arguments are invalid
   250  // for the given method.
   251  func (atc *AtomicTransactionComposer) AddMethodCall(params AddMethodCallParams) error {
   252  	if atc.status != BUILDING {
   253  		return errors.New("status must be BUILDING in order to add transactions")
   254  	}
   255  
   256  	if len(params.MethodArgs) != len(params.Method.Args) {
   257  		return fmt.Errorf("the incorrect number of arguments were provided: %d != %d", len(params.MethodArgs), len(params.Method.Args))
   258  	}
   259  
   260  	if atc.Count()+params.Method.GetTxCount() > MaxAtomicGroupSize {
   261  		return fmt.Errorf("reached max group size: %d", MaxAtomicGroupSize)
   262  	}
   263  
   264  	if params.AppID == 0 {
   265  		if len(params.ApprovalProgram) == 0 || len(params.ClearProgram) == 0 {
   266  			return fmt.Errorf("ApprovalProgram and ClearProgram must be provided for an application creation call")
   267  		}
   268  	} else if params.OnComplete == types.UpdateApplicationOC {
   269  		if len(params.ApprovalProgram) == 0 || len(params.ClearProgram) == 0 {
   270  			return fmt.Errorf("ApprovalProgram and ClearProgram must be provided for an application update call")
   271  		}
   272  		if (params.GlobalSchema != types.StateSchema{}) || (params.LocalSchema != types.StateSchema{}) {
   273  			return fmt.Errorf("GlobalSchema and LocalSchema must not be provided for an application update call")
   274  		}
   275  	} else if len(params.ApprovalProgram) != 0 || len(params.ClearProgram) != 0 || (params.GlobalSchema != types.StateSchema{}) || (params.LocalSchema != types.StateSchema{}) {
   276  		return fmt.Errorf("ApprovalProgram, ClearProgram, GlobalSchema, and LocalSchema must not be provided for a non-creation call")
   277  	}
   278  
   279  	var txsToAdd []TransactionWithSigner
   280  	var basicArgValues []interface{}
   281  	var basicArgTypes []abi.Type
   282  	var refArgValues []interface{}
   283  	var refArgTypes []string
   284  	refArgIndexToBasicArgIndex := make(map[int]int)
   285  	for i, arg := range params.Method.Args {
   286  		argValue := params.MethodArgs[i]
   287  
   288  		if arg.IsTransactionArg() {
   289  			txnAndSigner, ok := argValue.(TransactionWithSigner)
   290  			if !ok {
   291  				return fmt.Errorf("invalid arg type, expected transaction")
   292  			}
   293  
   294  			err := atc.validateTransaction(txnAndSigner.Txn, arg.Type)
   295  			if err != nil {
   296  				return err
   297  			}
   298  			txsToAdd = append(txsToAdd, txnAndSigner)
   299  		} else {
   300  			var abiType abi.Type
   301  			var err error
   302  
   303  			if arg.IsReferenceArg() {
   304  				refArgIndexToBasicArgIndex[len(refArgTypes)] = len(basicArgTypes)
   305  				refArgValues = append(refArgValues, argValue)
   306  				refArgTypes = append(refArgTypes, arg.Type)
   307  
   308  				// treat the reference as a uint8 for encoding purposes
   309  				abiType, err = abi.TypeOf("uint8")
   310  			} else {
   311  				abiType, err = arg.GetTypeObject()
   312  			}
   313  			if err != nil {
   314  				return err
   315  			}
   316  
   317  			basicArgValues = append(basicArgValues, argValue)
   318  			basicArgTypes = append(basicArgTypes, abiType)
   319  		}
   320  	}
   321  
   322  	// copy foreign arrays before modifying in populateMethodCallReferenceArgs
   323  	foreignAccounts := make([]string, len(params.ForeignAccounts))
   324  	copy(foreignAccounts, params.ForeignAccounts)
   325  	foreignApps := make([]uint64, len(params.ForeignApps))
   326  	copy(foreignApps, params.ForeignApps)
   327  	foreignAssets := make([]uint64, len(params.ForeignAssets))
   328  	copy(foreignAssets, params.ForeignAssets)
   329  
   330  	refArgsResolved, err := populateMethodCallReferenceArgs(
   331  		params.Sender.String(),
   332  		params.AppID,
   333  		refArgTypes,
   334  		refArgValues,
   335  		&foreignAccounts,
   336  		&foreignApps,
   337  		&foreignAssets,
   338  	)
   339  	if err != nil {
   340  		return err
   341  	}
   342  	for i, resolved := range refArgsResolved {
   343  		basicArgIndex := refArgIndexToBasicArgIndex[i]
   344  		// use the foreign array index as the encoded argument value
   345  		basicArgValues[basicArgIndex] = resolved
   346  	}
   347  
   348  	// Up to 16 app arguments can be passed to app call. First is reserved for method selector,
   349  	// and the rest are for method call arguments. But if more than 15 method call arguments
   350  	// are present, then the method arguments after the 14th are placed in a tuple in the last app
   351  	// argument slot
   352  	if len(basicArgValues) > maxAppArgs-1 {
   353  		typesForTuple := make([]abi.Type, len(basicArgTypes)-methodArgsTupleThreshold)
   354  		copy(typesForTuple, basicArgTypes[methodArgsTupleThreshold:])
   355  
   356  		valueForTuple := make([]interface{}, len(basicArgValues)-methodArgsTupleThreshold)
   357  		copy(valueForTuple, basicArgValues[methodArgsTupleThreshold:])
   358  
   359  		tupleType, err := abi.MakeTupleType(typesForTuple)
   360  		if err != nil {
   361  			return err
   362  		}
   363  
   364  		basicArgValues = append(basicArgValues[:methodArgsTupleThreshold], valueForTuple)
   365  		basicArgTypes = append(basicArgTypes[:methodArgsTupleThreshold], tupleType)
   366  	}
   367  
   368  	encodedAbiArgs := [][]byte{params.Method.GetSelector()}
   369  
   370  	for i, abiArg := range basicArgValues {
   371  		encodedArg, err := basicArgTypes[i].Encode(abiArg)
   372  		if err != nil {
   373  			return err
   374  		}
   375  
   376  		encodedAbiArgs = append(encodedAbiArgs, encodedArg)
   377  	}
   378  
   379  	tx, err := MakeApplicationCallTxWithBoxes(
   380  		params.AppID,
   381  		encodedAbiArgs,
   382  		foreignAccounts,
   383  		foreignApps,
   384  		foreignAssets,
   385  		params.BoxReferences,
   386  		params.OnComplete,
   387  		params.ApprovalProgram,
   388  		params.ClearProgram,
   389  		params.GlobalSchema,
   390  		params.LocalSchema,
   391  		params.ExtraPages,
   392  		params.SuggestedParams,
   393  		params.Sender,
   394  		params.Note,
   395  		types.Digest{},
   396  		params.Lease,
   397  		params.RekeyTo)
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	txAndSigner := TransactionWithSigner{
   403  		Txn:    tx,
   404  		Signer: params.Signer,
   405  	}
   406  
   407  	for _, txAndSigner := range txsToAdd {
   408  		txContext := transactionContext{
   409  			txn:    txAndSigner.Txn,
   410  			signer: txAndSigner.Signer,
   411  		}
   412  		atc.txContexts = append(atc.txContexts, txContext)
   413  	}
   414  
   415  	methodCallTxContext := transactionContext{
   416  		txn:    txAndSigner.Txn,
   417  		signer: txAndSigner.Signer,
   418  		method: &params.Method,
   419  	}
   420  	atc.txContexts = append(atc.txContexts, methodCallTxContext)
   421  	return nil
   422  }
   423  
   424  func (atc *AtomicTransactionComposer) getFinalizedTxWithSigners() []TransactionWithSigner {
   425  	txWithSigners := make([]TransactionWithSigner, len(atc.txContexts))
   426  	for i, txContext := range atc.txContexts {
   427  		txWithSigners[i] = TransactionWithSigner{
   428  			Txn:    txContext.txn,
   429  			Signer: txContext.signer,
   430  		}
   431  	}
   432  	return txWithSigners
   433  }
   434  
   435  // BuildGroup finalizes the transaction group and returned the finalized transactions.
   436  //
   437  // The composer's status will be at least BUILT after executing this method.
   438  func (atc *AtomicTransactionComposer) BuildGroup() ([]TransactionWithSigner, error) {
   439  	if atc.status > BUILDING {
   440  		return atc.getFinalizedTxWithSigners(), nil
   441  	}
   442  
   443  	if atc.Count() == 0 {
   444  		return nil, fmt.Errorf("attempting to build group with zero transactions")
   445  	}
   446  
   447  	var txns []types.Transaction
   448  	for _, txContext := range atc.txContexts {
   449  		txns = append(txns, txContext.txn)
   450  	}
   451  
   452  	if len(txns) > 1 {
   453  		gid, err := crypto.ComputeGroupID(txns)
   454  		if err != nil {
   455  			return nil, err
   456  		}
   457  
   458  		for i := range atc.txContexts {
   459  			atc.txContexts[i].txn.Group = gid
   460  		}
   461  	}
   462  
   463  	atc.status = BUILT
   464  	return atc.getFinalizedTxWithSigners(), nil
   465  }
   466  
   467  func (atc *AtomicTransactionComposer) getRawSignedTxs() [][]byte {
   468  	stxs := make([][]byte, len(atc.txContexts))
   469  	for i, txContext := range atc.txContexts {
   470  		stxs[i] = txContext.stxBytes
   471  	}
   472  	return stxs
   473  }
   474  
   475  // GatherSignatures obtains signatures for each transaction in this group. If signatures have
   476  // already been obtained, this method will return cached versions of the signatures.
   477  //
   478  // The composer's status will be at least SIGNED after executing this method.
   479  //
   480  // An error will be thrown if signing any of the transactions fails. Otherwise, this will return an
   481  // array of signed transactions.
   482  func (atc *AtomicTransactionComposer) GatherSignatures() ([][]byte, error) {
   483  	// if status is at least signed then return cached signed transactions
   484  	if atc.status >= SIGNED {
   485  		return atc.getRawSignedTxs(), nil
   486  	}
   487  
   488  	// retrieve built transactions and verify status is BUILT
   489  	txsWithSigners, err := atc.BuildGroup()
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  
   494  	var txs []types.Transaction
   495  	for _, txWithSigner := range txsWithSigners {
   496  		txs = append(txs, txWithSigner.Txn)
   497  	}
   498  
   499  	visited := make([]bool, len(txs))
   500  	rawSignedTxs := make([][]byte, len(txs))
   501  	for i, txWithSigner := range txsWithSigners {
   502  		if visited[i] {
   503  			continue
   504  		}
   505  
   506  		var indexesToSign []int
   507  		for j, other := range txsWithSigners {
   508  			if !visited[j] && txWithSigner.Signer.Equals(other.Signer) {
   509  				indexesToSign = append(indexesToSign, j)
   510  				visited[j] = true
   511  			}
   512  		}
   513  
   514  		if len(indexesToSign) == 0 {
   515  			return nil, fmt.Errorf("invalid tx signer provided, isn't equal to self")
   516  		}
   517  
   518  		sigStxs, err := txWithSigner.Signer.SignTransactions(txs, indexesToSign)
   519  		if err != nil {
   520  			return nil, err
   521  		}
   522  
   523  		for i, index := range indexesToSign {
   524  			rawSignedTxs[index] = sigStxs[i]
   525  		}
   526  	}
   527  
   528  	for i, stxBytes := range rawSignedTxs {
   529  		atc.txContexts[i].stxBytes = stxBytes
   530  	}
   531  	atc.status = SIGNED
   532  	return rawSignedTxs, nil
   533  }
   534  
   535  func (atc *AtomicTransactionComposer) getTxIDs() []string {
   536  	txIDs := make([]string, len(atc.txContexts))
   537  	for i, txContext := range atc.txContexts {
   538  		txIDs[i] = txContext.txID()
   539  	}
   540  	return txIDs
   541  }
   542  
   543  // Submit sends the transaction group to the network, but doesn't wait for it to be committed to a
   544  // block. An error will be thrown if submission fails.
   545  //
   546  // The composer's status must be SUBMITTED or lower before calling this method. If submission is
   547  // successful, this composer's status will update to SUBMITTED.
   548  //
   549  // Note: a group can only be submitted again if it fails.
   550  //
   551  // Returns a list of TxIDs of the submitted transactions.
   552  func (atc *AtomicTransactionComposer) Submit(client *algod.Client, ctx context.Context) ([]string, error) {
   553  	if atc.status > SUBMITTED {
   554  		return nil, errors.New("status must be SUBMITTED or lower in order to call Submit()")
   555  	}
   556  
   557  	stxs, err := atc.GatherSignatures()
   558  	if err != nil {
   559  		return nil, err
   560  	}
   561  
   562  	var serializedStxs []byte
   563  	for _, stx := range stxs {
   564  		serializedStxs = append(serializedStxs, stx...)
   565  	}
   566  
   567  	_, err = client.SendRawTransaction(serializedStxs).Do(ctx)
   568  	if err != nil {
   569  		return nil, err
   570  	}
   571  
   572  	atc.status = SUBMITTED
   573  	return atc.getTxIDs(), nil
   574  }
   575  
   576  // Execute sends the transaction group to the network and waits until it's committed to a block. An
   577  // error will be thrown if submission or execution fails.
   578  //
   579  // The composer's status must be SUBMITTED or lower before calling this method, since execution is
   580  // only allowed once. If submission is successful, this composer's status will update to SUBMITTED.
   581  // If the execution is also successful, this composer's status will update to COMMITTED.
   582  //
   583  // Note: a group can only be submitted again if it fails.
   584  //
   585  // Returns the confirmed round for this transaction, the txIDs of the submitted transactions, and an
   586  // ABIResult for each method call in this group.
   587  func (atc *AtomicTransactionComposer) Execute(client *algod.Client, ctx context.Context, waitRounds uint64) (ExecuteResult, error) {
   588  	if atc.status == COMMITTED {
   589  		return ExecuteResult{}, errors.New("status is already committed")
   590  	}
   591  
   592  	_, err := atc.Submit(client, ctx)
   593  	if err != nil {
   594  		return ExecuteResult{}, err
   595  	}
   596  	atc.status = SUBMITTED
   597  
   598  	indexToWaitFor := 0
   599  	numMethodCalls := 0
   600  	for i, txContext := range atc.txContexts {
   601  		if txContext.isMethodCallTx() {
   602  			// if there is a method call in the group, we need to query the
   603  			// pending tranaction endpoint for it anyway, so as an optimization
   604  			// we should wait for its TxID
   605  			if numMethodCalls == 0 {
   606  				indexToWaitFor = i
   607  			}
   608  			numMethodCalls += 1
   609  		}
   610  	}
   611  
   612  	groupInfo, err := WaitForConfirmation(client, atc.txContexts[indexToWaitFor].txID(), waitRounds, ctx)
   613  	if err != nil {
   614  		return ExecuteResult{}, err
   615  	}
   616  	atc.status = COMMITTED
   617  
   618  	executeResponse := ExecuteResult{
   619  		ConfirmedRound: groupInfo.ConfirmedRound,
   620  		TxIDs:          atc.getTxIDs(),
   621  		MethodResults:  make([]ABIMethodResult, 0, numMethodCalls),
   622  	}
   623  
   624  	for i, txContext := range atc.txContexts {
   625  		// Verify method call is available. This may not be the case if the App Call Tx wasn't created
   626  		// by AddMethodCall().
   627  		if !txContext.isMethodCallTx() {
   628  			continue
   629  		}
   630  
   631  		result := ABIMethodResult{TxID: txContext.txID(), Method: *txContext.method}
   632  
   633  		if i == indexToWaitFor {
   634  			result.TransactionInfo = groupInfo
   635  		} else {
   636  			methodCallInfo, _, err := client.PendingTransactionInformation(result.TxID).Do(ctx)
   637  			if err != nil {
   638  				result.DecodeError = err
   639  				executeResponse.MethodResults = append(executeResponse.MethodResults, result)
   640  				continue
   641  			}
   642  			result.TransactionInfo = methodCallInfo
   643  		}
   644  
   645  		if txContext.method.Returns.IsVoid() {
   646  			result.RawReturnValue = []byte{}
   647  			executeResponse.MethodResults = append(executeResponse.MethodResults, result)
   648  			continue
   649  		}
   650  
   651  		if len(result.TransactionInfo.Logs) == 0 {
   652  			result.DecodeError = errors.New("method call did not log a return value")
   653  			executeResponse.MethodResults = append(executeResponse.MethodResults, result)
   654  			continue
   655  		}
   656  
   657  		lastLog := result.TransactionInfo.Logs[len(result.TransactionInfo.Logs)-1]
   658  		if !bytes.HasPrefix(lastLog, abiReturnHash) {
   659  			result.DecodeError = errors.New("method call did not log a return value")
   660  			executeResponse.MethodResults = append(executeResponse.MethodResults, result)
   661  			continue
   662  		}
   663  
   664  		result.RawReturnValue = lastLog[len(abiReturnHash):]
   665  
   666  		abiType, err := txContext.method.Returns.GetTypeObject()
   667  		if err != nil {
   668  			result.DecodeError = err
   669  			executeResponse.MethodResults = append(executeResponse.MethodResults, result)
   670  			break
   671  		}
   672  
   673  		result.ReturnValue, result.DecodeError = abiType.Decode(result.RawReturnValue)
   674  		executeResponse.MethodResults = append(executeResponse.MethodResults, result)
   675  	}
   676  
   677  	return executeResponse, nil
   678  }
   679  
   680  // marshallAbiUint64 converts any value used to represent an ABI "uint64" into
   681  // a golang uint64
   682  func marshallAbiUint64(value interface{}) (uint64, error) {
   683  	abiType, err := abi.TypeOf("uint64")
   684  	if err != nil {
   685  		return 0, err
   686  	}
   687  	encoded, err := abiType.Encode(value)
   688  	if err != nil {
   689  		return 0, err
   690  	}
   691  	decoded, err := abiType.Decode(encoded)
   692  	if err != nil {
   693  		return 0, err
   694  	}
   695  	marshalledValue, ok := decoded.(uint64)
   696  	if !ok {
   697  		err = fmt.Errorf("Decoded value is not a uint64")
   698  	}
   699  	return marshalledValue, err
   700  }
   701  
   702  // marshallAbiAddress converts any value used to represent an ABI "address" into
   703  // a golang address string
   704  func marshallAbiAddress(value interface{}) (string, error) {
   705  	abiType, err := abi.TypeOf("address")
   706  	if err != nil {
   707  		return "", err
   708  	}
   709  	encoded, err := abiType.Encode(value)
   710  	if err != nil {
   711  		return "", err
   712  	}
   713  	decoded, err := abiType.Decode(encoded)
   714  	if err != nil {
   715  		return "", err
   716  	}
   717  	marshalledValue, ok := decoded.([]byte)
   718  	if !ok || len(marshalledValue) != len(types.ZeroAddress) {
   719  		err = fmt.Errorf("Decoded value is not a 32 length byte slice")
   720  	}
   721  	var addressValue types.Address
   722  	copy(addressValue[:], marshalledValue)
   723  	return addressValue.String(), err
   724  }
   725  
   726  // populateMethodCallReferenceArgs parses reference argument types and resolves them to an index
   727  // into the appropriate foreign array. Their placement will be as compact as possible, which means
   728  // values will be deduplicated and any value that is the sender or the current app will not be added
   729  // to the foreign array.
   730  func populateMethodCallReferenceArgs(sender string, currentApp uint64, types []string, values []interface{}, accounts *[]string, apps *[]uint64, assets *[]uint64) ([]int, error) {
   731  	resolvedIndexes := make([]int, len(types))
   732  
   733  	for i, value := range values {
   734  		var resolved int
   735  
   736  		switch types[i] {
   737  		case abi.AccountReferenceType:
   738  			address, err := marshallAbiAddress(value)
   739  			if err != nil {
   740  				return nil, err
   741  			}
   742  			if address == sender {
   743  				resolved = 0
   744  			} else {
   745  				duplicate := false
   746  				for j, account := range *accounts {
   747  					if address == account {
   748  						resolved = j + 1 // + 1 because 0 is the sender
   749  						duplicate = true
   750  						break
   751  					}
   752  				}
   753  				if !duplicate {
   754  					resolved = len(*accounts) + 1
   755  					*accounts = append(*accounts, address)
   756  				}
   757  			}
   758  		case abi.ApplicationReferenceType:
   759  			appID, err := marshallAbiUint64(value)
   760  			if err != nil {
   761  				return nil, err
   762  			}
   763  			if appID == currentApp {
   764  				resolved = 0
   765  			} else {
   766  				duplicate := false
   767  				for j, app := range *apps {
   768  					if appID == app {
   769  						resolved = j + 1 // + 1 because 0 is the current app
   770  						duplicate = true
   771  						break
   772  					}
   773  				}
   774  				if !duplicate {
   775  					resolved = len(*apps) + 1
   776  					*apps = append(*apps, appID)
   777  				}
   778  			}
   779  		case abi.AssetReferenceType:
   780  			assetID, err := marshallAbiUint64(value)
   781  			if err != nil {
   782  				return nil, err
   783  			}
   784  			duplicate := false
   785  			for j, asset := range *assets {
   786  				if assetID == asset {
   787  					resolved = j
   788  					duplicate = true
   789  					break
   790  				}
   791  			}
   792  			if !duplicate {
   793  				resolved = len(*assets)
   794  				*assets = append(*assets, assetID)
   795  			}
   796  		default:
   797  			return nil, fmt.Errorf("Unknown reference type: %s", types[i])
   798  		}
   799  
   800  		resolvedIndexes[i] = resolved
   801  	}
   802  
   803  	return resolvedIndexes, nil
   804  }