Merge PR #3750: Outstanding per-validator rewards; correctly handle same-BeginBlock redelegation-double-slash

This commit is contained in:
Christopher Goes 2019-03-06 19:54:12 +01:00 committed by GitHub
parent bf7cbbbdf8
commit 4c50380181
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 526 additions and 256 deletions

View File

@ -30,8 +30,17 @@ exactly 9 atoms and transfer 1 atom, and `MsgSend` is disabled.
### SDK ### SDK
* \#3750 Track outstanding rewards per-validator instead of globally,
and fix the main simulation issue, which was that slashes of
re-delegations to a validator were not correctly accounted for
in fee distribution when the redelegation in question had itself
been slashed (from a fault committed by a different validator)
in the same BeginBlock. Outstanding rewards are now available
on a per-validator basis in REST.
* [\#3669] Ensure consistency in message naming, codec registration, and JSON * [\#3669] Ensure consistency in message naming, codec registration, and JSON
tags. tags.
* #3788 Change order of operations for greater accuracy when calculating delegation share token value
* #3788 DecCoins.Cap -> DecCoins.Intersect
* [\#3666] Improve coins denom validation. * [\#3666] Improve coins denom validation.
* [\#3751] Disable (temporarily) support for ED25519 account key pairs. * [\#3751] Disable (temporarily) support for ED25519 account key pairs.

View File

@ -553,7 +553,7 @@ func TestBonding(t *testing.T) {
// hence we utilize the exchange rate in the following test // hence we utilize the exchange rate in the following test
validator2 := getValidator(t, port, operAddrs[1]) validator2 := getValidator(t, port, operAddrs[1])
delTokensAfterRedelegation := delegatorDels[0].GetShares().Mul(validator2.DelegatorShareExRate()) delTokensAfterRedelegation := validator2.ShareTokens(delegatorDels[0].GetShares())
require.Equal(t, rdTokens.ToDec(), delTokensAfterRedelegation) require.Equal(t, rdTokens.ToDec(), delTokensAfterRedelegation)
redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1]) redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1])
@ -945,7 +945,7 @@ func TestDistributionFlow(t *testing.T) {
operAddr := sdk.AccAddress(valAddr) operAddr := sdk.AccAddress(valAddr)
var rewards sdk.DecCoins var rewards sdk.DecCoins
res, body := Request(t, port, "GET", fmt.Sprintf("/distribution/outstanding_rewards"), nil) res, body := Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/outstanding_rewards", valAddr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards))
@ -967,7 +967,7 @@ func TestDistributionFlow(t *testing.T) {
require.Equal(t, uint32(0), resultTx.Code) require.Equal(t, uint32(0), resultTx.Code)
// Query outstanding rewards changed // Query outstanding rewards changed
res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/outstanding_rewards"), nil) res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/outstanding_rewards", valAddr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards))

View File

@ -1558,6 +1558,28 @@ paths:
description: Invalid validator address description: Invalid validator address
500: 500:
description: Internal Server Error description: Internal Server Error
/distribution/validators/{validatorAddr}/outstanding_rewards:
parameters:
- in: path
name: validatorAddr
description: Bech32 OperatorAddress of validator
required: true
type: string
get:
summary: Fee distribution outstanding rewards of a single validator
tags:
- ICS24
produces:
- application/json
responses:
200:
description: OK
schema:
type: array
items:
$ref: "#/definitions/Coin"
500:
description: Internal Server Error
/distribution/validators/{validatorAddr}/rewards: /distribution/validators/{validatorAddr}/rewards:
parameters: parameters:
- in: path - in: path
@ -1566,8 +1588,8 @@ paths:
required: true required: true
type: string type: string
get: get:
summary: Commission and self-delegation rewards of a single a validator summary: Commission and self-delegation rewards of a single validator
description: Query the commission and self-delegation rewards of a validator. description: Query the commission and self-delegation rewards of validator.
tags: tags:
- ICS24 - ICS24
produces: produces:
@ -1630,22 +1652,6 @@ paths:
type: string type: string
500: 500:
description: Internal Server Error description: Internal Server Error
/distribution/outstanding_rewards:
get:
summary: Fee distribution outstanding rewards
tags:
- ICS24
produces:
- application/json
responses:
200:
description: OK
schema:
type: array
items:
$ref: "#/definitions/Coin"
500:
description: Internal Server Error
definitions: definitions:
CheckTxResult: CheckTxResult:
type: object type: object

View File

@ -104,6 +104,13 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []st
// reinitialize all validators // reinitialize all validators
app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator())
feePool := app.distrKeeper.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Add(scraps)
app.distrKeeper.SetFeePool(ctx, feePool)
app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator())
return false return false
}) })

View File

@ -278,9 +278,12 @@ func (coins DecCoins) SafeSub(coinsB DecCoins) (DecCoins, bool) {
return diff, diff.IsAnyNegative() return diff, diff.IsAnyNegative()
} }
// Trims any denom amount from coin which exceeds that of coinB, // Intersect will return a new set of coins which contains the minimum DecCoin
// such that (coin.Cap(coinB)).IsLTE(coinB). // for common denoms found in both `coins` and `coinsB`. For denoms not common
func (coins DecCoins) Cap(coinsB DecCoins) DecCoins { // to both `coins` and `coinsB` the minimum is considered to be 0, thus they
// are not added to the final set.In other words, trim any denom amount from
// coin which exceeds that of coinB, such that (coin.Intersect(coinB)).IsLTE(coinB).
func (coins DecCoins) Intersect(coinsB DecCoins) DecCoins {
res := make([]DecCoin, len(coins)) res := make([]DecCoin, len(coins))
for i, coin := range coins { for i, coin := range coins {
minCoin := DecCoin{ minCoin := DecCoin{

View File

@ -225,7 +225,7 @@ func TestDecCoinsString(t *testing.T) {
} }
} }
func TestDecCoinsCap(t *testing.T) { func TestDecCoinsIntersect(t *testing.T) {
testCases := []struct { testCases := []struct {
input1 string input1 string
input2 string input2 string
@ -252,7 +252,7 @@ func TestDecCoinsCap(t *testing.T) {
exr, err := ParseDecCoins(tc.expectedResult) exr, err := ParseDecCoins(tc.expectedResult)
require.NoError(t, err, "unexpected parse error in %v", i) require.NoError(t, err, "unexpected parse error in %v", i)
require.True(t, in1.Cap(in2).IsEqual(exr), "in1.cap(in2) != exr in %v", i) require.True(t, in1.Intersect(in2).IsEqual(exr), "in1.cap(in2) != exr in %v", i)
// require.Equal(t, tc.expectedResult, in1.Cap(in2).String(), "in1.cap(in2) != exr in %v", i) // require.Equal(t, tc.expectedResult, in1.Intersect(in2).String(), "in1.cap(in2) != exr in %v", i)
} }
} }

View File

@ -291,6 +291,21 @@ func (d Dec) QuoTruncate(d2 Dec) Dec {
return Dec{chopped} return Dec{chopped}
} }
// quotient, round up
func (d Dec) QuoRoundUp(d2 Dec) Dec {
// multiply precision twice
mul := new(big.Int).Mul(d.Int, precisionReuse)
mul.Mul(mul, precisionReuse)
quo := new(big.Int).Quo(mul, d2.Int)
chopped := chopPrecisionAndRoundUp(quo)
if chopped.BitLen() > 255+DecimalPrecisionBits {
panic("Int overflow")
}
return Dec{chopped}
}
// quotient // quotient
func (d Dec) QuoInt(i Int) Dec { func (d Dec) QuoInt(i Int) Dec {
mul := new(big.Int).Quo(d.Int, i.i) mul := new(big.Int).Quo(d.Int, i.i)
@ -412,6 +427,29 @@ func chopPrecisionAndRound(d *big.Int) *big.Int {
} }
} }
func chopPrecisionAndRoundUp(d *big.Int) *big.Int {
// remove the negative and add it back when returning
if d.Sign() == -1 {
// make d positive, compute chopped value, and then un-mutate d
d = d.Neg(d)
// truncate since d is negative...
d = chopPrecisionAndTruncate(d)
d = d.Neg(d)
return d
}
// get the truncated quotient and remainder
quo, rem := d, big.NewInt(0)
quo, rem = quo.QuoRem(d, precisionReuse, rem)
if rem.Sign() == 0 { // remainder is zero
return quo
}
return quo.Add(quo, oneInt)
}
func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int { func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int {
tmp := new(big.Int).Set(d) tmp := new(big.Int).Set(d)
return chopPrecisionAndRound(tmp) return chopPrecisionAndRound(tmp)

View File

@ -156,44 +156,61 @@ func TestDecsEqual(t *testing.T) {
func TestArithmetic(t *testing.T) { func TestArithmetic(t *testing.T) {
tests := []struct { tests := []struct {
d1, d2 Dec d1, d2 Dec
expMul, expQuo, expAdd, expSub Dec expMul, expMulTruncate Dec
expQuo, expQuoRoundUp, expQuoTruncate Dec
expAdd, expSub Dec
}{ }{
// d1 d2 MUL DIV ADD SUB // d1 d2 MUL MulTruncate QUO QUORoundUp QUOTrunctate ADD SUB
{NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0)}, {NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0)},
{NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(1)}, {NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(1)},
{NewDec(0), NewDec(1), NewDec(0), NewDec(0), NewDec(1), NewDec(-1)}, {NewDec(0), NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(-1)},
{NewDec(0), NewDec(-1), NewDec(0), NewDec(0), NewDec(-1), NewDec(1)}, {NewDec(0), NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(1)},
{NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(-1)}, {NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(-1)},
{NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(2), NewDec(0)}, {NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(2), NewDec(0)},
{NewDec(-1), NewDec(-1), NewDec(1), NewDec(1), NewDec(-2), NewDec(0)}, {NewDec(-1), NewDec(-1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(-2), NewDec(0)},
{NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(2)}, {NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(2)},
{NewDec(-1), NewDec(1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(-2)}, {NewDec(-1), NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(-2)},
{NewDec(3), NewDec(7), NewDec(21), NewDecWithPrec(428571428571428571, 18), NewDec(10), NewDec(-4)}, {NewDec(3), NewDec(7), NewDec(21), NewDec(21),
{NewDec(2), NewDec(4), NewDec(8), NewDecWithPrec(5, 1), NewDec(6), NewDec(-2)}, NewDecWithPrec(428571428571428571, 18), NewDecWithPrec(428571428571428572, 18), NewDecWithPrec(428571428571428571, 18),
{NewDec(100), NewDec(100), NewDec(10000), NewDec(1), NewDec(200), NewDec(0)}, NewDec(10), NewDec(-4)},
{NewDec(2), NewDec(4), NewDec(8), NewDec(8), NewDecWithPrec(5, 1), NewDecWithPrec(5, 1), NewDecWithPrec(5, 1),
NewDec(6), NewDec(-2)},
{NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2), {NewDec(100), NewDec(100), NewDec(10000), NewDec(10000), NewDec(1), NewDec(1), NewDec(1), NewDec(200), NewDec(0)},
NewDec(1), NewDec(3), NewDec(0)},
{NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), {NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2), NewDecWithPrec(225, 2),
MustNewDecFromStr("10.009009009009009009"), NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)}, NewDec(1), NewDec(1), NewDec(1), NewDec(3), NewDec(0)},
{NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), NewDecWithPrec(1109889, 8),
MustNewDecFromStr("10.009009009009009009"), MustNewDecFromStr("10.009009009009009010"), MustNewDecFromStr("10.009009009009009009"),
NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)},
} }
for tcIndex, tc := range tests { for tcIndex, tc := range tests {
resAdd := tc.d1.Add(tc.d2) resAdd := tc.d1.Add(tc.d2)
resSub := tc.d1.Sub(tc.d2) resSub := tc.d1.Sub(tc.d2)
resMul := tc.d1.Mul(tc.d2) resMul := tc.d1.Mul(tc.d2)
resMulTruncate := tc.d1.MulTruncate(tc.d2)
require.True(t, tc.expAdd.Equal(resAdd), "exp %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex) require.True(t, tc.expAdd.Equal(resAdd), "exp %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex)
require.True(t, tc.expSub.Equal(resSub), "exp %v, res %v, tc %d", tc.expSub, resSub, tcIndex) require.True(t, tc.expSub.Equal(resSub), "exp %v, res %v, tc %d", tc.expSub, resSub, tcIndex)
require.True(t, tc.expMul.Equal(resMul), "exp %v, res %v, tc %d", tc.expMul, resMul, tcIndex) require.True(t, tc.expMul.Equal(resMul), "exp %v, res %v, tc %d", tc.expMul, resMul, tcIndex)
require.True(t, tc.expMulTruncate.Equal(resMulTruncate), "exp %v, res %v, tc %d", tc.expMulTruncate, resMulTruncate, tcIndex)
if tc.d2.IsZero() { // panic for divide by zero if tc.d2.IsZero() { // panic for divide by zero
require.Panics(t, func() { tc.d1.Quo(tc.d2) }) require.Panics(t, func() { tc.d1.Quo(tc.d2) })
} else { } else {
resQuo := tc.d1.Quo(tc.d2) resQuo := tc.d1.Quo(tc.d2)
require.True(t, tc.expQuo.Equal(resQuo), "exp %v, res %v, tc %d", tc.expQuo.String(), resQuo.String(), tcIndex) require.True(t, tc.expQuo.Equal(resQuo), "exp %v, res %v, tc %d", tc.expQuo.String(), resQuo.String(), tcIndex)
resQuoRoundUp := tc.d1.QuoRoundUp(tc.d2)
require.True(t, tc.expQuoRoundUp.Equal(resQuoRoundUp), "exp %v, res %v, tc %d",
tc.expQuoRoundUp.String(), resQuoRoundUp.String(), tcIndex)
resQuoTruncate := tc.d1.QuoTruncate(tc.d2)
require.True(t, tc.expQuoTruncate.Equal(resQuoTruncate), "exp %v, res %v, tc %d",
tc.expQuoTruncate.String(), resQuoTruncate.String(), tcIndex)
} }
} }
} }

