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