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: add simulation for nft module #10522

Merged
merged 5 commits into from
Nov 11, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
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