Merge PR #3099: F1 fee distribution

This commit is contained in:
Christopher Goes 2019-01-16 22:38:05 +01:00 committed by GitHub
parent 26cb0a125a
commit 2942f83ff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1785 additions and 2253 deletions

View File

@ -57,6 +57,8 @@ FEATURES
* [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) `add-genesis-account` can take both account addresses and key names
* SDK
- \#3099 Implement F1 fee distribution
- [\#2926](https://github.com/cosmos/cosmos-sdk/issues/2926) Add TxEncoder to client TxBuilder.
* \#2694 Vesting account implementation.
* \#2996 Update the `AccountKeeper` to contain params used in the context of
the ante handler.

View File

@ -5,11 +5,12 @@ import (
"errors"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/libs/common"

View File

@ -238,6 +238,9 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt
app.accountKeeper.SetAccount(ctx, acc)
}
// initialize distribution (must happen before staking)
distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData)
// load the initial staking information
validators, err := staking.InitGenesis(ctx, app.stakingKeeper, genesisState.StakingData)
if err != nil {
@ -249,7 +252,6 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData)
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData)
distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData)
// validate genesis state
err = GaiaValidateGenesisState(genesisState)
@ -370,3 +372,11 @@ func (h StakingHooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAd
h.dh.BeforeDelegationRemoved(ctx, delAddr, valAddr)
h.sh.BeforeDelegationRemoved(ctx, delAddr, valAddr)
}
func (h StakingHooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
h.dh.AfterDelegationModified(ctx, delAddr, valAddr)
h.sh.AfterDelegationModified(ctx, delAddr, valAddr)
}
func (h StakingHooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {
h.dh.BeforeValidatorSlashed(ctx, valAddr, fraction)
h.sh.BeforeValidatorSlashed(ctx, valAddr, fraction)
}

View File

@ -2,7 +2,6 @@ package app
import (
"encoding/json"
"fmt"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
@ -14,7 +13,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
"github.com/cosmos/cosmos-sdk/x/slashing"
staking "github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking"
)
// export the state of gaia for a genesis file
@ -62,55 +61,41 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
/* Handle fee distribution state. */
// withdraw all delegator & validator rewards
vdiIter := func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) {
err := app.distrKeeper.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr)
if err != nil {
panic(err)
}
return false
}
app.distrKeeper.IterateValidatorDistInfos(ctx, vdiIter)
ddiIter := func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) {
err := app.distrKeeper.WithdrawDelegationReward(
ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr)
if err != nil {
panic(err)
}
return false
}
app.distrKeeper.IterateDelegationDistInfos(ctx, ddiIter)
app.assertRuntimeInvariantsOnContext(ctx)
// set distribution info withdrawal heights to 0
app.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, delInfo distr.DelegationDistInfo) (stop bool) {
delInfo.DelPoolWithdrawalHeight = 0
app.distrKeeper.SetDelegationDistInfo(ctx, delInfo)
return false
})
app.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) {
valInfo.FeePoolWithdrawalHeight = 0
valInfo.DelAccum.UpdateHeight = 0
app.distrKeeper.SetValidatorDistInfo(ctx, valInfo)
// withdraw all validator commission
app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
_ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator())
return false
})
// assert that the fee pool is empty
feePool := app.distrKeeper.GetFeePool(ctx)
if !feePool.TotalValAccum.Accum.IsZero() {
panic("unexpected leftover validator accum")
}
bondDenom := app.stakingKeeper.GetParams(ctx).BondDenom
if !feePool.ValPool.AmountOf(bondDenom).IsZero() {
panic(fmt.Sprintf("unexpected leftover validator pool coins: %v",
feePool.ValPool.AmountOf(bondDenom).String()))
// withdraw all delegator rewards
dels := app.stakingKeeper.GetAllDelegations(ctx)
for _, delegation := range dels {
_ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr)
}
// reset fee pool height, save fee pool
feePool.TotalValAccum = distr.NewTotalAccum(0)
app.distrKeeper.SetFeePool(ctx, feePool)
// clear validator slash events
app.distrKeeper.DeleteValidatorSlashEvents(ctx)
// clear validator historical rewards
app.distrKeeper.DeleteValidatorHistoricalRewards(ctx)
// set context height to zero
height := ctx.BlockHeight()
ctx = ctx.WithBlockHeight(0)
// reinitialize all validators
app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator())
return false
})
// reinitialize all delegations
for _, del := range dels {
app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddr, del.ValidatorAddr)
}
// reset context height
ctx = ctx.WithBlockHeight(height)
/* Handle staking state. */

View File

@ -16,7 +16,7 @@ import (
func (app *GaiaApp) runtimeInvariants() []simulation.Invariant {
return []simulation.Invariant{
banksim.NonnegativeBalanceInvariant(app.accountKeeper),
distrsim.ValAccumInvariants(app.distrKeeper, app.stakingKeeper),
distrsim.NonNegativeOutstandingInvariant(app.distrKeeper),
stakingsim.SupplyInvariants(app.bankKeeper, app.stakingKeeper,
app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper),
stakingsim.NonNegativePowerInvariant(app.stakingKeeper),

View File

@ -29,7 +29,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
staking "github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
@ -120,8 +120,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
Params: slashing.Params{
MaxEvidenceAge: stakingGenesis.Params.UnbondingTime,
SignedBlocksWindow: int64(randIntBetween(r, 10, 1000)),
DowntimeJailDuration: time.Duration(randIntBetween(r, 60, 60*60*24)) * time.Second,
MinSignedPerWindow: sdk.NewDecWithPrec(int64(r.Intn(10)), 1),
DowntimeJailDuration: time.Duration(randIntBetween(r, 60, 60*60*24)) * time.Second,
SlashFractionDoubleSign: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(50) + 1))),
SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))),
},
@ -160,12 +160,20 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
stakingGenesis.Validators = validators
stakingGenesis.Bonds = delegations
distrGenesis := distr.GenesisState{
FeePool: distr.InitialFeePool(),
CommunityTax: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)),
BaseProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)),
BonusProposerReward: sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)),
}
fmt.Printf("Selected randomly generated distribution parameters:\n\t%+v\n", distrGenesis)
genesis := GenesisState{
Accounts: genesisAccounts,
AuthData: authGenesis,
StakingData: stakingGenesis,
MintData: mintGenesis,
DistrData: distr.DefaultGenesisWithValidators(valAddrs),
DistrData: distrGenesis,
SlashingData: slashingGenesis,
GovData: govGenesis,
}
@ -188,9 +196,8 @@ func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
{5, authsim.SimulateDeductFee(app.accountKeeper, app.feeCollectionKeeper)},
{100, banksim.SingleInputSendMsg(app.accountKeeper, app.bankKeeper)},
{50, distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper)},
{50, distrsim.SimulateMsgWithdrawDelegatorRewardsAll(app.accountKeeper, app.distrKeeper)},
{50, distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper)},
{50, distrsim.SimulateMsgWithdrawValidatorRewardsAll(app.accountKeeper, app.distrKeeper)},
{50, distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper)},
{5, govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, app.stakingKeeper)},
{100, govsim.SimulateMsgDeposit(app.govKeeper)},
{100, stakingsim.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper)},

View File

@ -76,7 +76,7 @@ func TestGaiaCLIMinimumFees(t *testing.T) {
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(feeDenom, 23))
success, _, _ = f.TxSend(keyFoo, barAddr, sdk.NewInt64Coin(feeDenom, 10), txFees)
require.True(f.T, success)
tests.WaitForNextNBlocksTM(1, f.Port)
tests.WaitForNextNBlocksTM(2, f.Port)
// Ensure tx w/ improper fees (footoken) fails
txFees = fmt.Sprintf("--fees=%s", sdk.NewInt64Coin(fooDenom, 23))

View File