View File

@ -73,7 +73,8 @@ type Validator interface {
GetCommission() Dec // validator commission rate GetCommission() Dec // validator commission rate
GetMinSelfDelegation() Int // validator minimum self delegation GetMinSelfDelegation() Int // validator minimum self delegation
GetDelegatorShares() Dec // total outstanding delegator shares GetDelegatorShares() Dec // total outstanding delegator shares
GetDelegatorShareExRate() Dec // tokens per delegator share exchange rate ShareTokens(Dec) Dec // token worth of provided delegator shares
ShareTokensTruncated(Dec) Dec // token worth of provided delegator shares, truncated
} }
// validator which fulfills abci validator interface for use in Tendermint // validator which fulfills abci validator interface for use in Tendermint

View File

@ -50,14 +50,15 @@ var (
NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward
NewMsgWithdrawValidatorCommission = types.NewMsgWithdrawValidatorCommission NewMsgWithdrawValidatorCommission = types.NewMsgWithdrawValidatorCommission
NewKeeper = keeper.NewKeeper NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier NewQuerier = keeper.NewQuerier
NewQueryValidatorCommissionParams = keeper.NewQueryValidatorCommissionParams NewQueryValidatorOutstandingRewardsParams = keeper.NewQueryValidatorOutstandingRewardsParams
NewQueryValidatorSlashesParams = keeper.NewQueryValidatorSlashesParams NewQueryValidatorCommissionParams = keeper.NewQueryValidatorCommissionParams
NewQueryDelegationRewardsParams = keeper.NewQueryDelegationRewardsParams NewQueryValidatorSlashesParams = keeper.NewQueryValidatorSlashesParams
NewQueryDelegatorParams = keeper.NewQueryDelegatorParams NewQueryDelegationRewardsParams = keeper.NewQueryDelegationRewardsParams
NewQueryDelegatorWithdrawAddrParams = keeper.NewQueryDelegatorWithdrawAddrParams NewQueryDelegatorParams = keeper.NewQueryDelegatorParams
DefaultParamspace = keeper.DefaultParamspace NewQueryDelegatorWithdrawAddrParams = keeper.NewQueryDelegatorWithdrawAddrParams
DefaultParamspace = keeper.DefaultParamspace
RegisterCodec = types.RegisterCodec RegisterCodec = types.RegisterCodec
DefaultGenesisState = types.DefaultGenesisState DefaultGenesisState = types.DefaultGenesisState

View File

@ -32,22 +32,22 @@ func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command {
} }
} }
// GetCmdQueryOutstandingRewards implements the query outstanding rewards command. // GetCmdQueryValidatorOutstandingRewards implements the query validator outstanding rewards command.
func GetCmdQueryOutstandingRewards(queryRoute string, cdc *codec.Codec) *cobra.Command { func GetCmdQueryValidatorOutstandingRewards(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "outstanding-rewards", Use: "validator-outstanding-rewards",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Short: "Query distribution outstanding (un-withdrawn) rewards", Short: "Query distribution outstanding (un-withdrawn) rewards for a validator and all their delegations",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
route := fmt.Sprintf("custom/%s/outstanding_rewards", queryRoute) route := fmt.Sprintf("custom/%s/validator_outstanding_rewards", queryRoute)
res, err := cliCtx.QueryWithData(route, []byte{}) res, err := cliCtx.QueryWithData(route, []byte{})
if err != nil { if err != nil {
return err return err
} }
var outstandingRewards types.OutstandingRewards var outstandingRewards types.ValidatorOutstandingRewards
cdc.MustUnmarshalJSON(res, &outstandingRewards) cdc.MustUnmarshalJSON(res, &outstandingRewards)
return cliCtx.PrintOutput(outstandingRewards) return cliCtx.PrintOutput(outstandingRewards)
}, },

View File

