Skip to content

Commit

Permalink
feat: add simulation for nft module (cosmos#10522)
Browse files Browse the repository at this point in the history
<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## Description

Add nft module simulation and unit test,refer cosmos#9826

<!-- Add a description of the changes that this PR introduces and the files that
are the most critical to review. -->

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
Zhiqiang Zhang authored Nov 11, 2021
1 parent b6d416d commit ba843a3
Show file tree
Hide file tree
Showing 8 changed files with 555 additions and 2 deletions.
1 change: 1 addition & 0 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ func NewSimApp(
params.NewAppModule(app.ParamsKeeper),
evidence.NewAppModule(app.EvidenceKeeper),
authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry),
nftmodule.NewAppModule(appCodec, app.NFTKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry),
)

app.sm.RegisterStoreDecoders()
Expand Down
42 changes: 40 additions & 2 deletions x/nft/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package nft
import (
"context"
"encoding/json"
"math/rand"

"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
Expand All @@ -15,15 +16,18 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"

"github.com/cosmos/cosmos-sdk/x/nft"
"github.com/cosmos/cosmos-sdk/x/nft/client/cli"
"github.com/cosmos/cosmos-sdk/x/nft/keeper"
"github.com/cosmos/cosmos-sdk/x/nft/simulation"
)

var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
_ module.AppModuleSimulation = AppModule{}
)

// AppModuleBasic defines the basic application module used by the nft module.
Expand Down Expand Up @@ -159,3 +163,37 @@ func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {}
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}

// ____________________________________________________________________________

// AppModuleSimulation functions

// GenerateGenesisState creates a randomized GenState of the nft module.
func (AppModule) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}

// ProposalContents returns all the nft content functions used to
// simulate governance proposals.
func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent {
return nil
}

// RandomizedParams creates randomized nft param changes for the simulator.
func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange {
return nil
}

// RegisterStoreDecoder registers a decoder for nft module's types
func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
sdr[keeper.StoreKey] = simulation.NewDecodeStore(am.cdc)
}

// WeightedOperations returns the all the nft module operations with their respective weights.
func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation {
return simulation.WeightedOperations(
am.registry,
simState.AppParams, simState.Cdc,
am.accountKeeper, am.bankKeeper, am.keeper,
)
}
45 changes: 45 additions & 0 deletions x/nft/simulation/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package simulation

import (
"bytes"
"fmt"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/cosmos/cosmos-sdk/x/nft"
"github.com/cosmos/cosmos-sdk/x/nft/keeper"
)

// NewDecodeStore returns a decoder function closure that umarshals 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 {
switch {
case bytes.Equal(kvA.Key[:1], keeper.ClassKey):
var classA, classB nft.Class
cdc.MustUnmarshal(kvA.Value, &classA)
cdc.MustUnmarshal(kvB.Value, &classB)
return fmt.Sprintf("%v\n%v", classA, classB)
case bytes.Equal(kvA.Key[:1], keeper.NFTKey):
var nftA, nftB nft.NFT
cdc.MustUnmarshal(kvA.Value, &nftA)
cdc.MustUnmarshal(kvB.Value, &nftB)
return fmt.Sprintf("%v\n%v", nftA, nftB)
case bytes.Equal(kvA.Key[:1], keeper.NFTOfClassByOwnerKey):
return fmt.Sprintf("%v\n%v", kvA.Value, kvB.Value)
case bytes.Equal(kvA.Key[:1], keeper.OwnerKey):
var ownerA, ownerB sdk.AccAddress
ownerA = sdk.AccAddress(kvA.Value)
ownerB = sdk.AccAddress(kvB.Value)
return fmt.Sprintf("%v\n%v", ownerA, ownerB)
case bytes.Equal(kvA.Key[:1], keeper.ClassTotalSupply):
var supplyA, supplyB uint64
supplyA = sdk.BigEndianToUint64(kvA.Value)
supplyB = sdk.BigEndianToUint64(kvB.Value)
return fmt.Sprintf("%v\n%v", supplyA, supplyB)
default:
panic(fmt.Sprintf("invalid nft key %X", kvA.Key))
}
}
}
84 changes: 84 additions & 0 deletions x/nft/simulation/decoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package simulation_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/cosmos/cosmos-sdk/x/nft"
"github.com/cosmos/cosmos-sdk/x/nft/keeper"
"github.com/cosmos/cosmos-sdk/x/nft/simulation"
)

var (
ownerPk1 = ed25519.GenPrivKey().PubKey()
ownerAddr1 = sdk.AccAddress(ownerPk1.Address())
)