@ -4,6 +4,8 @@ import (
"encoding/json"
"io"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/store"

View File

@ -69,6 +69,11 @@ func (v Validator) GetMoniker() string {
return ""
}
// Implements sdk.Validator
func (v Validator) GetDelegatorShareExRate() sdk.Dec {
return sdk.ZeroDec()
}
// Implements sdk.Validator
type ValidatorSet struct {
Validators []Validator

View File

@ -3,34 +3,32 @@ package types
import (
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Coins which can have additional decimal points
type DecCoin struct {
Denom string `json:"denom"`
Amount sdk.Dec `json:"amount"`
Denom string `json:"denom"`
Amount Dec `json:"amount"`
}
func NewDecCoin(denom string, amount int64) DecCoin {
return DecCoin{
Denom: denom,
Amount: sdk.NewDec(amount),
Amount: NewDec(amount),
}
}
func NewDecCoinFromDec(denom string, amount sdk.Dec) DecCoin {
func NewDecCoinFromDec(denom string, amount Dec) DecCoin {
return DecCoin{
Denom: denom,
Amount: amount,
}
}
func NewDecCoinFromCoin(coin sdk.Coin) DecCoin {
func NewDecCoinFromCoin(coin Coin) DecCoin {
return DecCoin{
Denom: coin.Denom,
Amount: sdk.NewDecFromInt(coin.Amount),
Amount: NewDecFromInt(coin.Amount),
}
}
@ -51,10 +49,10 @@ func (coin DecCoin) Minus(coinB DecCoin) DecCoin {
}
// return the decimal coins with trunctated decimals, and return the change
func (coin DecCoin) TruncateDecimal() (sdk.Coin, DecCoin) {
func (coin DecCoin) TruncateDecimal() (Coin, DecCoin) {
truncated := coin.Amount.TruncateInt()
change := coin.Amount.Sub(sdk.NewDecFromInt(truncated))
return sdk.NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change}
change := coin.Amount.Sub(NewDecFromInt(truncated))
return NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change}
}
//_______________________________________________________________________
@ -62,7 +60,7 @@ func (coin DecCoin) TruncateDecimal() (sdk.Coin, DecCoin) {
// coins with decimal
type DecCoins []DecCoin
func NewDecCoins(coins sdk.Coins) DecCoins {
func NewDecCoins(coins Coins) DecCoins {
dcs := make(DecCoins, len(coins))
for i, coin := range coins {
dcs[i] = NewDecCoinFromCoin(coin)
@ -71,9 +69,9 @@ func NewDecCoins(coins sdk.Coins) DecCoins {
}
// return the coins with trunctated decimals, and return the change
func (coins DecCoins) TruncateDecimal() (sdk.Coins, DecCoins) {
func (coins DecCoins) TruncateDecimal() (Coins, DecCoins) {
changeSum := DecCoins{}
out := make(sdk.Coins, len(coins))
out := make(Coins, len(coins))
for i, coin := range coins {
truncated, change := coin.TruncateDecimal()
out[i] = truncated
@ -135,7 +133,7 @@ func (coins DecCoins) Minus(coinsB DecCoins) DecCoins {
}
// multiply all the coins by a decimal
func (coins DecCoins) MulDec(d sdk.Dec) DecCoins {
func (coins DecCoins) MulDec(d Dec) DecCoins {
res := make([]DecCoin, len(coins))
for i, coin := range coins {
product := DecCoin{
@ -148,7 +146,7 @@ func (coins DecCoins) MulDec(d sdk.Dec) DecCoins {
}
// divide all the coins by a decimal
func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins {
func (coins DecCoins) QuoDec(d Dec) DecCoins {
res := make([]DecCoin, len(coins))
for i, coin := range coins {
quotient := DecCoin{
@ -161,16 +159,16 @@ func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins {
}
// returns the amount of a denom from deccoins
func (coins DecCoins) AmountOf(denom string) sdk.Dec {
func (coins DecCoins) AmountOf(denom string) Dec {
switch len(coins) {
case 0:
return sdk.ZeroDec()
return ZeroDec()
case 1:
coin := coins[0]
if coin.Denom == denom {
return coin.Amount
}
return sdk.ZeroDec()
return ZeroDec()
default:
midIdx := len(coins) / 2 // binary search
coin := coins[midIdx]

View File

@ -5,14 +5,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestPlusDecCoin(t *testing.T) {
decCoinA1 := DecCoin{"A", sdk.NewDecWithPrec(11, 1)}
decCoinA2 := DecCoin{"A", sdk.NewDecWithPrec(22, 1)}
decCoinB1 := DecCoin{"B", sdk.NewDecWithPrec(11, 1)}
decCoinA1 := DecCoin{"A", NewDecWithPrec(11, 1)}
decCoinA2 := DecCoin{"A", NewDecWithPrec(22, 1)}
decCoinB1 := DecCoin{"B", NewDecWithPrec(11, 1)}
// regular add
res := decCoinA1.Plus(decCoinA1)
@ -25,11 +23,11 @@ func TestPlusDecCoin(t *testing.T) {
}
func TestPlusCoins(t *testing.T) {
one := sdk.NewDec(1)
zero := sdk.NewDec(0)
negone := sdk.NewDec(-1)
two := sdk.NewDec(2)
func TestPlusDecCoins(t *testing.T) {
one := NewDec(1)
zero := NewDec(0)
negone := NewDec(-1)
two := NewDec(2)
cases := []struct {
inputOne DecCoins

View File

@ -45,8 +45,9 @@ type Validator interface {
GetPower() Int // validation power
GetTokens() Int // validation tokens
GetCommission() Dec // validator commission rate
GetDelegatorShares() Dec // Total out standing delegator shares
GetDelegatorShares() Dec // total outstanding delegator shares
GetBondHeight() int64 // height in which the validator became active
GetDelegatorShareExRate() Dec // tokens per delegator share exchange rate
}
// validator which fulfills abci validator interface for use in Tendermint
@ -126,4 +127,6 @@ type StakingHooks interface {
BeforeDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is created
BeforeDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation's shares are modified
BeforeDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is removed
AfterDelegationModified(ctx Context, delAddr AccAddress, valAddr ValAddress)
BeforeValidatorSlashed(ctx Context, valAddr ValAddress, fraction Dec)
}

View File

@ -10,21 +10,8 @@ import (
// set the proposer for determining distribution during endblock
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
if ctx.BlockHeight() > 1 {
previousPercentPrecommitVotes := getPreviousPercentPrecommitVotes(req)
previousProposer := k.GetPreviousProposerConsAddr(ctx)
k.AllocateTokens(ctx, previousPercentPrecommitVotes, previousProposer)
}
consAddr := sdk.ConsAddress(req.Header.ProposerAddress)
k.SetPreviousProposerConsAddr(ctx, consAddr)
}
// percent precommit votes for the previous block
func getPreviousPercentPrecommitVotes(req abci.RequestBeginBlock) sdk.Dec {
// determine the total number of signed power
totalPower, sumPrecommitPower := int64(0), int64(0)
// determine the total power signing the block
var totalPower, sumPrecommitPower int64
for _, voteInfo := range req.LastCommitInfo.GetVotes() {
totalPower += voteInfo.Validator.Power
if voteInfo.SignedLastBlock {
@ -32,8 +19,15 @@ func getPreviousPercentPrecommitVotes(req abci.RequestBeginBlock) sdk.Dec {
}
}
if totalPower == 0 {
return sdk.ZeroDec()
// TODO this is Tendermint-dependent
// ref https://github.com/cosmos/cosmos-sdk/issues/3095
if ctx.BlockHeight() > 1 {
previousProposer := k.GetPreviousProposerConsAddr(ctx)
k.AllocateTokens(ctx, sumPrecommitPower, totalPower, previousProposer, req.LastCommitInfo.GetVotes())
}
return sdk.NewDec(sumPrecommitPower).Quo(sdk.NewDec(totalPower))
// record the proposer for when we payout on the next block
consAddr := sdk.ConsAddress(req.Header.ProposerAddress)
k.SetPreviousProposerConsAddr(ctx, consAddr)
}

View File

@ -11,18 +11,9 @@ type (
Keeper = keeper.Keeper
Hooks = keeper.Hooks
DelegatorWithdrawInfo = types.DelegatorWithdrawInfo
DelegationDistInfo = types.DelegationDistInfo
ValidatorDistInfo = types.ValidatorDistInfo
TotalAccum = types.TotalAccum
FeePool = types.FeePool
DecCoin = types.DecCoin
DecCoins = types.DecCoins
MsgSetWithdrawAddress = types.MsgSetWithdrawAddress
MsgWithdrawDelegatorRewardsAll = types.MsgWithdrawDelegatorRewardsAll
MsgWithdrawDelegatorReward = types.MsgWithdrawDelegatorReward
MsgWithdrawValidatorRewardsAll = types.MsgWithdrawValidatorRewardsAll
MsgWithdrawValidatorCommission = types.MsgWithdrawValidatorCommission
GenesisState = types.GenesisState
@ -32,39 +23,6 @@ type (
FeeCollectionKeeper = types.FeeCollectionKeeper
)
var (
NewKeeper = keeper.NewKeeper
GetValidatorDistInfoKey = keeper.GetValidatorDistInfoKey
GetDelegationDistInfoKey = keeper.GetDelegationDistInfoKey
GetDelegationDistInfosKey = keeper.GetDelegationDistInfosKey
GetDelegatorWithdrawAddrKey = keeper.GetDelegatorWithdrawAddrKey
FeePoolKey = keeper.FeePoolKey
ValidatorDistInfoKey = keeper.ValidatorDistInfoKey
DelegationDistInfoKey = keeper.DelegationDistInfoKey
DelegatorWithdrawInfoKey = keeper.DelegatorWithdrawInfoKey
ProposerKey = keeper.ProposerKey
DefaultParamspace = keeper.DefaultParamspace
InitialFeePool = types.InitialFeePool
NewGenesisState = types.NewGenesisState
ValidateGenesis = types.ValidateGenesis
DefaultGenesisState = types.DefaultGenesisState
DefaultGenesisWithValidators = types.DefaultGenesisWithValidators
RegisterCodec = types.RegisterCodec
NewMsgSetWithdrawAddress = types.NewMsgSetWithdrawAddress
NewMsgWithdrawDelegatorRewardsAll = types.NewMsgWithdrawDelegatorRewardsAll
NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward
NewMsgWithdrawValidatorRewardsAll = types.NewMsgWithdrawValidatorRewardsAll
NewDecCoins = types.NewDecCoins
NewTotalAccum = types.NewTotalAccum
)
const (
DefaultCodespace = types.DefaultCodespace
CodeInvalidInput = types.CodeInvalidInput
@ -81,4 +39,16 @@ var (
TagValidator = tags.Validator
TagDelegator = tags.Delegator
NewMsgSetWithdrawAddress = types.NewMsgSetWithdrawAddress
NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward
NewMsgWithdrawValidatorCommission = types.NewMsgWithdrawValidatorCommission
NewKeeper = keeper.NewKeeper
DefaultParamspace = keeper.DefaultParamspace
RegisterCodec = types.RegisterCodec
DefaultGenesisState = types.DefaultGenesisState
ValidateGenesis = types.ValidateGenesis
InitialFeePool = types.InitialFeePool
)

View File

@ -43,7 +43,7 @@ func GetTxCmd(storeKey string, cdc *amino.Codec) *cobra.Command {
func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "withdraw-rewards",
Short: "withdraw rewards for either: all-delegations, a delegation, or a validator",
Short: "withdraw rewards for either a delegation or a validator",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
@ -68,8 +68,8 @@ func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command {
return err
}
valAddr := sdk.ValAddress(addr.Bytes())
msg = types.NewMsgWithdrawValidatorRewardsAll(valAddr)
case onlyFromVal != "":
msg = types.NewMsgWithdrawValidatorCommission(valAddr)
default:
delAddr, err := cliCtx.GetFromAddress()
if err != nil {
return err
@ -81,12 +81,6 @@ func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command {
}
msg = types.NewMsgWithdrawDelegatorReward(delAddr, valAddr)
default:
delAddr, err := cliCtx.GetFromAddress()
if err != nil {
return err
}
msg = types.NewMsgWithdrawDelegatorRewardsAll(delAddr)
}
if cliCtx.GenerateOnly {

View File

@ -11,30 +11,97 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) {
keeper.SetCommunityTax(ctx, data.CommunityTax)
keeper.SetBaseProposerReward(ctx, data.BaseProposerReward)
keeper.SetBonusProposerReward(ctx, data.BonusProposerReward)
for _, vdi := range data.ValidatorDistInfos {
keeper.SetValidatorDistInfo(ctx, vdi)
}
for _, ddi := range data.DelegationDistInfos {
keeper.SetDelegationDistInfo(ctx, ddi)
}
for _, dw := range data.DelegatorWithdrawInfos {
keeper.SetDelegatorWithdrawAddr(ctx, dw.DelegatorAddr, dw.WithdrawAddr)
for _, dwi := range data.DelegatorWithdrawInfos {
keeper.SetDelegatorWithdrawAddr(ctx, dwi.DelegatorAddr, dwi.WithdrawAddr)
}
keeper.SetPreviousProposerConsAddr(ctx, data.PreviousProposer)
keeper.SetOutstandingRewards(ctx, data.OutstandingRewards)
for _, acc := range data.ValidatorAccumulatedCommissions {
keeper.SetValidatorAccumulatedCommission(ctx, acc.ValidatorAddr, acc.Accumulated)
}
for _, his := range data.ValidatorHistoricalRewards {
keeper.SetValidatorHistoricalRewards(ctx, his.ValidatorAddr, his.Period, his.Rewards)
}
for _, cur := range data.ValidatorCurrentRewards {
keeper.SetValidatorCurrentRewards(ctx, cur.ValidatorAddr, cur.Rewards)
}
for _, del := range data.DelegatorStartingInfos {
keeper.SetDelegatorStartingInfo(ctx, del.ValidatorAddr, del.DelegatorAddr, del.StartingInfo)
}
for _, evt := range data.ValidatorSlashEvents {
keeper.SetValidatorSlashEvent(ctx, evt.ValidatorAddr, evt.Height, evt.Event)
}
}
// ExportGenesis returns a GenesisState for a given context and keeper. The
// GenesisState will contain the pool, and validator/delegator distribution info's
// ExportGenesis returns a GenesisState for a given context and keeper.
func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
feePool := keeper.GetFeePool(ctx)
communityTax := keeper.GetCommunityTax(ctx)
baseProposerRewards := keeper.GetBaseProposerReward(ctx)
bonusProposerRewards := keeper.GetBonusProposerReward(ctx)
vdis := keeper.GetAllValidatorDistInfos(ctx)
ddis := keeper.GetAllDelegationDistInfos(ctx)
dwis := keeper.GetAllDelegatorWithdrawInfos(ctx)
dwi := make([]types.DelegatorWithdrawInfo, 0)
keeper.IterateDelegatorWithdrawAddrs(ctx, func(del sdk.AccAddress, addr sdk.AccAddress) (stop bool) {
dwi = append(dwi, types.DelegatorWithdrawInfo{
DelegatorAddr: del,
WithdrawAddr: addr,
})
return false
})
pp := keeper.GetPreviousProposerConsAddr(ctx)
return NewGenesisState(feePool, communityTax, baseProposerRewards,
bonusProposerRewards, vdis, ddis, dwis, pp)
outstanding := keeper.GetOutstandingRewards(ctx)
acc := make([]types.ValidatorAccumulatedCommissionRecord, 0)
keeper.IterateValidatorAccumulatedCommissions(ctx,
func(addr sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool) {
acc = append(acc, types.ValidatorAccumulatedCommissionRecord{
ValidatorAddr: addr,
Accumulated: commission,
})
return false
},
)
his := make([]types.ValidatorHistoricalRewardsRecord, 0)
keeper.IterateValidatorHistoricalRewards(ctx,
func(val sdk.ValAddress, period uint64, rewards types.ValidatorHistoricalRewards) (stop bool) {
his = append(his, types.ValidatorHistoricalRewardsRecord{
ValidatorAddr: val,
Period: period,
Rewards: rewards,
})
return false
},
)
cur := make([]types.ValidatorCurrentRewardsRecord, 0)
keeper.IterateValidatorCurrentRewards(ctx,
func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool) {
cur = append(cur, types.ValidatorCurrentRewardsRecord{
ValidatorAddr: val,
Rewards: rewards,
})
return false
},
)
dels := make([]types.DelegatorStartingInfoRecord, 0)
keeper.IterateDelegatorStartingInfos(ctx,
func(val sdk.ValAddress, del sdk.AccAddress, info types.DelegatorStartingInfo) (stop bool) {
dels = append(dels, types.DelegatorStartingInfoRecord{
ValidatorAddr: val,
DelegatorAddr: del,
StartingInfo: info,
})
return false
},
)
slashes := make([]types.ValidatorSlashEventRecord, 0)
keeper.IterateValidatorSlashEvents(ctx,
func(val sdk.ValAddress, height uint64, event types.ValidatorSlashEvent) (stop bool) {
slashes = append(slashes, types.ValidatorSlashEventRecord{
ValidatorAddr: val,
Height: height,
Event: event,
})
return false
},
)
return types.NewGenesisState(feePool, communityTax, baseProposerRewards, bonusProposerRewards, dwi, pp, outstanding,
acc, his, cur, dels, slashes)
}

View File

@ -13,24 +13,20 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
switch msg := msg.(type) {
case types.MsgSetWithdrawAddress:
return handleMsgModifyWithdrawAddress(ctx, msg, k)
case types.MsgWithdrawDelegatorRewardsAll:
return handleMsgWithdrawDelegatorRewardsAll(ctx, msg, k)
case types.MsgWithdrawDelegatorReward:
return handleMsgWithdrawDelegatorReward(ctx, msg, k)
case types.MsgWithdrawValidatorRewardsAll:
return handleMsgWithdrawValidatorRewardsAll(ctx, msg, k)
case types.MsgWithdrawValidatorCommission:
return handleMsgWithdrawValidatorCommission(ctx, msg, k)
default:
return sdk.ErrTxDecode("invalid message parse in distribution module").Result()
}
}
}
//_____________________________________________________________________
// These functions assume everything has been authenticated,
// now we just perform action and save
// These functions assume everything has been authenticated (ValidateBasic passed, and signatures checked)
func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAddress, k keeper.Keeper) sdk.Result {
k.SetDelegatorWithdrawAddr(ctx, msg.DelegatorAddr, msg.WithdrawAddr)
tags := sdk.NewTags(
@ -41,19 +37,9 @@ func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAdd
}
}
func handleMsgWithdrawDelegatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawDelegatorRewardsAll, k keeper.Keeper) sdk.Result {
k.WithdrawDelegationRewardsAll(ctx, msg.DelegatorAddr)
tags := sdk.NewTags(
tags.Delegator, []byte(msg.DelegatorAddr.String()),
)
return sdk.Result{
Tags: tags,
}
}
func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) sdk.Result {
err := k.WithdrawDelegationReward(ctx, msg.DelegatorAddr, msg.ValidatorAddr)
err := k.WithdrawDelegationRewards(ctx, msg.DelegatorAddr, msg.ValidatorAddr)
if err != nil {
return err.Result()
}
@ -67,8 +53,9 @@ func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDele
}
}
func handleMsgWithdrawValidatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawValidatorRewardsAll, k keeper.Keeper) sdk.Result {
err := k.WithdrawValidatorRewardsAll(ctx, msg.ValidatorAddr)
func handleMsgWithdrawValidatorCommission(ctx sdk.Context, msg types.MsgWithdrawValidatorCommission, k keeper.Keeper) sdk.Result {
err := k.WithdrawValidatorCommission(ctx, msg.ValidatorAddr)
if err != nil {
return err.Result()
}

View File

@ -1,57 +1,85 @@
package keeper
import (
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// Allocate fees handles distribution of the collected fees
func (k Keeper) AllocateTokens(ctx sdk.Context, percentVotes sdk.Dec, proposer sdk.ConsAddress) {
// get the proposer of this block
proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, proposer)
proposerDist := k.GetValidatorDistInfo(ctx, proposerValidator.GetOperator())
// get the fees which have been getting collected through all the
// transactions in the block
feesCollected := k.feeCollectionKeeper.GetCollectedFees(ctx)
feesCollectedDec := types.NewDecCoins(feesCollected)
// allocate fees handles distribution of the collected fees
func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower int64, proposer sdk.ConsAddress, votes []abci.VoteInfo) {
// fetch collected fees & fee pool
feesCollectedInt := k.feeCollectionKeeper.GetCollectedFees(ctx)
feesCollected := sdk.NewDecCoins(feesCollectedInt)
feePool := k.GetFeePool(ctx)
// Temporary workaround to keep CanWithdrawInvariant happy.
// General discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634
if k.stakingKeeper.GetLastTotalPower(ctx).IsZero() {
feePool.CommunityPool = feePool.CommunityPool.Plus(feesCollectedDec)
// clear collected fees, which will now be distributed
k.feeCollectionKeeper.ClearCollectedFees(ctx)
// temporary workaround to keep CanWithdrawInvariant happy
// general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634
if totalPower == 0 {
feePool.CommunityPool = feePool.CommunityPool.Plus(feesCollected)
k.SetFeePool(ctx, feePool)
k.feeCollectionKeeper.ClearCollectedFees(ctx)
return
}
// allocated rewards to proposer
// calculate fraction votes
fractionVotes := sdk.NewDec(sumPrecommitPower).Quo(sdk.NewDec(totalPower))
// calculate proposer reward
baseProposerReward := k.GetBaseProposerReward(ctx)
bonusProposerReward := k.GetBonusProposerReward(ctx)
proposerMultiplier := baseProposerReward.Add(bonusProposerReward.Mul(percentVotes))
proposerReward := feesCollectedDec.MulDec(proposerMultiplier)
proposerMultiplier := baseProposerReward.Add(bonusProposerReward.Mul(fractionVotes))
proposerReward := feesCollected.MulDec(proposerMultiplier)
// apply commission
commission := proposerReward.MulDec(proposerValidator.GetCommission())
remaining := proposerReward.Minus(commission)
proposerDist.ValCommission = proposerDist.ValCommission.Plus(commission)
proposerDist.DelPool = proposerDist.DelPool.Plus(remaining)
// pay proposer
proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, proposer)
k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward)
remaining := feesCollected.Minus(proposerReward)
// calculate fraction allocated to validators
communityTax := k.GetCommunityTax(ctx)
voteMultiplier := sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax)
// allocate tokens proportionally to voting power
// TODO consider parallelizing later, ref https://github.com/cosmos/cosmos-sdk/pull/3099#discussion_r246276376
for _, vote := range votes {
validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address)
// TODO likely we should only reward validators who actually signed the block.
// ref https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701
powerFraction := sdk.NewDec(vote.Validator.Power).Quo(sdk.NewDec(totalPower))
reward := feesCollected.MulDec(voteMultiplier).MulDec(powerFraction)
k.AllocateTokensToValidator(ctx, validator, reward)
remaining = remaining.Minus(reward)
}
// allocate community funding
communityTax := k.GetCommunityTax(ctx)
communityFunding := feesCollectedDec.MulDec(communityTax)
feePool.CommunityPool = feePool.CommunityPool.Plus(communityFunding)
// set the global pool within the distribution module
poolReceived := feesCollectedDec.Minus(proposerReward).Minus(communityFunding)
feePool.ValPool = feePool.ValPool.Plus(poolReceived)
k.SetValidatorDistInfo(ctx, proposerDist)
feePool.CommunityPool = feePool.CommunityPool.Plus(remaining)
k.SetFeePool(ctx, feePool)
// clear the now distributed fees
k.feeCollectionKeeper.ClearCollectedFees(ctx)
// update outstanding rewards
outstanding := k.GetOutstandingRewards(ctx)
outstanding = outstanding.Plus(feesCollected.Minus(remaining))
k.SetOutstandingRewards(ctx, outstanding)
}
// allocate tokens to a particular validator, splitting according to commission
func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val sdk.Validator, tokens sdk.DecCoins) {
// split tokens between validator and delegators according to commission
commission := tokens.MulDec(val.GetCommission())
shared := tokens.Minus(commission)
// update current commission
currentCommission := k.GetValidatorAccumulatedCommission(ctx, val.GetOperator())
currentCommission = currentCommission.Plus(commission)
k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), currentCommission)
// update current rewards
currentRewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator())
currentRewards.Rewards = currentRewards.Rewards.Plus(shared)
k.SetValidatorCurrentRewards(ctx, val.GetOperator(), currentRewards)
}

View File

@ -3,109 +3,106 @@ package keeper
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking"
)
func TestAllocateTokensBasic(t *testing.T) {
func TestAllocateTokensToValidatorWithCommission(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
// no community tax on inputs
ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
//first make a validator
totalPower := int64(10)
totalPowerInt := sdk.NewInt(totalPower)
msgCreateValidator := staking.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower)
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
val := sk.Validator(ctx, valOpAddr1)
// verify everything has been set in staking correctly
validator, found := sk.GetValidator(ctx, valOpAddr1)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status)
assert.True(sdk.IntEq(t, totalPowerInt, validator.Tokens))
assert.True(sdk.DecEq(t, sdk.NewDec(totalPower), validator.DelegatorShares))
bondedTokens := sk.TotalPower(ctx)
assert.True(sdk.IntEq(t, totalPowerInt, bondedTokens))
// allocate tokens
tokens := sdk.DecCoins{
{staking.DefaultBondDenom, sdk.NewDec(10)},
}
k.AllocateTokensToValidator(ctx, val, tokens)
// initial fee pool should be empty
feePool := keeper.GetFeePool(ctx)
require.Nil(t, feePool.ValPool)
// check commission
expected := sdk.DecCoins{
{staking.DefaultBondDenom, sdk.NewDec(5)},
}
require.Equal(t, expected, k.GetValidatorAccumulatedCommission(ctx, val.GetOperator()))
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// verify that these fees have been received by the feePool
percentProposer := sdk.NewDecWithPrec(5, 2)
percentRemaining := sdk.OneDec().Sub(percentProposer)
feePool = keeper.GetFeePool(ctx)
expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining)
require.Equal(t, 1, len(feePool.ValPool))
require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount))
// check current rewards
require.Equal(t, expected, k.GetValidatorCurrentRewards(ctx, val.GetOperator()).Rewards)
}
func TestAllocateTokensWithCommunityTax(t *testing.T) {
communityTax := sdk.NewDecWithPrec(1, 2) //1%
ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax)
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
func TestAllocateTokensToManyValidators(t *testing.T) {
ctx, _, k, sk, fck := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
//first make a validator
totalPower := int64(10)
msgCreateValidator := staking.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower)
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
// verify that these fees have been received by the feePool
feePool := keeper.GetFeePool(ctx)
// 5% goes to proposer, 1% community tax
percentProposer := sdk.NewDecWithPrec(5, 2)
percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer))
expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining)
require.Equal(t, 1, len(feePool.ValPool))
require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount))
}
func TestAllocateTokensWithPartialPrecommitPower(t *testing.T) {
communityTax := sdk.NewDecWithPrec(1, 2)
ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax)
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
//first make a validator
totalPower := int64(100)
msgCreateValidator := staking.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower)
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
percentPrecommitVotes := sdk.NewDecWithPrec(25, 2)
keeper.AllocateTokens(ctx, percentPrecommitVotes, valConsAddr1)
// verify that these fees have been received by the feePool
feePool := keeper.GetFeePool(ctx)
// 1% + 4%*0.25 to proposer + 1% community tax = 97%
percentProposer := sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(4, 2).Mul(percentPrecommitVotes))
percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer))
expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining)
require.Equal(t, 1, len(feePool.ValPool))
require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount))
// create second validator with 0% commission
commission = staking.NewCommissionMsg(sdk.NewDec(0), sdk.NewDec(0), sdk.NewDec(0))
msg = staking.NewMsgCreateValidator(valOpAddr2, valConsPk2,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
abciValA := abci.Validator{
Address: valConsPk1.Address(),
Power: 100,
}
abciValB := abci.Validator{
Address: valConsPk2.Address(),
Power: 100,
}
// assert initial state: zero outstanding rewards, zero community pool, zero commission, zero current rewards
require.True(t, k.GetOutstandingRewards(ctx).IsZero())
require.True(t, k.GetFeePool(ctx).CommunityPool.IsZero())
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero())
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero())
require.True(t, k.GetValidatorCurrentRewards(ctx, valOpAddr1).Rewards.IsZero())
require.True(t, k.GetValidatorCurrentRewards(ctx, valOpAddr2).Rewards.IsZero())
// allocate tokens as if both had voted and second was proposer
fees := sdk.Coins{
{staking.DefaultBondDenom, sdk.NewInt(100)},
}
fck.SetCollectedFees(fees)
votes := []abci.VoteInfo{
{
Validator: abciValA,
SignedLastBlock: true,
},
{
Validator: abciValB,
SignedLastBlock: true,
},
}
k.AllocateTokens(ctx, 200, 200, valConsAddr2, votes)
// 98 outstanding rewards (100 less 2 to community pool)
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(98)}}, k.GetOutstandingRewards(ctx))
// 2 community pool coins
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(2)}}, k.GetFeePool(ctx).CommunityPool)
// 50% commission for first proposer, (0.5 * 93%) * 100 / 2 = 23.25
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDecWithPrec(2325, 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
// zero commission for second proposer
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero())
// just staking.proportional for first proposer less commission = (0.5 * 93%) * 100 / 2 = 23.25
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDecWithPrec(2325, 2)}}, k.GetValidatorCurrentRewards(ctx, valOpAddr1).Rewards)
// proposer reward + staking.proportional for second proposer = (5 % + 0.5 * (93%)) * 100 = 51.5
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDecWithPrec(515, 1)}}, k.GetValidatorCurrentRewards(ctx, valOpAddr2).Rewards)
}

