Merge PR #2526: Distribution fixes from simulation

This commit is contained in:
Christopher Goes 2018-10-18 21:58:57 +02:00 committed by GitHub
parent f527163d8a
commit e099491daa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 31 deletions

View File

@ -124,7 +124,7 @@ func invariants(app *GaiaApp) []simulation.Invariant {
banksim.NonnegativeBalanceInvariant(app.accountMapper),
distributionsim.AllInvariants(app.bankKeeper, app.distrKeeper, app.accountMapper),
govsim.AllInvariants(),
stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, app.accountMapper),
stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, app.distrKeeper, app.accountMapper),
slashingsim.AllInvariants(),
}
}

View File

@ -58,7 +58,10 @@ func handleMsgWithdrawDelegatorRewardsAll(ctx sdk.Context, msg types.MsgWithdraw
func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) sdk.Result {
k.WithdrawDelegationReward(ctx, msg.DelegatorAddr, msg.ValidatorAddr)
err := k.WithdrawDelegationReward(ctx, msg.DelegatorAddr, msg.ValidatorAddr)
if err != nil {
return err.Result()
}
tags := sdk.NewTags(
tags.Action, tags.ActionWithdrawDelegatorReward,
@ -72,7 +75,10 @@ func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDele
func handleMsgWithdrawValidatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawValidatorRewardsAll, k keeper.Keeper) sdk.Result {
k.WithdrawValidatorRewardsAll(ctx, msg.ValidatorAddr)
err := k.WithdrawValidatorRewardsAll(ctx, msg.ValidatorAddr)
if err != nil {
return err.Result()
}
tags := sdk.NewTags(
tags.Action, tags.ActionWithdrawValidatorRewardsAll,

View File

@ -5,6 +5,13 @@ import (
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// check whether a delegator distribution info exists
func (k Keeper) HasDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress,
valOperatorAddr sdk.ValAddress) (has bool) {
store := ctx.KVStore(k.storeKey)
return store.Has(GetDelegationDistInfoKey(delAddr, valOperatorAddr))
}
// get the delegator distribution info
func (k Keeper) GetDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress,
valOperatorAddr sdk.ValAddress) (ddi types.DelegationDistInfo) {
@ -64,7 +71,11 @@ func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAd
// withdraw all the rewards for a single delegation
func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delegatorAddr sdk.AccAddress,
validatorAddr sdk.ValAddress) {
validatorAddr sdk.ValAddress) sdk.Error {
if !k.HasDelegationDistInfo(ctx, delegatorAddr, validatorAddr) {
return types.ErrNoDelegationDistInfo(k.codespace)
}
height := ctx.BlockHeight()
bondedTokens := k.stakeKeeper.TotalPower(ctx)
@ -77,14 +88,17 @@ func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delegatorAddr sdk.AccA
delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(feePool, valInfo, height, bondedTokens,
validator.GetTokens(), validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission())
k.SetFeePool(ctx, feePool)
k.SetValidatorDistInfo(ctx, valInfo)
k.SetDelegationDistInfo(ctx, delInfo)
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delegatorAddr)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, withdraw.TruncateDecimal())
coinsToAdd, change := withdraw.TruncateDecimal()
feePool.CommunityPool = feePool.CommunityPool.Plus(change)
k.SetFeePool(ctx, feePool)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coinsToAdd)
if err != nil {
panic(err)
}
return nil
}
//___________________________________________________________________________________________
@ -93,8 +107,12 @@ func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delegatorAddr sdk.AccA
func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delegatorAddr sdk.AccAddress) {
height := ctx.BlockHeight()
withdraw := k.getDelegatorRewardsAll(ctx, delegatorAddr, height)
feePool := k.GetFeePool(ctx)
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delegatorAddr)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, withdraw.TruncateDecimal())
coinsToAdd, change := withdraw.TruncateDecimal()
feePool.CommunityPool = feePool.CommunityPool.Plus(change)
k.SetFeePool(ctx, feePool)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coinsToAdd)
if err != nil {
panic(err)
}

View File

