fix(autocli): fix simapp enhancing (#15906)
This commit is contained in:
parent
e77156858f
commit
ebcad58468
@ -4268,9 +4268,9 @@ type ModuleOptions struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// tx describes the tx command for the module.
|
||||
// tx describes the tx commands for the module.
|
||||
Tx *ServiceCommandDescriptor `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"`
|
||||
// query describes the tx command for the module.
|
||||
// query describes the queries commands for the module.
|
||||
Query *ServiceCommandDescriptor `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@ -355,10 +355,11 @@ func GetClientContextFromCmd(cmd *cobra.Command) Context {
|
||||
}
|
||||
|
||||
// SetCmdClientContext sets a command's Context value to the provided argument.
|
||||
// If the context has not been set, set the given context as the default.
|
||||
func SetCmdClientContext(cmd *cobra.Command, clientCtx Context) error {
|
||||
v := cmd.Context().Value(ClientContextKey)
|
||||
if v == nil {
|
||||
return errors.New("client context not set")
|
||||
v = &clientCtx
|
||||
}
|
||||
|
||||
clientCtxPtr := v.(*Context)
|
||||
|
||||
37
client/v2/CHANGELOG.md
Normal file
37
client/v2/CHANGELOG.md
Normal file
@ -0,0 +1,37 @@
|
||||
<!--
|
||||
Guiding Principles:
|
||||
|
||||
Changelogs are for humans, not machines.
|
||||
There should be an entry for every single version.
|
||||
The same types of changes should be grouped.
|
||||
Versions and sections should be linkable.
|
||||
The latest version comes first.
|
||||
The release date of each version is displayed.
|
||||
Mention whether you follow Semantic Versioning.
|
||||
|
||||
Usage:
|
||||
|
||||
Change log entries are to be added to the Unreleased section under the
|
||||
appropriate stanza (see below). Each entry should ideally include a tag and
|
||||
the Github issue reference in the following format:
|
||||
|
||||
* (<tag>) \#<issue-number> message
|
||||
|
||||
The issue numbers will later be link-ified during the release process so you do
|
||||
not have to worry about including a link manually, but you can if you wish.
|
||||
|
||||
Types of changes (Stanzas):
|
||||
|
||||
"Features" for new features.
|
||||
"Improvements" for changes in existing functionality.
|
||||
"Deprecated" for soon-to-be removed features.
|
||||
"Bug Fixes" for any bug fixes.
|
||||
"Client Breaking" for breaking Protobuf, gRPC and REST routes used by end-users.
|
||||
"CLI Breaking" for breaking CLI commands.
|
||||
"API Breaking" for breaking exported APIs used by developers building on SDK.
|
||||
Ref: https://keepachangelog.com/en/1.0.0/
|
||||
-->
|
||||
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
7
client/v2/README.md
Normal file
7
client/v2/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# AutoCLI
|
||||
|
||||
The `autocli` package is a Go library for generating CLIs (command line interfaces) for Cosmos SDK-based applications.
|
||||
|
||||
Read more about in it the Cosmos SDK documentation:
|
||||
|
||||
* https://docs.cosmos.network/main/building-modules/autocli
|
||||
@ -1,9 +1,9 @@
|
||||
package autocli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"cosmossdk.io/client/v2/autocli/flag"
|
||||
"cosmossdk.io/core/address"
|
||||
"cosmossdk.io/core/appmodule"
|
||||
"cosmossdk.io/depinject"
|
||||
|
||||
@ -32,23 +32,18 @@ type AppOptions struct {
|
||||
// app to override module options if they are either not provided by a
|
||||
// module or need to be improved.
|
||||
ModuleOptions map[string]*autocliv1.ModuleOptions `optional:"true"`
|
||||
}
|
||||
|
||||
// RootCmd generates a root command for an app based on the AppOptions. This
|
||||
// command currently only includes query commands but will be enhanced over
|
||||
// time to cover the full scope of an app CLI.
|
||||
func (appOptions AppOptions) RootCmd() (*cobra.Command, error) {
|
||||
rootCmd := &cobra.Command{}
|
||||
err := appOptions.EnhanceRootCommand(rootCmd)
|
||||
return rootCmd, err
|
||||
// AddressCodec is the address codec to use for the app.
|
||||
// If not provided the default address prefix will be fetched from the reflection client.
|
||||
AddressCodec address.Codec `optional:"true"`
|
||||
}
|
||||
|
||||
// EnhanceRootCommand enhances the provided root command with autocli AppOptions,
|
||||
// only adding missing query commands and doesn't override commands already
|
||||
// only adding missing commands and doesn't override commands already
|
||||
// in the root command. This allows for the graceful integration of autocli with
|
||||
// existing app CLI commands where autocli simply automatically adds things that
|
||||
// weren't manually provided. It does take into account custom query commands
|
||||
// provided by modules with the HasCustomQueryCommand extension interface.
|
||||
// weren't manually provided. It does take into account custom commands
|
||||
// provided by modules with the HasCustomQueryCommand or HasCustomTxCommand extension interface.
|
||||
// Example Usage:
|
||||
//
|
||||
// var autoCliOpts autocli.AppOptions
|
||||
@ -60,6 +55,12 @@ func (appOptions AppOptions) RootCmd() (*cobra.Command, error) {
|
||||
// err = autoCliOpts.EnhanceRootCommand(rootCmd)
|
||||
func (appOptions AppOptions) EnhanceRootCommand(rootCmd *cobra.Command) error {
|
||||
builder := &Builder{
|
||||
Builder: flag.Builder{
|
||||
AddressCodec: appOptions.AddressCodec,
|
||||
GetClientConn: func() (grpc.ClientConnInterface, error) {
|
||||
return client.GetClientQueryContext(rootCmd)
|
||||
},
|
||||
},
|
||||
GetClientConn: func(cmd *cobra.Command) (grpc.ClientConnInterface, error) {
|
||||
return client.GetClientQueryContext(cmd)
|
||||
},
|
||||
@ -71,19 +72,8 @@ func (appOptions AppOptions) EnhanceRootCommand(rootCmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
func (appOptions AppOptions) EnhanceRootCommandWithBuilder(rootCmd *cobra.Command, builder *Builder) error {
|
||||
moduleOptions := appOptions.ModuleOptions
|
||||
if moduleOptions == nil {
|
||||
moduleOptions = map[string]*autocliv1.ModuleOptions{}
|
||||
|
||||
for name, module := range appOptions.Modules {
|
||||
if module, ok := module.(HasAutoCLIConfig); ok {
|
||||
moduleOptions[name] = module.AutoCLIOptions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customQueryCmds := map[string]*cobra.Command{}
|
||||
customMsgCmds := map[string]*cobra.Command{}
|
||||
// extract any custom commands from modules
|
||||
customQueryCmds, customMsgCmds := map[string]*cobra.Command{}, map[string]*cobra.Command{}
|
||||
for name, module := range appOptions.Modules {
|
||||
if queryModule, ok := module.(HasCustomQueryCommand); ok {
|
||||
queryCmd := queryModule.GetQueryCmd()
|
||||
@ -101,27 +91,12 @@ func (appOptions AppOptions) EnhanceRootCommandWithBuilder(rootCmd *cobra.Comman
|
||||
}
|
||||
}
|
||||
|
||||
// if we have an existing query command, enhance it or build a custom one
|
||||
enhanceQuery := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
queryCmdDesc := modOpts.Query
|
||||
if queryCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
|
||||
err := builder.AddQueryServiceCommands(cmd, queryCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if queryCmd := findSubCommand(rootCmd, "query"); queryCmd != nil {
|
||||
if err := builder.enhanceCommandCommon(queryCmd, moduleOptions, customQueryCmds, enhanceQuery); err != nil {
|
||||
if err := builder.enhanceCommandCommon(queryCmd, appOptions, customQueryCmds, enhanceQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
queryCmd, err := builder.BuildQueryCommand(moduleOptions, customQueryCmds)
|
||||
queryCmd, err := builder.BuildQueryCommand(appOptions, customQueryCmds, enhanceQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -129,26 +104,12 @@ func (appOptions AppOptions) EnhanceRootCommandWithBuilder(rootCmd *cobra.Comman
|
||||
rootCmd.AddCommand(queryCmd)
|
||||
}
|
||||
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txCmdDesc := modOpts.Tx
|
||||
if txCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
|
||||
err := builder.AddQueryServiceCommands(cmd, txCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if msgCmd := findSubCommand(rootCmd, "tx"); msgCmd != nil {
|
||||
if err := builder.enhanceCommandCommon(msgCmd, moduleOptions, customQueryCmds, enhanceMsg); err != nil {
|
||||
if err := builder.enhanceCommandCommon(msgCmd, appOptions, customMsgCmds, enhanceMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
subCmd, err := builder.BuildMsgCommand(moduleOptions, customQueryCmds)
|
||||
subCmd, err := builder.BuildMsgCommand(appOptions, customMsgCmds, enhanceMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/maps"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
@ -67,22 +68,27 @@ func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescrip
|
||||
// options or the provided custom commands for each module. If the provided query command already contains a command
|
||||
// for a module, that command is not over-written by this method. This allows a graceful addition of autocli to
|
||||
// automatically fill in missing commands.
|
||||
func (b *Builder) enhanceCommandCommon(cmd *cobra.Command, moduleOptions map[string]*autocliv1.ModuleOptions, customCmds map[string]*cobra.Command, buildModuleCommand func(*cobra.Command, *autocliv1.ModuleOptions, string) error) error {
|
||||
allModuleNames := map[string]bool{}
|
||||
for moduleName := range moduleOptions {
|
||||
allModuleNames[moduleName] = true
|
||||
}
|
||||
for moduleName := range customCmds {
|
||||
allModuleNames[moduleName] = true
|
||||
func (b *Builder) enhanceCommandCommon(
|
||||
cmd *cobra.Command,
|
||||
appOptions AppOptions,
|
||||
customCmds map[string]*cobra.Command,
|
||||
buildModuleCommand enhanceCommandFunc,
|
||||
) error {
|
||||
moduleOptions := appOptions.ModuleOptions
|
||||
if len(moduleOptions) == 0 {
|
||||
moduleOptions = map[string]*autocliv1.ModuleOptions{}
|
||||
for name, module := range appOptions.Modules {
|
||||
if module, ok := module.(HasAutoCLIConfig); ok {
|
||||
moduleOptions[name] = module.AutoCLIOptions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for moduleName := range allModuleNames {
|
||||
modules := append(maps.Keys(appOptions.Modules), maps.Keys(moduleOptions)...)
|
||||
for _, moduleName := range modules {
|
||||
// if we have an existing command skip adding one here
|
||||
if cmd.HasSubCommands() {
|
||||
if _, _, err := cmd.Find([]string{moduleName}); err == nil {
|
||||
// command already exists, skip
|
||||
continue
|
||||
}
|
||||
if findSubCommand(cmd, moduleName) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we have a custom command use that instead of generating one
|
||||
@ -98,8 +104,7 @@ func (b *Builder) enhanceCommandCommon(cmd *cobra.Command, moduleOptions map[str
|
||||
continue
|
||||
}
|
||||
|
||||
err := buildModuleCommand(cmd, modOpts, moduleName)
|
||||
if err != nil {
|
||||
if err := buildModuleCommand(b, moduleName, cmd, modOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -107,19 +112,51 @@ func (b *Builder) enhanceCommandCommon(cmd *cobra.Command, moduleOptions map[str
|
||||
return nil
|
||||
}
|
||||
|
||||
type enhanceCommandFunc func(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error
|
||||
|
||||
// enhanceQuery enhances the provided query command with the autocli commands for a module.
|
||||
func enhanceQuery(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error {
|
||||
queryCmdDesc := modOpts.Query
|
||||
if queryCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
|
||||
if err := builder.AddQueryServiceCommands(subCmd, queryCmdDesc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// enhanceMsg enhances the provided msg command with the autocli commands for a module.
|
||||
func enhanceMsg(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error {
|
||||
txCmdDesc := modOpts.Tx
|
||||
if txCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
|
||||
if err := builder.AddMsgServiceCommands(subCmd, txCmdDesc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// outOrStdoutFormat formats the output based on the output flag and writes it to the command's output stream.
|
||||
func (b *Builder) outOrStdoutFormat(cmd *cobra.Command, out []byte) error {
|
||||
var err error
|
||||
outputType := cmd.Flag(flags.FlagOutput)
|
||||
// if the output type is text, convert the json to yaml
|
||||
// if output type is json or nil, default to json
|
||||
if outputType != nil && outputType.Value.String() == "text" {
|
||||
if outputType != nil && outputType.Value.String() == flags.OutputFormatText {
|
||||
out, err = yaml.JSONToYAML(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), string(out))
|
||||
|
||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), string(out))
|
||||
return err
|
||||
}
|
||||
|
||||
@ -2,16 +2,19 @@ package flag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
reflectionv2alpha1 "cosmossdk.io/api/cosmos/base/reflection/v2alpha1"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
"cosmossdk.io/core/address"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
|
||||
)
|
||||
|
||||
type addressStringType struct{}
|
||||
|
||||
func (a addressStringType) NewValue(ctx context.Context, b *Builder) Value {
|
||||
if b.AddressPrefix == "" {
|
||||
if b.AddressCodec == nil {
|
||||
conn, err := b.GetClientConn()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -24,9 +27,11 @@ func (a addressStringType) NewValue(ctx context.Context, b *Builder) Value {
|
||||
if resp == nil || resp.Config == nil {
|
||||
panic("bech32 account address prefix is not set")
|
||||
}
|
||||
b.AddressPrefix = resp.Config.Bech32AccountAddressPrefix
|
||||
|
||||
b.AddressCodec = addresscodec.NewBech32Codec(resp.Config.Bech32AccountAddressPrefix)
|
||||
}
|
||||
return &addressValue{addressPrefix: b.AddressPrefix}
|
||||
|
||||
return &addressValue{addressCodec: b.AddressCodec}
|
||||
}
|
||||
|
||||
func (a addressStringType) DefaultValue() string {
|
||||
@ -34,8 +39,8 @@ func (a addressStringType) DefaultValue() string {
|
||||
}
|
||||
|
||||
type addressValue struct {
|
||||
value string
|
||||
addressPrefix string
|
||||
value string
|
||||
addressCodec address.Codec
|
||||
}
|
||||
|
||||
func (a addressValue) Get(protoreflect.Value) (protoreflect.Value, error) {
|
||||
@ -48,10 +53,11 @@ func (a addressValue) String() string {
|
||||
|
||||
// Set implements the flag.Value interface for addressValue it only supports bech32 addresses.
|
||||
func (a *addressValue) Set(s string) error {
|
||||
_, err := types.GetFromBech32(s, a.addressPrefix)
|
||||
_, err := a.addressCodec.StringToBytes(s)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("invalid bech32 account address: %w", err)
|
||||
}
|
||||
|
||||
a.value = s
|
||||
|
||||
return nil
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
|
||||
"cosmossdk.io/core/address"
|
||||
)
|
||||
|
||||
// Builder manages options for building pflag flags for protobuf messages.
|
||||
@ -23,9 +25,10 @@ type Builder struct {
|
||||
messageFlagTypes map[protoreflect.FullName]Type
|
||||
scalarFlagTypes map[string]Type
|
||||
|
||||
// AddressPrefix is the prefix for the address flag
|
||||
AddressPrefix string
|
||||
// reflectionClient is the reflection client for the address flag
|
||||
// AddressCodec is the address codec used for the address flag
|
||||
AddressCodec address.Codec
|
||||
|
||||
// GetClientConn is the reflection client for the address flag
|
||||
GetClientConn func() (grpc.ClientConnInterface, error)
|
||||
}
|
||||
|
||||
|
||||
@ -14,22 +14,9 @@ import (
|
||||
// BuildMsgCommand builds the msg commands for all the provided modules. If a custom command is provided for a
|
||||
// module, this is used instead of any automatically generated CLI commands. This allows apps to a fully dynamic client
|
||||
// with a more customized experience if a binary with custom commands is downloaded.
|
||||
func (b *Builder) BuildMsgCommand(moduleOptions map[string]*autocliv1.ModuleOptions, customCmds map[string]*cobra.Command) (*cobra.Command, error) {
|
||||
func (b *Builder) BuildMsgCommand(appOptions AppOptions, customCmds map[string]*cobra.Command, buildModuleCommand enhanceCommandFunc) (*cobra.Command, error) {
|
||||
msgCmd := topLevelCmd("tx", "Transaction subcommands")
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txCmdDesc := modOpts.Tx
|
||||
if txCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
|
||||
err := b.AddMsgServiceCommands(subCmd, txCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := b.enhanceCommandCommon(msgCmd, moduleOptions, customCmds, enhanceMsg); err != nil {
|
||||
if err := b.enhanceCommandCommon(msgCmd, appOptions, customCmds, enhanceMsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -78,8 +65,7 @@ func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autoc
|
||||
|
||||
}
|
||||
|
||||
methodsLength := methods.Len()
|
||||
for i := 0; i < methodsLength; i++ {
|
||||
for i := 0; i < methods.Len(); i++ {
|
||||
methodDescriptor := methods.Get(i)
|
||||
methodOpts, ok := rpcOptMap[methodDescriptor.Name()]
|
||||
if !ok {
|
||||
@ -89,10 +75,12 @@ func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autoc
|
||||
if methodOpts.Skip {
|
||||
continue
|
||||
}
|
||||
|
||||
methodCmd, err := b.BuildMsgMethodCommand(methodDescriptor, methodOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if methodCmd != nil {
|
||||
cmd.AddCommand(methodCmd)
|
||||
}
|
||||
@ -101,6 +89,7 @@ func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autoc
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildMsgMethodCommand returns a command that outputs the JSON representation of the message.
|
||||
func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions) (*cobra.Command, error) {
|
||||
jsonMarshalOptions := protojson.MarshalOptions{
|
||||
Indent: " ",
|
||||
@ -116,11 +105,12 @@ func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.outOrStdoutFormat(cmd, bz)
|
||||
return err
|
||||
return b.outOrStdoutFormat(cmd, bz)
|
||||
})
|
||||
|
||||
if b.AddTxConnFlags != nil {
|
||||
b.AddTxConnFlags(cmd)
|
||||
}
|
||||
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
@ -5,14 +5,12 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"cosmossdk.io/client/v2/internal/testpb"
|
||||
)
|
||||
|
||||
@ -225,18 +223,22 @@ func TestHelpMsg(t *testing.T) {
|
||||
golden.Assert(t, conn.out.String(), "help-deprecated-msg.golden")
|
||||
}
|
||||
|
||||
func TestBuildCustomMsgCommand(t *testing.T) {
|
||||
func TestBuildMsgCommand(t *testing.T) {
|
||||
b := &Builder{}
|
||||
customCommandCalled := false
|
||||
cmd, err := b.BuildMsgCommand(map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: testCmdMsgDesc,
|
||||
appOptions := AppOptions{
|
||||
ModuleOptions: map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: testCmdMsgDesc,
|
||||
},
|
||||
},
|
||||
}, map[string]*cobra.Command{
|
||||
}
|
||||
|
||||
cmd, err := b.BuildMsgCommand(appOptions, map[string]*cobra.Command{
|
||||
"test": {Use: "test", Run: func(cmd *cobra.Command, args []string) {
|
||||
customCommandCalled = true
|
||||
}},
|
||||
})
|
||||
}, enhanceMsg)
|
||||
assert.NilError(t, err)
|
||||
cmd.SetArgs([]string{"test", "tx"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
@ -260,21 +262,20 @@ func TestErrorBuildMsgCommand(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
opts := map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: commandDescriptor,
|
||||
appOptions := AppOptions{
|
||||
ModuleOptions: map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: commandDescriptor,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := b.BuildMsgCommand(opts, nil)
|
||||
|
||||
_, err := b.BuildMsgCommand(appOptions, nil, enhanceMsg)
|
||||
assert.ErrorContains(t, err, "can't find field un-existent-proto-field")
|
||||
|
||||
nonExistentService := &autocliv1.ServiceCommandDescriptor{Service: "un-existent-service"}
|
||||
opts = map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: nonExistentService,
|
||||
},
|
||||
}
|
||||
_, err = b.BuildMsgCommand(opts, nil)
|
||||
appOptions.ModuleOptions["test"].Tx = nonExistentService
|
||||
_, err = b.BuildMsgCommand(appOptions, nil, enhanceMsg)
|
||||
assert.ErrorContains(t, err, "can't find service un-existent-service")
|
||||
}
|
||||
|
||||
@ -328,42 +329,35 @@ func TestNotFoundErrorsMsg(t *testing.T) {
|
||||
|
||||
func TestEnhanceMessageCommand(t *testing.T) {
|
||||
b := &Builder{}
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txCmdDesc := modOpts.Tx
|
||||
if txCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
|
||||
err := b.AddMsgServiceCommands(cmd, txCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test that the command has a subcommand
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
cmd.AddCommand(&cobra.Command{Use: "test"})
|
||||
options := map[string]*autocliv1.ModuleOptions{
|
||||
"test": {},
|
||||
|
||||
appOptions := AppOptions{
|
||||
ModuleOptions: map[string]*autocliv1.ModuleOptions{
|
||||
"test": {},
|
||||
},
|
||||
}
|
||||
err := b.enhanceCommandCommon(cmd, options, map[string]*cobra.Command{}, enhanceMsg)
|
||||
|
||||
err := b.enhanceCommandCommon(cmd, appOptions, map[string]*cobra.Command{}, enhanceMsg)
|
||||
assert.NilError(t, err)
|
||||
|
||||
cmd = &cobra.Command{Use: "test"}
|
||||
options = map[string]*autocliv1.ModuleOptions{}
|
||||
|
||||
appOptions.ModuleOptions = map[string]*autocliv1.ModuleOptions{}
|
||||
customCommands := map[string]*cobra.Command{
|
||||
"test2": {Use: "test"},
|
||||
}
|
||||
err = b.enhanceCommandCommon(cmd, options, customCommands, enhanceMsg)
|
||||
err = b.enhanceCommandCommon(cmd, appOptions, customCommands, enhanceMsg)
|
||||
assert.NilError(t, err)
|
||||
|
||||
cmd = &cobra.Command{Use: "test"}
|
||||
options = map[string]*autocliv1.ModuleOptions{
|
||||
"test": {Tx: nil},
|
||||
appOptions = AppOptions{
|
||||
ModuleOptions: map[string]*autocliv1.ModuleOptions{
|
||||
"test": {Tx: nil},
|
||||
},
|
||||
}
|
||||
customCommands = map[string]*cobra.Command{}
|
||||
err = b.enhanceCommandCommon(cmd, options, customCommands, enhanceMsg)
|
||||
err = b.enhanceCommandCommon(cmd, appOptions, customCommands, enhanceMsg)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
@ -16,24 +16,11 @@ import (
|
||||
// BuildQueryCommand builds the query commands for all the provided modules. If a custom command is provided for a
|
||||
// module, this is used instead of any automatically generated CLI commands. This allows apps to a fully dynamic client
|
||||
// with a more customized experience if a binary with custom commands is downloaded.
|
||||
func (b *Builder) BuildQueryCommand(moduleOptions map[string]*autocliv1.ModuleOptions, customCmds map[string]*cobra.Command) (*cobra.Command, error) {
|
||||
func (b *Builder) BuildQueryCommand(appOptions AppOptions, customCmds map[string]*cobra.Command, enhanceQuery enhanceCommandFunc) (*cobra.Command, error) {
|
||||
queryCmd := topLevelCmd("query", "Querying subcommands")
|
||||
queryCmd.Aliases = []string{"q"}
|
||||
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txQueryDesc := modOpts.Query
|
||||
if txQueryDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
|
||||
err := b.AddQueryServiceCommands(subCmd, txQueryDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := b.enhanceCommandCommon(queryCmd, moduleOptions, customCmds, enhanceMsg); err != nil {
|
||||
if err := b.enhanceCommandCommon(queryCmd, appOptions, customCmds, enhanceQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -99,7 +86,6 @@ func (b *Builder) AddQueryServiceCommands(cmd *cobra.Command, cmdDescriptor *aut
|
||||
}
|
||||
|
||||
cmd.AddCommand(methodCmd)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -336,15 +336,20 @@ func TestDeprecated(t *testing.T) {
|
||||
func TestBuildCustomQueryCommand(t *testing.T) {
|
||||
b := &Builder{}
|
||||
customCommandCalled := false
|
||||
cmd, err := b.BuildQueryCommand(map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Query: testCmdDesc,
|
||||
|
||||
appOptions := AppOptions{
|
||||
ModuleOptions: map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Query: testCmdDesc,
|
||||
},
|
||||
},
|
||||
}, map[string]*cobra.Command{
|
||||
}
|
||||
|
||||
cmd, err := b.BuildQueryCommand(appOptions, map[string]*cobra.Command{
|
||||
"test": {Use: "test", Run: func(cmd *cobra.Command, args []string) {
|
||||
customCommandCalled = true
|
||||
}},
|
||||
})
|
||||
}, enhanceQuery)
|
||||
assert.NilError(t, err)
|
||||
cmd.SetArgs([]string{"test", "query"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
|
||||
@ -11,6 +11,7 @@ require (
|
||||
github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20230424095137-b73c17cb9cc8
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||
google.golang.org/grpc v1.54.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gotest.tools/v3 v3.4.0
|
||||
@ -104,7 +105,6 @@ require (
|
||||
github.com/zondax/hid v0.9.1 // indirect
|
||||
github.com/zondax/ledger-go v0.14.1 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
|
||||
@ -24,7 +24,7 @@ The `autocli` package is a [Go library](https://pkg.go.dev/cosmossdk.io/client/v
|
||||
Here are the steps to use the `autocli` package:
|
||||
|
||||
1. Define your app's modules that implement the `appmodule.AppModule` interface.
|
||||
2. (optional) When willing to configure how behave `autocli` command generation, implement the `func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions` method on the module. Learn more [here](#advanced-usage).
|
||||
2. Configure how behave `autocli` command generation, by implementing the `func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions` method on the module. Learn more [here](#advanced-usage).
|
||||
3. Use the `autocli.AppOptions` struct to specifies the modules you defined. If you are using the `depinject` package to manage your app's dependencies, it can automatically create an instance of `autocli.AppOptions` based on your app's configuration.
|
||||
4. Use the `EnhanceRootCommand()` method provided by `autocli` to add the CLI commands for the specified modules to your root command and can also be found in the `client/v2/autocli/app.go` file. Additionally, this method adds the `autocli` functionality to your app's root command. This method is additive only, meaning that it does not create commands if they are already registered for a module. Instead, it adds any missing commands to the root command.
|
||||
|
||||
|
||||
@ -53,13 +53,13 @@ x/{module_name}
|
||||
├── module
|
||||
│ └── module.go
|
||||
│ └── abci.go
|
||||
│ └── autocli.go
|
||||
├── simulation
|
||||
│ ├── decoder.go
|
||||
│ ├── genesis.go
|
||||
│ ├── operations.go
|
||||
│ └── params.go
|
||||
├── {module_name}.pb.go
|
||||
├── autocli.go
|
||||
├── codec.go
|
||||
├── errors.go
|
||||
├── events.go
|
||||
@ -80,10 +80,10 @@ x/{module_name}
|
||||
* `keeper/`: The module's `Keeper` and `MsgServer` implementation.
|
||||
* `module/`: The module's `AppModule` and `AppModuleBasic` implementation.
|
||||
* `abci.go`: The module's `BeginBlocker` and `EndBlocker` implementations (this file is only required if `BeginBlocker` and/or `EndBlocker` need to be defined).
|
||||
* `autocli.go`: The module [autocli](./10-autocli.md) options.
|
||||
* `simulation/`: The module's [simulation](./14-simulator.md) package defines functions used by the blockchain simulator application (`simapp`).
|
||||
* `REAMDE.md`: The module's specification documents outlining important concepts, state storage structure, and message and event type definitions. Learn more how to write module specs in the [spec guidelines](../spec/SPEC-SPEC.md).
|
||||
* The root directory includes type definitions for messages, events, and genesis state, including the type definitions generated by Protocol Buffers.
|
||||
* `autocli.go`: The module [autocli](./10-autocli.md) options.
|
||||
* `codec.go`: The module's registry methods for interface types.
|
||||
* `errors.go`: The module's sentinel errors.
|
||||
* `events.go`: The module's event types and constructors.
|
||||
|
||||
@ -6,10 +6,10 @@ option go_package = "cosmossdk.io/api/cosmos/base/cli/v1;cliv1";
|
||||
|
||||
// ModuleOptions describes the CLI options for a Cosmos SDK module.
|
||||
message ModuleOptions {
|
||||
// tx describes the tx command for the module.
|
||||
// tx describes the tx commands for the module.
|
||||
ServiceCommandDescriptor tx = 1;
|
||||
|
||||
// query describes the tx command for the module.
|
||||
// query describes the queries commands for the module.
|
||||
ServiceCommandDescriptor query = 2;
|
||||
}
|
||||
|
||||
|
||||
@ -213,10 +213,11 @@ func GetServerContextFromCmd(cmd *cobra.Command) *Context {
|
||||
}
|
||||
|
||||
// SetCmdServerContext sets a command's Context value to the provided argument.
|
||||
// If the context has not been set, set the given context as the default.
|
||||
func SetCmdServerContext(cmd *cobra.Command, serverCtx *Context) error {
|
||||
v := cmd.Context().Value(ServerContextKey)
|
||||
if v == nil {
|
||||
return errors.New("server context not set")
|
||||
v = serverCtx
|
||||
}
|
||||
|
||||
serverCtxPtr := v.(*Context)
|
||||
|
||||
@ -5,7 +5,7 @@ go 1.20
|
||||
require (
|
||||
cosmossdk.io/api v0.4.1
|
||||
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba
|
||||
cosmossdk.io/core v0.6.1
|
||||
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4
|
||||
cosmossdk.io/depinject v1.0.0-alpha.3
|
||||
cosmossdk.io/log v1.0.0
|
||||
cosmossdk.io/math v1.0.0
|
||||
@ -197,6 +197,7 @@ require (
|
||||
replace (
|
||||
// TODO tag all extracted modules after SDK refactor
|
||||
cosmossdk.io/api => ../api
|
||||
cosmossdk.io/client/v2 => ../client/v2
|
||||
cosmossdk.io/store => ../store
|
||||
cosmossdk.io/tools/confix => ../tools/confix
|
||||
cosmossdk.io/tools/rosetta => ../tools/rosetta
|
||||
|
||||
@ -188,12 +188,10 @@ cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xX
|
||||
cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
|
||||
cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
|
||||
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
|
||||
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba h1:LuPHCncU2KLMNPItFECs709uo46I9wSu2fAWYVCx+/U=
|
||||
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba/go.mod h1:SXdwqO7cN5htalh/lhXWP8V4zKtBrhhcSTU+ytuEtmM=
|
||||
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
|
||||
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
|
||||
cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s=
|
||||
cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA=
|
||||
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4 h1:l1scDTT2VX18ZuR6P0irvT/bAP0h4297D/Lka5nz2vE=
|
||||
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4/go.mod h1:J8R0E7soOpQFVqFiFd7EKepXCPpINa2n2t2EqbEsXnY=
|
||||
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
|
||||
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
|
||||
cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w=
|
||||
|
||||
@ -239,8 +239,6 @@ func queryCommand() *cobra.Command {
|
||||
authcmd.QueryTxCmd(),
|
||||
)
|
||||
|
||||
simapp.ModuleBasics.AddQueryCommands(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -265,8 +263,6 @@ func txCommand() *cobra.Command {
|
||||
authcmd.GetAuxToFeeCommand(),
|
||||
)
|
||||
|
||||
simapp.ModuleBasics.AddTxCommands(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
@ -1,399 +0,0 @@
|
||||
package nft
|
||||
|
||||
import (
|
||||
"cosmossdk.io/x/nft"
|
||||
)
|
||||
|
||||
func (s *E2ETestSuite) TestQueryClass() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
args struct {
|
||||
ClassID string
|
||||
}
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "class id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
}{
|
||||
ClassID: "class",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "class id exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := ExecQueryClass(val, tc.args.ClassID)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QueryClassResponse
|
||||
err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().EqualValues(ExpClass, *result.Class)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestQueryClasses() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "no params",
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := ExecQueryClasses(val)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QueryClassesResponse
|
||||
err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Len(result.Classes, 1)
|
||||
s.Require().EqualValues(ExpClass, *result.Classes[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestQueryNFT() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
args struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "class id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}{
|
||||
ClassID: "class",
|
||||
ID: testID,
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "nft id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
ID: "id",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "exist nft",
|
||||
args: struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
ID: testID,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := ExecQueryNFT(val, tc.args.ClassID, tc.args.ID)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QueryNFTResponse
|
||||
err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().EqualValues(ExpNFT, *result.Nft)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestQueryNFTs() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
args struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}
|
||||
expectErr bool
|
||||
expectResult []*nft.NFT
|
||||
}{
|
||||
{
|
||||
name: "class id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{
|
||||
ClassID: "class",
|
||||
Owner: val.Address.String(),
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: []*nft.NFT{},
|
||||
},
|
||||
{
|
||||
name: "owner does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
Owner: s.owner.String(),
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: []*nft.NFT{},
|
||||
},
|
||||
{
|
||||
name: "class id and owner both does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{},
|
||||
expectErr: true,
|
||||
expectResult: []*nft.NFT{},
|
||||
},
|
||||
{
|
||||
name: "nft exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
Owner: val.Address.String(),
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: []*nft.NFT{&ExpNFT},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := ExecQueryNFTs(val, tc.args.ClassID, tc.args.Owner)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QueryNFTsResponse
|
||||
err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().EqualValues(tc.expectResult, result.Nfts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestQueryOwner() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
args struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}
|
||||
expectErr bool
|
||||
errorMsg string
|
||||
expectResult string
|
||||
}{
|
||||
{
|
||||
name: "class id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}{
|
||||
ClassID: "class",
|
||||
ID: testID,
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: "",
|
||||
},
|
||||
{
|
||||
name: "nft id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
ID: "nft-id",
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: "",
|
||||
},
|
||||
{
|
||||
name: "nft exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
ID string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
ID: testID,
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: val.Address.String(),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := ExecQueryOwner(val, tc.args.ClassID, tc.args.ID)
|
||||
if tc.expectErr {
|
||||
s.Require().Contains(string(resp.Bytes()), tc.errorMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QueryOwnerResponse
|
||||
err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().EqualValues(tc.expectResult, result.Owner)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestQueryBalance() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
args struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}
|
||||
expectErr bool
|
||||
errorMsg string
|
||||
expectResult uint64
|
||||
}{
|
||||
{
|
||||
name: "class id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{
|
||||
ClassID: "class",
|
||||
Owner: val.Address.String(),
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: 0,
|
||||
},
|
||||
{
|
||||
name: "owner does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
Owner: s.owner.String(),
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: 0,
|
||||
},
|
||||
{
|
||||
name: "nft exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
Owner: val.Address.String(),
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: 1,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := ExecQueryBalance(val, tc.args.ClassID, tc.args.Owner)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QueryBalanceResponse
|
||||
err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().EqualValues(tc.expectResult, result.Amount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestQuerySupply() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
args struct {
|
||||
ClassID string
|
||||
}
|
||||
expectErr bool
|
||||
errorMsg string
|
||||
expectResult uint64
|
||||
}{
|
||||
{
|
||||
name: "class id is empty",
|
||||
args: struct {
|
||||
ClassID string
|
||||
}{
|
||||
ClassID: "",
|
||||
},
|
||||
expectErr: true,
|
||||
errorMsg: nft.ErrEmptyClassID.Error(),
|
||||
expectResult: 0,
|
||||
},
|
||||
{
|
||||
name: "class id does not exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
}{
|
||||
ClassID: "class",
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: 0,
|
||||
},
|
||||
{
|
||||
name: "class id exist",
|
||||
args: struct {
|
||||
ClassID string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
},
|
||||
expectErr: false,
|
||||
expectResult: 1,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := ExecQuerySupply(val, tc.args.ClassID)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QuerySupplyResponse
|
||||
err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().EqualValues(tc.expectResult, result.Amount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
package nft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/x/nft/client/cli"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec/address"
|
||||
"github.com/cosmos/cosmos-sdk/testutil"
|
||||
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||
)
|
||||
|
||||
func ExecSend(val *network.Validator, args []string) (testutil.BufferWriter, error) {
|
||||
cmd := cli.NewCmdSend()
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
|
||||
func ExecQueryClass(val *network.Validator, classID string) (testutil.BufferWriter, error) {
|
||||
cmd := cli.GetCmdQueryClass()
|
||||
var args []string
|
||||
args = append(args, classID)
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
|
||||
func ExecQueryClasses(val *network.Validator) (testutil.BufferWriter, error) {
|
||||
cmd := cli.GetCmdQueryClasses()
|
||||
var args []string
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
|
||||
func ExecQueryNFT(val *network.Validator, classID, nftID string) (testutil.BufferWriter, error) {
|
||||
cmd := cli.GetCmdQueryNFT()
|
||||
var args []string
|
||||
args = append(args, classID)
|
||||
args = append(args, nftID)
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
|
||||
func ExecQueryNFTs(val *network.Validator, classID, owner string) (testutil.BufferWriter, error) {
|
||||
cmd := cli.GetCmdQueryNFTs(address.NewBech32Codec("cosmos"))
|
||||
var args []string
|
||||
args = append(args, fmt.Sprintf("--%s=%s", cli.FlagClassID, classID))
|
||||
args = append(args, fmt.Sprintf("--%s=%s", cli.FlagOwner, owner))
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
|
||||
func ExecQueryOwner(val *network.Validator, classID, nftID string) (testutil.BufferWriter, error) {
|
||||
cmd := cli.GetCmdQueryOwner()
|
||||
var args []string
|
||||
args = append(args, classID)
|
||||
args = append(args, nftID)
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
|
||||
func ExecQueryBalance(val *network.Validator, classID, owner string) (testutil.BufferWriter, error) {
|
||||
cmd := cli.GetCmdQueryBalance()
|
||||
var args []string
|
||||
args = append(args, owner)
|
||||
args = append(args, classID)
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
|
||||
func ExecQuerySupply(val *network.Validator, classID string) (testutil.BufferWriter, error) {
|
||||
cmd := cli.GetCmdQuerySupply()
|
||||
var args []string
|
||||
args = append(args, classID)
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
}
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"cosmossdk.io/x/nft"
|
||||
"cosmossdk.io/x/nft/client/cli"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||
@ -92,6 +93,7 @@ func (s *E2ETestSuite) TearDownSuite() {
|
||||
}
|
||||
|
||||
func (s *E2ETestSuite) TestCLITxSend() {
|
||||
cmd := cli.NewCmdSend()
|
||||
val := s.network.Validators[0]
|
||||
args := []string{
|
||||
fmt.Sprintf("--%s=%s", flags.FlagFrom, OwnerName),
|
||||
@ -122,10 +124,8 @@ func (s *E2ETestSuite) TestCLITxSend() {
|
||||
s.Run(tc.name, func() {
|
||||
clientCtx := val.ClientCtx
|
||||
args = append(args, tc.args...)
|
||||
out, err := ExecSend(
|
||||
val,
|
||||
args,
|
||||
)
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
} else {
|
||||
|
||||
@ -38,7 +38,7 @@ require (
|
||||
cloud.google.com/go/storage v1.30.0 // indirect
|
||||
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba // indirect
|
||||
cosmossdk.io/collections v0.1.0 // indirect
|
||||
cosmossdk.io/core v0.6.1 // indirect
|
||||
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/99designs/keyring v1.2.1 // indirect
|
||||
|
||||
@ -192,8 +192,8 @@ cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba h1:LuPHCncU2KLMNPItFEC
|
||||
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba/go.mod h1:SXdwqO7cN5htalh/lhXWP8V4zKtBrhhcSTU+ytuEtmM=
|
||||
cosmossdk.io/collections v0.1.0 h1:nzJGeiq32KnZroSrhB6rPifw4I85Cgmzw/YAmr4luv8=
|
||||
cosmossdk.io/collections v0.1.0/go.mod h1:xbauc0YsbUF8qKMVeBZl0pFCunxBIhKN/WlxpZ3lBuo=
|
||||
cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s=
|
||||
cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA=
|
||||
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4 h1:l1scDTT2VX18ZuR6P0irvT/bAP0h4297D/Lka5nz2vE=
|
||||
cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4/go.mod h1:J8R0E7soOpQFVqFiFd7EKepXCPpINa2n2t2EqbEsXnY=
|
||||
cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw=
|
||||
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
|
||||
cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w=
|
||||
|
||||
@ -138,9 +138,6 @@ func (bm BasicManager) RegisterGRPCGatewayRoutes(clientCtx client.Context, rtr *
|
||||
}
|
||||
|
||||
// AddTxCommands adds all tx commands to the rootTxCmd.
|
||||
//
|
||||
// TODO: Remove clientCtx argument.
|
||||
// REF: https://github.com/cosmos/cosmos-sdk/issues/6571
|
||||
func (bm BasicManager) AddTxCommands(rootTxCmd *cobra.Command) {
|
||||
for _, b := range bm {
|
||||
if cmd := b.GetTxCmd(); cmd != nil {
|
||||
@ -150,9 +147,6 @@ func (bm BasicManager) AddTxCommands(rootTxCmd *cobra.Command) {
|
||||
}
|
||||
|
||||
// AddQueryCommands adds all query commands to the rootQueryCmd.
|
||||
//
|
||||
// TODO: Remove clientCtx argument.
|
||||
// REF: https://github.com/cosmos/cosmos-sdk/issues/6571
|
||||
func (bm BasicManager) AddQueryCommands(rootQueryCmd *cobra.Command) {
|
||||
for _, b := range bm {
|
||||
if cmd := b.GetQueryCmd(); cmd != nil {
|
||||
|
||||
@ -25,8 +25,6 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
|
||||
},
|
||||
},
|
||||
},
|
||||
Tx: &autocliv1.ServiceCommandDescriptor{
|
||||
Service: authv1beta1.Msg_ServiceDesc.ServiceName,
|
||||
},
|
||||
// Tx is purposely left empty, as the only tx is MsgUpdateParams which is gov gated.
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,270 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"cosmossdk.io/core/address"
|
||||
"cosmossdk.io/x/nft"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
// Flag names and values
|
||||
const (
|
||||
FlagOwner = "owner"
|
||||
FlagClassID = "class-id"
|
||||
)
|
||||
|
||||
// GetQueryCmd returns the cli query commands for this module
|
||||
func GetQueryCmd(ac address.Codec) *cobra.Command {
|
||||
nftQueryCmd := &cobra.Command{
|
||||
Use: nft.ModuleName,
|
||||
Short: "Querying commands for the nft module",
|
||||
Long: "",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
nftQueryCmd.AddCommand(
|
||||
GetCmdQueryClass(),
|
||||
GetCmdQueryClasses(),
|
||||
GetCmdQueryNFT(),
|
||||
GetCmdQueryNFTs(ac),
|
||||
GetCmdQueryOwner(),
|
||||
GetCmdQueryBalance(),
|
||||
GetCmdQuerySupply(),
|
||||
)
|
||||
return nftQueryCmd
|
||||
}
|
||||
|
||||
// GetCmdQueryClass implements the query class command.
|
||||
func GetCmdQueryClass() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "class [class-id]",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "query an NFT class based on its id",
|
||||
Example: fmt.Sprintf(`$ %s query %s class <class-id>`, version.AppName, nft.ModuleName),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := nft.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Class(cmd.Context(), &nft.QueryClassRequest{
|
||||
ClassId: args[0],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQueryClasses implements the query classes command.
|
||||
func GetCmdQueryClasses() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "classes",
|
||||
Short: "query all NFT classes",
|
||||
Example: fmt.Sprintf(`$ %s query %s classes`, version.AppName, nft.ModuleName),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := nft.NewQueryClient(clientCtx)
|
||||
pageReq, err := client.ReadPageRequest(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := queryClient.Classes(cmd.Context(), &nft.QueryClassesRequest{
|
||||
Pagination: pageReq,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "classes")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQueryNFT implements the query nft command.
|
||||
func GetCmdQueryNFT() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "nft [class-id] [nft-id]",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Short: "query an NFT based on its class and id.",
|
||||
Example: fmt.Sprintf(`$ %s query %s nft`, version.AppName, nft.ModuleName),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := nft.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.NFT(cmd.Context(), &nft.QueryNFTRequest{
|
||||
ClassId: args[0],
|
||||
Id: args[1],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQueryNFTs implements the query nft command.
|
||||
func GetCmdQueryNFTs(ac address.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "nfts",
|
||||
Short: "query all NFTs of a given class or owner address.",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Query all NFTs of a given class or owner address. If owner
|
||||
is set, all nfts that belong to the owner are filtered out.
|
||||
Examples:
|
||||
$ %s query %s nfts <class-id> --owner=<owner>
|
||||
`,
|
||||
version.AppName, nft.ModuleName),
|
||||
),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := nft.NewQueryClient(clientCtx)
|
||||
pageReq, err := client.ReadPageRequest(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner, err := cmd.Flags().GetString(FlagOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(owner) > 0 {
|
||||
if _, err := ac.StringToBytes(owner); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
classID, err := cmd.Flags().GetString(FlagClassID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(owner) == 0 && len(classID) == 0 {
|
||||
return errors.ErrInvalidRequest.Wrap("must provide at least one of classID or owner")
|
||||
}
|
||||
|
||||
request := &nft.QueryNFTsRequest{
|
||||
ClassId: classID,
|
||||
Owner: owner,
|
||||
Pagination: pageReq,
|
||||
}
|
||||
res, err := queryClient.NFTs(cmd.Context(), request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "nfts")
|
||||
cmd.Flags().String(FlagOwner, "", "The owner of the nft")
|
||||
cmd.Flags().String(FlagClassID, "", "The class-id of the nft")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQueryOwner implements the query owner command.
|
||||
func GetCmdQueryOwner() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "owner [class-id] [nft-id]",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Short: "query the owner of the NFT based on its class and id.",
|
||||
Example: fmt.Sprintf(`$ %s query %s owner <class-id> <nft-id>`, version.AppName, nft.ModuleName),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := nft.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Owner(cmd.Context(), &nft.QueryOwnerRequest{
|
||||
ClassId: args[0],
|
||||
Id: args[1],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQueryBalance implements the query balance command.
|
||||
func GetCmdQueryBalance() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "balance [owner] [class-id]",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Short: "query the number of NFTs of a given class owned by the owner.",
|
||||
Example: fmt.Sprintf(`$ %s query %s balance <owner> <class-id>`, version.AppName, nft.ModuleName),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := nft.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Balance(cmd.Context(), &nft.QueryBalanceRequest{
|
||||
ClassId: args[1],
|
||||
Owner: args[0],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetCmdQuerySupply implements the query supply command.
|
||||
func GetCmdQuerySupply() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "supply [class-id]",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "query the number of nft based on the class.",
|
||||
Example: fmt.Sprintf(`$ %s query %s supply <class-id>`, version.AppName, nft.ModuleName),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queryClient := nft.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Supply(cmd.Context(), &nft.QuerySupplyRequest{
|
||||
ClassId: args[0],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
@ -1,308 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"cosmossdk.io/x/nft"
|
||||
"cosmossdk.io/x/nft/client/cli"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec/address"
|
||||
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
|
||||
"github.com/cosmos/cosmos-sdk/testutil"
|
||||
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
||||
)
|
||||
|
||||
func (s *CLITestSuite) TestQueryClass() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
}{
|
||||
{
|
||||
name: "json output",
|
||||
args: []string{testClassID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
|
||||
expCmdOutput: `[kitty --output=json]`,
|
||||
},
|
||||
{
|
||||
name: "text output",
|
||||
args: []string{testClassID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
|
||||
expCmdOutput: `[kitty --output=text]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
cmd := cli.GetCmdQueryClass()
|
||||
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetOut(io.Discard)
|
||||
s.Require().NotNil(cmd)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
s.Require().Contains(fmt.Sprint(cmd), "class [class-id] [] [] query an NFT class based on its id")
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
|
||||
_, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestQueryClasses() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
flagArgs []string
|
||||
expCmdOutput string
|
||||
}{
|
||||
{
|
||||
name: "json output",
|
||||
flagArgs: []string{fmt.Sprintf("--%s=json", flags.FlagOutput)},
|
||||
expCmdOutput: `[--output=json]`,
|
||||
},
|
||||
{
|
||||
name: "text output",
|
||||
flagArgs: []string{fmt.Sprintf("--%s=text", flags.FlagOutput)},
|
||||
expCmdOutput: `[--output=text]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
cmd := cli.GetCmdQueryClasses()
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetOut(io.Discard)
|
||||
s.Require().NotNil(cmd)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.flagArgs)
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
s.Require().Contains(fmt.Sprint(cmd), "classes [] [] query all NFT classes")
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
|
||||
_, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.flagArgs)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestQueryNFT() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
}{
|
||||
{
|
||||
name: "json output",
|
||||
args: []string{testClassID, testID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
|
||||
expCmdOutput: `[kitty kitty1 --output=json]`,
|
||||
},
|
||||
{
|
||||
name: "text output",
|
||||
args: []string{testClassID, testID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
|
||||
expCmdOutput: `[kitty kitty1 --output=text]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
cmd := cli.GetCmdQueryNFT()
|
||||
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetOut(io.Discard)
|
||||
s.Require().NotNil(cmd)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
s.Require().Contains(fmt.Sprint(cmd), "nft [class-id] [nft-id] [] [] query an NFT based on its class and id")
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
|
||||
_, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestQueryNFTs() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}
|
||||
expectErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "empty class id and owner",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{},
|
||||
expectErr: true,
|
||||
expErrMsg: "must provide at least one of classID or owner",
|
||||
},
|
||||
{
|
||||
name: "valid case",
|
||||
args: struct {
|
||||
ClassID string
|
||||
Owner string
|
||||
}{
|
||||
ClassID: testClassID,
|
||||
Owner: accounts[0].Address.String(),
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
cmd := cli.GetCmdQueryNFTs(address.NewBech32Codec("cosmos"))
|
||||
var args []string
|
||||
args = append(args, fmt.Sprintf("--%s=%s", cli.FlagClassID, tc.args.ClassID))
|
||||
args = append(args, fmt.Sprintf("--%s=%s", cli.FlagOwner, tc.args.Owner))
|
||||
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args)
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(err.Error(), tc.expErrMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
var result nft.QueryNFTsResponse
|
||||
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestQueryOwner() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
}{
|
||||
{
|
||||
name: "json output",
|
||||
args: []string{testClassID, testID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
|
||||
expCmdOutput: `[kitty kitty1 --output=json]`,
|
||||
},
|
||||
{
|
||||
name: "text output",
|
||||
args: []string{testClassID, testID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
|
||||
expCmdOutput: `[kitty kitty1 --output=text]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
cmd := cli.GetCmdQueryOwner()
|
||||
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetOut(io.Discard)
|
||||
s.Require().NotNil(cmd)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
s.Require().Contains(fmt.Sprint(cmd), "owner [class-id] [nft-id] [] [] query the owner of the NFT based on its class and id")
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
|
||||
_, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestQueryBalance() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
}{
|
||||
{
|
||||
name: "json output",
|
||||
args: []string{accounts[0].Address.String(), testClassID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
|
||||
expCmdOutput: fmt.Sprintf("%s kitty --output=json", accounts[0].Address.String()),
|
||||
},
|
||||
{
|
||||
name: "text output",
|
||||
args: []string{accounts[0].Address.String(), testClassID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
|
||||
expCmdOutput: fmt.Sprintf("%s kitty --output=text", accounts[0].Address.String()),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
cmd := cli.GetCmdQueryBalance()
|
||||
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetOut(io.Discard)
|
||||
s.Require().NotNil(cmd)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
s.Require().Contains(fmt.Sprint(cmd), "balance [owner] [class-id] [] [] query the number of NFTs of a given class owned by the owner")
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
|
||||
_, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestQuerySupply() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
}{
|
||||
{
|
||||
name: "valid case",
|
||||
args: []string{testClassID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
|
||||
expCmdOutput: `[kitty --output=json]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
cmd := cli.GetCmdQuerySupply()
|
||||
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetOut(io.Discard)
|
||||
s.Require().NotNil(cmd)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
s.Require().Contains(fmt.Sprint(cmd), "supply [class-id] [] [] query the number of nft based on the class")
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
|
||||
_, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
87
x/nft/module/autocli.go
Normal file
87
x/nft/module/autocli.go
Normal file
@ -0,0 +1,87 @@
|
||||
package module
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
nftv1beta1 "cosmossdk.io/api/cosmos/nft/v1beta1"
|
||||
"cosmossdk.io/x/nft"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
// AutoCLIOptions implements the autocli.HasAutoCLIConfig interface.
|
||||
func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
|
||||
return &autocliv1.ModuleOptions{
|
||||
Query: &autocliv1.ServiceCommandDescriptor{
|
||||
Service: nftv1beta1.Query_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "Balance",
|
||||
Use: "balance [owner] [class-id]",
|
||||
Short: "Query the number of NFTs of a given class owned by the owner.",
|
||||
Example: fmt.Sprintf(`%s query %s balance <owner> <class-id>`, version.AppName, nft.ModuleName),
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "owner"},
|
||||
{ProtoField: "class_id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "Owner",
|
||||
Use: "owner [class-id] [nft-id]",
|
||||
Short: "Query the owner of the NFT based on its class and id.",
|
||||
Example: fmt.Sprintf(`%s query %s owner <class-id> <nft-id>`, version.AppName, nft.ModuleName),
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "class_id"},
|
||||
{ProtoField: "id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "Supply",
|
||||
Use: "supply [class-id]",
|
||||
Short: "Query the number of nft based on the class.",
|
||||
Example: fmt.Sprintf(`%s query %s supply <class-id>`, version.AppName, nft.ModuleName),
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "class_id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "NFTs",
|
||||
Use: "nfts [class-id]",
|
||||
Short: "Query all NFTs of a given class or owner address.",
|
||||
Example: fmt.Sprintf(`%s query %s nfts <class-id> --owner=<owner>`, version.AppName, nft.ModuleName),
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "class_id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "NFT",
|
||||
Use: "nft [class-id] [nft-id]",
|
||||
Short: "Query an NFT based on its class and id.",
|
||||
Example: fmt.Sprintf(`%s query %s nft <class-id> <nft-id>`, version.AppName, nft.ModuleName),
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "class_id"},
|
||||
{ProtoField: "id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "Class",
|
||||
Use: "class [class-id]",
|
||||
Short: "Query an NFT class based on its id",
|
||||
Example: fmt.Sprintf(`%s query %s class <class-id>`, version.AppName, nft.ModuleName),
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "class_id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "Classes",
|
||||
Use: "classes",
|
||||
Short: "Query all NFT classes.",
|
||||
Example: fmt.Sprintf(`%s query %s classes`, version.AppName, nft.ModuleName),
|
||||
},
|
||||
},
|
||||
},
|
||||
Tx: &autocliv1.ServiceCommandDescriptor{
|
||||
Service: nftv1beta1.Msg_ServiceDesc.ServiceName,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -86,9 +86,10 @@ func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux
|
||||
}
|
||||
}
|
||||
|
||||
// GetQueryCmd returns the cli query commands for the nft module
|
||||
// GetQueryCmd returns a no-op command for the nft module.
|
||||
// Queries for NFT are registered by autocli.
|
||||
func (ab AppModuleBasic) GetQueryCmd() *cobra.Command {
|
||||
return cli.GetQueryCmd(ab.ac)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTxCmd returns the transaction commands for the nft module
|
||||
|
||||
Loading…
Reference in New Issue
Block a user