diff --git a/CHANGELOG.md b/CHANGELOG.md index a2113cf20b87..8259c4604fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,8 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i ### Deprecated +* (modules) [#22994](https://github.com/cosmos/cosmos-sdk/pull/22994) Deprecate `Invariants` and associated methods. + ## [v0.52.0-rc.1](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.52.0-rc.1) - 2024-12-18 Every module contains its own CHANGELOG.md. Please refer to the module you are interested in. diff --git a/docs/build/building-modules/11-structure.md b/docs/build/building-modules/11-structure.md index 95750c4e4867..f291fda24182 100644 --- a/docs/build/building-modules/11-structure.md +++ b/docs/build/building-modules/11-structure.md @@ -45,7 +45,6 @@ x/{module_name} │   ├── genesis.go │   ├── grpc_query.go │   ├── hooks.go -│   ├── invariants.go │   ├── keeper.go │   ├── keys.go │   ├── msg_server.go diff --git a/testutil/mock/types_mock_appmodule.go b/testutil/mock/types_mock_appmodule.go index 686e246195f7..342baad21dbf 100644 --- a/testutil/mock/types_mock_appmodule.go +++ b/testutil/mock/types_mock_appmodule.go @@ -16,7 +16,6 @@ import ( appmodule "cosmossdk.io/core/appmodule" appmodulev2 "cosmossdk.io/core/appmodule/v2" - types "github.com/cosmos/cosmos-sdk/types" module "github.com/cosmos/cosmos-sdk/types/module" gomock "go.uber.org/mock/gomock" ) @@ -155,18 +154,6 @@ func (mr *MockAppModuleWithAllExtensionsMockRecorder) Name() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockAppModuleWithAllExtensions)(nil).Name)) } -// RegisterInvariants mocks base method. -func (m *MockAppModuleWithAllExtensions) RegisterInvariants(arg0 types.InvariantRegistry) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RegisterInvariants", arg0) -} - -// RegisterInvariants indicates an expected call of RegisterInvariants. -func (mr *MockAppModuleWithAllExtensionsMockRecorder) RegisterInvariants(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterInvariants", reflect.TypeOf((*MockAppModuleWithAllExtensions)(nil).RegisterInvariants), arg0) -} - // RegisterServices mocks base method. func (m *MockAppModuleWithAllExtensions) RegisterServices(arg0 module.Configurator) { m.ctrl.T.Helper() @@ -328,18 +315,6 @@ func (mr *MockAppModuleWithAllExtensionsABCIMockRecorder) Name() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockAppModuleWithAllExtensionsABCI)(nil).Name)) } -// RegisterInvariants mocks base method. -func (m *MockAppModuleWithAllExtensionsABCI) RegisterInvariants(arg0 types.InvariantRegistry) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RegisterInvariants", arg0) -} - -// RegisterInvariants indicates an expected call of RegisterInvariants. -func (mr *MockAppModuleWithAllExtensionsABCIMockRecorder) RegisterInvariants(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterInvariants", reflect.TypeOf((*MockAppModuleWithAllExtensionsABCI)(nil).RegisterInvariants), arg0) -} - // RegisterServices mocks base method. func (m *MockAppModuleWithAllExtensionsABCI) RegisterServices(arg0 module.Configurator) { m.ctrl.T.Helper() diff --git a/types/invariant.go b/types/invariant.go index 1e7cae7f8b10..52a847b272e7 100644 --- a/types/invariant.go +++ b/types/invariant.go @@ -6,17 +6,21 @@ import "fmt" // The invariant returns a descriptive message about what happened // and a boolean indicating whether the invariant has been broken. // The simulator will then halt and print the logs. +// Deprecated: to be removed in > 0.52. type Invariant func(ctx Context) (string, bool) // Invariants defines a group of invariants +// Deprecated: to be removed in the next SDK version. type Invariants []Invariant // expected interface for registering invariants +// Deprecated: to be removed in the next SDK version. type InvariantRegistry interface { RegisterRoute(moduleName, route string, invar Invariant) } // FormatInvariant returns a standardized invariant message. +// Deprecated: to be removed in the next SDK version. func FormatInvariant(module, name, msg string) string { return fmt.Sprintf("%s: %s invariant\n%s\n", module, name, msg) } diff --git a/types/module/mock_appmodule_test.go b/types/module/mock_appmodule_test.go index 5d5782b599ec..ed4311a891f9 100644 --- a/types/module/mock_appmodule_test.go +++ b/types/module/mock_appmodule_test.go @@ -1,4 +1,4 @@ -// module_test inconsistenctly import appmodulev2 & appmodulev1 due to limitation in mockgen +// module_test inconsistently imports appmodulev2 & appmodulev1 due to limitation in mockgen // eventually, when we change mocking library, we should be consistent in our appmodule imports package module_test @@ -14,7 +14,6 @@ import ( type AppModuleWithAllExtensions interface { module.AppModule module.HasServices - module.HasInvariants appmodulev2.HasConsensusVersion appmodulev2.HasGenesis module.HasABCIEndBlock @@ -25,7 +24,6 @@ type AppModuleWithAllExtensionsABCI interface { module.AppModule module.HasServices appmodulev2.HasABCIGenesis - module.HasInvariants appmodulev2.HasConsensusVersion module.HasABCIEndBlock } diff --git a/types/module/module.go b/types/module/module.go index 7d865930b26b..714a9490d848 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -84,6 +84,7 @@ type HasGenesis = appmodulev2.HasGenesis type HasABCIGenesis = appmodulev2.HasABCIGenesis // HasInvariants is the interface for registering invariants. +// Deprecated: invariants are no longer used from modules. type HasInvariants interface { // RegisterInvariants registers module invariants. RegisterInvariants(sdk.InvariantRegistry) @@ -389,6 +390,7 @@ func (m *Manager) AddQueryCommands(rootQueryCmd *cobra.Command) { } // RegisterInvariants registers all module invariants +// Deprecated: this function is no longer to be used as invariants are deprecated. func (m *Manager) RegisterInvariants(ir sdk.InvariantRegistry) { for _, module := range m.Modules { if module, ok := module.(HasInvariants); ok { diff --git a/types/module/module_test.go b/types/module/module_test.go index cab5b88288fc..9f2363d0a679 100644 --- a/types/module/module_test.go +++ b/types/module/module_test.go @@ -104,27 +104,6 @@ func TestManagerOrderSetters(t *testing.T) { require.Equal(t, []string{"module3", "module2", "module1"}, mm.OrderPrecommiters) } -func TestManager_RegisterInvariants(t *testing.T) { - mockCtrl := gomock.NewController(t) - t.Cleanup(mockCtrl.Finish) - - mockAppModule1 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) - mockAppModule2 := mock.NewMockAppModuleWithAllExtensions(mockCtrl) - mockAppModule3 := mock.NewMockCoreAppModule(mockCtrl) - mockAppModule1.EXPECT().Name().Times(2).Return("module1") - mockAppModule2.EXPECT().Name().Times(2).Return("module2") - // TODO: This is not working for Core API modules yet - mm := module.NewManager(mockAppModule1, mockAppModule2, module.CoreAppModuleAdaptor("mockAppModule3", mockAppModule3)) - require.NotNil(t, mm) - require.Equal(t, 3, len(mm.Modules)) - - // test RegisterInvariants - mockInvariantRegistry := mock.NewMockInvariantRegistry(mockCtrl) - mockAppModule1.EXPECT().RegisterInvariants(gomock.Eq(mockInvariantRegistry)).Times(1) - mockAppModule2.EXPECT().RegisterInvariants(gomock.Eq(mockInvariantRegistry)).Times(1) - mm.RegisterInvariants(mockInvariantRegistry) -} - func TestManager_RegisterQueryServices(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) diff --git a/x/bank/keeper/invariants.go b/x/bank/keeper/invariants.go deleted file mode 100644 index 51dfa157b809..000000000000 --- a/x/bank/keeper/invariants.go +++ /dev/null @@ -1,78 +0,0 @@ -package keeper - -import ( - "fmt" - - "cosmossdk.io/x/bank/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/query" -) - -// RegisterInvariants registers the bank module invariants -func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { - ir.RegisterRoute(types.ModuleName, "nonnegative-outstanding", NonnegativeBalanceInvariant(k)) - ir.RegisterRoute(types.ModuleName, "total-supply", TotalSupply(k)) -} - -// AllInvariants runs all invariants of the X/bank module. -func AllInvariants(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - res, stop := NonnegativeBalanceInvariant(k)(ctx) - if stop { - return res, stop - } - return TotalSupply(k)(ctx) - } -} - -// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances -func NonnegativeBalanceInvariant(k ViewKeeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var ( - msg string - count int - ) - - k.IterateAllBalances(ctx, func(addr sdk.AccAddress, balance sdk.Coin) bool { - if balance.IsNegative() { - count++ - msg += fmt.Sprintf("\t%s has a negative balance of %s\n", addr, balance) - } - - return false - }) - - broken := count != 0 - - return sdk.FormatInvariant( - types.ModuleName, "nonnegative-outstanding", - fmt.Sprintf("amount of negative balances found %d\n%s", count, msg), - ), broken - } -} - -// TotalSupply checks that the total supply reflects all the coins held in accounts -func TotalSupply(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - expectedTotal := sdk.Coins{} - supply, _, err := k.GetPaginatedTotalSupply(ctx, &query.PageRequest{Limit: query.PaginationMaxLimit}) - if err != nil { - return sdk.FormatInvariant(types.ModuleName, "query supply", - fmt.Sprintf("error querying total supply %v", err)), false - } - - k.IterateAllBalances(ctx, func(_ sdk.AccAddress, balance sdk.Coin) bool { - expectedTotal = expectedTotal.Add(balance) - return false - }) - - broken := !expectedTotal.Equal(supply) - - return sdk.FormatInvariant(types.ModuleName, "total supply", - fmt.Sprintf( - "\tsum of accounts coins: %v\n"+ - "\tsupply.Total: %v\n", - expectedTotal, supply)), broken - } -} diff --git a/x/bank/module.go b/x/bank/module.go index cc459f23b340..624a024ed97e 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -21,7 +21,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simsx" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -33,7 +32,6 @@ var ( _ module.HasAminoCodec = AppModule{} _ module.HasGRPCGateway = AppModule{} _ module.AppModuleSimulation = AppModule{} - _ module.HasInvariants = AppModule{} _ appmodule.AppModule = AppModule{} _ appmodule.HasMigrations = AppModule{} @@ -114,11 +112,6 @@ func (am AppModule) RegisterMigrations(mr appmodule.MigrationRegistrar) error { return nil } -// RegisterInvariants registers the bank module invariants. -func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - keeper.RegisterInvariants(ir, am.keeper) -} - // DefaultGenesis returns default genesis state as raw bytes for the bank module. func (am AppModule) DefaultGenesis() json.RawMessage { return am.cdc.MustMarshalJSON(types.DefaultGenesisState()) diff --git a/x/distribution/keeper/invariants.go b/x/distribution/keeper/invariants.go deleted file mode 100644 index bf01b4cf644d..000000000000 --- a/x/distribution/keeper/invariants.go +++ /dev/null @@ -1,208 +0,0 @@ -package keeper - -import ( - "fmt" - - "cosmossdk.io/collections" - "cosmossdk.io/x/distribution/types" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// register all distribution invariants -func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { - ir.RegisterRoute(types.ModuleName, "nonnegative-outstanding", - NonNegativeOutstandingInvariant(k)) - ir.RegisterRoute(types.ModuleName, "can-withdraw", - CanWithdrawInvariant(k)) - ir.RegisterRoute(types.ModuleName, "reference-count", - ReferenceCountInvariant(k)) - ir.RegisterRoute(types.ModuleName, "module-account", - ModuleAccountInvariant(k)) -} - -// AllInvariants runs all invariants of the distribution module -func AllInvariants(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - res, stop := CanWithdrawInvariant(k)(ctx) - if stop { - return res, stop - } - res, stop = NonNegativeOutstandingInvariant(k)(ctx) - if stop { - return res, stop - } - res, stop = ReferenceCountInvariant(k)(ctx) - if stop { - return res, stop - } - return ModuleAccountInvariant(k)(ctx) - } -} - -// NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative -func NonNegativeOutstandingInvariant(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var msg string - var count int - var outstanding sdk.DecCoins - - err := k.ValidatorOutstandingRewards.Walk(ctx, nil, func(addr sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool, err error) { - outstanding = rewards.GetRewards() - if outstanding.IsAnyNegative() { - count++ - msg += fmt.Sprintf("\t%v has negative outstanding coins: %v\n", addr, outstanding) - } - return false, nil - }) - if err != nil { - return sdk.FormatInvariant(types.ModuleName, "nonnegative outstanding", err.Error()), true - } - broken := count != 0 - - return sdk.FormatInvariant(types.ModuleName, "nonnegative outstanding", - fmt.Sprintf("found %d validators with negative outstanding rewards\n%s", count, msg)), broken - } -} - -// CanWithdrawInvariant checks that current rewards can be completely withdrawn -func CanWithdrawInvariant(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - // cache, we don't want to write changes - ctx, _ = ctx.CacheContext() - - var remaining sdk.DecCoins - - valDelegationAddrs := make(map[string][][]byte) - allDelegations, err := k.stakingKeeper.GetAllSDKDelegations(ctx) - if err != nil { - panic(err) - } - - for _, del := range allDelegations { - delAddr, err := k.addrCdc.StringToBytes(del.GetDelegatorAddr()) - if err != nil { - panic(err) - } - valAddr := del.GetValidatorAddr() - valDelegationAddrs[valAddr] = append(valDelegationAddrs[valAddr], delAddr) - } - - // iterate over all validators - err = k.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.ValidatorI) (stop bool) { - valBz, err1 := k.stakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) - if err != nil { - panic(err1) - } - _, _ = k.WithdrawValidatorCommission(ctx, valBz) - - delegationAddrs, ok := valDelegationAddrs[val.GetOperator()] - if ok { - for _, delAddr := range delegationAddrs { - if _, err := k.WithdrawDelegationRewards(ctx, delAddr, valBz); err != nil { - panic(err) - } - } - } - - var err error - remaining, err = k.GetValidatorOutstandingRewardsCoins(ctx, valBz) - if err != nil { - panic(err) - } - - if len(remaining) > 0 && remaining[0].Amount.IsNegative() { - return true - } - - return false - }) - if err != nil { - panic(err) - } - - broken := len(remaining) > 0 && remaining[0].Amount.IsNegative() - return sdk.FormatInvariant(types.ModuleName, "can withdraw", - fmt.Sprintf("remaining coins: %v\n", remaining)), broken - } -} - -// ReferenceCountInvariant checks that the number of historical rewards records is correct -func ReferenceCountInvariant(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - valCount := uint64(0) - err := k.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.ValidatorI) (stop bool) { - valCount++ - return false - }) - if err != nil { - panic(err) - } - - dels, err := k.stakingKeeper.GetAllSDKDelegations(ctx) - if err != nil { - panic(err) - } - - slashCount := uint64(0) - err = k.ValidatorSlashEvents.Walk( - ctx, - nil, - func(k collections.Triple[sdk.ValAddress, uint64, uint64], event types.ValidatorSlashEvent) (stop bool, err error) { - slashCount++ - return false, nil - }, - ) - if err != nil { - panic(err) - } - - // one record per validator (last tracked period), one record per - // delegation (previous period), one record per slash (previous period) - expected := valCount + uint64(len(dels)) + slashCount - count := uint64(0) - err = k.ValidatorHistoricalRewards.Walk( - ctx, nil, func(key collections.Pair[sdk.ValAddress, uint64], rewards types.ValidatorHistoricalRewards) (stop bool, err error) { - count += uint64(rewards.ReferenceCount) - return false, nil - }, - ) - if err != nil { - panic(err) - } - - broken := count != expected - - return sdk.FormatInvariant(types.ModuleName, "reference count", - fmt.Sprintf("expected historical reference count: %d = %v validators + %v delegations + %v slashes\n"+ - "total validator historical reference count: %d\n", - expected, valCount, len(dels), slashCount, count)), broken - } -} - -// ModuleAccountInvariant checks that the coins held by the distr ModuleAccount -// is consistent with the sum of validator outstanding rewards -func ModuleAccountInvariant(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var expectedCoins sdk.DecCoins - err := k.ValidatorOutstandingRewards.Walk(ctx, nil, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool, err error) { - expectedCoins = expectedCoins.Add(rewards.Rewards...) - return false, nil - }) - if err != nil { - return sdk.FormatInvariant(types.ModuleName, "module account coins", err.Error()), true - } - - expectedInt, _ := expectedCoins.TruncateDecimal() - - balances := k.bankKeeper.GetAllBalances(ctx, k.GetDistributionAccount(ctx).GetAddress()) - broken := !balances.Equal(expectedInt) - return sdk.FormatInvariant( - types.ModuleName, "ModuleAccount coins", - fmt.Sprintf("\texpected ModuleAccount coins: %s\n"+ - "\tdistribution ModuleAccount coins: %s\n", - expectedInt, balances, - ), - ), broken - } -} diff --git a/x/distribution/module.go b/x/distribution/module.go index ae1ae85e82c9..3d284d44621c 100644 --- a/x/distribution/module.go +++ b/x/distribution/module.go @@ -21,7 +21,6 @@ import ( sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simsx" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -33,7 +32,6 @@ var ( _ module.HasAminoCodec = AppModule{} _ module.HasGRPCGateway = AppModule{} _ module.AppModuleSimulation = AppModule{} - _ module.HasInvariants = AppModule{} _ appmodule.AppModule = AppModule{} _ appmodule.HasBeginBlocker = AppModule{} @@ -89,11 +87,6 @@ func (AppModule) RegisterInterfaces(registrar registry.InterfaceRegistrar) { types.RegisterInterfaces(registrar) } -// RegisterInvariants registers the distribution module invariants. -func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - keeper.RegisterInvariants(ir, am.keeper) -} - // RegisterServices registers module services. func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { types.RegisterMsgServer(registrar, keeper.NewMsgServerImpl(am.keeper)) diff --git a/x/gov/keeper/invariants.go b/x/gov/keeper/invariants.go deleted file mode 100644 index d19474c3a8bd..000000000000 --- a/x/gov/keeper/invariants.go +++ /dev/null @@ -1,44 +0,0 @@ -package keeper - -import ( - "fmt" - - "cosmossdk.io/collections" - "cosmossdk.io/x/gov/types" - v1 "cosmossdk.io/x/gov/types/v1" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// RegisterInvariants registers all governance invariants -func RegisterInvariants(ir sdk.InvariantRegistry, keeper *Keeper, bk types.BankKeeper) { - ir.RegisterRoute(types.ModuleName, "module-account", ModuleAccountInvariant(keeper, bk)) -} - -// ModuleAccountInvariant checks that the module account coins reflects the sum of -// deposit amounts held on store. -func ModuleAccountInvariant(keeper *Keeper, bk types.BankKeeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var expectedDeposits sdk.Coins - - err := keeper.Deposits.Walk(ctx, nil, func(key collections.Pair[uint64, sdk.AccAddress], value v1.Deposit) (stop bool, err error) { - expectedDeposits = expectedDeposits.Add(value.Amount...) - return false, nil - }) - if err != nil { - panic(err) - } - - macc := keeper.GetGovernanceAccount(ctx) - balances := bk.GetAllBalances(ctx, macc.GetAddress()) - - // Require that the deposit balances are <= than the x/gov module's total - // balances. We use the <= operator since external funds can be sent to x/gov - // module's account and so the balance can be larger. - broken := !balances.IsAllGTE(expectedDeposits) - - return sdk.FormatInvariant(types.ModuleName, "deposits", - fmt.Sprintf("\tgov ModuleAccount coins: %s\n\tsum of deposit amounts: %s\n", - balances, expectedDeposits)), broken - } -} diff --git a/x/gov/module.go b/x/gov/module.go index 8fc5f3061aab..df3f35c0aee4 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -22,7 +22,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simsx" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -33,7 +32,6 @@ var ( _ module.HasAminoCodec = AppModule{} _ module.HasGRPCGateway = AppModule{} _ module.AppModuleSimulation = AppModule{} - _ module.HasInvariants = AppModule{} _ appmodule.AppModule = AppModule{} _ appmodule.HasEndBlocker = AppModule{} @@ -115,11 +113,6 @@ func (AppModule) RegisterInterfaces(registrar registry.InterfaceRegistrar) { v1beta1.RegisterInterfaces(registrar) } -// RegisterInvariants registers module invariants -func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - keeper.RegisterInvariants(ir, am.keeper, am.bankKeeper) -} - // RegisterServices registers module services. func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { msgServer := keeper.NewMsgServerImpl(am.keeper) diff --git a/x/group/go.mod b/x/group/go.mod index 0c3c7847ee07..b692c40e4836 100644 --- a/x/group/go.mod +++ b/x/group/go.mod @@ -6,7 +6,6 @@ require ( cosmossdk.io/api v0.8.0-rc.3 cosmossdk.io/client/v2 v2.0.0-beta.6 cosmossdk.io/core v1.0.0-alpha.6 - cosmossdk.io/core/testing v0.0.1 cosmossdk.io/depinject v1.1.0 cosmossdk.io/errors v1.0.1 cosmossdk.io/log v1.5.0 @@ -33,6 +32,7 @@ require ( buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.36.0-20241120201313-68e42a58b301.1 // indirect buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.36.0-20240130113600-88ef6483f90f.1 // indirect cosmossdk.io/collections v1.0.0-rc.1 // indirect + cosmossdk.io/core/testing v0.0.1 // indirect cosmossdk.io/schema v1.0.0 // indirect cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 // indirect cosmossdk.io/x/tx v1.0.0-alpha.3 // indirect diff --git a/x/group/keeper/invariants.go b/x/group/keeper/invariants.go deleted file mode 100644 index 33034b5d7506..000000000000 --- a/x/group/keeper/invariants.go +++ /dev/null @@ -1,126 +0,0 @@ -package keeper - -import ( - "fmt" - "maps" - "math" - "slices" - - storetypes "cosmossdk.io/core/store" - "cosmossdk.io/x/group" - "cosmossdk.io/x/group/errors" - groupmath "cosmossdk.io/x/group/internal/math" - "cosmossdk.io/x/group/internal/orm" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -const weightInvariant = "Group-TotalWeight" - -// RegisterInvariants registers all group invariants. -func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) { - ir.RegisterRoute(group.ModuleName, weightInvariant, GroupTotalWeightInvariant(keeper)) -} - -// GroupTotalWeightInvariant checks that group's TotalWeight must be equal to the sum of its members. -func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - msg, broken := GroupTotalWeightInvariantHelper(ctx, keeper.KVStoreService, keeper.groupTable, keeper.groupMemberByGroupIndex) - return sdk.FormatInvariant(group.ModuleName, weightInvariant, msg), broken - } -} - -func GroupTotalWeightInvariantHelper(ctx sdk.Context, storeService storetypes.KVStoreService, groupTable orm.AutoUInt64Table, groupMemberByGroupIndex orm.Index) (string, bool) { - var msg string - var broken bool - - kvStore := storeService.OpenKVStore(ctx) - - groupIt, err := groupTable.PrefixScan(kvStore, 1, math.MaxUint64) - if err != nil { - msg += fmt.Sprintf("PrefixScan failure on group table\n%v\n", err) - return msg, broken - } - defer groupIt.Close() - - groups := make(map[uint64]group.GroupInfo) - for { - var groupInfo group.GroupInfo - _, err = groupIt.LoadNext(&groupInfo) - if errors.ErrORMIteratorDone.Is(err) { - break - } - if err != nil { - msg += fmt.Sprintf("LoadNext failure on group table iterator\n%v\n", err) - return msg, broken - } - groups[groupInfo.Id] = groupInfo - } - - groupByIDs := slices.Collect(maps.Keys(groups)) - slices.SortFunc(groupByIDs, func(i, j uint64) int { - if groupByIDs[i] < groupByIDs[j] { - return -1 - } else if groupByIDs[i] > groupByIDs[j] { - return 1 - } - return 0 - }) - - for _, groupID := range groupByIDs { - groupInfo := groups[groupID] - membersWeight, err := groupmath.NewNonNegativeDecFromString("0") - if err != nil { - msg += fmt.Sprintf("error while parsing positive dec zero for group member\n%v\n", err) - return msg, broken - } - - err = func() error { - memIt, err := groupMemberByGroupIndex.Get(kvStore, groupInfo.Id) - if err != nil { - return fmt.Errorf("error while returning group member iterator for group with ID %d\n%w", groupInfo.Id, err) - } - defer memIt.Close() - - for { - var groupMember group.GroupMember - _, err = memIt.LoadNext(&groupMember) - if errors.ErrORMIteratorDone.Is(err) { - break - } - if err != nil { - return fmt.Errorf("LoadNext failure on member table iterator\n%w", err) - } - - curMemWeight, err := groupmath.NewPositiveDecFromString(groupMember.GetMember().GetWeight()) - if err != nil { - return fmt.Errorf("error while parsing non-nengative decimal for group member %s\n%w", groupMember.Member.Address, err) - } - - membersWeight, err = groupmath.Add(membersWeight, curMemWeight) - if err != nil { - return fmt.Errorf("decimal addition error while adding group member voting weight to total voting weight\n%w", err) - } - } - return nil - }() - if err != nil { - msg += err.Error() + "\n" - return msg, broken - } - - groupWeight, err := groupmath.NewNonNegativeDecFromString(groupInfo.GetTotalWeight()) - if err != nil { - msg += fmt.Sprintf("error while parsing non-nengative decimal for group with ID %d\n%v\n", groupInfo.Id, err) - return msg, broken - } - - if groupWeight.Cmp(membersWeight) != 0 { - broken = true - msg += fmt.Sprintf("group's TotalWeight must be equal to the sum of its members' weights\ngroup weight: %s\nSum of group members weights: %s\n", groupWeight.String(), membersWeight.String()) - break - } - } - - return msg, broken -} diff --git a/x/group/keeper/invariants_test.go b/x/group/keeper/invariants_test.go deleted file mode 100644 index 2a7d422029b0..000000000000 --- a/x/group/keeper/invariants_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package keeper_test - -import ( - "testing" - - "github.com/stretchr/testify/suite" - - coretesting "cosmossdk.io/core/testing" - "cosmossdk.io/log" - "cosmossdk.io/store" - "cosmossdk.io/store/metrics" - storetypes "cosmossdk.io/store/types" - "cosmossdk.io/x/group" - "cosmossdk.io/x/group/internal/orm" - "cosmossdk.io/x/group/keeper" - - "github.com/cosmos/cosmos-sdk/codec" - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type invariantTestSuite struct { - suite.Suite - - ctx sdk.Context - cdc *codec.ProtoCodec - key *storetypes.KVStoreKey -} - -func TestInvariantTestSuite(t *testing.T) { - suite.Run(t, new(invariantTestSuite)) -} - -func (s *invariantTestSuite) SetupSuite() { - interfaceRegistry := types.NewInterfaceRegistry() - group.RegisterInterfaces(interfaceRegistry) - cdc := codec.NewProtoCodec(interfaceRegistry) - key := storetypes.NewKVStoreKey(group.ModuleName) - db := coretesting.NewMemDB() - cms := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) - cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) - _ = cms.LoadLatestVersion() - sdkCtx := sdk.NewContext(cms, false, log.NewNopLogger()) - - s.ctx = sdkCtx - s.cdc = cdc - s.key = key -} - -func (s *invariantTestSuite) TestGroupTotalWeightInvariant() { - sdkCtx, _ := s.ctx.CacheContext() - curCtx, cdc, key := sdkCtx, s.cdc, s.key - addressCodec := codectestutil.CodecOptions{}.GetAddressCodec() - - // Group Table - groupTable, err := orm.NewAutoUInt64Table([2]byte{keeper.GroupTablePrefix}, keeper.GroupTableSeqPrefix, &group.GroupInfo{}, cdc, addressCodec) - s.Require().NoError(err) - - // Group Member Table - groupMemberTable, err := orm.NewPrimaryKeyTable([2]byte{keeper.GroupMemberTablePrefix}, &group.GroupMember{}, cdc, addressCodec) - s.Require().NoError(err) - - groupMemberByGroupIndex, err := orm.NewIndex(groupMemberTable, keeper.GroupMemberByGroupIndexPrefix, func(val interface{}) ([]interface{}, error) { - group := val.(*group.GroupMember).GroupId - return []interface{}{group}, nil - }, group.GroupMember{}.GroupId) - s.Require().NoError(err) - - _, _, addr1 := testdata.KeyTestPubAddr() - _, _, addr2 := testdata.KeyTestPubAddr() - - addr1Str, err := addressCodec.BytesToString(addr1) - s.Require().NoError(err) - addr2Str, err := addressCodec.BytesToString(addr2) - s.Require().NoError(err) - - specs := map[string]struct { - groupsInfo *group.GroupInfo - groupMembers []*group.GroupMember - expBroken bool - }{ - "invariant not broken": { - groupsInfo: &group.GroupInfo{ - Id: 1, - Admin: addr1Str, - Version: 1, - TotalWeight: "3", - }, - groupMembers: []*group.GroupMember{ - { - GroupId: 1, - Member: &group.Member{ - Address: addr1Str, - Weight: "1", - }, - }, - { - GroupId: 1, - Member: &group.Member{ - Address: addr2Str, - Weight: "2", - }, - }, - }, - expBroken: false, - }, - - "group's TotalWeight must be equal to sum of its members weight ": { - groupsInfo: &group.GroupInfo{ - Id: 1, - Admin: addr1Str, - Version: 1, - TotalWeight: "3", - }, - groupMembers: []*group.GroupMember{ - { - GroupId: 1, - Member: &group.Member{ - Address: addr1Str, - Weight: "2", - }, - }, - { - GroupId: 1, - Member: &group.Member{ - Address: addr2Str, - Weight: "2", - }, - }, - }, - expBroken: true, - }, - } - - for _, spec := range specs { - cacheCurCtx, _ := curCtx.CacheContext() - groupsInfo := spec.groupsInfo - groupMembers := spec.groupMembers - storeService := runtime.NewKVStoreService(key) - kvStore := storeService.OpenKVStore(cacheCurCtx) - _, err := groupTable.Create(kvStore, groupsInfo) - s.Require().NoError(err) - - for i := 0; i < len(groupMembers); i++ { - err := groupMemberTable.Create(kvStore, groupMembers[i]) - s.Require().NoError(err) - } - - _, broken := keeper.GroupTotalWeightInvariantHelper(cacheCurCtx, storeService, *groupTable, groupMemberByGroupIndex) - s.Require().Equal(spec.expBroken, broken) - - } -} diff --git a/x/group/module/module.go b/x/group/module/module.go index 97b0b40118b7..5ffe3fe6234b 100644 --- a/x/group/module/module.go +++ b/x/group/module/module.go @@ -20,7 +20,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/simsx" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) @@ -32,7 +31,6 @@ var ( _ module.HasAminoCodec = AppModule{} _ module.HasGRPCGateway = AppModule{} _ module.AppModuleSimulation = AppModule{} - _ module.HasInvariants = AppModule{} _ appmodule.AppModule = AppModule{} _ appmodule.HasEndBlocker = AppModule{} @@ -92,11 +90,6 @@ func (AppModule) RegisterLegacyAminoCodec(registrar registry.AminoRegistrar) { group.RegisterLegacyAminoCodec(registrar) } -// RegisterInvariants does nothing, there are no invariants to enforce -func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - keeper.RegisterInvariants(ir, am.keeper) -} - // RegisterServices registers module services. func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { group.RegisterMsgServer(registrar, am.keeper) diff --git a/x/staking/keeper/invariants.go b/x/staking/keeper/invariants.go deleted file mode 100644 index bedceb58115f..000000000000 --- a/x/staking/keeper/invariants.go +++ /dev/null @@ -1,226 +0,0 @@ -package keeper - -import ( - "bytes" - "fmt" - - "cosmossdk.io/collections" - "cosmossdk.io/math" - "cosmossdk.io/x/staking/types" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// RegisterInvariants registers all staking invariants -func RegisterInvariants(ir sdk.InvariantRegistry, k *Keeper) { - ir.RegisterRoute(types.ModuleName, "module-accounts", - ModuleAccountInvariants(k)) - ir.RegisterRoute(types.ModuleName, "nonnegative-power", - NonNegativePowerInvariant(k)) - ir.RegisterRoute(types.ModuleName, "positive-delegation", - PositiveDelegationInvariant(k)) - ir.RegisterRoute(types.ModuleName, "delegator-shares", - DelegatorSharesInvariant(k)) -} - -// AllInvariants runs all invariants of the staking module. -func AllInvariants(k *Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - res, stop := ModuleAccountInvariants(k)(ctx) - if stop { - return res, stop - } - - res, stop = NonNegativePowerInvariant(k)(ctx) - if stop { - return res, stop - } - - res, stop = PositiveDelegationInvariant(k)(ctx) - if stop { - return res, stop - } - - return DelegatorSharesInvariant(k)(ctx) - } -} - -// ModuleAccountInvariants checks that the bonded and notBonded ModuleAccounts pools -// reflects the tokens actively bonded and not bonded -func ModuleAccountInvariants(k *Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - bonded := math.ZeroInt() - notBonded := math.ZeroInt() - bondedPool := k.GetBondedPool(ctx) - notBondedPool := k.GetNotBondedPool(ctx) - bondDenom, err := k.BondDenom(ctx) - if err != nil { - panic(err) - } - - err = k.IterateValidators(ctx, func(_ int64, validator sdk.ValidatorI) bool { - switch validator.GetStatus() { - case sdk.Bonded: - bonded = bonded.Add(validator.GetTokens()) - case sdk.Unbonding, sdk.Unbonded: - notBonded = notBonded.Add(validator.GetTokens()) - default: - panic("invalid validator status") - } - return false - }) - if err != nil { - panic(err) - } - - err = k.UnbondingDelegations.Walk( - ctx, - nil, - func(key collections.Pair[[]byte, []byte], ubd types.UnbondingDelegation) (stop bool, err error) { - for _, entry := range ubd.Entries { - notBonded = notBonded.Add(entry.Balance) - } - return false, nil - }, - ) - if err != nil { - panic(err) - } - - poolBonded := k.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom) - poolNotBonded := k.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom) - broken := !poolBonded.Amount.Equal(bonded) || !poolNotBonded.Amount.Equal(notBonded) - - // Bonded tokens should equal sum of tokens with bonded validators - // Not-bonded tokens should equal unbonding delegations plus tokens on unbonded validators - return sdk.FormatInvariant(types.ModuleName, "bonded and not bonded module account coins", fmt.Sprintf( - "\tPool's bonded tokens: %v\n"+ - "\tsum of bonded tokens: %v\n"+ - "not bonded token invariance:\n"+ - "\tPool's not bonded tokens: %v\n"+ - "\tsum of not bonded tokens: %v\n"+ - "module accounts total (bonded + not bonded):\n"+ - "\tModule Accounts' tokens: %v\n"+ - "\tsum tokens: %v\n", - poolBonded, bonded, poolNotBonded, notBonded, poolBonded.Add(poolNotBonded), bonded.Add(notBonded))), broken - } -} - -// NonNegativePowerInvariant checks that all stored validators have >= 0 power. -func NonNegativePowerInvariant(k *Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var ( - msg string - broken bool - ) - - iterator, err := k.ValidatorsPowerStoreIterator(ctx) - if err != nil { - panic(err) - } - for ; iterator.Valid(); iterator.Next() { - validator, err := k.GetValidator(ctx, iterator.Value()) - if err != nil { - panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value())) - } - - powerKey := types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx), k.ValidatorAddressCodec()) - - if !bytes.Equal(iterator.Key(), powerKey) { - broken = true - msg += fmt.Sprintf("power store invariance:\n\tvalidator.Power: %v"+ - "\n\tkey should be: %v\n\tkey in store: %v\n", - validator.GetConsensusPower(k.PowerReduction(ctx)), powerKey, iterator.Key()) - } - - if validator.Tokens.IsNegative() { - broken = true - msg += fmt.Sprintf("\tnegative tokens for validator: %v\n", validator) - } - } - iterator.Close() - - return sdk.FormatInvariant(types.ModuleName, "nonnegative power", fmt.Sprintf("found invalid validator powers\n%s", msg)), broken - } -} - -// PositiveDelegationInvariant checks that all stored delegations have > 0 shares. -func PositiveDelegationInvariant(k *Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var ( - msg string - count int - ) - - delegations, err := k.GetAllDelegations(ctx) - if err != nil { - panic(err) - } - for _, delegation := range delegations { - if delegation.Shares.IsNegative() { - count++ - msg += fmt.Sprintf("\tdelegation with negative shares: %+v\n", delegation) - } - - if delegation.Shares.IsZero() { - count++ - msg += fmt.Sprintf("\tdelegation with zero shares: %+v\n", delegation) - } - } - - broken := count != 0 - - return sdk.FormatInvariant(types.ModuleName, "positive delegations", fmt.Sprintf( - "%d invalid delegations found\n%s", count, msg)), broken - } -} - -// DelegatorSharesInvariant checks whether all the delegator shares which persist -// in the delegator object add up to the correct total delegator shares -// amount stored in each validator. -func DelegatorSharesInvariant(k *Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var ( - msg string - broken bool - ) - - validators, err := k.GetAllValidators(ctx) - if err != nil { - panic(err) - } - - validatorsDelegationShares := map[string]math.LegacyDec{} - - // initialize a map: validator -> its delegation shares - for _, validator := range validators { - validatorsDelegationShares[validator.GetOperator()] = math.LegacyZeroDec() - } - - // iterate through all the delegations to calculate the total delegation shares for each validator - delegations, err := k.GetAllDelegations(ctx) - if err != nil { - panic(err) - } - - for _, delegation := range delegations { - delegationValidatorAddr := delegation.GetValidatorAddr() - validatorDelegationShares := validatorsDelegationShares[delegationValidatorAddr] - validatorsDelegationShares[delegationValidatorAddr] = validatorDelegationShares.Add(delegation.Shares) - } - - // for each validator, check if its total delegation shares calculated from the step above equals to its expected delegation shares - for _, validator := range validators { - expValTotalDelShares := validator.GetDelegatorShares() - calculatedValTotalDelShares := validatorsDelegationShares[validator.GetOperator()] - if !calculatedValTotalDelShares.Equal(expValTotalDelShares) { - broken = true - msg += fmt.Sprintf("broken delegator shares invariance:\n"+ - "\tvalidator.DelegatorShares: %v\n"+ - "\tsum of Delegator.Shares: %v\n", expValTotalDelShares, calculatedValTotalDelShares) - } - } - - return sdk.FormatInvariant(types.ModuleName, "delegator shares", msg), broken - } -} diff --git a/x/staking/module.go b/x/staking/module.go index 041906ad1c03..d21b3c396848 100644 --- a/x/staking/module.go +++ b/x/staking/module.go @@ -20,7 +20,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" ) @@ -32,7 +31,6 @@ var ( _ module.AppModuleSimulation = AppModule{} _ module.HasAminoCodec = AppModule{} _ module.HasGRPCGateway = AppModule{} - _ module.HasInvariants = AppModule{} _ module.HasABCIGenesis = AppModule{} _ module.HasABCIEndBlock = AppModule{} @@ -89,11 +87,6 @@ func (AppModule) GetTxCmd() *cobra.Command { return cli.NewTxCmd() } -// RegisterInvariants registers the staking module invariants. -func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - keeper.RegisterInvariants(ir, am.keeper) -} - // RegisterServices registers module services. func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { types.RegisterMsgServer(registrar, keeper.NewMsgServerImpl(am.keeper))