@ -5,6 +5,13 @@ import (
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// check whether a validator has distribution info
func (k Keeper) HasValidatorDistInfo(ctx sdk.Context,
operatorAddr sdk.ValAddress) (exists bool) {
store := ctx.KVStore(k.storeKey)
return store.Has(GetValidatorDistInfoKey(operatorAddr))
}
// get the validator distribution info
func (k Keeper) GetValidatorDistInfo(ctx sdk.Context,
operatorAddr sdk.ValAddress) (vdi types.ValidatorDistInfo) {
@ -34,7 +41,11 @@ func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress)
}
// withdrawal all the validator rewards including the commission
func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.ValAddress) {
func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.ValAddress) sdk.Error {
if !k.HasValidatorDistInfo(ctx, operatorAddr) {
return types.ErrNoValidatorDistInfo(k.codespace)
}
// withdraw self-delegation
height := ctx.BlockHeight()
@ -50,11 +61,30 @@ func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.Va
validator.GetTokens(), validator.GetCommission())
withdraw = withdraw.Plus(commission)
k.SetValidatorDistInfo(ctx, valInfo)
k.SetFeePool(ctx, feePool)
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, accAddr)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, withdraw.TruncateDecimal())
truncated, change := withdraw.TruncateDecimal()
feePool.CommunityPool = feePool.CommunityPool.Plus(change)
k.SetFeePool(ctx, feePool)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, truncated)
if err != nil {
panic(err)
}
return nil
}
func (k Keeper) IterateValidatorDistInfos(ctx sdk.Context, fn func(index int64, distInfo types.ValidatorDistInfo) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey)
defer iter.Close()
index := int64(0)
for ; iter.Valid(); iter.Next() {
var vdi types.ValidatorDistInfo
k.cdc.MustUnmarshalBinary(iter.Value(), &vdi)
if fn(index, vdi) {
return
}
index++
}
}

View File

@ -43,9 +43,11 @@ func (coin DecCoin) Minus(coinB DecCoin) DecCoin {
return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)}
}
// return the decimal coins with trunctated decimals
func (coin DecCoin) TruncateDecimal() sdk.Coin {
return sdk.NewCoin(coin.Denom, coin.Amount.TruncateInt())
// return the decimal coins with trunctated decimals, and return the change
func (coin DecCoin) TruncateDecimal() (sdk.Coin, DecCoin) {
truncated := coin.Amount.TruncateInt()
change := coin.Amount.Sub(sdk.NewDecFromInt(truncated))
return sdk.NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change}
}
//_______________________________________________________________________
@ -61,13 +63,16 @@ func NewDecCoins(coins sdk.Coins) DecCoins {
return dcs
}
// return the coins with trunctated decimals
func (coins DecCoins) TruncateDecimal() sdk.Coins {
// return the coins with trunctated decimals, and return the change
func (coins DecCoins) TruncateDecimal() (sdk.Coins, DecCoins) {
changeSum := DecCoins{}
out := make(sdk.Coins, len(coins))
for i, coin := range coins {
out[i] = coin.TruncateDecimal()
truncated, change := coin.TruncateDecimal()
out[i] = truncated
changeSum = changeSum.Plus(DecCoins{change})
}
return out
return out, changeSum
}
// Plus combines two sets of coins
@ -147,3 +152,27 @@ func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins {
}
return res
}
// returns the amount of a denom from deccoins
func (coins DecCoins) AmountOf(denom string) sdk.Dec {
switch len(coins) {
case 0:
return sdk.ZeroDec()
case 1:
coin := coins[0]
if coin.Denom == denom {
return coin.Amount
}
return sdk.ZeroDec()
default:
midIdx := len(coins) / 2 // 2:1, 3:1, 4:2
coin := coins[midIdx]
if denom < coin.Denom {
return coins[:midIdx].AmountOf(denom)
} else if denom == coin.Denom {
return coin.Amount
} else {
return coins[midIdx+1:].AmountOf(denom)
}
}
}

View File

@ -8,8 +8,9 @@ import (
type CodeType = sdk.CodeType
const (
DefaultCodespace sdk.CodespaceType = 6
CodeInvalidInput CodeType = 103
DefaultCodespace sdk.CodespaceType = 6
CodeInvalidInput CodeType = 103
CodeNoDistributionInfo CodeType = 104
)
func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error {
@ -21,3 +22,9 @@ func ErrNilWithdrawAddr(codespace sdk.CodespaceType) sdk.Error {
func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil")
}
func ErrNoDelegationDistInfo(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeNoDistributionInfo, "no delegation distribution info")
}
func ErrNoValidatorDistInfo(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeNoDistributionInfo, "no validator distribution info")
}

