184 lines
5.5 KiB
Go
184 lines
5.5 KiB
Go
package simulation
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
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"
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
)
|
|
|
|
// 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(app *baseapp.BaseApp) error {
|
|
err := ValAccumInvariants(d, sk)(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = DelAccumInvariants(d, sk)(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = CanWithdrawInvariant(d, stk)(app)
|
|
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(app *baseapp.BaseApp) error {
|
|
mockHeader := abci.Header{Height: app.LastBlockHeight() + 1}
|
|
ctx := app.NewContext(false, mockHeader)
|
|
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(app *baseapp.BaseApp) error {
|
|
mockHeader := abci.Header{Height: app.LastBlockHeight() + 1}
|
|
ctx := app.NewContext(false, mockHeader)
|
|
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(app *baseapp.BaseApp) error {
|
|
mockHeader := abci.Header{Height: app.LastBlockHeight() + 1}
|
|
ctx := app.NewContext(false, mockHeader)
|
|
|
|
// 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
|
|
}
|
|
}
|