@ -27,7 +27,7 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command {
distQueryCmd.AddCommand(client.GetCommands( distQueryCmd.AddCommand(client.GetCommands(
distCmds.GetCmdQueryParams(mc.storeKey, mc.cdc), distCmds.GetCmdQueryParams(mc.storeKey, mc.cdc),
distCmds.GetCmdQueryOutstandingRewards(mc.storeKey, mc.cdc), distCmds.GetCmdQueryValidatorOutstandingRewards(mc.storeKey, mc.cdc),
distCmds.GetCmdQueryValidatorCommission(mc.storeKey, mc.cdc), distCmds.GetCmdQueryValidatorCommission(mc.storeKey, mc.cdc),
distCmds.GetCmdQueryValidatorSlashes(mc.storeKey, mc.cdc), distCmds.GetCmdQueryValidatorSlashes(mc.storeKey, mc.cdc),
distCmds.GetCmdQueryDelegatorRewards(mc.storeKey, mc.cdc), distCmds.GetCmdQueryDelegatorRewards(mc.storeKey, mc.cdc),

View File

@ -49,17 +49,18 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router,
validatorRewardsHandlerFn(cliCtx, cdc, queryRoute), validatorRewardsHandlerFn(cliCtx, cdc, queryRoute),
).Methods("GET") ).Methods("GET")
// Outstanding rewards of a single validator
r.HandleFunc(
"/distribution/validators/{validatorAddr}/outstanding_rewards",
outstandingRewardsHandlerFn(cliCtx, cdc, queryRoute),
).Methods("GET")
// Get the current distribution parameter values // Get the current distribution parameter values
r.HandleFunc( r.HandleFunc(
"/distribution/parameters", "/distribution/parameters",
paramsHandlerFn(cliCtx, cdc, queryRoute), paramsHandlerFn(cliCtx, cdc, queryRoute),
).Methods("GET") ).Methods("GET")
// Get the current distribution pool
r.HandleFunc(
"/distribution/outstanding_rewards",
outstandingRewardsHandlerFn(cliCtx, cdc, queryRoute),
).Methods("GET")
} }
// HTTP request handler to query the total rewards balance from all delegations // HTTP request handler to query the total rewards balance from all delegations
@ -211,7 +212,13 @@ func outstandingRewardsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec,
queryRoute string) http.HandlerFunc { queryRoute string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/outstanding_rewards", queryRoute), []byte{}) validatorAddr, ok := checkValidatorAddressVar(w, r)
if !ok {
return
}
bin := cdc.MustMarshalJSON(distribution.NewQueryValidatorOutstandingRewardsParams(validatorAddr))
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/validator_outstanding_rewards", queryRoute), bin)
if err != nil { if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return

View File

@ -16,7 +16,9 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) {
keeper.SetDelegatorWithdrawAddr(ctx, dwi.DelegatorAddress, dwi.WithdrawAddress) keeper.SetDelegatorWithdrawAddr(ctx, dwi.DelegatorAddress, dwi.WithdrawAddress)
} }
keeper.SetPreviousProposerConsAddr(ctx, data.PreviousProposer) keeper.SetPreviousProposerConsAddr(ctx, data.PreviousProposer)
keeper.SetOutstandingRewards(ctx, data.OutstandingRewards) for _, rew := range data.OutstandingRewards {
keeper.SetValidatorOutstandingRewards(ctx, rew.ValidatorAddress, rew.OutstandingRewards)
}
for _, acc := range data.ValidatorAccumulatedCommissions { for _, acc := range data.ValidatorAccumulatedCommissions {
keeper.SetValidatorAccumulatedCommission(ctx, acc.ValidatorAddress, acc.Accumulated) keeper.SetValidatorAccumulatedCommission(ctx, acc.ValidatorAddress, acc.Accumulated)
} }
@ -50,7 +52,16 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
return false return false
}) })
pp := keeper.GetPreviousProposerConsAddr(ctx) pp := keeper.GetPreviousProposerConsAddr(ctx)
outstanding := keeper.GetOutstandingRewards(ctx) outstanding := make([]types.ValidatorOutstandingRewardsRecord, 0)
keeper.IterateValidatorOutstandingRewards(ctx,
func(addr sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) {
outstanding = append(outstanding, types.ValidatorOutstandingRewardsRecord{
ValidatorAddress: addr,
OutstandingRewards: rewards,
})
return false
},
)
acc := make([]types.ValidatorAccumulatedCommissionRecord, 0) acc := make([]types.ValidatorAccumulatedCommissionRecord, 0)
keeper.IterateValidatorAccumulatedCommissions(ctx, keeper.IterateValidatorAccumulatedCommissions(ctx,
func(addr sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool) { func(addr sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool) {

View File

@ -5,8 +5,8 @@ import (
) )
// get outstanding rewards // get outstanding rewards
func (k Keeper) GetOutstandingRewardsCoins(ctx sdk.Context) sdk.DecCoins { func (k Keeper) GetValidatorOutstandingRewardsCoins(ctx sdk.Context, val sdk.ValAddress) sdk.DecCoins {
return k.GetOutstandingRewards(ctx) return k.GetValidatorOutstandingRewards(ctx, val)
} }
// get the community coins // get the community coins

View File

@ -42,14 +42,17 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in
proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, proposer) proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, proposer)
if proposerValidator != nil { if proposerValidator != nil {
k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward) k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward)
remaining = feesCollected.Sub(proposerReward) remaining = remaining.Sub(proposerReward)
} else { } else {
// proposer can be unknown if say, the unbonding period is 1 block, so // proposer can be unknown if say, the unbonding period is 1 block, so
// e.g. a validator undelegates at block X, it's removed entirely by // e.g. a validator undelegates at block X, it's removed entirely by
// block X+1's endblock, then X+2 we need to refer to the previous // block X+1's endblock, then X+2 we need to refer to the previous
// proposer for X+1, but we've forgotten about them. // proposer for X+1, but we've forgotten about them.
logger.Error(fmt.Sprintf( logger.Error(fmt.Sprintf(
"WARNING: Attempt to allocate proposer rewards to unknown proposer %s. This should happen only if the proposer unbonded completely within a single block, which generally should not happen except in exceptional circumstances (or fuzz testing). We recommend you investigate immediately.", "WARNING: Attempt to allocate proposer rewards to unknown proposer %s. "+
"This should happen only if the proposer unbonded completely within a single block, "+
"which generally should not happen except in exceptional circumstances (or fuzz testing). "+
"We recommend you investigate immediately.",
proposer.String())) proposer.String()))
} }
@ -66,7 +69,7 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in
// ref https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701 // ref https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701
powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPower)) powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPower))
reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction) reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction)
reward = reward.Cap(remaining) reward = reward.Intersect(remaining)
k.AllocateTokensToValidator(ctx, validator, reward) k.AllocateTokensToValidator(ctx, validator, reward)
remaining = remaining.Sub(reward) remaining = remaining.Sub(reward)
} }
@ -75,15 +78,11 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPrecommitPower, totalPower in
feePool.CommunityPool = feePool.CommunityPool.Add(remaining) feePool.CommunityPool = feePool.CommunityPool.Add(remaining)
k.SetFeePool(ctx, feePool) k.SetFeePool(ctx, feePool)
// update outstanding rewards
outstanding := k.GetOutstandingRewards(ctx)
outstanding = outstanding.Add(feesCollected.Sub(remaining))
k.SetOutstandingRewards(ctx, outstanding)
} }
// allocate tokens to a particular validator, splitting according to commission // allocate tokens to a particular validator, splitting according to commission
func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val sdk.Validator, tokens sdk.DecCoins) { func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val sdk.Validator, tokens sdk.DecCoins) {
// split tokens between validator and delegators according to commission // split tokens between validator and delegators according to commission
commission := tokens.MulDec(val.GetCommission()) commission := tokens.MulDec(val.GetCommission())
shared := tokens.Sub(commission) shared := tokens.Sub(commission)
@ -97,4 +96,9 @@ func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val sdk.Validator, to
currentRewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator()) currentRewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator())
currentRewards.Rewards = currentRewards.Rewards.Add(shared) currentRewards.Rewards = currentRewards.Rewards.Add(shared)
k.SetValidatorCurrentRewards(ctx, val.GetOperator(), currentRewards) k.SetValidatorCurrentRewards(ctx, val.GetOperator(), currentRewards)
// update outstanding rewards
outstanding := k.GetValidatorOutstandingRewards(ctx, val.GetOperator())
outstanding = outstanding.Add(tokens)
k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), outstanding)
} }

View File

@ -14,9 +14,6 @@ func TestAllocateTokensToValidatorWithCommission(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
@ -44,9 +41,6 @@ func TestAllocateTokensToManyValidators(t *testing.T) {
ctx, _, k, sk, fck := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, fck := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
@ -69,7 +63,8 @@ func TestAllocateTokensToManyValidators(t *testing.T) {
} }
// assert initial state: zero outstanding rewards, zero community pool, zero commission, zero current rewards // 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.GetValidatorOutstandingRewards(ctx, valOpAddr1).IsZero())
require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr2).IsZero())
require.True(t, k.GetFeePool(ctx).CommunityPool.IsZero()) require.True(t, k.GetFeePool(ctx).CommunityPool.IsZero())
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero())
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero()) require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero())
@ -94,7 +89,8 @@ func TestAllocateTokensToManyValidators(t *testing.T) {
k.AllocateTokens(ctx, 200, 200, valConsAddr2, votes) k.AllocateTokens(ctx, 200, 200, valConsAddr2, votes)
// 98 outstanding rewards (100 less 2 to community pool) // 98 outstanding rewards (100 less 2 to community pool)
require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(98)}}, k.GetOutstandingRewards(ctx)) require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecWithPrec(465, 1)}}, k.GetValidatorOutstandingRewards(ctx, valOpAddr1))
require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDecWithPrec(515, 1)}}, k.GetValidatorOutstandingRewards(ctx, valOpAddr2))
// 2 community pool coins // 2 community pool coins
require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(2)}}, k.GetFeePool(ctx).CommunityPool) require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(2)}}, k.GetFeePool(ctx).CommunityPool)
// 50% commission for first proposer, (0.5 * 93%) * 100 / 2 = 23.25 // 50% commission for first proposer, (0.5 * 93%) * 100 / 2 = 23.25
@ -112,9 +108,6 @@ func TestAllocateTokensTruncation(t *testing.T) {
ctx, _, k, sk, fck := CreateTestInputAdvanced(t, false, 1000000, communityTax) ctx, _, k, sk, fck := CreateTestInputAdvanced(t, false, 1000000, communityTax)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 10% commission // create validator with 10% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(1, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(1, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
@ -147,7 +140,9 @@ func TestAllocateTokensTruncation(t *testing.T) {
} }
// assert initial state: zero outstanding rewards, zero community pool, zero commission, zero current rewards // 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.GetValidatorOutstandingRewards(ctx, valOpAddr1).IsZero())
require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr2).IsZero())
require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr3).IsZero())
require.True(t, k.GetFeePool(ctx).CommunityPool.IsZero()) require.True(t, k.GetFeePool(ctx).CommunityPool.IsZero())
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero())
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero()) require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr2).IsZero())
@ -175,5 +170,7 @@ func TestAllocateTokensTruncation(t *testing.T) {
} }
k.AllocateTokens(ctx, 31, 31, valConsAddr2, votes) k.AllocateTokens(ctx, 31, 31, valConsAddr2, votes)
require.True(t, k.GetOutstandingRewards(ctx).IsValid()) require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr1).IsValid())
require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr2).IsValid())
require.True(t, k.GetValidatorOutstandingRewards(ctx, valOpAddr3).IsValid())
} }

