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
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"`
// 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"`
}
@ -1799,8 +1803,11 @@ type EventMint struct {
sizeCache protoimpl.SizeCache
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"`
// id is a unique identifier of the nft
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"`
}
@ -1851,8 +1858,11 @@ type EventBurn struct {
sizeCache protoimpl.SizeCache
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"`
// id is a unique identifier of the nft
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"`
}

View File

@ -1223,6 +1223,7 @@ type GenesisState struct {
// class defines the class of the nft type.
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"`
}

View File

@ -6567,7 +6567,9 @@ type QueryBalanceRequest struct {
sizeCache protoimpl.SizeCache
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"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
}
@ -6611,6 +6613,7 @@ type QueryBalanceResponse struct {
sizeCache protoimpl.SizeCache
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"`
}
@ -6647,7 +6650,9 @@ type QueryOwnerRequest struct {
sizeCache protoimpl.SizeCache
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"`
// id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
}
@ -6691,6 +6696,7 @@ type QueryOwnerResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// owner is the owner address of the nft
Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"`
}
@ -6727,6 +6733,7 @@ type QuerySupplyRequest struct {
sizeCache protoimpl.SizeCache
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"`
}
@ -6763,6 +6770,7 @@ type QuerySupplyResponse struct {
sizeCache protoimpl.SizeCache
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"`
}
@ -6799,8 +6807,11 @@ type QueryNFTsRequest struct {
sizeCache protoimpl.SizeCache
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"`
// 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"`
}
@ -6851,7 +6862,9 @@ type QueryNFTsResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 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"`
}
@ -6895,7 +6908,9 @@ type QueryNFTRequest struct {
sizeCache protoimpl.SizeCache
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"`
// id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
}
@ -6939,6 +6954,7 @@ type QueryNFTResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// owner is the owner address of the nft
Nft *NFT `protobuf:"bytes,1,opt,name=nft,proto3" json:"nft,omitempty"`
}
@ -6975,6 +6991,7 @@ type QueryClassRequest struct {
sizeCache protoimpl.SizeCache
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"`
}
@ -7011,6 +7028,7 @@ type QueryClassResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// class defines the class of the nft type.
Class *Class `protobuf:"bytes,1,opt,name=class,proto3" json:"class,omitempty"`
}
@ -7084,7 +7102,9 @@ type QueryClassesResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 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"`
}

View File