View File

@ -551,6 +551,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress,
if err != nil {
return types.Redelegation{}, err
}
k.OnDelegationCreated(ctx, delAddr, valDstAddr)
// create the unbonding delegation
minTime, height, completeNow := k.getBeginInfo(ctx, valSrcAddr)

View File

@ -7,6 +7,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"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"
@ -14,9 +15,9 @@ import (
// AllInvariants runs all invariants of the stake module.
// Currently: total supply, positive power
func AllInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simulation.Invariant {
func AllInvariants(ck bank.Keeper, k stake.Keeper, d distribution.Keeper, am auth.AccountMapper) simulation.Invariant {
return func(app *baseapp.BaseApp) error {
err := SupplyInvariants(ck, k, am)(app)
err := SupplyInvariants(ck, k, d, am)(app)
if err != nil {
return err
}
@ -31,19 +32,19 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simula
// SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations
// nolint: unparam
func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simulation.Invariant {
func SupplyInvariants(ck bank.Keeper, k stake.Keeper, d distribution.Keeper, am auth.AccountMapper) simulation.Invariant {
return func(app *baseapp.BaseApp) error {
ctx := app.NewContext(false, abci.Header{})
pool := k.GetPool(ctx)
loose := sdk.ZeroInt()
loose := sdk.ZeroDec()
bonded := sdk.ZeroDec()
am.IterateAccounts(ctx, func(acc auth.Account) bool {
loose = loose.Add(acc.GetCoins().AmountOf("steak"))
loose = loose.Add(sdk.NewDecFromInt(acc.GetCoins().AmountOf("steak")))
return false
})
k.IterateUnbondingDelegations(ctx, func(_ int64, ubd stake.UnbondingDelegation) bool {
loose = loose.Add(ubd.Balance.Amount)
loose = loose.Add(sdk.NewDecFromInt(ubd.Balance.Amount))
return false
})
k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool {
@ -51,21 +52,36 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim
case sdk.Bonded:
bonded = bonded.Add(validator.GetPower())
case sdk.Unbonding:
loose = loose.Add(validator.GetTokens().RoundInt())
loose = loose.Add(validator.GetTokens())
case sdk.Unbonded:
loose = loose.Add(validator.GetTokens().RoundInt())
loose = loose.Add(validator.GetTokens())
}
return false
})
feePool := d.GetFeePool(ctx)
// add community pool
loose = loose.Add(feePool.CommunityPool.AmountOf("steak"))
// add validator distribution pool
loose = loose.Add(feePool.Pool.AmountOf("steak"))
// add validator distribution commission and yet-to-be-withdrawn-by-delegators
d.IterateValidatorDistInfos(ctx, func(_ int64, distInfo distribution.ValidatorDistInfo) (stop bool) {
loose = loose.Add(distInfo.Pool.AmountOf("steak"))
loose = loose.Add(distInfo.PoolCommission.AmountOf("steak"))
return false
})
// Loose tokens should equal coin supply plus unbonding delegations plus tokens on unbonded validators
if pool.LooseTokens.RoundInt64() != loose.Int64() {
return fmt.Errorf("expected loose tokens to equal total steak held by accounts - pool.LooseTokens: %v, sum of account tokens: %v", pool.LooseTokens.RoundInt64(), loose.Int64())
if !pool.LooseTokens.Equal(loose) {
return fmt.Errorf("expected loose tokens to equal total steak held by accounts - pool.LooseTokens: %v, sum of account tokens: %v", pool.LooseTokens, loose)
}
// Bonded tokens should equal sum of tokens with bonded validators
if pool.BondedTokens.RoundInt64() != bonded.RoundInt64() {
return fmt.Errorf("expected bonded tokens to equal total steak held by bonded validators - pool.BondedTokens: %v, sum of bonded validator tokens: %v", pool.BondedTokens.RoundInt64(), bonded.RoundInt64())
if !pool.BondedTokens.Equal(bonded) {
return fmt.Errorf("expected bonded tokens to equal total steak held by bonded validators - pool.BondedTokens: %v, sum of bonded validator tokens: %v", pool.BondedTokens, bonded)
}
// TODO Inflation check on total supply