Skip to content

Commit

Permalink
ibc middlewares + support multitype arrays for any[]
Browse files Browse the repository at this point in the history
  • Loading branch information
trevormil committed Aug 14, 2024
1 parent 6776565 commit 1ea4f05
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 32 deletions.
4 changes: 3 additions & 1 deletion app/ante/eip712.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,8 @@ func VerifySignature(
//Then, we sort the message field by alphabetizing the JSON keys

//Creates the EIP712 message payload
eip712Message := typedData.Message //map[string]interface{}
// Marshal the map to JSON
eip712Message := typedData.Message //map[string]interface{}
jsonData, err := json.Marshal(eip712Message)
if err != nil {
return sdkerrors.Wrap(err, "failed to marshal json")
Expand Down Expand Up @@ -401,6 +401,8 @@ func VerifySignature(
if !standardMsgSigValid && !hashedMsgSigValid && !humanReadableMsgSigValid {
return sdkerrors.Wrapf(types.ErrorInvalidSigner, "failed to verify delegated fee payer %s signature %s %s", recoveredFeePayerAcc, jsonStr, jsonHashHexStr)
}


} else if chain == "Bitcoin" {
//verify bitcoin bip322 signature
message := string(sortedBytes)
Expand Down
34 changes: 31 additions & 3 deletions app/ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import (
ibcfee "github.com/cosmos/ibc-go/v8/modules/apps/29-fee"
ibcfeekeeper "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/keeper"
ibcfeetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types"
"github.com/cosmos/ibc-go/v8/modules/apps/transfer"
ibctransfer "github.com/cosmos/ibc-go/v8/modules/apps/transfer"
ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper"
ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"

ibccallbacks "github.com/cosmos/ibc-go/modules/apps/callbacks"
ibc "github.com/cosmos/ibc-go/v8/modules/core"
ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
ibcconnectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"
Expand Down Expand Up @@ -157,7 +159,6 @@ func (app *App) registerIBCModules(appOpts servertypes.AppOptions) error {
// Create IBC modules with ibcfee middleware
transferIBCModule := ibcfee.NewIBCMiddleware(ibctransfer.NewIBCModule(app.TransferKeeper), app.IBCFeeKeeper)


//TODO: There was some wasmd IBC code that we did not fully copy here (e.g. middlewares, etc)

// integration point for custom authentication modules
Expand All @@ -169,7 +170,6 @@ func (app *App) registerIBCModules(appOpts servertypes.AppOptions) error {

icaHostIBCModule := ibcfee.NewIBCMiddleware(icahost.NewIBCModule(app.ICAHostKeeper), app.IBCFeeKeeper)


// Create static IBC router, add transfer route, then set and seal it
ibcRouter := porttypes.NewRouter().
AddRoute(ibctransfertypes.ModuleName, transferIBCModule).
Expand All @@ -184,14 +184,42 @@ func (app *App) registerIBCModules(appOpts servertypes.AppOptions) error {
ibcRouter.AddRoute(mapsmoduletypes.ModuleName, mapsIBCModule)

var wasmStack porttypes.IBCModule
wasmStack = wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCFeeKeeper)
wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCFeeKeeper)
wasmStack = wasmStackIBCHandler
wasmStack = ibcfee.NewIBCMiddleware(wasmStack, app.IBCFeeKeeper)
ibcRouter.AddRoute(wasmtypes.ModuleName, wasmStack)

wasmxIBCModule := ibcfee.NewIBCMiddleware(wasmxmodule.NewIBCModule(app.WasmxKeeper), app.IBCFeeKeeper)
ibcRouter.AddRoute(wasmxmoduletypes.ModuleName, wasmxIBCModule)
// this line is used by starport scaffolding # ibc/app/module

// Create Interchain Accounts Stack
// SendPacket, since it is originating from the application to core IBC:
// icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket
var icaControllerStack porttypes.IBCModule
icaControllerStack = icacontroller.NewIBCMiddleware(noAuthzModule, app.ICAControllerKeeper)
// app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule)
icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper)
icaControllerStack = ibccallbacks.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper, wasmStackIBCHandler, wasm.DefaultMaxIBCCallbackGas)
icaICS4Wrapper := icaControllerStack.(porttypes.ICS4Wrapper)
icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper)
// Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the ica controller keeper
app.ICAControllerKeeper.WithICS4Wrapper(icaICS4Wrapper)

// Create Transfer Stack
var transferStack porttypes.IBCModule
transferStack = transfer.NewIBCModule(app.TransferKeeper)
transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, wasmStackIBCHandler, wasm.DefaultMaxIBCCallbackGas)
transferICS4Wrapper := transferStack.(porttypes.ICS4Wrapper)
transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper)
app.TransferKeeper.WithICS4Wrapper(transferICS4Wrapper)

