refactor: x/nft audit changes (backport #14055) (#14083)

This commit is contained in:
mergify[bot] 2022-11-30 08:49:42 +00:00 committed by GitHub
parent 58a167d702
commit 11bbeede3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 469 additions and 232 deletions

View File

@ -1739,9 +1739,13 @@ type EventSend struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` // class_id associated with the nft
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` // id is a unique identifier of the nft
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// sender is the address of the owner of nft
Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"`
// receiver is the receiver address of nft
Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"`
} }
@ -1799,9 +1803,12 @@ type EventMint struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"` Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"`
} }
func (x *EventMint) Reset() { func (x *EventMint) Reset() {
@ -1851,9 +1858,12 @@ type EventBurn struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"` Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"`
} }
func (x *EventBurn) Reset() { func (x *EventBurn) Reset() {

View File

@ -1223,6 +1223,7 @@ type GenesisState struct {
// class defines the class of the nft type. // class defines the class of the nft type.
Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"` Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"`
// entry defines all nft owned by a person.
Entries []*Entry `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"` Entries []*Entry `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"`
} }

View File

@ -6567,8 +6567,10 @@ type QueryBalanceRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"` // owner is the owner address of the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
} }
func (x *QueryBalanceRequest) Reset() { func (x *QueryBalanceRequest) Reset() {
@ -6611,6 +6613,7 @@ type QueryBalanceResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// amount is the number of all NFTs of a given class owned by the owner
Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"`
} }
@ -6647,8 +6650,10 @@ type QueryOwnerRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
} }
func (x *QueryOwnerRequest) Reset() { func (x *QueryOwnerRequest) Reset() {
@ -6691,6 +6696,7 @@ type QueryOwnerResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// owner is the owner address of the nft
Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"`
} }
@ -6727,6 +6733,7 @@ type QuerySupplyRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
} }
@ -6763,6 +6770,7 @@ type QuerySupplyResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// amount is the number of all NFTs from the given class
Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"`
} }
@ -6799,8 +6807,11 @@ type QueryNFTsRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` // class_id associated with the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
// pagination defines an optional pagination for the request.
Pagination *v1beta1.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` Pagination *v1beta1.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"`
} }
@ -6851,7 +6862,9 @@ type QueryNFTsResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Nfts []*NFT `protobuf:"bytes,1,rep,name=nfts,proto3" json:"nfts,omitempty"` // NFT defines the NFT
Nfts []*NFT `protobuf:"bytes,1,rep,name=nfts,proto3" json:"nfts,omitempty"`
// pagination defines the pagination in the response.
Pagination *v1beta1.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` Pagination *v1beta1.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
} }
@ -6895,8 +6908,10 @@ type QueryNFTRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
} }
func (x *QueryNFTRequest) Reset() { func (x *QueryNFTRequest) Reset() {
@ -6939,6 +6954,7 @@ type QueryNFTResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// owner is the owner address of the nft
Nft *NFT `protobuf:"bytes,1,opt,name=nft,proto3" json:"nft,omitempty"` Nft *NFT `protobuf:"bytes,1,opt,name=nft,proto3" json:"nft,omitempty"`
} }
@ -6975,6 +6991,7 @@ type QueryClassRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
} }
@ -7011,6 +7028,7 @@ type QueryClassResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// class defines the class of the nft type.
Class *Class `protobuf:"bytes,1,opt,name=class,proto3" json:"class,omitempty"` Class *Class `protobuf:"bytes,1,opt,name=class,proto3" json:"class,omitempty"`
} }
@ -7084,7 +7102,9 @@ type QueryClassesResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"` // class defines the class of the nft type.
Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"`
// pagination defines the pagination in the response.
Pagination *v1beta1.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` Pagination *v1beta1.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
} }

View File

@ -32,13 +32,13 @@ message MsgVerifyInvariant {
option (gogoproto.goproto_getters) = false; option (gogoproto.goproto_getters) = false;
// sender is the account address of private key to send coins to fee collector account. // sender is the account address of private key to send coins to fee collector account.
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// name of the invariant module. // name of the invariant module.
string invariant_module_name = 2; string invariant_module_name = 2;
// invariant_route is the msg's invariant route. // invariant_route is the msg's invariant route.
string invariant_route = 3; string invariant_route = 3;
} }
// MsgVerifyInvariantResponse defines the Msg/VerifyInvariant response type. // MsgVerifyInvariantResponse defines the Msg/VerifyInvariant response type.

View File

@ -5,22 +5,39 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/nft";
// EventSend is emitted on Msg/Send // EventSend is emitted on Msg/Send
message EventSend { message EventSend {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
string id = 2;
string sender = 3; // id is a unique identifier of the nft
string id = 2;
// sender is the address of the owner of nft
string sender = 3;
// receiver is the receiver address of nft
string receiver = 4; string receiver = 4;
} }
// EventMint is emitted on Mint // EventMint is emitted on Mint
message EventMint { message EventMint {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
string id = 2;
string owner = 3; // id is a unique identifier of the nft
string id = 2;
// owner is the owner address of the nft
string owner = 3;
} }
// EventBurn is emitted on Burn // EventBurn is emitted on Burn
message EventBurn { message EventBurn {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
string id = 2;
string owner = 3; // id is a unique identifier of the nft
string id = 2;
// owner is the owner address of the nft
string owner = 3;
} }

View File

@ -9,7 +9,9 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/nft";
message GenesisState { message GenesisState {
// class defines the class of the nft type. // class defines the class of the nft type.
repeated cosmos.nft.v1beta1.Class classes = 1; repeated cosmos.nft.v1beta1.Class classes = 1;
repeated Entry entries = 2;
// entry defines all nft owned by a person.
repeated Entry entries = 2;
} }
// Entry Defines all nft owned by a person // Entry Defines all nft owned by a person

View File

@ -48,67 +48,91 @@ service Query {
// QueryBalanceRequest is the request type for the Query/Balance RPC method // QueryBalanceRequest is the request type for the Query/Balance RPC method
message QueryBalanceRequest { message QueryBalanceRequest {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
string owner = 2;
// owner is the owner address of the nft
string owner = 2;
} }
// QueryBalanceResponse is the response type for the Query/Balance RPC method // QueryBalanceResponse is the response type for the Query/Balance RPC method
message QueryBalanceResponse { message QueryBalanceResponse {
// amount is the number of all NFTs of a given class owned by the owner
uint64 amount = 1; uint64 amount = 1;
} }
// QueryOwnerRequest is the request type for the Query/Owner RPC method // QueryOwnerRequest is the request type for the Query/Owner RPC method
message QueryOwnerRequest { message QueryOwnerRequest {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
string id = 2;
// id is a unique identifier of the NFT
string id = 2;
} }
// QueryOwnerResponse is the response type for the Query/Owner RPC method // QueryOwnerResponse is the response type for the Query/Owner RPC method
message QueryOwnerResponse { message QueryOwnerResponse {
// owner is the owner address of the nft
string owner = 1; string owner = 1;
} }
// QuerySupplyRequest is the request type for the Query/Supply RPC method // QuerySupplyRequest is the request type for the Query/Supply RPC method
message QuerySupplyRequest { message QuerySupplyRequest {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
} }
// QuerySupplyResponse is the response type for the Query/Supply RPC method // QuerySupplyResponse is the response type for the Query/Supply RPC method
message QuerySupplyResponse { message QuerySupplyResponse {
// amount is the number of all NFTs from the given class
uint64 amount = 1; uint64 amount = 1;
} }
// QueryNFTstRequest is the request type for the Query/NFTs RPC method // QueryNFTstRequest is the request type for the Query/NFTs RPC method
message QueryNFTsRequest { message QueryNFTsRequest {
string class_id = 1; // class_id associated with the nft
string owner = 2; string class_id = 1;
// owner is the owner address of the nft
string owner = 2;
// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 3; cosmos.base.query.v1beta1.PageRequest pagination = 3;
} }
// QueryNFTsResponse is the response type for the Query/NFTs RPC methods // QueryNFTsResponse is the response type for the Query/NFTs RPC methods
message QueryNFTsResponse { message QueryNFTsResponse {
repeated cosmos.nft.v1beta1.NFT nfts = 1; // NFT defines the NFT
repeated cosmos.nft.v1beta1.NFT nfts = 1;
// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2; cosmos.base.query.v1beta1.PageResponse pagination = 2;
} }
// QueryNFTRequest is the request type for the Query/NFT RPC method // QueryNFTRequest is the request type for the Query/NFT RPC method
message QueryNFTRequest { message QueryNFTRequest {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
string id = 2;
// id is a unique identifier of the NFT
string id = 2;
} }
// QueryNFTResponse is the response type for the Query/NFT RPC method // QueryNFTResponse is the response type for the Query/NFT RPC method
message QueryNFTResponse { message QueryNFTResponse {
// owner is the owner address of the nft
cosmos.nft.v1beta1.NFT nft = 1; cosmos.nft.v1beta1.NFT nft = 1;
} }
// QueryClassRequest is the request type for the Query/Class RPC method // QueryClassRequest is the request type for the Query/Class RPC method
message QueryClassRequest { message QueryClassRequest {
// class_id associated with the nft
string class_id = 1; string class_id = 1;
} }
// QueryClassResponse is the response type for the Query/Class RPC method // QueryClassResponse is the response type for the Query/Class RPC method
message QueryClassResponse { message QueryClassResponse {
// class defines the class of the nft type.
cosmos.nft.v1beta1.Class class = 1; cosmos.nft.v1beta1.Class class = 1;
} }
@ -120,6 +144,9 @@ message QueryClassesRequest {
// QueryClassesResponse is the response type for the Query/Classes RPC method // QueryClassesResponse is the response type for the Query/Classes RPC method
message QueryClassesResponse { message QueryClassesResponse {
repeated cosmos.nft.v1beta1.Class classes = 1; // class defines the class of the nft type.
repeated cosmos.nft.v1beta1.Class classes = 1;
// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2; cosmos.base.query.v1beta1.PageResponse pagination = 2;
} }

View File

@ -1,9 +1,13 @@
package cli_test package cli_test
import ( import (
"context"
"fmt" "fmt"
"io"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/cosmos/cosmos-sdk/x/nft" "github.com/cosmos/cosmos-sdk/x/nft"
@ -12,111 +16,119 @@ import (
func (s *CLITestSuite) TestQueryClass() { func (s *CLITestSuite) TestQueryClass() {
testCases := []struct { testCases := []struct {
name string name string
args struct { args []string
ClassID string expCmdOutput string
}
expectErr bool
}{ }{
{ {
name: "valid case", name: "json output",
args: struct { args: []string{testClassID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
ClassID string expCmdOutput: `[kitty --output=json]`,
}{ },
ClassID: testClassID, {
}, name: "text output",
expectErr: false, args: []string{testClassID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
expCmdOutput: `[kitty --output=text]`,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
s.Run(tc.name, func() { s.Run(tc.name, func() {
cmd := cli.GetCmdQueryClass() cmd := cli.GetCmdQueryClass()
var args []string
args = append(args, tc.args.ClassID)
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args)
if tc.expectErr { ctx := svrcmd.CreateExecuteContext(context.Background())
s.Require().Error(err)
} else { cmd.SetOut(io.Discard)
s.Require().NoError(err) s.Require().NotNil(cmd)
var result nft.QueryClassResponse
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result) cmd.SetContext(ctx)
s.Require().NoError(err) 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() { func (s *CLITestSuite) TestQueryClasses() {
testCases := []struct { testCases := []struct {
name string name string
expectErr bool flagArgs []string
expCmdOutput string
}{ }{
{ {
name: "no params", name: "json output",
expectErr: false, 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 { for _, tc := range testCases {
s.Run(tc.name, func() { s.Run(tc.name, func() {
cmd := cli.GetCmdQueryClasses() cmd := cli.GetCmdQueryClasses()
var args []string ctx := svrcmd.CreateExecuteContext(context.Background())
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) cmd.SetOut(io.Discard)
if tc.expectErr { s.Require().NotNil(cmd)
s.Require().Error(err)
} else { cmd.SetContext(ctx)
s.Require().NoError(err) cmd.SetArgs(tc.flagArgs)
var result nft.QueryClassesResponse s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
s.Require().NoError(err) 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() { func (s *CLITestSuite) TestQueryNFT() {
testCases := []struct { testCases := []struct {
name string name string
args struct { args []string
ClassID string expCmdOutput string
ID string
}
expectErr bool
}{ }{
{ {
name: "valid case", name: "json output",
args: struct { args: []string{testClassID, testID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
ClassID string expCmdOutput: `[kitty kitty1 --output=json]`,
ID string },
}{ {
ClassID: testClassID, name: "text output",
ID: testID, args: []string{testClassID, testID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
}, expCmdOutput: `[kitty kitty1 --output=text]`,
expectErr: false,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
s.Run(tc.name, func() { s.Run(tc.name, func() {
cmd := cli.GetCmdQueryNFT() cmd := cli.GetCmdQueryNFT()
var args []string
args = append(args, tc.args.ClassID)
args = append(args, tc.args.ID)
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) ctx := svrcmd.CreateExecuteContext(context.Background())
if tc.expectErr {
s.Require().Error(err) cmd.SetOut(io.Discard)
} else { s.Require().NotNil(cmd)
s.Require().NoError(err)
var result nft.QueryNFTResponse cmd.SetContext(ctx)
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result) cmd.SetArgs(tc.args)
s.Require().NoError(err) 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)
}) })
} }
} }
@ -131,6 +143,7 @@ func (s *CLITestSuite) TestQueryNFTs() {
Owner string Owner string
} }
expectErr bool expectErr bool
expErrMsg string
}{ }{
{ {
name: "empty class id and owner", name: "empty class id and owner",
@ -139,6 +152,7 @@ func (s *CLITestSuite) TestQueryNFTs() {
Owner string Owner string
}{}, }{},
expectErr: true, expectErr: true,
expErrMsg: "must provide at least one of classID or owner",
}, },
{ {
name: "valid case", name: "valid case",
@ -164,6 +178,7 @@ func (s *CLITestSuite) TestQueryNFTs() {
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args)
if tc.expectErr { if tc.expectErr {
s.Require().Error(err) s.Require().Error(err)
s.Require().Contains(err.Error(), tc.expErrMsg)
} else { } else {
s.Require().NoError(err) s.Require().NoError(err)
var result nft.QueryNFTsResponse var result nft.QueryNFTsResponse
@ -176,43 +191,40 @@ func (s *CLITestSuite) TestQueryNFTs() {
func (s *CLITestSuite) TestQueryOwner() { func (s *CLITestSuite) TestQueryOwner() {
testCases := []struct { testCases := []struct {
name string name string
args struct { args []string
ClassID string expCmdOutput string
ID string
}
expectErr bool
}{ }{
{ {
name: "valid case", name: "json output",
args: struct { args: []string{testClassID, testID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
ClassID string expCmdOutput: `[kitty kitty1 --output=json]`,
ID string },
}{ {
ClassID: testClassID, name: "text output",
ID: testID, args: []string{testClassID, testID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
}, expCmdOutput: `[kitty kitty1 --output=text]`,
expectErr: false,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
s.Run(tc.name, func() { s.Run(tc.name, func() {
cmd := cli.GetCmdQueryOwner() cmd := cli.GetCmdQueryOwner()
var args []string
args = append(args, tc.args.ClassID)
args = append(args, tc.args.ID)
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) ctx := svrcmd.CreateExecuteContext(context.Background())
if tc.expectErr {
s.Require().Error(err) cmd.SetOut(io.Discard)
} else { s.Require().NotNil(cmd)
s.Require().NoError(err)
var result nft.QueryOwnerResponse cmd.SetContext(ctx)
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result) cmd.SetArgs(tc.args)
s.Require().NoError(err) 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)
}) })
} }
} }
@ -221,82 +233,75 @@ func (s *CLITestSuite) TestQueryBalance() {
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1) accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
testCases := []struct { testCases := []struct {
name string name string
args struct { args []string
ClassID string expCmdOutput string
Owner string
}
expectErr bool
}{ }{
{ {
name: "valid case", name: "json output",
args: struct { args: []string{accounts[0].Address.String(), testClassID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
ClassID string expCmdOutput: fmt.Sprintf("%s kitty --output=json", accounts[0].Address.String()),
Owner string },
}{ {
ClassID: testClassID, name: "text output",
Owner: accounts[0].Address.String(), args: []string{accounts[0].Address.String(), testClassID, fmt.Sprintf("--%s=text", flags.FlagOutput)},
}, expCmdOutput: fmt.Sprintf("%s kitty --output=text", accounts[0].Address.String()),
expectErr: false,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
s.Run(tc.name, func() { s.Run(tc.name, func() {
cmd := cli.GetCmdQueryBalance() cmd := cli.GetCmdQueryBalance()
var args []string
args = append(args, tc.args.Owner)
args = append(args, tc.args.ClassID)
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) ctx := svrcmd.CreateExecuteContext(context.Background())
if tc.expectErr {
s.Require().Error(err) cmd.SetOut(io.Discard)
} else { s.Require().NotNil(cmd)
s.Require().NoError(err)
var result nft.QueryBalanceResponse cmd.SetContext(ctx)
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result) cmd.SetArgs(tc.args)
s.Require().NoError(err) 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() { func (s *CLITestSuite) TestQuerySupply() {
testCases := []struct { testCases := []struct {
name string name string
args struct { args []string
ClassID string expCmdOutput string
}
expectErr bool
}{ }{
{ {
name: "valid case", name: "valid case",
args: struct { args: []string{testClassID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
ClassID string expCmdOutput: `[kitty --output=json]`,
}{
ClassID: testClassID,
},
expectErr: false,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
s.Run(tc.name, func() { s.Run(tc.name, func() {
cmd := cli.GetCmdQuerySupply() cmd := cli.GetCmdQuerySupply()
var args []string
args = append(args, tc.args.ClassID)
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) ctx := svrcmd.CreateExecuteContext(context.Background())
if tc.expectErr {
s.Require().Error(err) cmd.SetOut(io.Discard)
} else { s.Require().NotNil(cmd)
s.Require().NoError(err)
var result nft.QuerySupplyResponse cmd.SetContext(ctx)
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result) cmd.SetArgs(tc.args)
s.Require().NoError(err) 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)
}) })
} }
} }

View File

@ -31,6 +31,7 @@ func GetTxCmd() *cobra.Command {
return nftTxCmd return nftTxCmd
} }
// NewCmdSend creates a CLI command for MsgSend.
func NewCmdSend() *cobra.Command { func NewCmdSend() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "send [class-id] [nft-id] [receiver] --from [sender]", Use: "send [class-id] [nft-id] [receiver] --from [sender]",

View File

@ -126,7 +126,7 @@ func (s *CLITestSuite) SetupSuite() {
func (s *CLITestSuite) TestCLITxSend() { func (s *CLITestSuite) TestCLITxSend() {
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1) accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
args := []string{ extraArgs := []string{
fmt.Sprintf("--%s=%s", flags.FlagFrom, OwnerName), fmt.Sprintf("--%s=%s", flags.FlagFrom, OwnerName),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
@ -138,7 +138,41 @@ func (s *CLITestSuite) TestCLITxSend() {
args []string args []string
expectedCode uint32 expectedCode uint32
expectErr bool expectErr bool
expErrMsg string
}{ }{
{
"class id is empty",
[]string{
"",
testID,
accounts[0].Address.String(),
},
0,
true,
"empty class id",
},
{
"nft id is empty",
[]string{
testClassID,
"",
accounts[0].Address.String(),
},
0,
true,
"empty nft id",
},
{
"invalid receiver address",
[]string{
testClassID,
testID,
"invalid receiver",
},
0,
true,
"Invalid receiver address",
},
{ {
"valid transaction", "valid transaction",
[]string{ []string{
@ -148,13 +182,14 @@ func (s *CLITestSuite) TestCLITxSend() {
}, },
0, 0,
false, false,
"",
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
tc := tc tc := tc
s.Run(tc.name, func() { s.Run(tc.name, func() {
args = append(args, tc.args...) args := append(tc.args, extraArgs...)
cmd := cli.NewCmdSend() cmd := cli.NewCmdSend()
cmd.SetContext(s.ctx) cmd.SetContext(s.ctx)
cmd.SetArgs(args) cmd.SetArgs(args)
@ -164,6 +199,7 @@ func (s *CLITestSuite) TestCLITxSend() {
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args) out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args)
if tc.expectErr { if tc.expectErr {
s.Require().Error(err) s.Require().Error(err)
s.Require().Contains(err.Error(), tc.expErrMsg)
} else { } else {
var txResp sdk.TxResponse var txResp sdk.TxResponse
s.Require().NoError(err) s.Require().NoError(err)

View File

@ -6,6 +6,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/msgservice" "github.com/cosmos/cosmos-sdk/types/msgservice"
) )
// RegisterInterfaces registers the interfaces types with the interface registry.
func RegisterInterfaces(registry types.InterfaceRegistry) { func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil), registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgSend{}, &MsgSend{},

View File

@ -6,9 +6,9 @@ import (
// x/nft module sentinel errors // x/nft module sentinel errors
var ( var (
ErrClassExists = errors.Register(ModuleName, 3, "nft class already exist") ErrClassExists = errors.Register(ModuleName, 3, "nft class already exists")
ErrClassNotExists = errors.Register(ModuleName, 4, "nft class does not exist") ErrClassNotExists = errors.Register(ModuleName, 4, "nft class does not exist")
ErrNFTExists = errors.Register(ModuleName, 5, "nft already exist") ErrNFTExists = errors.Register(ModuleName, 5, "nft already exists")
ErrNFTNotExists = errors.Register(ModuleName, 6, "nft does not exist") ErrNFTNotExists = errors.Register(ModuleName, 6, "nft does not exist")
ErrEmptyClassID = errors.Register(ModuleName, 7, "empty class id") ErrEmptyClassID = errors.Register(ModuleName, 7, "empty class id")
ErrEmptyNFTID = errors.Register(ModuleName, 8, "empty nft id") ErrEmptyNFTID = errors.Register(ModuleName, 8, "empty nft id")

View File

@ -24,9 +24,13 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// EventSend is emitted on Msg/Send // EventSend is emitted on Msg/Send
type EventSend struct { type EventSend struct {
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` // class_id associated with the nft
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` // id is a unique identifier of the nft
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// sender is the address of the owner of nft
Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"`
// receiver is the receiver address of nft
Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"`
} }
@ -93,9 +97,12 @@ func (m *EventSend) GetReceiver() string {
// EventMint is emitted on Mint // EventMint is emitted on Mint
type EventMint struct { type EventMint struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"` Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"`
} }
func (m *EventMint) Reset() { *m = EventMint{} } func (m *EventMint) Reset() { *m = EventMint{} }
@ -154,9 +161,12 @@ func (m *EventMint) GetOwner() string {
// EventBurn is emitted on Burn // EventBurn is emitted on Burn
type EventBurn struct { type EventBurn struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"` Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"`
} }
func (m *EventBurn) Reset() { *m = EventBurn{} } func (m *EventBurn) Reset() { *m = EventBurn{} }

