From a9575aa3c612a4f356863fda8e6cffd0e3cd2910 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Wed, 16 Oct 2024 20:56:04 -0400 Subject: [PATCH] refactor abi methods --- ethcoder/abi.go | 656 +++--------------- ethcoder/abi_deprecated.go | 63 ++ ethcoder/abi_helpers.go | 597 ++++++++++++++++ ethcoder/abi_sig.go | 80 +++ .../{events_parser.go => abi_sig_parser.go} | 104 +-- ..._parser_test.go => abi_sig_parser_test.go} | 6 +- ethcoder/abi_sig_test.go | 152 ++++ ethcoder/abi_test.go | 98 +-- ethcoder/ethcoder.go | 14 +- ethcoder/events.go | 133 ++-- ethcoder/events_test.go | 66 +- ethcoder/solidity_pack.go | 2 + 12 files changed, 1192 insertions(+), 779 deletions(-) create mode 100644 ethcoder/abi_deprecated.go create mode 100644 ethcoder/abi_helpers.go create mode 100644 ethcoder/abi_sig.go rename ethcoder/{events_parser.go => abi_sig_parser.go} (63%) rename ethcoder/{events_parser_test.go => abi_sig_parser_test.go} (97%) create mode 100644 ethcoder/abi_sig_test.go diff --git a/ethcoder/abi.go b/ethcoder/abi.go index 6f57ee61..493e548e 100644 --- a/ethcoder/abi.go +++ b/ethcoder/abi.go @@ -1,617 +1,143 @@ package ethcoder import ( - "encoding/json" - "errors" "fmt" - "math/big" - "strconv" "strings" + "sync" "github.com/0xsequence/ethkit/go-ethereum/accounts/abi" - "github.com/0xsequence/ethkit/go-ethereum/common" - "github.com/0xsequence/ethkit/go-ethereum/common/hexutil" ) -func AbiCoder(argTypes []string, argValues []interface{}) ([]byte, error) { - if len(argTypes) != len(argValues) { - return nil, errors.New("invalid arguments - types and values do not match") - } - args, err := buildArgumentsFromTypes(argTypes) - if err != nil { - return nil, fmt.Errorf("failed to build abi: %v", err) - } - return args.Pack(argValues...) +type ABI struct { + rawABI abi.ABI + eventSigs map[string]ABISignature + methodSigs map[string]ABISignature + mu sync.RWMutex } -func AbiCoderHex(argTypes []string, argValues []interface{}) (string, error) { - b, err := AbiCoder(argTypes, argValues) - if err != nil { - return "", err +func NewABI() ABI { + return ABI{ + rawABI: abi.ABI{ + Methods: make(map[string]abi.Method), + Events: make(map[string]abi.Event), + Errors: make(map[string]abi.Error), + }, + eventSigs: make(map[string]ABISignature), + methodSigs: make(map[string]ABISignature), } - h := hexutil.Encode(b) - return h, nil } -func AbiDecoder(argTypes []string, input []byte, argValues []interface{}) error { - if len(argTypes) != len(argValues) { - return errors.New("invalid arguments - types and values do not match") - } - args, err := buildArgumentsFromTypes(argTypes) - if err != nil { - return fmt.Errorf("failed to build abi: %v", err) - } - values, err := args.Unpack(input) - if err != nil { - return err - } - if len(args) > 1 { - return args.Copy(&argValues, values) - } else { - return args.Copy(&argValues[0], values) - } -} +// type EventDef struct { +// TopicHash string `json:"topicHash"` // the event topic hash, ie. 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef +// Name string `json:"name"` // the event name, ie. Transfer +// Sig string `json:"sig"` // the event sig, ie. Transfer(address,address,uint256) +// ArgTypes []string `json:"argTypes"` // the event arg types, ie. [address, address, uint256] +// ArgNames []string `json:"argNames"` // the event arg names, ie. [from, to, value] or ["","",""] +// ArgIndexed []bool `json:"argIndexed"` // the event arg indexed flag, ie. [true, false, true] +// NumIndexed int `json:"-"` +// } -func AbiDecoderWithReturnedValues(argTypes []string, input []byte) ([]interface{}, error) { - args, err := buildArgumentsFromTypes(argTypes) - if err != nil { - return nil, fmt.Errorf("failed to build abi: %v", err) - } - return args.UnpackValues(input) +func (d *ABI) RawABI() abi.ABI { + return d.rawABI } -func AbiEncodeMethodCalldata(methodExpr string, argValues []interface{}) ([]byte, error) { - mabi, methodName, err := ParseMethodABI(methodExpr, "") +func (d *ABI) AddEvent(eventSig string) (string, error) { + abiSig, err := ParseABISignature(eventSig) if err != nil { - return nil, err + return "", nil } + return d.AddABISignature(abiSig, true) +} - data, err := mabi.Pack(methodName, argValues...) +func (d *ABI) AddMethod(methodSig string) (string, error) { + abiSig, err := ParseABISignature(methodSig) if err != nil { - return nil, err + return "", nil } - return data, nil + return d.AddABISignature(abiSig, false) } -func AbiEncodeMethodCalldataFromStringValues(methodExpr string, argStringValues []string) ([]byte, error) { - _, argsList, err := parseMethodExpr(methodExpr) +func (d *ABI) AddABISignature(abiSig ABISignature, isEvent bool) (string, error) { + contractABI, name, err := abiSig.ToABI(isEvent) if err != nil { - return nil, err - } - argTypes := []string{} - for _, v := range argsList { - argTypes = append(argTypes, v.Type) + return name, err } - argValues, err := AbiUnmarshalStringValues(argTypes, argStringValues) - if err != nil { - return nil, err + if isEvent { + d.mu.Lock() + d.rawABI.Events[name] = contractABI.Events[name] + d.eventSigs[name] = abiSig + d.mu.Unlock() + } else { + d.mu.Lock() + d.rawABI.Methods[name] = contractABI.Methods[name] + d.methodSigs[name] = abiSig + d.mu.Unlock() } - return AbiEncodeMethodCalldata(methodExpr, argValues) -} -func AbiDecodeExpr(expr string, input []byte, argValues []interface{}) error { - argsList := parseArgumentExpr(expr) - argTypes := []string{} - for _, v := range argsList { - argTypes = append(argTypes, v.Type) - } - return AbiDecoder(argTypes, input, argValues) + return name, nil } -func AbiDecodeExprAndStringify(expr string, input []byte) ([]string, error) { - argsList := parseArgumentExpr(expr) - argTypes := []string{} - for _, v := range argsList { - argTypes = append(argTypes, v.Type) - } - - return AbiMarshalStringValues(argTypes, input) +func (d *ABI) GetEventABISignature(eventName string) (ABISignature, bool) { + // but.. do we always have the abiSignature..? maybe.. we can memoize i guess.. + return ABISignature{}, false } -func AbiMarshalStringValues(argTypes []string, input []byte) ([]string, error) { - values, err := AbiDecoderWithReturnedValues(argTypes, input) - if err != nil { - return nil, err - } - return StringifyValues(values) +func (d *ABI) GetMethodABISignature(methodName string) (ABISignature, bool) { + // but.. do we always have the abiSignature..? maybe.. we can memoize i guess.. + return ABISignature{}, false } -// AbiUnmarshalStringValuesAny will take an array of ethereum types as string values, and decode -// the string values to runtime objects. This allows simple string value input from an app -// or user, and converts them to the appropriate runtime objects. -// -// NOTE: this is a variant of AbiUnmarshalStringValues but the `stringValues` argument type -// is []any, in order to support input types of array of strings for abi types like `address[]` -// and tuples. -// -// For example, some valid inputs: -// - AbiUnmarshalStringValuesAny([]string{"address","uint256"}, []any{"0x1234...", "543"}) -// returns []interface{}{common.HexToAddress("0x1234..."), big.NewInt(543)} -// - AbiUnmarshalStringValuesAny([]string{"address[]", []any{[]string{"0x1234...", "0x5678..."}}) -// returns []interface{}{[]common.Address{common.HexToAddress("0x1234..."), common.HexToAddress("0x5678...")}} -// - AbiUnmarshalStringValuesAny([]string{"(address,uint256)"}, []any{[]any{"0x1234...", "543"}}) -// returns []interface{}{[]interface{}{common.HexToAddress("0x1234..."), big.NewInt(543)}} -// -// The common use for this method is to pass a JSON object of string values for an abi method -// and have it properly encode to the native abi types. -func AbiUnmarshalStringValuesAny(argTypes []string, stringValues []any) ([]any, error) { - if len(argTypes) != len(stringValues) { - return nil, fmt.Errorf("ethcoder: argTypes and stringValues must be of equal length") - } - - values := []interface{}{} - - for i, typ := range argTypes { - v := stringValues[i] - - switch typ { - case "address": - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting address in hex", i) - } - - // expected "0xabcde......" - if !strings.HasPrefix(s, "0x") { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting address in hex", i) - } - values = append(values, common.HexToAddress(s)) - continue - - case "string": - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting string", i) - } - - // expected: string value - values = append(values, s) - continue - - case "bytes": - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) - } - - // expected: bytes in hex encoding with 0x prefix - if strings.HasPrefix(s, "0x") { - values = append(values, common.Hex2Bytes(s[2:])) - continue - } else { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) - } - - case "bool": - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bool as 'true' or 'false'", i) - } - - // expected: "true" | "false" - if s == "true" { - values = append(values, true) - } else if s == "false" { - values = append(values, false) - } else { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bool as 'true' or 'false'", i) - } - continue - } - - // numbers - if match := regexArgNumber.FindStringSubmatch(typ); len(match) > 0 { - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting number as string", i) - } - - size, err := strconv.ParseInt(match[2], 10, 64) - if err != nil { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting %s. reason: %w", i, typ, err) - } - if (size%8 != 0) || size == 0 || size > 256 { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ) - } - - num := big.NewInt(0) - num, ok = num.SetString(s, 10) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s) - } - values = append(values, num) - continue - } - - // bytesXX (fixed) - if match := regexArgBytes.FindStringSubmatch(typ); len(match) > 0 { - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) - } - - if !strings.HasPrefix(s, "0x") { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) - } - size, err := strconv.ParseInt(match[1], 10, 64) - if err != nil { - return nil, err - } - if size == 0 || size > 32 { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, bytes type '%s' is invalid", i, typ) - } - val := common.Hex2Bytes(s[2:]) - if int64(len(val)) != size { - 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)) - } - values = append(values, val) - continue - } - - // arrays - // TODO: can we avoid regexp...? - if match := regexArgArray.FindStringSubmatch(typ); len(match) > 0 { - baseTyp := match[1] - if match[2] == "" { - match[2] = "0" - } - count, err := strconv.ParseInt(match[2], 10, 64) - if err != nil { - return nil, err - } - - if baseTyp != "address" { - submatch := regexArgNumber.FindStringSubmatch(baseTyp) - if len(submatch) == 0 { - return nil, fmt.Errorf("ethcoder: value at position %d of type %s is unsupported. Only number string arrays are presently supported", i, typ) - } - } - - s, ok := v.([]string) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting string array", i) - } - - stringValues := s - if count > 0 && len(stringValues) != int(count) { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, array size does not match required size of %d", i, count) - } - - var arrayArgs []string - for i := 0; i < len(stringValues); i++ { - arrayArgs = append(arrayArgs, baseTyp) - } - - arrayValues, err := AbiUnmarshalStringValues(arrayArgs, stringValues) - if err != nil { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, failed to get string values for array - %w", i, err) - } - - if baseTyp == "address" { - var addresses []common.Address - for _, element := range arrayValues { - address, ok := element.(common.Address) - if !ok { - return nil, fmt.Errorf("ethcoder: expected common.Address, got %v", element) - } - addresses = append(addresses, address) - } - values = append(values, addresses) - } else { - var bnArray []*big.Int - for _, n := range arrayValues { - bn, ok := n.(*big.Int) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting array element to be *big.Int", i) - } - bnArray = append(bnArray, bn) - } - values = append(values, bnArray) - } - } - - // tuples - idx := strings.Index(typ, "(") - idx2 := strings.Index(typ, ")") - if idx >= 0 && idx2 > 0 { - - // TODO: add nested tuple support in the future: - // need to encode inner parts first, ind the next '(' after idx .. etc.. and call AbiUnmarshalStringValuesAny recursively - - t := typ[idx+1 : idx2] - idx := strings.Index(t, "(") - if idx >= 0 { - return nil, fmt.Errorf("ethcoder: value at position %d has found a nested tuple, which is unsupported currently, please contact support and make a request", i) - } - args := strings.Split(t, ",") - - var vv []any - switch v := v.(type) { - case []any: - if len(v) != len(args) { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, tuple size does not match required size of %d", i, len(args)) - } - vv = v - case []string: - if len(v) != len(args) { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, tuple size does not match required size of %d", i, len(args)) - } - vv = make([]any, len(v)) - for i, x := range v { - vv[i] = x - } - default: - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting array of any or string", i) - } - - out, err := AbiUnmarshalStringValuesAny(args, vv) - if err != nil { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid, failed to get string values for tuple: %w", i, err) - } - values = append(values, out) - } - } - - return values, nil +func (d *ABI) SetABI(rawABI abi.ABI) error { + d.rawABI = rawABI + return nil } -// AbiUnmarshalStringValues will take an array of ethereum types as string values, and decode -// the string values to runtime objects. This allows simple string value input from an app -// or user, and converts them to the appropriate runtime objects. -// -// The common use for this method is to pass a JSON object of string values for an abi method -// and have it properly encode to the native abi types. -func AbiUnmarshalStringValues(argTypes []string, stringValues []string) ([]any, error) { - if len(argTypes) != len(stringValues) { - return nil, fmt.Errorf("ethcoder: argTypes and stringValues must be of equal length") - } - - values := []interface{}{} - - for i, typ := range argTypes { - s := stringValues[i] - - switch typ { - case "address": - // expected "0xabcde......" - if !strings.HasPrefix(s, "0x") { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting address in hex", i) - } - values = append(values, common.HexToAddress(s)) - continue - - case "string": - // expected: string value - values = append(values, s) - continue - - case "bytes": - // expected: bytes in hex encoding with 0x prefix - if strings.HasPrefix(s, "0x") { - values = append(values, common.Hex2Bytes(s[2:])) - continue - } else { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bytes in hex", i) - } - - case "bool": - // expected: "true" | "false" - if s == "true" { - values = append(values, true) - } else if s == "false" { - values = append(values, false) - } else { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bool as 'true' or 'false'", i) - } - continue - } - - // numbers - if match := regexArgNumber.FindStringSubmatch(typ); len(match) > 0 { - size, err := strconv.ParseInt(match[2], 10, 64) - if err != nil { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting %s. reason: %w", i, typ, err) - } - if (size%8 != 0) || size == 0 || size > 256 { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ) - } - - num := big.NewInt(0) - num, ok := num.SetString(s, 10) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s) - } - values = append(values, num) - continue - } - - // bytesXX (fixed) - if match := regexArgBytes.FindStringSubmatch(typ); len(match) > 0 { - if !strings.HasPrefix(s, "0x") { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bytes in hex", i) - } - size, err := strconv.ParseInt(match[1], 10, 64) - if err != nil { - return nil, err - } - if size == 0 || size > 32 { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. bytes type '%s' is invalid", i, typ) - } - val := common.Hex2Bytes(s[2:]) - if int64(len(val)) != size { - 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)) - } - values = append(values, val) - continue - } - - // arrays - // TODO: can we avoid regexp...? - if match := regexArgArray.FindStringSubmatch(typ); len(match) > 0 { - baseTyp := match[1] - if match[2] == "" { - match[2] = "0" - } - count, err := strconv.ParseInt(match[2], 10, 64) - if err != nil { - return nil, err - } - - if baseTyp != "address" { - submatch := regexArgNumber.FindStringSubmatch(baseTyp) - if len(submatch) == 0 { - return nil, fmt.Errorf("ethcoder: value at position %d of type %s is unsupported. Only number string arrays are presently supported", i, typ) - } - } - - var stringValues []string - err = json.Unmarshal([]byte(s), &stringValues) - if err != nil { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. failed to unmarshal json string array '%s'", i, s) - } - if count > 0 && len(stringValues) != int(count) { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. array size does not match required size of %d", i, count) - } - - var arrayArgs []string - for i := 0; i < len(stringValues); i++ { - arrayArgs = append(arrayArgs, baseTyp) - } - - arrayValues, err := AbiUnmarshalStringValues(arrayArgs, stringValues) - if err != nil { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. failed to get string values for array - %w", i, err) - } - - if baseTyp == "address" { - var addresses []common.Address - for _, element := range arrayValues { - address, ok := element.(common.Address) - if !ok { - return nil, fmt.Errorf("ethcoder: expected common.Address, got %v", element) - } - addresses = append(addresses, address) - } - values = append(values, addresses) - } else { - var bnArray []*big.Int - for _, n := range arrayValues { - bn, ok := n.(*big.Int) - if !ok { - return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting array element to be *big.Int", i) - } - bnArray = append(bnArray, bn) - } - values = append(values, bnArray) - } - } - - // tuples - idx := strings.Index(typ, "(") - idx2 := strings.Index(typ, ")") - if idx >= 0 && idx2 > 0 { - // NOTE: perhaps we can support tuples here too..? but really its better to use - // AbiUnmarshalStringValuesAny anyways. - return nil, fmt.Errorf("ethcoder: tuples are not supported by this method, use AbiUnmarshalStringValuesAny instead") - } - } - - return values, nil -} - -// ParseMethodABI will return an `abi.ABI` object from the short-hand method string expression, -// for example, methodExpr: `balanceOf(address)` returnsExpr: `uint256` -func ParseMethodABI(methodExpr, returnsExpr string) (*abi.ABI, string, error) { - var methodName string - var inputArgs, outputArgs []abiArgument - var err error - - methodName, inputArgs, err = parseMethodExpr(methodExpr) +func (d *ABI) AddABIJSON(j string) error { + rawABI, err := abi.JSON(strings.NewReader(j)) if err != nil { - return nil, "", err + return err } + d.rawABI = rawABI + return nil +} - if returnsExpr != "" { - outputArgs = parseArgumentExpr(returnsExpr) +func (d *ABI) EncodeMethodCalldata(methodName string, argValues []interface{}) ([]byte, error) { + _, ok := d.rawABI.Methods[methodName] + if !ok { + return nil, fmt.Errorf("method %s not found", methodName) } + return d.rawABI.Pack(methodName, argValues...) +} - // generate method abi json for parsing - methodABI := abiJSON{ - Name: methodName, - Type: "function", - Inputs: inputArgs, - Outputs: outputArgs, +func (d *ABI) EncodeMethodCalldataFromStringValuesAny(methodName string, argStringValues []any) ([]byte, error) { + _, ok := d.rawABI.Methods[methodName] + if !ok { + return nil, fmt.Errorf("method %s not found", methodName) } - - abiJSON, err := json.Marshal(methodABI) - if err != nil { - return nil, methodName, err + abiSig, ok := d.methodSigs[methodName] + if !ok { + return nil, fmt.Errorf("method %s not found", methodName) } - - mabi, err := abi.JSON(strings.NewReader(fmt.Sprintf("[%s]", string(abiJSON)))) + argValues, err := ABIUnmarshalStringValuesAny(abiSig.ArgTypes, argStringValues) if err != nil { - return nil, methodName, err - } - - return &mabi, methodName, nil -} - -func buildArgumentsFromTypes(argTypes []string) (abi.Arguments, error) { - args := abi.Arguments{} - for _, argType := range argTypes { - abiType, err := abi.NewType(argType, "", nil) - if err != nil { - return nil, err - } - args = append(args, abi.Argument{Type: abiType}) + return nil, err } - return args, nil + return d.rawABI.Pack(methodName, argValues...) } -func parseMethodExpr(expr string) (string, []abiArgument, error) { - expr = strings.Trim(expr, " ") - idx := strings.Index(expr, "(") - if idx < 0 { - return "", nil, errors.New("ethcoder: invalid input expr. expected format is: methodName(arg1Type, arg2Type)") - } - methodName := expr[0:idx] - expr = expr[idx:] - if expr[0] != '(' || expr[len(expr)-1] != ')' { - return "", nil, errors.New("ethcoder: invalid input expr. expected format is: methodName(arg1Type, arg2Type)") +func (d *ABI) EncodeMethodCalldataFromStringValues(methodName string, argStringValues []string) ([]byte, error) { + _, ok := d.rawABI.Methods[methodName] + if !ok { + return nil, fmt.Errorf("method %s not found", methodName) } - argsList := parseArgumentExpr(expr) - return methodName, argsList, nil -} - -func parseArgumentExpr(expr string) []abiArgument { - args := []abiArgument{} - expr = strings.Trim(expr, "() ") - p := strings.Split(expr, ",") - - if expr == "" { - return args + abiSig, ok := d.methodSigs[methodName] + if !ok { + return nil, fmt.Errorf("method %s not found", methodName) } - for _, v := range p { - v = strings.Trim(v, " ") - n := strings.Split(v, " ") - arg := abiArgument{Type: n[0]} - if len(n) > 1 { - arg.Name = n[1] - } - args = append(args, arg) + argValues, err := ABIUnmarshalStringValues(abiSig.ArgTypes, argStringValues) + if err != nil { + return nil, err } - return args -} - -type abiJSON struct { - Name string `json:"name"` - Inputs []abiArgument `json:"inputs"` - Outputs []abiArgument `json:"outputs"` - Type string `json:"type"` -} - -type abiArgument struct { - Name string `json:"name,omitempty"` - Type string `json:"type"` + return d.rawABI.Pack(methodName, argValues...) } diff --git a/ethcoder/abi_deprecated.go b/ethcoder/abi_deprecated.go new file mode 100644 index 00000000..0bb82d95 --- /dev/null +++ b/ethcoder/abi_deprecated.go @@ -0,0 +1,63 @@ +package ethcoder + +// Deprecated: use ABIPackArguments instead +func AbiCoder(argTypes []string, argValues []interface{}) ([]byte, error) { + return ABIPackArguments(argTypes, argValues) +} + +// Deprecated: use ABIPackArgumentsHex instead +func AbiCoderHex(argTypes []string, argValues []interface{}) (string, error) { + return ABIPackArgumentsHex(argTypes, argValues) +} + +// Deprecated: use ABIUnpackArgumentsByRef instead +func AbiDecoder(argTypes []string, input []byte, outArgValues []interface{}) error { + return ABIUnpackArgumentsByRef(argTypes, input, outArgValues) +} + +// Deprecated: use ABIUnpackArguments instead +func AbiDecoderWithReturnedValues(argTypes []string, input []byte) ([]interface{}, error) { + return ABIUnpackArguments(argTypes, input) +} + +// Deprecated: use ABIEncodeMethodCalldataFromStringValuesAny instead +func AbiEncodeMethodCalldataFromStringValuesAny(methodSig string, argStringValues []any) ([]byte, error) { + return ABIEncodeMethodCalldataFromStringValuesAny(methodSig, argStringValues) +} + +// Deprecated: use ABIEncodeMethodCalldataFromStringValuesAny instead +func AbiEncodeMethodCalldataFromStringValues(methodExpr string, argStringValues []string) ([]byte, error) { + return ABIEncodeMethodCalldataFromStringValues(methodExpr, argStringValues) +} + +// Deprecated: use ABIUnpack instead +func AbiDecodeExpr(expr string, input []byte, argValues []interface{}) error { + return ABIUnpack(expr, input, argValues) +} + +// Deprecated: use ABIUnpackAndStringify instead +func AbiDecodeExprAndStringify(expr string, input []byte) ([]string, error) { + return ABIUnpackAndStringify(expr, input) +} + +// Deprecated: use ABIMarshalStringValues instead +func AbiMarshalStringValues(argTypes []string, input []byte) ([]string, error) { + return ABIMarshalStringValues(argTypes, input) +} + +// AbiUnmarshalStringValues will take an array of ethereum types as string values, and decode +// the string values to runtime objects. This allows simple string value input from an app +// or user, and converts them to the appropriate runtime objects. +// +// The common use for this method is to pass a JSON object of string values for an abi method +// and have it properly encode to the native abi types. +// +// Deprecated: use ABIUnmarshalStringValues instead +func AbiUnmarshalStringValues(argTypes []string, stringValues []string) ([]any, error) { + return ABIUnmarshalStringValues(argTypes, stringValues) +} + +// Deprecated: use ABIEncodeMethodCalldata instead +func AbiEncodeMethodCalldata(methodExpr string, argValues []interface{}) ([]byte, error) { + return ABIEncodeMethodCalldata(methodExpr, argValues) +} diff --git a/ethcoder/abi_helpers.go b/ethcoder/abi_helpers.go new file mode 100644 index 00000000..4a2dd419 --- /dev/null +++ b/ethcoder/abi_helpers.go @@ -0,0 +1,597 @@ +package ethcoder + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/0xsequence/ethkit/go-ethereum/accounts/abi" + "github.com/0xsequence/ethkit/go-ethereum/common" + "github.com/0xsequence/ethkit/go-ethereum/common/hexutil" +) + +func ABIPackArguments(argTypes []string, argValues []interface{}) ([]byte, error) { + if len(argTypes) != len(argValues) { + return nil, errors.New("invalid arguments - types and values do not match") + } + args, err := buildArgumentsFromTypes(argTypes) + if err != nil { + return nil, fmt.Errorf("failed to build abi: %v", err) + } + return args.Pack(argValues...) +} + +func ABIPackArgumentsHex(argTypes []string, argValues []interface{}) (string, error) { + b, err := AbiCoder(argTypes, argValues) + if err != nil { + return "", err + } + h := hexutil.Encode(b) + return h, nil +} + +func ABIUnpackArgumentsByRef(argTypes []string, input []byte, outArgValues []interface{}) error { + if len(argTypes) != len(outArgValues) { + return errors.New("invalid arguments - types and values do not match") + } + args, err := buildArgumentsFromTypes(argTypes) + if err != nil { + return fmt.Errorf("failed to build abi: %v", err) + } + values, err := args.Unpack(input) + if err != nil { + return err + } + if len(args) > 1 { + return args.Copy(&outArgValues, values) + } else { + return args.Copy(&outArgValues[0], values) + } +} + +func ABIUnpackArguments(argTypes []string, input []byte) ([]interface{}, error) { + args, err := buildArgumentsFromTypes(argTypes) + if err != nil { + return nil, fmt.Errorf("failed to build abi: %v", err) + } + return args.UnpackValues(input) +} + +func ABIEncodeMethodCalldataFromStringValuesAny(methodSig string, argStringValues []any) ([]byte, error) { + abi := NewABI() + // TODO/NOTE: we are doing this again.. bad.. + // maybe we should just remove this method entirely..? probably. + methodName, err := abi.AddMethod(methodSig) + if err != nil { + return nil, err + } + return abi.EncodeMethodCalldataFromStringValuesAny(methodName, argStringValues) + + // eventDef, err := ParseABISignature(methodSig) + // if err != nil { + // return nil, err + // } + // argTypes := eventDef.ArgTypes + // spew.Dump("zzzz", argTypes) + + // argValues, err := ABIUnmarshalStringValuesAny(argTypes, argStringValues) + // if err != nil { + // return nil, err + // } + + // // TODO: we're passing methodSig again here... we should pass abiSig instead + // return ABIEncodeMethodCalldata(methodSig, argValues) +} + +func ABIEncodeMethodCalldataFromStringValues(methodSig string, argStringValues []string) ([]byte, error) { + abi := NewABI() + // TODO/NOTE: we are doing this again.. bad.. + // maybe we should just remove this method entirely..? probably. + methodName, err := abi.AddMethod(methodSig) + if err != nil { + return nil, err + } + return abi.EncodeMethodCalldataFromStringValues(methodName, argStringValues) + + // abiSig, err := ParseABISignature(methodSig) + // if err != nil { + // return nil, err + // } + + // argValues, err := ABIUnmarshalStringValues(abiSig.ArgTypes, argStringValues) + // if err != nil { + // return nil, err + // } + + // return ABIEncodeMethodCalldata(methodSig, argValues) +} + +// TODO: rename ABIUnpackExpr(expr, input, argValues) +// or just ABIUnpack(abiXX) where abiXX can be json or expr .. +// TODO: change expr argument to abiXX +func ABIUnpack(exprSig string, input []byte, argValues []interface{}) error { + if len(exprSig) == 0 { + return errors.New("ethcoder: exprSig is required") + } + if exprSig[0] != '(' { + exprSig = "(" + exprSig + ")" + } + abiSig, err := ParseABISignature(exprSig) + if err != nil { + return err + } + return AbiDecoder(abiSig.ArgTypes, input, argValues) +} + +// TODO: rename to ABIUnpackAndStringify(abiJSONorExpr, ..) +// TODO: change expr argument to abiXX like abiJSONorExpr +func ABIUnpackAndStringify(exprSig string, input []byte) ([]string, error) { + if len(exprSig) == 0 { + return nil, errors.New("ethcoder: exprSig is required") + } + if exprSig[0] != '(' { + exprSig = "(" + exprSig + ")" + } + abiSig, err := ParseABISignature(exprSig) + if err != nil { + return nil, err + } + return AbiMarshalStringValues(abiSig.ArgTypes, input) +} + +// TODO: rename to...? +// * ABIBytesToStringValues ....? maybe more clear..? +// * ABIMarshalStringValues might work since we have AbiUnmarshalStringValuesAny as well.. +func ABIMarshalStringValues(argTypes []string, input []byte) ([]string, error) { + values, err := ABIUnpackArguments(argTypes, input) + if err != nil { + return nil, err + } + return StringifyValues(values) +} + +// AbiUnmarshalStringValuesAny will take an array of ethereum types as string values, and decode +// the string values to runtime objects. This allows simple string value input from an app +// or user, and converts them to the appropriate runtime objects. +// +// NOTE: this is a variant of AbiUnmarshalStringValues but the `stringValues` argument type +// is []any, in order to support input types of array of strings for abi types like `address[]` +// and tuples. +// +// For example, some valid inputs: +// - AbiUnmarshalStringValuesAny([]string{"address","uint256"}, []any{"0x1234...", "543"}) +// returns []interface{}{common.HexToAddress("0x1234..."), big.NewInt(543)} +// - AbiUnmarshalStringValuesAny([]string{"address[]", []any{[]string{"0x1234...", "0x5678..."}}) +// returns []interface{}{[]common.Address{common.HexToAddress("0x1234..."), common.HexToAddress("0x5678...")}} +// - AbiUnmarshalStringValuesAny([]string{"(address,uint256)"}, []any{[]any{"0x1234...", "543"}}) +// returns []interface{}{[]interface{}{common.HexToAddress("0x1234..."), big.NewInt(543)}} +// +// The common use for this method is to pass a JSON object of string values for an abi method +// and have it properly encode to the native abi types. +func ABIUnmarshalStringValuesAny(argTypes []string, stringValues []any) ([]any, error) { + if len(argTypes) != len(stringValues) { + return nil, fmt.Errorf("ethcoder: argTypes and stringValues must be of equal length") + } + + values := []interface{}{} + + for i, typ := range argTypes { + v := stringValues[i] + + switch typ { + case "address": + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting address in hex", i) + } + + // expected "0xabcde......" + if !strings.HasPrefix(s, "0x") { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting address in hex", i) + } + values = append(values, common.HexToAddress(s)) + continue + + case "string": + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting string", i) + } + + // expected: string value + values = append(values, s) + continue + + case "bytes": + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) + } + + // expected: bytes in hex encoding with 0x prefix + if strings.HasPrefix(s, "0x") { + values = append(values, common.Hex2Bytes(s[2:])) + continue + } else { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) + } + + case "bool": + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bool as 'true' or 'false'", i) + } + + // expected: "true" | "false" + if s == "true" { + values = append(values, true) + } else if s == "false" { + values = append(values, false) + } else { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bool as 'true' or 'false'", i) + } + continue + } + + // numbers + if match := regexArgNumber.FindStringSubmatch(typ); len(match) > 0 { + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting number as string", i) + } + + size, err := strconv.ParseInt(match[2], 10, 64) + if err != nil { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting %s. reason: %w", i, typ, err) + } + if (size%8 != 0) || size == 0 || size > 256 { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ) + } + + num := big.NewInt(0) + num, ok = num.SetString(s, 10) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s) + } + values = append(values, num) + continue + } + + // bytesXX (fixed) + if match := regexArgBytes.FindStringSubmatch(typ); len(match) > 0 { + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) + } + + if !strings.HasPrefix(s, "0x") { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting bytes in hex", i) + } + size, err := strconv.ParseInt(match[1], 10, 64) + if err != nil { + return nil, err + } + if size == 0 || size > 32 { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, bytes type '%s' is invalid", i, typ) + } + val := common.Hex2Bytes(s[2:]) + if int64(len(val)) != size { + 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)) + } + values = append(values, val) + continue + } + + // arrays + // TODO: can we avoid regexp...? + if match := regexArgArray.FindStringSubmatch(typ); len(match) > 0 { + baseTyp := match[1] + if match[2] == "" { + match[2] = "0" + } + count, err := strconv.ParseInt(match[2], 10, 64) + if err != nil { + return nil, err + } + + if baseTyp != "address" { + submatch := regexArgNumber.FindStringSubmatch(baseTyp) + if len(submatch) == 0 { + return nil, fmt.Errorf("ethcoder: value at position %d of type %s is unsupported. Only number string arrays are presently supported", i, typ) + } + } + + s, ok := v.([]string) + if !ok { + vv, ok := v.([]any) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting string array", i) + } + s = make([]string, len(vv)) + for j, x := range vv { + s[j], ok = x.(string) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting string array", i) + } + } + } + + stringValues := s + if count > 0 && len(stringValues) != int(count) { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, array size does not match required size of %d", i, count) + } + + var arrayArgs []string + for i := 0; i < len(stringValues); i++ { + arrayArgs = append(arrayArgs, baseTyp) + } + + arrayValues, err := ABIUnmarshalStringValues(arrayArgs, stringValues) + if err != nil { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, failed to get string values for array - %w", i, err) + } + + if baseTyp == "address" { + var addresses []common.Address + for _, element := range arrayValues { + address, ok := element.(common.Address) + if !ok { + return nil, fmt.Errorf("ethcoder: expected common.Address, got %v", element) + } + addresses = append(addresses, address) + } + values = append(values, addresses) + } else { + var bnArray []*big.Int + for _, n := range arrayValues { + bn, ok := n.(*big.Int) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting array element to be *big.Int", i) + } + bnArray = append(bnArray, bn) + } + values = append(values, bnArray) + } + } + + // tuples + idx := strings.Index(typ, "(") + idx2 := strings.Index(typ, ")") + if idx >= 0 && idx2 > 0 { + + // TODO: add nested tuple support in the future: + // need to encode inner parts first, ind the next '(' after idx .. etc.. and call AbiUnmarshalStringValuesAny recursively + + t := typ[idx+1 : idx2] + idx := strings.Index(t, "(") + if idx >= 0 { + return nil, fmt.Errorf("ethcoder: value at position %d has found a nested tuple, which is unsupported currently, please contact support and make a request", i) + } + args := strings.Split(t, ",") + + var vv []any + switch v := v.(type) { + case []any: + if len(v) != len(args) { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, tuple size does not match required size of %d", i, len(args)) + } + vv = v + case []string: + if len(v) != len(args) { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, tuple size does not match required size of %d", i, len(args)) + } + vv = make([]any, len(v)) + for i, x := range v { + vv[i] = x + } + default: + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, expecting array of any or string", i) + } + + out, err := ABIUnmarshalStringValuesAny(args, vv) + if err != nil { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid, failed to get string values for tuple: %w", i, err) + } + values = append(values, out) + } + } + + return values, nil +} + +// AbiUnmarshalStringValues will take an array of ethereum types as string values, and decode +// the string values to runtime objects. This allows simple string value input from an app +// or user, and converts them to the appropriate runtime objects. +// +// The common use for this method is to pass a JSON object of string values for an abi method +// and have it properly encode to the native abi types. +func ABIUnmarshalStringValues(argTypes []string, stringValues []string) ([]any, error) { + if len(argTypes) != len(stringValues) { + return nil, fmt.Errorf("ethcoder: argTypes and stringValues must be of equal length") + } + + values := []interface{}{} + + for i, typ := range argTypes { + s := stringValues[i] + + switch typ { + case "address": + // expected "0xabcde......" + if !strings.HasPrefix(s, "0x") { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting address in hex", i) + } + values = append(values, common.HexToAddress(s)) + continue + + case "string": + // expected: string value + values = append(values, s) + continue + + case "bytes": + // expected: bytes in hex encoding with 0x prefix + if strings.HasPrefix(s, "0x") { + values = append(values, common.Hex2Bytes(s[2:])) + continue + } else { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bytes in hex", i) + } + + case "bool": + // expected: "true" | "false" + if s == "true" { + values = append(values, true) + } else if s == "false" { + values = append(values, false) + } else { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bool as 'true' or 'false'", i) + } + continue + } + + // numbers + if match := regexArgNumber.FindStringSubmatch(typ); len(match) > 0 { + size, err := strconv.ParseInt(match[2], 10, 64) + if err != nil { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting %s. reason: %w", i, typ, err) + } + if (size%8 != 0) || size == 0 || size > 256 { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ) + } + + num := big.NewInt(0) + num, ok := num.SetString(s, 10) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s) + } + values = append(values, num) + continue + } + + // bytesXX (fixed) + if match := regexArgBytes.FindStringSubmatch(typ); len(match) > 0 { + if !strings.HasPrefix(s, "0x") { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting bytes in hex", i) + } + size, err := strconv.ParseInt(match[1], 10, 64) + if err != nil { + return nil, err + } + if size == 0 || size > 32 { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. bytes type '%s' is invalid", i, typ) + } + val := common.Hex2Bytes(s[2:]) + if int64(len(val)) != size { + 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)) + } + values = append(values, val) + continue + } + + // arrays + // TODO: can we avoid regexp...? + if match := regexArgArray.FindStringSubmatch(typ); len(match) > 0 { + baseTyp := match[1] + if match[2] == "" { + match[2] = "0" + } + count, err := strconv.ParseInt(match[2], 10, 64) + if err != nil { + return nil, err + } + + if baseTyp != "address" { + submatch := regexArgNumber.FindStringSubmatch(baseTyp) + if len(submatch) == 0 { + return nil, fmt.Errorf("ethcoder: value at position %d of type %s is unsupported. Only number string arrays are presently supported", i, typ) + } + } + + var stringValues []string + err = json.Unmarshal([]byte(s), &stringValues) + if err != nil { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. failed to unmarshal json string array '%s'", i, s) + } + if count > 0 && len(stringValues) != int(count) { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. array size does not match required size of %d", i, count) + } + + var arrayArgs []string + for i := 0; i < len(stringValues); i++ { + arrayArgs = append(arrayArgs, baseTyp) + } + + arrayValues, err := ABIUnmarshalStringValues(arrayArgs, stringValues) + if err != nil { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. failed to get string values for array - %w", i, err) + } + + if baseTyp == "address" { + var addresses []common.Address + for _, element := range arrayValues { + address, ok := element.(common.Address) + if !ok { + return nil, fmt.Errorf("ethcoder: expected common.Address, got %v", element) + } + addresses = append(addresses, address) + } + values = append(values, addresses) + } else { + var bnArray []*big.Int + for _, n := range arrayValues { + bn, ok := n.(*big.Int) + if !ok { + return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting array element to be *big.Int", i) + } + bnArray = append(bnArray, bn) + } + values = append(values, bnArray) + } + } + + // tuples + idx := strings.Index(typ, "(") + idx2 := strings.Index(typ, ")") + if idx >= 0 && idx2 > 0 { + // NOTE: perhaps we can support tuples here too..? but really its better to use + // AbiUnmarshalStringValuesAny anyways. + return nil, fmt.Errorf("ethcoder: tuples are not supported by this method, use AbiUnmarshalStringValuesAny instead") + } + } + + return values, nil +} + +// TODO: rename to... ABIEncodeMethodCalldata(methodExpr, argValues []interface{}) ([]byte, error) +// and have it call ABI.AddMethod(methodExpr) and then, call the .EncodeMethodCallData +func ABIEncodeMethodCalldata(methodSig string, argValues []interface{}) ([]byte, error) { + // TODO: we're parsing again, silly...... too much methodExpr parsing.. + + // NOTE: this is a bit of a hack, but it works for now + // this is a short-hand method for now, until we have a better way to do this + // maybe we should remove it altogether and just + // use ABI type? + + abi := NewABI() + methodName, err := abi.AddMethod(methodSig) + if err != nil { + return nil, err + } + return abi.EncodeMethodCalldata(methodName, argValues) +} + +func buildArgumentsFromTypes(argTypes []string) (abi.Arguments, error) { + args := abi.Arguments{} + for _, argType := range argTypes { + abiType, err := abi.NewType(argType, "", nil) + if err != nil { + return nil, err + } + args = append(args, abi.Argument{Type: abiType}) + } + return args, nil +} diff --git a/ethcoder/abi_sig.go b/ethcoder/abi_sig.go new file mode 100644 index 00000000..8cc16970 --- /dev/null +++ b/ethcoder/abi_sig.go @@ -0,0 +1,80 @@ +package ethcoder + +import ( + "fmt" + + "github.com/0xsequence/ethkit/go-ethereum/accounts/abi" +) + +type ABISignature struct { + Name string // the method or event name, ie. Transfer + Signature string // the abi signature string, ie. Transfer(address,address,uint256) + Hash string // the method/event topic hash, ie. 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef + ArgTypes []string // the method/event arg types, ie. [address, address, uint256] + ArgNames []string // the method/event arg names, ie. [from, to, value] or ["","",""] + ArgIndexed []bool // the event arg indexed flag, ie. [true, false, true] + NumIndexed int +} + +// TODO: search for word "event" everywhere.. + +func (e ABISignature) String() string { + if !(len(e.ArgTypes) == len(e.ArgIndexed) && len(e.ArgTypes) == len(e.ArgNames)) { + return "" + } + s := "" + for i := range e.ArgTypes { + s += e.ArgTypes[i] + if e.ArgIndexed[i] { + s += " indexed" + } + if e.ArgNames[i] != "" { + s += " " + e.ArgNames[i] + } + if i < len(e.ArgTypes)-1 { + s += "," + } + } + return fmt.Sprintf("%s(%s)", e.Name, s) +} + +func (s ABISignature) ToABI(isEvent bool) (abi.ABI, string, error) { + abiArgs := abi.Arguments{} + selector, err := abi.ParseSelector(s.Signature) + if err != nil { + return abi.ABI{}, "", err + } + + for i, argType := range s.ArgTypes { + selectorArg := selector.Inputs[i] + + argName := s.ArgNames[i] + if argName == "" { + argName = fmt.Sprintf("arg%d", i+1) + } + + typ, err := abi.NewType(selectorArg.Type, "", selectorArg.Components) + if err != nil { + return abi.ABI{}, "", fmt.Errorf("invalid abi argument type '%s': %w", argType, err) + } + + abiArgs = append(abiArgs, abi.Argument{Name: argName, Type: typ, Indexed: s.ArgIndexed[i]}) + } + + var contractABI abi.ABI + if isEvent { + abiEvent := abi.NewEvent(s.Name, s.Name, false, abiArgs) + contractABI = abi.ABI{ + Events: map[string]abi.Event{}, + } + contractABI.Events[s.Name] = abiEvent + } else { + abiMethod := abi.NewMethod(s.Name, s.Name, abi.Function, "", false, false, abiArgs, nil) + contractABI = abi.ABI{ + Methods: map[string]abi.Method{}, + } + contractABI.Methods[s.Name] = abiMethod + } + + return contractABI, s.Name, nil +} diff --git a/ethcoder/events_parser.go b/ethcoder/abi_sig_parser.go similarity index 63% rename from ethcoder/events_parser.go rename to ethcoder/abi_sig_parser.go index 95257130..d24ecf46 100644 --- a/ethcoder/events_parser.go +++ b/ethcoder/abi_sig_parser.go @@ -5,122 +5,92 @@ import ( "strings" ) -type EventDef struct { - TopicHash string `json:"topicHash"` // the event topic hash, ie. 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef - Name string `json:"name"` // the event name, ie. Transfer - Sig string `json:"sig"` // the event sig, ie. Transfer(address,address,uint256) - ArgTypes []string `json:"argTypes"` // the event arg types, ie. [address, address, uint256] - ArgNames []string `json:"argNames"` // the event arg names, ie. [from, to, value] or ["","",""] - ArgIndexed []bool `json:"argIndexed"` // the event arg indexed flag, ie. [true, false, true] - NumIndexed int `json:"-"` -} - -func (e EventDef) String() string { - if !(len(e.ArgTypes) == len(e.ArgIndexed) && len(e.ArgTypes) == len(e.ArgNames)) { - return "" - } - s := "" - for i := range e.ArgTypes { - s += e.ArgTypes[i] - if e.ArgIndexed[i] { - s += " indexed" - } - if e.ArgNames[i] != "" { - s += " " + e.ArgNames[i] - } - if i < len(e.ArgTypes)-1 { - s += "," - } - } - return fmt.Sprintf("%s(%s)", e.Name, s) -} - -func ParseEventDef(event string) (EventDef, error) { - eventDef := EventDef{ +func ParseABISignature(abiSignature string) (ABISignature, error) { + abiSig := ABISignature{ ArgTypes: []string{}, ArgIndexed: []bool{}, ArgNames: []string{}, } - var errInvalid = fmt.Errorf("event format is invalid, expecting Method(arg1,arg2,..)") + var errInvalid = fmt.Errorf("abi format is invalid, expecting Method(arg1,arg2,..)") - if !strings.Contains(event, "(") || !strings.Contains(event, ")") { - return eventDef, errInvalid + if !strings.Contains(abiSignature, "(") || !strings.Contains(abiSignature, ")") { + return abiSig, errInvalid } - a := strings.Count(event, "(") - b := strings.Count(event, ")") + a := strings.Count(abiSignature, "(") + b := strings.Count(abiSignature, ")") if a != b || a < 1 { - return eventDef, errInvalid + return abiSig, errInvalid } - a = strings.Index(event, "(") - b = strings.LastIndex(event, ")") + a = strings.Index(abiSignature, "(") + b = strings.LastIndex(abiSignature, ")") - method := strings.TrimSpace(event[:a]) - eventDef.Name = method + method := strings.TrimSpace(abiSignature[:a]) + abiSig.Name = method - args := strings.TrimSpace(event[a+1 : b]) + args := strings.TrimSpace(abiSignature[a+1 : b]) if args == "" { // no arguments, we are done - eventDef.Sig = fmt.Sprintf("%s()", method) + abiSig.Signature = fmt.Sprintf("%s()", method) } else { // event parser - tree, err := parseEventArgs(args, 0) + tree, err := parseABISignatureArgs(args, 0) if err != nil { - return eventDef, err + return abiSig, err } - sig, typs, indexed, names, err := groupEventSelectorTree(tree, true) + sig, typs, indexed, names, err := groupABISignatureTree(tree, true) if err != nil { - return eventDef, err + return abiSig, err } - eventDef.Sig = fmt.Sprintf("%s(%s)", method, sig) - eventDef.ArgTypes = typs + abiSig.Signature = fmt.Sprintf("%s(%s)", method, sig) + abiSig.ArgTypes = typs for i, name := range names { if name != "" { - eventDef.ArgNames = append(eventDef.ArgNames, name) + abiSig.ArgNames = append(abiSig.ArgNames, name) } else { - eventDef.ArgNames = append(eventDef.ArgNames, fmt.Sprintf("arg%d", i+1)) + abiSig.ArgNames = append(abiSig.ArgNames, fmt.Sprintf("arg%d", i+1)) } } - eventDef.ArgIndexed = indexed + abiSig.ArgIndexed = indexed } numIndexed := 0 - for _, indexed := range eventDef.ArgIndexed { + for _, indexed := range abiSig.ArgIndexed { if indexed { numIndexed++ } } - eventDef.NumIndexed = numIndexed + abiSig.NumIndexed = numIndexed - eventDef.TopicHash = Keccak256Hash([]byte(eventDef.Sig)).String() + abiSig.Hash = Keccak256Hash([]byte(abiSig.Signature)).String() - return eventDef, nil + return abiSig, nil } -type eventSelectorTree struct { +type abiSignatureTree struct { left string indexed []bool names []string - tuple []eventSelectorTree + tuple []abiSignatureTree tupleArray string tupleIndexed bool tupleName string - right []eventSelectorTree + right []abiSignatureTree } // parseEventArgs parses the event arguments and returns a tree structure // ie. "address indexed from, address indexed to, uint256 value". -func parseEventArgs(eventArgs string, iteration int) (eventSelectorTree, error) { +func parseABISignatureArgs(eventArgs string, iteration int) (abiSignatureTree, error) { args := strings.TrimSpace(eventArgs) // if iteration == 0 { // args = strings.ReplaceAll(eventArgs, " ", "") // } - out := eventSelectorTree{} + out := abiSignatureTree{} if args == "" { return out, nil } @@ -239,7 +209,7 @@ func parseEventArgs(eventArgs string, iteration int) (eventSelectorTree, error) // p2 if len(p2) > 0 { - out2, err := parseEventArgs(p2, iteration+1) + out2, err := parseABISignatureArgs(p2, iteration+1) if err != nil { return out, err } @@ -251,7 +221,7 @@ func parseEventArgs(eventArgs string, iteration int) (eventSelectorTree, error) // p3 if len(p3) > 0 { - out3, err := parseEventArgs(p3, iteration+1) + out3, err := parseABISignatureArgs(p3, iteration+1) if err != nil { return out, err } @@ -261,7 +231,7 @@ func parseEventArgs(eventArgs string, iteration int) (eventSelectorTree, error) return out, nil } -func groupEventSelectorTree(t eventSelectorTree, include bool) (string, []string, []bool, []string, error) { +func groupABISignatureTree(t abiSignatureTree, include bool) (string, []string, []bool, []string, error) { out := "" typs := []string{} indexed := []bool{} @@ -287,7 +257,7 @@ func groupEventSelectorTree(t eventSelectorTree, include bool) (string, []string } for _, child := range t.tuple { - s, _, _, _, err := groupEventSelectorTree(child, false) + s, _, _, _, err := groupABISignatureTree(child, false) if err != nil { return "", nil, nil, nil, err } @@ -303,7 +273,7 @@ func groupEventSelectorTree(t eventSelectorTree, include bool) (string, []string } for _, child := range t.right { - s, rtyps, rindexed, rnames, err := groupEventSelectorTree(child, true) + s, rtyps, rindexed, rnames, err := groupABISignatureTree(child, true) if err != nil { return "", nil, nil, nil, err } diff --git a/ethcoder/events_parser_test.go b/ethcoder/abi_sig_parser_test.go similarity index 97% rename from ethcoder/events_parser_test.go rename to ethcoder/abi_sig_parser_test.go index 2f71e13b..a36cadbb 100644 --- a/ethcoder/events_parser_test.go +++ b/ethcoder/abi_sig_parser_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseEventArgs(t *testing.T) { +func TestParseABISignature(t *testing.T) { cases := []struct { in string sig string @@ -128,11 +128,11 @@ func TestParseEventArgs(t *testing.T) { } for _, c := range cases { - tree, err := parseEventArgs(c.in, 0) + tree, err := parseABISignatureArgs(c.in, 0) require.NoError(t, err) // spew.Dump(tree) - out, typs, indexed, names, err := groupEventSelectorTree(tree, true) + out, typs, indexed, names, err := groupABISignatureTree(tree, true) require.NoError(t, err) require.Equal(t, c.sig, out) // spew.Dump(typs) diff --git a/ethcoder/abi_sig_test.go b/ethcoder/abi_sig_test.go new file mode 100644 index 00000000..a83e9924 --- /dev/null +++ b/ethcoder/abi_sig_test.go @@ -0,0 +1,152 @@ +package ethcoder + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseABISignatureArgs(t *testing.T) { + cases := []struct { + in string + sig string + argIndexed []bool + argNames []string + }{ + { + "bytes32 num, address[] indexed from, uint256 val, ( address op, (uint256 val, bytes32 data)) indexed yes, address, (int128 a, int64 b), uint256", + "bytes32,address[],uint256,(address,(uint256,bytes32)),address,(int128,int64),uint256", + []bool{false, true, false, true, false, false, false}, + []string{"num", "from", "val", "yes", "", "", ""}, + }, + { // its not actually valid selector, but use it for parser testing + "bytes indexed blah, uint256[2][] yes, ( address yo, uint256[2] no )[2][] indexed okay, address last", + "bytes,uint256[2][],(address,uint256[2])[2][],address", + []bool{true, false, true, false}, + []string{"blah", "yes", "okay", "last"}, + }, + { // its not actually valid selector, but use it for parser testing + "address from, ( uint256 num, address cool, ( address op, uint256 val )[2] hmm)[][] lol, uint256 val", + "address,(uint256,address,(address,uint256)[2])[][],uint256", + []bool{false, false, false}, + []string{"from", "lol", "val"}, + }, + { + "address indexed from, address indexed to, uint256 value", + "address,address,uint256", + []bool{true, true, false}, + []string{"from", "to", "value"}, + }, + { + "bytes32,address,address indexed yes,((uint32,uint32,uint32,address,address,bool,bytes,uint256,address,uint256,uint256,uint256,bytes32),address[],bytes[],address,bytes) indexed cool,address,uint256,address indexed last", + "bytes32,address,address,((uint32,uint32,uint32,address,address,bool,bytes,uint256,address,uint256,uint256,uint256,bytes32),address[],bytes[],address,bytes),address,uint256,address", + []bool{false, false, true, true, false, false, true}, + []string{"", "", "yes", "cool", "", "", "last"}, + }, + { + "(address,uint256,string,string) indexed okay", + "(address,uint256,string,string)", + []bool{true}, + []string{"okay"}, + }, + { + "bytes32,address,address,address,(uint8,address,uint256,uint256)[] indexed good,(uint8,address,uint256,uint256,address)[] indexed last", + "bytes32,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256,address)[]", + []bool{false, false, false, false, true, true}, + []string{"", "", "", "", "good", "last"}, + }, + { + "address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,address,uint40,uint40)", + "address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,address,uint40,uint40)", + []bool{false, false}, + []string{"", ""}, + }, + { + "bytes32,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256,address)[]", + "bytes32,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256,address)[]", + []bool{false, false, false, false, false, false}, + []string{"", "", "", "", "", ""}, + }, + + // + // repeat, with no arg names + // + { + "bytes32, address[] indexed, uint256, ( address op, (uint256 val, bytes32 data)), address, (int128 a, int64 b), uint256", + "bytes32,address[],uint256,(address,(uint256,bytes32)),address,(int128,int64),uint256", + []bool{false, true, false, false, false, false, false}, + []string{"", "", "", "", "", "", ""}, + }, + { // its not actually valid selector, but use it for parser testing + "bytes, uint256[2][], ( address yo, uint256[2] no )[2][] indexed, address", + "bytes,uint256[2][],(address,uint256[2])[2][],address", + []bool{false, false, true, false}, + []string{"", "", "", ""}, + }, + { // its not actually valid selector, but use it for parser testing + "address, ( uint256 num, address cool, ( address op, uint256 val )[2] hmm)[][], uint256", + "address,(uint256,address,(address,uint256)[2])[][],uint256", + []bool{false, false, false}, + []string{"", "", ""}, + }, + { + "address,address,uint256", + "address,address,uint256", + []bool{false, false, false}, + []string{"", "", ""}, + }, + { + "bytes32,address,address,((uint32,uint32,uint32,address,address,bool,bytes,uint256,address,uint256,uint256,uint256,bytes32),address[],bytes[],address,bytes),address,uint256,address", + "bytes32,address,address,((uint32,uint32,uint32,address,address,bool,bytes,uint256,address,uint256,uint256,uint256,bytes32),address[],bytes[],address,bytes),address,uint256,address", + []bool{false, false, false, false, false, false, false}, + []string{"", "", "", "", "", "", ""}, + }, + { + "(address,uint256,string,string)", + "(address,uint256,string,string)", + []bool{false}, + []string{""}, + }, + { + "bytes32,address,address,address,(uint8,address,uint256,uint256)[] indexed,(uint8,address,uint256,uint256,address)[]", + "bytes32,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256,address)[]", + []bool{false, false, false, false, true, false}, + []string{"", "", "", "", "", ""}, + }, + { + "address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,address,uint40,uint40)", + "address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,address,uint40,uint40)", + []bool{false, false}, + []string{"", ""}, + }, + { + "bytes32,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256,address)[]", + "bytes32,address,address,address,(uint8,address,uint256,uint256)[],(uint8,address,uint256,uint256,address)[]", + []bool{false, false, false, false, false, false}, + []string{"", "", "", "", "", ""}, + }, + } + + for _, c := range cases { + tree, err := parseABISignatureArgs(c.in, 0) + require.NoError(t, err) + // spew.Dump(tree) + + out, typs, indexed, names, err := groupABISignatureTree(tree, true) + require.NoError(t, err) + require.Equal(t, c.sig, out) + // spew.Dump(typs) + // spew.Dump(indexed) + // spew.Dump(names) + + require.Equal(t, len(c.argNames), len(typs)) + require.Equal(t, len(c.argNames), len(indexed)) + require.Equal(t, len(c.argNames), len(names)) + require.Equal(t, c.argIndexed, indexed) + require.Equal(t, c.argNames, names) + + // ok, err := ValidateEventSig(fmt.Sprintf("Test(%s)", out)) + // require.NoError(t, err) + // require.True(t, ok) + } +} diff --git a/ethcoder/abi_test.go b/ethcoder/abi_test.go index 531047fc..6f89832a 100644 --- a/ethcoder/abi_test.go +++ b/ethcoder/abi_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestAbiEncoding(t *testing.T) { +func TestABIEncoding(t *testing.T) { cases := []struct { argTypes []string expected string @@ -48,7 +48,7 @@ func TestAbiEncoding(t *testing.T) { } } -func TestAbiDecoder(t *testing.T) { +func TestABIDecoder(t *testing.T) { { input, err := HexDecode("0x000000000000000000000000000000000000000000007998f984c2040a5a9e01000000000000000000000000000000000000000000007998f984c2040a5a9e01") assert.NoError(t, err) @@ -79,45 +79,45 @@ func TestAbiDecoder(t *testing.T) { } } -func TestParseMethodABI(t *testing.T) { - // correct usage - { - mabi, methodName, err := ParseMethodABI("balanceOf(address,uint256)", "uint256") - assert.NoError(t, err) - assert.Equal(t, "balanceOf", methodName) +// func TestParseMethodABI(t *testing.T) { +// // correct usage +// { +// mabi, methodName, err := ParseMethodABI("balanceOf(address,uint256)", "uint256") +// assert.NoError(t, err) +// assert.Equal(t, "balanceOf", methodName) - ownerAddress := common.HexToAddress("0x6615e4e985bf0d137196897dfa182dbd7127f54f") - data, err := mabi.Pack("balanceOf", ownerAddress, big.NewInt(2)) - assert.NoError(t, err) +// ownerAddress := common.HexToAddress("0x6615e4e985bf0d137196897dfa182dbd7127f54f") +// data, err := mabi.Pack("balanceOf", ownerAddress, big.NewInt(2)) +// assert.NoError(t, err) - assert.Equal(t, "0x00fdd58e0000000000000000000000006615e4e985bf0d137196897dfa182dbd7127f54f0000000000000000000000000000000000000000000000000000000000000002", HexEncode(data)) - } +// assert.Equal(t, "0x00fdd58e0000000000000000000000006615e4e985bf0d137196897dfa182dbd7127f54f0000000000000000000000000000000000000000000000000000000000000002", HexEncode(data)) +// } - // correct usage - { - _, _, err := ParseMethodABI("someMethod(address)", "(uint256, bytes)") - assert.NoError(t, err) +// // correct usage +// { +// _, _, err := ParseMethodABI("someMethod(address)", "(uint256, bytes)") +// assert.NoError(t, err) - // we also allow names for input/output arguments - _, _, err = ParseMethodABI("someMethod(address owner)", "(uint256 count, bytes value)") - assert.NoError(t, err) +// // we also allow names for input/output arguments +// _, _, err = ParseMethodABI("someMethod(address owner)", "(uint256 count, bytes value)") +// assert.NoError(t, err) - // no args - _, _, err = ParseMethodABI("read()", "uint256") - assert.NoError(t, err) - } +// // no args +// _, _, err = ParseMethodABI("read()", "uint256") +// assert.NoError(t, err) +// } - // invalid usage - { - _, _, err := ParseMethodABI("balanceOf address, uint256)", "uint256") - assert.Error(t, err) +// // invalid usage +// { +// _, _, err := ParseMethodABI("balanceOf address, uint256)", "uint256") +// assert.Error(t, err) - _, _, err = ParseMethodABI("balanceOf(address, uint256)", "blah") - assert.Contains(t, "unsupported arg type: blah", err.Error()) - } -} +// _, _, err = ParseMethodABI("balanceOf(address, uint256)", "blah") +// assert.Contains(t, "unsupported arg type: blah", err.Error()) +// } +// } -func TestAbiEncodeMethodCalldata(t *testing.T) { +func TestABIEncodeMethodCalldata(t *testing.T) { ownerAddress := common.HexToAddress("0x6615e4e985bf0d137196897dfa182dbd7127f54f") { @@ -143,7 +143,7 @@ func TestAbiEncodeMethodCalldata(t *testing.T) { } } -func TestAbiDecodeExpr(t *testing.T) { +func TestABIDecodeExpr(t *testing.T) { ret := "0x000000000000000000000000000000000000000000007998f984c2040a5a9e01" { @@ -169,7 +169,7 @@ func TestAbiDecodeExpr(t *testing.T) { } } -func TestAbiDecodeExprAndStringify(t *testing.T) { +func TestABIDecodeExprAndStringify(t *testing.T) { { values, err := AbiDecodeExprAndStringify("uint256", MustHexDecode("0x000000000000000000000000000000000000000000007998f984c2040a5a9e01")) assert.NoError(t, err) @@ -210,9 +210,9 @@ func TestAbiDecodeExprAndStringify(t *testing.T) { } } -func TestAbiUnmarshalStringValuesAny(t *testing.T) { +func TestABIUnmarshalStringValuesAny(t *testing.T) { { - values, err := AbiUnmarshalStringValuesAny([]string{"address", "uint256"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "2"}) + values, err := ABIUnmarshalStringValuesAny([]string{"address", "uint256"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "2"}) assert.NoError(t, err) assert.Len(t, values, 2) @@ -226,7 +226,7 @@ func TestAbiUnmarshalStringValuesAny(t *testing.T) { } { - values, err := AbiUnmarshalStringValuesAny([]string{"address", "bytes8"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0xaabbccddaabbccdd"}) + values, err := ABIUnmarshalStringValuesAny([]string{"address", "bytes8"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0xaabbccddaabbccdd"}) assert.NoError(t, err) v1, ok := values[0].(common.Address) @@ -239,7 +239,7 @@ func TestAbiUnmarshalStringValuesAny(t *testing.T) { } { - values, err := AbiUnmarshalStringValuesAny([]string{"address", "bytes7"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0xaabbccddaabbcc"}) + values, err := ABIUnmarshalStringValuesAny([]string{"address", "bytes7"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0xaabbccddaabbcc"}) assert.NoError(t, err) v1, ok := values[0].(common.Address) @@ -252,25 +252,25 @@ func TestAbiUnmarshalStringValuesAny(t *testing.T) { } { - values, err := AbiUnmarshalStringValuesAny([]string{"address", "uint256"}, []any{"", "2"}) + values, err := ABIUnmarshalStringValuesAny([]string{"address", "uint256"}, []any{"", "2"}) assert.Error(t, err) assert.Len(t, values, 0) } { - values, err := AbiUnmarshalStringValuesAny([]string{"bytes", "uint256"}, []any{"0", "2"}) + values, err := ABIUnmarshalStringValuesAny([]string{"bytes", "uint256"}, []any{"0", "2"}) assert.Error(t, err) assert.Len(t, values, 0) } { - values, err := AbiUnmarshalStringValuesAny([]string{"bytes", "uint256"}, []any{"0z", "2"}) + values, err := ABIUnmarshalStringValuesAny([]string{"bytes", "uint256"}, []any{"0z", "2"}) assert.Error(t, err) assert.Len(t, values, 0) } { - values, err := AbiUnmarshalStringValuesAny([]string{"address", "uint256"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "2"}) + values, err := ABIUnmarshalStringValuesAny([]string{"address", "uint256"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "2"}) require.NoError(t, err) require.Len(t, values, 2) @@ -281,7 +281,7 @@ func TestAbiUnmarshalStringValuesAny(t *testing.T) { { in := []string{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0x1231f65f29f98e7D71A4655cCD7B2bc441211feb"} - values, err := AbiUnmarshalStringValuesAny([]string{"address[]"}, []any{in}) + values, err := ABIUnmarshalStringValuesAny([]string{"address[]"}, []any{in}) require.NoError(t, err) require.Len(t, values, 1) @@ -296,7 +296,7 @@ func TestAbiUnmarshalStringValuesAny(t *testing.T) { { in := []string{"1234", "0x1231f65f29f98e7D71A4655cCD7B2bc441211feb"} - values, err := AbiUnmarshalStringValuesAny([]string{"(uint256,address)"}, []any{in}) + values, err := ABIUnmarshalStringValuesAny([]string{"(uint256,address)"}, []any{in}) require.NoError(t, err) require.Len(t, values, 1) @@ -318,7 +318,7 @@ func TestAbiUnmarshalStringValuesAny(t *testing.T) { { // (uint256,(uint256,address[])) in := []any{"444", []any{"1234", []string{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0x1231f65f29f98e7D71A4655cCD7B2bc441211feb"}}} - values, err := AbiUnmarshalStringValuesAny([]string{"uint256", "(uint256,address[])"}, in) + values, err := ABIUnmarshalStringValuesAny([]string{"uint256", "(uint256,address[])"}, in) require.NoError(t, err) require.Len(t, values, 2) @@ -344,7 +344,7 @@ func TestAbiUnmarshalStringValuesAny(t *testing.T) { } } -func TestAbiUnmarshalStringValues(t *testing.T) { +func TestABIUnmarshalStringValues(t *testing.T) { { values, err := AbiUnmarshalStringValues([]string{"address", "uint256"}, []string{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "2"}) assert.NoError(t, err) @@ -422,7 +422,7 @@ func TestAbiUnmarshalStringValues(t *testing.T) { } } -// func TestAbiContractCall1(t *testing.T) { +// func TestABIContractCall1(t *testing.T) { // calldata, err := AbiEncodeMethodCalldata("getCurrencyReserves(uint256[])", []interface{}{[]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}}) // assert.NoError(t, err) // assert.Equal(t, "0x209b96c500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", HexEncode(calldata)) @@ -446,7 +446,7 @@ func TestAbiUnmarshalStringValues(t *testing.T) { // // spew.Dump(values) // } -// func TestAbiContractCall2(t *testing.T) { +// func TestABIContractCall2(t *testing.T) { // calldata, err := AbiEncodeMethodCalldataFromStringValues("getCurrencyReserves(uint256[])", []string{`["1","2","3"]`}) // assert.NoError(t, err) // assert.Equal(t, "0x209b96c500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", HexEncode(calldata)) diff --git a/ethcoder/ethcoder.go b/ethcoder/ethcoder.go index 81fd6b88..0487449f 100644 --- a/ethcoder/ethcoder.go +++ b/ethcoder/ethcoder.go @@ -11,21 +11,21 @@ func BytesToBytes32(slice []byte) [32]byte { return bytes32 } -func AddressPadding(input string) string { - if strings.HasPrefix(input, "0x") { - input = input[2:] +func PaddedAddress(address string) string { + if strings.HasPrefix(address, "0x") { + address = address[2:] } - if len(input) < 64 { - input = strings.Repeat("0", 64-len(input)) + input + if len(address) < 64 { + address = strings.Repeat("0", 64-len(address)) + address } - return input[0:64] + return address[0:64] } func FunctionSignature(functionExpr string) string { return HexEncode(Keccak256([]byte(functionExpr))[0:4]) } -func StringifyValues(values []interface{}) ([]string, error) { +func StringifyValues(values []any) ([]string, error) { strs := []string{} for _, value := range values { diff --git a/ethcoder/events.go b/ethcoder/events.go index 1e2b5449..80e87f60 100644 --- a/ethcoder/events.go +++ b/ethcoder/events.go @@ -17,23 +17,23 @@ import ( // e.g. "Transfer(address indexed from, address indexed to, uint256 value)" // will return 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef func EventTopicHash(event string) (ethkit.Hash, string, error) { - eventDef, err := ParseEventDef(event) + eventDef, err := ParseABISignature(event) if err != nil { return ethkit.Hash{}, "", fmt.Errorf("ethcoder: %w", err) } - topicHash := common.HexToHash(eventDef.TopicHash) - return topicHash, eventDef.Sig, nil + topicHash := common.HexToHash(eventDef.Hash) + return topicHash, eventDef.Signature, nil } func ValidateEventSig(eventSig string) (bool, error) { // First parse with eventDef to normalize - eventDef, err := ParseEventDef(eventSig) + eventDef, err := ParseABISignature(eventSig) if err != nil { return false, err } // Then check against the selector to confirm - selector, err := abi.ParseSelector(eventDef.Sig) + selector, err := abi.ParseSelector(eventDef.Signature) if err != nil { return false, err } @@ -48,47 +48,47 @@ func ValidateEventSig(eventSig string) (bool, error) { return true, nil } -func DecodeTransactionLogByEventSig(txnLog types.Log, eventSig string) (EventDef, []interface{}, bool, error) { +func DecodeTransactionLogByEventSig(txnLog types.Log, eventSig string) (ABISignature, []interface{}, bool, error) { decoder := NewEventDecoder() err := decoder.RegisterEventSig(eventSig) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("DecodeTransactionLogByEventSig: %w", err) + return ABISignature{}, nil, false, fmt.Errorf("DecodeTransactionLogByEventSig: %w", err) } return decoder.DecodeLog(txnLog) } -func DecodeTransactionLogByEventSigAsHex(txnLog types.Log, eventSig string) (EventDef, []string, bool, error) { +func DecodeTransactionLogByEventSigAsHex(txnLog types.Log, eventSig string) (ABISignature, []string, bool, error) { decoder := NewEventDecoder() err := decoder.RegisterEventSig(eventSig) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("DecodeTransactionLogByEventSigAsHex: %w", err) + return ABISignature{}, nil, false, fmt.Errorf("DecodeTransactionLogByEventSigAsHex: %w", err) } return decoder.DecodeLogAsHex(txnLog) } // .. -func DecodeTransactionLogByContractABIJSON(txnLog types.Log, contractABIJSON string) (EventDef, []interface{}, bool, error) { +func DecodeTransactionLogByContractABIJSON(txnLog types.Log, contractABIJSON string) (ABISignature, []interface{}, bool, error) { contractABI, err := abi.JSON(strings.NewReader(contractABIJSON)) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("invalid contract ABI definition: %w", err) + return ABISignature{}, nil, false, fmt.Errorf("invalid contract ABI definition: %w", err) } return DecodeTransactionLogByContractABI(txnLog, contractABI) } -func DecodeTransactionLogByContractABI(txnLog types.Log, contractABI abi.ABI) (EventDef, []interface{}, bool, error) { +func DecodeTransactionLogByContractABI(txnLog types.Log, contractABI abi.ABI) (ABISignature, []interface{}, bool, error) { decoder := NewEventDecoder() err := decoder.RegisterContractABI(contractABI) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("DecodeTransactionLogByContractABI: %w", err) + return ABISignature{}, nil, false, fmt.Errorf("DecodeTransactionLogByContractABI: %w", err) } return decoder.DecodeLog(txnLog) } -func DecodeTransactionLogByContractABIAsHex(txnLog types.Log, contractABI abi.ABI) (EventDef, []string, bool, error) { +func DecodeTransactionLogByContractABIAsHex(txnLog types.Log, contractABI abi.ABI) (ABISignature, []string, bool, error) { decoder := NewEventDecoder() err := decoder.RegisterContractABI(contractABI) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("DecodeTransactionLogByContractABIAsHex: %w", err) + return ABISignature{}, nil, false, fmt.Errorf("DecodeTransactionLogByContractABIAsHex: %w", err) } return decoder.DecodeLogAsHex(txnLog) } @@ -110,7 +110,7 @@ type EventDecoder struct { // eventDecoderDef is the decoder definition for a single event type eventDecoderDef struct { - EventDef + ABISignature abi abi.ABI } @@ -140,32 +140,37 @@ func NewEventDecoder() *EventDecoder { func (d *EventDecoder) RegisterEventSig(eventSig ...string) error { LOOP: for _, sig := range eventSig { - eventDef, err := ParseEventDef(sig) + eventDef, err := ParseABISignature(sig) if err != nil { return fmt.Errorf("ethcoder: %w", err) } - _, ok := d.decoders[eventDef.TopicHash] + _, ok := d.decoders[eventDef.Hash] if !ok { - d.decoders[eventDef.TopicHash] = []eventDecoderDef{} + d.decoders[eventDef.Hash] = []eventDecoderDef{} } // Dedupe check - dds := d.decoders[eventDef.TopicHash] + dds := d.decoders[eventDef.Hash] for _, dd := range dds { - if dd.Sig == eventDef.Sig && dd.NumIndexed == eventDef.NumIndexed { + if dd.Signature == eventDef.Signature && dd.NumIndexed == eventDef.NumIndexed { continue LOOP } } + // evDefOld, err := ParseEventDef(sig) + // if err != nil { + // return fmt.Errorf("ethcoder: %w", err) + // } + // Register new abi, err := EventDefToABI(eventDef) if err != nil { return fmt.Errorf("ethcoder: %w", err) } - d.decoders[eventDef.TopicHash] = append(dds, eventDecoderDef{ - EventDef: eventDef, - abi: abi, + d.decoders[eventDef.Hash] = append(dds, eventDecoderDef{ + ABISignature: eventDef, + abi: abi, }) } @@ -188,15 +193,15 @@ func (d *EventDecoder) RegisterContractABI(contractABI abi.ABI, eventNames ...st LOOP: for _, eventDef := range eventDefs { - _, ok := d.decoders[eventDef.TopicHash] + _, ok := d.decoders[eventDef.Hash] if !ok { - d.decoders[eventDef.TopicHash] = []eventDecoderDef{} + d.decoders[eventDef.Hash] = []eventDecoderDef{} } // Dedupe check - dds := d.decoders[eventDef.TopicHash] + dds := d.decoders[eventDef.Hash] for _, dd := range dds { - if dd.Sig == eventDef.Sig && dd.NumIndexed == eventDef.NumIndexed { + if dd.Signature == eventDef.Signature && dd.NumIndexed == eventDef.NumIndexed { continue LOOP } } @@ -205,8 +210,8 @@ LOOP: // evABI := abi.ABI{} // evABI.Events[eventDef.Name] = contractABI.Events[eventDef.Name] - d.decoders[eventDef.TopicHash] = append(dds, eventDecoderDef{ - EventDef: eventDef, + d.decoders[eventDef.Hash] = append(dds, eventDecoderDef{ + ABISignature: eventDef, // abi: evABI, abi: contractABI, }) @@ -214,14 +219,14 @@ LOOP: return nil } -func (d *EventDecoder) DecodeLog(log types.Log) (EventDef, []interface{}, bool, error) { +func (d *EventDecoder) DecodeLog(log types.Log) (ABISignature, []interface{}, bool, error) { dd, err := d.getLogDecoder(log) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("DecodeLog, %w", err) + return ABISignature{}, nil, false, fmt.Errorf("DecodeLog, %w", err) } if len(log.Topics) == 0 { - return EventDef{}, nil, false, fmt.Errorf("log has no topics, unable to decode") + return ABISignature{}, nil, false, fmt.Errorf("log has no topics, unable to decode") } abiEvent := dd.abi.Events[dd.Name] @@ -229,7 +234,7 @@ func (d *EventDecoder) DecodeLog(log types.Log) (EventDef, []interface{}, bool, eventMap := map[string]interface{}{} err = bc.UnpackLogIntoMap(eventMap, abiEvent.Name, log) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("UnpackLogIntoMap: decoding failed due to %w", err) + return ABISignature{}, nil, false, fmt.Errorf("UnpackLogIntoMap: decoding failed due to %w", err) } eventValues := []interface{}{} @@ -237,21 +242,21 @@ func (d *EventDecoder) DecodeLog(log types.Log) (EventDef, []interface{}, bool, eventValues = append(eventValues, eventMap[arg.Name]) } - return dd.EventDef, eventValues, true, nil + return dd.ABISignature, eventValues, true, nil } -func (d *EventDecoder) DecodeLogAsHex(log types.Log) (EventDef, []string, bool, error) { +func (d *EventDecoder) DecodeLogAsHex(log types.Log) (ABISignature, []string, bool, error) { dd, err := d.getLogDecoder(log) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("DecodeLogAsHex, %w", err) + return ABISignature{}, nil, false, fmt.Errorf("DecodeLogAsHex, %w", err) } abiEvent := dd.abi.Events[dd.Name] - eventDef := dd.EventDef + eventDef := dd.ABISignature // fast decode if were not parsing any dynamic types var fastDecode bool - if !strings.Contains(dd.Sig, "[") && strings.Count(dd.Sig, "(") == 1 { + if !strings.Contains(dd.Signature, "[") && strings.Count(dd.Signature, "(") == 1 { fastDecode = true } @@ -288,7 +293,7 @@ func (d *EventDecoder) DecodeLogAsHex(log types.Log) (EventDef, []string, bool, // which is suboptimal, but works. eventDef, eventValues, _, err := d.DecodeLog(log) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("DecodeLogAsHex: %w", err) + return ABISignature{}, nil, false, fmt.Errorf("DecodeLogAsHex: %w", err) } out := []string{} @@ -296,7 +301,7 @@ func (d *EventDecoder) DecodeLogAsHex(log types.Log) (EventDef, []string, bool, x := abi.Arguments{arg} data, err := x.Pack(eventValues[i]) if err != nil { - return EventDef{}, nil, false, fmt.Errorf("PackValues: %w", err) + return ABISignature{}, nil, false, fmt.Errorf("PackValues: %w", err) } out = append(out, hexutil.Encode(data)) } @@ -304,11 +309,11 @@ func (d *EventDecoder) DecodeLogAsHex(log types.Log) (EventDef, []string, bool, return eventDef, out, true, nil } -func (d *EventDecoder) EventDefList() []EventDef { - eventDefs := []EventDef{} +func (d *EventDecoder) EventDefList() []ABISignature { + eventDefs := []ABISignature{} for _, dds := range d.decoders { for _, dd := range dds { - eventDefs = append(eventDefs, dd.EventDef) + eventDefs = append(eventDefs, dd.ABISignature) } } return eventDefs @@ -358,9 +363,17 @@ func (d *EventDecoder) getLogDecoder(log types.Log) (eventDecoderDef, error) { return eventDecoderDef{}, fmt.Errorf("no decoder found for topic hash with indexed args: %s", topicHash) } -func EventDefToABI(eventDef EventDef) (abi.ABI, error) { +// TODO: get rid of this.. we have "ethcoder.ABI" now .. +func EventDefToABI(eventDef ABISignature, isFunction ...bool) (abi.ABI, error) { + // func EventDefToABI(eventDef EventDef, isFunction ...bool) (abi.ABI, error) { + + isFunc := false + if len(isFunction) > 0 { + isFunc = isFunction[0] + } + abiArgs := abi.Arguments{} - selector, err := abi.ParseSelector(eventDef.Sig) + selector, err := abi.ParseSelector(eventDef.Signature) if err != nil { return abi.ABI{}, err } @@ -381,17 +394,27 @@ func EventDefToABI(eventDef EventDef) (abi.ABI, error) { abiArgs = append(abiArgs, abi.Argument{Name: argName, Type: typ, Indexed: eventDef.ArgIndexed[i]}) } - abiEvent := abi.NewEvent(eventDef.Name, eventDef.Name, false, abiArgs) - contractABI := abi.ABI{ - Events: map[string]abi.Event{}, + var contractABI abi.ABI + if isFunc { + abiMethod := abi.NewMethod(eventDef.Name, eventDef.Name, abi.Function, "", false, false, abiArgs, nil) + contractABI = abi.ABI{ + Methods: map[string]abi.Method{}, + } + contractABI.Methods[eventDef.Name] = abiMethod + } else { + abiEvent := abi.NewEvent(eventDef.Name, eventDef.Name, false, abiArgs) + contractABI = abi.ABI{ + Events: map[string]abi.Event{}, + } + contractABI.Events[eventDef.Name] = abiEvent } - contractABI.Events[eventDef.Name] = abiEvent return contractABI, nil } -func abiToEventDefs(contractABI abi.ABI, eventNames []string) ([]EventDef, error) { - eventDefs := []EventDef{} +// TODO: move this on ABI struct as well.. +func abiToEventDefs(contractABI abi.ABI, eventNames []string) ([]ABISignature, error) { + eventDefs := []ABISignature{} if len(eventNames) == 0 { eventNames = []string{} @@ -409,7 +432,7 @@ func abiToEventDefs(contractABI abi.ABI, eventNames []string) ([]EventDef, error return nil, fmt.Errorf("event is anonymous: %s", eventName) } - eventDef := EventDef{ + eventDef := ABISignature{ Name: eventName, } @@ -432,8 +455,8 @@ func abiToEventDefs(contractABI abi.ABI, eventNames []string) ([]EventDef, error eventDef.ArgIndexed = indexed eventDef.NumIndexed = numIndexed - eventDef.Sig = fmt.Sprintf("%s(%s)", eventDef.Name, strings.Join(typs, ",")) - eventDef.TopicHash = Keccak256Hash([]byte(eventDef.Sig)).String() + eventDef.Signature = fmt.Sprintf("%s(%s)", eventDef.Name, strings.Join(typs, ",")) + eventDef.Hash = Keccak256Hash([]byte(eventDef.Signature)).String() eventDefs = append(eventDefs, eventDef) } diff --git a/ethcoder/events_test.go b/ethcoder/events_test.go index 9358d268..b25f130a 100644 --- a/ethcoder/events_test.go +++ b/ethcoder/events_test.go @@ -29,11 +29,11 @@ func TestEventTopicHash1(t *testing.T) { } for _, x := range in { - eventDef, err := ethcoder.ParseEventDef(x.event) + eventDef, err := ethcoder.ParseABISignature(x.event) require.NoError(t, err) - require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.TopicHash) + require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.Hash) require.Equal(t, "Transfer", eventDef.Name) - require.Equal(t, "Transfer(address,address,uint256)", eventDef.Sig) + require.Equal(t, "Transfer(address,address,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "uint256"}, eventDef.ArgTypes) // require.Equal(t, []string{"from", "to", "value"}, eventDef.ArgNames) } @@ -56,11 +56,11 @@ func TestEventTopicHash2(t *testing.T) { } for _, x := range in { - eventDef, err := ethcoder.ParseEventDef(x.event) + eventDef, err := ethcoder.ParseABISignature(x.event) require.NoError(t, err) - require.Equal(t, "0x9c248aa1a265aa616f707b979d57f4529bb63a4fc34dc7fc61fdddc18410f74e", eventDef.TopicHash) + require.Equal(t, "0x9c248aa1a265aa616f707b979d57f4529bb63a4fc34dc7fc61fdddc18410f74e", eventDef.Hash) require.Equal(t, "ERC721SellOrderFilled", eventDef.Name) - require.Equal(t, "ERC721SellOrderFilled(bytes32,address,address,uint256,address,uint256,(address,uint256)[],address,uint256)", eventDef.Sig) + require.Equal(t, "ERC721SellOrderFilled(bytes32,address,address,uint256,address,uint256,(address,uint256)[],address,uint256)", eventDef.Signature) require.Equal(t, []string{"bytes32", "address", "address", "uint256", "address", "uint256", "(address,uint256)[]", "address", "uint256"}, eventDef.ArgTypes) } } @@ -82,11 +82,11 @@ func TestEventTopicHash3(t *testing.T) { } for _, x := range in { - eventDef, err := ethcoder.ParseEventDef(x.event) + eventDef, err := ethcoder.ParseABISignature(x.event) require.NoError(t, err) - require.Equal(t, "0x041b7d65461f6f51e8fd92623a3848b22ce7077c215e4ea064d790e9efa08b8f", eventDef.TopicHash) + require.Equal(t, "0x041b7d65461f6f51e8fd92623a3848b22ce7077c215e4ea064d790e9efa08b8f", eventDef.Hash) require.Equal(t, "NftItemCreated", eventDef.Name) - require.Equal(t, "NftItemCreated(uint256,uint32,address,bool,uint32,address,address[],uint32[])", eventDef.Sig) + require.Equal(t, "NftItemCreated(uint256,uint32,address,bool,uint32,address,address[],uint32[])", eventDef.Signature) require.Equal(t, []string{"uint256", "uint32", "address", "bool", "uint32", "address", "address[]", "uint32[]"}, eventDef.ArgTypes) } } @@ -130,9 +130,9 @@ func TestDecodeTransactionLogByContractABIJSON(t *testing.T) { eventDef, eventValues, ok, err := ethcoder.DecodeTransactionLogByContractABIJSON(txnLog, erc20ABI) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.TopicHash) + require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.Hash) require.Equal(t, "Transfer", eventDef.Name) - require.Equal(t, "Transfer(address,address,uint256)", eventDef.Sig) + require.Equal(t, "Transfer(address,address,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, false}, eventDef.ArgIndexed) require.Equal(t, []string{"from", "to", "value"}, eventDef.ArgNames) @@ -164,9 +164,9 @@ func TestDecodeTransactionLogByEventSig1(t *testing.T) { eventDef, eventValues, ok, err := ethcoder.DecodeTransactionLogByEventSig(txnLog, eventSig) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.TopicHash) + require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.Hash) require.Equal(t, "Transfer", eventDef.Name) - require.Equal(t, "Transfer(address,address,uint256)", eventDef.Sig) + require.Equal(t, "Transfer(address,address,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, false}, eventDef.ArgIndexed) require.Equal(t, []string{"arg1", "arg2", "arg3"}, eventDef.ArgNames) @@ -178,9 +178,9 @@ func TestDecodeTransactionLogByEventSig1(t *testing.T) { eventDef, eventHexValues, ok, err := ethcoder.DecodeTransactionLogByEventSigAsHex(txnLog, eventSig) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.TopicHash) + require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.Hash) require.Equal(t, "Transfer", eventDef.Name) - require.Equal(t, "Transfer(address,address,uint256)", eventDef.Sig) + require.Equal(t, "Transfer(address,address,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, false}, eventDef.ArgIndexed) require.Equal(t, []string{"arg1", "arg2", "arg3"}, eventDef.ArgNames) @@ -212,9 +212,9 @@ func TestDecodeTransactionLogByEventSig2(t *testing.T) { eventDef, eventHexValues, ok, err := ethcoder.DecodeTransactionLogByEventSigAsHex(txnLog, eventSig) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.TopicHash) + require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.Hash) require.Equal(t, "Transfer", eventDef.Name) - require.Equal(t, "Transfer(address,address,uint256)", eventDef.Sig) + require.Equal(t, "Transfer(address,address,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, true}, eventDef.ArgIndexed) require.Equal(t, []string{"from", "to", "amount"}, eventDef.ArgNames) @@ -246,9 +246,9 @@ func TestDecodeTransactionLogByEventSig3(t *testing.T) { eventDef, eventHexValues, ok, err := ethcoder.DecodeTransactionLogByEventSigAsHex(txnLog, eventSig) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62", eventDef.TopicHash) + require.Equal(t, "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62", eventDef.Hash) require.Equal(t, "TransferSingle", eventDef.Name) - require.Equal(t, "TransferSingle(address,address,address,uint256,uint256)", eventDef.Sig) + require.Equal(t, "TransferSingle(address,address,address,uint256,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "address", "uint256", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, true, false, false}, eventDef.ArgIndexed) require.Equal(t, []string{"arg1", "arg2", "arg3", "arg4", "arg5"}, eventDef.ArgNames) @@ -281,9 +281,9 @@ func TestDecodeTransactionLogByEventSig4(t *testing.T) { eventDef, eventHexValues, ok, err := ethcoder.DecodeTransactionLogByEventSigAsHex(txnLog, eventSig) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67", eventDef.TopicHash) + require.Equal(t, "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67", eventDef.Hash) require.Equal(t, "Swap", eventDef.Name) - require.Equal(t, "Swap(address,address,int256,int256,uint160,uint128,int24)", eventDef.Sig) + require.Equal(t, "Swap(address,address,int256,int256,uint160,uint128,int24)", eventDef.Signature) require.Equal(t, []string{"address", "address", "int256", "int256", "uint160", "uint128", "int24"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, false, false, false, false, false}, eventDef.ArgIndexed) require.Equal(t, []string{"sender", "recipient", "amount0", "amount1", "sqrtPriceX96", "liquidity", "tick"}, eventDef.ArgNames) @@ -328,9 +328,9 @@ func TestDecodeTransactionLogByEventSig5(t *testing.T) { eventDef, eventHexValues, ok, err := ethcoder.DecodeTransactionLogByEventSigAsHex(txnLog, eventSig) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0x9c248aa1a265aa616f707b979d57f4529bb63a4fc34dc7fc61fdddc18410f74e", eventDef.TopicHash) + require.Equal(t, "0x9c248aa1a265aa616f707b979d57f4529bb63a4fc34dc7fc61fdddc18410f74e", eventDef.Hash) require.Equal(t, "ERC721SellOrderFilled", eventDef.Name) - require.Equal(t, "ERC721SellOrderFilled(bytes32,address,address,uint256,address,uint256,(address,uint256)[],address,uint256)", eventDef.Sig) + require.Equal(t, "ERC721SellOrderFilled(bytes32,address,address,uint256,address,uint256,(address,uint256)[],address,uint256)", eventDef.Signature) require.Equal(t, []string{"bytes32", "address", "address", "uint256", "address", "uint256", "(address,uint256)[]", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{false, false, false, false, false, false, false, false, false}, eventDef.ArgIndexed) require.Equal(t, []string{"arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8", "arg9"}, eventDef.ArgNames) @@ -397,9 +397,9 @@ func TestDecodeTransactionLogByEventSig6(t *testing.T) { require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c", eventDef.TopicHash) + require.Equal(t, "0xefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c", eventDef.Hash) require.Equal(t, "OFTReceived", eventDef.Name) - require.Equal(t, "OFTReceived(bytes32,uint32,address,uint256)", eventDef.Sig) + require.Equal(t, "OFTReceived(bytes32,uint32,address,uint256)", eventDef.Signature) require.Equal(t, []string{"bytes32", "uint32", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, false, true, false}, eventDef.ArgIndexed) require.Equal(t, []string{"guid", "srcEid", "toAddress", "amountReceivedLD"}, eventDef.ArgNames) @@ -418,9 +418,9 @@ func TestDecodeTransactionLogByEventSig6(t *testing.T) { require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c", eventDef.TopicHash) + require.Equal(t, "0xefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c", eventDef.Hash) require.Equal(t, "OFTReceived", eventDef.Name) - require.Equal(t, "OFTReceived(bytes32,uint32,address,uint256)", eventDef.Sig) + require.Equal(t, "OFTReceived(bytes32,uint32,address,uint256)", eventDef.Signature) require.Equal(t, []string{"bytes32", "uint32", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, false, true, false}, eventDef.ArgIndexed) require.Equal(t, []string{"guid", "srcEid", "toAddress", "amountReceivedLD"}, eventDef.ArgNames) @@ -465,9 +465,9 @@ func TestEventDecoder(t *testing.T) { eventDef, eventValues, ok, err := ed.DecodeLog(txnLog) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.TopicHash) + require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.Hash) require.Equal(t, "Transfer", eventDef.Name) - require.Equal(t, "Transfer(address,address,uint256)", eventDef.Sig) + require.Equal(t, "Transfer(address,address,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, true}, eventDef.ArgIndexed) require.Equal(t, []string{"from", "to", "amount"}, eventDef.ArgNames) @@ -478,9 +478,9 @@ func TestEventDecoder(t *testing.T) { eventDef, eventHexValues, ok, err := ed.DecodeLogAsHex(txnLog) require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.TopicHash) + require.Equal(t, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", eventDef.Hash) require.Equal(t, "Transfer", eventDef.Name) - require.Equal(t, "Transfer(address,address,uint256)", eventDef.Sig) + require.Equal(t, "Transfer(address,address,uint256)", eventDef.Signature) require.Equal(t, []string{"address", "address", "uint256"}, eventDef.ArgTypes) require.Equal(t, []bool{true, true, true}, eventDef.ArgIndexed) require.Equal(t, []string{"from", "to", "amount"}, eventDef.ArgNames) @@ -514,9 +514,9 @@ func TestEventDecoder2(t *testing.T) { require.NoError(t, err) require.True(t, ok) - require.Equal(t, "0x8c5d0c41fb16a7317a6c55ff7ba93d9d74f79e434fefa694e50d6028afbfa3f0", eventDef.TopicHash) + require.Equal(t, "0x8c5d0c41fb16a7317a6c55ff7ba93d9d74f79e434fefa694e50d6028afbfa3f0", eventDef.Hash) require.Equal(t, "ERC721OrderPreSigned", eventDef.Name) - require.Equal(t, "ERC721OrderPreSigned(uint8,address,address,uint256,uint256,address,uint256,(address,uint256,bytes)[],address,uint256,(address,bytes)[])", eventDef.Sig) + require.Equal(t, "ERC721OrderPreSigned(uint8,address,address,uint256,uint256,address,uint256,(address,uint256,bytes)[],address,uint256,(address,bytes)[])", eventDef.Signature) require.Equal(t, []string{"uint8", "address", "address", "uint256", "uint256", "address", "uint256", "(address,uint256,bytes)[]", "address", "uint256", "(address,bytes)[]"}, eventDef.ArgTypes) require.Equal(t, []bool{false, false, false, false, false, false, false, false, false, false, false}, eventDef.ArgIndexed) require.Equal(t, []string{"direction", "maker", "taker", "expiry", "nonce", "erc20Token", "erc20TokenAmount", "fees", "erc721Token", "erc721TokenId", "erc721TokenProperties"}, eventDef.ArgNames) diff --git a/ethcoder/solidity_pack.go b/ethcoder/solidity_pack.go index d2780639..500ae866 100644 --- a/ethcoder/solidity_pack.go +++ b/ethcoder/solidity_pack.go @@ -203,6 +203,8 @@ func solidityArgumentPack(typ string, val interface{}, isArray bool) ([]byte, er buf = append(buf, b...) } + // TODO: tuples, see AbiUnmarshalStringValuesAny method + return buf, nil }