View File

@ -2,229 +2,88 @@ package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"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))
// initialize starting info for a new delegation
func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) {
// period has already been incremented
period := k.GetValidatorCurrentRewards(ctx, val).Period
validator := k.stakingKeeper.Validator(ctx, val)
delegation := k.stakingKeeper.Delegation(ctx, del, val)
// calculate delegation stake in tokens
// we don't store directly, so multiply delegation shares * (tokens per share)
stake := delegation.GetShares().Mul(validator.GetDelegatorShareExRate())
k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(period-1, stake, uint64(ctx.BlockHeight())))
}
// get the delegator distribution info
func (k Keeper) GetDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress,
valOperatorAddr sdk.ValAddress) (ddi types.DelegationDistInfo) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetDelegationDistInfoKey(delAddr, valOperatorAddr))
if b == nil {
panic("Stored delegation-distribution info should not have been nil")
// calculate the rewards accrued by a delegation between two periods
func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Validator,
startingPeriod, endingPeriod uint64, staking sdk.Dec) (rewards sdk.DecCoins) {
// sanity check
if startingPeriod > endingPeriod {
panic("startingPeriod cannot be greater than endingPeriod")
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &ddi)
// return staking * (ending - starting)
starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod)
ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod)
difference := ending.Minus(starting)
rewards = difference.MulDec(staking)
return
}
// set the delegator distribution info
func (k Keeper) SetDelegationDistInfo(ctx sdk.Context, ddi types.DelegationDistInfo) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(ddi)
store.Set(GetDelegationDistInfoKey(ddi.DelegatorAddr, ddi.ValOperatorAddr), b)
}
// calculate the total rewards accrued by a delegation
func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation, endingPeriod uint64) (rewards sdk.DecCoins) {
// fetch starting info for delegation
startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())
startingPeriod := startingInfo.PreviousPeriod
stake := startingInfo.Stake
// remove a delegator distribution info
func (k Keeper) RemoveDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress,
valOperatorAddr sdk.ValAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegationDistInfoKey(delAddr, valOperatorAddr))
}
// remove all delegation distribution infos
func (k Keeper) RemoveDelegationDistInfos(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
// iterate through slashes and withdraw with calculated staking for sub-intervals
// these offsets are dependent on *when* slashes happen - namely, in BeginBlock, after rewards are allocated...
// ... so we don't reduce stake for slashes which happened in the *first* block, because the delegation wouldn't have existed
startingHeight := startingInfo.Height + 1
// ... or slashes which happened in *this* block, since they would have happened after reward allocation
endingHeight := uint64(ctx.BlockHeight()) - 1
if endingHeight >= startingHeight {
k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight,
func(height uint64, event types.ValidatorSlashEvent) (stop bool) {
endingPeriod := event.ValidatorPeriod - 1
rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake))
stake = stake.Mul(sdk.OneDec().Sub(event.Fraction))
startingPeriod = endingPeriod
return false
},
)
}
// calculate rewards for final period
rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake))
return
}
// iterate over all the validator distribution infos
func (k Keeper) IterateDelegationDistInfos(ctx sdk.Context,
fn func(index int64, distInfo types.DelegationDistInfo) (stop bool)) {
func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) sdk.Error {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey)
defer iter.Close()
index := int64(0)
for ; iter.Valid(); iter.Next() {
var ddi types.DelegationDistInfo
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &ddi)
if fn(index, ddi) {
return
}
index++
}
}
// end current period and calculate rewards
endingPeriod := k.incrementValidatorPeriod(ctx, val)
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
//___________________________________________________________________________________________
// get the delegator withdraw address, return the delegator address if not set
func (k Keeper) GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetDelegatorWithdrawAddrKey(delAddr))
if b == nil {
return delAddr
}
return sdk.AccAddress(b)
}
// set the delegator withdraw address
func (k Keeper) SetDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes())
}
// remove a delegator withdraw info
func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegatorWithdrawAddrKey(delAddr))
}
//___________________________________________________________________________________________
// return all rewards for a delegation
func (k Keeper) withdrawDelegationReward(ctx sdk.Context,
delAddr sdk.AccAddress, valAddr sdk.ValAddress) (types.FeePool,
types.ValidatorDistInfo, types.DelegationDistInfo, types.DecCoins) {
wc := k.GetWithdrawContext(ctx, valAddr)
valInfo := k.GetValidatorDistInfo(ctx, valAddr)
delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr)
validator := k.stakingKeeper.Validator(ctx, valAddr)
delegation := k.stakingKeeper.Delegation(ctx, delAddr, valAddr)
delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(wc, valInfo,
validator.GetDelegatorShares(), delegation.GetShares())
return feePool, valInfo, delInfo, withdraw
}
// get all rewards for all delegations of a delegator
func (k Keeper) currentDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) types.DecCoins {
wc := k.GetWithdrawContext(ctx, valAddr)
valInfo := k.GetValidatorDistInfo(ctx, valAddr)
delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr)
validator := k.stakingKeeper.Validator(ctx, valAddr)
delegation := k.stakingKeeper.Delegation(ctx, delAddr, valAddr)
estimation := delInfo.CurrentRewards(wc, valInfo,
validator.GetDelegatorShares(), delegation.GetShares())
return estimation
}
//___________________________________________________________________________________________
// withdraw all rewards for a single delegation
// NOTE: This gets called "onDelegationSharesModified",
// meaning any changes to bonded coins
func (k Keeper) WithdrawToDelegator(ctx sdk.Context, feePool types.FeePool,
delAddr sdk.AccAddress, amount types.DecCoins) {
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delAddr)
coinsToAdd, change := amount.TruncateDecimal()
feePool.CommunityPool = feePool.CommunityPool.Plus(change)
// truncate coins, return remainder to community pool
coins, remainder := rewards.TruncateDecimal()
outstanding := k.GetOutstandingRewards(ctx)
k.SetOutstandingRewards(ctx, outstanding.Minus(rewards))
feePool := k.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Plus(remainder)
k.SetFeePool(ctx, feePool)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coinsToAdd)
if err != nil {
panic(err)
// add coins to user account
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, del.GetDelegatorAddr())
if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil {
return err
}
}
//___________________________________________________________________________________________
// withdraw all rewards for a single delegation
// NOTE: This gets called "onDelegationSharesModified",
// meaning any changes to bonded coins
func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) sdk.Error {
if !k.HasDelegationDistInfo(ctx, delAddr, valAddr) {
return types.ErrNoDelegationDistInfo(k.codespace)
}
feePool, valInfo, delInfo, withdraw := k.withdrawDelegationReward(ctx, delAddr, valAddr)
k.SetValidatorDistInfo(ctx, valInfo)
k.SetDelegationDistInfo(ctx, delInfo)
k.WithdrawToDelegator(ctx, feePool, delAddr, withdraw)
return nil
}
// current rewards for a single delegation
func (k Keeper) CurrentDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) (sdk.Coins, sdk.Error) {
if !k.HasDelegationDistInfo(ctx, delAddr, valAddr) {
return sdk.Coins{}, types.ErrNoDelegationDistInfo(k.codespace)
}
estCoins := k.currentDelegationReward(ctx, delAddr, valAddr)
trucate, _ := estCoins.TruncateDecimal()
return trucate, nil
}
//___________________________________________________________________________________________
// return all rewards for all delegations of a delegator
func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress) {
withdraw := k.withdrawDelegationRewardsAll(ctx, delAddr)
feePool := k.GetFeePool(ctx)
k.WithdrawToDelegator(ctx, feePool, delAddr, withdraw)
}
func (k Keeper) withdrawDelegationRewardsAll(ctx sdk.Context,
delAddr sdk.AccAddress) types.DecCoins {
withdraw := types.DecCoins{}
// iterate over all the delegations
operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) {
valAddr := del.GetValidatorAddr()
feePool, valInfo, delInfo, diWithdraw := k.withdrawDelegationReward(ctx, delAddr, valAddr)
withdraw = withdraw.Plus(diWithdraw)
k.SetFeePool(ctx, feePool)
k.SetValidatorDistInfo(ctx, valInfo)
k.SetDelegationDistInfo(ctx, delInfo)
return false
}
k.stakingKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation)
return withdraw
}
// get all rewards for all delegations of a delegator
func (k Keeper) CurrentDelegationRewardsAll(ctx sdk.Context,
delAddr sdk.AccAddress) types.DecCoins {
// iterate over all the delegations
total := types.DecCoins{}
operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) {
valAddr := del.GetValidatorAddr()
est := k.currentDelegationReward(ctx, delAddr, valAddr)
total = total.Plus(est)
return false
}
k.stakingKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation)
return total
}

View File

@ -9,246 +9,530 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking"
)
func TestWithdrawDelegationRewardBasic(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
func TestCalculateRewardsBasic(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
//first make a validator
msgCreateValidator := staking.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10)
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// delegate
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// end block to bond validator
staking.EndBlocker(ctx, sk)
// withdraw delegation
ctx = ctx.WithBlockHeight(1)
sk.SetLastTotalPower(ctx, sdk.NewInt(10))
sk.SetLastValidatorPower(ctx, valOpAddr1, sdk.NewInt(10))
keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1)
amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
expRes := sdk.NewDec(90).Add(sdk.NewDec(100).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20
require.True(sdk.IntEq(t, expRes, amt))
// end period
endingPeriod := k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// allocate some rewards
initial := int64(10)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
}
func TestWithdrawDelegationRewardWithCommission(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
func TestCalculateRewardsAfterSlash(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
//first make a validator with 10% commission
msgCreateValidator := staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1))
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// delegate
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// end block to bond validator
staking.EndBlocker(ctx, sk)
// withdraw delegation
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1)
amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
expRes := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100*90% tokens * 10/20
require.True(sdk.IntEq(t, expRes, amt))
// end period
endingPeriod := k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// start out block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// slash the validator by 50%
sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1))
// retrieve validator
val = sk.Validator(ctx, valOpAddr1)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some rewards
initial := int64(10)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
}
func TestWithdrawDelegationRewardTwoDelegators(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
func TestCalculateRewardsAfterManySlashes(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
//first make a validator with 10% commission
msgCreateValidator := staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1))
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// delegate
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
msgDelegate = staking.NewTestMsgDelegate(delAddr2, valOpAddr1, 20)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom)
require.Equal(t, int64(80), amt.Int64())
// end block to bond validator
staking.EndBlocker(ctx, sk)
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// delegator 1 withdraw delegation
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1)
amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
// end period
endingPeriod := k.incrementValidatorPeriod(ctx, val)
expRes := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(4))).TruncateInt() // 90 + 100*90% tokens * 10/40
require.True(sdk.IntEq(t, expRes, amt))
// calculate delegation rewards
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// start out block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// slash the validator by 50%
sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1))
// fetch the validator again
val = sk.Validator(ctx, valOpAddr1)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some rewards
initial := int64(10)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
// slash the validator by 50% again
sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 50, sdk.NewDecWithPrec(5, 1))
// fetch the validator again
val = sk.Validator(ctx, valOpAddr1)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
}
// this test demonstrates how two delegators with the same power can end up
// with different rewards in the end
func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
func TestCalculateRewardsMultiDelegator(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
//first make a validator with no commission
msgCreateValidator := staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr1, valConsPk1, 10, sdk.ZeroDec())
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// delegate
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
msgDelegate = staking.NewTestMsgDelegate(delAddr2, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
// end block to bond validator
staking.EndBlocker(ctx, sk)
// allocate 100 denom of fees
feeInputs := sdk.NewInt(90)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
ctx = ctx.WithBlockHeight(1)
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// delegator 1 withdraw delegation early, delegator 2 just keeps it's accum
keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1)
amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
// allocate some rewards
initial := int64(20)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
expRes1 := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(3))).TruncateInt() // 90 + 100 * 10/30
require.True(sdk.IntEq(t, expRes1, amt))
// second delegation
msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)))
require.True(t, sh(ctx, msg2).IsOK())
del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1)
// allocate 200 denom of fees
feeInputs = sdk.NewInt(180)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
ctx = ctx.WithBlockHeight(2)
// fetch updated validator
val = sk.Validator(ctx, valOpAddr1)
// delegator 2 now withdraws everything it's entitled to
keeper.WithdrawDelegationReward(ctx, delAddr2, valOpAddr1)
amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom)
// existingTokens + (100+200 * (10/(20+30))
withdrawnFromVal := sdk.NewDec(60 + 180).Mul(sdk.NewDec(2)).Quo(sdk.NewDec(5))
expRes2 := sdk.NewDec(90).Add(withdrawnFromVal).TruncateInt()
require.True(sdk.IntEq(t, expRes2, amt))
// end block
staking.EndBlocker(ctx, sk)
// finally delegator 1 withdraws the remainder of its reward
keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1)
amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
// allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens)
remainingInVal := sdk.NewDec(60 + 180).Sub(withdrawnFromVal)
expRes3 := sdk.NewDecFromInt(expRes1).Add(remainingInVal.Mul(sdk.NewDec(1)).Quo(sdk.NewDec(3))).TruncateInt()
require.True(sdk.IntEq(t, expRes3, amt))
// end period
endingPeriod := k.incrementValidatorPeriod(ctx, val)
// verify the final withdraw amounts are different
require.True(t, expRes2.GT(expRes3))
// calculate delegation rewards for del1
rewards := k.calculateDelegationRewards(ctx, val, del1, endingPeriod)
// rewards for del1 should be 3/4 initial
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial * 3 / 4)}}, rewards)
// calculate delegation rewards for del2
rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be 1/4 initial
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial * 1 / 4)}}, rewards)
// commission should be equal to initial (50% twice)
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
}
func TestWithdrawDelegationRewardsAll(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
func TestWithdrawDelegationRewardsBasic(t *testing.T) {
balance := int64(1000)
ctx, ak, k, sk, _ := CreateTestInputDefault(t, false, balance)
sh := staking.NewHandler(sk)
//make some validators with different commissions
msgCreateValidator := staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1))
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
msgCreateValidator = staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr2, valConsPk2, 50, sdk.NewDecWithPrec(2, 1))
got = stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// create validator with 50% commission
bond := int64(100)
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(bond)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
msgCreateValidator = staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr3, valConsPk3, 40, sdk.NewDecWithPrec(3, 1))
got = stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// assert correct initial balance
require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond)}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins())
// delegate to all the validators
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
require.True(t, stakingHandler(ctx, msgDelegate).IsOK())
msgDelegate = staking.NewTestMsgDelegate(delAddr1, valOpAddr2, 20)
require.True(t, stakingHandler(ctx, msgDelegate).IsOK())
msgDelegate = staking.NewTestMsgDelegate(delAddr1, valOpAddr3, 30)
require.True(t, stakingHandler(ctx, msgDelegate).IsOK())
// end block to bond validator
staking.EndBlocker(ctx, sk)
// Update sk's LastValidatorPower/LastTotalPowers.
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// set zero outstanding rewards
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// 40 tokens left after delegating 60 of them
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(40), amt.Int64())
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
// total power of each validator:
// validator 1: 10 (self) + 10 (delegator) = 20
// validator 2: 50 (self) + 20 (delegator) = 70
// validator 3: 40 (self) + 30 (delegator) = 70
// grand total: 160
// allocate some rewards
initial := int64(10)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
// allocate 100 denom of fees
feeInputs := sdk.NewInt(1000)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// withdraw rewards
require.Nil(t, k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1))
// withdraw delegation
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawDelegationRewardsAll(ctx, delAddr1)
amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
// assert correct balance
require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond + (initial / 2))}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins())
// orig-amount + fees *(1-proposerReward)* (val1Portion * delegatorPotion * (1-val1Commission) ... etc)
// + fees *(proposerReward) * (delegatorPotion * (1-val1Commission))
// 40 + 1000 *(1- 0.95)* (20/160 * 10/20 * 0.9 + 70/160 * 20/70 * 0.8 + 70/160 * 30/70 * 0.7)
// 40 + 1000 *( 0.05) * (10/20 * 0.9)
feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2))
feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2))
feesInVal1 := feesInNonProposer.Mul(sdk.NewDec(10).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(9, 1))
feesInVal2 := feesInNonProposer.Mul(sdk.NewDec(20).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(8, 1))
feesInVal3 := feesInNonProposer.Mul(sdk.NewDec(30).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(7, 1))
feesInVal1Proposer := feesInProposer.Mul(sdk.NewDec(10).Quo(sdk.NewDec(20))).Mul(sdk.NewDecWithPrec(9, 1))
expRes := sdk.NewDec(40).Add(feesInVal1).Add(feesInVal2).Add(feesInVal3).Add(feesInVal1Proposer).TruncateInt()
require.True(sdk.IntEq(t, expRes, amt))
// withdraw commission
require.Nil(t, k.WithdrawValidatorCommission(ctx, valOpAddr1))
// assert correct balance
require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond + initial)}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins())
}
func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
// end block to bond validator
staking.EndBlocker(ctx, sk)
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// end period
endingPeriod := k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be zero
require.True(t, rewards.IsZero())
// start out block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some rewards
initial := int64(10)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
// slash the validator by 50%
sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1))
// slash the validator by 50% again
sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 50, sdk.NewDecWithPrec(5, 1))
// fetch the validator again
val = sk.Validator(ctx, valOpAddr1)
// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens)
// end period
endingPeriod = k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards
rewards = k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// rewards should be half the tokens
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, rewards)
// commission should be the other half
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
}
func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
// end block to bond validator
staking.EndBlocker(ctx, sk)
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// allocate some rewards
initial := int64(30)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
// slash the validator
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1))
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// second delegation
msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)))
require.True(t, sh(ctx, msg2).IsOK())
del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1)
// end block
staking.EndBlocker(ctx, sk)
// allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens)
// slash the validator again
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
sk.Slash(ctx, valConsAddr1, ctx.BlockHeight(), 100, sdk.NewDecWithPrec(5, 1))
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
// fetch updated validator
val = sk.Validator(ctx, valOpAddr1)
// end period
endingPeriod := k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards := k.calculateDelegationRewards(ctx, val, del1, endingPeriod)
// rewards for del1 should be 2/3 initial (half initial first period, 1/6 initial second period)
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec((initial / 2) + (initial / 6))}}, rewards)
// calculate delegation rewards for del2
rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be initial / 3
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 3)}}, rewards)
// commission should be equal to initial (twice 50% commission, unaffected by slashing)
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
}
func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, commission)
require.True(t, sh(ctx, msg).IsOK())
// end block to bond validator
staking.EndBlocker(ctx, sk)
// fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1)
del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// allocate some rewards
initial := int64(20)
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)
// second delegation
msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)))
require.True(t, sh(ctx, msg2).IsOK())
// fetch updated validator
val = sk.Validator(ctx, valOpAddr1)
del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1)
// end block
staking.EndBlocker(ctx, sk)
// allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens)
// first delegator withdraws
k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// second delegator withdraws
k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1)
// validator withdraws commission
k.WithdrawValidatorCommission(ctx, valOpAddr1)
// end period
endingPeriod := k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards := k.calculateDelegationRewards(ctx, val, del1, endingPeriod)
// rewards for del1 should be zero
require.True(t, rewards.IsZero())
// calculate delegation rewards for del2
rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be zero
require.True(t, rewards.IsZero())
// commission should be zero
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero())
// allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens)
// first delegator withdraws again
k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// end period
endingPeriod = k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards = k.calculateDelegationRewards(ctx, val, del1, endingPeriod)
// rewards for del1 should be zero
require.True(t, rewards.IsZero())
// calculate delegation rewards for del2
rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be 1/4 initial
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 4)}}, rewards)
// commission should be half initial
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
// allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens)
// withdraw commission
k.WithdrawValidatorCommission(ctx, valOpAddr1)
// end period
endingPeriod = k.incrementValidatorPeriod(ctx, val)
// calculate delegation rewards for del1
rewards = k.calculateDelegationRewards(ctx, val, del1, endingPeriod)
// rewards for del1 should be 1/4 initial
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 4)}}, rewards)
// calculate delegation rewards for del2
rewards = k.calculateDelegationRewards(ctx, val, del2, endingPeriod)
// rewards for del2 should be 1/2 initial
require.Equal(t, sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards)
// commission should be zero
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero())
}