View File

@ -4,7 +4,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
// ValidateGenesis check the given genesis state has no integrity issues // ValidateGenesis checks that the given genesis state has no integrity issues
func ValidateGenesis(data GenesisState) error { func ValidateGenesis(data GenesisState) error {
for _, class := range data.Classes { for _, class := range data.Classes {
if len(class.Id) == 0 { if len(class.Id) == 0 {
@ -24,7 +24,7 @@ func ValidateGenesis(data GenesisState) error {
return nil return nil
} }
// DefaultGenesisState - Return a default genesis state // DefaultGenesisState - Returns a default genesis state
func DefaultGenesisState() *GenesisState { func DefaultGenesisState() *GenesisState {
return &GenesisState{} return &GenesisState{}
} }

View File

@ -26,6 +26,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type GenesisState struct { type GenesisState struct {
// class defines the class of the nft type. // class defines the class of the nft type.
Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"` Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"`
// entry defines all nft owned by a person.
Entries []*Entry `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"` Entries []*Entry `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"`
} }

View File

@ -20,7 +20,7 @@ func (k Keeper) SaveClass(ctx sdk.Context, class nft.Class) error {
return nil return nil
} }
// UpdateClass defines a method for updating a exist nft class // UpdateClass defines a method for updating an exist nft class
func (k Keeper) UpdateClass(ctx sdk.Context, class nft.Class) error { func (k Keeper) UpdateClass(ctx sdk.Context, class nft.Class) error {
if !k.HasClass(ctx, class.Id) { if !k.HasClass(ctx, class.Id) {
return sdkerrors.Wrap(nft.ErrClassNotExists, class.Id) return sdkerrors.Wrap(nft.ErrClassNotExists, class.Id)

View File

@ -7,7 +7,8 @@ import (
"github.com/cosmos/cosmos-sdk/x/nft" "github.com/cosmos/cosmos-sdk/x/nft"
) )
// InitGenesis new nft genesis // InitGenesis initializes the nft module's genesis state from a given
// genesis state.
func (k Keeper) InitGenesis(ctx sdk.Context, data *nft.GenesisState) { func (k Keeper) InitGenesis(ctx sdk.Context, data *nft.GenesisState) {
for _, class := range data.Classes { for _, class := range data.Classes {
if err := k.SaveClass(ctx, *class); err != nil { if err := k.SaveClass(ctx, *class); err != nil {

View File

@ -14,10 +14,6 @@ var _ nft.QueryServer = Keeper{}
// Balance return the number of NFTs of a given class owned by the owner, same as balanceOf in ERC721 // Balance return the number of NFTs of a given class owned by the owner, same as balanceOf in ERC721
func (k Keeper) Balance(goCtx context.Context, r *nft.QueryBalanceRequest) (*nft.QueryBalanceResponse, error) { func (k Keeper) Balance(goCtx context.Context, r *nft.QueryBalanceRequest) (*nft.QueryBalanceResponse, error) {
if r == nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty request")
}
if len(r.ClassId) == 0 { if len(r.ClassId) == 0 {
return nil, nft.ErrEmptyClassID return nil, nft.ErrEmptyClassID
} }
@ -34,10 +30,6 @@ func (k Keeper) Balance(goCtx context.Context, r *nft.QueryBalanceRequest) (*nft
// Owner return the owner of the NFT based on its class and id, same as ownerOf in ERC721 // Owner return the owner of the NFT based on its class and id, same as ownerOf in ERC721
func (k Keeper) Owner(goCtx context.Context, r *nft.QueryOwnerRequest) (*nft.QueryOwnerResponse, error) { func (k Keeper) Owner(goCtx context.Context, r *nft.QueryOwnerRequest) (*nft.QueryOwnerResponse, error) {
if r == nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty request")
}
if len(r.ClassId) == 0 { if len(r.ClassId) == 0 {
return nil, nft.ErrEmptyClassID return nil, nft.ErrEmptyClassID
} }
@ -53,10 +45,6 @@ func (k Keeper) Owner(goCtx context.Context, r *nft.QueryOwnerRequest) (*nft.Que
// Supply return the number of NFTs from the given class, same as totalSupply of ERC721. // Supply return the number of NFTs from the given class, same as totalSupply of ERC721.
func (k Keeper) Supply(goCtx context.Context, r *nft.QuerySupplyRequest) (*nft.QuerySupplyResponse, error) { func (k Keeper) Supply(goCtx context.Context, r *nft.QuerySupplyRequest) (*nft.QuerySupplyResponse, error) {
if r == nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty request")
}
if len(r.ClassId) == 0 { if len(r.ClassId) == 0 {
return nil, nft.ErrEmptyClassID return nil, nft.ErrEmptyClassID
} }
@ -67,10 +55,6 @@ func (k Keeper) Supply(goCtx context.Context, r *nft.QuerySupplyRequest) (*nft.Q
// NFTs queries all NFTs of a given class or owner (at least one must be provided), similar to tokenByIndex in ERC721Enumerable // NFTs queries all NFTs of a given class or owner (at least one must be provided), similar to tokenByIndex in ERC721Enumerable
func (k Keeper) NFTs(goCtx context.Context, r *nft.QueryNFTsRequest) (*nft.QueryNFTsResponse, error) { func (k Keeper) NFTs(goCtx context.Context, r *nft.QueryNFTsRequest) (*nft.QueryNFTsResponse, error) {
if r == nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty request")
}
var err error var err error
var owner sdk.AccAddress var owner sdk.AccAddress
@ -129,10 +113,6 @@ func (k Keeper) NFTs(goCtx context.Context, r *nft.QueryNFTsRequest) (*nft.Query
// NFT return an NFT based on its class and id. // NFT return an NFT based on its class and id.
func (k Keeper) NFT(goCtx context.Context, r *nft.QueryNFTRequest) (*nft.QueryNFTResponse, error) { func (k Keeper) NFT(goCtx context.Context, r *nft.QueryNFTRequest) (*nft.QueryNFTResponse, error) {
if r == nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty request")
}
if len(r.ClassId) == 0 { if len(r.ClassId) == 0 {
return nil, nft.ErrEmptyClassID return nil, nft.ErrEmptyClassID
} }
@ -150,10 +130,6 @@ func (k Keeper) NFT(goCtx context.Context, r *nft.QueryNFTRequest) (*nft.QueryNF
// Class return an NFT class based on its id // Class return an NFT class based on its id
func (k Keeper) Class(goCtx context.Context, r *nft.QueryClassRequest) (*nft.QueryClassResponse, error) { func (k Keeper) Class(goCtx context.Context, r *nft.QueryClassRequest) (*nft.QueryClassResponse, error) {
if r == nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty request")
}
if len(r.ClassId) == 0 { if len(r.ClassId) == 0 {
return nil, nft.ErrEmptyClassID return nil, nft.ErrEmptyClassID
} }
@ -168,10 +144,6 @@ func (k Keeper) Class(goCtx context.Context, r *nft.QueryClassRequest) (*nft.Que
// Classes return all NFT classes // Classes return all NFT classes
func (k Keeper) Classes(goCtx context.Context, r *nft.QueryClassesRequest) (*nft.QueryClassesResponse, error) { func (k Keeper) Classes(goCtx context.Context, r *nft.QueryClassesRequest) (*nft.QueryClassesResponse, error) {
if r == nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap("empty request")
}
ctx := sdk.UnwrapSDKContext(goCtx) ctx := sdk.UnwrapSDKContext(goCtx)
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
classStore := prefix.NewStore(store, ClassKey) classStore := prefix.NewStore(store, ClassKey)

View File

@ -10,7 +10,7 @@ import (
var _ nft.MsgServer = Keeper{} var _ nft.MsgServer = Keeper{}
// Send implement Send method of the types.MsgServer. // Send implements Send method of the types.MsgServer.
func (k Keeper) Send(goCtx context.Context, msg *nft.MsgSend) (*nft.MsgSendResponse, error) { func (k Keeper) Send(goCtx context.Context, msg *nft.MsgSend) (*nft.MsgSendResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx) ctx := sdk.UnwrapSDKContext(goCtx)
sender, err := sdk.AccAddressFromBech32(msg.Sender) sender, err := sdk.AccAddressFromBech32(msg.Sender)

View File

@ -0,0 +1,110 @@
package keeper_test
import (
"fmt"
"github.com/cosmos/cosmos-sdk/x/nft"
)
var (
ExpClass = nft.Class{
Id: testClassID,
Name: testClassName,
Symbol: testClassSymbol,
Description: testClassDescription,
Uri: testClassURI,
UriHash: testClassURIHash,
}
ExpNFT = nft.NFT{
ClassId: testClassID,
Id: testID,
Uri: testURI,
}
)
func (s *TestSuite) TestSend() {
err := s.nftKeeper.SaveClass(s.ctx, ExpClass)
s.Require().NoError(err)
actual, has := s.nftKeeper.GetClass(s.ctx, testClassID)
s.Require().True(has)
s.Require().EqualValues(ExpClass, actual)
err = s.nftKeeper.Mint(s.ctx, ExpNFT, s.addrs[0])
s.Require().NoError(err)
expGenesis := &nft.GenesisState{
Classes: []*nft.Class{&ExpClass},
Entries: []*nft.Entry{{
Owner: s.addrs[0].String(),
Nfts: []*nft.NFT{&ExpNFT},
}},
}
genesis := s.nftKeeper.ExportGenesis(s.ctx)
s.Require().Equal(expGenesis, genesis)
testCases := []struct {
name string
req *nft.MsgSend
expErr bool
errMsg string
}{
{
name: "invalid class id",
req: &nft.MsgSend{
ClassId: "invalid ClassId",
Id: testID,
Sender: s.addrs[0].String(),
Receiver: s.addrs[1].String(),
},
expErr: true,
errMsg: "unauthorized",
},
{
name: "invalid nft id",
req: &nft.MsgSend{
ClassId: testClassID,
Id: "invalid Id",
Sender: s.addrs[0].String(),
Receiver: s.addrs[1].String(),
},
expErr: true,
errMsg: "unauthorized",
},
{
name: "unauthorized sender",
req: &nft.MsgSend{
ClassId: testClassID,
Id: testID,
Sender: s.addrs[1].String(),
Receiver: s.addrs[2].String(),
},
expErr: true,
errMsg: fmt.Sprintf("%s is not the owner of nft %s", s.addrs[1].String(), testID),
},
{
name: "valid transaction",
req: &nft.MsgSend{
ClassId: testClassID,
Id: testID,
Sender: s.addrs[0].String(),
Receiver: s.addrs[1].String(),
},
expErr: false,
errMsg: "",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
_, err := s.nftKeeper.Send(s.ctx, tc.req)
if tc.expErr {
s.Require().Error(err)
s.Require().Contains(err.Error(), tc.errMsg)
} else {
s.Require().NoError(err)
}
})
}
}

View File

@ -1,7 +1,7 @@
package nft package nft
const ( const (
// ModuleName module name // ModuleName defines the name of the nft module
ModuleName = "nft" ModuleName = "nft"
// StoreKey is the default store key for nft // StoreKey is the default store key for nft

View File

@ -2,6 +2,7 @@ package nft
import ( import (
"cosmossdk.io/errors" "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
) )
@ -35,7 +36,7 @@ func (m MsgSend) ValidateBasic() error {
return nil return nil
} }
// GetSigners implements Msg // GetSigners returns the expected signers for MsgSend.
func (m MsgSend) GetSigners() []sdk.AccAddress { func (m MsgSend) GetSigners() []sdk.AccAddress {
signer, _ := sdk.AccAddressFromBech32(m.Sender) signer, _ := sdk.AccAddressFromBech32(m.Sender)
return []sdk.AccAddress{signer} return []sdk.AccAddress{signer}

View File

@ -31,8 +31,10 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// QueryBalanceRequest is the request type for the Query/Balance RPC method // QueryBalanceRequest is the request type for the Query/Balance RPC method
type QueryBalanceRequest struct { type QueryBalanceRequest struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"` // owner is the owner address of the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
} }
func (m *QueryBalanceRequest) Reset() { *m = QueryBalanceRequest{} } func (m *QueryBalanceRequest) Reset() { *m = QueryBalanceRequest{} }
@ -84,6 +86,7 @@ func (m *QueryBalanceRequest) GetOwner() string {
// QueryBalanceResponse is the response type for the Query/Balance RPC method // QueryBalanceResponse is the response type for the Query/Balance RPC method
type QueryBalanceResponse struct { type QueryBalanceResponse struct {
// amount is the number of all NFTs of a given class owned by the owner
Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"`
} }
@ -129,8 +132,10 @@ func (m *QueryBalanceResponse) GetAmount() uint64 {
// QueryOwnerRequest is the request type for the Query/Owner RPC method // QueryOwnerRequest is the request type for the Query/Owner RPC method
type QueryOwnerRequest struct { type QueryOwnerRequest struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
} }
func (m *QueryOwnerRequest) Reset() { *m = QueryOwnerRequest{} } func (m *QueryOwnerRequest) Reset() { *m = QueryOwnerRequest{} }
@ -182,6 +187,7 @@ func (m *QueryOwnerRequest) GetId() string {
// QueryOwnerResponse is the response type for the Query/Owner RPC method // QueryOwnerResponse is the response type for the Query/Owner RPC method
type QueryOwnerResponse struct { type QueryOwnerResponse struct {
// owner is the owner address of the nft
Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"`
} }
@ -227,6 +233,7 @@ func (m *QueryOwnerResponse) GetOwner() string {
// QuerySupplyRequest is the request type for the Query/Supply RPC method // QuerySupplyRequest is the request type for the Query/Supply RPC method
type QuerySupplyRequest struct { type QuerySupplyRequest struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
} }
@ -272,6 +279,7 @@ func (m *QuerySupplyRequest) GetClassId() string {
// QuerySupplyResponse is the response type for the Query/Supply RPC method // QuerySupplyResponse is the response type for the Query/Supply RPC method
type QuerySupplyResponse struct { type QuerySupplyResponse struct {
// amount is the number of all NFTs from the given class
Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"`
} }
@ -317,8 +325,11 @@ func (m *QuerySupplyResponse) GetAmount() uint64 {
// QueryNFTstRequest is the request type for the Query/NFTs RPC method // QueryNFTstRequest is the request type for the Query/NFTs RPC method
type QueryNFTsRequest struct { type QueryNFTsRequest struct {
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` // class_id associated with the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
// pagination defines an optional pagination for the request.
Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"`
} }
@ -378,7 +389,9 @@ func (m *QueryNFTsRequest) GetPagination() *query.PageRequest {
// QueryNFTsResponse is the response type for the Query/NFTs RPC methods // QueryNFTsResponse is the response type for the Query/NFTs RPC methods
type QueryNFTsResponse struct { type QueryNFTsResponse struct {
Nfts []*NFT `protobuf:"bytes,1,rep,name=nfts,proto3" json:"nfts,omitempty"` // NFT defines the NFT
Nfts []*NFT `protobuf:"bytes,1,rep,name=nfts,proto3" json:"nfts,omitempty"`
// pagination defines the pagination in the response.
Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
} }
@ -431,8 +444,10 @@ func (m *QueryNFTsResponse) GetPagination() *query.PageResponse {
// QueryNFTRequest is the request type for the Query/NFT RPC method // QueryNFTRequest is the request type for the Query/NFT RPC method
type QueryNFTRequest struct { type QueryNFTRequest struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
} }
func (m *QueryNFTRequest) Reset() { *m = QueryNFTRequest{} } func (m *QueryNFTRequest) Reset() { *m = QueryNFTRequest{} }
@ -484,6 +499,7 @@ func (m *QueryNFTRequest) GetId() string {
// QueryNFTResponse is the response type for the Query/NFT RPC method // QueryNFTResponse is the response type for the Query/NFT RPC method
type QueryNFTResponse struct { type QueryNFTResponse struct {
// owner is the owner address of the nft
Nft *NFT `protobuf:"bytes,1,opt,name=nft,proto3" json:"nft,omitempty"` Nft *NFT `protobuf:"bytes,1,opt,name=nft,proto3" json:"nft,omitempty"`
} }
@ -529,6 +545,7 @@ func (m *QueryNFTResponse) GetNft() *NFT {
// QueryClassRequest is the request type for the Query/Class RPC method // QueryClassRequest is the request type for the Query/Class RPC method
type QueryClassRequest struct { type QueryClassRequest struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"` ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,omitempty"`
} }
@ -574,6 +591,7 @@ func (m *QueryClassRequest) GetClassId() string {
// QueryClassResponse is the response type for the Query/Class RPC method // QueryClassResponse is the response type for the Query/Class RPC method
type QueryClassResponse struct { type QueryClassResponse struct {
// class defines the class of the nft type.
Class *Class `protobuf:"bytes,1,opt,name=class,proto3" json:"class,omitempty"` Class *Class `protobuf:"bytes,1,opt,name=class,proto3" json:"class,omitempty"`
} }
@ -665,7 +683,9 @@ func (m *QueryClassesRequest) GetPagination() *query.PageRequest {
// QueryClassesResponse is the response type for the Query/Classes RPC method // QueryClassesResponse is the response type for the Query/Classes RPC method
type QueryClassesResponse struct { type QueryClassesResponse struct {
Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"` // class defines the class of the nft type.
Classes []*Class `protobuf:"bytes,1,rep,name=classes,proto3" json:"classes,omitempty"`
// pagination defines the pagination in the response.
Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
} }

View File

@ -11,7 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/nft/keeper" "github.com/cosmos/cosmos-sdk/x/nft/keeper"
) )
// NewDecodeStore returns a decoder function closure that umarshals the KVPair's // NewDecodeStore returns a decoder function closure that unmarshals the KVPair's
// Value to the corresponding nft type. // Value to the corresponding nft type.
func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string {
return func(kvA, kvB kv.Pair) string { return func(kvA, kvB kv.Pair) string {

View File

@ -119,6 +119,7 @@ func SimulateMsgSend(
} }
} }
// randNFT picks a random NFT from a class belonging to the specified owner(minter).
func randNFT(ctx sdk.Context, r *rand.Rand, k keeper.Keeper, minter sdk.AccAddress) (nft.NFT, error) { func randNFT(ctx sdk.Context, r *rand.Rand, k keeper.Keeper, minter sdk.AccAddress) (nft.NFT, error) {
c, err := randClass(ctx, r, k) c, err := randClass(ctx, r, k)
if err != nil { if err != nil {
@ -141,6 +142,7 @@ func randNFT(ctx sdk.Context, r *rand.Rand, k keeper.Keeper, minter sdk.AccAddre
return n, nil return n, nil
} }
// randClass picks a random Class.
func randClass(ctx sdk.Context, r *rand.Rand, k keeper.Keeper) (nft.Class, error) { func randClass(ctx sdk.Context, r *rand.Rand, k keeper.Keeper) (nft.Class, error) {
classes := k.GetClasses(ctx) classes := k.GetClasses(ctx)
if len(classes) == 0 { if len(classes) == 0 {