View File

@ -1,6 +1,8 @@
package keeper package keeper
import ( import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/distribution/types"
@ -20,7 +22,7 @@ func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sd
// calculate delegation stake in tokens // calculate delegation stake in tokens
// we don't store directly, so multiply delegation shares * (tokens per share) // we don't store directly, so multiply delegation shares * (tokens per share)
// note: necessary to truncate so we don't allow withdrawing more rewards than owed // note: necessary to truncate so we don't allow withdrawing more rewards than owed
stake := delegation.GetShares().MulTruncate(validator.GetDelegatorShareExRate()) stake := validator.ShareTokensTruncated(delegation.GetShares())
k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight()))) k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight())))
} }
@ -53,32 +55,51 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Valid
func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation, endingPeriod uint64) (rewards sdk.DecCoins) { func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation, endingPeriod uint64) (rewards sdk.DecCoins) {
// fetch starting info for delegation // fetch starting info for delegation
startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())
if startingInfo.Height == uint64(ctx.BlockHeight()) {
// started this height, no rewards yet
return
}
startingPeriod := startingInfo.PreviousPeriod startingPeriod := startingInfo.PreviousPeriod
stake := startingInfo.Stake stake := startingInfo.Stake
// iterate through slashes and withdraw with calculated staking for sub-intervals // 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... // 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 // slashes which happened in the first block would have been before this delegation existed,
startingHeight := startingInfo.Height + 1 // UNLESS they were slashes of a redelegation to this validator which was itself slashed
// ... or slashes which happened in *this* block, since they would have happened after reward allocation // (from a fault committed by the redelegation source validator) earlier in the same BeginBlock
endingHeight := uint64(ctx.BlockHeight()) - 1 startingHeight := startingInfo.Height
if endingHeight >= startingHeight { // slashes this block happened after reward allocation, but we have to account for them for the stake sanity check below
endingHeight := uint64(ctx.BlockHeight())
if endingHeight > startingHeight {
k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight, k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight,
func(height uint64, event types.ValidatorSlashEvent) (stop bool) { func(height uint64, event types.ValidatorSlashEvent) (stop bool) {
endingPeriod := event.ValidatorPeriod endingPeriod := event.ValidatorPeriod
rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) if endingPeriod > startingPeriod {
// note: necessary to truncate so we don't allow withdrawing more rewards than owed rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake))
stake = stake.MulTruncate(sdk.OneDec().Sub(event.Fraction)) // note: necessary to truncate so we don't allow withdrawing more rewards than owed
startingPeriod = endingPeriod stake = stake.MulTruncate(sdk.OneDec().Sub(event.Fraction))
startingPeriod = endingPeriod
}
return false return false
}, },
) )
} }
// a stake sanity check - recalculated final stake should be less than or equal to current stake
// here we cannot use Equals because stake is truncated when multiplied by slash fractions
// we could only use equals if we had arbitrary-precision rationals
currentStake := val.ShareTokens(del.GetShares())
if stake.GT(currentStake) {
panic(fmt.Sprintf("calculated final stake for delegator %s greater than current stake: %s, %s",
del.GetDelegatorAddr(), stake, currentStake))
}
// calculate rewards for final period // calculate rewards for final period
rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) rewards = rewards.Add(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake))
return return rewards
} }
func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) sdk.Error { func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) sdk.Error {
@ -90,7 +111,18 @@ func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, de
// end current period and calculate rewards // end current period and calculate rewards
endingPeriod := k.incrementValidatorPeriod(ctx, val) endingPeriod := k.incrementValidatorPeriod(ctx, val)
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) rewardsRaw := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
outstanding := k.GetValidatorOutstandingRewards(ctx, del.GetValidatorAddr())
// defensive edge case may happen on the very final digits
// of the decCoins due to operation order of the distribution mechanism.
rewards := rewardsRaw.Intersect(outstanding)
if !rewards.IsEqual(rewardsRaw) {
logger := ctx.Logger().With("module", "x/distr")
logger.Info(fmt.Sprintf("missing rewards rounding error, delegator %v"+
"withdrawing rewards from validator %v, should have received %v, got %v",
val.GetOperator(), del.GetDelegatorAddr(), rewardsRaw, rewards))
}
// decrement reference count of starting period // decrement reference count of starting period
startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())
@ -99,9 +131,8 @@ func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, de
// truncate coins, return remainder to community pool // truncate coins, return remainder to community pool
coins, remainder := rewards.TruncateDecimal() coins, remainder := rewards.TruncateDecimal()
outstanding := k.GetOutstandingRewards(ctx)
k.SetOutstandingRewards(ctx, outstanding.Sub(rewards)) k.SetValidatorOutstandingRewards(ctx, del.GetValidatorAddr(), outstanding.Sub(rewards))
feePool := k.GetFeePool(ctx) feePool := k.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Add(remainder) feePool.CommunityPool = feePool.CommunityPool.Add(remainder)
k.SetFeePool(ctx, feePool) k.SetFeePool(ctx, feePool)

View File

@ -13,9 +13,6 @@ func TestCalculateRewardsBasic(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
@ -25,6 +22,9 @@ func TestCalculateRewardsBasic(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
@ -66,9 +66,6 @@ func TestCalculateRewardsAfterSlash(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
valPower := int64(100) valPower := int64(100)
@ -81,6 +78,9 @@ func TestCalculateRewardsAfterSlash(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
@ -129,9 +129,6 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
power := int64(100) power := int64(100)
valTokens := sdk.TokensFromTendermintPower(power) valTokens := sdk.TokensFromTendermintPower(power)
@ -143,6 +140,9 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
@ -203,9 +203,6 @@ func TestCalculateRewardsMultiDelegator(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
@ -215,6 +212,9 @@ func TestCalculateRewardsMultiDelegator(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
@ -235,6 +235,9 @@ func TestCalculateRewardsMultiDelegator(t *testing.T) {
// end block // end block
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards // allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)
@ -263,9 +266,6 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) {
ctx, ak, k, sk, _ := CreateTestInputDefault(t, false, balancePower) ctx, ak, k, sk, _ := CreateTestInputDefault(t, false, balancePower)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
power := int64(100) power := int64(100)
valTokens := sdk.TokensFromTendermintPower(power) valTokens := sdk.TokensFromTendermintPower(power)
@ -287,6 +287,9 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
@ -294,7 +297,6 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) {
initial := sdk.TokensFromTendermintPower(10) initial := sdk.TokensFromTendermintPower(10)
tokens := sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, initial)} tokens := sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, initial)}
k.SetOutstandingRewards(ctx, tokens)
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)
// historical count should be 2 (initial + latest for delegation) // historical count should be 2 (initial + latest for delegation)
@ -328,9 +330,6 @@ func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
power := int64(100) power := int64(100)
valTokens := sdk.TokensFromTendermintPower(power) valTokens := sdk.TokensFromTendermintPower(power)
@ -342,6 +341,9 @@ func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
@ -395,9 +397,6 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) {
ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000) ctx, _, k, sk, _ := CreateTestInputDefault(t, false, 1000)
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
// initialize state
k.SetOutstandingRewards(ctx, sdk.DecCoins{})
// create validator with 50% commission // create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
power := int64(100) power := int64(100)
@ -409,6 +408,9 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
@ -433,6 +435,9 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) {
// end block // end block
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards // allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)
@ -471,9 +476,6 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
totalRewards := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDec(initial*2))} totalRewards := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDec(initial*2))}
tokens := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDec(initial))} tokens := sdk.DecCoins{sdk.NewDecCoinFromDec(sdk.DefaultBondDenom, sdk.NewDec(initial))}
// initialize state
k.SetOutstandingRewards(ctx, totalRewards)
// create validator with 50% commission // create validator with 50% commission
commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
@ -483,6 +485,9 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
// end block to bond validator // end block to bond validator
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// fetch validator and delegation // fetch validator and delegation
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del1 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
@ -507,6 +512,9 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
// end block // end block
staking.EndBlocker(ctx, sk) staking.EndBlocker(ctx, sk)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards // allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)
@ -540,8 +548,10 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
// commission should be zero // commission should be zero
require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero()) require.True(t, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1).IsZero())
totalRewards = k.GetOutstandingRewards(ctx).Add(tokens) totalRewards = totalRewards.Add(tokens)
k.SetOutstandingRewards(ctx, totalRewards)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards // allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)
@ -567,8 +577,10 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
// commission should be half initial // commission should be half initial
require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1)) require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(initial / 2)}}, k.GetValidatorAccumulatedCommission(ctx, valOpAddr1))
totalRewards = k.GetOutstandingRewards(ctx).Add(tokens) totalRewards = k.GetValidatorOutstandingRewards(ctx, valOpAddr1).Add(tokens)
k.SetOutstandingRewards(ctx, totalRewards)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some more rewards // allocate some more rewards
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)

View File

