Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added query to list burnt NFTs #521

Merged
merged 6 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions integration-tests/modules/assetnft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"
authztypes "github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal"
Expand Down Expand Up @@ -464,11 +465,13 @@ func TestAssetNFTBurn(t *testing.T) {
issuer := chain.GenAccount()

nftClient := nft.NewQueryClient(chain.ClientContext)
assetnftClient := assetnfttypes.NewQueryClient(chain.ClientContext)
chain.FundAccountsWithOptions(ctx, t, issuer, integrationtests.BalancesOptions{
Messages: []sdk.Msg{
&assetnfttypes.MsgIssueClass{},
&assetnfttypes.MsgMint{},
&assetnfttypes.MsgBurn{},
&assetnfttypes.MsgBurn{},
&assetnfttypes.MsgMint{},
&assetnfttypes.MsgMint{},
},
Expand Down Expand Up @@ -571,6 +574,46 @@ func TestAssetNFTBurn(t *testing.T) {
mintMsg,
)
requireT.NoError(err)

// burn the second NFT
msgBurn = &assetnfttypes.MsgBurn{
Sender: issuer.String(),
ClassID: classID,
ID: "id-1-2",
}
_, err = client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(msgBurn)),
msgBurn,
)
requireT.NoError(err)

// query burnt NFTs
burntListRes, err := assetnftClient.BurntNFTsInClass(ctx, &assetnfttypes.QueryBurntNFTsInClassRequest{
Pagination: &query.PageRequest{},
ClassId: mintMsg.ClassID,
})
requireT.NoError(err)
requireT.Len(burntListRes.NftIds, 2)

// test pagination works
burntListRes, err = assetnftClient.BurntNFTsInClass(ctx, &assetnfttypes.QueryBurntNFTsInClassRequest{
Pagination: &query.PageRequest{
Offset: 1,
},
ClassId: mintMsg.ClassID,
})
requireT.NoError(err)
requireT.Len(burntListRes.NftIds, 1)

// query is nft burnt
burntNft, err := assetnftClient.BurntNFT(ctx, &assetnfttypes.QueryBurntNFTRequest{
ClassId: mintMsg.ClassID,
NftId: "id-1",
})
requireT.NoError(err)
requireT.True(burntNft.Burnt)
}

// TestAssetNFTBurnFrozen tests that frozen NFT cannot be burnt.
Expand Down
29 changes: 29 additions & 0 deletions proto/coreum/asset/nft/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ service Query {
rpc WhitelistedAccountsForNFT (QueryWhitelistedAccountsForNFTRequest) returns (QueryWhitelistedAccountsForNFTResponse) {
option (google.api.http).get = "/coreum/asset/nft/v1/classes/{class_id}/nfts/{id}/whitelisted";
}

// BurntNFTsInClass checks if an nft if is in burnt NFTs list.
rpc BurntNFT (QueryBurntNFTRequest) returns (QueryBurntNFTResponse) {
option (google.api.http).get = "/coreum/asset/nft/v1/classes/{class_id}/burnt/{nft_id}";
}

// BurntNFTsInClass returns the list of burnt nfts in a class.
rpc BurntNFTsInClass (QueryBurntNFTsInClassRequest) returns (QueryBurntNFTsInClassResponse) {
option (google.api.http).get = "/coreum/asset/nft/v1/classes/{class_id}/burnt";
}
}

// QueryParamsRequest defines the request type for querying x/asset/nft parameters.
Expand Down Expand Up @@ -106,3 +116,22 @@ message QueryWhitelistedAccountsForNFTResponse {
cosmos.base.query.v1beta1.PageResponse pagination = 1;
repeated string accounts = 2;
}

message QueryBurntNFTRequest {
string class_id = 1;
string nft_id = 2;
}

message QueryBurntNFTResponse {
bool burnt = 1;
}

message QueryBurntNFTsInClassRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
string class_id = 2;
}

message QueryBurntNFTsInClassResponse {
cosmos.base.query.v1beta1.PageResponse pagination = 1;
repeated string nft_ids = 2;
}
59 changes: 59 additions & 0 deletions x/asset/nft/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func GetQueryCmd() *cobra.Command {
CmdQueryFrozen(),
CmdQueryWhitelisted(),
CmdQueryWhitelistedAccounts(),
CmdQueryBurnt(),
CmdQueryParams(),
)

Expand Down Expand Up @@ -285,3 +286,61 @@ $ %[1]s query %s params

return cmd
}