View File

@ -1,50 +0,0 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// Get the set of all validator-distribution-info's with no limits, used during genesis dump
func (k Keeper) GetAllValidatorDistInfos(ctx sdk.Context) (vdis []types.ValidatorDistInfo) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var vdi types.ValidatorDistInfo
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vdi)
vdis = append(vdis, vdi)
}
return vdis
}
// Get the set of all delegator-distribution-info's with no limits, used during genesis dump
func (k Keeper) GetAllDelegationDistInfos(ctx sdk.Context) (ddis []types.DelegationDistInfo) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var ddi types.DelegationDistInfo
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &ddi)
ddis = append(ddis, ddi)
}
return ddis
}
// Get the set of all delegator-withdraw addresses with no limits, used during genesis dump
func (k Keeper) GetAllDelegatorWithdrawInfos(ctx sdk.Context) (dwis []types.DelegatorWithdrawInfo) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, DelegatorWithdrawInfoKey)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
dw := types.DelegatorWithdrawInfo{
DelegatorAddr: GetDelegatorWithdrawInfoAddress(iterator.Key()),
WithdrawAddr: sdk.AccAddress(iterator.Value()),
}
dwis = append(dwis, dw)
}
return dwis
}

View File

@ -1,109 +1,9 @@
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// Create a new validator distribution record
func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {
// defensive check for existence
if k.HasValidatorDistInfo(ctx, valAddr) {
panic("validator dist info already exists (not cleaned up properly)")
}
height := ctx.BlockHeight()
vdi := types.ValidatorDistInfo{
OperatorAddr: valAddr,
FeePoolWithdrawalHeight: height,
DelAccum: types.NewTotalAccum(height),
DelPool: types.DecCoins{},
ValCommission: types.DecCoins{},
}
k.SetValidatorDistInfo(ctx, vdi)
}
// Withdraw all validator rewards
func (k Keeper) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {
// Move the validator's rewards from the global pool to the validator's pools
// (dist info), but without actually withdrawing the rewards. This does not
// need to happen during the genesis block.
if ctx.BlockHeight() > 0 {
if err := k.takeValidatorFeePoolRewards(ctx, valAddr); err != nil {
panic(err)
}
}
}
// Withdraw all validator rewards
func (k Keeper) AfterValidatorBonded(ctx sdk.Context, valAddr sdk.ValAddress) {
lastPower := k.stakingKeeper.GetLastValidatorPower(ctx, valAddr)
if !lastPower.Equal(sdk.ZeroInt()) {
panic("expected last power to be 0 for validator entering bonded state")
}
k.BeforeValidatorModified(ctx, valAddr)
}
// Sanity check, very useful!
func (k Keeper) AfterValidatorPowerDidChange(ctx sdk.Context, valAddr sdk.ValAddress) {
vi := k.GetValidatorDistInfo(ctx, valAddr)
if vi.FeePoolWithdrawalHeight != ctx.BlockHeight() {
panic(fmt.Sprintf("expected validator (%v) dist info FeePoolWithdrawalHeight to be updated to %v, but was %v.",
valAddr.String(), ctx.BlockHeight(), vi.FeePoolWithdrawalHeight))
}
}
// Withdrawal all validator distribution rewards and cleanup the distribution record
func (k Keeper) AfterValidatorRemoved(ctx sdk.Context, valAddr sdk.ValAddress) {
k.RemoveValidatorDistInfo(ctx, valAddr)
}
//_________________________________________________________________________________________
// Create a new delegator distribution record
func (k Keeper) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) {
ddi := types.DelegationDistInfo{
DelegatorAddr: delAddr,
ValOperatorAddr: valAddr,
DelPoolWithdrawalHeight: ctx.BlockHeight(),
}
k.SetDelegationDistInfo(ctx, ddi)
}
// Withdrawal all validator rewards
func (k Keeper) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) {
if err := k.WithdrawDelegationReward(ctx, delAddr, valAddr); err != nil {
panic(err)
}
}
// Withdrawal all validator distribution rewards and cleanup the distribution record
func (k Keeper) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) {
// Withdraw validator commission when validator self-bond is removed.
// Because we maintain the invariant that all delegations must be removed
// before a validator is deleted, this ensures that commission will be withdrawn
// before the validator is deleted (and the corresponding ValidatorDistInfo removed).
// If we change other parts of the code such that a self-delegation might remain after
// a validator is deleted, this logic will no longer be safe.
// TODO: Consider instead implementing this in a "BeforeValidatorRemoved" hook.
if valAddr.Equals(sdk.ValAddress(delAddr)) {
feePool, commission := k.withdrawValidatorCommission(ctx, valAddr)
k.WithdrawToDelegator(ctx, feePool, delAddr, commission)
}
k.RemoveDelegationDistInfo(ctx, delAddr, valAddr)
}
//_________________________________________________________________________________________
// Wrapper struct
type Hooks struct {
k Keeper
@ -111,36 +11,50 @@ type Hooks struct {
var _ sdk.StakingHooks = Hooks{}
// New Validator Hooks
// Create new distribution hooks
func (k Keeper) Hooks() Hooks { return Hooks{k} }
// nolint
func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {
h.k.AfterValidatorCreated(ctx, valAddr)
val := h.k.stakingKeeper.Validator(ctx, valAddr)
h.k.initializeValidator(ctx, val)
}
func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {
h.k.BeforeValidatorModified(ctx, valAddr)
val := h.k.stakingKeeper.Validator(ctx, valAddr)
// increment period
h.k.incrementValidatorPeriod(ctx, val)
}
func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
h.k.AfterValidatorRemoved(ctx, valAddr)
}
func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
h.k.BeforeValidatorModified(ctx, valAddr)
h.k.BeforeDelegationCreated(ctx, delAddr, valAddr)
val := h.k.stakingKeeper.Validator(ctx, valAddr)
// increment period
h.k.incrementValidatorPeriod(ctx, val)
}
func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
h.k.BeforeValidatorModified(ctx, valAddr)
h.k.BeforeDelegationSharesModified(ctx, delAddr, valAddr)
val := h.k.stakingKeeper.Validator(ctx, valAddr)
del := h.k.stakingKeeper.Delegation(ctx, delAddr, valAddr)
// withdraw delegation rewards (which also increments period)
if err := h.k.withdrawDelegationRewards(ctx, val, del); err != nil {
panic(err)
}
}
func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
h.k.BeforeDelegationRemoved(ctx, delAddr, valAddr)
// nothing needed here since OnDelegationSharesModified will always also be called
}
func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
// create new delegation period record
h.k.initializeDelegation(ctx, valAddr, delAddr)
}
func (h Hooks) AfterValidatorBeginUnbonding(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
h.k.BeforeValidatorModified(ctx, valAddr)
}
func (h Hooks) AfterValidatorBonded(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
h.k.AfterValidatorBonded(ctx, valAddr)
}
func (h Hooks) AfterValidatorPowerDidChange(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
h.k.AfterValidatorPowerDidChange(ctx, valAddr)
}
func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {
// record the slash event
h.k.updateValidatorSlashFraction(ctx, valAddr, fraction)
}

View File

@ -20,9 +20,9 @@ type Keeper struct {
codespace sdk.CodespaceType
}
// create a new keeper
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, ck types.BankKeeper,
sk types.StakingKeeper, fck types.FeeCollectionKeeper, codespace sdk.CodespaceType) Keeper {
keeper := Keeper{
storeKey: key,
cdc: cdc,
@ -35,123 +35,53 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, c
return keeper
}
//______________________________________________________________________
// get the global fee pool distribution info
func (k Keeper) GetFeePool(ctx sdk.Context) (feePool types.FeePool) {
store := ctx.KVStore(k.storeKey)
b := store.Get(FeePoolKey)
if b == nil {
panic("Stored fee pool should not have been nil")
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &feePool)
return
}
// set the global fee pool distribution info
func (k Keeper) SetFeePool(ctx sdk.Context, feePool types.FeePool) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(feePool)
store.Set(FeePoolKey, b)
}
// get the total validator accum for the ctx height
// in the fee pool
func (k Keeper) GetFeePoolValAccum(ctx sdk.Context) sdk.Dec {
// withdraw self-delegation
height := ctx.BlockHeight()
totalPower := sdk.NewDecFromInt(k.stakingKeeper.GetLastTotalPower(ctx))
fp := k.GetFeePool(ctx)
return fp.GetTotalValAccum(height, totalPower)
}
//______________________________________________________________________
// set the proposer public key for this block
func (k Keeper) GetPreviousProposerConsAddr(ctx sdk.Context) (consAddr sdk.ConsAddress) {
store := ctx.KVStore(k.storeKey)
b := store.Get(ProposerKey)
if b == nil {
panic("Previous proposer not set")
// withdraw rewards from a delegation
func (k Keeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Error {
val := k.stakingKeeper.Validator(ctx, valAddr)
if val == nil {
return types.ErrNoValidatorDistInfo(k.codespace)
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &consAddr)
return
del := k.stakingKeeper.Delegation(ctx, delAddr, valAddr)
if del == nil {
return types.ErrNoDelegationDistInfo(k.codespace)
}
// withdraw rewards
if err := k.withdrawDelegationRewards(ctx, val, del); err != nil {
return err
}
// reinitialize the delegation
k.initializeDelegation(ctx, valAddr, delAddr)
return nil
}
// get the proposer public key for this block
func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(consAddr)
store.Set(ProposerKey, b)
}
//______________________________________________________________________
// get context required for withdraw operations
func (k Keeper) GetWithdrawContext(ctx sdk.Context,
valOperatorAddr sdk.ValAddress) types.WithdrawContext {
feePool := k.GetFeePool(ctx)
height := ctx.BlockHeight()
validator := k.stakingKeeper.Validator(ctx, valOperatorAddr)
lastValPower := k.stakingKeeper.GetLastValidatorPower(ctx, valOperatorAddr)
lastTotalPower := sdk.NewDecFromInt(k.stakingKeeper.GetLastTotalPower(ctx))
return types.NewWithdrawContext(
feePool, height, lastTotalPower, sdk.NewDecFromInt(lastValPower),
validator.GetCommission())
}
//______________________________________________________________________
// PARAM STORE
// Type declaration for parameters
func ParamTypeTable() params.TypeTable {
return params.NewTypeTable(
ParamStoreKeyCommunityTax, sdk.Dec{},
ParamStoreKeyBaseProposerReward, sdk.Dec{},
ParamStoreKeyBonusProposerReward, sdk.Dec{},
)
}
// Returns the current CommunityTax rate from the global param store
// nolint: errcheck
func (k Keeper) GetCommunityTax(ctx sdk.Context) sdk.Dec {
var percent sdk.Dec
k.paramSpace.Get(ctx, ParamStoreKeyCommunityTax, &percent)
return percent
}
// nolint: errcheck
func (k Keeper) SetCommunityTax(ctx sdk.Context, percent sdk.Dec) {
k.paramSpace.Set(ctx, ParamStoreKeyCommunityTax, &percent)
}
// Returns the current BaseProposerReward rate from the global param store
// nolint: errcheck
func (k Keeper) GetBaseProposerReward(ctx sdk.Context) sdk.Dec {
var percent sdk.Dec
k.paramSpace.Get(ctx, ParamStoreKeyBaseProposerReward, &percent)
return percent
}
// nolint: errcheck
func (k Keeper) SetBaseProposerReward(ctx sdk.Context, percent sdk.Dec) {
k.paramSpace.Set(ctx, ParamStoreKeyBaseProposerReward, &percent)
}
// Returns the current BaseProposerReward rate from the global param store
// nolint: errcheck
func (k Keeper) GetBonusProposerReward(ctx sdk.Context) sdk.Dec {
var percent sdk.Dec
k.paramSpace.Get(ctx, ParamStoreKeyBonusProposerReward, &percent)
return percent
}
// nolint: errcheck
func (k Keeper) SetBonusProposerReward(ctx sdk.Context, percent sdk.Dec) {
k.paramSpace.Set(ctx, ParamStoreKeyBonusProposerReward, &percent)
// withdraw validator commission
func (k Keeper) WithdrawValidatorCommission(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Error {
// fetch validator accumulated commission
commission := k.GetValidatorAccumulatedCommission(ctx, valAddr)
if commission.IsZero() {
return types.ErrNoValidatorCommission(k.codespace)
}
coins, remainder := commission.TruncateDecimal()
// leave remainder to withdraw later
k.SetValidatorAccumulatedCommission(ctx, valAddr, remainder)
// update outstanding
outstanding := k.GetOutstandingRewards(ctx)
k.SetOutstandingRewards(ctx, outstanding.Minus(sdk.NewDecCoins(coins)))
accAddr := sdk.AccAddress(valAddr)
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, accAddr)
if _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil {
return err
}
return nil
}

View File

@ -9,30 +9,40 @@ import (
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
func TestSetGetPreviousProposerConsAddr(t *testing.T) {
ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0)
func TestWithdrawValidatorCommission(t *testing.T) {
ctx, ak, keeper, _, _ := CreateTestInputDefault(t, false, 1000)
keeper.SetPreviousProposerConsAddr(ctx, valConsAddr1)
res := keeper.GetPreviousProposerConsAddr(ctx)
require.True(t, res.Equals(valConsAddr1), "expected: %v got: %v", valConsAddr1.String(), res.String())
}
func TestSetGetCommunityTax(t *testing.T) {
ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0)
someDec := sdk.NewDec(333)
keeper.SetCommunityTax(ctx, someDec)
res := keeper.GetCommunityTax(ctx)
require.True(sdk.DecEq(t, someDec, res))
}
func TestSetGetFeePool(t *testing.T) {
ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0)
fp := types.InitialFeePool()
fp.TotalValAccum.UpdateHeight = 777
keeper.SetFeePool(ctx, fp)
res := keeper.GetFeePool(ctx)
require.Equal(t, fp.TotalValAccum, res.TotalValAccum)
// set zero outstanding rewards
keeper.SetOutstandingRewards(ctx, types.OutstandingRewards{})
// check initial balance
balance := ak.GetAccount(ctx, sdk.AccAddress(valOpAddr3)).GetCoins()
require.Equal(t, balance, sdk.Coins{
{"stake", sdk.NewInt(1000)},
})
// set commission
keeper.SetValidatorAccumulatedCommission(ctx, valOpAddr3, sdk.DecCoins{
{"mytoken", sdk.NewDec(5).Quo(sdk.NewDec(4))},
{"stake", sdk.NewDec(3).Quo(sdk.NewDec(2))},
})
// withdraw commission
keeper.WithdrawValidatorCommission(ctx, valOpAddr3)
// check balance increase
balance = ak.GetAccount(ctx, sdk.AccAddress(valOpAddr3)).GetCoins()
require.Equal(t, balance, sdk.Coins{
{"mytoken", sdk.NewInt(1)},
{"stake", sdk.NewInt(1001)},
})
// check remainder
remainder := keeper.GetValidatorAccumulatedCommission(ctx, valOpAddr3)
require.Equal(t, remainder, sdk.DecCoins{
{"mytoken", sdk.NewDec(1).Quo(sdk.NewDec(4))},
{"stake", sdk.NewDec(1).Quo(sdk.NewDec(2))},
})
require.True(t, true)
}

View File

@ -1,49 +1,33 @@
package keeper
import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// keys/key-prefixes
var (
FeePoolKey = []byte{0x00} // key for global distribution state
ValidatorDistInfoKey = []byte{0x01} // prefix for each key to a validator distribution
DelegationDistInfoKey = []byte{0x02} // prefix for each key to a delegation distribution
DelegatorWithdrawInfoKey = []byte{0x03} // prefix for each key to a delegator withdraw info
ProposerKey = []byte{0x04} // key for storing the proposer operator address
// params store
ParamStoreKeyCommunityTax = []byte("communitytax")
ParamStoreKeyBaseProposerReward = []byte("baseproposerreward")
ParamStoreKeyBonusProposerReward = []byte("bonusproposerreward")
)
const (
// default paramspace for params keeper
DefaultParamspace = "distr"
)
// gets the key for the validator distribution info from address
// VALUE: distribution/types.ValidatorDistInfo
func GetValidatorDistInfoKey(operatorAddr sdk.ValAddress) []byte {
return append(ValidatorDistInfoKey, operatorAddr.Bytes()...)
}
// keys
var (
FeePoolKey = []byte{0x00} // key for global distribution state
ProposerKey = []byte{0x01} // key for the proposer operator address
OutstandingRewardsKey = []byte{0x02} // key for outstanding rewards
// gets the key for delegator distribution for a validator
// VALUE: distribution/types.DelegationDistInfo
func GetDelegationDistInfoKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
return append(GetDelegationDistInfosKey(delAddr), valAddr.Bytes()...)
}
DelegatorWithdrawAddrPrefix = []byte{0x03} // key for delegator withdraw address
DelegatorStartingInfoPrefix = []byte{0x04} // key for delegator starting info
ValidatorHistoricalRewardsPrefix = []byte{0x05} // key for historical validators rewards / stake
ValidatorCurrentRewardsPrefix = []byte{0x06} // key for current validator rewards
ValidatorAccumulatedCommissionPrefix = []byte{0x07} // key for accumulated validator commission
ValidatorSlashEventPrefix = []byte{0x08} // key for validator slash fraction
// gets the prefix for a delegator's distributions across all validators
func GetDelegationDistInfosKey(delAddr sdk.AccAddress) []byte {
return append(DelegationDistInfoKey, delAddr.Bytes()...)
}
// gets the prefix for a delegator's withdraw info
func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte {
return append(DelegatorWithdrawInfoKey, delAddr.Bytes()...)
}
ParamStoreKeyCommunityTax = []byte("communitytax")
ParamStoreKeyBaseProposerReward = []byte("baseproposerreward")
ParamStoreKeyBonusProposerReward = []byte("bonusproposerreward")
)
// gets an address from a delegator's withdraw info key
func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) {
@ -53,3 +37,100 @@ func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) {
}
return sdk.AccAddress(addr)
}
// gets the addresses from a delegator starting info key
func GetDelegatorStartingInfoAddresses(key []byte) (valAddr sdk.ValAddress, delAddr sdk.AccAddress) {
addr := key[1 : 1+sdk.AddrLen]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
valAddr = sdk.ValAddress(addr)
addr = key[1+sdk.AddrLen:]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
delAddr = sdk.AccAddress(addr)
return
}
// gets the address & period from a validator's historical rewards key
func GetValidatorHistoricalRewardsAddressPeriod(key []byte) (valAddr sdk.ValAddress, period uint64) {
addr := key[1 : 1+sdk.AddrLen]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
valAddr = sdk.ValAddress(addr)
b := key[1+sdk.AddrLen:]
if len(b) != 8 {
panic("unexpected key length")
}
period = binary.LittleEndian.Uint64(b)
return
}
// gets the address from a validator's current rewards key
func GetValidatorCurrentRewardsAddress(key []byte) (valAddr sdk.ValAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
return sdk.ValAddress(addr)
}
// gets the address from a validator's accumulated commission key
func GetValidatorAccumulatedCommissionAddress(key []byte) (valAddr sdk.ValAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
return sdk.ValAddress(addr)
}
// gets the height from a validator's slash event key
func GetValidatorSlashEventAddressHeight(key []byte) (valAddr sdk.ValAddress, height uint64) {
addr := key[1 : 1+sdk.AddrLen]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
valAddr = sdk.ValAddress(addr)
b := key[1+sdk.AddrLen:]
if len(b) != 8 {
panic("unexpected key length")
}
height = binary.BigEndian.Uint64(b)
return
}
// gets the key for a delegator's withdraw addr
func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte {
return append(DelegatorWithdrawAddrPrefix, delAddr.Bytes()...)
}
// gets the key for a delegator's starting info
func GetDelegatorStartingInfoKey(v sdk.ValAddress, d sdk.AccAddress) []byte {
return append(append(DelegatorStartingInfoPrefix, v.Bytes()...), d.Bytes()...)
}
// gets the key for a validator's historical rewards
func GetValidatorHistoricalRewardsKey(v sdk.ValAddress, k uint64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, k)
return append(append(ValidatorHistoricalRewardsPrefix, v.Bytes()...), b...)
}
// gets the key for a validator's current rewards
func GetValidatorCurrentRewardsKey(v sdk.ValAddress) []byte {
return append(ValidatorCurrentRewardsPrefix, v.Bytes()...)
}
// gets the key for a validator's current commission
func GetValidatorAccumulatedCommissionKey(v sdk.ValAddress) []byte {
return append(ValidatorAccumulatedCommissionPrefix, v.Bytes()...)
}
// gets the key for a validator's slash fraction
func GetValidatorSlashEventKey(v sdk.ValAddress, height uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, height)
return append(append(ValidatorSlashEventPrefix, v.Bytes()...), b...)
}

