github.com/algorand/go-algorand-sdk@v1.24.0/abi/interactions.go (about)

     1  package abi
     2  
     3  import (
     4  	"crypto/sha512"
     5  	"fmt"
     6  	"strings"
     7  
     8  	avm_abi "github.com/algorand/avm-abi/abi"
     9  )
    10  
    11  // Arg represents an ABI Method argument
    12  type Arg struct {
    13  	// Optional, user-friendly name for the argument
    14  	Name string `json:"name,omitempty"`
    15  	// The type of the argument as a string. See the method GetTypeObject to
    16  	// obtain the ABI type object
    17  	Type string `json:"type"`
    18  	// A hidden type object cache that holds the parsed type object
    19  	typeObject *Type `json:"-"`
    20  	// Optional, user-friendly description for the argument
    21  	Desc string `json:"desc,omitempty"`
    22  }
    23  
    24  // IsTransactionArg checks if this argument's type is a transaction type
    25  func (a Arg) IsTransactionArg() bool {
    26  	return IsTransactionType(a.Type)
    27  }
    28  
    29  // IsReferenceArg checks if this argument's type is a reference type
    30  func (a Arg) IsReferenceArg() bool {
    31  	return IsReferenceType(a.Type)
    32  }
    33  
    34  // GetTypeObject parses and returns the ABI type object for this argument's
    35  // type. An error will be returned if this argument's type is a transaction or
    36  // reference type
    37  func (a *Arg) GetTypeObject() (Type, error) {
    38  	if a.IsTransactionArg() {
    39  		return Type{}, fmt.Errorf("Invalid operation on transaction type %s", a.Type)
    40  	}
    41  	if a.IsReferenceArg() {
    42  		return Type{}, fmt.Errorf("Invalid operation on reference type %s", a.Type)
    43  	}
    44  	if a.typeObject != nil {
    45  		return *a.typeObject, nil
    46  	}
    47  	typeObject, err := TypeOf(a.Type)
    48  	if err == nil {
    49  		a.typeObject = &typeObject
    50  	}
    51  	return typeObject, err
    52  }
    53  
    54  // Return represents an ABI method return value
    55  type Return struct {
    56  	// The type of the return value as a string. See the method GetTypeObject to
    57  	// obtain the ABI type object
    58  	Type string `json:"type"`
    59  	// A hidden type object cache that holds the parsed type object
    60  	typeObject *Type `json:"-"`
    61  	// Optional, user-friendly description for the return value
    62  	Desc string `json:"desc,omitempty"`
    63  }
    64  
    65  // IsVoid checks if this return type is void, meaning the method does not have
    66  // any return value
    67  func (r Return) IsVoid() bool {
    68  	return r.Type == VoidReturnType
    69  }
    70  
    71  // GetTypeObject parses and returns the ABI type object for this return type.
    72  // An error will be returned if this is a void return type.
    73  func (r *Return) GetTypeObject() (Type, error) {
    74  	if r.IsVoid() {
    75  		return Type{}, fmt.Errorf("Invalid operation on void return type")
    76  	}
    77  	if r.typeObject != nil {
    78  		return *r.typeObject, nil
    79  	}
    80  	typeObject, err := TypeOf(r.Type)
    81  	if err == nil {
    82  		r.typeObject = &typeObject
    83  	}
    84  	return typeObject, err
    85  }
    86  
    87  // Method represents an individual ABI method definition
    88  type Method struct {
    89  	// The name of the method
    90  	Name string `json:"name"`
    91  	// Optional, user-friendly description for the method
    92  	Desc string `json:"desc,omitempty"`
    93  	// The arguments of the method, in order
    94  	Args []Arg `json:"args"`
    95  	// Information about the method's return value
    96  	Returns Return `json:"returns"`
    97  }
    98  
    99  // MethodFromSignature decoded a method signature string into a Method object.
   100  func MethodFromSignature(methodStr string) (Method, error) {
   101  	name, argTypesStr, retTypeStr, err := avm_abi.ParseMethodSignature(methodStr)
   102  	if err != nil {
   103  		return Method{}, err
   104  	}
   105  
   106  	returnType := Return{Type: retTypeStr}
   107  	if !returnType.IsVoid() {
   108  		// fill type object cache and catch any errors
   109  		_, err := returnType.GetTypeObject()
   110  		if err != nil {
   111  			return Method{}, fmt.Errorf("Could not parse method return type: %w", err)
   112  		}
   113  	}
   114  
   115  	args := make([]Arg, len(argTypesStr))
   116  	for i, argTypeStr := range argTypesStr {
   117  		args[i].Type = argTypeStr
   118  
   119  		if IsTransactionType(argTypeStr) || IsReferenceType(argTypeStr) {
   120  			continue
   121  		}
   122  
   123  		// fill type object cache and catch any errors
   124  		_, err := args[i].GetTypeObject()
   125  		if err != nil {
   126  			return Method{}, fmt.Errorf("Could not parse argument type at index %d: %w", i, err)
   127  		}
   128  	}
   129  
   130  	return Method{
   131  		Name:    name,
   132  		Args:    args,
   133  		Returns: returnType,
   134  	}, nil
   135  }
   136  
   137  // GetSignature calculates and returns the signature of the method
   138  func (method *Method) GetSignature() string {
   139  	var methodSignature string
   140  	methodSignature += method.Name + "("
   141  
   142  	var strTypes []string
   143  	for _, arg := range method.Args {
   144  		strTypes = append(strTypes, arg.Type)
   145  	}
   146  
   147  	methodSignature += strings.Join(strTypes, ",")
   148  	methodSignature += ")"
   149  	methodSignature += method.Returns.Type
   150  
   151  	return methodSignature
   152  }
   153  
   154  // GetSelector calculates and returns the 4-byte selector of the method
   155  func (method *Method) GetSelector() []byte {
   156  	sig := method.GetSignature()
   157  	sigHash := sha512.Sum512_256([]byte(sig))
   158  	return sigHash[:4]
   159  }
   160  
   161  // GetTxCount returns the number of transactions required to invoke this method
   162  func (method *Method) GetTxCount() int {
   163  	cnt := 1
   164  	for _, arg := range method.Args {
   165  		if arg.IsTransactionArg() {
   166  			cnt++
   167  		}
   168  	}
   169  	return cnt
   170  }
   171  
   172  func GetMethodByName(methods []Method, name string) (Method, error) {
   173  	var filteredMethods []Method
   174  	for _, method := range methods {
   175  		if method.Name == name {
   176  			filteredMethods = append(filteredMethods, method)
   177  		}
   178  	}
   179  
   180  	if len(filteredMethods) > 1 {
   181  		var sigs []string
   182  		for _, method := range filteredMethods {
   183  			sigs = append(sigs, method.GetSignature())
   184  		}
   185  
   186  		return Method{}, fmt.Errorf("found %d methods with the same name %s", len(filteredMethods), strings.Join(sigs, ","))
   187  	}
   188  
   189  	if len(filteredMethods) == 0 {
   190  		return Method{}, fmt.Errorf("found 0 methods with the name %s", name)
   191  	}
   192  
   193  	return filteredMethods[0], nil
   194  }
   195  
   196  // Interface represents an ABI interface, which is a logically grouped
   197  // collection of methods
   198  type Interface struct {
   199  	// A user-friendly name for the interface
   200  	Name string `json:"name"`
   201  	// Optional, user-friendly description for the interface
   202  	Desc string `json:"desc,omitempty"`
   203  	// The methods that the interface contains
   204  	Methods []Method `json:"methods"`
   205  }
   206  
   207  func (i *Interface) GetMethodByName(name string) (Method, error) {
   208  	return GetMethodByName(i.Methods, name)
   209  }
   210  
   211  // ContractNetworkInfo contains network-specific information about the contract
   212  type ContractNetworkInfo struct {
   213  	// The application ID of the contract for this network
   214  	AppID uint64 `json:"appID"`
   215  }
   216  
   217  // Contract represents an ABI contract, which is a concrete set of methods
   218  // implemented by a single app
   219  type Contract struct {
   220  	// A user-friendly name for the contract
   221  	Name string `json:"name"`
   222  	// Optional, user-friendly description for the contract
   223  	Desc string `json:"desc,omitempty"`
   224  	// Optional information about the contract's instances across different
   225  	// networks
   226  	Networks map[string]ContractNetworkInfo `json:"networks,omitempty"`
   227  	// The methods that the contract implements
   228  	Methods []Method `json:"methods"`
   229  }
   230  
   231  func (c *Contract) GetMethodByName(name string) (Method, error) {
   232  	return GetMethodByName(c.Methods, name)
   233  }