@ -22,9 +22,17 @@ func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {
func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {
} }
func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
// fetch outstanding
outstanding := h.k.GetValidatorOutstandingRewards(ctx, valAddr)
// force-withdraw commission // force-withdraw commission
commission := h.k.GetValidatorAccumulatedCommission(ctx, valAddr) commission := h.k.GetValidatorAccumulatedCommission(ctx, valAddr)
if !commission.IsZero() { if !commission.IsZero() {
// subtract from outstanding
outstanding = outstanding.Sub(commission)
// split into integral & remainder
coins, remainder := commission.TruncateDecimal() coins, remainder := commission.TruncateDecimal()
// remainder to community pool // remainder to community pool
@ -32,12 +40,9 @@ func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr
feePool.CommunityPool = feePool.CommunityPool.Add(remainder) feePool.CommunityPool = feePool.CommunityPool.Add(remainder)
h.k.SetFeePool(ctx, feePool) h.k.SetFeePool(ctx, feePool)
// update outstanding
outstanding := h.k.GetOutstandingRewards(ctx)
h.k.SetOutstandingRewards(ctx, outstanding.Sub(commission))
// add to validator account // add to validator account
if !coins.IsZero() { if !coins.IsZero() {
accAddr := sdk.AccAddress(valAddr) accAddr := sdk.AccAddress(valAddr)
withdrawAddr := h.k.GetDelegatorWithdrawAddr(ctx, accAddr) withdrawAddr := h.k.GetDelegatorWithdrawAddr(ctx, accAddr)
@ -46,6 +51,15 @@ func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr
} }
} }
} }
// add outstanding to community pool
feePool := h.k.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Add(outstanding)
h.k.SetFeePool(ctx, feePool)
// delete outstanding
h.k.DeleteValidatorOutstandingRewards(ctx, valAddr)
// remove commission record // remove commission record
h.k.DeleteValidatorAccumulatedCommission(ctx, valAddr) h.k.DeleteValidatorAccumulatedCommission(ctx, valAddr)

View File

@ -84,8 +84,8 @@ func (k Keeper) WithdrawValidatorCommission(ctx sdk.Context, valAddr sdk.ValAddr
k.SetValidatorAccumulatedCommission(ctx, valAddr, remainder) k.SetValidatorAccumulatedCommission(ctx, valAddr, remainder)
// update outstanding // update outstanding
outstanding := k.GetOutstandingRewards(ctx) outstanding := k.GetValidatorOutstandingRewards(ctx, valAddr)
k.SetOutstandingRewards(ctx, outstanding.Sub(sdk.NewDecCoins(coins))) k.SetValidatorOutstandingRewards(ctx, valAddr, outstanding.Sub(sdk.NewDecCoins(coins)))
if !coins.IsZero() { if !coins.IsZero() {
accAddr := sdk.AccAddress(valAddr) accAddr := sdk.AccAddress(valAddr)

View File

@ -30,9 +30,6 @@ func TestWithdrawValidatorCommission(t *testing.T) {
sdk.NewDecCoinFromDec("stake", sdk.NewDec(3).Quo(sdk.NewDec(2))), sdk.NewDecCoinFromDec("stake", sdk.NewDec(3).Quo(sdk.NewDec(2))),
} }
// set zero outstanding rewards
keeper.SetOutstandingRewards(ctx, valCommission)
// check initial balance // check initial balance
balance := ak.GetAccount(ctx, sdk.AccAddress(valOpAddr3)).GetCoins() balance := ak.GetAccount(ctx, sdk.AccAddress(valOpAddr3)).GetCoins()
expTokens := sdk.TokensFromTendermintPower(1000) expTokens := sdk.TokensFromTendermintPower(1000)
@ -40,6 +37,9 @@ func TestWithdrawValidatorCommission(t *testing.T) {
sdk.NewCoin("stake", sdk.TokensFromTendermintPower(1000)), sdk.NewCoin("stake", sdk.TokensFromTendermintPower(1000)),
}, balance) }, balance)
// set outstanding rewards
keeper.SetValidatorOutstandingRewards(ctx, valOpAddr3, valCommission)
// set commission // set commission
keeper.SetValidatorAccumulatedCommission(ctx, valOpAddr3, valCommission) keeper.SetValidatorAccumulatedCommission(ctx, valOpAddr3, valCommission)

View File

@ -13,9 +13,9 @@ const (
// keys // keys
var ( var (
FeePoolKey = []byte{0x00} // key for global distribution state FeePoolKey = []byte{0x00} // key for global distribution state
ProposerKey = []byte{0x01} // key for the proposer operator address ProposerKey = []byte{0x01} // key for the proposer operator address
OutstandingRewardsKey = []byte{0x02} // key for outstanding rewards ValidatorOutstandingRewardsPrefix = []byte{0x02} // key for outstanding rewards
DelegatorWithdrawAddrPrefix = []byte{0x03} // key for delegator withdraw address DelegatorWithdrawAddrPrefix = []byte{0x03} // key for delegator withdraw address
DelegatorStartingInfoPrefix = []byte{0x04} // key for delegator starting info DelegatorStartingInfoPrefix = []byte{0x04} // key for delegator starting info
@ -30,6 +30,15 @@ var (
ParamStoreKeyWithdrawAddrEnabled = []byte("withdrawaddrenabled") ParamStoreKeyWithdrawAddrEnabled = []byte("withdrawaddrenabled")
) )
// gets an address from a validator's outstanding rewards key
func GetValidatorOutstandingRewardsAddress(key []byte) (valAddr sdk.ValAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
return sdk.ValAddress(addr)
}
// gets an address from a delegator's withdraw info key // gets an address from a delegator's withdraw info key
func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) { func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) {
addr := key[1:] addr := key[1:]
@ -102,6 +111,11 @@ func GetValidatorSlashEventAddressHeight(key []byte) (valAddr sdk.ValAddress, he
return return
} }
// gets the outstanding rewards key for a validator
func GetValidatorOutstandingRewardsKey(valAddr sdk.ValAddress) []byte {
return append(ValidatorOutstandingRewardsPrefix, valAddr.Bytes()...)
}
// gets the key for a delegator's withdraw addr // gets the key for a delegator's withdraw addr
func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte { func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte {
return append(DelegatorWithdrawAddrPrefix, delAddr.Bytes()...) return append(DelegatorWithdrawAddrPrefix, delAddr.Bytes()...)

View File

@ -12,14 +12,14 @@ import (
// nolint // nolint
const ( const (
QueryParams = "params" QueryParams = "params"
QueryOutstandingRewards = "outstanding_rewards" QueryValidatorOutstandingRewards = "validator_outstanding_rewards"
QueryValidatorCommission = "validator_commission" QueryValidatorCommission = "validator_commission"
QueryValidatorSlashes = "validator_slashes" QueryValidatorSlashes = "validator_slashes"
QueryDelegationRewards = "delegation_rewards" QueryDelegationRewards = "delegation_rewards"
QueryDelegatorTotalRewards = "delegator_total_rewards" QueryDelegatorTotalRewards = "delegator_total_rewards"
QueryDelegatorValidators = "delegator_validators" QueryDelegatorValidators = "delegator_validators"
QueryWithdrawAddr = "withdraw_addr" QueryWithdrawAddr = "withdraw_addr"
ParamCommunityTax = "community_tax" ParamCommunityTax = "community_tax"
ParamBaseProposerReward = "base_proposer_reward" ParamBaseProposerReward = "base_proposer_reward"
@ -33,8 +33,8 @@ func NewQuerier(k Keeper) sdk.Querier {
case QueryParams: case QueryParams:
return queryParams(ctx, path[1:], req, k) return queryParams(ctx, path[1:], req, k)
case QueryOutstandingRewards: case QueryValidatorOutstandingRewards:
return queryOutstandingRewards(ctx, path[1:], req, k) return queryValidatorOutstandingRewards(ctx, path[1:], req, k)
case QueryValidatorCommission: case QueryValidatorCommission:
return queryValidatorCommission(ctx, path[1:], req, k) return queryValidatorCommission(ctx, path[1:], req, k)
@ -91,8 +91,25 @@ func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper
} }
} }
func queryOutstandingRewards(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { // params for query 'custom/distr/validator_outstanding_rewards'
bz, err := codec.MarshalJSONIndent(k.cdc, k.GetOutstandingRewards(ctx)) type QueryValidatorOutstandingRewardsParams struct {
ValidatorAddress sdk.ValAddress `json:"validator_address"`
}
// creates a new instance of QueryValidatorOutstandingRewardsParams
func NewQueryValidatorOutstandingRewardsParams(validatorAddr sdk.ValAddress) QueryValidatorOutstandingRewardsParams {
return QueryValidatorOutstandingRewardsParams{
ValidatorAddress: validatorAddr,
}
}
func queryValidatorOutstandingRewards(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) {
var params QueryValidatorOutstandingRewardsParams
err := k.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
bz, err := codec.MarshalJSONIndent(k.cdc, k.GetValidatorOutstandingRewards(ctx, params.ValidatorAddress))
if err != nil { if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
} }

View File

@ -57,13 +57,13 @@ func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier s
return return
} }
func getQueriedOutstandingRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (outstandingRewards sdk.DecCoins) { func getQueriedValidatorOutstandingRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, validatorAddr sdk.ValAddress) (outstandingRewards sdk.DecCoins) {
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, QueryOutstandingRewards}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, QueryValidatorOutstandingRewards}, "/"),
Data: []byte{}, Data: cdc.MustMarshalJSON(NewQueryValidatorOutstandingRewardsParams(validatorAddr)),
} }
bz, err := querier(ctx, []string{QueryOutstandingRewards}, query) bz, err := querier(ctx, []string{QueryValidatorOutstandingRewards}, query)
require.Nil(t, err) require.Nil(t, err)
require.Nil(t, cdc.UnmarshalJSON(bz, &outstandingRewards)) require.Nil(t, cdc.UnmarshalJSON(bz, &outstandingRewards))
@ -131,8 +131,8 @@ func TestQueries(t *testing.T) {
// test outstanding rewards query // test outstanding rewards query
outstandingRewards := sdk.DecCoins{{"mytoken", sdk.NewDec(3)}, {"myothertoken", sdk.NewDecWithPrec(3, 7)}} outstandingRewards := sdk.DecCoins{{"mytoken", sdk.NewDec(3)}, {"myothertoken", sdk.NewDecWithPrec(3, 7)}}
keeper.SetOutstandingRewards(ctx, outstandingRewards) keeper.SetValidatorOutstandingRewards(ctx, valOpAddr1, outstandingRewards)
retOutstandingRewards := getQueriedOutstandingRewards(t, ctx, cdc, querier) retOutstandingRewards := getQueriedValidatorOutstandingRewards(t, ctx, cdc, querier, valOpAddr1)
require.Equal(t, outstandingRewards, retOutstandingRewards) require.Equal(t, outstandingRewards, retOutstandingRewards)
// test validator commission query // test validator commission query
@ -155,7 +155,6 @@ func TestQueries(t *testing.T) {
// test delegation rewards query // test delegation rewards query
sh := staking.NewHandler(sk) sh := staking.NewHandler(sk)
keeper.SetOutstandingRewards(ctx, sdk.DecCoins{})
comm := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) comm := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1, msg := staking.NewMsgCreateValidator(valOpAddr1, valConsPk1,
sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, comm, sdk.OneInt()) sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)), staking.Description{}, comm, sdk.OneInt())
@ -165,6 +164,7 @@ func TestQueries(t *testing.T) {
rewards := getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1) rewards := getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1)
require.True(t, rewards.IsZero()) require.True(t, rewards.IsZero())
initial := int64(10) initial := int64(10)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
tokens := sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(initial)}} tokens := sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(initial)}}
keeper.AllocateTokensToValidator(ctx, val, tokens) keeper.AllocateTokensToValidator(ctx, val, tokens)
rewards = getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1) rewards = getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1)

