github.com/Finschia/finschia-sdk@v0.48.1/x/staking/keeper/invariants.go (about)

     1  package keeper
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	sdk "github.com/Finschia/finschia-sdk/types"
     8  	"github.com/Finschia/finschia-sdk/x/staking/types"
     9  )
    10  
    11  // RegisterInvariants registers all staking invariants
    12  func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
    13  	ir.RegisterRoute(types.ModuleName, "module-accounts",
    14  		ModuleAccountInvariants(k))
    15  	ir.RegisterRoute(types.ModuleName, "nonnegative-power",
    16  		NonNegativePowerInvariant(k))
    17  	ir.RegisterRoute(types.ModuleName, "positive-delegation",
    18  		PositiveDelegationInvariant(k))
    19  	ir.RegisterRoute(types.ModuleName, "delegator-shares",
    20  		DelegatorSharesInvariant(k))
    21  }
    22  
    23  // AllInvariants runs all invariants of the staking module.
    24  func AllInvariants(k Keeper) sdk.Invariant {
    25  	return func(ctx sdk.Context) (string, bool) {
    26  		res, stop := ModuleAccountInvariants(k)(ctx)
    27  		if stop {
    28  			return res, stop
    29  		}
    30  
    31  		res, stop = NonNegativePowerInvariant(k)(ctx)
    32  		if stop {
    33  			return res, stop
    34  		}
    35  
    36  		res, stop = PositiveDelegationInvariant(k)(ctx)
    37  		if stop {
    38  			return res, stop
    39  		}
    40  
    41  		return DelegatorSharesInvariant(k)(ctx)
    42  	}
    43  }
    44  
    45  // ModuleAccountInvariants checks that the bonded and notBonded ModuleAccounts pools
    46  // reflects the tokens actively bonded and not bonded
    47  func ModuleAccountInvariants(k Keeper) sdk.Invariant {
    48  	return func(ctx sdk.Context) (string, bool) {
    49  		bonded := sdk.ZeroInt()
    50  		notBonded := sdk.ZeroInt()
    51  		bondedPool := k.GetBondedPool(ctx)
    52  		notBondedPool := k.GetNotBondedPool(ctx)
    53  		bondDenom := k.BondDenom(ctx)
    54  
    55  		k.IterateValidators(ctx, func(_ int64, validator types.ValidatorI) bool {
    56  			switch validator.GetStatus() {
    57  			case types.Bonded:
    58  				bonded = bonded.Add(validator.GetTokens())
    59  			case types.Unbonding, types.Unbonded:
    60  				notBonded = notBonded.Add(validator.GetTokens())
    61  			default:
    62  				panic("invalid validator status")
    63  			}
    64  			return false
    65  		})
    66  
    67  		k.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) bool {
    68  			for _, entry := range ubd.Entries {
    69  				notBonded = notBonded.Add(entry.Balance)
    70  			}
    71  			return false
    72  		})
    73  
    74  		poolBonded := k.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom)
    75  		poolNotBonded := k.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom)
    76  		broken := !poolBonded.Amount.Equal(bonded) || !poolNotBonded.Amount.Equal(notBonded)
    77  
    78  		// Bonded tokens should equal sum of tokens with bonded validators
    79  		// Not-bonded tokens should equal unbonding delegations	plus tokens on unbonded validators
    80  		return sdk.FormatInvariant(types.ModuleName, "bonded and not bonded module account coins", fmt.Sprintf(
    81  			"\tPool's bonded tokens: %v\n"+
    82  				"\tsum of bonded tokens: %v\n"+
    83  				"not bonded token invariance:\n"+
    84  				"\tPool's not bonded tokens: %v\n"+
    85  				"\tsum of not bonded tokens: %v\n"+
    86  				"module accounts total (bonded + not bonded):\n"+
    87  				"\tModule Accounts' tokens: %v\n"+
    88  				"\tsum tokens:              %v\n",
    89  			poolBonded, bonded, poolNotBonded, notBonded, poolBonded.Add(poolNotBonded), bonded.Add(notBonded))), broken
    90  	}
    91  }
    92  
    93  // NonNegativePowerInvariant checks that all stored validators have >= 0 power.
    94  func NonNegativePowerInvariant(k Keeper) sdk.Invariant {
    95  	return func(ctx sdk.Context) (string, bool) {
    96  		var (
    97  			msg    string
    98  			broken bool
    99  		)
   100  
   101  		iterator := k.ValidatorsPowerStoreIterator(ctx)
   102  		for ; iterator.Valid(); iterator.Next() {
   103  			validator, found := k.GetValidator(ctx, iterator.Value())
   104  			if !found {
   105  				panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value()))
   106  			}
   107  
   108  			powerKey := types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx))
   109  
   110  			if !bytes.Equal(iterator.Key(), powerKey) {
   111  				broken = true
   112  				msg += fmt.Sprintf("power store invariance:\n\tvalidator.Power: %v"+
   113  					"\n\tkey should be: %v\n\tkey in store: %v\n",
   114  					validator.GetConsensusPower(k.PowerReduction(ctx)), powerKey, iterator.Key())
   115  			}
   116  
   117  			if validator.Tokens.IsNegative() {
   118  				broken = true
   119  				msg += fmt.Sprintf("\tnegative tokens for validator: %v\n", validator)
   120  			}
   121  		}
   122  		iterator.Close()
   123  
   124  		return sdk.FormatInvariant(types.ModuleName, "nonnegative power", fmt.Sprintf("found invalid validator powers\n%s", msg)), broken
   125  	}
   126  }
   127  
   128  // PositiveDelegationInvariant checks that all stored delegations have > 0 shares.
   129  func PositiveDelegationInvariant(k Keeper) sdk.Invariant {
   130  	return func(ctx sdk.Context) (string, bool) {
   131  		var (
   132  			msg   string
   133  			count int
   134  		)
   135  
   136  		delegations := k.GetAllDelegations(ctx)
   137  		for _, delegation := range delegations {
   138  			if delegation.Shares.IsNegative() {
   139  				count++
   140  
   141  				msg += fmt.Sprintf("\tdelegation with negative shares: %+v\n", delegation)
   142  			}
   143  
   144  			if delegation.Shares.IsZero() {
   145  				count++
   146  
   147  				msg += fmt.Sprintf("\tdelegation with zero shares: %+v\n", delegation)
   148  			}
   149  		}
   150  
   151  		broken := count != 0
   152  
   153  		return sdk.FormatInvariant(types.ModuleName, "positive delegations", fmt.Sprintf(
   154  			"%d invalid delegations found\n%s", count, msg)), broken
   155  	}
   156  }
   157  
   158  // DelegatorSharesInvariant checks whether all the delegator shares which persist
   159  // in the delegator object add up to the correct total delegator shares
   160  // amount stored in each validator.
   161  func DelegatorSharesInvariant(k Keeper) sdk.Invariant {
   162  	return func(ctx sdk.Context) (string, bool) {
   163  		var (
   164  			msg    string
   165  			broken bool
   166  		)
   167  
   168  		validators := k.GetAllValidators(ctx)
   169  		validatorsDelegationShares := map[string]sdk.Dec{}
   170  
   171  		// initialize a map: validator -> its delegation shares
   172  		for _, validator := range validators {
   173  			validatorsDelegationShares[validator.GetOperator().String()] = sdk.ZeroDec()
   174  		}
   175  
   176  		// iterate through all the delegations to calculate the total delegation shares for each validator
   177  		delegations := k.GetAllDelegations(ctx)
   178  		for _, delegation := range delegations {
   179  			delegationValidatorAddr := delegation.GetValidatorAddr().String()
   180  			validatorDelegationShares := validatorsDelegationShares[delegationValidatorAddr]
   181  			validatorsDelegationShares[delegationValidatorAddr] = validatorDelegationShares.Add(delegation.Shares)
   182  		}
   183  
   184  		// for each validator, check if its total delegation shares calculated from the step above equals to its expected delegation shares
   185  		for _, validator := range validators {
   186  			expValTotalDelShares := validator.GetDelegatorShares()
   187  			calculatedValTotalDelShares := validatorsDelegationShares[validator.GetOperator().String()]
   188  			if !calculatedValTotalDelShares.Equal(expValTotalDelShares) {
   189  				broken = true
   190  				msg += fmt.Sprintf("broken delegator shares invariance:\n"+
   191  					"\tvalidator.DelegatorShares: %v\n"+
   192  					"\tsum of Delegator.Shares: %v\n", expValTotalDelShares, calculatedValTotalDelShares)
   193  			}
   194  		}
   195  
   196  		return sdk.FormatInvariant(types.ModuleName, "delegator shares", msg), broken
   197  	}
   198  }