cosmos-sdk/x/distribution/simulation/invariants.go

175 lines
5.1 KiB
Go

package simulation
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
"github.com/cosmos/cosmos-sdk/x/stake"
)
// AllInvariants runs all invariants of the distribution module
// Currently: total supply, positive power
func AllInvariants(d distr.Keeper, stk stake.Keeper) simulation.Invariant {
sk := distr.StakeKeeper(stk)
return func(ctx sdk.Context) error {
err := ValAccumInvariants(d, sk)(ctx)
if err != nil {
return err
}
err = DelAccumInvariants(d, sk)(ctx)
if err != nil {
return err
}
err = CanWithdrawInvariant(d, stk)(ctx)
if err != nil {
return err
}
return nil
}
}
// ValAccumInvariants checks that the fee pool accum == sum all validators' accum
func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant {
return func(ctx sdk.Context) error {
height := ctx.BlockHeight()
valAccum := sdk.ZeroDec()
k.IterateValidatorDistInfos(ctx, func(_ int64, vdi distr.ValidatorDistInfo) bool {
lastValPower := sk.GetLastValidatorPower(ctx, vdi.OperatorAddr)
valAccum = valAccum.Add(vdi.GetValAccum(height, sdk.NewDecFromInt(lastValPower)))
return false
})
lastTotalPower := sdk.NewDecFromInt(sk.GetLastTotalPower(ctx))
totalAccum := k.GetFeePool(ctx).GetTotalValAccum(height, lastTotalPower)
if !totalAccum.Equal(valAccum) {
return fmt.Errorf("validator accum invariance: \n\tfee pool totalAccum: %v"+
"\n\tvalidator accum \t%v\n", totalAccum.String(), valAccum.String())
}
return nil
}
}
// DelAccumInvariants checks that each validator del accum == sum all delegators' accum
func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant {
return func(ctx sdk.Context) error {
height := ctx.BlockHeight()
totalDelAccumFromVal := make(map[string]sdk.Dec) // key is the valOpAddr string
totalDelAccum := make(map[string]sdk.Dec)
// iterate the validators
iterVal := func(_ int64, vdi distr.ValidatorDistInfo) bool {
key := vdi.OperatorAddr.String()
validator := sk.Validator(ctx, vdi.OperatorAddr)
totalDelAccumFromVal[key] = vdi.GetTotalDelAccum(height,
validator.GetDelegatorShares())
// also initialize the delegation map
totalDelAccum[key] = sdk.ZeroDec()
return false
}
k.IterateValidatorDistInfos(ctx, iterVal)
// iterate the delegations
iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool {
key := ddi.ValOperatorAddr.String()
delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr)
totalDelAccum[key] = totalDelAccum[key].Add(
ddi.GetDelAccum(height, delegation.GetShares()))
return false
}
k.IterateDelegationDistInfos(ctx, iterDel)
// compare
for key, delAccumFromVal := range totalDelAccumFromVal {
sumDelAccum := totalDelAccum[key]
if !sumDelAccum.Equal(delAccumFromVal) {
logDelAccums := ""
iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool {
keyLog := ddi.ValOperatorAddr.String()
if keyLog == key {
delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr)
accum := ddi.GetDelAccum(height, delegation.GetShares())
if accum.IsPositive() {
logDelAccums += fmt.Sprintf("\n\t\tdel: %v, accum: %v",
ddi.DelegatorAddr.String(),
accum.String())
}
}
return false
}
k.IterateDelegationDistInfos(ctx, iterDel)
operAddr, err := sdk.ValAddressFromBech32(key)
if err != nil {
panic(err)
}
validator := sk.Validator(ctx, operAddr)
return fmt.Errorf("delegator accum invariance: \n"+
"\tvalidator key: %v\n"+
"\tvalidator: %+v\n"+
"\tsum delegator accum: %v\n"+
"\tvalidator's total delegator accum: %v\n"+
"\tlog of delegations with accum: %v\n",
key, validator, sumDelAccum.String(),
delAccumFromVal.String(), logDelAccums)
}
}
return nil
}
}
// CanWithdrawInvariant checks that current rewards can be completely withdrawn
func CanWithdrawInvariant(k distr.Keeper, sk stake.Keeper) simulation.Invariant {
return func(ctx sdk.Context) error {
// we don't want to write the changes
ctx, _ = ctx.CacheContext()
// withdraw all delegator & validator rewards
vdiIter := func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) {
err := k.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr)
if err != nil {
panic(err)
}
return false
}
k.IterateValidatorDistInfos(ctx, vdiIter)
ddiIter := func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) {
err := k.WithdrawDelegationReward(
ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr)
if err != nil {
panic(err)
}
return false
}
k.IterateDelegationDistInfos(ctx, ddiIter)
// assert that the fee pool is empty
feePool := k.GetFeePool(ctx)
if !feePool.TotalValAccum.Accum.IsZero() {
return fmt.Errorf("unexpected leftover validator accum")
}
bondDenom := sk.GetParams(ctx).BondDenom
if !feePool.ValPool.AmountOf(bondDenom).IsZero() {
return fmt.Errorf("unexpected leftover validator pool coins: %v",
feePool.ValPool.AmountOf(bondDenom).String())
}
// all ok
return nil
}
}