From 930802e7a13c9e223bf27f35b42481811bf7f02f Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Thu, 30 Apr 2020 11:07:43 -0400 Subject: [PATCH] x/capability: simulations (#6062) * x/capability: simulations * update logs * validate genesis state * InitGenesis and ExportGenesis functions * update validation func * fix import-export sim * remove nondeterminism from capability genesis * Update x/capability/types/genesis.go * Update x/capability/types/genesis.go * fix tests * fix merge * consistency updates * try fix nondeterminism * fix conditional * Fix random index logic * lint * lint Co-authored-by: Aditya Sripal Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Alexander Bezobchuk --- simapp/app.go | 3 +- simapp/sim_test.go | 5 +- x/capability/alias.go | 2 + x/capability/genesis.go | 41 ++++++++ x/capability/keeper/keeper.go | 65 +++++++++--- x/capability/module.go | 65 +++++++++--- x/capability/simulation/decoder.go | 34 ++++++ x/capability/simulation/decoder_test.go | 59 +++++++++++ x/capability/simulation/genesis.go | 36 +++++++ x/capability/types/genesis.go | 56 +++++++++- x/capability/types/genesis_test.go | 131 ++++++++++++++++++++++++ x/ibc/20-transfer/genesis.go | 15 ++- x/ibc/20-transfer/keeper/keeper.go | 6 ++ 13 files changed, 477 insertions(+), 41 deletions(-) create mode 100644 x/capability/genesis.go create mode 100644 x/capability/simulation/decoder.go create mode 100644 x/capability/simulation/decoder_test.go create mode 100644 x/capability/simulation/genesis.go create mode 100644 x/capability/types/genesis_test.go diff --git a/simapp/app.go b/simapp/app.go index 0f77240daa..0d44f006f1 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -262,7 +262,7 @@ func NewSimApp( genutil.NewAppModule(app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx), auth.NewAppModule(appCodec, app.AccountKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), - capability.NewAppModule(*app.CapabilityKeeper), + capability.NewAppModule(appCodec, *app.CapabilityKeeper), crisis.NewAppModule(&app.CrisisKeeper), gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper), @@ -307,6 +307,7 @@ func NewSimApp( app.sm = module.NewSimulationManager( auth.NewAppModule(appCodec, app.AccountKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), + capability.NewAppModule(appCodec, *app.CapabilityKeeper), gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 6e12ed7d37..ef441193b6 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -19,6 +19,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/capability" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/evidence" "github.com/cosmos/cosmos-sdk/x/gov" @@ -158,6 +159,7 @@ func TestAppImportExport(t *testing.T) { {app.keys[paramtypes.StoreKey], newApp.keys[paramtypes.StoreKey], [][]byte{}}, {app.keys[gov.StoreKey], newApp.keys[gov.StoreKey], [][]byte{}}, {app.keys[evidence.StoreKey], newApp.keys[evidence.StoreKey], [][]byte{}}, + {app.keys[capability.StoreKey], newApp.keys[capability.StoreKey], [][]byte{}}, } for _, skp := range storeKeysPrefixes { @@ -268,7 +270,6 @@ func TestAppStateDeterminism(t *testing.T) { } db := dbm.NewMemDB() - app := NewSimApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, FlagPeriodValue, interBlockCacheOpt()) fmt.Printf( @@ -292,7 +293,7 @@ func TestAppStateDeterminism(t *testing.T) { if j != 0 { require.Equal( - t, appHashList[0], appHashList[j], + t, string(appHashList[0]), string(appHashList[j]), "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, ) } diff --git a/x/capability/alias.go b/x/capability/alias.go index bb753342a3..c21ca73cf9 100644 --- a/x/capability/alias.go +++ b/x/capability/alias.go @@ -37,4 +37,6 @@ type ( ScopedKeeper = keeper.ScopedKeeper Capability = types.Capability CapabilityOwners = types.CapabilityOwners + GenesisState = types.GenesisState + GenesisOwners = types.GenesisOwners ) diff --git a/x/capability/genesis.go b/x/capability/genesis.go new file mode 100644 index 0000000000..b25c2102e4 --- /dev/null +++ b/x/capability/genesis.go @@ -0,0 +1,41 @@ +package capability + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the capability module's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k Keeper, genState GenesisState) { + k.SetIndex(ctx, genState.Index) + + // set owners for each index and initialize capability + for _, genOwner := range genState.Owners { + k.SetOwners(ctx, genOwner.Index, genOwner.Owners) + k.InitializeCapability(ctx, genOwner.Index, genOwner.Owners) + } +} + +// ExportGenesis returns the capability module's exported genesis. +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + index := k.GetLatestIndex(ctx) + owners := []GenesisOwners{} + + for i := uint64(1); i < index; i++ { + capabilityOwners, ok := k.GetOwners(ctx, i) + if !ok || len(capabilityOwners.Owners) == 0 { + continue + } + + genOwner := GenesisOwners{ + Index: i, + Owners: capabilityOwners, + } + owners = append(owners, genOwner) + } + + return GenesisState{ + Index: index, + Owners: owners, + } +} diff --git a/x/capability/keeper/keeper.go b/x/capability/keeper/keeper.go index 138f500e4e..ce3c50611b 100644 --- a/x/capability/keeper/keeper.go +++ b/x/capability/keeper/keeper.go @@ -106,25 +106,10 @@ func (k *Keeper) InitializeAndSeal(ctx sdk.Context) { defer iterator.Close() for ; iterator.Valid(); iterator.Next() { index := types.IndexFromKey(iterator.Key()) - cap := types.NewCapability(index) var capOwners types.CapabilityOwners k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &capOwners) - - for _, owner := range capOwners.Owners { - // Set the forward mapping between the module and capability tuple and the - // capability name in the memKVStore - memStore.Set(types.FwdCapabilityKey(owner.Module, cap), []byte(owner.Name)) - - // Set the reverse mapping between the module and capability name and the - // index in the in-memory store. Since marshalling and unmarshalling into a store - // will change memory address of capability, we simply store index as value here - // and retrieve the in-memory pointer to the capability from our map - memStore.Set(types.RevCapabilityKey(owner.Module, owner.Name), sdk.Uint64ToBigEndian(index)) - - // Set the mapping from index from index to in-memory capability in the go map - k.capMap[index] = cap - } + k.InitializeCapability(ctx, index, capOwners) } k.sealed = true @@ -144,6 +129,54 @@ func (k Keeper) GetLatestIndex(ctx sdk.Context) uint64 { return types.IndexFromKey(store.Get(types.KeyIndex)) } +// SetOwners set the capability owners to the store +func (k Keeper) SetOwners(ctx sdk.Context, index uint64, owners types.CapabilityOwners) { + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixIndexCapability) + indexKey := types.IndexToKey(index) + + // set owners in persistent store + prefixStore.Set(indexKey, k.cdc.MustMarshalBinaryBare(&owners)) +} + +// GetOwners returns the capability owners with a given index. +func (k Keeper) GetOwners(ctx sdk.Context, index uint64) (types.CapabilityOwners, bool) { + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixIndexCapability) + indexKey := types.IndexToKey(index) + + // get owners for index from persistent store + ownerBytes := prefixStore.Get(indexKey) + if ownerBytes == nil { + return types.CapabilityOwners{}, false + } + var owners types.CapabilityOwners + k.cdc.MustUnmarshalBinaryBare(ownerBytes, &owners) + return owners, true +} + +// InitializeCapability takes in an index and an owners array. It creates the capability in memory +// and sets the fwd and reverse keys for each owner in the memstore +func (k Keeper) InitializeCapability(ctx sdk.Context, index uint64, owners types.CapabilityOwners) { + + memStore := ctx.KVStore(k.memKey) + + cap := types.NewCapability(index) + for _, owner := range owners.Owners { + // Set the forward mapping between the module and capability tuple and the + // capability name in the memKVStore + memStore.Set(types.FwdCapabilityKey(owner.Module, cap), []byte(owner.Name)) + + // Set the reverse mapping between the module and capability name and the + // index in the in-memory store. Since marshalling and unmarshalling into a store + // will change memory address of capability, we simply store index as value here + // and retrieve the in-memory pointer to the capability from our map + memStore.Set(types.RevCapabilityKey(owner.Module, owner.Name), sdk.Uint64ToBigEndian(index)) + + // Set the mapping from index from index to in-memory capability in the go map + k.capMap[index] = cap + } + +} + // NewCapability attempts to create a new capability with a given name. If the // capability already exists in the in-memory store, an error will be returned. // Otherwise, a new capability is created with the current global unique index. diff --git a/x/capability/module.go b/x/capability/module.go index 3c8f2117aa..d0b46ab5ab 100644 --- a/x/capability/module.go +++ b/x/capability/module.go @@ -2,11 +2,15 @@ package capability import ( "encoding/json" + "fmt" + "math/rand" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/capability/simulation" "github.com/cosmos/cosmos-sdk/x/capability/types" "github.com/gorilla/mux" @@ -15,11 +19,9 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} - - // TODO: Enable simulation once concrete types are defined. - // _ module.AppModuleSimulation = AppModuleSimulation{} + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} ) // ---------------------------------------------------------------------------- @@ -28,10 +30,11 @@ var ( // AppModuleBasic implements the AppModuleBasic interface for the capability module. type AppModuleBasic struct { + cdc codec.Marshaler } -func NewAppModuleBasic() AppModuleBasic { - return AppModuleBasic{} +func NewAppModuleBasic(cdc codec.Marshaler) AppModuleBasic { + return AppModuleBasic{cdc: cdc} } // Name returns the capability module's name. @@ -50,7 +53,13 @@ func (AppModuleBasic) DefaultGenesis(cdc codec.JSONMarshaler) json.RawMessage { } // ValidateGenesis performs genesis state validation for the capability module. -func (AppModuleBasic) ValidateGenesis(_ codec.JSONMarshaler, _ json.RawMessage) error { return nil } +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", ModuleName, err) + } + return genState.Validate() +} // RegisterRESTRoutes registers the capability module's REST service handlers. func (a AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) {} @@ -58,7 +67,7 @@ func (a AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) // GetTxCmd returns the capability module's root tx command. func (a AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } -// GetTxCmd returns the capability module's root query command. +// GetQueryCmd returns the capability module's root query command. func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command { return nil } // ---------------------------------------------------------------------------- @@ -72,9 +81,9 @@ type AppModule struct { keeper Keeper } -func NewAppModule(keeper Keeper) AppModule { +func NewAppModule(cdc codec.Marshaler, keeper Keeper) AppModule { return AppModule{ - AppModuleBasic: NewAppModuleBasic(), + AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, } } @@ -102,19 +111,18 @@ func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the capability module's genesis initialization It returns // no validator updates. func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, gs json.RawMessage) []abci.ValidatorUpdate { - var genState types.GenesisState + var genState GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) - am.keeper.SetIndex(ctx, genState.Index) - + InitGenesis(ctx, am.keeper, genState) return []abci.ValidatorUpdate{} } // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage { - index := am.keeper.GetLatestIndex(ctx) - return cdc.MustMarshalJSON(types.GenesisState{index}) + genState := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(genState) } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. @@ -125,3 +133,28 @@ func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +// GenerateGenesisState creates a randomized GenState of the capability module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents performs a no-op +func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { + return nil +} + +// RandomizedParams creates randomized capability param changes for the simulator. +func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { + return nil +} + +// RegisterStoreDecoder registers a decoder for capability module's types +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return nil +} diff --git a/x/capability/simulation/decoder.go b/x/capability/simulation/decoder.go new file mode 100644 index 0000000000..8980efd570 --- /dev/null +++ b/x/capability/simulation/decoder.go @@ -0,0 +1,34 @@ +package simulation + +import ( + "bytes" + "fmt" + + tmkv "github.com/tendermint/tendermint/libs/kv" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding capaility type. +func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB tmkv.Pair) string { + return func(kvA, kvB tmkv.Pair) string { + switch { + case bytes.Equal(kvA.Key, types.KeyIndex): + idxA := sdk.BigEndianToUint64(kvA.Value) + idxB := sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("Index A: %d\nIndex B: %d\n", idxA, idxB) + + case bytes.HasPrefix(kvA.Key, types.KeyPrefixIndexCapability): + var capOwnersA, capOwnersB types.CapabilityOwners + cdc.MustUnmarshalBinaryBare(kvA.Value, &capOwnersA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &capOwnersB) + return fmt.Sprintf("CapabilityOwners A: %v\nCapabilityOwners B: %v\n", capOwnersA, capOwnersB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X (%s)", types.ModuleName, kvA.Key, string(kvA.Key))) + } + } +} diff --git a/x/capability/simulation/decoder_test.go b/x/capability/simulation/decoder_test.go new file mode 100644 index 0000000000..72130b59ad --- /dev/null +++ b/x/capability/simulation/decoder_test.go @@ -0,0 +1,59 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + tmkv "github.com/tendermint/tendermint/libs/kv" + + "github.com/cosmos/cosmos-sdk/simapp" + codecstd "github.com/cosmos/cosmos-sdk/std" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/capability/simulation" + "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +func TestDecodeStore(t *testing.T) { + cdc := codecstd.NewAppCodec(codecstd.MakeCodec(simapp.ModuleBasics)) + dec := simulation.NewDecodeStore(cdc) + + capOwners := types.CapabilityOwners{ + Owners: []types.Owner{{Module: "transfer", Name: "ports/transfer"}}, + } + + kvPairs := tmkv.Pairs{ + tmkv.Pair{ + Key: types.KeyIndex, + Value: sdk.Uint64ToBigEndian(10), + }, + tmkv.Pair{ + Key: types.KeyPrefixIndexCapability, + Value: cdc.MustMarshalBinaryBare(&capOwners), + }, + tmkv.Pair{ + Key: []byte{0x99}, + Value: []byte{0x99}, + }, + } + tests := []struct { + name string + expectedLog string + }{ + {"Index", "Index A: 10\nIndex B: 10\n"}, + {"CapabilityOwners", fmt.Sprintf("CapabilityOwners A: %v\nCapabilityOwners B: %v\n", capOwners, capOwners)}, + {"other", ""}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} diff --git a/x/capability/simulation/genesis.go b/x/capability/simulation/genesis.go new file mode 100644 index 0000000000..9a8dee5f5b --- /dev/null +++ b/x/capability/simulation/genesis.go @@ -0,0 +1,36 @@ +package simulation + +// DONTCOVER + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +// Simulation parameter constants +const index = "index" + +// GenIndex returns a random global index between 1-1000 +func GenIndex(r *rand.Rand) uint64 { + return uint64(r.Int63n(1000)) + 1 +} + +// RandomizedGenState generates a random GenesisState for capability +func RandomizedGenState(simState *module.SimulationState) { + var idx uint64 + + simState.AppParams.GetOrGenerate( + simState.Cdc, index, &idx, simState.Rand, + func(r *rand.Rand) { idx = GenIndex(r) }, + ) + + capabilityGenesis := types.GenesisState{Index: idx} + + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, capabilityGenesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(capabilityGenesis) +} diff --git a/x/capability/types/genesis.go b/x/capability/types/genesis.go index 85c7f41626..bc5d2cb7cb 100644 --- a/x/capability/types/genesis.go +++ b/x/capability/types/genesis.go @@ -1,11 +1,65 @@ package types +import ( + "fmt" + "strings" +) + +// DefaultIndex is the default capability global index +const DefaultIndex uint64 = 1 + +// GenesisOwners defines the capability owners with their corresponding index. +type GenesisOwners struct { + Index uint64 `json:"index" yaml:"index"` + Owners CapabilityOwners `json:"index_owners" yaml:"index_owners"` +} + // GenesisState represents the Capability module genesis state type GenesisState struct { + // capability global index Index uint64 `json:"index" yaml:"index"` + + // map from index to owners of the capability index + // index key is string to allow amino marshalling + Owners []GenesisOwners `json:"owners" yaml:"owners"` } // DefaultGenesis returns the default Capability genesis state func DefaultGenesis() GenesisState { - return GenesisState{Index: 1} + return GenesisState{ + Index: DefaultIndex, + Owners: []GenesisOwners{}, + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + // NOTE: index must be greater than 0 + if gs.Index == 0 { + return fmt.Errorf("capability index must be non-zero") + } + + for _, genOwner := range gs.Owners { + if len(genOwner.Owners.Owners) == 0 { + return fmt.Errorf("empty owners in genesis") + } + + // all exported existing indices must be between [1, gs.Index) + if genOwner.Index == 0 || genOwner.Index >= gs.Index { + return fmt.Errorf("owners exist for index %d outside of valid range: %d-%d", genOwner.Index, 1, gs.Index-1) + } + + for _, owner := range genOwner.Owners.Owners { + if strings.TrimSpace(owner.Module) == "" { + return fmt.Errorf("owner's module cannot be blank: %s", owner) + } + + if strings.TrimSpace(owner.Name) == "" { + return fmt.Errorf("owner's name cannot be blank: %s", owner) + } + } + } + + return nil } diff --git a/x/capability/types/genesis_test.go b/x/capability/types/genesis_test.go new file mode 100644 index 0000000000..5741de1ac2 --- /dev/null +++ b/x/capability/types/genesis_test.go @@ -0,0 +1,131 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateGenesis(t *testing.T) { + testCases := []struct { + name string + malleate func(*GenesisState) + expPass bool + }{ + { + name: "default", + malleate: func(_ *GenesisState) {}, + expPass: true, + }, + { + name: "valid genesis state", + malleate: func(genState *GenesisState) { + genState.Index = 10 + genOwner := GenesisOwners{ + Index: 1, + Owners: CapabilityOwners{[]Owner{{Module: "ibc", Name: "port/transfer"}}}, + } + + genState.Owners = append(genState.Owners, genOwner) + }, + expPass: true, + }, + { + name: "initial index is 0", + malleate: func(genState *GenesisState) { + genState.Index = 0 + genOwner := GenesisOwners{ + Index: 0, + Owners: CapabilityOwners{[]Owner{{Module: "ibc", Name: "port/transfer"}}}, + } + + genState.Owners = append(genState.Owners, genOwner) + + }, + expPass: false, + }, + + { + name: "blank owner module", + malleate: func(genState *GenesisState) { + genState.Index = 1 + genOwner := GenesisOwners{ + Index: 1, + Owners: CapabilityOwners{[]Owner{{Module: "", Name: "port/transfer"}}}, + } + + genState.Owners = append(genState.Owners, genOwner) + + }, + expPass: false, + }, + { + name: "blank owner name", + malleate: func(genState *GenesisState) { + genState.Index = 1 + genOwner := GenesisOwners{ + Index: 1, + Owners: CapabilityOwners{[]Owner{{Module: "ibc", Name: ""}}}, + } + + genState.Owners = append(genState.Owners, genOwner) + + }, + expPass: false, + }, + { + name: "index above range", + malleate: func(genState *GenesisState) { + genState.Index = 10 + genOwner := GenesisOwners{ + Index: 12, + Owners: CapabilityOwners{[]Owner{{Module: "ibc", Name: "port/transfer"}}}, + } + + genState.Owners = append(genState.Owners, genOwner) + + }, + expPass: false, + }, + { + name: "index below range", + malleate: func(genState *GenesisState) { + genState.Index = 10 + genOwner := GenesisOwners{ + Index: 0, + Owners: CapabilityOwners{[]Owner{{Module: "ibc", Name: "port/transfer"}}}, + } + + genState.Owners = append(genState.Owners, genOwner) + + }, + expPass: false, + }, + { + name: "owners are empty", + malleate: func(genState *GenesisState) { + genState.Index = 10 + genOwner := GenesisOwners{ + Index: 0, + Owners: CapabilityOwners{[]Owner{}}, + } + + genState.Owners = append(genState.Owners, genOwner) + + }, + expPass: false, + }, + } + + for _, tc := range testCases { + tc := tc + genState := DefaultGenesis() + tc.malleate(&genState) + err := genState.Validate() + if tc.expPass { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} diff --git a/x/ibc/20-transfer/genesis.go b/x/ibc/20-transfer/genesis.go index c91e2acf93..659a9428f5 100644 --- a/x/ibc/20-transfer/genesis.go +++ b/x/ibc/20-transfer/genesis.go @@ -9,12 +9,17 @@ import ( // InitGenesis binds to portid from genesis state func InitGenesis(ctx sdk.Context, keeper Keeper, state types.GenesisState) { - // transfer module binds to the transfer port on InitChain - // and claims the returned capability - err := keeper.BindPort(ctx, state.PortID) - if err != nil { - panic(fmt.Sprintf("could not claim port capability: %v", err)) + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !keeper.IsBound(ctx, state.PortID) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := keeper.BindPort(ctx, state.PortID) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } } + // check if the module account exists moduleAcc := keeper.GetTransferAccount(ctx) if moduleAcc == nil { diff --git a/x/ibc/20-transfer/keeper/keeper.go b/x/ibc/20-transfer/keeper/keeper.go index 6cb3ed838f..4ec6f06f3d 100644 --- a/x/ibc/20-transfer/keeper/keeper.go +++ b/x/ibc/20-transfer/keeper/keeper.go @@ -94,6 +94,12 @@ func (k Keeper) ChanCloseInit(ctx sdk.Context, portID, channelID string) error { return k.channelKeeper.ChanCloseInit(ctx, portID, channelID, chanCap) } +// IsBound checks if the transfer module is already bound to the desired port +func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, porttypes.PortPath(portID)) + return ok +} + // BindPort defines a wrapper function for the ort Keeper's function in // order to expose it to module's InitGenesis function func (k Keeper) BindPort(ctx sdk.Context, portID string) error {