View File

@ -263,19 +263,40 @@ func (k Keeper) IterateValidatorAccumulatedCommissions(ctx sdk.Context, handler
} }
} }
// get outstanding rewards // get validator outstanding rewards
func (k Keeper) GetOutstandingRewards(ctx sdk.Context) (rewards types.OutstandingRewards) { func (k Keeper) GetValidatorOutstandingRewards(ctx sdk.Context, val sdk.ValAddress) (rewards types.ValidatorOutstandingRewards) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
b := store.Get(OutstandingRewardsKey) b := store.Get(GetValidatorOutstandingRewardsKey(val))
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards) k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &rewards)
return return
} }
// set outstanding rewards // set validator outstanding rewards
func (k Keeper) SetOutstandingRewards(ctx sdk.Context, rewards types.OutstandingRewards) { func (k Keeper) SetValidatorOutstandingRewards(ctx sdk.Context, val sdk.ValAddress, rewards types.ValidatorOutstandingRewards) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards) b := k.cdc.MustMarshalBinaryLengthPrefixed(rewards)
store.Set(OutstandingRewardsKey, b) store.Set(GetValidatorOutstandingRewardsKey(val), b)
}
// delete validator outstanding rewards
func (k Keeper) DeleteValidatorOutstandingRewards(ctx sdk.Context, val sdk.ValAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetValidatorOutstandingRewardsKey(val))
}
// iterate validator outstanding rewards
func (k Keeper) IterateValidatorOutstandingRewards(ctx sdk.Context, handler func(val sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorOutstandingRewardsPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
var rewards types.ValidatorOutstandingRewards
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &rewards)
addr := GetValidatorOutstandingRewardsAddress(iter.Key())
if handler(addr, rewards) {
break
}
}
} }
// get slash event for height // get slash event for height

View File

@ -16,6 +16,9 @@ func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) {
// set accumulated commission // set accumulated commission
k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), types.InitialValidatorAccumulatedCommission()) k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), types.InitialValidatorAccumulatedCommission())
// set outstanding rewards
k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), sdk.DecCoins{})
} }
// increment validator period, returning the period just ended // increment validator period, returning the period just ended
@ -30,11 +33,11 @@ func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uin
// can't calculate ratio for zero-token validators // can't calculate ratio for zero-token validators
// ergo we instead add to the community pool // ergo we instead add to the community pool
feePool := k.GetFeePool(ctx) feePool := k.GetFeePool(ctx)
outstanding := k.GetOutstandingRewards(ctx) outstanding := k.GetValidatorOutstandingRewards(ctx, val.GetOperator())
feePool.CommunityPool = feePool.CommunityPool.Add(rewards.Rewards) feePool.CommunityPool = feePool.CommunityPool.Add(rewards.Rewards)
outstanding = outstanding.Sub(rewards.Rewards) outstanding = outstanding.Sub(rewards.Rewards)
k.SetFeePool(ctx, feePool) k.SetFeePool(ctx, feePool)
k.SetOutstandingRewards(ctx, outstanding) k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), outstanding)
current = sdk.DecCoins{} current = sdk.DecCoins{}
} else { } else {

View File

@ -30,11 +30,23 @@ func AllInvariants(d distr.Keeper, stk types.StakingKeeper) sdk.Invariant {
// NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative // NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative
func NonNegativeOutstandingInvariant(k distr.Keeper) sdk.Invariant { func NonNegativeOutstandingInvariant(k distr.Keeper) sdk.Invariant {
return func(ctx sdk.Context) error { return func(ctx sdk.Context) error {
outstanding := k.GetOutstandingRewards(ctx)
var outstanding sdk.DecCoins
k.IterateValidatorOutstandingRewards(ctx, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) {
outstanding = rewards
if outstanding.IsAnyNegative() {
return true
}
return false
})
if outstanding.IsAnyNegative() { if outstanding.IsAnyNegative() {
return fmt.Errorf("negative outstanding coins: %v", outstanding) return fmt.Errorf("negative outstanding coins: %v", outstanding)
} }
return nil return nil
} }
} }
@ -45,20 +57,29 @@ func CanWithdrawInvariant(k distr.Keeper, sk types.StakingKeeper) sdk.Invariant
// cache, we don't want to write changes // cache, we don't want to write changes
ctx, _ = ctx.CacheContext() ctx, _ = ctx.CacheContext()
// iterate over all bonded validators, withdraw commission var remaining sdk.DecCoins
// iterate over all validators
sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
_ = k.WithdrawValidatorCommission(ctx, val.GetOperator()) _ = k.WithdrawValidatorCommission(ctx, val.GetOperator())
// TODO fetch delegations just for the validator, requires sdk.ValidatorSet change
// iterate over all current delegations, withdraw rewards
dels := sk.GetAllSDKDelegations(ctx)
for _, delegation := range dels {
if delegation.GetValidatorAddr().String() == val.GetOperator().String() {
err := k.WithdrawDelegationRewards(ctx, delegation.GetDelegatorAddr(), delegation.GetValidatorAddr())
if err != nil {
panic(err)
}
}
}
remaining = k.GetValidatorOutstandingRewards(ctx, val.GetOperator())
if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) {
return true
}
return false return false
}) })
// iterate over all current delegations, withdraw rewards
dels := sk.GetAllSDKDelegations(ctx)
for _, delegation := range dels {
_ = k.WithdrawDelegationRewards(ctx, delegation.GetDelegatorAddr(), delegation.GetValidatorAddr())
}
remaining := k.GetOutstandingRewards(ctx)
if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) { if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) {
return fmt.Errorf("negative remaining coins: %v", remaining) return fmt.Errorf("negative remaining coins: %v", remaining)
} }

View File