func TestDecodeStore(t *testing.T) {
cdc := simapp.MakeTestEncodingConfig().Codec
dec := simulation.NewDecodeStore(cdc)

class := nft.Class{
Id: "ClassID",
Name: "ClassName",
Symbol: "ClassSymbol",
Description: "ClassDescription",
Uri: "ClassURI",
}
classBz, err := cdc.Marshal(&class)
require.NoError(t, err)

nft := nft.NFT{
ClassId: "ClassID",
Id: "NFTID",
Uri: "NFTURI",
}
nftBz, err := cdc.Marshal(&nft)
require.NoError(t, err)

nftOfClassByOwnerValue := []byte{0x01}

totalSupply := 1
totalSupplyBz := sdk.Uint64ToBigEndian(1)

kvPairs := kv.Pairs{
Pairs: []kv.Pair{
{Key: []byte(keeper.ClassKey), Value: classBz},
{Key: []byte(keeper.NFTKey), Value: nftBz},
{Key: []byte(keeper.NFTOfClassByOwnerKey), Value: nftOfClassByOwnerValue},
{Key: []byte(keeper.OwnerKey), Value: ownerAddr1},
{Key: []byte(keeper.ClassTotalSupply), Value: totalSupplyBz},
{Key: []byte{0x99}, Value: []byte{0x99}},
},
}

tests := []struct {
name string
expectErr bool
expectedLog string
}{
{"Class", false, fmt.Sprintf("%v\n%v", class, class)},
{"NFT", false, fmt.Sprintf("%v\n%v", nft, nft)},
{"NFTOfClassByOwnerKey", false, fmt.Sprintf("%v\n%v", nftOfClassByOwnerValue, nftOfClassByOwnerValue)},
{"OwnerKey", false, fmt.Sprintf("%v\n%v", ownerAddr1, ownerAddr1)},
{"ClassTotalSupply", false, fmt.Sprintf("%v\n%v", totalSupply, totalSupply)},
{"other", true, ""},
}

for i, tt := range tests {
i, tt := i, tt
t.Run(tt.name, func(t *testing.T) {
if tt.expectErr {
require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name)
} else {
require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name)
}
})
}
}
67 changes: 67 additions & 0 deletions x/nft/simulation/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package simulation

import (
"math/rand"

"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/nft"
)

// genClasses returns a slice of nft class.
func genClasses(r *rand.Rand, accounts []simtypes.Account) []*nft.Class {
classes := make([]*nft.Class, len(accounts)-1)
for i := 0; i < len(accounts)-1; i++ {
classes[i] = &nft.Class{
Id: simtypes.RandStringOfLength(r, 10),
Name: simtypes.RandStringOfLength(r, 10),
Symbol: simtypes.RandStringOfLength(r, 10),
Description: simtypes.RandStringOfLength(r, 10),
Uri: simtypes.RandStringOfLength(r, 10),
}
}
return classes
}

// genNFT returns a slice of nft.
func genNFT(r *rand.Rand, classID string, accounts []simtypes.Account) []*nft.Entry {
entries := make([]*nft.Entry, len(accounts)-1)
for i := 0; i < len(accounts)-1; i++ {
owner := accounts[i]
entries[i] = &nft.Entry{
Owner: owner.Address.String(),
Nfts: []*nft.NFT{
{
ClassId: classID,
Id: simtypes.RandStringOfLength(r, 10),
Uri: simtypes.RandStringOfLength(r, 10),
},
},
}
}
return entries
}

// RandomizedGenState generates a random GenesisState for nft.
func RandomizedGenState(simState *module.SimulationState) {
var classes []*nft.Class
simState.AppParams.GetOrGenerate(
simState.Cdc, "nft", &classes, simState.Rand,
func(r *rand.Rand) { classes = genClasses(r, simState.Accounts) },
)

var entries []*nft.Entry
simState.AppParams.GetOrGenerate(
simState.Cdc, "nft", &entries, simState.Rand,
func(r *rand.Rand) {
class := classes[r.Int63n(int64(len(classes)))]
entries = genNFT(r, class.Id, simState.Accounts)
},
)

nftGenesis := &nft.GenesisState{
Classes: classes,
Entries: entries,
}
simState.GenState[nft.ModuleName] = simState.Cdc.MustMarshalJSON(nftGenesis)
}
39 changes: 39 additions & 0 deletions x/nft/simulation/genesis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package simulation_test

import (
"encoding/json"
"math/rand"
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/nft"
"github.com/cosmos/cosmos-sdk/x/nft/simulation"
)

func TestRandomizedGenState(t *testing.T) {
app := simapp.Setup(t, false)

s := rand.NewSource(1)
r := rand.New(s)

simState := module.SimulationState{
AppParams: make(simtypes.AppParams),
Cdc: app.AppCodec(),
Rand: r,
NumBonded: 3,
Accounts: simtypes.RandomAccounts(r, 3),
InitialStake: 1000,
GenState: make(map[string]json.RawMessage),
}

simulation.RandomizedGenState(&simState)
var nftGenesis nft.GenesisState
simState.Cdc.MustUnmarshalJSON(simState.GenState[nft.ModuleName], &nftGenesis)

require.Len(t, nftGenesis.Classes, len(simState.Accounts)-1)
require.Len(t, nftGenesis.Entries, len(simState.Accounts)-1)
}
Loading

0 comments on commit ba843a3

Please sign in to comment.