diff --git a/x/gov/keeper/delegation.go b/x/gov/keeper/delegation.go index d733bf00..77c779f0 100644 --- a/x/gov/keeper/delegation.go +++ b/x/gov/keeper/delegation.go @@ -93,6 +93,22 @@ func (k Keeper) IterateGovernorValShares(ctx sdk.Context, governorAddr types.Gov } } +// IterateGovernorDelegations iterates over all governor delegations +func (k Keeper) IterateGovernorDelegations(ctx sdk.Context, governorAddr types.GovernorAddress, cb func(index int64, delegation v1.GovernanceDelegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.GovernanceDelegationsByGovernorKey(governorAddr, []byte{})) + defer iterator.Close() + + for i := int64(0); iterator.Valid(); iterator.Next() { + var delegation v1.GovernanceDelegation + k.cdc.MustUnmarshal(iterator.Value(), &delegation) + if cb(i, delegation) { + break + } + i++ + } +} + // GetGovernorValSharesByValidator gets all governor validator shares for a specific validator func (k Keeper) GetGovernorValSharesByValidator(ctx sdk.Context, validatorAddr sdk.ValAddress) []v1.GovernorValShares { store := ctx.KVStore(k.storeKey) diff --git a/x/gov/keeper/invariants.go b/x/gov/keeper/invariants.go index 494ebbcb..45891f2e 100644 --- a/x/gov/keeper/invariants.go +++ b/x/gov/keeper/invariants.go @@ -6,6 +6,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/atomone-hub/atomone/x/gov/types" v1 "github.com/atomone-hub/atomone/x/gov/types/v1" @@ -15,6 +16,7 @@ import ( func RegisterInvariants(ir sdk.InvariantRegistry, keeper *Keeper, bk types.BankKeeper) { ir.RegisterRoute(types.ModuleName, "module-account", ModuleAccountInvariant(keeper, bk)) ir.RegisterRoute(types.ModuleName, "governors-voting-power", GovernorsVotingPowerInvariant(keeper, keeper.sk)) + ir.RegisterRoute(types.ModuleName, "governors-delegations", GovernorsDelegationsInvariant(keeper, keeper.sk)) } // AllInvariants runs all invariants of the governance module @@ -69,14 +71,14 @@ func GovernorsVotingPowerInvariant(keeper *Keeper, sk types.StakingKeeper) sdk.I keeper.IterateGovernorValShares(ctx, governor.GetAddress(), func(index int64, shares v1.GovernorValShares) bool { validatorAddr, err := sdk.ValAddressFromBech32(shares.ValidatorAddress) if err != nil { - invariantStr = sdk.FormatInvariant(types.ModuleName, "governor %s voting power", + invariantStr = sdk.FormatInvariant(types.ModuleName, fmt.Sprintf("governor %s voting power", governor.GetAddress().String()), fmt.Sprintf("failed to parse validator address %s: %v", shares.ValidatorAddress, err)) fail = true return true } validator, found := sk.GetValidator(ctx, validatorAddr) if !found { - invariantStr = sdk.FormatInvariant(types.ModuleName, "governor %s voting power", + invariantStr = sdk.FormatInvariant(types.ModuleName, fmt.Sprintf("governor %s voting power", governor.GetAddress().String()), fmt.Sprintf("validator %s not found", validatorAddr.String())) fail = true return true @@ -95,10 +97,77 @@ func GovernorsVotingPowerInvariant(keeper *Keeper, sk types.StakingKeeper) sdk.I return broken // break on first broken invariant }) if !fail { - invariantStr = sdk.FormatInvariant(types.ModuleName, "governor %s voting power", - fmt.Sprintf("\texpected %s voting power: %s\n\tactual voting power: %s\n", - brokenGovernorAddr, expectedVotingPower, actualVotingPower)) + invariantStr = sdk.FormatInvariant(types.ModuleName, fmt.Sprintf("governor %s voting power", brokenGovernorAddr), + fmt.Sprintf("\texpected voting power: %s\n\tactual voting power: %s\n", expectedVotingPower, actualVotingPower)) } return invariantStr, broken } } + +// GovernorsDelegationsInvariant checks that the validator shares resulting from all +// governor delegations actually correspond to the stored governor validator shares +func GovernorsDelegationsInvariant(keeper *Keeper, sk types.StakingKeeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + var ( + broken bool = false + invariantStr string + ) + + keeper.IterateGovernors(ctx, func(index int64, governor v1.GovernorI) bool { + // check that if governor is active, it has a valid governance self-delegation + if governor.IsActive() { + if del, ok := keeper.GetGovernanceDelegation(ctx, sdk.AccAddress(governor.GetAddress())); !ok || !governor.GetAddress().Equals(types.MustGovernorAddressFromBech32(del.GovernorAddress)) { + invariantStr = sdk.FormatInvariant(types.ModuleName, fmt.Sprintf("governor %s delegations", governor.GetAddress().String()), + "active governor without governance self-delegation") + broken = true + return true + } + } + + valShares := make(map[string]sdk.Dec) + keeper.IterateGovernorDelegations(ctx, governor.GetAddress(), func(index int64, delegation v1.GovernanceDelegation) bool { + delAddr := sdk.MustAccAddressFromBech32(delegation.DelegatorAddress) + keeper.sk.IterateDelegations(ctx, delAddr, func(_ int64, delegation stakingtypes.DelegationI) (stop bool) { + validatorAddr := delegation.GetValidatorAddr() + shares := delegation.GetShares() + if _, ok := valShares[validatorAddr.String()]; !ok { + valShares[validatorAddr.String()] = sdk.ZeroDec() + } + valShares[validatorAddr.String()] = valShares[validatorAddr.String()].Add(shares) + return false + }) + return false + }) + + for valAddrStr, shares := range valShares { + validatorAddr, _ := sdk.ValAddressFromBech32(valAddrStr) + valShares, ok := keeper.GetGovernorValShares(ctx, governor.GetAddress(), validatorAddr) + if !ok { + invariantStr = sdk.FormatInvariant(types.ModuleName, fmt.Sprintf("governor %s delegations", governor.GetAddress().String()), + fmt.Sprintf("validator %s shares not found", valAddrStr)) + broken = true + return true + } + if !valShares.Shares.Equal(shares) { + invariantStr = sdk.FormatInvariant(types.ModuleName, fmt.Sprintf("governor %s delegations", governor.GetAddress().String()), + fmt.Sprintf("stored shares %s for validator %s do not match actual shares %s", valShares.Shares, valAddrStr, shares)) + broken = true + return true + } + } + + keeper.IterateGovernorValShares(ctx, governor.GetAddress(), func(index int64, shares v1.GovernorValShares) bool { + if _, ok := valShares[shares.ValidatorAddress]; !ok && shares.Shares.GT(sdk.ZeroDec()) { + invariantStr = sdk.FormatInvariant(types.ModuleName, fmt.Sprintf("governor %s delegations", governor.GetAddress().String()), + fmt.Sprintf("non-zero (%s) shares stored for validator %s where there should be none", shares.Shares, shares.ValidatorAddress)) + broken = true + return true + } + return false + }) + + return broken + }) + return invariantStr, broken + } +}