From 323bd5d362e42b7801536f56fe4984d23e8d74a4 Mon Sep 17 00:00:00 2001 From: Patryk Kalinowski Date: Thu, 19 Dec 2024 17:51:07 +0100 Subject: [PATCH] deps: update ethkit to v1.30.6 (#90) --- go.mod | 2 +- go.sum | 4 +- .../0xsequence/ethkit/ethcoder/abi_helpers.go | 16 +- .../0xsequence/ethkit/ethcoder/events.go | 10 +- .../0xsequence/ethkit/ethcoder/typed_data.go | 202 ----------- .../ethkit/ethcoder/typed_data_json.go | 337 ++++++++++++++++++ vendor/modules.txt | 2 +- 7 files changed, 363 insertions(+), 210 deletions(-) create mode 100644 vendor/github.com/0xsequence/ethkit/ethcoder/typed_data_json.go diff --git a/go.mod b/go.mod index 40f30281..269f0417 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.7 toolchain go1.23.2 require ( - github.com/0xsequence/ethkit v1.30.2 + github.com/0xsequence/ethkit v1.30.6 github.com/0xsequence/go-sequence v0.44.2 github.com/0xsequence/nitrocontrol v0.3.0 github.com/BurntSushi/toml v1.4.0 diff --git a/go.sum b/go.sum index 7b56a803..c29cd55c 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/0xsequence/ethkit v1.30.2 h1:TZCxXF+5kjJWE8+CKQGQkDm/coeLL7uwYuLiHiJ4iuM= -github.com/0xsequence/ethkit v1.30.2/go.mod h1:rv0FAIyEyN0hhwGefbduAz4ujmyjyJXhCd6a0/yF3tk= +github.com/0xsequence/ethkit v1.30.6 h1:JcIYjQ5jmzsooPmFUKKqO6ISiDQBYCenSC51sLcSsE4= +github.com/0xsequence/ethkit v1.30.6/go.mod h1:rv0FAIyEyN0hhwGefbduAz4ujmyjyJXhCd6a0/yF3tk= github.com/0xsequence/go-ethauth v0.14.0 h1:0TQKu/bFpTZ0hNtedqfHuRn/fa2wI8qlLJE0rDgJ0Dw= github.com/0xsequence/go-ethauth v0.14.0/go.mod h1:AX6zc0LNxzRHXW+i+IPyuLLgj+AdLl7KjLNRXB/10r0= github.com/0xsequence/go-sequence v0.44.2 h1:UMc0DWme4bcFl32OZu2ace4wNh5XkUI9AQ2cUGtn3/g= diff --git a/vendor/github.com/0xsequence/ethkit/ethcoder/abi_helpers.go b/vendor/github.com/0xsequence/ethkit/ethcoder/abi_helpers.go index fa1a7a3e..0fdd6917 100644 --- a/vendor/github.com/0xsequence/ethkit/ethcoder/abi_helpers.go +++ b/vendor/github.com/0xsequence/ethkit/ethcoder/abi_helpers.go @@ -196,8 +196,14 @@ func ABIUnmarshalStringValuesAny(argTypes []string, stringValues []any) ([]any, return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ) } + base := 10 + if strings.HasPrefix(s, "0x") { + base = 16 + s = s[2:] + } + num := big.NewInt(0) - num, ok = num.SetString(s, 10) + num, ok = num.SetString(s, base) if !ok { return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s) } @@ -408,8 +414,14 @@ func ABIUnmarshalStringValues(argTypes []string, stringValues []string) ([]any, return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ) } + base := 10 + if strings.HasPrefix(s, "0x") { + base = 16 + s = s[2:] + } + num := big.NewInt(0) - num, ok := num.SetString(s, 10) + num, ok := num.SetString(s, base) if !ok { return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s) } diff --git a/vendor/github.com/0xsequence/ethkit/ethcoder/events.go b/vendor/github.com/0xsequence/ethkit/ethcoder/events.go index 8c63ac59..60033ffc 100644 --- a/vendor/github.com/0xsequence/ethkit/ethcoder/events.go +++ b/vendor/github.com/0xsequence/ethkit/ethcoder/events.go @@ -270,8 +270,14 @@ func (d *EventDecoder) DecodeLogAsHex(log types.Log) (ABISignature, []string, bo return eventDef, nil, false, fmt.Errorf("indexed argument out of range: %d > %d", idx+1, len(log.Topics)-1) } data := log.Topics[idx+1].Bytes() - arg := data[32-byteSize:] - eventValues = append(eventValues, HexEncode(arg)) + + var argVal []byte + if arg.Type.T == abi.BytesTy || arg.Type.T == abi.FixedBytesTy { + argVal = data[:byteSize] + } else { + argVal = data[32-byteSize:] + } + eventValues = append(eventValues, HexEncode(argVal)) idx++ } else { byteSize := abi.GetTypeSize(arg.Type) diff --git a/vendor/github.com/0xsequence/ethkit/ethcoder/typed_data.go b/vendor/github.com/0xsequence/ethkit/ethcoder/typed_data.go index 9551b55a..d1936882 100644 --- a/vendor/github.com/0xsequence/ethkit/ethcoder/typed_data.go +++ b/vendor/github.com/0xsequence/ethkit/ethcoder/typed_data.go @@ -2,10 +2,8 @@ package ethcoder import ( "bytes" - "encoding/json" "fmt" "math/big" - "slices" "sort" "strings" @@ -269,203 +267,3 @@ func (t *TypedData) EncodeDigest() ([]byte, error) { } return digest, nil } - -func TypedDataFromJSON(typedDataJSON string) (*TypedData, error) { - var typedData TypedData - err := json.Unmarshal([]byte(typedDataJSON), &typedData) - if err != nil { - return nil, err - } - return &typedData, nil -} - -func (t *TypedData) UnmarshalJSON(data []byte) error { - // Intermediary structure to decode message field - type TypedDataRaw struct { - Types TypedDataTypes `json:"types"` - PrimaryType string `json:"primaryType"` - Domain TypedDataDomain `json:"domain"` - Message map[string]interface{} `json:"message"` - } - - // Json decoder with json.Number support, so that we can decode big.Int values - dec := json.NewDecoder(bytes.NewReader(data)) - dec.UseNumber() - - var raw TypedDataRaw - if err := dec.Decode(&raw); err != nil { - return err - } - - // Ensure the "EIP712Domain" type is defined. In case its not defined - // we will add it to the types map - _, ok := raw.Types["EIP712Domain"] - if !ok { - raw.Types["EIP712Domain"] = []TypedDataArgument{} - if raw.Domain.Name != "" { - raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "name", Type: "string"}) - } - if raw.Domain.Version != "" { - raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "version", Type: "string"}) - } - if raw.Domain.ChainID != nil { - raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "chainId", Type: "uint256"}) - } - if raw.Domain.VerifyingContract != nil { - raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "verifyingContract", Type: "address"}) - } - if raw.Domain.Salt != nil { - raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "salt", Type: "bytes32"}) - } - } - - // Ensure primary type is defined - if raw.PrimaryType == "" { - // detect primary type if its unspecified - primaryType, err := typedDataDetectPrimaryType(raw.Types.Map(), raw.Message) - if err != nil { - return err - } - raw.PrimaryType = primaryType - } - _, ok = raw.Types[raw.PrimaryType] - if !ok { - return fmt.Errorf("primary type '%s' is not defined", raw.PrimaryType) - } - - // Decode the raw message into Go runtime types - message, err := typedDataDecodeRawMessageMap(raw.Types.Map(), raw.PrimaryType, raw.Message) - if err != nil { - return err - } - - t.Types = raw.Types - t.PrimaryType = raw.PrimaryType - t.Domain = raw.Domain - - m, ok := message.(map[string]interface{}) - if !ok { - return fmt.Errorf("resulting message is not a map") - } - t.Message = m - - return nil -} - -func typedDataDetectPrimaryType(typesMap map[string]map[string]string, message map[string]interface{}) (string, error) { - // If there are only two types, and one is the EIP712Domain, then the other is the primary type - if len(typesMap) == 2 { - _, ok := typesMap["EIP712Domain"] - if ok { - for typ := range typesMap { - if typ == "EIP712Domain" { - continue - } - return typ, nil - } - } - } - - // Otherwise search for the primary type by looking for the first type that has a message field keys - messageKeys := []string{} - for k := range message { - messageKeys = append(messageKeys, k) - } - sort.Strings(messageKeys) - - for typ := range typesMap { - if typ == "EIP712Domain" { - continue - } - if len(typesMap[typ]) != len(messageKeys) { - continue - } - - typKeys := []string{} - for k := range typesMap[typ] { - typKeys = append(typKeys, k) - } - sort.Strings(typKeys) - - if !slices.Equal(messageKeys, typKeys) { - continue - } - return typ, nil - } - - return "", fmt.Errorf("no primary type found") -} - -func typedDataDecodeRawMessageMap(typesMap map[string]map[string]string, primaryType string, data interface{}) (interface{}, error) { - // Handle array types - if arr, ok := data.([]interface{}); ok { - results := make([]interface{}, len(arr)) - for i, item := range arr { - decoded, err := typedDataDecodeRawMessageMap(typesMap, primaryType, item) - if err != nil { - return nil, err - } - results[i] = decoded - } - return results, nil - } - - // Handle primitive directly - message, ok := data.(map[string]interface{}) - if !ok { - return typedDataDecodePrimitiveValue(primaryType, data) - } - - currentType, ok := typesMap[primaryType] - if !ok { - return nil, fmt.Errorf("type %s is not defined", primaryType) - } - - processedMessage := make(map[string]interface{}) - for k, v := range message { - typ, ok := currentType[k] - if !ok { - return nil, fmt.Errorf("message field '%s' is missing type definition on '%s'", k, primaryType) - } - - // Extract base type and check if it's an array - baseType := typ - isArray := false - if idx := strings.Index(typ, "["); idx != -1 { - baseType = typ[:idx] - isArray = true - } - - // Process value based on whether it's a custom or primitive type - if _, isCustomType := typesMap[baseType]; isCustomType { - decoded, err := typedDataDecodeRawMessageMap(typesMap, baseType, v) - if err != nil { - return nil, err - } - processedMessage[k] = decoded - } else { - var decoded interface{} - var err error - if isArray { - decoded, err = typedDataDecodeRawMessageMap(typesMap, baseType, v) - } else { - decoded, err = typedDataDecodePrimitiveValue(baseType, v) - } - if err != nil { - return nil, fmt.Errorf("failed to decode field '%s': %w", k, err) - } - processedMessage[k] = decoded - } - } - - return processedMessage, nil -} - -func typedDataDecodePrimitiveValue(typ string, value interface{}) (interface{}, error) { - val := fmt.Sprintf("%v", value) - out, err := ABIUnmarshalStringValuesAny([]string{typ}, []any{val}) - if err != nil { - return nil, err - } - return out[0], nil -} diff --git a/vendor/github.com/0xsequence/ethkit/ethcoder/typed_data_json.go b/vendor/github.com/0xsequence/ethkit/ethcoder/typed_data_json.go new file mode 100644 index 00000000..6d8645be --- /dev/null +++ b/vendor/github.com/0xsequence/ethkit/ethcoder/typed_data_json.go @@ -0,0 +1,337 @@ +package ethcoder + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "slices" + "sort" + "strings" + + "github.com/0xsequence/ethkit/go-ethereum/common" +) + +func TypedDataFromJSON(typedDataJSON string) (*TypedData, error) { + var typedData TypedData + err := json.Unmarshal([]byte(typedDataJSON), &typedData) + if err != nil { + return nil, err + } + return &typedData, nil +} + +func (t *TypedData) MarshalJSON() ([]byte, error) { + type TypedDataJSON struct { + Types TypedDataTypes `json:"types"` + PrimaryType string `json:"primaryType"` + Domain TypedDataDomain `json:"domain"` + Message map[string]interface{} `json:"message"` + } + + encodedMessage, err := t.jsonEncodeMessageValues(t.PrimaryType, t.Message) + if err != nil { + return nil, err + } + + return json.Marshal(TypedDataJSON{ + Types: t.Types, + PrimaryType: t.PrimaryType, + Domain: t.Domain, + Message: encodedMessage, + }) +} + +func (t *TypedData) jsonEncodeMessageValues(typeName string, message map[string]interface{}) (map[string]interface{}, error) { + typeFields, ok := t.Types[typeName] + if !ok { + return nil, fmt.Errorf("type '%s' not found in types", typeName) + } + + encodedMessage := make(map[string]interface{}) + + for _, field := range typeFields { + val, exists := message[field.Name] + if !exists { + continue + } + + // Handle arrays + if strings.HasSuffix(field.Type, "[]") { + baseType := field.Type[:len(field.Type)-2] + if arr, ok := val.([]interface{}); ok { + encodedArr := make([]interface{}, len(arr)) + for i, item := range arr { + encoded, err := t.jsonEncodeValue(baseType, item) + if err != nil { + return nil, err + } + encodedArr[i] = encoded + } + encodedMessage[field.Name] = encodedArr + continue + } + } + + // Handle single values + encoded, err := t.jsonEncodeValue(field.Type, val) + if err != nil { + return nil, err + } + encodedMessage[field.Name] = encoded + } + + return encodedMessage, nil +} + +func (t *TypedData) jsonEncodeValue(fieldType string, value interface{}) (interface{}, error) { + // Handle bytes/bytes32 + if strings.HasPrefix(fieldType, "bytes") { + switch v := value.(type) { + case []byte: + return "0x" + common.Bytes2Hex(v), nil + case [8]byte: + return "0x" + common.Bytes2Hex(v[:]), nil + case [16]byte: + return "0x" + common.Bytes2Hex(v[:]), nil + case [24]byte: + return "0x" + common.Bytes2Hex(v[:]), nil + case [32]byte: + return "0x" + common.Bytes2Hex(v[:]), nil + } + return value, nil + } + + // Handle nested custom types + if _, isCustomType := t.Types[fieldType]; isCustomType { + if nestedMsg, ok := value.(map[string]interface{}); ok { + return t.jsonEncodeMessageValues(fieldType, nestedMsg) + } + return nil, fmt.Errorf("value for custom type '%s' is not a map", fieldType) + } + + // Return primitive values as-is + return value, nil +} + +func (t *TypedData) UnmarshalJSON(data []byte) error { + // Intermediary structure to decode message field + type TypedDataRaw struct { + Types TypedDataTypes `json:"types"` + PrimaryType string `json:"primaryType"` + Domain struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + ChainID interface{} `json:"chainId,omitempty"` + VerifyingContract *common.Address `json:"verifyingContract,omitempty"` + Salt *common.Hash `json:"salt,omitempty"` + } `json:"domain"` + Message map[string]interface{} `json:"message"` + } + + // Json decoder with json.Number support, so that we can decode big.Int values + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + + var raw TypedDataRaw + if err := dec.Decode(&raw); err != nil { + return err + } + + // Ensure the "EIP712Domain" type is defined. In case its not defined + // we will add it to the types map + _, ok := raw.Types["EIP712Domain"] + if !ok { + raw.Types["EIP712Domain"] = []TypedDataArgument{} + if raw.Domain.Name != "" { + raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "name", Type: "string"}) + } + if raw.Domain.Version != "" { + raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "version", Type: "string"}) + } + if raw.Domain.ChainID != nil { + raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "chainId", Type: "uint256"}) + } + if raw.Domain.VerifyingContract != nil { + raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "verifyingContract", Type: "address"}) + } + if raw.Domain.Salt != nil { + raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "salt", Type: "bytes32"}) + } + } + + // Ensure primary type is defined + if raw.PrimaryType == "" { + // detect primary type if its unspecified + primaryType, err := typedDataDetectPrimaryType(raw.Types.Map(), raw.Message) + if err != nil { + return err + } + raw.PrimaryType = primaryType + } + _, ok = raw.Types[raw.PrimaryType] + if !ok { + return fmt.Errorf("primary type '%s' is not defined", raw.PrimaryType) + } + + // Decode the domain, which is mostly decooded except the chainId is an interface{} type + // because the value may be a number of a hex encoded number. We want it in a big.Int. + domain := TypedDataDomain{ + Name: raw.Domain.Name, + Version: raw.Domain.Version, + ChainID: nil, + VerifyingContract: raw.Domain.VerifyingContract, + Salt: raw.Domain.Salt, + } + if raw.Domain.ChainID != nil { + chainID := big.NewInt(0) + if val, ok := raw.Domain.ChainID.(float64); ok { + chainID.SetInt64(int64(val)) + } else if val, ok := raw.Domain.ChainID.(json.Number); ok { + chainID.SetString(val.String(), 10) + } else if val, ok := raw.Domain.ChainID.(string); ok { + if strings.HasPrefix(val, "0x") { + chainID.SetString(val[2:], 16) + } else { + chainID.SetString(val, 10) + } + } + domain.ChainID = chainID + } + + // Decode the raw message into Go runtime types + message, err := typedDataDecodeRawMessageMap(raw.Types.Map(), raw.PrimaryType, raw.Message) + if err != nil { + return err + } + + t.Types = raw.Types + t.PrimaryType = raw.PrimaryType + t.Domain = domain + + m, ok := message.(map[string]interface{}) + if !ok { + return fmt.Errorf("resulting message is not a map") + } + t.Message = m + + return nil +} + +func typedDataDetectPrimaryType(typesMap map[string]map[string]string, message map[string]interface{}) (string, error) { + // If there are only two types, and one is the EIP712Domain, then the other is the primary type + if len(typesMap) == 2 { + _, ok := typesMap["EIP712Domain"] + if ok { + for typ := range typesMap { + if typ == "EIP712Domain" { + continue + } + return typ, nil + } + } + } + + // Otherwise search for the primary type by looking for the first type that has a message field keys + messageKeys := []string{} + for k := range message { + messageKeys = append(messageKeys, k) + } + sort.Strings(messageKeys) + + for typ := range typesMap { + if typ == "EIP712Domain" { + continue + } + if len(typesMap[typ]) != len(messageKeys) { + continue + } + + typKeys := []string{} + for k := range typesMap[typ] { + typKeys = append(typKeys, k) + } + sort.Strings(typKeys) + + if !slices.Equal(messageKeys, typKeys) { + continue + } + return typ, nil + } + + return "", fmt.Errorf("no primary type found") +} + +func typedDataDecodeRawMessageMap(typesMap map[string]map[string]string, primaryType string, data interface{}) (interface{}, error) { + // Handle array types + if arr, ok := data.([]interface{}); ok { + results := make([]interface{}, len(arr)) + for i, item := range arr { + decoded, err := typedDataDecodeRawMessageMap(typesMap, primaryType, item) + if err != nil { + return nil, err + } + results[i] = decoded + } + return results, nil + } + + // Handle primitive directly + message, ok := data.(map[string]interface{}) + if !ok { + return typedDataDecodePrimitiveValue(primaryType, data) + } + + currentType, ok := typesMap[primaryType] + if !ok { + return nil, fmt.Errorf("type %s is not defined", primaryType) + } + + processedMessage := make(map[string]interface{}) + for k, v := range message { + typ, ok := currentType[k] + if !ok { + return nil, fmt.Errorf("message field '%s' is missing type definition on '%s'", k, primaryType) + } + + // Extract base type and check if it's an array + baseType := typ + isArray := false + if idx := strings.Index(typ, "["); idx != -1 { + baseType = typ[:idx] + isArray = true + } + + // Process value based on whether it's a custom or primitive type + if _, isCustomType := typesMap[baseType]; isCustomType { + decoded, err := typedDataDecodeRawMessageMap(typesMap, baseType, v) + if err != nil { + return nil, err + } + processedMessage[k] = decoded + } else { + var decoded interface{} + var err error + if isArray { + decoded, err = typedDataDecodeRawMessageMap(typesMap, baseType, v) + } else { + decoded, err = typedDataDecodePrimitiveValue(baseType, v) + } + if err != nil { + return nil, fmt.Errorf("failed to decode field '%s': %w", k, err) + } + processedMessage[k] = decoded + } + } + + return processedMessage, nil +} + +func typedDataDecodePrimitiveValue(typ string, value interface{}) (interface{}, error) { + val := fmt.Sprintf("%v", value) + out, err := ABIUnmarshalStringValuesAny([]string{typ}, []any{val}) + if err != nil { + return nil, fmt.Errorf("typedDataDecodePrimitiveValue: %w", err) + } + return out[0], nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index fa6c1ae0..5b1bf3f6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,7 +1,7 @@ # dario.cat/mergo v1.0.0 ## explicit; go 1.13 dario.cat/mergo -# github.com/0xsequence/ethkit v1.30.2 +# github.com/0xsequence/ethkit v1.30.6 ## explicit; go 1.21 github.com/0xsequence/ethkit github.com/0xsequence/ethkit/ethartifact