View File

@ -0,0 +1,54 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
)
// type declaration for parameters
func ParamTypeTable() params.TypeTable {
return params.NewTypeTable(
ParamStoreKeyCommunityTax, sdk.Dec{},
ParamStoreKeyBaseProposerReward, sdk.Dec{},
ParamStoreKeyBonusProposerReward, sdk.Dec{},
)
}
// returns the current CommunityTax rate from the global param store
// nolint: errcheck
func (k Keeper) GetCommunityTax(ctx sdk.Context) sdk.Dec {
var percent sdk.Dec
k.paramSpace.Get(ctx, ParamStoreKeyCommunityTax, &percent)
return percent
}
// nolint: errcheck
func (k Keeper) SetCommunityTax(ctx sdk.Context, percent sdk.Dec) {
k.paramSpace.Set(ctx, ParamStoreKeyCommunityTax, &percent)
}
// returns the current BaseProposerReward rate from the global param store
// nolint: errcheck
func (k Keeper) GetBaseProposerReward(ctx sdk.Context) sdk.Dec {
var percent sdk.Dec
k.paramSpace.Get(ctx, ParamStoreKeyBaseProposerReward, &percent)
return percent
}
// nolint: errcheck
func (k Keeper) SetBaseProposerReward(ctx sdk.Context, percent sdk.Dec) {
k.paramSpace.Set(ctx, ParamStoreKeyBaseProposerReward, &percent)
}
// returns the current BaseProposerReward rate from the global param store
// nolint: errcheck
func (k Keeper) GetBonusProposerReward(ctx sdk.Context) sdk.Dec {
var percent sdk.Dec
k.paramSpace.Get(ctx, ParamStoreKeyBonusProposerReward, &percent)
return percent
}
// nolint: errcheck
func (k Keeper) SetBonusProposerReward(ctx sdk.Context, percent sdk.Dec) {
k.paramSpace.Set(ctx, ParamStoreKeyBonusProposerReward, &percent)
}

View File

@ -0,0 +1,288 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// get the delegator withdraw address, defaulting to the delegator address
func (k Keeper) GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetDelegatorWithdrawAddrKey(delAddr))
if b == nil {
return delAddr
}
return sdk.AccAddress(b)
}
// set the delegator withdraw address
func (k Keeper) SetDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes())
}
// remove a delegator withdraw addr
func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegatorWithdrawAddrKey(delAddr))
}
// iterate over delegator withdraw addrs
func (k Keeper) IterateDelegatorWithdrawAddrs(ctx sdk.Context, handler func(del sdk.AccAddress, addr sdk.AccAddress) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, DelegatorWithdrawAddrPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
addr := sdk.AccAddress(iter.Value())
del := GetDelegatorWithdrawInfoAddress(iter.Key())
if handler(del, addr) {
break
}
}
}
// get the global fee pool distribution info
func (k Keeper) GetFeePool(ctx sdk.Context) (feePool types.FeePool) {
store := ctx.KVStore(k.storeKey)
b := store.Get(FeePoolKey)
if b == nil {
panic("Stored fee pool should not have been nil")
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &feePool)
return
}
// set the global fee pool distribution info
func (k Keeper) SetFeePool(ctx sdk.Context, feePool types.FeePool) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(feePool)
store.Set(FeePoolKey, b)
}
// get the proposer public key for this block
func (k Keeper) GetPreviousProposerConsAddr(ctx sdk.Context) (consAddr sdk.ConsAddress) {
store := ctx.KVStore(k.storeKey)
b := store.Get(ProposerKey)
if b == nil {
panic("Previous proposer not set")
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &consAddr)
return
}
// set the proposer public key for this block
func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(consAddr)
store.Set(ProposerKey, b)
}
// get the starting period associated with a delegator
func (k Keeper) GetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) (period types.DelegatorStartingInfo) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetDelegatorStartingInfoKey(val, del))
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &period)
return
}
// set the starting period associated with a delegator
func (k Keeper) SetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress, period types.DelegatorStartingInfo) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(period)
store.Set(GetDelegatorStartingInfoKey(val, del), b)
}
// iterate over delegator starting infos
func (k Keeper) IterateDelegatorStartingInfos(ctx sdk.Context, handler func(val sdk.ValAddress, del sdk.AccAddress, info types.DelegatorStartingInfo) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, DelegatorStartingInfoPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var info types.DelegatorStartingInfo
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &info)
val, del := GetDelegatorStartingInfoAddresses(iter.Key())
if handler(val, del, info) {
break
}
}
}
// get historical rewards for a particular period
func (k Keeper) GetValidatorHistoricalRewards(ctx sdk.Context, val sdk.ValAddress, period uint64) (rewards types.ValidatorHistoricalRewards) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetValidatorHistoricalRewardsKey(val, period))
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards)
return
}
// set historical rewards for a particular period
func (k Keeper) SetValidatorHistoricalRewards(ctx sdk.Context, val sdk.ValAddress, period uint64, rewards types.ValidatorHistoricalRewards) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards)
store.Set(GetValidatorHistoricalRewardsKey(val, period), b)
}
// iterate over historical rewards
func (k Keeper) IterateValidatorHistoricalRewards(ctx sdk.Context, handler func(val sdk.ValAddress, period uint64, rewards types.ValidatorHistoricalRewards) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var rewards types.ValidatorHistoricalRewards
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &rewards)
addr, period := GetValidatorHistoricalRewardsAddressPeriod(iter.Key())
if handler(addr, period, rewards) {
break
}
}
}
// delete historical rewards
func (k Keeper) DeleteValidatorHistoricalRewards(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
// get current rewards for a validator
func (k Keeper) GetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress) (rewards types.ValidatorCurrentRewards) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetValidatorCurrentRewardsKey(val))
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards)
return
}
// set current rewards for a validator
func (k Keeper) SetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress, rewards types.ValidatorCurrentRewards) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards)
store.Set(GetValidatorCurrentRewardsKey(val), b)
}
// iterate over current rewards
func (k Keeper) IterateValidatorCurrentRewards(ctx sdk.Context, handler func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorCurrentRewardsPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var rewards types.ValidatorCurrentRewards
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &rewards)
addr := GetValidatorCurrentRewardsAddress(iter.Key())
if handler(addr, rewards) {
break
}
}
}
// get accumulated commission for a validator
func (k Keeper) GetValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAddress) (commission types.ValidatorAccumulatedCommission) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetValidatorAccumulatedCommissionKey(val))
if b == nil {
return types.ValidatorAccumulatedCommission{}
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &commission)
return
}
// set accumulated commission for a validator
func (k Keeper) SetValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAddress, commission types.ValidatorAccumulatedCommission) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(commission)
store.Set(GetValidatorAccumulatedCommissionKey(val), b)
}
// iterate over accumulated commissions
func (k Keeper) IterateValidatorAccumulatedCommissions(ctx sdk.Context, handler func(val sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorAccumulatedCommissionPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var commission types.ValidatorAccumulatedCommission
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &commission)
addr := GetValidatorAccumulatedCommissionAddress(iter.Key())
if handler(addr, commission) {
break
}
}
}
// get outstanding rewards
func (k Keeper) GetOutstandingRewards(ctx sdk.Context) (rewards types.OutstandingRewards) {
store := ctx.KVStore(k.storeKey)
b := store.Get(OutstandingRewardsKey)
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards)
return
}
// set outstanding rewards
func (k Keeper) SetOutstandingRewards(ctx sdk.Context, rewards types.OutstandingRewards) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards)
store.Set(OutstandingRewardsKey, b)
}
// get slash event for height
func (k Keeper) GetValidatorSlashEvent(ctx sdk.Context, val sdk.ValAddress, height uint64) (event types.ValidatorSlashEvent, found bool) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetValidatorSlashEventKey(val, height))
if b == nil {
return types.ValidatorSlashEvent{}, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &event)
return event, true
}
// set slash event for height
func (k Keeper) SetValidatorSlashEvent(ctx sdk.Context, val sdk.ValAddress, height uint64, event types.ValidatorSlashEvent) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(event)
store.Set(GetValidatorSlashEventKey(val, height), b)
}
// iterate over slash events between heights, inclusive
func (k Keeper) IterateValidatorSlashEventsBetween(ctx sdk.Context, val sdk.ValAddress, startingHeight uint64, endingHeight uint64,
handler func(height uint64, event types.ValidatorSlashEvent) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := store.Iterator(
GetValidatorSlashEventKey(val, startingHeight),
GetValidatorSlashEventKey(val, endingHeight+1),
)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var event types.ValidatorSlashEvent
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &event)
_, height := GetValidatorSlashEventAddressHeight(iter.Key())
if handler(height, event) {
break
}
}
}
// iterate over all slash events
func (k Keeper) IterateValidatorSlashEvents(ctx sdk.Context, handler func(val sdk.ValAddress, height uint64, event types.ValidatorSlashEvent) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorSlashEventPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var event types.ValidatorSlashEvent
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &event)
val, height := GetValidatorSlashEventAddressHeight(iter.Key())
if handler(val, height, event) {
break
}
}
}
// delete all slash events
func (k Keeper) DeleteValidatorSlashEvents(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorSlashEventPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}

View File