// CmdQueryBurnt return the CmdQueryBurnt cobra command.
func CmdQueryBurnt() *cobra.Command {
cmd := &cobra.Command{
Use: "burnt [class-id] [id]",
Args: cobra.RangeArgs(1, 2),
Short: "Query for the burnt NFTs",
Long: strings.TrimSpace(
fmt.Sprintf(`Query for the burnt NFTs in a class.

Example:
$ %s query %s burnt [class-id] [id]
$ %s query %s burnt [class-id]
`,
version.AppName, types.ModuleName,
version.AppName, types.ModuleName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)

classID := args[0]

if len(args) == 2 {
id := args[1]
res, err := queryClient.BurntNFT(cmd.Context(), &types.QueryBurntNFTRequest{
ClassId: classID,
NftId: id,
})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
}

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

res, err := queryClient.BurntNFTsInClass(cmd.Context(), &types.QueryBurntNFTsInClassRequest{
Pagination: pageReq,
ClassId: classID,
})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "burnt NFTs")

return cmd
}
16 changes: 16 additions & 0 deletions x/asset/nft/client/cli/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ func TestCmdTxBurn(t *testing.T) {
args = append(args, txValidator1Args(testNetwork)...)
_, err = clitestutil.ExecTestCLICmd(ctx, cli.CmdTxBurn(), args)
requireT.NoError(err)

args = []string{classID, "nft-1", "--output", "json"}
buf, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdQueryBurnt(), args)
requireT.NoError(err)

var resp types.QueryBurntNFTResponse
requireT.NoError(ctx.Codec.UnmarshalJSON(buf.Bytes(), &resp))
requireT.True(resp.Burnt)

args = []string{classID, "--output", "json"}
buf, err = clitestutil.ExecTestCLICmd(ctx, cli.CmdQueryBurnt(), args)
requireT.NoError(err)

var respList types.QueryBurntNFTsInClassResponse
requireT.NoError(ctx.Codec.UnmarshalJSON(buf.Bytes(), &respList))
requireT.Len(respList.NftIds, 1)
}

func TestCmdQueryParams(t *testing.T) {
Expand Down
27 changes: 27 additions & 0 deletions x/asset/nft/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type QueryKeeper interface {
IsFrozen(ctx sdk.Context, classID, nftID string) (bool, error)
IsWhitelisted(ctx sdk.Context, classID, nftID string, account sdk.AccAddress) (bool, error)
GetWhitelistedAccountsForNFT(ctx sdk.Context, classID, nftID string, q *query.PageRequest) ([]string, *query.PageResponse, error)
GetBurnt(ctx sdk.Context, classID string, q *query.PageRequest) (*query.PageResponse, []string, error)
IsBurnt(ctx sdk.Context, classID, nftID string) (bool, error)
}

// QueryService serves grpc query requests for assetsnft module.
Expand Down Expand Up @@ -104,3 +106,28 @@ func (qs QueryService) WhitelistedAccountsForNFT(ctx context.Context, req *types
Accounts: accounts,
}, err
}

// BurntNFT checks if an NFT is burnt or not.
func (qs QueryService) BurntNFT(ctx context.Context, req *types.QueryBurntNFTRequest) (*types.QueryBurntNFTResponse, error) {
isBurnt, err := qs.keeper.IsBurnt(sdk.UnwrapSDKContext(ctx), req.ClassId, req.NftId)
if err != nil {
return nil, err
}

return &types.QueryBurntNFTResponse{
Burnt: isBurnt,
}, nil
}

// BurntNFTsInClass returns the list of burnt NFTs in a class.
func (qs QueryService) BurntNFTsInClass(ctx context.Context, req *types.QueryBurntNFTsInClassRequest) (*types.QueryBurntNFTsInClassResponse, error) {
pageRes, list, err := qs.keeper.GetBurnt(sdk.UnwrapSDKContext(ctx), req.ClassId, req.Pagination)
if err != nil {
return nil, err
}

return &types.QueryBurntNFTsInClassResponse{
Pagination: pageRes,
NftIds: list,
}, nil
}
26 changes: 26 additions & 0 deletions x/asset/nft/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,32 @@ func (k Keeper) IsBurnt(ctx sdk.Context, classID, nftID string) (bool, error) {
return bytes.Equal(ctx.KVStore(k.storeKey).Get(key), asset.StoreTrue), nil
}

// GetBurnt return the list of burnt NFTs in class.
func (k Keeper) GetBurnt(ctx sdk.Context, classID string, q *query.PageRequest) (*query.PageResponse, []string, error) {
key, err := types.CreateClassBurningKey(classID)
if err != nil {
return nil, nil, err
}

nfts := []string{}
pageRes, err := query.Paginate(prefix.NewStore(ctx.KVStore(k.storeKey), key), q,
func(key, value []byte) error {
if !bytes.Equal(value, asset.StoreTrue) {
return errors.Errorf("value stored in burnt store is not %x, value %x", asset.StoreTrue, value)
}

nft := string(key[1:]) // the first byte contains the length prefix
nfts = append(nfts, nft)
return nil
},
)
if err != nil {
return nil, nil, err
}

return pageRes, nfts, nil
}

// SetBurnt marks the nft burnt, but does not make any checks
// should not be used directly outside the module except for genesis.
func (k Keeper) SetBurnt(ctx sdk.Context, classID, nftID string) error {
Expand Down
10 changes: 10 additions & 0 deletions x/asset/nft/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ func CreateBurningKey(classID, nftID string) ([]byte, error) {
return store.JoinKeys(NFTBurningKeyPrefix, compositeKey), nil
}

// CreateClassBurningKey constructs the key for the burning of non-fungible token.
func CreateClassBurningKey(classID string) ([]byte, error) {
compositeKey, err := store.JoinKeysWithLength([]byte(classID))
if err != nil {
return nil, err
}

return store.JoinKeys(NFTBurningKeyPrefix, compositeKey), nil
}

// ParseBurningKey parses burning key back to class id and nft id.
func ParseBurningKey(key []byte) (string, string, error) {
parsedKeys, err := store.ParseLengthPrefixedKeys(key)
Expand Down
Loading