-
Notifications
You must be signed in to change notification settings - Fork 637
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Add host cli to generate ica packet data (#2297) (cherry picked from commit 8f0e7d5) # Conflicts: # CHANGELOG.md * fix conflict Co-authored-by: Cian Hatton <[email protected]> Co-authored-by: Carlos Rodriguez <[email protected]> Co-authored-by: colin axnér <[email protected]>
- Loading branch information
1 parent
da9da72
commit 32bf4d0
Showing
5 changed files
with
323 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
147 changes: 147 additions & 0 deletions
147
modules/apps/27-interchain-accounts/host/client/cli/tx.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package cli | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/cosmos/cosmos-sdk/client" | ||
"github.com/cosmos/cosmos-sdk/codec" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/cosmos/cosmos-sdk/version" | ||
"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. | ||
`, | ||
Example: fmt.Sprintf(`%s tx interchain-accounts host generate-packet-data '{ | ||
"@type":"/cosmos.bank.v1beta1.MsgSend", | ||
"from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", | ||
"to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", | ||
"amount": [ | ||
{ | ||
"denom": "stake", | ||
"amount": "1000" | ||
} | ||
] | ||
}' --memo memo | ||
%s tx interchain-accounts host generate-packet-data '[{ | ||
"@type":"/cosmos.bank.v1beta1.MsgSend", | ||
"from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", | ||
"to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", | ||
"amount": [ | ||
{ | ||
"denom": "stake", | ||
"amount": "1000" | ||
} | ||
] | ||
}, | ||
{ | ||
"@type": "/cosmos.staking.v1beta1.MsgDelegate", | ||
"delegator_address": "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", | ||
"validator_address": "cosmosvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnmxnh3k", | ||
"amount": { | ||
"denom": "stake", | ||
"amount": "1000" | ||
} | ||
}]'`, version.AppName, version.AppName), | ||
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) | ||
} |
155 changes: 155 additions & 0 deletions
155
modules/apps/27-interchain-accounts/host/client/cli/tx_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: "packet data generation succeeds (MsgDelegate & MsgSend)", | ||
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(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, 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()) | ||
} | ||
|
||
func assertMsgDelegate(t *testing.T, 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()) | ||
} |