diff --git a/simapp/app_di.go b/simapp/app_di.go index 3e8e010d0acb..7bb4a0ff3c4e 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -76,7 +76,7 @@ type SimApp struct { // supplementary keepers FeeGrantKeeper feegrantkeeper.Keeper AuthzKeeper authzkeeper.Keeper - EpochsKeeper epochskeeper.Keeper + EpochsKeeper *epochskeeper.Keeper ProtocolPoolKeeper protocolpoolkeeper.Keeper // simulation manager @@ -232,7 +232,12 @@ func NewSimApp( // NOTE: this is not required apps that don't use the simulator for fuzz testing // transactions overrideModules := map[string]module.AppModuleSimulation{ - authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil), + authtypes.ModuleName: auth.NewAppModule( + app.appCodec, + app.AccountKeeper, + authsims.RandomGenesisAccounts, + nil, + ), } app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) diff --git a/x/epochs/README.md b/x/epochs/README.md index d56970668c99..9a81b0b3cd12 100644 --- a/x/epochs/README.md +++ b/x/epochs/README.md @@ -95,6 +95,174 @@ func (k MyModuleKeeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, e } ``` +### Wiring Hooks + +**Manual Wiring:** + +Import the following: + +```go +import ( + // ... + "github.com/cosmos/cosmos-sdk/x/epochs" + epochskeeper "github.com/cosmos/cosmos-sdk/x/epochs/keeper" + epochstypes "github.com/cosmos/cosmos-sdk/x/epochs/types" +) +``` + +Add the epochs keeper to your application struct: + +```go +EpochsKeeper epochskeeper.Keeper +``` + +Add the store key: + +```go +keys := storetypes.NewKVStoreKeys( + // ... + epochstypes.StoreKey, +) +``` + +Instantiate the keeper: + +```go +app.EpochsKeeper = epochskeeper.NewKeeper( + runtime.NewKVStoreService(keys[epochstypes.StoreKey]), + appCodec, +) +``` + +Set up hooks for the epochs keeper: + +```go +app.EpochsKeeper.SetHooks( + epochstypes.NewMultiEpochHooks( + // insert epoch hooks receivers here + app.SomeOtherModule + ), +) +``` + +Add the epochs module to the module manager: + +```go +app.ModuleManager = module.NewManager( + // ... + epochs.NewAppModule(appCodec, app.EpochsKeeper), +) +``` + +Add entries for SetOrderBeginBlockers and SetOrderInitGenesis: + +```go +app.ModuleManager.SetOrderBeginBlockers( + // ... + epochstypes.ModuleName, +) +``` + +```go +app.ModuleManager.SetOrderInitGenesis( + // ... + epochstypes.ModuleName, +) +``` + +**DI Wiring:** + +First, set up the keeper for the application. + +Import the epochs keeper: + +```go +epochskeeper "github.com/cosmos/cosmos-sdk/x/epochs/keeper" +``` + +Add the keeper to your application struct: + +```go +EpochsKeeper *epochskeeper.Keeper // must be a pointer for DI +``` + +Add the keeper to the depinject system: + +```go +depinject.Inject( + appConfig, + &appBuilder, + &app.appCodec, + &app.legacyAmino, + &app.txConfig, + &app.interfaceRegistry, + // ... other modules + &app.EpochsKeeper, // NEW MODULE! +) +``` + +Next, set up configuration for the module. + +Import the following: + +```go +import ( + epochsmodulev1 "cosmossdk.io/api/cosmos/epochs/module/v1" + + _ "github.com/cosmos/cosmos-sdk/x/epochs" // import for side-effects + epochstypes "github.com/cosmos/cosmos-sdk/x/epochs/types" +) +``` + +Add an entry for BeginBlockers and InitGenesis: + +```go +BeginBlockers: []string{ + // ... + epochstypes.ModuleName, +}, +``` + +```go +InitGenesis: []string{ + // ... + epochstypes.ModuleName, +}, +``` + +Add an entry for epochs in the ModuleConfig: + +```go +{ + Name: epochstypes.ModuleName, + Config: appconfig.WrapAny(&epochsmodulev1.Module{}), +}, +``` + +depinject can automatically add your hooks to the epochs `Keeper`. For it do so, specify an output of your module with the type `epochtypes.EpochHooksWrapper`, ie: + +```go +type TestInputs struct { + depinject.In +} + +type TestOutputs struct { + depinject.Out + + Hooks types.EpochHooksWrapper +} + +func DummyProvider(in TestInputs) TestOutputs { + return TestOutputs{ + Hooks: types.EpochHooksWrapper{ + EpochHooks: testEpochHooks{}, + }, + } +} +``` + +for an example see [`depinject_test.go`](https://github.com/cosmos/cosmos-sdk/tree/main/x/epochs/depinject_test.go) + ### Panic isolation If a given epoch hook panics, its state update is reverted, but we keep diff --git a/x/epochs/depinject.go b/x/epochs/depinject.go index 7c4fd2a53f58..3b0d293a600a 100644 --- a/x/epochs/depinject.go +++ b/x/epochs/depinject.go @@ -39,14 +39,14 @@ type ModuleInputs struct { type ModuleOutputs struct { depinject.Out - EpochKeeper keeper.Keeper + EpochKeeper *keeper.Keeper Module appmodule.AppModule } func ProvideModule(in ModuleInputs) ModuleOutputs { k := keeper.NewKeeper(in.StoreService, in.Cdc) - m := NewAppModule(k) - return ModuleOutputs{EpochKeeper: k, Module: m} + m := NewAppModule(&k) + return ModuleOutputs{EpochKeeper: m.keeper, Module: m} } func InvokeSetHooks(keeper *keeper.Keeper, hooks map[string]types.EpochHooksWrapper) error { diff --git a/x/epochs/depinject_test.go b/x/epochs/depinject_test.go index ac67263a0d0a..952ef8ec278d 100644 --- a/x/epochs/depinject_test.go +++ b/x/epochs/depinject_test.go @@ -6,22 +6,41 @@ import ( "github.com/stretchr/testify/require" + appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" + bankmodulev1 "cosmossdk.io/api/cosmos/bank/module/v1" + "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/store" + "cosmossdk.io/depinject" + "cosmossdk.io/depinject/appconfig" storetypes "cosmossdk.io/store/types" + modulev1 "cosmossdk.io/api/cosmos/epochs/module/v1" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/types/module/testutil" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/epochs" "github.com/cosmos/cosmos-sdk/x/epochs/keeper" "github.com/cosmos/cosmos-sdk/x/epochs/types" ) +var _ types.EpochHooks = testEpochHooks{} + type testEpochHooks struct{} -func (h testEpochHooks) AfterEpochEnd(ctx context.Context, epochIdentifier string, epochNumber int64) error { +func (h testEpochHooks) AfterEpochEnd( + ctx context.Context, + epochIdentifier string, + epochNumber int64, +) error { return nil } -func (h testEpochHooks) BeforeEpochStart(ctx context.Context, epochIdentifier string, epochNumber int64) error { +func (h testEpochHooks) BeforeEpochStart( + ctx context.Context, + epochIdentifier string, + epochNumber int64, +) error { return nil } @@ -58,3 +77,88 @@ func TestInvokeSetHooks(t *testing.T) { require.Equal(t, hook1, multiHooks[0]) require.Equal(t, hook2, multiHooks[1]) } + +type TestInputs struct { + depinject.In +} + +type TestOutputs struct { + depinject.Out + + Hooks types.EpochHooksWrapper +} + +func DummyProvider(in TestInputs) TestOutputs { + return TestOutputs{ + Hooks: types.EpochHooksWrapper{ + EpochHooks: testEpochHooks{}, + }, + } +} + +func ProvideDeps(depinject.In) struct { + depinject.Out + Cdc codec.Codec + StoreService store.KVStoreService +} { + encCfg := testutil.MakeTestEncodingConfig() + + key := storetypes.NewKVStoreKey(types.StoreKey) + return struct { + depinject.Out + Cdc codec.Codec + StoreService store.KVStoreService + }{ + Cdc: encCfg.Codec, + StoreService: runtime.NewKVStoreService(key), + } +} + +func TestDepinject(t *testing.T) { + /// we just need any module's proto to register the provider here, no specific reason to use bank + appconfig.RegisterModule(&bankmodulev1.Module{}, appconfig.Provide(DummyProvider)) + var appModules map[string]appmodule.AppModule + keeper := new(keeper.Keeper) + require.NoError(t, + depinject.Inject( + depinject.Configs( + appconfig.Compose( + &appv1alpha1.Config{ + Modules: []*appv1alpha1.ModuleConfig{ + { + Name: banktypes.ModuleName, + Config: appconfig.WrapAny(&bankmodulev1.Module{}), + }, + { + Name: types.ModuleName, + Config: appconfig.WrapAny(&modulev1.Module{}), + }, + }, + }, + ), + depinject.Provide(ProvideDeps), + ), + &appModules, + &keeper, + ), + ) + + require.NotNil(t, keeper, "expected keeper to not be nil after depinject") + multihooks, ok := keeper.Hooks().(types.MultiEpochHooks) + require.True(t, ok, "expected keeper to have MultiEpochHooks after depinject") + require.Len(t, multihooks, 1, "expected MultiEpochHooks to have 1 element after depinject") + require.Equal( + t, + types.EpochHooksWrapper{EpochHooks: testEpochHooks{}}, + multihooks[0], + "expected the only hook in MultiEpochHooks to be the test hook", + ) + module, ok := appModules[types.ModuleName].(epochs.AppModule) + require.True(t, ok, "expected depinject to fill map with the epochs AppModule") + require.Equal( + t, + types.MultiEpochHooks{types.EpochHooksWrapper{EpochHooks: testEpochHooks{}}}, + module.Keeper().Hooks(), + ) + require.Same(t, keeper, module.Keeper()) // pointers pointing to the same instance +} diff --git a/x/epochs/export_test.go b/x/epochs/export_test.go new file mode 100644 index 000000000000..cb8a24599bd6 --- /dev/null +++ b/x/epochs/export_test.go @@ -0,0 +1,7 @@ +package epochs + +import "github.com/cosmos/cosmos-sdk/x/epochs/keeper" + +func (am AppModule) Keeper() *keeper.Keeper { + return am.keeper +} diff --git a/x/epochs/module.go b/x/epochs/module.go index a0c90009472a..c687ad67e51c 100644 --- a/x/epochs/module.go +++ b/x/epochs/module.go @@ -33,11 +33,11 @@ const ConsensusVersion = 1 // AppModule implements the AppModule interface for the epochs module. type AppModule struct { - keeper keeper.Keeper + keeper *keeper.Keeper } // NewAppModule creates a new AppModule object. -func NewAppModule(keeper keeper.Keeper) AppModule { +func NewAppModule(keeper *keeper.Keeper) AppModule { return AppModule{ keeper: keeper, } @@ -66,7 +66,7 @@ func (AppModule) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *gwrunt // RegisterServices registers module services. func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { - types.RegisterQueryServer(registrar, keeper.NewQuerier(am.keeper)) + types.RegisterQueryServer(registrar, keeper.NewQuerier(*am.keeper)) return nil } @@ -80,7 +80,11 @@ func (am AppModule) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { } // ValidateGenesis performs genesis state validation for the epochs module. -func (am AppModule) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { +func (am AppModule) ValidateGenesis( + cdc codec.JSONCodec, + _ client.TxEncodingConfig, + bz json.RawMessage, +) error { var gs types.GenesisState if err := cdc.UnmarshalJSON(bz, &gs); err != nil { return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)