// RecvPacket, message that originates from core IBC and goes down to app, the flow is:
// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket
var icaHostStack porttypes.IBCModule
icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper)
icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper)

app.IBCKeeper.SetRouter(ibcRouter)

app.ScopedIBCKeeper = scopedIBCKeeper
Expand Down
114 changes: 90 additions & 24 deletions chain-handlers/ethereum/ethereum/eip712/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"

"golang.org/x/text/cases"
Expand Down Expand Up @@ -65,7 +66,6 @@ func CreateEIP712Types(messagePayload eip712MessagePayload) (apitypes.Types, err
{Name: "fee", Type: "Fee"},
{Name: "memo", Type: "string"},
{Name: "sequence", Type: "string"},
// Note timeout_height was removed because it was not getting filled with the legacyTx
},
"Fee": {
{Name: "amount", Type: "Coin[]"},
Expand All @@ -82,23 +82,13 @@ func CreateEIP712Types(messagePayload eip712MessagePayload) (apitypes.Types, err
field := msgFieldForIndex(i)
msg := messagePayload.Payload.Get(field)

//This is new code. The original Ethermint code does not account
//for optional fields / empty fields.
//Thus, there are often mismatches between the types generated by the
//SDK and the types here.

//To combat this, we simply make everything mandatory.
//Here, we get a sample JSON with all fields populated and calculate the types from that.
//We later populate any empty fields

//This is a bit of a hack, but it works.
//Note this isn't applicable if all fields are already populated which is often the case
//for most SDK messages.
msg, err := GetPopulatedSchemaForMsg(msg)
if err != nil {
return nil, errorsmod.Wrapf(err, "%v", msg.Raw)
}
// return nil, errorsmod.Wrapf(err, "%v", msg.Raw)

if err := addMsgTypesToRoot(eip712Types, field, msg); err != nil {
return nil, err
Expand Down Expand Up @@ -184,9 +174,6 @@ func recursivelyAddTypesToRoot(
continue
}

// Handle array type by unwrapping the first element.
// Note that arrays with multiple types are not supported
// using EIP-712, so we can ignore that case.
isCollection := false
if field.IsArray() {
fieldAsArray := field.Array()
Expand All @@ -206,8 +193,7 @@ func recursivelyAddTypesToRoot(

ethType := getEthTypeForJSON(field)

// Handle JSON primitive types by adding the corresponding
// EIP-712 type to the types schema.
// Handle JSON primitive types
if ethType != "" {
if isCollection {
ethType += "[]"
Expand All @@ -221,18 +207,98 @@ func recursivelyAddTypesToRoot(
// in EIP-712, so we can exclude that case.
if field.IsObject() {
fieldPrefix := prefixForSubField(prefix, fieldName)
isMultiTypeArray := false

fieldTypeDef, err := recursivelyAddTypesToRoot(typeMap, rootType, fieldPrefix, field)
if err != nil {
return "", err
}

fieldTypeDef = sanitizeTypedef(fieldTypeDef)
//check if we have a multi-type array (e.g. google.protobuf.Any with multiple types)
//if so, we append the index to the type (Prefix0, Prefix1, etc) instead of using a SingleType[]
if isCollection {
fieldTypeDef += "[]"
originalField := payload.Get(fieldName)
origArr := originalField.Array()

// we will compare the first field to the rest of the fields
firstField := origArr[0]
firstBlankTypeMap := apitypes.Types{}
_, err = recursivelyAddTypesToRoot(firstBlankTypeMap, "_", "_", firstField)
if err != nil {
return "", err
}

for i := range origArr {
if i <= 0 {
continue
}

if isMultiTypeArray {
break
}

// Note we assume that multi-type arrays will be multi-type Object arrays
// If we need to handle others, we need to add logic here (getEthTypeForJSON returns "" for Object, Array, Null)
currEthType := getEthTypeForJSON(origArr[i])
if ethType != currEthType {
//is a different primitive type
isMultiTypeArray = true
break
}

currField := origArr[i]
blankTypeMap := apitypes.Types{}
_, err := recursivelyAddTypesToRoot(blankTypeMap, "_", "_", currField)
if err != nil {
return "", err
}

//compare the types of the two maps
//for each key, compare the types
for k, types := range blankTypeMap {
if len(types) != len(firstBlankTypeMap[k]) {
isMultiTypeArray = true
break
}

for i, typeObj := range types {
if typeObj.Type != firstBlankTypeMap[k][i].Type || typeObj.Name != firstBlankTypeMap[k][i].Name {
isMultiTypeArray = true
break
}
}
}

}
}

typesToAdd = appendedTypesList(typesToAdd, fieldName, fieldTypeDef)
if !isMultiTypeArray {
fieldTypeDef, err := recursivelyAddTypesToRoot(typeMap, rootType, fieldPrefix, field)
if err != nil {
return "", err
}

fieldTypeDef = sanitizeTypedef(fieldTypeDef)
if isCollection {
fieldTypeDef += "[]"
}

typesToAdd = appendedTypesList(typesToAdd, fieldName, fieldTypeDef)
} else {
//TODO: we add the first but this is really unused so it should probably be cleaned up
fieldTypeDef, err := recursivelyAddTypesToRoot(typeMap, rootType, fieldPrefix, field)
if err != nil {
return "", err
}
fieldTypeDef = sanitizeTypedef(fieldTypeDef)
fieldTypeDef += "Any[]"
typesToAdd = appendedTypesList(typesToAdd, fieldName, fieldTypeDef)

//For multi-type arrays, we also add the prefix + "0" or "1" etc to the types map
for i := range field.Array() {
field = field.Array()[i]
fieldPrefix := prefixForSubField(fieldPrefix, fieldName) + strconv.Itoa(i)
_, err := recursivelyAddTypesToRoot(typeMap, rootType, fieldPrefix, field)
if err != nil {
return "", err
}
}
}

continue
}
Expand Down
23 changes: 19 additions & 4 deletions chain-handlers/ethereum/ethereum/eip712/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package eip712

import (
"strconv"
"strings"

"github.com/ethereum/go-ethereum/signer/core/apitypes"
Expand All @@ -9,7 +10,7 @@ import (

// NormalizeEmptyTypes is a recursive function that adds empty values to fields that are omitted when serialized with Proto and Amino.
// EIP712 doesn't support optional fields, so we add the omitted empty values back in here.
// This includes empty strings, when a uint64 is 0, and when a bool is false.
// This includes empty strings, when a uint64 is 0 (for cosmos.Uint we do empty string since it is a custom type ""), and when a bool is false.
func NormalizeEmptyTypes(typedData apitypes.TypedData, typeObjArr []apitypes.Type, mapObject map[string]interface{}) (map[string]interface{}, error) {
for _, typeObj := range typeObjArr {
typeStr := typeObj.Type
Expand All @@ -19,12 +20,26 @@ func NormalizeEmptyTypes(typedData apitypes.TypedData, typeObjArr []apitypes.Typ
mapObject[typeObj.Name] = []interface{}{}
} else if strings.Contains(typeStr, "[]") && value != nil {
valueArr := value.([]interface{})
// Get typeStr without the brackets at the end
// Get typeStr without the [] brackets at the end
typeStr = typeStr[:len(typeStr)-2]

//For multi-type arrays, we add Any[] to the end of the typeStr
//And the individual element types are going to be the stripped typeStr + "0" or "1" etc
isMultiTypeArray := strings.Contains(typeStr, "Any[]")
if isMultiTypeArray {
typeStr = typeStr[:len(typeStr)-3]
}

for i, value := range valueArr {

elementTypeStr := typeStr
if isMultiTypeArray {
elementTypeStr += strconv.Itoa(i)
}

innerMap, ok := value.(map[string]interface{})
if ok {
newMap, err := NormalizeEmptyTypes(typedData, typedData.Types[typeStr], innerMap)
newMap, err := NormalizeEmptyTypes(typedData, typedData.Types[elementTypeStr], innerMap)
if err != nil {
return mapObject, err
}
Expand All @@ -35,7 +50,7 @@ func NormalizeEmptyTypes(typedData apitypes.TypedData, typeObjArr []apitypes.Typ
} else if typeStr == "string" && value == nil {
mapObject[typeObj.Name] = ""
} else if typeStr == "uint64" && value == nil {
mapObject[typeObj.Name] = "0" //TODO: Does this really work / resolve correctly? We don't use it in any x/badges txs but it should be tested
mapObject[typeObj.Name] = "0"
} else if typeStr == "bool" && value == nil {
mapObject[typeObj.Name] = false
} else {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
github.com/cosmos/ibc-go/modules/apps/callbacks v0.2.1-0.20231113120333-342c00b0f8bd
github.com/ethereum/go-ethereum v1.14.7
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.5.4
Expand Down

0 comments on commit 1ea4f05

Please sign in to comment.