@ -5,22 +5,39 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/nft";
// EventSend is emitted on Msg/Send
message EventSend {
// class_id associated with the nft
string class_id = 1;
// 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;
}
// EventMint is emitted on Mint
message EventMint {
// class_id associated with the nft
string class_id = 1;
// 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
message EventBurn {
// class_id associated with the nft
string class_id = 1;
// 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,6 +9,8 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/nft";
message GenesisState {
// class defines the class of the nft type.
repeated cosmos.nft.v1beta1.Class classes = 1;
// entry defines all nft owned by a person.
repeated Entry entries = 2;
}

View File

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

View File

@ -1,9 +1,13 @@
package cli_test
import (
"context"
"fmt"
"io"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"github.com/cosmos/cosmos-sdk/testutil"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/cosmos/cosmos-sdk/x/nft"
@ -13,38 +17,39 @@ import (
func (s *CLITestSuite) TestQueryClass() {
testCases := []struct {
name string
args struct {
ClassID string
}
expectErr bool
args []string
expCmdOutput string
}{
{
name: "valid case",
args: struct {
ClassID string
}{
ClassID: testClassID,
name: "json output",
args: []string{testClassID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
expCmdOutput: `[kitty --output=json]`,
},
expectErr: false,
{
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()
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 {
s.Require().Error(err)
} else {
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)
var result nft.QueryClassResponse
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
s.Require().NoError(err)
}
})
}
}
@ -52,28 +57,38 @@ func (s *CLITestSuite) TestQueryClass() {
func (s *CLITestSuite) TestQueryClasses() {
testCases := []struct {
name string
expectErr bool
flagArgs []string
expCmdOutput string
}{
{
name: "no params",
expectErr: false,
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()
var args []string
args = append(args, fmt.Sprintf("--%s=json", flags.FlagOutput))
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args)
if tc.expectErr {
s.Require().Error(err)
} else {
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)
var result nft.QueryClassesResponse
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
s.Require().NoError(err)
}
})
}
}
@ -81,42 +96,39 @@ func (s *CLITestSuite) TestQueryClasses() {
func (s *CLITestSuite) TestQueryNFT() {
testCases := []struct {
name string
args struct {
ClassID string
ID string
}
expectErr bool
args []string
expCmdOutput string
}{
{
name: "valid case",
args: struct {
ClassID string
ID string
}{
ClassID: testClassID,
ID: testID,
name: "json output",
args: []string{testClassID, testID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
expCmdOutput: `[kitty kitty1 --output=json]`,
},
expectErr: false,
{
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()
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)
if tc.expectErr {
s.Require().Error(err)
} else {
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)
var result nft.QueryNFTResponse
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
s.Require().NoError(err)
}
})
}
}
@ -131,6 +143,7 @@ func (s *CLITestSuite) TestQueryNFTs() {
Owner string
}
expectErr bool
expErrMsg string
}{
{
name: "empty class id and owner",
@ -139,6 +152,7 @@ func (s *CLITestSuite) TestQueryNFTs() {
Owner string
}{},
expectErr: true,
expErrMsg: "must provide at least one of classID or owner",
},
{
name: "valid case",
@ -164,6 +178,7 @@ func (s *CLITestSuite) TestQueryNFTs() {
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
@ -177,42 +192,39 @@ func (s *CLITestSuite) TestQueryNFTs() {
func (s *CLITestSuite) TestQueryOwner() {
testCases := []struct {
name string
args struct {
ClassID string
ID string
}
expectErr bool
args []string
expCmdOutput string
}{
{
name: "valid case",
args: struct {
ClassID string
ID string
}{
ClassID: testClassID,
ID: testID,
name: "json output",
args: []string{testClassID, testID, fmt.Sprintf("--%s=json", flags.FlagOutput)},
expCmdOutput: `[kitty kitty1 --output=json]`,
},
expectErr: false,
{
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()
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)
if tc.expectErr {
s.Require().Error(err)
} else {
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)
var result nft.QueryOwnerResponse
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
s.Require().NoError(err)
}
})
}
}
@ -222,42 +234,39 @@ func (s *CLITestSuite) TestQueryBalance() {
testCases := []struct {
name string
args struct {
ClassID string
Owner string
}
expectErr bool
args []string
expCmdOutput string
}{
{
name: "valid case",
args: struct {
ClassID string
Owner string
}{
ClassID: testClassID,
Owner: accounts[0].Address.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()),
},
expectErr: false,
{
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()
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)
if tc.expectErr {
s.Require().Error(err)
} else {
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)
var result nft.QueryBalanceResponse
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
s.Require().NoError(err)
}
})
}
}
@ -265,38 +274,34 @@ func (s *CLITestSuite) TestQueryBalance() {
func (s *CLITestSuite) TestQuerySupply() {
testCases := []struct {
name string
args struct {
ClassID string
}
expectErr bool
args []string
expCmdOutput string
}{
{
name: "valid case",
args: struct {
ClassID string
}{
ClassID: testClassID,
},
expectErr: false,
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()
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 {
s.Require().Error(err)
} else {
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)
var result nft.QuerySupplyResponse
err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &result)
s.Require().NoError(err)
}
})
}
}

View File

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

View File

