github.com/0xsequence/ethkit@v1.25.0/ethcoder/abi.go (about)

     1  package ethcoder
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/0xsequence/ethkit/go-ethereum/accounts/abi"
    12  	"github.com/0xsequence/ethkit/go-ethereum/common"
    13  	"github.com/0xsequence/ethkit/go-ethereum/common/hexutil"
    14  )
    15  
    16  func AbiCoder(argTypes []string, argValues []interface{}) ([]byte, error) {
    17  	if len(argTypes) != len(argValues) {
    18  		return nil, errors.New("invalid arguments - types and values do not match")
    19  	}
    20  	args, err := buildArgumentsFromTypes(argTypes)
    21  	if err != nil {
    22  		return nil, fmt.Errorf("failed to build abi: %v", err)
    23  	}
    24  	return args.Pack(argValues...)
    25  }
    26  
    27  func AbiCoderHex(argTypes []string, argValues []interface{}) (string, error) {
    28  	b, err := AbiCoder(argTypes, argValues)
    29  	if err != nil {
    30  		return "", err
    31  	}
    32  	h := hexutil.Encode(b)
    33  	return h, nil
    34  }
    35  
    36  func AbiDecoder(argTypes []string, input []byte, argValues []interface{}) error {
    37  	if len(argTypes) != len(argValues) {
    38  		return errors.New("invalid arguments - types and values do not match")
    39  	}
    40  	args, err := buildArgumentsFromTypes(argTypes)
    41  	if err != nil {
    42  		return fmt.Errorf("failed to build abi: %v", err)
    43  	}
    44  	values, err := args.Unpack(input)
    45  	if err != nil {
    46  		return err
    47  	}
    48  	if len(args) > 1 {
    49  		return args.Copy(&argValues, values)
    50  	} else {
    51  		return args.Copy(&argValues[0], values)
    52  	}
    53  }
    54  
    55  func AbiDecoderWithReturnedValues(argTypes []string, input []byte) ([]interface{}, error) {
    56  	args, err := buildArgumentsFromTypes(argTypes)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("failed to build abi: %v", err)
    59  	}
    60  	return args.UnpackValues(input)
    61  }
    62  
    63  func AbiEncodeMethodCalldata(methodExpr string, argValues []interface{}) ([]byte, error) {
    64  	mabi, methodName, err := ParseMethodABI(methodExpr, "")
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	data, err := mabi.Pack(methodName, argValues...)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return data, nil
    73  }
    74  
    75  func AbiEncodeMethodCalldataFromStringValues(methodExpr string, argStringValues []string) ([]byte, error) {
    76  	_, argsList, err := parseMethodExpr(methodExpr)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	argTypes := []string{}
    81  	for _, v := range argsList {
    82  		argTypes = append(argTypes, v.Type)
    83  	}
    84  
    85  	argValues, err := AbiUnmarshalStringValues(argTypes, argStringValues)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	return AbiEncodeMethodCalldata(methodExpr, argValues)
    90  }
    91  
    92  func AbiDecodeExpr(expr string, input []byte, argValues []interface{}) error {
    93  	argsList := parseArgumentExpr(expr)
    94  	argTypes := []string{}
    95  	for _, v := range argsList {
    96  		argTypes = append(argTypes, v.Type)
    97  	}
    98  	return AbiDecoder(argTypes, input, argValues)
    99  }
   100  
   101  func AbiDecodeExprAndStringify(expr string, input []byte) ([]string, error) {
   102  	argsList := parseArgumentExpr(expr)
   103  	argTypes := []string{}
   104  	for _, v := range argsList {
   105  		argTypes = append(argTypes, v.Type)
   106  	}
   107  
   108  	return AbiMarshalStringValues(argTypes, input)
   109  }
   110  
   111  func AbiMarshalStringValues(argTypes []string, input []byte) ([]string, error) {
   112  	values, err := AbiDecoderWithReturnedValues(argTypes, input)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return StringifyValues(values)
   117  }
   118  
   119  // AbiDecodeStringValues will take an array of ethereum types and string values, and decode
   120  // the string values to runtime objects.
   121  func AbiUnmarshalStringValues(argTypes []string, stringValues []string) ([]interface{}, error) {
   122  	if len(argTypes) != len(stringValues) {
   123  		return nil, fmt.Errorf("ethcoder: argTypes and stringValues must be of equal length")
   124  	}
   125  
   126  	values := []interface{}{}
   127  
   128  	for i, typ := range argTypes {
   129  		s := stringValues[i]
   130  
   131  		switch typ {
   132  		case "address":
   133  			// expected "0xabcde......"
   134  			if !strings.HasPrefix(s, "0x") {
   135  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting address in hex", i)
   136  			}
   137  			values = append(values, common.HexToAddress(s))
   138  			continue
   139  
   140  		case "string":
   141  			// expected: string value
   142  			values = append(values, s)
   143  			continue
   144  
   145  		case "bytes":
   146  			// expected: bytes in hex encoding with 0x prefix
   147  			if strings.HasPrefix(s, "0x") {
   148  				values = append(values, common.Hex2Bytes(s[2:]))
   149  				continue
   150  			} else {
   151  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bytes in hex", i)
   152  			}
   153  
   154  		case "bool":
   155  			// expected: "true" | "false"
   156  			if s == "true" {
   157  				values = append(values, true)
   158  			} else if s == "false" {
   159  				values = append(values, false)
   160  			} else {
   161  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bool as 'true' or 'false'", i)
   162  			}
   163  			continue
   164  		}
   165  
   166  		// numbers
   167  		if match := regexArgNumber.FindStringSubmatch(typ); len(match) > 0 {
   168  			size, err := strconv.ParseInt(match[2], 10, 64)
   169  			if err != nil {
   170  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting %s. reason: %w", i, typ, err)
   171  			}
   172  			if (size%8 != 0) || size == 0 || size > 256 {
   173  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ)
   174  			}
   175  
   176  			num := big.NewInt(0)
   177  			num, ok := num.SetString(s, 10)
   178  			if !ok {
   179  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s)
   180  			}
   181  			values = append(values, num)
   182  			continue
   183  		}
   184  
   185  		// bytesXX (fixed)
   186  		if match := regexArgBytes.FindStringSubmatch(typ); len(match) > 0 {
   187  			if !strings.HasPrefix(s, "0x") {
   188  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bytes in hex", i)
   189  			}
   190  			size, err := strconv.ParseInt(match[1], 10, 64)
   191  			if err != nil {
   192  				return nil, err
   193  			}
   194  			if size == 0 || size > 32 {
   195  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. bytes type '%s' is invalid", i, typ)
   196  			}
   197  			val := common.Hex2Bytes(s[2:])
   198  			if int64(len(val)) != size {
   199  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. %s type expects a %d byte value but received %d", i, typ, size, len(val))
   200  			}
   201  			values = append(values, val)
   202  			continue
   203  		}
   204  
   205  		// arrays
   206  		if match := regexArgArray.FindStringSubmatch(typ); len(match) > 0 {
   207  			baseTyp := match[1]
   208  			if match[2] == "" {
   209  				match[2] = "0"
   210  			}
   211  			count, err := strconv.ParseInt(match[2], 10, 64)
   212  			if err != nil {
   213  				return nil, err
   214  			}
   215  
   216  			if baseTyp != "address" {
   217  				submatch := regexArgNumber.FindStringSubmatch(baseTyp)
   218  				if len(submatch) == 0 {
   219  					return nil, fmt.Errorf("ethcoder: value at position %d of type %s is unsupported. Only number string arrays are presently supported.", i, typ)
   220  				}
   221  			}
   222  
   223  			var stringValues []string
   224  			err = json.Unmarshal([]byte(s), &stringValues)
   225  			if err != nil {
   226  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. failed to unmarshal json string array '%s'", i, s)
   227  			}
   228  			if count > 0 && len(stringValues) != int(count) {
   229  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. array size does not match required size of %d", i, count)
   230  			}
   231  
   232  			var arrayArgs []string
   233  			for i := 0; i < len(stringValues); i++ {
   234  				arrayArgs = append(arrayArgs, baseTyp)
   235  			}
   236  
   237  			arrayValues, err := AbiUnmarshalStringValues(arrayArgs, stringValues)
   238  			if err != nil {
   239  				return nil, fmt.Errorf("ethcoder: value at position %d is invalid. failed to get string values for array - %w", i, err)
   240  			}
   241  
   242  			if baseTyp == "address" {
   243  				var addresses []common.Address
   244  				for _, element := range arrayValues {
   245  					address, ok := element.(common.Address)
   246  					if !ok {
   247  						return nil, fmt.Errorf("ethcoder: expected common.Address, got %v", element)
   248  					}
   249  					addresses = append(addresses, address)
   250  				}
   251  				values = append(values, addresses)
   252  			} else {
   253  				var bnArray []*big.Int
   254  				for _, n := range arrayValues {
   255  					bn, ok := n.(*big.Int)
   256  					if !ok {
   257  						return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting array element to be *big.Int", i)
   258  					}
   259  					bnArray = append(bnArray, bn)
   260  				}
   261  				values = append(values, bnArray)
   262  			}
   263  		}
   264  	}
   265  
   266  	return values, nil
   267  }
   268  
   269  // ParseMethodABI will return an `abi.ABI` object from the short-hand method string expression,
   270  // for example, methodExpr: `balanceOf(address)` returnsExpr: `uint256`
   271  func ParseMethodABI(methodExpr, returnsExpr string) (*abi.ABI, string, error) {
   272  	var methodName string
   273  	var inputArgs, outputArgs []abiArgument
   274  	var err error
   275  
   276  	methodName, inputArgs, err = parseMethodExpr(methodExpr)
   277  	if err != nil {
   278  		return nil, "", err
   279  	}
   280  
   281  	if returnsExpr != "" {
   282  		outputArgs = parseArgumentExpr(returnsExpr)
   283  	}
   284  
   285  	// generate method abi json for parsing
   286  	methodABI := abiJSON{
   287  		Name:    methodName,
   288  		Type:    "function",
   289  		Inputs:  inputArgs,
   290  		Outputs: outputArgs,
   291  	}
   292  
   293  	abiJSON, err := json.Marshal(methodABI)
   294  	if err != nil {
   295  		return nil, methodName, err
   296  	}
   297  
   298  	mabi, err := abi.JSON(strings.NewReader(fmt.Sprintf("[%s]", string(abiJSON))))
   299  	if err != nil {
   300  		return nil, methodName, err
   301  	}
   302  
   303  	return &mabi, methodName, nil
   304  }
   305  
   306  func buildArgumentsFromTypes(argTypes []string) (abi.Arguments, error) {
   307  	args := abi.Arguments{}
   308  	for _, argType := range argTypes {
   309  		abiType, err := abi.NewType(argType, "", nil)
   310  		if err != nil {
   311  			return nil, err
   312  		}
   313  		args = append(args, abi.Argument{Type: abiType})
   314  	}
   315  	return args, nil
   316  }
   317  
   318  func parseMethodExpr(expr string) (string, []abiArgument, error) {
   319  	expr = strings.Trim(expr, " ")
   320  	idx := strings.Index(expr, "(")
   321  	if idx < 0 {
   322  		return "", nil, errors.New("ethcoder: invalid input expr. expected format is: methodName(arg1Type, arg2Type)")
   323  	}
   324  	methodName := expr[0:idx]
   325  	expr = expr[idx:]
   326  	if expr[0] != '(' || expr[len(expr)-1] != ')' {
   327  		return "", nil, errors.New("ethcoder: invalid input expr. expected format is: methodName(arg1Type, arg2Type)")
   328  	}
   329  	argsList := parseArgumentExpr(expr)
   330  	return methodName, argsList, nil
   331  }
   332  
   333  func parseArgumentExpr(expr string) []abiArgument {
   334  	args := []abiArgument{}
   335  	expr = strings.Trim(expr, "() ")
   336  	p := strings.Split(expr, ",")
   337  
   338  	if expr == "" {
   339  		return args
   340  	}
   341  	for _, v := range p {
   342  		v = strings.Trim(v, " ")
   343  		n := strings.Split(v, " ")
   344  		arg := abiArgument{Type: n[0]}
   345  		if len(n) > 1 {
   346  			arg.Name = n[1]
   347  		}
   348  		args = append(args, arg)
   349  	}
   350  	return args
   351  }
   352  
   353  type abiJSON struct {
   354  	Name    string        `json:"name"`
   355  	Inputs  []abiArgument `json:"inputs"`
   356  	Outputs []abiArgument `json:"outputs"`
   357  	Type    string        `json:"type"`
   358  }
   359  
   360  type abiArgument struct {
   361  	Name string `json:"name,omitempty"`
   362  	Type string `json:"type"`
   363  }