-
Notifications
You must be signed in to change notification settings - Fork 626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add host cli to generate ica packet data #2297
Changes from 16 commits
22d9e1b
3ecdc1d
1f12260
fe4880a
4f3ef30
4e6ed4e
47abc0b
96f1f24
e067218
8645a8b
d520833
2a43f5c
c5f7fdd
cf2179f
d477702
5ae13d3
e306637
7411b53
5076fbd
30b7907
8bfbf57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package cli | ||
|
||
import ( | ||
"encoding/json" | ||
|
||
"github.com/cosmos/cosmos-sdk/client" | ||
"github.com/cosmos/cosmos-sdk/codec" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/spf13/cobra" | ||
|
||
icatypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/types" | ||
) | ||
|
||
const ( | ||
memoFlag string = "memo" | ||
) | ||
|
||
func generatePacketDataCmd() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "generate-packet-data [message]", | ||
Short: "Generates ICA packet data.", | ||
Long: `generate-packet-data accepts a message string and serializes it | ||
into packet data which is outputted to stdout. It can be used in conjunction with send-tx" | ||
which submits pre-built packet data containing messages to be executed on the host chain. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to add an example here? Maybe we can just use the regular bank transfer message used in the example in the ica-demo repo?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great suggestions, I'll add this as an example There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @crodriguezvega I added this example and also a multi message example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, @chatton! |
||
`, | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
clientCtx, err := client.GetClientTxContext(cmd) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) | ||
|
||
memo, err := cmd.Flags().GetString(memoFlag) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
packetDataBytes, err := generatePacketData(cdc, []byte(args[0]), memo) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
cmd.Println(string(packetDataBytes)) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
cmd.Flags().String(memoFlag, "", "an optional memo to be included in the interchain account packet data") | ||
return cmd | ||
} | ||
|
||
// 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) { | ||
sdkMessages, err := convertBytesIntoSdkMessages(cdc, msgBytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
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 | ||
} | ||
|
||
icaPacketData := icatypes.InterchainAccountPacketData{ | ||
Type: icatypes.EXECUTE_TX, | ||
Data: icaPacketDataBytes, | ||
Memo: memo, | ||
} | ||
|
||
if err := icaPacketData.ValidateBasic(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return cdc.MarshalJSON(&icaPacketData) | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,155 @@ | ||||||
package cli | ||||||
|
||||||
import ( | ||||||
"fmt" | ||||||
"testing" | ||||||
|
||||||
"github.com/cosmos/cosmos-sdk/codec" | ||||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||||||
sdk "github.com/cosmos/cosmos-sdk/types" | ||||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" | ||||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||||||
"github.com/stretchr/testify/require" | ||||||
|
||||||
icatypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/types" | ||||||
) | ||||||
|
||||||
const msgDelegateMessage = `{ | ||||||
"@type": "/cosmos.staking.v1beta1.MsgDelegate", | ||||||
"delegator_address": "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", | ||||||
"validator_address": "cosmosvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnmxnh3k", | ||||||
"amount": { | ||||||
"denom": "stake", | ||||||
"amount": "1000" | ||||||
} | ||||||
}` | ||||||
|
||||||
const bankSendMessage = `{ | ||||||
"@type":"/cosmos.bank.v1beta1.MsgSend", | ||||||
"from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", | ||||||
"to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", | ||||||
"amount": [ | ||||||
{ | ||||||
"denom": "stake", | ||||||
"amount": "1000" | ||||||
} | ||||||
] | ||||||
}` | ||||||
|
||||||
var multiMsg = fmt.Sprintf("[ %s, %s ]", msgDelegateMessage, bankSendMessage) | ||||||
|
||||||
func TestGeneratePacketData(t *testing.T) { | ||||||
tests := []struct { | ||||||
name string | ||||||
memo string | ||||||
expectedPass bool | ||||||
message string | ||||||
registerInterfaceFn func(registry codectypes.InterfaceRegistry) | ||||||
assertionFn func(t *testing.T, msgs []sdk.Msg) | ||||||
}{ | ||||||
{ | ||||||
name: "multi message", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a nit, but instead of passing the slice of messages and the index, couldn't you just pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes you're right, this can simplify things a little bit! |
||||||
assertMsgBankSend(t, msgs, 1) | ||||||
}, | ||||||
}, | ||||||
{ | ||||||
name: "packet data generation succeeds (MsgDelegate)", | ||||||
memo: "non-empty-memo", | ||||||
expectedPass: true, | ||||||
message: msgDelegateMessage, | ||||||
registerInterfaceFn: stakingtypes.RegisterInterfaces, | ||||||
assertionFn: func(t *testing.T, msgs []sdk.Msg) { | ||||||
assertMsgDelegate(t, msgs, 0) | ||||||
}, | ||||||
}, | ||||||
{ | ||||||
name: "packet data generation succeeds (MsgSend)", | ||||||
memo: "non-empty-memo", | ||||||
expectedPass: true, | ||||||
message: bankSendMessage, | ||||||
registerInterfaceFn: banktypes.RegisterInterfaces, | ||||||
assertionFn: func(t *testing.T, msgs []sdk.Msg) { | ||||||
assertMsgBankSend(t, msgs, 0) | ||||||
}, | ||||||
}, | ||||||
{ | ||||||
name: "empty memo is valid", | ||||||
memo: "", | ||||||
expectedPass: true, | ||||||
message: msgDelegateMessage, | ||||||
registerInterfaceFn: stakingtypes.RegisterInterfaces, | ||||||
assertionFn: nil, | ||||||
}, | ||||||
{ | ||||||
name: "invalid message string", | ||||||
expectedPass: false, | ||||||
message: "<invalid-message-body>", | ||||||
}, | ||||||
} | ||||||
|
||||||
for _, tc := range tests { | ||||||
tc := tc | ||||||
ir := codectypes.NewInterfaceRegistry() | ||||||
if tc.registerInterfaceFn != nil { | ||||||
tc.registerInterfaceFn(ir) | ||||||
} | ||||||
|
||||||
cdc := codec.NewProtoCodec(ir) | ||||||
|
||||||
t.Run(tc.name, func(t *testing.T) { | ||||||
bz, err := generatePacketData(cdc, []byte(tc.message), tc.memo) | ||||||
|
||||||
if tc.expectedPass { | ||||||
require.NoError(t, err) | ||||||
require.NotNil(t, bz) | ||||||
|
||||||
packetData := icatypes.InterchainAccountPacketData{} | ||||||
err = cdc.UnmarshalJSON(bz, &packetData) | ||||||
require.NoError(t, err) | ||||||
|
||||||
require.Equal(t, icatypes.EXECUTE_TX, packetData.Type) | ||||||
require.Equal(t, tc.memo, packetData.Memo) | ||||||
|
||||||
data := packetData.Data | ||||||
messages, err := icatypes.DeserializeCosmosTx(cdc, data) | ||||||
|
||||||
require.NoError(t, err) | ||||||
require.NotNil(t, messages) | ||||||
|
||||||
if tc.assertionFn != nil { | ||||||
tc.assertionFn(t, messages) | ||||||
} | ||||||
} else { | ||||||
require.Error(t, err) | ||||||
require.Nil(t, bz) | ||||||
} | ||||||
}) | ||||||
} | ||||||
} | ||||||
|
||||||
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()) | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See this PR.