batch_helpers.go 5 KB
Newer Older
Mark Tyneway's avatar
Mark Tyneway committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
package safe

import (
	"fmt"
	"math/big"
	"reflect"
	"strconv"
	"strings"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
)

// stringifyArg converts a Go type to a string that is representable by ABI.
// To do so, this function must be recursive to handle nested tuples.
func stringifyArg(argument any) (string, error) {
	switch arg := argument.(type) {
	case common.Address:
		return arg.String(), nil
	case *common.Address:
		return arg.String(), nil
	case *big.Int:
		return arg.String(), nil
	case big.Int:
		return arg.String(), nil
	case bool:
		if arg {
			return "true", nil
		}
		return "false", nil
	case int64:
		return strconv.FormatInt(arg, 10), nil
	case int32:
		return strconv.FormatInt(int64(arg), 10), nil
	case int16:
		return strconv.FormatInt(int64(arg), 10), nil
	case int8:
		return strconv.FormatInt(int64(arg), 10), nil
	case int:
		return strconv.FormatInt(int64(arg), 10), nil
	case uint64:
		return strconv.FormatUint(uint64(arg), 10), nil
	case uint32:
		return strconv.FormatUint(uint64(arg), 10), nil
	case uint16:
		return strconv.FormatUint(uint64(arg), 10), nil
	case uint8:
		return strconv.FormatUint(uint64(arg), 10), nil
	case uint:
		return strconv.FormatUint(uint64(arg), 10), nil
	case []byte:
		return hexutil.Encode(arg), nil
	case []any:
		ret := make([]string, len(arg))
		for i, v := range arg {
			str, err := stringifyArg(v)
			if err != nil {
				return "", err
			}
			ret[i] = str
		}
		return "[" + strings.Join(ret, ",") + "]", nil
	default:
		typ := reflect.TypeOf(argument)
		if typ.Kind() == reflect.Ptr {
			typ = typ.Elem()
		}

		if typ.Kind() == reflect.Struct {
			v := reflect.ValueOf(argument)
			numField := v.NumField()
			ret := make([]string, numField)

			for i := 0; i < numField; i++ {
				val := v.Field(i).Interface()
				str, err := stringifyArg(val)
				if err != nil {
					return "", err
				}
				ret[i] = str
			}

			return "[" + strings.Join(ret, ",") + "]", nil
		}

		return "", fmt.Errorf("unknown type as argument: %T", arg)
	}
}

Mark Tyneway's avatar
Mark Tyneway committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
// unstringifyArg converts a string to a Go type.
func unstringifyArg(arg string, typ string) (any, error) {
	switch typ {
	case "address":
		return common.HexToAddress(arg), nil
	case "bool":
		return strconv.ParseBool(arg)
	case "uint8":
		val, err := strconv.ParseUint(arg, 10, 8)
		return uint8(val), err
	case "uint16":
		val, err := strconv.ParseUint(arg, 10, 16)
		return uint16(val), err
	case "uint32":
		val, err := strconv.ParseUint(arg, 10, 32)
		return uint32(val), err
	case "uint64":
		val, err := strconv.ParseUint(arg, 10, 64)
		return val, err
	case "int8":
		val, err := strconv.ParseInt(arg, 10, 8)
		return val, err
	case "int16":
		val, err := strconv.ParseInt(arg, 10, 16)
		return val, err
	case "int32":
		val, err := strconv.ParseInt(arg, 10, 32)
		return val, err
	case "int64":
		val, err := strconv.ParseInt(arg, 10, 64)
		return val, err
	case "uint256", "int256":
		val, ok := new(big.Int).SetString(arg, 10)
		if !ok {
			return nil, fmt.Errorf("failed to parse %s as big.Int", arg)
Mark Tyneway's avatar
Mark Tyneway committed
126
		}
Mark Tyneway's avatar
Mark Tyneway committed
127 128 129 130 131 132 133
		return val, nil
	case "string":
		return arg, nil
	case "bytes":
		return hexutil.Decode(arg)
	default:
		return nil, fmt.Errorf("unknown type: %s", typ)
Mark Tyneway's avatar
Mark Tyneway committed
134 135 136 137 138 139 140 141 142 143 144
	}
}

// createContractInput converts an abi.Argument to one or more ContractInputs.
func createContractInput(input abi.Argument, inputs []ContractInput) ([]ContractInput, error) {
	inputType, err := stringifyType(input.Type)
	if err != nil {
		return nil, err
	}

	internalType := input.Type.String()
145
	if input.Type.T == abi.TupleTy {
Mark Tyneway's avatar
Mark Tyneway committed
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
		internalType = input.Type.TupleRawName
	}

	components := make([]ContractInput, 0)
	for i, elem := range input.Type.TupleElems {
		e := *elem
		arg := abi.Argument{
			Name: input.Type.TupleRawNames[i],
			Type: e,
		}
		component, err := createContractInput(arg, inputs)
		if err != nil {
			return nil, err
		}
		components = append(components, component...)
	}

	contractInput := ContractInput{
		InternalType: internalType,
		Name:         input.Name,
		Type:         inputType,
		Components:   components,
	}

	inputs = append(inputs, contractInput)

	return inputs, nil
}

// stringifyType turns an abi.Type into a string
func stringifyType(t abi.Type) (string, error) {
	switch t.T {
	case abi.TupleTy:
		return "tuple", nil
	case abi.BoolTy:
		return t.String(), nil
	case abi.AddressTy:
		return t.String(), nil
	case abi.UintTy:
		return t.String(), nil
	case abi.IntTy:
		return t.String(), nil
	case abi.StringTy:
		return t.String(), nil
	case abi.BytesTy:
		return t.String(), nil
	default:
		return "", fmt.Errorf("unknown type: %d", t.T)
	}
}
196 197 198 199 200 201 202 203 204 205 206 207 208

// buildFunctionSignature builds a function signature from a ContractInput.
// It is recursive to handle tuples.
func buildFunctionSignature(input ContractInput) string {
	if input.Type == "tuple" {
		types := make([]string, len(input.Components))
		for i, component := range input.Components {
			types[i] = buildFunctionSignature(component)
		}
		return fmt.Sprintf("(%s)", strings.Join(types, ","))
	}
	return input.InternalType
}