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  }