@ -126,7 +126,7 @@ func (s *CLITestSuite) SetupSuite() {
func (s *CLITestSuite) TestCLITxSend() {
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
args := []string{
extraArgs := []string{
fmt.Sprintf("--%s=%s", flags.FlagFrom, OwnerName),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
@ -138,7 +138,41 @@ func (s *CLITestSuite) TestCLITxSend() {
args []string
expectedCode uint32
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",
[]string{
@ -148,13 +182,14 @@ func (s *CLITestSuite) TestCLITxSend() {
},
0,
false,
"",
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
args = append(args, tc.args...)
args := append(tc.args, extraArgs...)
cmd := cli.NewCmdSend()
cmd.SetContext(s.ctx)
cmd.SetArgs(args)
@ -164,6 +199,7 @@ func (s *CLITestSuite) TestCLITxSend() {
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args)
if tc.expectErr {
s.Require().Error(err)
s.Require().Contains(err.Error(), tc.expErrMsg)
} else {
var txResp sdk.TxResponse
s.Require().NoError(err)

View File

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

View File

@ -6,9 +6,9 @@ import (
// x/nft module sentinel errors
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")
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")
ErrEmptyClassID = errors.Register(ModuleName, 7, "empty class 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
type EventSend struct {
// class_id associated with the nft
ClassId string `protobuf:"bytes,1,opt,name=class_id,json=classId,proto3" json:"class_id,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"`
}
@ -93,8 +97,11 @@ func (m *EventSend) GetReceiver() string {
// EventMint is emitted on Mint
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"`
// id is a unique identifier of the nft
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"`
}
@ -154,8 +161,11 @@ func (m *EventMint) GetOwner() string {
// EventBurn is emitted on Burn
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"`
// id is a unique identifier of the nft
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"`
}

View File

@ -4,7 +4,7 @@ import (
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 {
for _, class := range data.Classes {
if len(class.Id) == 0 {
@ -24,7 +24,7 @@ func ValidateGenesis(data GenesisState) error {
return nil
}
// DefaultGenesisState - Return a default genesis state
// DefaultGenesisState - Returns a default genesis state
func DefaultGenesisState() *GenesisState {
return &GenesisState{}
}

View File

@ -26,6 +26,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type GenesisState struct {
// class defines the class of the nft type.
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"`
}

View File

@ -20,7 +20,7 @@ func (k Keeper) SaveClass(ctx sdk.Context, class nft.Class) error {
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 {
if !k.HasClass(ctx, class.Id) {
return sdkerrors.Wrap(nft.ErrClassNotExists, class.Id)

View File

@ -7,7 +7,8 @@ import (
"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) {
for _, class := range data.Classes {
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
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 {
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
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 {
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.
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 {
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
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 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.
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 {
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
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 {
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
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)
store := ctx.KVStore(k.storeKey)
classStore := prefix.NewStore(store, ClassKey)

View File

@ -10,7 +10,7 @@ import (
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) {
ctx := sdk.UnwrapSDKContext(goCtx)
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
const (
// ModuleName module name
// ModuleName defines the name of the nft module
ModuleName = "nft"
// StoreKey is the default store key for nft

View File

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

View File

@ -31,7 +31,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// QueryBalanceRequest is the request type for the Query/Balance RPC method
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"`
// owner is the owner address of the nft
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
}
@ -84,6 +86,7 @@ func (m *QueryBalanceRequest) GetOwner() string {
// QueryBalanceResponse is the response type for the Query/Balance RPC method
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"`
}
@ -129,7 +132,9 @@ func (m *QueryBalanceResponse) GetAmount() uint64 {
// QueryOwnerRequest is the request type for the Query/Owner RPC method
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"`
// id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
}
@ -182,6 +187,7 @@ func (m *QueryOwnerRequest) GetId() string {
// QueryOwnerResponse is the response type for the Query/Owner RPC method
type QueryOwnerResponse struct {
// owner is the owner address of the nft
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
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"`
}
@ -272,6 +279,7 @@ func (m *QuerySupplyRequest) GetClassId() string {
// QuerySupplyResponse is the response type for the Query/Supply RPC method
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"`
}
@ -317,8 +325,11 @@ func (m *QuerySupplyResponse) GetAmount() uint64 {
// QueryNFTstRequest is the request type for the Query/NFTs RPC method
type QueryNFTsRequest struct {
// class_id associated with the nft
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"`
}
@ -378,7 +389,9 @@ func (m *QueryNFTsRequest) GetPagination() *query.PageRequest {
// QueryNFTsResponse is the response type for the Query/NFTs RPC methods
type QueryNFTsResponse struct {
// 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"`
}
@ -431,7 +444,9 @@ func (m *QueryNFTsResponse) GetPagination() *query.PageResponse {
// QueryNFTRequest is the request type for the Query/NFT RPC method
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"`
// id is a unique identifier of the NFT
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
}
@ -484,6 +499,7 @@ func (m *QueryNFTRequest) GetId() string {
// QueryNFTResponse is the response type for the Query/NFT RPC method
type QueryNFTResponse struct {
// owner is the owner address of the nft
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
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"`
}
@ -574,6 +591,7 @@ func (m *QueryClassRequest) GetClassId() string {
// QueryClassResponse is the response type for the Query/Class RPC method
type QueryClassResponse struct {
// class defines the class of the nft type.
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
type QueryClassesResponse struct {
// 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"`
}

View File

@ -11,7 +11,7 @@ import (
"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.
func NewDecodeStore(cdc codec.Codec) 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) {
c, err := randClass(ctx, r, k)
if err != nil {
@ -141,6 +142,7 @@ func randNFT(ctx sdk.Context, r *rand.Rand, k keeper.Keeper, minter sdk.AccAddre
return n, nil
}
// randClass picks a random Class.
func randClass(ctx sdk.Context, r *rand.Rand, k keeper.Keeper) (nft.Class, error) {
classes := k.GetClasses(ctx)
if len(classes) == 0 {