cosmos-sdk/x/accounts/cli/cli.go
testinginprod 40492cdf10
feat(accounts): add genesis account initialization (#20642)
Co-authored-by: unknown unknown <unknown@unknown>
2024-06-12 14:19:13 +00:00

206 lines
5.4 KiB
Go

package cli
import (
"bytes"
"fmt"
"reflect"
"github.com/cosmos/gogoproto/jsonpb"
gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/spf13/cobra"
v1 "cosmossdk.io/x/accounts/v1"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
)
func TxCmd(name string) *cobra.Command {
cmd := &cobra.Command{
Use: name,
Short: "Transactions command for the " + name + " module",
RunE: client.ValidateCmd,
DisableFlagParsing: true,
}
cmd.AddCommand(GetTxInitCmd(), GetExecuteCmd())
return cmd
}
func QueryCmd(name string) *cobra.Command {
cmd := &cobra.Command{
Use: name,
Short: "Query command for the " + name + " module",
RunE: client.ValidateCmd,
DisableFlagParsing: true,
}
cmd.AddCommand(GetQueryAccountCmd())
return cmd
}
func GetTxInitCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "init [account-type] [json-message]",
Short: "Initialize a new account",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
sender, err := clientCtx.AddressCodec.BytesToString(clientCtx.GetFromAddress())
if err != nil {
return err
}
// we need to convert the message from json to a protobuf message
// to know which message to use, we need to know the account type
// init message schema.
accClient := v1.NewQueryClient(clientCtx)
schema, err := accClient.Schema(cmd.Context(), &v1.SchemaRequest{
AccountType: args[0],
})
if err != nil {
return err
}
msgBytes, err := encodeJSONToProto(schema.InitSchema.Request, args[1])
if err != nil {
return err
}
msg := v1.MsgInit{
Sender: sender,
AccountType: args[0],
Message: msgBytes,
}
isGenesis, err := cmd.Flags().GetBool("genesis")
if err != nil {
return err
}
// in case the genesis flag is provided then the init message is printed.
if isGenesis {
return clientCtx.WithOutputFormat(flags.OutputFormatJSON).PrintProto(&msg)
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
},
}
cmd.Flags().Bool("genesis", false, "if true will print the json init message for genesis")
flags.AddTxFlagsToCmd(cmd)
return cmd
}
func GetExecuteCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "execute [account-address] [execute-msg-type-url] [json-message]",
Short: "Execute state transition to account",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
sender, err := clientCtx.AddressCodec.BytesToString(clientCtx.GetFromAddress())
if err != nil {
return err
}
schema, err := getSchemaForAccount(clientCtx, args[0])
if err != nil {
return err
}
msgBytes, err := handlerMsgBytes(schema.ExecuteHandlers, args[1], args[2])
if err != nil {
return err
}
msg := v1.MsgExecute{
Sender: sender,
Target: args[0],
Message: msgBytes,
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
},
}
flags.AddTxFlagsToCmd(cmd)
return cmd
}
func GetQueryAccountCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "query [account-address] [query-request-type-url] [json-message]",
Short: "Query account state",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
schema, err := getSchemaForAccount(clientCtx, args[0])
if err != nil {
return err
}
msgBytes, err := handlerMsgBytes(schema.QueryHandlers, args[1], args[2])
if err != nil {
return err
}
queryClient := v1.NewQueryClient(clientCtx)
res, err := queryClient.AccountQuery(cmd.Context(), &v1.AccountQueryRequest{
Target: args[0],
Request: msgBytes,
})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
func getSchemaForAccount(clientCtx client.Context, addr string) (*v1.SchemaResponse, error) {
queryClient := v1.NewQueryClient(clientCtx)
accType, err := queryClient.AccountType(clientCtx.CmdContext, &v1.AccountTypeRequest{
Address: addr,
})
if err != nil {
return nil, err
}
return queryClient.Schema(clientCtx.CmdContext, &v1.SchemaRequest{
AccountType: accType.AccountType,
})
}
func handlerMsgBytes(handlersSchema []*v1.SchemaResponse_Handler, msgTypeURL, msgString string) (*codectypes.Any, error) {
var msgSchema *v1.SchemaResponse_Handler
for _, handler := range handlersSchema {
if handler.Request == msgTypeURL {
msgSchema = handler
break
}
}
if msgSchema == nil {
return nil, fmt.Errorf("handler for message type %s not found", msgTypeURL)
}
return encodeJSONToProto(msgSchema.Request, msgString)
}
func encodeJSONToProto(name, jsonMsg string) (*codectypes.Any, error) {
impl := gogoproto.MessageType(name)
if impl == nil {
return nil, fmt.Errorf("message type %s not found", name)
}
msg := reflect.New(impl.Elem()).Interface().(gogoproto.Message)
err := jsonpb.Unmarshal(bytes.NewBufferString(jsonMsg), msg)
if err != nil {
return nil, fmt.Errorf("provided message is not valid %s: %w", jsonMsg, err)
}
return codectypes.NewAnyWithValue(msg)
}