From 16a1140ecaabbee7b2a52f4b3f3896b4bf568bb2 Mon Sep 17 00:00:00 2001 From: nhannamsiu Date: Fri, 16 Dec 2022 15:56:22 +1300 Subject: [PATCH 1/4] improve eip712 parser --- eip712_cosmos.go | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/eip712_cosmos.go b/eip712_cosmos.go index 27b7eba4..68133432 100644 --- a/eip712_cosmos.go +++ b/eip712_cosmos.go @@ -34,6 +34,9 @@ func WrapTxToEIP712( return typeddata.TypedData{}, err } + // remove all arrays with len = 0 to conform EIP712 standard + txData = trimEmptyArrays(reflect.ValueOf(txData)).Interface().(map[string]interface{}) + domain := typeddata.TypedDataDomain{ Name: "Injective Web3", Version: "1.0.0", @@ -267,9 +270,13 @@ func traverseFields( } fieldPrefix := fmt.Sprintf("%s.%s", prefix, fieldName) - ethTyp := typToEth(fieldType) if len(ethTyp) > 0 { + // explicitly convert []uint8 to string as EIP712 doesn't support []uint8 + if ethTyp == "uint8" && isCollection { + ethTyp = "string" + } + if prefix == typeDefPrefix { typeMap[rootType] = append(typeMap[rootType], typeddata.Type{ Name: fieldName, @@ -427,3 +434,33 @@ func doRecover(err *error) { *err = errors.Errorf("%v", r) } } + +func trimEmptyArrays(obj reflect.Value) reflect.Value { + for _, k := range obj.MapKeys() { + // if current level is object + if mapObj, ok := obj.Interface().(map[string]interface{}); ok { + // and its field is array + if arr, ok := obj.MapIndex(k).Interface().([]interface{}); ok { + // and array length is 0 then delete and move to next element + if len(arr) == 0 { + delete(mapObj, k.String()) + continue + } + } + } + + switch childObj := obj.MapIndex(k).Interface().(type) { + case []interface{}: + // scan child arrays + for _, arr := range childObj { + // continue scan + trimEmptyArrays(reflect.ValueOf(arr)) + } + case map[string]interface{}: + // scan child maps + trimEmptyArrays(reflect.ValueOf(childObj)) + } + } + + return obj +} From 0c336916880fa5745b024f73a26ed9b2b8a23f46 Mon Sep 17 00:00:00 2001 From: nhannamsiu Date: Sun, 18 Dec 2022 11:28:36 +1300 Subject: [PATCH 2/4] feat: parse inner cosmwasm msg to eip712 type --- eip712_cosmos.go | 93 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/eip712_cosmos.go b/eip712_cosmos.go index 68133432..a873586f 100644 --- a/eip712_cosmos.go +++ b/eip712_cosmos.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "fmt" + "golang.org/x/text/cases" + "golang.org/x/text/language" "math/big" "reflect" "runtime/debug" @@ -19,6 +21,12 @@ import ( "github.com/InjectiveLabs/sdk-go/typeddata" ) +const ( + CosmwasmPrefix = "Cosmwasm" + CosmwasmInnerMsgMarker = CosmwasmPrefix + "InnerMsgMarker" + CosmwasmExecType = "wasm/MsgExecuteContract" +) + // WrapTxToEIP712 is an ultimate method that wraps Amino-encoded Cosmos Tx JSON data // into an EIP712-compatible request. All messages must be of the same type. func WrapTxToEIP712( @@ -62,6 +70,20 @@ func WrapTxToEIP712( } } + // parse cosmwasm inner msg + cosmwasmEIP712Types := typeddata.Types{} + for _, m := range txData["msgs"].([]interface{}) { + msgObj := m.(map[string]interface{}) + if msgObj["type"] == CosmwasmExecType { + innerMsg := msgObj["value"].(map[string]interface{})["msg"] + cosmwasmEIP712Types = parseCosmwasmInnerMsg(innerMsg, cosmwasmEIP712Types) + fmt.Println(cosmwasmEIP712Types) + } + } + for k, v := range cosmwasmEIP712Types { + msgTypes[k] = v + } + var typedData = typeddata.TypedData{ Types: msgTypes, PrimaryType: "Tx", @@ -141,14 +163,11 @@ func walkFields(cdc codectypes.AnyUnpacker, typeMap typeddata.Types, rootType st v := reflect.ValueOf(in) for { - if t.Kind() == reflect.Ptr || - t.Kind() == reflect.Interface { + if t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface { t = t.Elem() v = v.Elem() - continue } - break } @@ -272,9 +291,9 @@ func traverseFields( fieldPrefix := fmt.Sprintf("%s.%s", prefix, fieldName) ethTyp := typToEth(fieldType) if len(ethTyp) > 0 { - // explicitly convert []uint8 to string as EIP712 doesn't support []uint8 + // special case to parse type for for cosmwasm inner msg if ethTyp == "uint8" && isCollection { - ethTyp = "string" + ethTyp = CosmwasmInnerMsgMarker } if prefix == typeDefPrefix { @@ -464,3 +483,65 @@ func trimEmptyArrays(obj reflect.Value) reflect.Value { return obj } + +func parseCosmwasmInnerMsg(msg interface{}, EIP712Types typeddata.Types) typeddata.Types { + var methodName string + var methodArgs interface{} + + // this top-level map should only have kv pair + for k, v := range msg.(map[string]interface{}) { + // top key will me contract method marker + methodBodyType := CosmwasmPrefix + cases.Title(language.English).String(k) + EIP712Types[CosmwasmInnerMsgMarker] = []typeddata.Type{{Name: k, Type: methodBodyType}} + + // get method name and args + methodName = CosmwasmPrefix + cases.Title(language.English).String(k) + methodArgs = v + + break + } + + // these will be contract method args + // TODO: remove assumption that cosmwasm fields will always be string + for mk, _ := range methodArgs.(map[string]interface{}) { + arg := typeddata.Type{Name: mk, Type: "string"} + EIP712Types[methodName] = append(EIP712Types[methodName], arg) + } + + return EIP712Types + + //// handle message cases + //switch m := msg.(type) { + //case map[string]interface{}: + // // walk map + // for k, v := range m { + // switch v.(type) { + // // walk nested interfaces + // case map[string]interface{}, []interface{}: + // EIP712Types[k] = append( + // EIP712Types[k], + // typeddata.Type{ + // Name: k, + // Type: CosmwasmPrefix + cases.Title(language.English).String(k), + // }, + // ) + // parseCosmwasmInnerMsg(v, EIP712Types) + // // append to types + // default: + // EIP712Types[k] = append( + // EIP712Types[k], + // typeddata.Type{ + // Name: k, + // Type: "string", + // }, + // ) + // } + // } + //case []interface{}: + // // TODO: support contract array arguments + // // walk array + // for i, v := range m { + // fmt.Println(i, v) + // } + //} +} From 6b3c3d92c714ccb7c9f5749a3fbbd2059cb17706 Mon Sep 17 00:00:00 2001 From: nhannamsiu Date: Mon, 19 Dec 2022 13:08:43 +1300 Subject: [PATCH 3/4] feat: support complex nested types for cosmwas inner msgs parser --- eip712_cosmos.go | 104 +++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 62 deletions(-) diff --git a/eip712_cosmos.go b/eip712_cosmos.go index a873586f..74a30c50 100644 --- a/eip712_cosmos.go +++ b/eip712_cosmos.go @@ -4,8 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "golang.org/x/text/cases" - "golang.org/x/text/language" "math/big" "reflect" "runtime/debug" @@ -76,8 +74,7 @@ func WrapTxToEIP712( msgObj := m.(map[string]interface{}) if msgObj["type"] == CosmwasmExecType { innerMsg := msgObj["value"].(map[string]interface{})["msg"] - cosmwasmEIP712Types = parseCosmwasmInnerMsg(innerMsg, cosmwasmEIP712Types) - fmt.Println(cosmwasmEIP712Types) + cosmwasmEIP712Types = ExtractCosmwasmTypes(CosmwasmInnerMsgMarker, cosmwasmEIP712Types, reflect.ValueOf(innerMsg)) } } for k, v := range cosmwasmEIP712Types { @@ -352,7 +349,6 @@ func jsonNameFromTag(tag reflect.StructTag) string { } // _.foo_bar.baz -> TypeFooBarBaz -// // this is needed for Geth's own signing code which doesn't // tolerate complex type names func sanitizeTypedef(str string) string { @@ -484,64 +480,48 @@ func trimEmptyArrays(obj reflect.Value) reflect.Value { return obj } -func parseCosmwasmInnerMsg(msg interface{}, EIP712Types typeddata.Types) typeddata.Types { - var methodName string - var methodArgs interface{} - - // this top-level map should only have kv pair - for k, v := range msg.(map[string]interface{}) { - // top key will me contract method marker - methodBodyType := CosmwasmPrefix + cases.Title(language.English).String(k) - EIP712Types[CosmwasmInnerMsgMarker] = []typeddata.Type{{Name: k, Type: methodBodyType}} - - // get method name and args - methodName = CosmwasmPrefix + cases.Title(language.English).String(k) - methodArgs = v - - break - } +func ExtractCosmwasmTypes(parentType string, rootTypes typeddata.Types, obj reflect.Value) typeddata.Types { + for _, k := range obj.MapKeys() { + switch field := obj.MapIndex(k).Interface().(type) { + // field is array + case []interface{}: + // ignore empty arrays + if len(field) == 0 { + continue + } + switch field[0].(type) { + // if element is struct then register new type and walk through child struct + case map[string]interface{}: + n := k.String() + t := n + "_value[]" + rootTypes[parentType] = append(rootTypes[parentType], typeddata.Type{Name: n, Type: t}) + ExtractCosmwasmTypes(t, rootTypes, reflect.ValueOf(field[0])) + + // if element is primary type then register new type + default: + n := k.String() + t := reflect.TypeOf(field[0]).String() + "[]" + rootTypes[parentType] = append(rootTypes[parentType], typeddata.Type{Name: n, Type: t}) + } - // these will be contract method args - // TODO: remove assumption that cosmwasm fields will always be string - for mk, _ := range methodArgs.(map[string]interface{}) { - arg := typeddata.Type{Name: mk, Type: "string"} - EIP712Types[methodName] = append(EIP712Types[methodName], arg) + // field is map + case map[string]interface{}: + // register new type and walk through child struct + n := k.String() + t := n + "_value" + rootTypes[parentType] = append(rootTypes[parentType], typeddata.Type{Name: n, Type: t}) + ExtractCosmwasmTypes(t, rootTypes, reflect.ValueOf(field)) + + // field is primary type then register normally + default: + n := k.String() + t := typeddata.Type{ + Name: n, + Type: reflect.TypeOf(field).String(), + } + rootTypes[parentType] = append(rootTypes[parentType], t) + } } - return EIP712Types - - //// handle message cases - //switch m := msg.(type) { - //case map[string]interface{}: - // // walk map - // for k, v := range m { - // switch v.(type) { - // // walk nested interfaces - // case map[string]interface{}, []interface{}: - // EIP712Types[k] = append( - // EIP712Types[k], - // typeddata.Type{ - // Name: k, - // Type: CosmwasmPrefix + cases.Title(language.English).String(k), - // }, - // ) - // parseCosmwasmInnerMsg(v, EIP712Types) - // // append to types - // default: - // EIP712Types[k] = append( - // EIP712Types[k], - // typeddata.Type{ - // Name: k, - // Type: "string", - // }, - // ) - // } - // } - //case []interface{}: - // // TODO: support contract array arguments - // // walk array - // for i, v := range m { - // fmt.Println(i, v) - // } - //} + return rootTypes } From baadcb79ecb96d9d24782bffe2ae530278dc3624 Mon Sep 17 00:00:00 2001 From: nhannamsiu Date: Mon, 19 Dec 2022 14:14:44 +1300 Subject: [PATCH 4/4] fix: format reference type --- eip712_cosmos.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/eip712_cosmos.go b/eip712_cosmos.go index 74a30c50..ea3afc90 100644 --- a/eip712_cosmos.go +++ b/eip712_cosmos.go @@ -493,7 +493,7 @@ func ExtractCosmwasmTypes(parentType string, rootTypes typeddata.Types, obj refl // if element is struct then register new type and walk through child struct case map[string]interface{}: n := k.String() - t := n + "_value[]" + t := sanitizeTypedef(n + "Value[]") rootTypes[parentType] = append(rootTypes[parentType], typeddata.Type{Name: n, Type: t}) ExtractCosmwasmTypes(t, rootTypes, reflect.ValueOf(field[0])) @@ -508,18 +508,15 @@ func ExtractCosmwasmTypes(parentType string, rootTypes typeddata.Types, obj refl case map[string]interface{}: // register new type and walk through child struct n := k.String() - t := n + "_value" + t := sanitizeTypedef(n + "Value") rootTypes[parentType] = append(rootTypes[parentType], typeddata.Type{Name: n, Type: t}) ExtractCosmwasmTypes(t, rootTypes, reflect.ValueOf(field)) // field is primary type then register normally default: n := k.String() - t := typeddata.Type{ - Name: n, - Type: reflect.TypeOf(field).String(), - } - rootTypes[parentType] = append(rootTypes[parentType], t) + t := reflect.TypeOf(field).String() + rootTypes[parentType] = append(rootTypes[parentType], typeddata.Type{Name: n, Type: t}) } }