@ -2,170 +2,70 @@ package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"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))
// initialize rewards for a new validator
func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) {
// set initial historical rewards (period 0)
k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), 0, types.ValidatorHistoricalRewards{})
// set current rewards (starting at period 1)
k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, 1))
// set accumulated commission
k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), types.InitialValidatorAccumulatedCommission())
}
// get the validator distribution info
func (k Keeper) GetValidatorDistInfo(ctx sdk.Context,
operatorAddr sdk.ValAddress) (vdi types.ValidatorDistInfo) {
// increment validator period, returning the period just ended
func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uint64 {
// fetch current rewards
rewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator())
store := ctx.KVStore(k.storeKey)
// calculate current ratio
var current sdk.DecCoins
if val.GetTokens().IsZero() {
b := store.Get(GetValidatorDistInfoKey(operatorAddr))
if b == nil {
panic("Stored validator-distribution info should not have been nil")
// can't calculate ratio for zero-token validators
// ergo we instead add to the community pool
feePool := k.GetFeePool(ctx)
outstanding := k.GetOutstandingRewards(ctx)
feePool.CommunityPool = feePool.CommunityPool.Plus(rewards.Rewards)
outstanding = outstanding.Minus(rewards.Rewards)
k.SetFeePool(ctx, feePool)
k.SetOutstandingRewards(ctx, outstanding)
current = sdk.DecCoins{}
} else {
current = rewards.Rewards.QuoDec(sdk.NewDecFromInt(val.GetTokens()))
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &vdi)
return
// fetch historical rewards for last period
historical := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period-1)
// fet new historical rewards
k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period, historical.Plus(current))
// set current rewards, incrementing period by 1
k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, rewards.Period+1))
return rewards.Period
}
// set the validator distribution info
func (k Keeper) SetValidatorDistInfo(ctx sdk.Context, vdi types.ValidatorDistInfo) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(vdi)
store.Set(GetValidatorDistInfoKey(vdi.OperatorAddr), b)
}
// remove a validator distribution info
func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) {
// defensive check
vdi := k.GetValidatorDistInfo(ctx, valAddr)
if vdi.DelAccum.Accum.IsPositive() {
panic("Should not delete validator with unwithdrawn delegator accum")
func (k Keeper) updateValidatorSlashFraction(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {
height := uint64(ctx.BlockHeight())
currentFraction := sdk.ZeroDec()
currentPeriod := k.GetValidatorCurrentRewards(ctx, valAddr).Period
current, found := k.GetValidatorSlashEvent(ctx, valAddr, height)
if found {
// there has already been a slash event this height,
// and we don't need to store more than one,
// so just update the current slash fraction
currentFraction = current.Fraction
}
if !vdi.ValCommission.IsZero() {
panic("Should not delete validator with unwithdrawn validator commission")
}
store := ctx.KVStore(k.storeKey)
store.Delete(GetValidatorDistInfoKey(valAddr))
}
// remove all validator distribution infos
func (k Keeper) RemoveValidatorDistInfos(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
// iterate over all the validator distribution infos
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.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &vdi)
if fn(index, vdi) {
return
}
index++
}
}
// Get the calculated accum of a validator at the current block
// without affecting the state.
func (k Keeper) GetValidatorAccum(ctx sdk.Context, operatorAddr sdk.ValAddress) (sdk.Dec, sdk.Error) {
if !k.HasValidatorDistInfo(ctx, operatorAddr) {
return sdk.Dec{}, types.ErrNoValidatorDistInfo(k.codespace)
}
// withdraw self-delegation
height := ctx.BlockHeight()
lastValPower := k.stakingKeeper.GetLastValidatorPower(ctx, operatorAddr)
valInfo := k.GetValidatorDistInfo(ctx, operatorAddr)
accum := valInfo.GetValAccum(height, sdk.NewDecFromInt(lastValPower))
return accum, nil
}
// takeValidatorFeePoolRewards updates the validator's distribution info
// from the global fee pool without withdrawing any rewards. This will be called
// from a onValidatorModified hook.
func (k Keeper) takeValidatorFeePoolRewards(ctx sdk.Context, operatorAddr sdk.ValAddress) sdk.Error {
if !k.HasValidatorDistInfo(ctx, operatorAddr) {
return types.ErrNoValidatorDistInfo(k.codespace)
}
accAddr := sdk.AccAddress(operatorAddr.Bytes())
// withdraw reward for self-delegation
if k.HasDelegationDistInfo(ctx, accAddr, operatorAddr) {
fp, vi, di, withdraw :=
k.withdrawDelegationReward(ctx, accAddr, operatorAddr)
k.SetFeePool(ctx, fp)
k.SetValidatorDistInfo(ctx, vi)
k.SetDelegationDistInfo(ctx, di)
k.WithdrawToDelegator(ctx, fp, accAddr, withdraw)
}
// withdrawal validator commission rewards
valInfo := k.GetValidatorDistInfo(ctx, operatorAddr)
wc := k.GetWithdrawContext(ctx, operatorAddr)
valInfo, feePool := valInfo.TakeFeePoolRewards(wc)
k.SetFeePool(ctx, feePool)
k.SetValidatorDistInfo(ctx, valInfo)
return nil
}
func (k Keeper) withdrawValidatorCommission(ctx sdk.Context, operatorAddr sdk.ValAddress) (types.FeePool, types.DecCoins) {
valInfo := k.GetValidatorDistInfo(ctx, operatorAddr)
wc := k.GetWithdrawContext(ctx, operatorAddr)
valInfo, feePool, commission := valInfo.WithdrawCommission(wc)
k.SetValidatorDistInfo(ctx, valInfo)
return feePool, commission
}
// withdrawal all the validator rewards including the commission
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
accAddr := sdk.AccAddress(operatorAddr.Bytes())
withdraw := k.withdrawDelegationRewardsAll(ctx, accAddr)
// withdrawal validator commission rewards
feePool, commission := k.withdrawValidatorCommission(ctx, operatorAddr)
withdraw = withdraw.Plus(commission)
k.WithdrawToDelegator(ctx, feePool, accAddr, withdraw)
return nil
}
// get all the validator rewards including the commission
func (k Keeper) CurrentValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.ValAddress) (sdk.Coins, sdk.Error) {
if !k.HasValidatorDistInfo(ctx, operatorAddr) {
return sdk.Coins{}, types.ErrNoValidatorDistInfo(k.codespace)
}
// withdraw self-delegation
accAddr := sdk.AccAddress(operatorAddr.Bytes())
withdraw := k.CurrentDelegationRewardsAll(ctx, accAddr)
// withdrawal validator commission rewards
valInfo := k.GetValidatorDistInfo(ctx, operatorAddr)
wc := k.GetWithdrawContext(ctx, operatorAddr)
commission := valInfo.CurrentCommissionRewards(wc)
withdraw = withdraw.Plus(commission)
truncated, _ := withdraw.TruncateDecimal()
return truncated, nil
currentMultiplicand := sdk.OneDec().Sub(currentFraction)
newMultiplicand := sdk.OneDec().Sub(fraction)
updatedFraction := sdk.OneDec().Sub(currentMultiplicand.Mul(newMultiplicand))
k.SetValidatorSlashEvent(ctx, valAddr, height, types.NewValidatorSlashEvent(currentPeriod, updatedFraction))
}

View File

@ -1,201 +0,0 @@
package keeper
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking"
)
func TestWithdrawValidatorRewardsAllNoDelegator(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
// first make a validator
msgCreateValidator := staking.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10)
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// withdraw self-delegation reward
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1)
amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom)
expRes := sdk.NewDec(90).Add(sdk.NewDec(100)).TruncateInt()
require.True(sdk.IntEq(t, expRes, amt))
}
func TestWithdrawValidatorRewardsAllDelegatorNoCommission(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
//first make a validator
msgCreateValidator := staking.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10)
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// delegate
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// withdraw self-delegation reward
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1)
amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom)
expRes := sdk.NewDec(90).Add(sdk.NewDec(100).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20
require.True(sdk.IntEq(t, expRes, amt))
}
func TestWithdrawValidatorRewardsAllDelegatorWithCommission(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
//first make a validator
commissionRate := sdk.NewDecWithPrec(1, 1)
msgCreateValidator := staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr1, valConsPk1, 10, commissionRate)
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// delegate
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// withdraw validator reward
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1)
amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom)
commissionTaken := sdk.NewDec(100).Mul(commissionRate)
afterCommission := sdk.NewDec(100).Sub(commissionTaken)
selfDelegationReward := afterCommission.Quo(sdk.NewDec(2))
expRes := sdk.NewDec(90).Add(commissionTaken).Add(selfDelegationReward).TruncateInt() // 90 + 100 tokens * 10/20
require.True(sdk.IntEq(t, expRes, amt))
}
func TestWithdrawValidatorRewardsAllMultipleValidator(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
// Make some validators with different commissions.
// Bond 10 of 100 with 0.1 commission.
msgCreateValidator := staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1))
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// Bond 50 of 100 with 0.2 commission.
msgCreateValidator = staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr2, valConsPk2, 50, sdk.NewDecWithPrec(2, 1))
got = stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// Bond 40 of 100 with 0.3 commission.
msgCreateValidator = staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr3, valConsPk3, 40, sdk.NewDecWithPrec(3, 1))
got = stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// Allocate 1000 denom of fees.
feeInputs := sdk.NewInt(1000)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
// Collect proposer reward for 100% of votes.
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// Withdraw validator reward.
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1)
amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom)
feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2))
feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2))
// NOTE: the non-proposer rewards (95) and proposer rewards (50) add up to
// 145. During computation, this is further split into 130.5 and 14.5,
// which is the non-commission and commission respectively, but the
// commission is for self so the result is just 145.
expRes := sdk.NewDec(90). // orig tokens (100) - bonded (10)
Add(feesInNonProposer.Quo(sdk.NewDec(10))). // validator 1 has 1/10 total power (non-proposer rewards = 95)
Add(feesInProposer). // (proposer rewards = 50)
TruncateInt()
require.True(sdk.IntEq(t, expRes, amt))
}
func TestWithdrawValidatorRewardsAllMultipleDelegator(t *testing.T) {
ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec())
stakingHandler := staking.NewHandler(sk)
denom := sk.GetParams(ctx).BondDenom
//first make a validator with 10% commission
commissionRate := sdk.NewDecWithPrec(1, 1)
msgCreateValidator := staking.NewTestMsgCreateValidatorWithCommission(
valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1))
got := stakingHandler(ctx, msgCreateValidator)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
_ = sk.ApplyAndReturnValidatorSetUpdates(ctx)
// delegate
msgDelegate := staking.NewTestMsgDelegate(delAddr1, valOpAddr1, 10)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom)
require.Equal(t, int64(90), amt.Int64())
msgDelegate = staking.NewTestMsgDelegate(delAddr2, valOpAddr1, 20)
got = stakingHandler(ctx, msgDelegate)
require.True(t, got.IsOK())
amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom)
require.Equal(t, int64(80), amt.Int64())
// allocate 100 denom of fees
feeInputs := sdk.NewInt(100)
fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)})
require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom))
keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1)
// withdraw validator reward
ctx = ctx.WithBlockHeight(1)
keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1)
amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom)
commissionTaken := sdk.NewDec(100).Mul(commissionRate)
afterCommission := sdk.NewDec(100).Sub(commissionTaken)
expRes := sdk.NewDec(90).
Add(afterCommission.Quo(sdk.NewDec(4))).
Add(commissionTaken).
TruncateInt() // 90 + 100*90% tokens * 10/40
require.True(sdk.IntEq(t, expRes, amt))
}

View File

@ -10,19 +10,13 @@ import (
)
// AllInvariants runs all invariants of the distribution module
// Currently: total supply, positive power
func AllInvariants(d distr.Keeper, stk staking.Keeper) simulation.Invariant {
sk := distr.StakingKeeper(stk)
return func(ctx sdk.Context) error {
err := ValAccumInvariants(d, sk)(ctx)
err := CanWithdrawInvariant(d, stk)(ctx)
if err != nil {
return err
}
err = DelAccumInvariants(d, sk)(ctx)
if err != nil {
return err
}
err = CanWithdrawInvariant(d, stk)(ctx)
err = NonNegativeOutstandingInvariant(d)(ctx)
if err != nil {
return err
}
@ -30,104 +24,13 @@ func AllInvariants(d distr.Keeper, stk staking.Keeper) simulation.Invariant {
}
}
// ValAccumInvariants checks that the fee pool accum == sum all validators' accum
func ValAccumInvariants(k distr.Keeper, sk distr.StakingKeeper) simulation.Invariant {
// NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative
func NonNegativeOutstandingInvariant(k distr.Keeper) 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())
outstanding := k.GetOutstandingRewards(ctx)
if outstanding.HasNegative() {
return fmt.Errorf("Negative outstanding coins: %v", outstanding)
}
return nil
}
}
// DelAccumInvariants checks that each validator del accum == sum all delegators' accum
func DelAccumInvariants(k distr.Keeper, sk distr.StakingKeeper) 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, power: %v",
ddi.DelegatorAddr.String(),
accum.String(),
delegation.GetShares())
}
}
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
}
}
@ -135,41 +38,28 @@ func DelAccumInvariants(k distr.Keeper, sk distr.StakingKeeper) simulation.Invar
// CanWithdrawInvariant checks that current rewards can be completely withdrawn
func CanWithdrawInvariant(k distr.Keeper, sk staking.Keeper) simulation.Invariant {
return func(ctx sdk.Context) error {
// we don't want to write the changes
// cache, we don't want to write 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)
}
// iterate over all bonded validators, withdraw commission
sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
_ = k.WithdrawValidatorCommission(ctx, val.GetOperator())
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())
// iterate over all current delegations, withdraw rewards
dels := sk.GetAllDelegations(ctx)
for _, delegation := range dels {
_ = k.WithdrawDelegationRewards(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr)
}
remaining := k.GetOutstandingRewards(ctx)
if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) {
return fmt.Errorf("Negative remaining coins: %v", remaining)
}
// all ok
return nil
}
}

View File

@ -39,33 +39,6 @@ func SimulateMsgSetWithdrawAddress(m auth.AccountKeeper, k distribution.Keeper)
}
}
// SimulateMsgWithdrawDelegatorRewardsAll
func SimulateMsgWithdrawDelegatorRewardsAll(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation {
handler := distribution.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, event func(string)) (
action string, fOp []simulation.FutureOperation, err error) {
account := simulation.RandomAcc(r, accs)
msg := distribution.NewMsgWithdrawDelegatorRewardsAll(account.Address)
if msg.ValidateBasic() != nil {
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
result := handler(ctx, msg)
if result.IsOK() {
write()
}
event(fmt.Sprintf("distribution/MsgWithdrawDelegatorRewardsAll/%v", result.IsOK()))
action = fmt.Sprintf("TestMsgWithdrawDelegatorRewardsAll: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
return action, nil, nil
}
}
// SimulateMsgWithdrawDelegatorReward
func SimulateMsgWithdrawDelegatorReward(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation {
handler := distribution.NewHandler(k)
@ -94,15 +67,15 @@ func SimulateMsgWithdrawDelegatorReward(m auth.AccountKeeper, k distribution.Kee
}
}
// SimulateMsgWithdrawValidatorRewardsAll
func SimulateMsgWithdrawValidatorRewardsAll(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation {
// SimulateMsgWithdrawValidatorCommission
func SimulateMsgWithdrawValidatorCommission(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation {
handler := distribution.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, event func(string)) (
action string, fOp []simulation.FutureOperation, err error) {
account := simulation.RandomAcc(r, accs)
msg := distribution.NewMsgWithdrawValidatorRewardsAll(sdk.ValAddress(account.Address))
msg := distribution.NewMsgWithdrawValidatorCommission(sdk.ValAddress(account.Address))
if msg.ValidateBasic() != nil {
return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
@ -114,9 +87,9 @@ func SimulateMsgWithdrawValidatorRewardsAll(m auth.AccountKeeper, k distribution
write()
}
event(fmt.Sprintf("distribution/MsgWithdrawValidatorRewardsAll/%v", result.IsOK()))
event(fmt.Sprintf("distribution/MsgWithdrawValidatorCommission/%v", result.IsOK()))
action = fmt.Sprintf("TestMsgWithdrawValidatorRewardsAll: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
action = fmt.Sprintf("TestMsgWithdrawValidatorCommission: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
return action, nil, nil
}
}

View File

@ -6,9 +6,8 @@ import (
// Register concrete types on codec codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgWithdrawDelegatorRewardsAll{}, "cosmos-sdk/MsgWithdrawDelegationRewardsAll", nil)
cdc.RegisterConcrete(MsgWithdrawDelegatorReward{}, "cosmos-sdk/MsgWithdrawDelegationReward", nil)
cdc.RegisterConcrete(MsgWithdrawValidatorRewardsAll{}, "cosmos-sdk/MsgWithdrawValidatorRewardsAll", nil)
cdc.RegisterConcrete(MsgWithdrawValidatorCommission{}, "cosmos-sdk/MsgWithdrawValidatorCommission", nil)
cdc.RegisterConcrete(MsgSetWithdrawAddress{}, "cosmos-sdk/MsgModifyWithdrawAddress", nil)
}

View File

@ -1,122 +0,0 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// distribution info for a delegation - used to determine entitled rewards
type DelegationDistInfo struct {
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
ValOperatorAddr sdk.ValAddress `json:"val_operator_addr"`
DelPoolWithdrawalHeight int64 `json:"del_pool_withdrawal_height"` // last time this delegation withdrew rewards
}
func NewDelegationDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress,
currentHeight int64) DelegationDistInfo {
return DelegationDistInfo{
DelegatorAddr: delegatorAddr,
ValOperatorAddr: valOperatorAddr,
DelPoolWithdrawalHeight: currentHeight,
}
}
// Get the calculated accum of this delegator at the provided height
func (di DelegationDistInfo) GetDelAccum(height int64, delegatorShares sdk.Dec) sdk.Dec {
blocks := height - di.DelPoolWithdrawalHeight
accum := delegatorShares.MulInt(sdk.NewInt(blocks))
// defensive check
if accum.IsNegative() {
panic(fmt.Sprintf("negative accum: %v\n"+
"\theight: %v\n"+
"\tdelegation_dist_info: %v\n"+
"\tdelegator_shares: %v\n",
accum.String(), height, di, delegatorShares))
}
return accum
}
// Withdraw rewards from delegator.
// Among many things, it does:
// * updates validator info's total del accum
// * calls vi.TakeFeePoolRewards, which:
// * updates validator info's FeePoolWithdrawalHeight, thus setting accum to 0
// * updates fee pool to latest height and total val accum w/ given totalBonded
// (see comment on TakeFeePoolRewards for more info)
func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDistInfo,
totalDelShares, delegatorShares sdk.Dec) (
DelegationDistInfo, ValidatorDistInfo, FeePool, DecCoins) {
fp := wc.FeePool
vi = vi.UpdateTotalDelAccum(wc.Height, totalDelShares)
// Break out to prevent a divide by zero.
if vi.DelAccum.Accum.IsZero() {
di.DelPoolWithdrawalHeight = wc.Height
return di, vi, fp, DecCoins{}
}
vi, fp = vi.TakeFeePoolRewards(wc)
accum := di.GetDelAccum(wc.Height, delegatorShares)
di.DelPoolWithdrawalHeight = wc.Height
withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum)
// Clip withdrawal tokens by pool, due to possible rounding errors.
// This rounding error may be introduced upon multiplication since
// we're clipping decimal digits, and then when we divide by a number ~1 or
// < 1, the error doesn't get "buried", and if << 1 it'll get amplified.
// more: https://github.com/cosmos/cosmos-sdk/issues/2888#issuecomment-441387987
for i, decCoin := range withdrawalTokens {
poolDenomAmount := vi.DelPool.AmountOf(decCoin.Denom)
if decCoin.Amount.GT(poolDenomAmount) {
withdrawalTokens[i] = NewDecCoinFromDec(decCoin.Denom, poolDenomAmount)
}
}
// defensive check for impossible accum ratios
if accum.GT(vi.DelAccum.Accum) {
panic(fmt.Sprintf("accum > vi.DelAccum.Accum:\n"+
"\taccum\t\t\t%v\n"+
"\tvi.DelAccum.Accum\t%v\n",
accum, vi.DelAccum.Accum))
}
remDelPool := vi.DelPool.Minus(withdrawalTokens)
// defensive check
if remDelPool.HasNegative() {
panic(fmt.Sprintf("negative remDelPool: %v\n"+
"\tvi.DelPool\t\t%v\n"+
"\taccum\t\t\t%v\n"+
"\tvi.DelAccum.Accum\t%v\n"+
"\twithdrawalTokens\t%v\n",
remDelPool, vi.DelPool, accum,
vi.DelAccum.Accum, withdrawalTokens))
}
vi.DelPool = remDelPool
vi.DelAccum.Accum = vi.DelAccum.Accum.Sub(accum)
return di, vi, fp, withdrawalTokens
}
// get the delegators rewards at this current state,
func (di DelegationDistInfo) CurrentRewards(wc WithdrawContext, vi ValidatorDistInfo,
totalDelShares, delegatorShares sdk.Dec) DecCoins {
totalDelAccum := vi.GetTotalDelAccum(wc.Height, totalDelShares)
if vi.DelAccum.Accum.IsZero() {
return DecCoins{}
}
rewards := vi.CurrentPoolRewards(wc)
accum := di.GetDelAccum(wc.Height, delegatorShares)
tokens := rewards.MulDec(accum).QuoDec(totalDelAccum)
return tokens
}

View File

@ -1,62 +0,0 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
func TestWithdrawRewards(t *testing.T) {
// initialize
height := int64(0)
fp := InitialFeePool()
vi := NewValidatorDistInfo(valAddr1, height)
commissionRate := sdk.NewDecWithPrec(2, 2)
validatorTokens := sdk.NewDec(10)
validatorDelShares := sdk.NewDec(10)
totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power
di1 := NewDelegationDistInfo(delAddr1, valAddr1, height)
di1Shares := sdk.NewDec(5) // this delegator has half the shares in the validator
di2 := NewDelegationDistInfo(delAddr2, valAddr1, height)
di2Shares := sdk.NewDec(5)
// simulate adding some stake for inflation
height = 10
fp.ValPool = DecCoins{NewDecCoin(stakingTypes.DefaultBondDenom, 1000)}
// withdraw rewards
wc := NewWithdrawContext(fp, height,
totalBondedTokens, validatorTokens, commissionRate)
di1, vi, fp, rewardRecv1 := di1.WithdrawRewards(wc, vi,
validatorDelShares, di1Shares)
assert.Equal(t, height, di1.DelPoolWithdrawalHeight)
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum))
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.ValCommission[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(49), rewardRecv1[0].Amount))
// add more blocks and inflation
height = 20
fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000))
// withdraw rewards
wc = NewWithdrawContext(fp, height,
totalBondedTokens, validatorTokens, commissionRate)
di2, vi, fp, rewardRecv2 := di2.WithdrawRewards(wc, vi,
validatorDelShares, di2Shares)
assert.Equal(t, height, di2.DelPoolWithdrawalHeight)
assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum))
assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.ValCommission[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(98), rewardRecv2[0].Amount))
}

