github.com/Finschia/finschia-sdk@v0.48.1/x/distribution/keeper/invariants.go (about) 1 package keeper 2 3 import ( 4 "fmt" 5 6 sdk "github.com/Finschia/finschia-sdk/types" 7 "github.com/Finschia/finschia-sdk/x/distribution/types" 8 stakingtypes "github.com/Finschia/finschia-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][]sdk.AccAddress) 73 for _, del := range k.stakingKeeper.GetAllSDKDelegations(ctx) { 74 valAddr := del.GetValidatorAddr().String() 75 valDelegationAddrs[valAddr] = append(valDelegationAddrs[valAddr], del.GetDelegatorAddr()) 76 } 77 78 // iterate over all validators 79 k.stakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 80 _, _ = k.WithdrawValidatorCommission(ctx, val.GetOperator()) 81 82 delegationAddrs, ok := valDelegationAddrs[val.GetOperator().String()] 83 if ok { 84 for _, delAddr := range delegationAddrs { 85 if _, err := k.WithdrawDelegationRewards(ctx, delAddr, val.GetOperator()); err != nil { 86 panic(err) 87 } 88 } 89 } 90 91 remaining = k.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator()) 92 if len(remaining) > 0 && remaining[0].Amount.IsNegative() { 93 return true 94 } 95 96 return false 97 }) 98 99 broken := len(remaining) > 0 && remaining[0].Amount.IsNegative() 100 return sdk.FormatInvariant(types.ModuleName, "can withdraw", 101 fmt.Sprintf("remaining coins: %v\n", remaining)), broken 102 } 103 } 104 105 // ReferenceCountInvariant checks that the number of historical rewards records is correct 106 func ReferenceCountInvariant(k Keeper) sdk.Invariant { 107 return func(ctx sdk.Context) (string, bool) { 108 valCount := uint64(0) 109 k.stakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 110 valCount++ 111 return false 112 }) 113 dels := k.stakingKeeper.GetAllSDKDelegations(ctx) 114 slashCount := uint64(0) 115 k.IterateValidatorSlashEvents(ctx, 116 func(_ sdk.ValAddress, _ uint64, _ types.ValidatorSlashEvent) (stop bool) { 117 slashCount++ 118 return false 119 }) 120 121 // one record per validator (last tracked period), one record per 122 // delegation (previous period), one record per slash (previous period) 123 expected := valCount + uint64(len(dels)) + slashCount 124 count := k.GetValidatorHistoricalReferenceCount(ctx) 125 broken := count != expected 126 127 return sdk.FormatInvariant(types.ModuleName, "reference count", 128 fmt.Sprintf("expected historical reference count: %d = %v validators + %v delegations + %v slashes\n"+ 129 "total validator historical reference count: %d\n", 130 expected, valCount, len(dels), slashCount, count)), broken 131 } 132 } 133 134 // ModuleAccountInvariant checks that the coins held by the distr ModuleAccount 135 // is consistent with the sum of validator outstanding rewards 136 func ModuleAccountInvariant(k Keeper) sdk.Invariant { 137 return func(ctx sdk.Context) (string, bool) { 138 var expectedCoins sdk.DecCoins 139 k.IterateValidatorOutstandingRewards(ctx, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) { 140 expectedCoins = expectedCoins.Add(rewards.Rewards...) 141 return false 142 }) 143 144 communityPool := k.GetFeePoolCommunityCoins(ctx) 145 expectedInt, _ := expectedCoins.Add(communityPool...).TruncateDecimal() 146 147 macc := k.GetDistributionAccount(ctx) 148 balances := k.bankKeeper.GetAllBalances(ctx, macc.GetAddress()) 149 150 broken := !balances.IsEqual(expectedInt) 151 return sdk.FormatInvariant( 152 types.ModuleName, "ModuleAccount coins", 153 fmt.Sprintf("\texpected ModuleAccount coins: %s\n"+ 154 "\tdistribution ModuleAccount coins: %s\n", 155 expectedInt, balances, 156 ), 157 ), broken 158 } 159 }