github.com/cosmos/cosmos-sdk@v0.50.10/x/distribution/keeper/invariants.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 6 sdk "github.com/cosmos/cosmos-sdk/types" 7 "github.com/cosmos/cosmos-sdk/x/distribution/types" 8 stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 9 ) 10 11 // register all distribution invariants 12 func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { 13 ir.RegisterRoute(types.ModuleName, "nonnegative-outstanding", 14 NonNegativeOutstandingInvariant(k)) 15 ir.RegisterRoute(types.ModuleName, "can-withdraw", 16 CanWithdrawInvariant(k)) 17 ir.RegisterRoute(types.ModuleName, "reference-count", 18 ReferenceCountInvariant(k)) 19 ir.RegisterRoute(types.ModuleName, "module-account", 20 ModuleAccountInvariant(k)) 21 } 22 23 // AllInvariants runs all invariants of the distribution module 24 func AllInvariants(k Keeper) sdk.Invariant { 25 return func(ctx sdk.Context) (string, bool) { 26 res, stop := CanWithdrawInvariant(k)(ctx) 27 if stop { 28 return res, stop 29 } 30 res, stop = NonNegativeOutstandingInvariant(k)(ctx) 31 if stop { 32 return res, stop 33 } 34 res, stop = ReferenceCountInvariant(k)(ctx) 35 if stop { 36 return res, stop 37 } 38 return ModuleAccountInvariant(k)(ctx) 39 } 40 } 41 42 // NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative 43 func NonNegativeOutstandingInvariant(k Keeper) sdk.Invariant { 44 return func(ctx sdk.Context) (string, bool) { 45 var msg string 46 var count int 47 var outstanding sdk.DecCoins 48 49 k.IterateValidatorOutstandingRewards(ctx, func(addr sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) { 50 outstanding = rewards.GetRewards() 51 if outstanding.IsAnyNegative() { 52 count++ 53 msg += fmt.Sprintf("\t%v has negative outstanding coins: %v\n", addr, outstanding) 54 } 55 return false 56 }) 57 broken := count != 0 58 59 return sdk.FormatInvariant(types.ModuleName, "nonnegative outstanding", 60 fmt.Sprintf("found %d validators with negative outstanding rewards\n%s", count, msg)), broken 61 } 62 } 63 64 // CanWithdrawInvariant checks that current rewards can be completely withdrawn 65 func CanWithdrawInvariant(k Keeper) sdk.Invariant { 66 return func(ctx sdk.Context) (string, bool) { 67 // cache, we don't want to write changes 68 ctx, _ = ctx.CacheContext() 69 70 var remaining sdk.DecCoins 71 72 valDelegationAddrs := make(map[string][][]byte) 73 allDelegations, err := k.stakingKeeper.GetAllSDKDelegations(ctx) 74 if err != nil { 75 panic(err) 76 } 77 78 for _, del := range allDelegations { 79 delAddr, err := k.authKeeper.AddressCodec().StringToBytes(del.GetDelegatorAddr()) 80 if err != nil { 81 panic(err) 82 } 83 valAddr := del.GetValidatorAddr() 84 valDelegationAddrs[valAddr] = append(valDelegationAddrs[valAddr], delAddr) 85 } 86 87 // iterate over all validators 88 err = k.stakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 89 valBz, err1 := k.stakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) 90 if err != nil { 91 panic(err1) 92 } 93 _, _ = k.WithdrawValidatorCommission(ctx, valBz) 94 95 delegationAddrs, ok := valDelegationAddrs[val.GetOperator()] 96 if ok { 97 for _, delAddr := range delegationAddrs { 98 if _, err := k.WithdrawDelegationRewards(ctx, delAddr, valBz); err != nil { 99 panic(err) 100 } 101 } 102 } 103 104 var err error 105 remaining, err = k.GetValidatorOutstandingRewardsCoins(ctx, valBz) 106 if err != nil { 107 panic(err) 108 } 109 110 if len(remaining) > 0 && remaining[0].Amount.IsNegative() { 111 return true 112 } 113 114 return false 115 }) 116 if err != nil { 117 panic(err) 118 } 119 120 broken := len(remaining) > 0 && remaining[0].Amount.IsNegative() 121 return sdk.FormatInvariant(types.ModuleName, "can withdraw", 122 fmt.Sprintf("remaining coins: %v\n", remaining)), broken 123 } 124 } 125 126 // ReferenceCountInvariant checks that the number of historical rewards records is correct 127 func ReferenceCountInvariant(k Keeper) sdk.Invariant { 128 return func(ctx sdk.Context) (string, bool) { 129 valCount := uint64(0) 130 err := k.stakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 131 valCount++ 132 return false 133 }) 134 if err != nil { 135 panic(err) 136 } 137 138 dels, err := k.stakingKeeper.GetAllSDKDelegations(ctx) 139 if err != nil { 140 panic(err) 141 } 142 143 slashCount := uint64(0) 144 k.IterateValidatorSlashEvents(ctx, 145 func(_ sdk.ValAddress, _ uint64, _ types.ValidatorSlashEvent) (stop bool) { 146 slashCount++ 147 return false 148 }) 149 150 // one record per validator (last tracked period), one record per 151 // delegation (previous period), one record per slash (previous period) 152 expected := valCount + uint64(len(dels)) + slashCount 153 count := k.GetValidatorHistoricalReferenceCount(ctx) 154 broken := count != expected 155 156 return sdk.FormatInvariant(types.ModuleName, "reference count", 157 fmt.Sprintf("expected historical reference count: %d = %v validators + %v delegations + %v slashes\n"+ 158 "total validator historical reference count: %d\n", 159 expected, valCount, len(dels), slashCount, count)), broken 160 } 161 } 162 163 // ModuleAccountInvariant checks that the coins held by the distr ModuleAccount 164 // is consistent with the sum of validator outstanding rewards 165 func ModuleAccountInvariant(k Keeper) sdk.Invariant { 166 return func(ctx sdk.Context) (string, bool) { 167 var expectedCoins sdk.DecCoins 168 k.IterateValidatorOutstandingRewards(ctx, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) { 169 expectedCoins = expectedCoins.Add(rewards.Rewards...) 170 return false 171 }) 172 173 communityPool, err := k.FeePool.Get(ctx) 174 if err != nil { 175 panic(err) 176 } 177 178 expectedInt, _ := expectedCoins.Add(communityPool.CommunityPool...).TruncateDecimal() 179 180 macc := k.GetDistributionAccount(ctx) 181 balances := k.bankKeeper.GetAllBalances(ctx, macc.GetAddress()) 182 183 broken := !balances.Equal(expectedInt) 184 return sdk.FormatInvariant( 185 types.ModuleName, "ModuleAccount coins", 186 fmt.Sprintf("\texpected ModuleAccount coins: %s\n"+ 187 "\tdistribution ModuleAccount coins: %s\n", 188 expectedInt, balances, 189 ), 190 ), broken 191 } 192 }