View File

@ -0,0 +1,27 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// starting info for a delegator reward period
// tracks the previous validator period, the delegation's amount
// of staking token, and the creation height (to check later on
// if any slashes have occurred)
// NOTE that even though validators are slashed to whole staking tokens, the
// delegators within the validator may be left with less than a full token,
// thus sdk.Dec is used
type DelegatorStartingInfo struct {
PreviousPeriod uint64 `json:"previous_period"` // period at which the delegation should withdraw starting from
Stake sdk.Dec `json:"stake"` // amount of staking token delegated
Height uint64 `json:"height"` // height at which delegation was created
}
// create a new DelegatorStartingInfo
func NewDelegatorStartingInfo(previousPeriod uint64, stake sdk.Dec, height uint64) DelegatorStartingInfo {
return DelegatorStartingInfo{
PreviousPeriod: previousPeriod,
Stake: stake,
Height: height,
}
}

View File

@ -8,9 +8,10 @@ import (
type CodeType = sdk.CodeType
const (
DefaultCodespace sdk.CodespaceType = "DISTR"
CodeInvalidInput CodeType = 103
CodeNoDistributionInfo CodeType = 104
DefaultCodespace sdk.CodespaceType = "DISTR"
CodeInvalidInput CodeType = 103
CodeNoDistributionInfo CodeType = 104
CodeNoValidatorCommission CodeType = 105
)
func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error {
@ -28,3 +29,6 @@ func ErrNoDelegationDistInfo(codespace sdk.CodespaceType) sdk.Error {
func ErrNoValidatorDistInfo(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeNoDistributionInfo, "no validator distribution info")
}
func ErrNoValidatorCommission(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeNoValidatorCommission, "no validator commission to withdraw")
}

View File

@ -8,46 +8,18 @@ import (
// global fee pool for distribution
type FeePool struct {
TotalValAccum TotalAccum `json:"val_accum"` // total valdator accum held by validators
ValPool DecCoins `json:"val_pool"` // funds for all validators which have yet to be withdrawn
CommunityPool DecCoins `json:"community_pool"` // pool for community funds yet to be spent
}
// update total validator accumulation factor
// NOTE: Do not call this except from ValidatorDistInfo.TakeFeePoolRewards().
func (f FeePool) UpdateTotalValAccum(height int64, totalBondedTokens sdk.Dec) FeePool {
f.TotalValAccum = f.TotalValAccum.UpdateForNewHeight(height, totalBondedTokens)
return f
}
// get the total validator accum for the fee pool without modifying the state
func (f FeePool) GetTotalValAccum(height int64, totalBondedTokens sdk.Dec) sdk.Dec {
return f.TotalValAccum.GetAccum(height, totalBondedTokens)
CommunityPool sdk.DecCoins `json:"community_pool"` // pool for community funds yet to be spent
}
// zero fee pool
func InitialFeePool() FeePool {
return FeePool{
TotalValAccum: NewTotalAccum(0),
ValPool: DecCoins{},
CommunityPool: DecCoins{},
CommunityPool: sdk.DecCoins{},
}
}
// ValidateGenesis validates the fee pool for a genesis state
func (f FeePool) ValidateGenesis() error {
if f.TotalValAccum.Accum.IsNegative() {
return fmt.Errorf("negative accum in distribution fee pool, is %v",
f.TotalValAccum.Accum.String())
}
if f.TotalValAccum.UpdateHeight < 0 {
return fmt.Errorf("negative update height in distribution fee pool, is %v",
f.TotalValAccum.UpdateHeight)
}
if f.ValPool.HasNegative() {
return fmt.Errorf("negative ValPool in distribution fee pool, is %v",
f.ValPool)
}
if f.CommunityPool.HasNegative() {
return fmt.Errorf("negative CommunityPool in distribution fee pool, is %v",
f.CommunityPool)
@ -55,3 +27,8 @@ func (f FeePool) ValidateGenesis() error {
return nil
}
// outstanding (un-withdrawn) rewards for everyone
// excludes the community pool
// inexpensive to track, allows simple sanity checks
type OutstandingRewards = sdk.DecCoins

View File

@ -8,13 +8,12 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestUpdateTotalValAccum(t *testing.T) {
func TestValidateGenesis(t *testing.T) {
fp := InitialFeePool()
require.Nil(t, fp.ValidateGenesis())
fp = fp.UpdateTotalValAccum(5, sdk.NewDec(3))
require.True(sdk.DecEq(t, sdk.NewDec(15), fp.TotalValAccum.Accum))
fp2 := FeePool{CommunityPool: sdk.DecCoins{{"stake", sdk.NewDec(-1)}}}
require.NotNil(t, fp2.ValidateGenesis())
fp = fp.UpdateTotalValAccum(8, sdk.NewDec(2))
require.True(sdk.DecEq(t, sdk.NewDec(21), fp.TotalValAccum.Accum))
}

View File

@ -13,62 +13,92 @@ type DelegatorWithdrawInfo struct {
WithdrawAddr sdk.AccAddress `json:"withdraw_addr"`
}
// used for import / export via genesis json
type ValidatorAccumulatedCommissionRecord struct {
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
Accumulated ValidatorAccumulatedCommission `json:"accumulated"`
}
// used for import / export via genesis json
type ValidatorHistoricalRewardsRecord struct {
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
Period uint64 `json:"period"`
Rewards ValidatorHistoricalRewards `json:"rewards"`
}
// used for import / export via genesis json
type ValidatorCurrentRewardsRecord struct {
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
Rewards ValidatorCurrentRewards `json:"rewards"`
}
// used for import / export via genesis json
type DelegatorStartingInfoRecord struct {
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
StartingInfo DelegatorStartingInfo `json:"starting_info"`
}
// used for import / export via genesis json
type ValidatorSlashEventRecord struct {
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
Height uint64 `json:"height"`
Event ValidatorSlashEvent `json:"validator_slash_event"`
}
// GenesisState - all distribution state that must be provided at genesis
type GenesisState struct {
FeePool FeePool `json:"fee_pool"`
CommunityTax sdk.Dec `json:"community_tax"`
BaseProposerReward sdk.Dec `json:"base_proposer_reward"`
BonusProposerReward sdk.Dec `json:"bonus_proposer_reward"`
ValidatorDistInfos []ValidatorDistInfo `json:"validator_dist_infos"`
DelegationDistInfos []DelegationDistInfo `json:"delegator_dist_infos"`
DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"`
PreviousProposer sdk.ConsAddress `json:"previous_proposer"`
FeePool FeePool `json:"fee_pool"`
CommunityTax sdk.Dec `json:"community_tax"`
BaseProposerReward sdk.Dec `json:"base_proposer_reward"`
BonusProposerReward sdk.Dec `json:"bonus_proposer_reward"`
DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"`
PreviousProposer sdk.ConsAddress `json:"previous_proposer"`
OutstandingRewards sdk.DecCoins `json:"outstanding_rewards"`
ValidatorAccumulatedCommissions []ValidatorAccumulatedCommissionRecord `json:"validator_accumulated_commissions"`
ValidatorHistoricalRewards []ValidatorHistoricalRewardsRecord `json:"validator_historical_rewards"`
ValidatorCurrentRewards []ValidatorCurrentRewardsRecord `json:"validator_current_rewards"`
DelegatorStartingInfos []DelegatorStartingInfoRecord `json:"delegator_starting_infos"`
ValidatorSlashEvents []ValidatorSlashEventRecord `json:"validator_slash_events"`
}
func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec,
vdis []ValidatorDistInfo, ddis []DelegationDistInfo, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress) GenesisState {
dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress, r OutstandingRewards,
acc []ValidatorAccumulatedCommissionRecord, historical []ValidatorHistoricalRewardsRecord,
cur []ValidatorCurrentRewardsRecord, dels []DelegatorStartingInfoRecord,
slashes []ValidatorSlashEventRecord) GenesisState {
return GenesisState{
FeePool: feePool,
CommunityTax: communityTax,
BaseProposerReward: baseProposerReward,
BonusProposerReward: bonusProposerReward,
ValidatorDistInfos: vdis,
DelegationDistInfos: ddis,
DelegatorWithdrawInfos: dwis,
PreviousProposer: pp,
FeePool: feePool,
CommunityTax: communityTax,
BaseProposerReward: baseProposerReward,
BonusProposerReward: bonusProposerReward,
DelegatorWithdrawInfos: dwis,
PreviousProposer: pp,
OutstandingRewards: r,
ValidatorAccumulatedCommissions: acc,
ValidatorHistoricalRewards: historical,
ValidatorCurrentRewards: cur,
DelegatorStartingInfos: dels,
ValidatorSlashEvents: slashes,
}
}
// get raw genesis raw message for testing
func DefaultGenesisState() GenesisState {
return GenesisState{
FeePool: InitialFeePool(),
CommunityTax: sdk.NewDecWithPrec(2, 2), // 2%
BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1%
BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4%
}
}
// default genesis utility function, initialize for starting validator set
func DefaultGenesisWithValidators(valAddrs []sdk.ValAddress) GenesisState {
vdis := make([]ValidatorDistInfo, len(valAddrs))
ddis := make([]DelegationDistInfo, len(valAddrs))
for i, valAddr := range valAddrs {
vdis[i] = NewValidatorDistInfo(valAddr, 0)
accAddr := sdk.AccAddress(valAddr)
ddis[i] = NewDelegationDistInfo(accAddr, valAddr, 0)
}
return GenesisState{
FeePool: InitialFeePool(),
CommunityTax: sdk.NewDecWithPrec(2, 2), // 2%
BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1%
BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4%
ValidatorDistInfos: vdis,
DelegationDistInfos: ddis,
FeePool: InitialFeePool(),
CommunityTax: sdk.NewDecWithPrec(2, 2), // 2%
BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1%
BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4%
DelegatorWithdrawInfos: []DelegatorWithdrawInfo{},
PreviousProposer: nil,
OutstandingRewards: sdk.DecCoins{},
ValidatorAccumulatedCommissions: []ValidatorAccumulatedCommissionRecord{},
ValidatorHistoricalRewards: []ValidatorHistoricalRewardsRecord{},
ValidatorCurrentRewards: []ValidatorCurrentRewardsRecord{},
DelegatorStartingInfos: []DelegatorStartingInfoRecord{},
ValidatorSlashEvents: []ValidatorSlashEventRecord{},
}
}

View File

@ -19,7 +19,7 @@ type BankKeeper interface {
AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
}
// from ante handler
// expected fee collection keeper
type FeeCollectionKeeper interface {
GetCollectedFees(ctx sdk.Context) sdk.Coins
ClearCollectedFees(ctx sdk.Context)

View File

@ -5,11 +5,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Verify interface at compile time
var _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorRewardsAll{}
var _, _ sdk.Msg = &MsgWithdrawDelegatorReward{}, &MsgWithdrawValidatorRewardsAll{}
// name to identify transaction types
const MsgRoute = "distr"
//______________________________________________________________________
// Verify interface at compile time
var _, _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorReward{}, &MsgWithdrawValidatorCommission{}
// msg struct for changing the withdraw address for a delegator (or validator self-delegation)
type MsgSetWithdrawAddress struct {
@ -24,7 +24,7 @@ func NewMsgSetWithdrawAddress(delAddr, withdrawAddr sdk.AccAddress) MsgSetWithdr
}
}
func (msg MsgSetWithdrawAddress) Route() string { return RouterKey }
func (msg MsgSetWithdrawAddress) Route() string { return MsgRoute }
func (msg MsgSetWithdrawAddress) Type() string { return "set_withdraw_address" }
// Return address that must sign over msg.GetSignBytes()
@ -52,46 +52,6 @@ func (msg MsgSetWithdrawAddress) ValidateBasic() sdk.Error {
return nil
}
//______________________________________________________________________
// msg struct for delegation withdraw for all of the delegator's delegations
type MsgWithdrawDelegatorRewardsAll struct {
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
}
func NewMsgWithdrawDelegatorRewardsAll(delAddr sdk.AccAddress) MsgWithdrawDelegatorRewardsAll {
return MsgWithdrawDelegatorRewardsAll{
DelegatorAddr: delAddr,
}
}
func (msg MsgWithdrawDelegatorRewardsAll) Route() string { return RouterKey }
func (msg MsgWithdrawDelegatorRewardsAll) Type() string { return "withdraw_delegation_rewards_all" }
// Return address that must sign over msg.GetSignBytes()
func (msg MsgWithdrawDelegatorRewardsAll) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)}
}
// get the bytes for the message signer to sign on
func (msg MsgWithdrawDelegatorRewardsAll) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(b)
}
// quick validity check
func (msg MsgWithdrawDelegatorRewardsAll) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrNilDelegatorAddr(DefaultCodespace)
}
return nil
}
//______________________________________________________________________
// msg struct for delegation withdraw from a single validator
type MsgWithdrawDelegatorReward struct {
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
@ -105,7 +65,7 @@ func NewMsgWithdrawDelegatorReward(delAddr sdk.AccAddress, valAddr sdk.ValAddres
}
}
func (msg MsgWithdrawDelegatorReward) Route() string { return RouterKey }
func (msg MsgWithdrawDelegatorReward) Route() string { return MsgRoute }
func (msg MsgWithdrawDelegatorReward) Type() string { return "withdraw_delegation_reward" }
// Return address that must sign over msg.GetSignBytes()
@ -133,29 +93,27 @@ func (msg MsgWithdrawDelegatorReward) ValidateBasic() sdk.Error {
return nil
}
//______________________________________________________________________
// msg struct for validator withdraw
type MsgWithdrawValidatorRewardsAll struct {
type MsgWithdrawValidatorCommission struct {
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
}
func NewMsgWithdrawValidatorRewardsAll(valAddr sdk.ValAddress) MsgWithdrawValidatorRewardsAll {
return MsgWithdrawValidatorRewardsAll{
func NewMsgWithdrawValidatorCommission(valAddr sdk.ValAddress) MsgWithdrawValidatorCommission {
return MsgWithdrawValidatorCommission{
ValidatorAddr: valAddr,
}
}
func (msg MsgWithdrawValidatorRewardsAll) Route() string { return RouterKey }
func (msg MsgWithdrawValidatorRewardsAll) Type() string { return "withdraw_validator_rewards_all" }
func (msg MsgWithdrawValidatorCommission) Route() string { return MsgRoute }
func (msg MsgWithdrawValidatorCommission) Type() string { return "withdraw_validator_rewards_all" }
// Return address that must sign over msg.GetSignBytes()
func (msg MsgWithdrawValidatorRewardsAll) GetSigners() []sdk.AccAddress {
func (msg MsgWithdrawValidatorCommission) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr.Bytes())}
}
// get the bytes for the message signer to sign on
func (msg MsgWithdrawValidatorRewardsAll) GetSignBytes() []byte {
func (msg MsgWithdrawValidatorCommission) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(msg)
if err != nil {
panic(err)
@ -164,7 +122,7 @@ func (msg MsgWithdrawValidatorRewardsAll) GetSignBytes() []byte {
}
// quick validity check
func (msg MsgWithdrawValidatorRewardsAll) ValidateBasic() sdk.Error {
func (msg MsgWithdrawValidatorCommission) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}

View File

@ -8,7 +8,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// test ValidateBasic for MsgCreateValidator
// test ValidateBasic for MsgSetWithdrawAddress
func TestMsgSetWithdrawAddress(t *testing.T) {
tests := []struct {
delegatorAddr sdk.AccAddress
@ -32,7 +32,7 @@ func TestMsgSetWithdrawAddress(t *testing.T) {
}
}
// test ValidateBasic for MsgEditValidator
// test ValidateBasic for MsgWithdrawDelegatorReward
func TestMsgWithdrawDelegatorReward(t *testing.T) {
tests := []struct {
delegatorAddr sdk.AccAddress
@ -54,27 +54,8 @@ func TestMsgWithdrawDelegatorReward(t *testing.T) {
}
}
// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf
func TestMsgWithdrawDelegatorRewardsAll(t *testing.T) {
tests := []struct {
delegatorAddr sdk.AccAddress
expectPass bool
}{
{delAddr1, true},
{emptyDelAddr, false},
}
for i, tc := range tests {
msg := NewMsgWithdrawDelegatorRewardsAll(tc.delegatorAddr)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test index: %v", i)
} else {
require.NotNil(t, msg.ValidateBasic(), "test index: %v", i)
}
}
}
// test ValidateBasic for MsgDelegate
func TestMsgWithdrawValidatorRewardsAll(t *testing.T) {
// test ValidateBasic for MsgWithdrawValidatorCommission
func TestMsgWithdrawValidatorCommission(t *testing.T) {
tests := []struct {
validatorAddr sdk.ValAddress
expectPass bool
@ -83,7 +64,7 @@ func TestMsgWithdrawValidatorRewardsAll(t *testing.T) {
{emptyValAddr, false},
}
for i, tc := range tests {
msg := NewMsgWithdrawValidatorRewardsAll(tc.validatorAddr)
msg := NewMsgWithdrawValidatorCommission(tc.validatorAddr)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test index: %v", i)
} else {

View File

@ -1,40 +0,0 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// total accumulation tracker
type TotalAccum struct {
UpdateHeight int64 `json:"update_height"`
Accum sdk.Dec `json:"accum"`
}
func NewTotalAccum(height int64) TotalAccum {
return TotalAccum{
UpdateHeight: height,
Accum: sdk.ZeroDec(),
}
}
// update total accumulation factor for the new height
// CONTRACT: height should be greater than the old height
func (ta TotalAccum) UpdateForNewHeight(height int64, accumCreatedPerBlock sdk.Dec) TotalAccum {
blocks := height - ta.UpdateHeight
if blocks < 0 {
panic("reverse updated for new height")
}
ta.Accum = ta.Accum.Add(accumCreatedPerBlock.MulInt(sdk.NewInt(blocks)))
ta.UpdateHeight = height
return ta
}
// get total accumulation factor for the given height
// CONTRACT: height should be greater than the old height
func (ta TotalAccum) GetAccum(height int64, accumCreatedPerBlock sdk.Dec) sdk.Dec {
blocks := height - ta.UpdateHeight
if blocks < 0 {
panic("reverse updated for new height")
}
return ta.Accum.Add(accumCreatedPerBlock.MulInt(sdk.NewInt(blocks)))
}

View File

@ -1,20 +0,0 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestTotalAccumUpdateForNewHeight(t *testing.T) {
ta := NewTotalAccum(0)
ta = ta.UpdateForNewHeight(5, sdk.NewDec(3))
require.True(sdk.DecEq(t, sdk.NewDec(15), ta.Accum))
ta = ta.UpdateForNewHeight(8, sdk.NewDec(2))
require.True(sdk.DecEq(t, sdk.NewDec(21), ta.Accum))
}

View File

@ -0,0 +1,52 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// historical rewards for a validator
// TODO add reference counter, ref https://github.com/cosmos/cosmos-sdk/pull/3099#discussion_r245747051
// height is implicit within the store key
type ValidatorHistoricalRewards = sdk.DecCoins
// current rewards and current period for a validator
// kept as a running counter and incremented each block
// as long as the validator's tokens remain constant
type ValidatorCurrentRewards struct {
Rewards sdk.DecCoins `json:"rewards"` // current rewards
Period uint64 `json:"period"` // current period
}
// create a new ValidatorCurrentRewards
func NewValidatorCurrentRewards(rewards sdk.DecCoins, period uint64) ValidatorCurrentRewards {
return ValidatorCurrentRewards{
Rewards: rewards,
Period: period,
}
}
// accumulated commission for a validator
// kept as a running counter, can be withdrawn at any time
type ValidatorAccumulatedCommission = sdk.DecCoins
// return the initial accumulated commission (zero)
func InitialValidatorAccumulatedCommission() ValidatorAccumulatedCommission {
return ValidatorAccumulatedCommission{}
}
// validator slash event
// height is implicit within the store key
// needed to calculate appropriate amounts of staking token
// for delegations which withdraw after a slash has occurred
type ValidatorSlashEvent struct {
ValidatorPeriod uint64 `json:"validator_period"` // period when the slash occurred
Fraction sdk.Dec `json:"fraction"` // slash fraction
}
// create a new ValidatorSlashEvent
func NewValidatorSlashEvent(validatorPeriod uint64, fraction sdk.Dec) ValidatorSlashEvent {
return ValidatorSlashEvent{
ValidatorPeriod: validatorPeriod,
Fraction: fraction,
}
}

View File

@ -1,153 +0,0 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// common parameters used in withdraws from validators
type WithdrawContext struct {
FeePool FeePool
Height int64 // block height
TotalPower sdk.Dec // total bonded tokens in the network
ValPower sdk.Dec // validator's bonded tokens
CommissionRate sdk.Dec // validator commission rate
}
func NewWithdrawContext(feePool FeePool, height int64, totalPower,
valPower, commissionRate sdk.Dec) WithdrawContext {
return WithdrawContext{
FeePool: feePool,
Height: height,
TotalPower: totalPower,
ValPower: valPower,
CommissionRate: commissionRate,
}
}
//_____________________________________________________________________________
// distribution info for a particular validator
type ValidatorDistInfo struct {
OperatorAddr sdk.ValAddress `json:"operator_addr"`
FeePoolWithdrawalHeight int64 `json:"fee_pool_withdrawal_height"` // last height this validator withdrew from the global pool
DelAccum TotalAccum `json:"del_accum"` // total accumulation factor held by delegators
DelPool DecCoins `json:"del_pool"` // rewards owed to delegators, commission has already been charged (includes proposer reward)
ValCommission DecCoins `json:"val_commission"` // commission collected by this validator (pending withdrawal)
}
func NewValidatorDistInfo(operatorAddr sdk.ValAddress, currentHeight int64) ValidatorDistInfo {
return ValidatorDistInfo{
OperatorAddr: operatorAddr,
FeePoolWithdrawalHeight: currentHeight,
DelPool: DecCoins{},
DelAccum: NewTotalAccum(currentHeight),
ValCommission: DecCoins{},
}
}
// update total delegator accumululation
func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares sdk.Dec) ValidatorDistInfo {
vi.DelAccum = vi.DelAccum.UpdateForNewHeight(height, totalDelShares)
return vi
}
// Get the total delegator accum within this validator at the provided height
func (vi ValidatorDistInfo) GetTotalDelAccum(height int64, totalDelShares sdk.Dec) sdk.Dec {
return vi.DelAccum.GetAccum(height, totalDelShares)
}
// Get the validator accum at the provided height
func (vi ValidatorDistInfo) GetValAccum(height int64, valTokens sdk.Dec) sdk.Dec {
blocks := height - vi.FeePoolWithdrawalHeight
return valTokens.MulInt(sdk.NewInt(blocks))
}
// Move any available accumulated fees in the FeePool to the validator's pool
// - updates validator info's FeePoolWithdrawalHeight, thus setting accum to 0
// - updates fee pool to latest height and total val accum w/ given totalBonded
// This is the only way to update the FeePool's validator TotalAccum.
// NOTE: This algorithm works as long as TakeFeePoolRewards is called after every power change.
// - called in ValidationDistInfo.WithdrawCommission
// - called in DelegationDistInfo.WithdrawRewards
// NOTE: When a delegator unbonds, say, onDelegationSharesModified ->
// WithdrawDelegationReward -> WithdrawRewards
func (vi ValidatorDistInfo) TakeFeePoolRewards(wc WithdrawContext) (
ValidatorDistInfo, FeePool) {
fp := wc.FeePool.UpdateTotalValAccum(wc.Height, wc.TotalPower)
if fp.TotalValAccum.Accum.IsZero() {
vi.FeePoolWithdrawalHeight = wc.Height
return vi, fp
}
// update the validators pool
accum := vi.GetValAccum(wc.Height, wc.ValPower)
vi.FeePoolWithdrawalHeight = wc.Height
if accum.GT(fp.TotalValAccum.Accum) {
panic("individual accum should never be greater than the total")
}
withdrawalTokens := fp.ValPool.MulDec(accum).QuoDec(fp.TotalValAccum.Accum) // XXX ensure this doesn't cause problems
remValPool := fp.ValPool.Minus(withdrawalTokens)
commission := withdrawalTokens.MulDec(wc.CommissionRate)
afterCommission := withdrawalTokens.Minus(commission)
fp.TotalValAccum.Accum = fp.TotalValAccum.Accum.Sub(accum)
fp.ValPool = remValPool
vi.ValCommission = vi.ValCommission.Plus(commission)
vi.DelPool = vi.DelPool.Plus(afterCommission)
return vi, fp
}
// withdraw commission rewards
func (vi ValidatorDistInfo) WithdrawCommission(wc WithdrawContext) (
vio ValidatorDistInfo, fpo FeePool, withdrawn DecCoins) {
vi, fp := vi.TakeFeePoolRewards(wc)
withdrawalTokens := vi.ValCommission
vi.ValCommission = DecCoins{} // zero
return vi, fp, withdrawalTokens
}
// get the validator's pool rewards at this current state,
func (vi ValidatorDistInfo) CurrentPoolRewards(
wc WithdrawContext) DecCoins {
fp := wc.FeePool
totalValAccum := fp.GetTotalValAccum(wc.Height, wc.TotalPower)
valAccum := vi.GetValAccum(wc.Height, wc.ValPower)
if valAccum.GT(totalValAccum) {
panic("individual accum should never be greater than the total")
}
withdrawalTokens := fp.ValPool.MulDec(valAccum).QuoDec(totalValAccum)
commission := withdrawalTokens.MulDec(wc.CommissionRate)
afterCommission := withdrawalTokens.Minus(commission)
pool := vi.DelPool.Plus(afterCommission)
return pool
}
// get the validator's commission pool rewards at this current state,
func (vi ValidatorDistInfo) CurrentCommissionRewards(
wc WithdrawContext) DecCoins {
fp := wc.FeePool
totalValAccum := fp.GetTotalValAccum(wc.Height, wc.TotalPower)
valAccum := vi.GetValAccum(wc.Height, wc.ValPower)
if valAccum.GT(totalValAccum) {
panic("individual accum should never be greater than the total")
}
withdrawalTokens := fp.ValPool.MulDec(valAccum).QuoDec(totalValAccum)
commission := withdrawalTokens.MulDec(wc.CommissionRate)
commissionPool := vi.ValCommission.Plus(commission)
return commissionPool
}

View File

@ -1,92 +0,0 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
func TestTakeFeePoolRewards(t *testing.T) {
// initialize
height := int64(0)
fp := InitialFeePool()
vi1 := NewValidatorDistInfo(valAddr1, height)
vi2 := NewValidatorDistInfo(valAddr2, height)
vi3 := NewValidatorDistInfo(valAddr3, height)
commissionRate1 := sdk.NewDecWithPrec(2, 2)
commissionRate2 := sdk.NewDecWithPrec(3, 2)
commissionRate3 := sdk.NewDecWithPrec(4, 2)
validatorTokens1 := sdk.NewDec(10)
validatorTokens2 := sdk.NewDec(40)
validatorTokens3 := sdk.NewDec(50)
totalBondedTokens := validatorTokens1.Add(validatorTokens2).Add(validatorTokens3)
// simulate adding some stake for inflation
height = 10
fp.ValPool = DecCoins{NewDecCoin(stakingTypes.DefaultBondDenom, 1000)}
vi1, fp = vi1.TakeFeePoolRewards(NewWithdrawContext(
fp, height, totalBondedTokens, validatorTokens1, commissionRate1))
require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum))
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.DelPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.ValCommission[0].Amount))
vi2, fp = vi2.TakeFeePoolRewards(NewWithdrawContext(
fp, height, totalBondedTokens, validatorTokens2, commissionRate2))
require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum))
assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(400-12), vi2.DelPool[0].Amount))
assert.True(sdk.DecEq(t, vi2.ValCommission[0].Amount, sdk.NewDec(12)))
// add more blocks and inflation
height = 20
fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000))
vi3, fp = vi3.TakeFeePoolRewards(NewWithdrawContext(
fp, height, totalBondedTokens, validatorTokens3, commissionRate3))
require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum))
assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(1000-40), vi3.DelPool[0].Amount))
assert.True(sdk.DecEq(t, vi3.ValCommission[0].Amount, sdk.NewDec(40)))
}
func TestWithdrawCommission(t *testing.T) {
// initialize
height := int64(0)
fp := InitialFeePool()
vi := NewValidatorDistInfo(valAddr1, height)
commissionRate := sdk.NewDecWithPrec(2, 2)
validatorTokens := sdk.NewDec(10)
totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power
// simulate adding some stake for inflation
height = 10
fp.ValPool = DecCoins{NewDecCoin(stakingTypes.DefaultBondDenom, 1000)}
// for a more fun staring condition, have an non-withdraw update
vi, fp = vi.TakeFeePoolRewards(NewWithdrawContext(
fp, height, totalBondedTokens, validatorTokens, commissionRate))
require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum))
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi.DelPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.ValCommission[0].Amount))
// add more blocks and inflation
height = 20
fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000))
vi, fp, commissionRecv := vi.WithdrawCommission(NewWithdrawContext(
fp, height, totalBondedTokens, validatorTokens, commissionRate))
require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum))
assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount))
assert.True(sdk.DecEq(t, sdk.NewDec(200-4), vi.DelPool[0].Amount))
assert.Zero(t, len(vi.ValCommission))
assert.True(sdk.DecEq(t, sdk.NewDec(4), commissionRecv[0].Amount))
}