@ -27,8 +27,3 @@ func (f FeePool) ValidateGenesis() error {
return nil 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

@ -13,6 +13,12 @@ type DelegatorWithdrawInfo struct {
WithdrawAddress sdk.AccAddress `json:"withdraw_address"` WithdrawAddress sdk.AccAddress `json:"withdraw_address"`
} }
// used for import/export via genesis json
type ValidatorOutstandingRewardsRecord struct {
ValidatorAddress sdk.ValAddress `json:"validator_address"`
OutstandingRewards sdk.DecCoins `json:"outstanding_rewards"`
}
// used for import / export via genesis json // used for import / export via genesis json
type ValidatorAccumulatedCommissionRecord struct { type ValidatorAccumulatedCommissionRecord struct {
ValidatorAddress sdk.ValAddress `json:"validator_address"` ValidatorAddress sdk.ValAddress `json:"validator_address"`
@ -55,7 +61,7 @@ type GenesisState struct {
WithdrawAddrEnabled bool `json:"withdraw_addr_enabled"` WithdrawAddrEnabled bool `json:"withdraw_addr_enabled"`
DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"` DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"`
PreviousProposer sdk.ConsAddress `json:"previous_proposer"` PreviousProposer sdk.ConsAddress `json:"previous_proposer"`
OutstandingRewards sdk.DecCoins `json:"outstanding_rewards"` OutstandingRewards []ValidatorOutstandingRewardsRecord `json:"outstanding_rewards"`
ValidatorAccumulatedCommissions []ValidatorAccumulatedCommissionRecord `json:"validator_accumulated_commissions"` ValidatorAccumulatedCommissions []ValidatorAccumulatedCommissionRecord `json:"validator_accumulated_commissions"`
ValidatorHistoricalRewards []ValidatorHistoricalRewardsRecord `json:"validator_historical_rewards"` ValidatorHistoricalRewards []ValidatorHistoricalRewardsRecord `json:"validator_historical_rewards"`
ValidatorCurrentRewards []ValidatorCurrentRewardsRecord `json:"validator_current_rewards"` ValidatorCurrentRewards []ValidatorCurrentRewardsRecord `json:"validator_current_rewards"`
@ -64,7 +70,7 @@ type GenesisState struct {
} }
func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec, func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec,
withdrawAddrEnabled bool, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress, r OutstandingRewards, withdrawAddrEnabled bool, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress, r []ValidatorOutstandingRewardsRecord,
acc []ValidatorAccumulatedCommissionRecord, historical []ValidatorHistoricalRewardsRecord, acc []ValidatorAccumulatedCommissionRecord, historical []ValidatorHistoricalRewardsRecord,
cur []ValidatorCurrentRewardsRecord, dels []DelegatorStartingInfoRecord, cur []ValidatorCurrentRewardsRecord, dels []DelegatorStartingInfoRecord,
slashes []ValidatorSlashEventRecord) GenesisState { slashes []ValidatorSlashEventRecord) GenesisState {
@ -96,7 +102,7 @@ func DefaultGenesisState() GenesisState {
WithdrawAddrEnabled: true, WithdrawAddrEnabled: true,
DelegatorWithdrawInfos: []DelegatorWithdrawInfo{}, DelegatorWithdrawInfos: []DelegatorWithdrawInfo{},
PreviousProposer: nil, PreviousProposer: nil,
OutstandingRewards: sdk.DecCoins{}, OutstandingRewards: []ValidatorOutstandingRewardsRecord{},
ValidatorAccumulatedCommissions: []ValidatorAccumulatedCommissionRecord{}, ValidatorAccumulatedCommissions: []ValidatorAccumulatedCommissionRecord{},
ValidatorHistoricalRewards: []ValidatorHistoricalRewardsRecord{}, ValidatorHistoricalRewards: []ValidatorHistoricalRewardsRecord{},
ValidatorCurrentRewards: []ValidatorCurrentRewardsRecord{}, ValidatorCurrentRewards: []ValidatorCurrentRewardsRecord{},

View File

@ -91,3 +91,7 @@ func (vs ValidatorSlashEvents) String() string {
} }
return strings.TrimSpace(out) return strings.TrimSpace(out)
} }
// outstanding (un-withdrawn) rewards for a validator
// inexpensive to track, allows simple sanity checks
type ValidatorOutstandingRewards = sdk.DecCoins

View File

@ -31,7 +31,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result {
return ErrMissingSelfDelegation(k.codespace).Result() return ErrMissingSelfDelegation(k.codespace).Result()
} }
if validator.GetDelegatorShareExRate().Mul(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { if validator.ShareTokens(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) {
return ErrSelfDelegationTooLowToUnjail(k.codespace).Result() return ErrSelfDelegationTooLowToUnjail(k.codespace).Result()
} }

View File

@ -301,8 +301,6 @@ func TestIncrementsMsgDelegate(t *testing.T) {
require.Equal(t, bondAmount, bond.Shares.RoundInt()) require.Equal(t, bondAmount, bond.Shares.RoundInt())
pool := keeper.GetPool(ctx) pool := keeper.GetPool(ctx)
exRate := validator.DelegatorShareExRate()
require.True(t, exRate.Equal(sdk.OneDec()), "expected exRate 1 got %v", exRate)
require.Equal(t, bondAmount, pool.BondedTokens) require.Equal(t, bondAmount, pool.BondedTokens)
// just send the same msgbond multiple times // just send the same msgbond multiple times
@ -320,9 +318,6 @@ func TestIncrementsMsgDelegate(t *testing.T) {
bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
require.True(t, found) require.True(t, found)
exRate := validator.DelegatorShareExRate()
require.True(t, exRate.Equal(sdk.OneDec()), "expected exRate 1 got %v, i = %v", exRate, i)
expBond := bondAmount.MulRaw(i + 1) expBond := bondAmount.MulRaw(i + 1)
expDelegatorShares := bondAmount.MulRaw(i + 2) // (1 self delegation) expDelegatorShares := bondAmount.MulRaw(i + 2) // (1 self delegation)
expDelegatorAcc := initBond.Sub(expBond) expDelegatorAcc := initBond.Sub(expBond)

View File

@ -450,7 +450,7 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.In
// In some situations, the exchange rate becomes invalid, e.g. if // In some situations, the exchange rate becomes invalid, e.g. if
// Validator loses all tokens due to slashing. In this case, // Validator loses all tokens due to slashing. In this case,
// make all future delegations invalid. // make all future delegations invalid.
if validator.DelegatorShareExRate().IsZero() { if validator.InvalidExRate() {
return sdk.ZeroDec(), types.ErrDelegatorShareExRateInvalid(k.Codespace()) return sdk.ZeroDec(), types.ErrDelegatorShareExRateInvalid(k.Codespace())
} }
@ -517,7 +517,9 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA
// if the delegation is the operator of the validator and undelegating will decrease the validator's self delegation below their minimum // if the delegation is the operator of the validator and undelegating will decrease the validator's self delegation below their minimum
// trigger a jail validator // trigger a jail validator
if isValidatorOperator && !validator.Jailed && validator.DelegatorShareExRate().Mul(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) { if isValidatorOperator && !validator.Jailed &&
validator.ShareTokens(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) {
k.jailValidator(ctx, validator) k.jailValidator(ctx, validator)
validator = k.mustGetValidator(ctx, validator.OperatorAddress) validator = k.mustGetValidator(ctx, validator.OperatorAddress)
} }

View File

@ -57,17 +57,6 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh
// call the before-modification hook // call the before-modification hook
k.BeforeValidatorModified(ctx, operatorAddress) k.BeforeValidatorModified(ctx, operatorAddress)
// we need to calculate the *effective* slash fraction for distribution
if validator.Tokens.GT(sdk.ZeroInt()) {
effectiveFraction := slashAmountDec.Quo(validator.Tokens.ToDec())
// possible if power has changed
if effectiveFraction.GT(sdk.OneDec()) {
effectiveFraction = sdk.OneDec()
}
// call the before-slashed hook
k.BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction)
}
// Track remaining slash amount for the validator // Track remaining slash amount for the validator
// This will decrease when we slash unbondings and // This will decrease when we slash unbondings and
// redelegations, as that stake has since unbonded // redelegations, as that stake has since unbonded
@ -115,6 +104,17 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh
tokensToBurn := sdk.MinInt(remainingSlashAmount, validator.Tokens) tokensToBurn := sdk.MinInt(remainingSlashAmount, validator.Tokens)
tokensToBurn = sdk.MaxInt(tokensToBurn, sdk.ZeroInt()) // defensive. tokensToBurn = sdk.MaxInt(tokensToBurn, sdk.ZeroInt()) // defensive.
// we need to calculate the *effective* slash fraction for distribution
if validator.Tokens.GT(sdk.ZeroInt()) {
effectiveFraction := tokensToBurn.ToDec().QuoRoundUp(validator.Tokens.ToDec())
// possible if power has changed
if effectiveFraction.GT(sdk.OneDec()) {
effectiveFraction = sdk.OneDec()
}
// call the before-slashed hook
k.BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction)
}
// Deduct from validator's bonded tokens and update the validator. // Deduct from validator's bonded tokens and update the validator.
// The deducted tokens are returned to pool.NotBondedTokens. // The deducted tokens are returned to pool.NotBondedTokens.
// TODO: Move the token accounting outside of `RemoveValidatorTokens` so it is less confusing // TODO: Move the token accounting outside of `RemoveValidatorTokens` so it is less confusing

View File

@ -67,6 +67,8 @@ func SupplyInvariants(k staking.Keeper,
case sdk.Unbonding, sdk.Unbonded: case sdk.Unbonding, sdk.Unbonded:
loose = loose.Add(validator.GetTokens().ToDec()) loose = loose.Add(validator.GetTokens().ToDec())
} }
// add yet-to-be-withdrawn
loose = loose.Add(d.GetValidatorOutstandingRewardsCoins(ctx, validator.GetOperator()).AmountOf(k.BondDenom(ctx)))
return false return false
}) })
@ -76,9 +78,6 @@ func SupplyInvariants(k staking.Keeper,
// add community pool // add community pool
loose = loose.Add(d.GetFeePoolCommunityCoins(ctx).AmountOf(k.BondDenom(ctx))) loose = loose.Add(d.GetFeePoolCommunityCoins(ctx).AmountOf(k.BondDenom(ctx)))
// add yet-to-be-withdrawn
loose = loose.Add(d.GetOutstandingRewardsCoins(ctx).AmountOf(k.BondDenom(ctx)))
// Not-bonded tokens should equal coin supply plus unbonding delegations // Not-bonded tokens should equal coin supply plus unbonding delegations
// plus tokens on unbonded validators // plus tokens on unbonded validators
if !pool.NotBondedTokens.ToDec().Equal(loose) { if !pool.NotBondedTokens.ToDec().Equal(loose) {

View File

@ -5,7 +5,7 @@ import sdk "github.com/cosmos/cosmos-sdk/types"
// expected coin keeper // expected coin keeper
type DistributionKeeper interface { type DistributionKeeper interface {
GetFeePoolCommunityCoins(ctx sdk.Context) sdk.DecCoins GetFeePoolCommunityCoins(ctx sdk.Context) sdk.DecCoins
GetOutstandingRewardsCoins(ctx sdk.Context) sdk.DecCoins GetValidatorOutstandingRewardsCoins(ctx sdk.Context, val sdk.ValAddress) sdk.DecCoins
} }
// expected fee collection keeper // expected fee collection keeper

View File

@ -348,10 +348,13 @@ func (v Validator) SetInitialCommission(commission Commission) (Validator, sdk.E
// CONTRACT: Tokens are assumed to have come from not-bonded pool. // CONTRACT: Tokens are assumed to have come from not-bonded pool.
func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, sdk.Dec) { func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, sdk.Dec) {
// bondedShare/delegatedShare // calculate the shares to issue
exRate := v.DelegatorShareExRate() var issuedShares sdk.Dec
if exRate.IsZero() { if v.DelegatorShares.IsZero() {
panic("zero exRate should not happen") // the first delegation to a validator sets the exchange rate to one
issuedShares = amount.ToDec()
} else {
issuedShares = v.DelegatorShares.MulInt(amount).QuoInt(v.Tokens)
} }
if v.Status == sdk.Bonded { if v.Status == sdk.Bonded {
@ -359,7 +362,6 @@ func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool,
} }
v.Tokens = v.Tokens.Add(amount) v.Tokens = v.Tokens.Add(amount)
issuedShares := amount.ToDec().Quo(exRate)
v.DelegatorShares = v.DelegatorShares.Add(issuedShares) v.DelegatorShares = v.DelegatorShares.Add(issuedShares)
return v, pool, issuedShares return v, pool, issuedShares
@ -382,7 +384,7 @@ func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Poo
// leave excess tokens in the validator // leave excess tokens in the validator
// however fully use all the delegator shares // however fully use all the delegator shares
issuedTokens = v.DelegatorShareExRate().Mul(delShares).TruncateInt() issuedTokens = v.ShareTokens(delShares).TruncateInt()
v.Tokens = v.Tokens.Sub(issuedTokens) v.Tokens = v.Tokens.Sub(issuedTokens)
if v.Tokens.IsNegative() { if v.Tokens.IsNegative() {
panic("attempting to remove more tokens than available in validator") panic("attempting to remove more tokens than available in validator")
@ -397,14 +399,21 @@ func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Poo
return v, pool, issuedTokens return v, pool, issuedTokens
} }
// DelegatorShareExRate gets the exchange rate of tokens over delegator shares. // In some situations, the exchange rate becomes invalid, e.g. if
// UNITS: tokens/delegator-shares // Validator loses all tokens due to slashing. In this case,
func (v Validator) DelegatorShareExRate() sdk.Dec { // make all future delegations invalid.
if v.DelegatorShares.IsZero() { func (v Validator) InvalidExRate() bool {
// the first delegation to a validator sets the exchange rate to one return v.Tokens.IsZero() && v.DelegatorShares.IsPositive()
return sdk.OneDec() }
}
return v.Tokens.ToDec().Quo(v.DelegatorShares) // calculate the token worth of provided shares
func (v Validator) ShareTokens(shares sdk.Dec) sdk.Dec {
return (shares.MulInt(v.Tokens)).Quo(v.DelegatorShares)
}
// calculate the token worth of provided shares, truncated
func (v Validator) ShareTokensTruncated(shares sdk.Dec) sdk.Dec {
return (shares.MulInt(v.Tokens)).QuoTruncate(v.DelegatorShares)
} }
// get the bonded tokens which the validator holds // get the bonded tokens which the validator holds
@ -433,16 +442,15 @@ func (v Validator) PotentialTendermintPower() int64 {
var _ sdk.Validator = Validator{} var _ sdk.Validator = Validator{}
// nolint - for sdk.Validator // nolint - for sdk.Validator
func (v Validator) GetJailed() bool { return v.Jailed } func (v Validator) GetJailed() bool { return v.Jailed }
func (v Validator) GetMoniker() string { return v.Description.Moniker } func (v Validator) GetMoniker() string { return v.Description.Moniker }
func (v Validator) GetStatus() sdk.BondStatus { return v.Status } func (v Validator) GetStatus() sdk.BondStatus { return v.Status }
func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddress } func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddress }
func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey } func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey }
func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) } func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) }
func (v Validator) GetTokens() sdk.Int { return v.Tokens } func (v Validator) GetTokens() sdk.Int { return v.Tokens }
func (v Validator) GetBondedTokens() sdk.Int { return v.BondedTokens() } func (v Validator) GetBondedTokens() sdk.Int { return v.BondedTokens() }
func (v Validator) GetTendermintPower() int64 { return v.TendermintPower() } func (v Validator) GetTendermintPower() int64 { return v.TendermintPower() }
func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate } func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate }
func (v Validator) GetMinSelfDelegation() sdk.Int { return v.MinSelfDelegation } func (v Validator) GetMinSelfDelegation() sdk.Int { return v.MinSelfDelegation }
func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares }
func (v Validator) GetDelegatorShareExRate() sdk.Dec { return v.DelegatorShareExRate() }

View File

@ -1,7 +1,6 @@
package types package types
import ( import (
"fmt"
"testing" "testing"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
@ -70,6 +69,21 @@ func TestABCIValidatorUpdateZero(t *testing.T) {
require.Equal(t, int64(0), abciVal.Power) require.Equal(t, int64(0), abciVal.Power)
} }
func TestShareTokens(t *testing.T) {
validator := Validator{
OperatorAddress: addr1,
ConsPubKey: pk1,
Status: sdk.Bonded,
Tokens: sdk.NewInt(100),
DelegatorShares: sdk.NewDec(100),
}
assert.True(sdk.DecEq(t, sdk.NewDec(50), validator.ShareTokens(sdk.NewDec(50))))
validator.Tokens = sdk.NewInt(50)
assert.True(sdk.DecEq(t, sdk.NewDec(25), validator.ShareTokens(sdk.NewDec(50))))
assert.True(sdk.DecEq(t, sdk.NewDec(5), validator.ShareTokens(sdk.NewDec(10))))
}
func TestRemoveTokens(t *testing.T) { func TestRemoveTokens(t *testing.T) {
validator := Validator{ validator := Validator{
@ -112,10 +126,9 @@ func TestAddTokensValidatorBonded(t *testing.T) {
validator, pool = validator.UpdateStatus(pool, sdk.Bonded) validator, pool = validator.UpdateStatus(pool, sdk.Bonded)
validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate())
assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares))
assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.BondedTokens())) assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.BondedTokens()))
assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares))
} }
func TestAddTokensValidatorUnbonding(t *testing.T) { func TestAddTokensValidatorUnbonding(t *testing.T) {
@ -125,11 +138,10 @@ func TestAddTokensValidatorUnbonding(t *testing.T) {
validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) validator, pool = validator.UpdateStatus(pool, sdk.Unbonding)
validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate())
assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares))
assert.Equal(t, sdk.Unbonding, validator.Status) assert.Equal(t, sdk.Unbonding, validator.Status)
assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens))
assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares))
} }
func TestAddTokensValidatorUnbonded(t *testing.T) { func TestAddTokensValidatorUnbonded(t *testing.T) {
@ -139,11 +151,10 @@ func TestAddTokensValidatorUnbonded(t *testing.T) {
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate())
assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares))
assert.Equal(t, sdk.Unbonded, validator.Status) assert.Equal(t, sdk.Unbonded, validator.Status)
assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens)) assert.True(sdk.IntEq(t, sdk.NewInt(10), validator.Tokens))
assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.DelegatorShares))
} }
// TODO refactor to make simpler like the AddToken tests above // TODO refactor to make simpler like the AddToken tests above
@ -158,7 +169,6 @@ func TestRemoveDelShares(t *testing.T) {
poolA := InitialPool() poolA := InitialPool()
poolA.NotBondedTokens = sdk.NewInt(10) poolA.NotBondedTokens = sdk.NewInt(10)
poolA.BondedTokens = valA.BondedTokens() poolA.BondedTokens = valA.BondedTokens()
require.Equal(t, valA.DelegatorShareExRate(), sdk.OneDec())
// Remove delegator shares // Remove delegator shares
valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewDec(10)) valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewDec(10))
@ -197,6 +207,26 @@ func TestRemoveDelShares(t *testing.T) {
pool.NotBondedTokens.Add(pool.BondedTokens))) pool.NotBondedTokens.Add(pool.BondedTokens)))
} }
func TestAddTokensFromDel(t *testing.T) {
val := NewValidator(addr1, pk1, Description{})
pool := InitialPool()
pool.NotBondedTokens = sdk.NewInt(10)
val, pool, shares := val.AddTokensFromDel(pool, sdk.NewInt(6))
require.True(sdk.DecEq(t, sdk.NewDec(6), shares))
require.True(sdk.DecEq(t, sdk.NewDec(6), val.DelegatorShares))
require.True(sdk.IntEq(t, sdk.NewInt(6), val.Tokens))
require.True(sdk.IntEq(t, sdk.NewInt(0), pool.BondedTokens))
require.True(sdk.IntEq(t, sdk.NewInt(10), pool.NotBondedTokens))
val, pool, shares = val.AddTokensFromDel(pool, sdk.NewInt(3))
require.True(sdk.DecEq(t, sdk.NewDec(3), shares))
require.True(sdk.DecEq(t, sdk.NewDec(9), val.DelegatorShares))
require.True(sdk.IntEq(t, sdk.NewInt(9), val.Tokens))
require.True(sdk.IntEq(t, sdk.NewInt(0), pool.BondedTokens))
require.True(sdk.IntEq(t, sdk.NewInt(10), pool.NotBondedTokens))
}
func TestUpdateStatus(t *testing.T) { func TestUpdateStatus(t *testing.T) {
pool := InitialPool() pool := InitialPool()
pool.NotBondedTokens = sdk.NewInt(100) pool.NotBondedTokens = sdk.NewInt(100)
@ -236,13 +266,10 @@ func TestPossibleOverflow(t *testing.T) {
BondedTokens: poolTokens, BondedTokens: poolTokens,
} }
tokens := int64(71) tokens := int64(71)
msg := fmt.Sprintf("validator %#v", validator)
newValidator, _, _ := validator.AddTokensFromDel(pool, sdk.NewInt(tokens)) newValidator, _, _ := validator.AddTokensFromDel(pool, sdk.NewInt(tokens))
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) require.False(t, newValidator.DelegatorShares.IsNegative())
require.False(t, newValidator.DelegatorShareExRate().LT(sdk.ZeroDec()), require.False(t, newValidator.Tokens.IsNegative())
"Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v",
msg, newValidator.DelegatorShareExRate())
} }
func TestValidatorMarshalUnmarshalJSON(t *testing.T) { func TestValidatorMarshalUnmarshalJSON(t *testing.T) {