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

feat: implement nft module msg server #10074

Merged
merged 23 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from 15 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
12 changes: 11 additions & 1 deletion docs/architecture/adr-043-nft-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ DRAFT

## Abstract

This ADR defines the `x/nft` module which is a generic implementation of NFTs, roughly "compatible" with ERC721.
This ADR defines the `x/nft` module which is a generic implementation of NFTs, roughly "compatible" with ERC721. **Applications using the `x/nft` module must implement the following functions**:

- `MsgNewClass` - Receive the user's request to create a class, and call the `NewClass` of the `x/nft` module.
- `MsgUpdateClass` - Receive the user's request to update a class, and call the `UpdateClass` of the `x/nft` module.
- `MsgMintNFT` - Receive the user's request to mint a nft, and call the `MintNFT` of the `x/nft` module.
- `BurnNFT` - Receive the user's request to burn a nft, and call the `BurnNFT` of the `x/nft` module.
- `UpdateNFT` - Receive the user's request to update a nft, and call the `UpdateNFT` of the `x/nft` module.

## Context

Expand Down Expand Up @@ -55,6 +61,7 @@ message Class {
string symbol = 3;
string description = 4;
string uri = 5;
string uri_hash = 6;
}
```

Expand All @@ -63,6 +70,7 @@ message Class {
- `symbol` is the symbol usually shown on exchanges for the NFT class; _optional_
- `description` is a detailed description of the NFT class; _optional_
- `uri` is a URL pointing to an off-chain JSON file that contains metadata about this NFT class ([OpenSea example](https://docs.opensea.io/docs/contract-level-metadata)); _optional_
- `uri_hash` is a hash of the `uri`; _optional_

#### NFT

Expand All @@ -73,6 +81,7 @@ message NFT {
string class_id = 1;
string id = 2;
string uri = 3;
string uri_hash = 4;
google.protobuf.Any data = 10;
}
```
Expand All @@ -83,6 +92,7 @@ message NFT {
{class_id}/{id} --> NFT (bytes)
```
- `uri` is a URL pointing to an off-chain JSON file that contains metadata about this NFT (Ref: [ERC721 standard and OpenSea extension](https://docs.opensea.io/docs/metadata-standards)); _required_
- `uri_hash` is a hash of the `uri`;
- `data` is a field that CAN be used by composing modules to specify additional properties for the NFT; _optional_

This ADR doesn't specify values that `data` can take; however, best practices recommend upper-level NFT modules clearly specify their contents. Although the value of this field doesn't provide the additional context required to manage NFT records, which means that the field can technically be removed from the specification, the field's existence allows basic informational/UI functionality.
Expand Down
12 changes: 6 additions & 6 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7258,11 +7258,11 @@ Class defines the class of the nft type.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `id` | [string](#string) | | id defines the unique identifier of the NFT classification, similar to the contract address of ERC721 |
| `name` | [string](#string) | | name defines the human-readable name of the NFT classification |
| `symbol` | [string](#string) | | symbol is an abbreviated name for nft classification |
| `description` | [string](#string) | | description is a brief description of nft classification |
| `uri` | [string](#string) | | uri is a URI may point to a JSON file that conforms to the nft classification Metadata JSON Schema. |
| `uri_hash` | [string](#string) | | uri_hash is a hash of the document pointed to uri |
| `name` | [string](#string) | | name defines the human-readable name of the NFT classification,optional |
| `symbol` | [string](#string) | | symbol is an abbreviated name for nft classification,optional |
| `description` | [string](#string) | | description is a brief description of nft classification,optional |
| `uri` | [string](#string) | | uri is a URI may point to a JSON file that conforms to the nft classification Metadata JSON Schema.optional |
| `uri_hash` | [string](#string) | | uri_hash is a hash of the document pointed to uri,optional |



Expand All @@ -7281,7 +7281,7 @@ NFT defines the NFT.
| `id` | [string](#string) | | id defines the unique identification of NFT |
| `uri` | [string](#string) | | uri defines NFT's metadata storage address outside the chain |
| `uri_hash` | [string](#string) | | uri_hash is a hash of the document pointed to uri |
| `data` | [google.protobuf.Any](#google.protobuf.Any) | | data is the metadata of the NFT |
| `data` | [google.protobuf.Any](#google.protobuf.Any) | | data is the metadata of the NFT,optional |



Expand Down
67 changes: 0 additions & 67 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,73 +54,6 @@ require (
sigs.k8s.io/yaml v1.3.0
)

require (
filippo.io/edwards25519 v1.0.0-beta.2 // indirect
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
github.com/DataDog/zstd v1.4.5 // indirect
github.com/Workiva/go-datastructures v1.0.52 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/cosmos/ledger-go v0.9.2 // indirect
github.com/danieljoos/wincred v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
github.com/dgraph-io/ristretto v0.0.3 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/gtank/merlin v0.1.1 // indirect
github.com/gtank/ristretto255 v0.1.2 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
github.com/klauspost/compress v1.11.7 // indirect
github.com/lib/pq v1.2.0 // indirect
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
github.com/minio/highwayhash v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/rs/cors v1.7.0 // indirect
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
github.com/zondax/hid v0.9.0 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
nhooyr.io/websocket v1.8.6 // indirect
)

// latest grpc doesn't work with with our modified proto compiler, so we need to enforce
// the following version across all dependencies.
replace google.golang.org/grpc => google.golang.org/grpc v1.33.2
Expand Down
12 changes: 6 additions & 6 deletions proto/cosmos/nft/v1beta1/nft.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ message Class {
// id defines the unique identifier of the NFT classification, similar to the contract address of ERC721
string id = 1;

// name defines the human-readable name of the NFT classification
// name defines the human-readable name of the NFT classification,optional
string name = 2;

// symbol is an abbreviated name for nft classification
// symbol is an abbreviated name for nft classification,optional
string symbol = 3;

// description is a brief description of nft classification
// description is a brief description of nft classification,optional
string description = 4;

// uri is a URI may point to a JSON file that conforms to the nft classification Metadata JSON Schema.
// uri is a URI may point to a JSON file that conforms to the nft classification Metadata JSON Schema.optional
string uri = 5;

// uri_hash is a hash of the document pointed to uri
// uri_hash is a hash of the document pointed to uri,optional
string uri_hash = 6;
}

Expand All @@ -40,6 +40,6 @@ message NFT {
// uri_hash is a hash of the document pointed to uri
string uri_hash = 4;

// data is the metadata of the NFT
// data is the metadata of the NFT,optional
google.protobuf.Any data = 10;
}
12 changes: 10 additions & 2 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ import (
"github.com/cosmos/cosmos-sdk/x/mint"
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/nft"
nftkeeper "github.com/cosmos/cosmos-sdk/x/nft/keeper"
nftmodule "github.com/cosmos/cosmos-sdk/x/nft/module"
"github.com/cosmos/cosmos-sdk/x/params"
paramsclient "github.com/cosmos/cosmos-sdk/x/params/client"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
Expand Down Expand Up @@ -116,6 +119,7 @@ var (
evidence.AppModuleBasic{},
authzmodule.AppModuleBasic{},
vesting.AppModuleBasic{},
nftmodule.AppModuleBasic{},
)

// module account permissions
Expand All @@ -126,6 +130,7 @@ var (
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
govtypes.ModuleName: {authtypes.Burner},
nft.ModuleName: nil,
}
)

Expand Down Expand Up @@ -167,6 +172,7 @@ type SimApp struct {
AuthzKeeper authzkeeper.Keeper
EvidenceKeeper evidencekeeper.Keeper
FeeGrantKeeper feegrantkeeper.Keeper
NFTKeeper nftkeeper.Keeper

// the module manager
mm *module.Manager
Expand Down Expand Up @@ -208,7 +214,7 @@ func NewSimApp(
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey,
evidencetypes.StoreKey, capabilitytypes.StoreKey,
authzkeeper.StoreKey,
authzkeeper.StoreKey, nftkeeper.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
// NOTE: The testingkey is just mounted for testing purposes. Actual applications should
Expand Down Expand Up @@ -290,6 +296,7 @@ func NewSimApp(
// register the governance hooks
),
)
app.NFTKeeper = nftkeeper.NewKeeper(keys[nftkeeper.StoreKey], appCodec, app.AccountKeeper, app.BankKeeper)

// create evidence keeper with router
evidenceKeeper := evidencekeeper.NewKeeper(
Expand Down Expand Up @@ -326,6 +333,7 @@ func NewSimApp(
evidence.NewAppModule(app.EvidenceKeeper),
params.NewAppModule(app.ParamsKeeper),
authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry),
nftmodule.NewAppModule(appCodec, app.NFTKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry),
)

// During begin block slashing happens after distr.BeginBlocker so that
Expand All @@ -348,7 +356,7 @@ func NewSimApp(
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName,
slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName,
genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName,
feegrant.ModuleName,
feegrant.ModuleName, nft.ModuleName,
)

app.mm.RegisterInvariants(&app.CrisisKeeper)
Expand Down
14 changes: 14 additions & 0 deletions x/nft/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nft

import (
types "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)

func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgSend{},
)
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}
15 changes: 15 additions & 0 deletions x/nft/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package nft

import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

// x/nft module sentinel errors
var (
ErrInvalidNFT = sdkerrors.Register(ModuleName, 2, "invalid nft")
ErrClassExists = sdkerrors.Register(ModuleName, 3, "nft class already exist")
ErrClassNotExists = sdkerrors.Register(ModuleName, 4, "nft class does not exist")
ErrNFTExists = sdkerrors.Register(ModuleName, 5, "nft already exist")
ErrNFTNotExists = sdkerrors.Register(ModuleName, 6, "nft does not exist")
ErrInvalidID = sdkerrors.Register(ModuleName, 7, "invalid id")
)
18 changes: 18 additions & 0 deletions x/nft/expected_keepers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package nft

import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)

// BankKeeper defines the contract needed to be fulfilled for banking and supply
// dependencies.
type BankKeeper interface {
SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
}

// AccountKeeper defines the contract required for account APIs.
type AccountKeeper interface {
GetModuleAddress(name string) sdk.AccAddress
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to depend on Query proto services rather than keepers directly, but I think we will firstly need to clean other parts of SDK before requesting that changes (and finalize adr-33).

30 changes: 30 additions & 0 deletions x/nft/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nft

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// ValidateGenesis check the given genesis state has no integrity issues
func ValidateGenesis(data GenesisState) error {
for _, class := range data.Classes {
if err := ValidateClassID(class.Id); err != nil {
return err
}
}
for _, entry := range data.Entries {
for _, nft := range entry.Nfts {
if err := ValidateNFTID(nft.Id); err != nil {
return err
}
if _, err := sdk.AccAddressFromBech32(entry.Owner); err != nil {
return err
}
}
}
return nil
}

// DefaultGenesisState - Return a default genesis state
func DefaultGenesisState() *GenesisState {
return &GenesisState{}
}
67 changes: 67 additions & 0 deletions x/nft/keeper/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/nft"
)

// SaveClass defines a method for creating a new nft class
func (k Keeper) SaveClass(ctx sdk.Context, class nft.Class) error {
if k.HasClass(ctx, class.Id) {
return sdkerrors.Wrap(nft.ErrClassExists, class.Id)
}
bz, err := k.cdc.Marshal(&class)
if err != nil {
return sdkerrors.Wrap(err, "Marshal nft.Class failed")
}
store := ctx.KVStore(k.storeKey)
store.Set(classStoreKey(class.Id), bz)
return nil
}

// UpdateClass defines a method for updating a 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)
}
bz, err := k.cdc.Marshal(&class)
if err != nil {
return sdkerrors.Wrap(err, "Marshal nft.Class failed")
}
store := ctx.KVStore(k.storeKey)
store.Set(classStoreKey(class.Id), bz)
return nil
}

// GetClass defines a method for returning the class information of the specified id
func (k Keeper) GetClass(ctx sdk.Context, classID string) (nft.Class, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(classStoreKey(classID))

var class nft.Class
if len(bz) == 0 {
return class, false
}
k.cdc.MustUnmarshal(bz, &class)
return class, true
}

// GetClasses defines a method for returning all classes information
func (k Keeper) GetClasses(ctx sdk.Context) (classes []*nft.Class) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ClassKey)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var class nft.Class
k.cdc.MustUnmarshal(iterator.Value(), &class)
classes = append(classes, &class)
}
return
}

// HasClass determines whether the specified classID exist
func (k Keeper) HasClass(ctx sdk.Context, classID string) bool {
store := ctx.KVStore(k.storeKey)
return store.Has(classStoreKey(classID))
}
Loading