View File

@ -71,3 +71,5 @@ func (h Hooks) BeforeValidatorModified(_ sdk.Context, _ sdk.ValAddress)
func (h Hooks) BeforeDelegationCreated(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {}
func (h Hooks) BeforeDelegationSharesModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {}
func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {}
func (h Hooks) AfterDelegationModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {}
func (h Hooks) BeforeValidatorSlashed(_ sdk.Context, _ sdk.ValAddress, _ sdk.Dec) {}

View File

@ -45,6 +45,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [
for _, delegation := range data.Bonds {
keeper.BeforeDelegationCreated(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr)
keeper.SetDelegation(ctx, delegation)
keeper.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr)
}
for _, ubd := range data.UnbondingDelegations {

View File

@ -77,6 +77,7 @@ func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) {
store := ctx.KVStore(k.storeKey)
b := types.MustMarshalDelegation(k.cdc, delegation)
store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr), b)
k.AfterDelegationModified(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr)
}
// remove a delegation from store

View File

@ -59,3 +59,15 @@ func (k Keeper) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress,
k.hooks.BeforeDelegationRemoved(ctx, delAddr, valAddr)
}
}
func (k Keeper) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
if k.hooks != nil {
k.hooks.AfterDelegationModified(ctx, delAddr, valAddr)
}
}
func (k Keeper) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {
if k.hooks != nil {
k.hooks.BeforeValidatorSlashed(ctx, valAddr, fraction)
}
}

View File

@ -55,6 +55,11 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh
operatorAddress := validator.GetOperator()
k.BeforeValidatorModified(ctx, operatorAddress)
// we need to calculate the *effective* slash fraction for distribution
if validator.Tokens.GT(sdk.ZeroInt()) {
k.BeforeValidatorSlashed(ctx, operatorAddress, slashAmountDec.Quo(sdk.NewDecFromInt(validator.Tokens)))
}
// Track remaining slash amount for the validator
// This will decrease when we slash unbondings and
// redelegations, as that stake has since unbonded

View File

@ -82,17 +82,8 @@ func SupplyInvariants(ck bank.Keeper, k staking.Keeper,
// add community pool
loose = loose.Add(feePool.CommunityPool.AmountOf(stakingTypes.DefaultBondDenom))
// add validator distribution pool
loose = loose.Add(feePool.ValPool.AmountOf(stakingTypes.DefaultBondDenom))
// 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.DelPool.AmountOf(stakingTypes.DefaultBondDenom))
loose = loose.Add(distInfo.ValCommission.AmountOf(stakingTypes.DefaultBondDenom))
return false
},
)
// add yet-to-be-withdrawn
loose = loose.Add(d.GetOutstandingRewards(ctx).AmountOf(stakingTypes.DefaultBondDenom))
// Loose tokens should equal coin supply plus unbonding delegations
// plus tokens on unbonded validators

View File

@ -67,6 +67,7 @@ var (
KeyBondDenom = types.KeyBondDenom
DefaultParams = types.DefaultParams
DefaultBondDenom = types.DefaultBondDenom
InitialPool = types.InitialPool
NewValidator = types.NewValidator
NewDescription = types.NewDescription

View File

@ -405,14 +405,15 @@ func (v Validator) BondedTokens() sdk.Int {
var _ sdk.Validator = Validator{}
// nolint - for sdk.Validator
func (v Validator) GetJailed() bool { return v.Jailed }
func (v Validator) GetMoniker() string { return v.Description.Moniker }
func (v Validator) GetStatus() sdk.BondStatus { return v.Status }
func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr }
func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey }
func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) }
func (v Validator) GetPower() sdk.Int { return v.BondedTokens() }
func (v Validator) GetTokens() sdk.Int { return v.Tokens }
func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate }
func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares }
func (v Validator) GetBondHeight() int64 { return v.BondHeight }
func (v Validator) GetJailed() bool { return v.Jailed }
func (v Validator) GetMoniker() string { return v.Description.Moniker }
func (v Validator) GetStatus() sdk.BondStatus { return v.Status }
func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr }
func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey }
func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) }
func (v Validator) GetPower() sdk.Int { return v.BondedTokens() }
func (v Validator) GetTokens() sdk.Int { return v.Tokens }
func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate }
func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares }
func (v Validator) GetBondHeight() int64 { return v.BondHeight }
func (v Validator) GetDelegatorShareExRate() sdk.Dec { return v.DelegatorShareExRate() }