diff --git a/modules/apps/27-interchain-accounts/host/client/cli/tx.go b/modules/apps/27-interchain-accounts/host/client/cli/tx.go index c67e57914ab..afe6da691e2 100644 --- a/modules/apps/27-interchain-accounts/host/client/cli/tx.go +++ b/modules/apps/27-interchain-accounts/host/client/cli/tx.go @@ -1,6 +1,8 @@ package cli import ( + "encoding/json" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -53,12 +55,45 @@ which submits pre-built packet data containing messages to be executed on the ho // generatePacketData takes in message bytes and a memo and serializes the message into an // instance of InterchainAccountPacketData which is returned as bytes. func generatePacketData(cdc *codec.ProtoCodec, msgBytes []byte, memo string) ([]byte, error) { - var msg sdk.Msg - if err := cdc.UnmarshalInterfaceJSON(msgBytes, &msg); err != nil { + sdkMessages, err := convertBytesIntoSdkMessages(cdc, msgBytes) + if err != nil { return nil, err } - icaPacketDataBytes, err := icatypes.SerializeCosmosTx(cdc, []sdk.Msg{msg}) + return generateIcaPacketDataFromSdkMessages(cdc, sdkMessages, memo) +} + +// convertBytesIntoSdkMessages returns a list of sdk messages from bytes. The bytes can be in the form of a single +// message, or a json array of messages. +func convertBytesIntoSdkMessages(cdc *codec.ProtoCodec, msgBytes []byte) ([]sdk.Msg, error) { + var rawMessages []json.RawMessage + if err := json.Unmarshal(msgBytes, &rawMessages); err != nil { + // if we fail to unmarshal a list of messages, we assume we are just dealing with a single message. + // in this case we return a list of a single item. + var msg sdk.Msg + if err := cdc.UnmarshalInterfaceJSON(msgBytes, &msg); err != nil { + return nil, err + } + + return []sdk.Msg{msg}, nil + } + + sdkMessages := make([]sdk.Msg, len(rawMessages)) + for i, anyJSON := range rawMessages { + var msg sdk.Msg + if err := cdc.UnmarshalInterfaceJSON(anyJSON, &msg); err != nil { + return nil, err + } + + sdkMessages[i] = msg + } + + return sdkMessages, nil +} + +// generateIcaPacketDataFromSdkMessages generates ica packet data as bytes from a given set of sdk messages and a memo. +func generateIcaPacketDataFromSdkMessages(cdc *codec.ProtoCodec, sdkMessages []sdk.Msg, memo string) ([]byte, error) { + icaPacketDataBytes, err := icatypes.SerializeCosmosTx(cdc, sdkMessages) if err != nil { return nil, err } diff --git a/modules/apps/27-interchain-accounts/host/client/cli/tx_test.go b/modules/apps/27-interchain-accounts/host/client/cli/tx_test.go index ae6a9c2b76b..7c4461921ee 100644 --- a/modules/apps/27-interchain-accounts/host/client/cli/tx_test.go +++ b/modules/apps/27-interchain-accounts/host/client/cli/tx_test.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "testing" "github.com/cosmos/cosmos-sdk/codec" @@ -35,6 +36,8 @@ const bankSendMessage = `{ ] }` +var multiMsg = fmt.Sprintf("[ %s, %s ]", msgDelegateMessage, bankSendMessage) + func TestGeneratePacketData(t *testing.T) { tests := []struct { name string @@ -42,21 +45,30 @@ func TestGeneratePacketData(t *testing.T) { expectedPass bool message string registerInterfaceFn func(registry codectypes.InterfaceRegistry) - assertionFn func(msg sdk.Msg) + assertionFn func(t *testing.T, msgs []sdk.Msg) }{ + { + name: "multi message", + memo: "", + expectedPass: true, + message: multiMsg, + registerInterfaceFn: func(registry codectypes.InterfaceRegistry) { + stakingtypes.RegisterInterfaces(registry) + banktypes.RegisterInterfaces(registry) + }, + assertionFn: func(t *testing.T, msgs []sdk.Msg) { + assertMsgDelegate(t, msgs, 0) + assertMsgBankSend(t, msgs, 1) + }, + }, { name: "packet data generation succeeds (MsgDelegate)", memo: "non-empty-memo", expectedPass: true, message: msgDelegateMessage, registerInterfaceFn: stakingtypes.RegisterInterfaces, - assertionFn: func(msg sdk.Msg) { - msgDelegate, ok := msg.(*stakingtypes.MsgDelegate) - require.True(t, ok) - require.Equal(t, "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", msgDelegate.DelegatorAddress) - require.Equal(t, "cosmosvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnmxnh3k", msgDelegate.ValidatorAddress) - require.Equal(t, "stake", msgDelegate.Amount.Denom) - require.Equal(t, uint64(1000), msgDelegate.Amount.Amount.Uint64()) + assertionFn: func(t *testing.T, msgs []sdk.Msg) { + assertMsgDelegate(t, msgs, 0) }, }, { @@ -65,13 +77,8 @@ func TestGeneratePacketData(t *testing.T) { expectedPass: true, message: bankSendMessage, registerInterfaceFn: banktypes.RegisterInterfaces, - assertionFn: func(msg sdk.Msg) { - bankSendMsg, ok := msg.(*banktypes.MsgSend) - require.True(t, ok) - require.Equal(t, "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", bankSendMsg.FromAddress) - require.Equal(t, "cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", bankSendMsg.ToAddress) - require.Equal(t, "stake", bankSendMsg.Amount.GetDenomByIndex(0)) - require.Equal(t, uint64(1000), bankSendMsg.Amount[0].Amount.Uint64()) + assertionFn: func(t *testing.T, msgs []sdk.Msg) { + assertMsgBankSend(t, msgs, 0) }, }, { @@ -80,21 +87,21 @@ func TestGeneratePacketData(t *testing.T) { expectedPass: true, message: msgDelegateMessage, registerInterfaceFn: stakingtypes.RegisterInterfaces, - assertionFn: func(msg sdk.Msg) {}, + assertionFn: nil, }, { - name: "invalid message string", - expectedPass: false, - message: "", - registerInterfaceFn: func(codectypes.InterfaceRegistry) {}, - assertionFn: func(sdk.Msg) {}, + name: "invalid message string", + expectedPass: false, + message: "", }, } for _, tc := range tests { tc := tc ir := codectypes.NewInterfaceRegistry() - tc.registerInterfaceFn(ir) + if tc.registerInterfaceFn != nil { + tc.registerInterfaceFn(ir) + } cdc := codec.NewProtoCodec(ir) @@ -118,7 +125,9 @@ func TestGeneratePacketData(t *testing.T) { require.NoError(t, err) require.NotNil(t, messages) - tc.assertionFn(messages[0]) + if tc.assertionFn != nil { + tc.assertionFn(t, messages) + } } else { require.Error(t, err) require.Nil(t, bz) @@ -126,3 +135,21 @@ func TestGeneratePacketData(t *testing.T) { }) } } + +func assertMsgBankSend(t *testing.T, msgs []sdk.Msg, idx int) { + bankSendMsg, ok := msgs[idx].(*banktypes.MsgSend) + require.True(t, ok) + require.Equal(t, "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", bankSendMsg.FromAddress) + require.Equal(t, "cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", bankSendMsg.ToAddress) + require.Equal(t, "stake", bankSendMsg.Amount.GetDenomByIndex(0)) + require.Equal(t, uint64(1000), bankSendMsg.Amount[0].Amount.Uint64()) +} + +func assertMsgDelegate(t *testing.T, msgs []sdk.Msg, idx int) { + msgDelegate, ok := msgs[idx].(*stakingtypes.MsgDelegate) + require.True(t, ok) + require.Equal(t, "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", msgDelegate.DelegatorAddress) + require.Equal(t, "cosmosvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnmxnh3k", msgDelegate.ValidatorAddress) + require.Equal(t, "stake", msgDelegate.Amount.Denom) + require.Equal(t, uint64(1000), msgDelegate.Amount.Amount.Uint64()) +}