refactor: remove invariants (#22994)
Co-authored-by: Marko <marko@baricevic.me>
This commit is contained in:
parent
6ed94527c1
commit
71ddfbddbf
@ -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.
|
||||
|
||||
1
docs/build/building-modules/11-structure.md
vendored
1
docs/build/building-modules/11-structure.md
vendored
@ -45,7 +45,6 @@ x/{module_name}
|
||||
│ ├── genesis.go
|
||||
│ ├── grpc_query.go
|
||||
│ ├── hooks.go
|
||||
│ ├── invariants.go
|
||||
│ ├── keeper.go
|
||||
│ ├── keys.go
|
||||
│ ├── msg_server.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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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())
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user