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 }