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  }