From 1e74da1abe20c961e2c86b8f292b615fb94f1720 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 4 Oct 2018 03:00:24 -0400 Subject: [PATCH] validator_info tests --- types/decimal.go | 4 +- x/distribution/keeper/allocation.go | 10 +-- x/distribution/types/dec_coin.go | 50 +++++++++++-- x/distribution/types/dec_coin_test.go | 14 ++-- x/distribution/types/delegator_info.go | 14 +++- x/distribution/types/fee_pool.go | 6 +- x/distribution/types/fee_pool_test.go | 19 +++-- x/distribution/types/msg_test.go | 26 +++---- x/distribution/types/validator_info.go | 24 +++++-- x/distribution/types/validator_info_test.go | 80 ++++++++++++++++++++- 10 files changed, 197 insertions(+), 50 deletions(-) diff --git a/types/decimal.go b/types/decimal.go index 13a8a26c1..a4d1e40ed 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -462,6 +462,6 @@ func MaxDec(d1, d2 Dec) Dec { } // intended to be used with require/assert: require.True(DecEq(...)) -func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) { - return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got +func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, string, string) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() } diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index 4a2d82003..3c4bd3971 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -26,22 +26,22 @@ func (k Keeper) AllocateFees(ctx sdk.Context) { sumPowerPrecommitValidators := sdk.NewDec(1) // XXX TODO actually calculate this proposerMultiplier := sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(4, 2).Mul( sumPowerPrecommitValidators).Quo(bondedTokens)) - proposerReward := feesCollectedDec.Mul(proposerMultiplier) + proposerReward := feesCollectedDec.MulDec(proposerMultiplier) // apply commission - commission := proposerReward.Mul(proposerValidator.GetCommission()) - remaining := proposerReward.Mul(sdk.OneDec().Sub(proposerValidator.GetCommission())) + commission := proposerReward.MulDec(proposerValidator.GetCommission()) + remaining := proposerReward.MulDec(sdk.OneDec().Sub(proposerValidator.GetCommission())) proposerDist.PoolCommission = proposerDist.PoolCommission.Plus(commission) proposerDist.Pool = proposerDist.Pool.Plus(remaining) // allocate community funding communityTax := k.GetCommunityTax(ctx) - communityFunding := feesCollectedDec.Mul(communityTax) + communityFunding := feesCollectedDec.MulDec(communityTax) feePool := k.GetFeePool(ctx) feePool.CommunityPool = feePool.CommunityPool.Plus(communityFunding) // set the global pool within the distribution module - poolReceived := feesCollectedDec.Mul(sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax)) + poolReceived := feesCollectedDec.MulDec(sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax)) feePool.Pool = feePool.Pool.Plus(poolReceived) k.SetValidatorDistInfo(ctx, proposerDist) diff --git a/x/distribution/types/dec_coin.go b/x/distribution/types/dec_coin.go index eef4f78a6..df94d864d 100644 --- a/x/distribution/types/dec_coin.go +++ b/x/distribution/types/dec_coin.go @@ -27,6 +27,14 @@ func (coin DecCoin) Plus(coinB DecCoin) DecCoin { return DecCoin{coin.Denom, coin.Amount.Add(coinB.Amount)} } +// Subtracts amounts of two coins with same denom +func (coin DecCoin) Minus(coinB DecCoin) DecCoin { + if !(coin.Denom == coinB.Denom) { + return coin + } + return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)} +} + // return the decimal coins with trunctated decimals func (coin DecCoin) TruncateDecimal() sdk.Coin { return sdk.NewCoin(coin.Denom, coin.Amount.TruncateInt()) @@ -89,15 +97,45 @@ func (coins DecCoins) Plus(coinsB DecCoins) DecCoins { } } -// multiply all the coins by a multiple -func (coins DecCoins) Mul(multiple sdk.Dec) DecCoins { - products := make([]DecCoin, len(coins)) +// Negative returns a set of coins with all amount negative +func (coins DecCoins) Negative() DecCoins { + res := make([]DecCoin, 0, len(coins)) + for _, coin := range coins { + res = append(res, DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.Neg(), + }) + } + return res +} + +// Minus subtracts a set of coins from another (adds the inverse) +func (coins DecCoins) Minus(coinsB DecCoins) DecCoins { + return coins.Plus(coinsB.Negative()) +} + +// multiply all the coins by a decimal +func (coins DecCoins) MulDec(d sdk.Dec) DecCoins { + res := make([]DecCoin, len(coins)) for i, coin := range coins { product := DecCoin{ Denom: coin.Denom, - Amount: coin.Amount.Mul(multiple), + Amount: coin.Amount.Mul(d), } - products[i] = product + res[i] = product } - return products + return res +} + +// divide all the coins by a multiple +func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins { + res := make([]DecCoin, len(coins)) + for i, coin := range coins { + quotient := DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.Quo(d), + } + res[i] = quotient + } + return res } diff --git a/x/distribution/types/dec_coin_test.go b/x/distribution/types/dec_coin_test.go index ee8aa30c0..61de361ca 100644 --- a/x/distribution/types/dec_coin_test.go +++ b/x/distribution/types/dec_coin_test.go @@ -4,17 +4,16 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPlusDecCoin(t *testing.T) { - decCoinA1 := DecCoin("A", sdk.NewDecWithPrec(11, 1)) - decCoinA2 := DecCoin("A", sdk.NewDecWithPrec(22, 1)) - decCoinB1 := DecCoin("B", sdk.NewDecWithPrec(11, 1)) - decCoinC1 := DecCoin("C", sdk.NewDecWithPrec(11, 1)) - decCoinCn4 := DecCoin("C", sdk.NewDecWithPrec(-44, 1)) - decCoinC5 := DecCoin("C", sdk.NewDecWithPrec(55, 1)) + decCoinA1 := DecCoin{"A", sdk.NewDecWithPrec(11, 1)} + decCoinA2 := DecCoin{"A", sdk.NewDecWithPrec(22, 1)} + decCoinB1 := DecCoin{"B", sdk.NewDecWithPrec(11, 1)} + decCoinC1 := DecCoin{"C", sdk.NewDecWithPrec(11, 1)} + decCoinCn4 := DecCoin{"C", sdk.NewDecWithPrec(-44, 1)} + decCoinC5 := DecCoin{"C", sdk.NewDecWithPrec(55, 1)} cases := []struct { inputOne DecCoin @@ -51,7 +50,6 @@ func TestPlusCoins(t *testing.T) { for tcIndex, tc := range cases { res := tc.inputOne.Plus(tc.inputTwo) - assert.True(t, res.IsValid()) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) } } diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 608ef68d4..e083cf2d4 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -9,6 +9,16 @@ type DelegatorDistInfo struct { WithdrawalHeight int64 `json:"withdrawal_height"` // last time this delegation withdrew rewards } +func NewDelegatorDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress, + currentHeight int64) DelegatorDistInfo { + + return DelegatorDistInfo{ + DelegatorAddr: delegatorAddr, + ValOperatorAddr: valOperatorAddr, + WithdrawalHeight: currentHeight, + } +} + // withdraw rewards from delegator func (di DelegatorDistInfo) WithdrawRewards(fp FeePool, vi ValidatorDistInfo, height int64, totalBonded, vdTokens, totalDelShares, delegatorShares, @@ -24,8 +34,8 @@ func (di DelegatorDistInfo) WithdrawRewards(fp FeePool, vi ValidatorDistInfo, blocks := height - di.WithdrawalHeight di.WithdrawalHeight = height accum := delegatorShares.Mul(sdk.NewDec(blocks)) - withdrawalTokens := vi.Pool.Mul(accum.Quo(vi.DelAccum.Accum)) - remainingTokens := vi.Pool.Mul(sdk.OneDec().Sub(accum.Quo(vi.DelAccum.Accum))) + withdrawalTokens := vi.Pool.MulDec(accum.Quo(vi.DelAccum.Accum)) + remainingTokens := vi.Pool.MulDec(sdk.OneDec().Sub(accum.Quo(vi.DelAccum.Accum))) vi.Pool = remainingTokens vi.DelAccum.Accum = vi.DelAccum.Accum.Sub(accum) diff --git a/x/distribution/types/fee_pool.go b/x/distribution/types/fee_pool.go index 03725d7c1..d373e0485 100644 --- a/x/distribution/types/fee_pool.go +++ b/x/distribution/types/fee_pool.go @@ -1,6 +1,8 @@ package types -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) // total accumulation tracker type TotalAccum struct { @@ -29,7 +31,7 @@ func (ta TotalAccum) Update(height int64, accumCreatedPerBlock sdk.Dec) TotalAcc type FeePool struct { ValAccum TotalAccum `json:"val_accum"` // total valdator accum held by validators Pool DecCoins `json:"pool"` // funds for all validators which have yet to be withdrawn - CommunityPool DecCoins `json:"community_pool"` // pool for community funds yet to be spent} + CommunityPool DecCoins `json:"community_pool"` // pool for community funds yet to be spent } // update total validator accumulation factor diff --git a/x/distribution/types/fee_pool_test.go b/x/distribution/types/fee_pool_test.go index fc14cb153..1c2a804b4 100644 --- a/x/distribution/types/fee_pool_test.go +++ b/x/distribution/types/fee_pool_test.go @@ -11,9 +11,20 @@ func TestTotalAccumUpdate(t *testing.T) { ta := NewTotalAccum(0) - ta.Update(5, sdk.NewDec(3)) - require.True(DecEq(t, sdk.NewDec(15), ta.Accum)) + ta = ta.Update(5, sdk.NewDec(3)) + require.True(sdk.DecEq(t, sdk.NewDec(15), ta.Accum)) - ta.Update(8, sdk.NewDec(2)) - require.True(DecEq(t, sdk.NewDec(21), ta.Accum)) + ta = ta.Update(8, sdk.NewDec(2)) + require.True(sdk.DecEq(t, sdk.NewDec(21), ta.Accum)) +} + +func TestUpdateTotalValAccum(t *testing.T) { + + fp := InitialFeePool() + + fp = fp.UpdateTotalValAccum(5, sdk.NewDec(3)) + require.True(sdk.DecEq(t, sdk.NewDec(15), fp.ValAccum.Accum)) + + fp = fp.UpdateTotalValAccum(8, sdk.NewDec(2)) + require.True(sdk.DecEq(t, sdk.NewDec(21), fp.ValAccum.Accum)) } diff --git a/x/distribution/types/msg_test.go b/x/distribution/types/msg_test.go index 24af43b2e..b0c0dd9eb 100644 --- a/x/distribution/types/msg_test.go +++ b/x/distribution/types/msg_test.go @@ -22,12 +22,12 @@ func TestMsgSetWithdrawAddress(t *testing.T) { {emptyDelAddr, emptyDelAddr, false}, } - for _, tc := range tests { + for i, tc := range tests { msg := NewMsgSetWithdrawAddress(tc.delegatorAddr, tc.withdrawAddr) if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) } } } @@ -44,12 +44,12 @@ func TestMsgWithdrawDelegatorReward(t *testing.T) { {delAddr1, emptyValAddr, false}, {emptyDelAddr, emptyValAddr, false}, } - for _, tc := range tests { + for i, tc := range tests { msg := NewMsgWithdrawDelegatorReward(tc.delegatorAddr, tc.validatorAddr) if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) } } } @@ -60,15 +60,15 @@ func TestMsgWithdrawDelegatorRewardsAll(t *testing.T) { delegatorAddr sdk.AccAddress expectPass bool }{ - {delAddr, true}, + {delAddr1, true}, {emptyDelAddr, false}, } - for _, tc := range tests { + for i, tc := range tests { msg := NewMsgWithdrawDelegatorRewardsAll(tc.delegatorAddr) if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) } } } @@ -82,12 +82,12 @@ func TestMsgWithdrawValidatorRewardsAll(t *testing.T) { {valAddr1, true}, {emptyValAddr, false}, } - for _, tc := range tests { + for i, tc := range tests { msg := NewMsgWithdrawValidatorRewardsAll(tc.validatorAddr) if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) } } } diff --git a/x/distribution/types/validator_info.go b/x/distribution/types/validator_info.go index 4a17e22ae..e32839046 100644 --- a/x/distribution/types/validator_info.go +++ b/x/distribution/types/validator_info.go @@ -1,6 +1,8 @@ package types -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) // distribution info for a particular validator type ValidatorDistInfo struct { @@ -13,6 +15,16 @@ type ValidatorDistInfo struct { DelAccum TotalAccum `json:"del_accum"` // total proposer pool accumulation factor held by delegators } +func NewValidatorDistInfo(operatorAddr sdk.ValAddress, currentHeight int64) ValidatorDistInfo { + return ValidatorDistInfo{ + OperatorAddr: operatorAddr, + FeePoolWithdrawalHeight: currentHeight, + Pool: DecCoins{}, + PoolCommission: DecCoins{}, + DelAccum: NewTotalAccum(currentHeight), + } +} + // update total delegator accumululation func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares sdk.Dec) ValidatorDistInfo { vi.DelAccum = vi.DelAccum.Update(height, totalDelShares) @@ -23,7 +35,7 @@ func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares sdk func (vi ValidatorDistInfo) TakeFeePoolRewards(fp FeePool, height int64, totalBonded, vdTokens, commissionRate sdk.Dec) (ValidatorDistInfo, FeePool) { - fp.UpdateTotalValAccum(height, totalBonded) + fp = fp.UpdateTotalValAccum(height, totalBonded) if fp.ValAccum.Accum.IsZero() { return vi, fp @@ -33,11 +45,11 @@ func (vi ValidatorDistInfo) TakeFeePoolRewards(fp FeePool, height int64, totalBo blocks := height - vi.FeePoolWithdrawalHeight vi.FeePoolWithdrawalHeight = height accum := sdk.NewDec(blocks).Mul(vdTokens) - withdrawalTokens := fp.Pool.Mul(accum.Quo(fp.ValAccum.Accum)) - remainingTokens := fp.Pool.Mul(sdk.OneDec().Sub(accum.Quo(fp.ValAccum.Accum))) + withdrawalTokens := fp.Pool.MulDec(accum).QuoDec(fp.ValAccum.Accum) + remainingTokens := fp.Pool.Minus(withdrawalTokens) - commission := withdrawalTokens.Mul(commissionRate) - afterCommission := withdrawalTokens.Mul(sdk.OneDec().Sub(commissionRate)) + commission := withdrawalTokens.MulDec(commissionRate) + afterCommission := withdrawalTokens.MulDec(sdk.OneDec().Sub(commissionRate)) fp.ValAccum.Accum = fp.ValAccum.Accum.Sub(accum) fp.Pool = remainingTokens diff --git a/x/distribution/types/validator_info_test.go b/x/distribution/types/validator_info_test.go index a18464be1..44c5e5daa 100644 --- a/x/distribution/types/validator_info_test.go +++ b/x/distribution/types/validator_info_test.go @@ -1,9 +1,85 @@ package types -import "testing" +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) func TestTakeFeePoolRewards(t *testing.T) { + + // initialize + height := int64(0) + fp := InitialFeePool() + vi1 := NewValidatorDistInfo(valAddr1, height) + vi2 := NewValidatorDistInfo(valAddr2, height) + vi3 := NewValidatorDistInfo(valAddr3, height) + commissionRate1 := sdk.NewDecWithPrec(2, 2) + commissionRate2 := sdk.NewDecWithPrec(3, 2) + commissionRate3 := sdk.NewDecWithPrec(4, 2) + validatorTokens1 := sdk.NewDec(10) + validatorTokens2 := sdk.NewDec(40) + validatorTokens3 := sdk.NewDec(50) + totalBondedTokens := validatorTokens1.Add(validatorTokens2).Add(validatorTokens3) + + // simulate adding some stake for inflation + height = 10 + fp.Pool = DecCoins{DecCoin{"stake", sdk.NewDec(1000)}} + + vi1, fp = vi1.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens1, commissionRate1) + require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.Pool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.PoolCommission[0].Amount)) + + vi2, fp = vi2.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens2, commissionRate2) + require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(400-12), vi2.Pool[0].Amount)) + assert.True(sdk.DecEq(t, vi2.PoolCommission[0].Amount, sdk.NewDec(12))) + + // add more blocks and inflation + height = 20 + fp.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000)) + + vi3, fp = vi3.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens3, commissionRate3) + require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(1000-40), vi3.Pool[0].Amount)) + assert.True(sdk.DecEq(t, vi3.PoolCommission[0].Amount, sdk.NewDec(40))) } -func WithdrawCommission(t *testing.T) { +func TestWithdrawCommission(t *testing.T) { + + // initialize + height := int64(0) + fp := InitialFeePool() + vi1 := NewValidatorDistInfo(valAddr1, height) + commissionRate1 := sdk.NewDecWithPrec(2, 2) + validatorTokens1 := sdk.NewDec(10) + totalBondedTokens := validatorTokens1.Add(sdk.NewDec(90)) // validator-1 is 10% of total power + + // simulate adding some stake for inflation + height = 10 + fp.Pool = DecCoins{DecCoin{"stake", sdk.NewDec(1000)}} + + // for a more fun staring condition, have an non-withdraw update + vi1, fp = vi1.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens1, commissionRate1) + require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.Pool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.PoolCommission[0].Amount)) + + // add more blocks and inflation + height = 20 + fp.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000)) + + vi1, fp, commissionRecv := vi1.WithdrawCommission(fp, height, totalBondedTokens, validatorTokens1, commissionRate1) + require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.Pool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(200-4), vi1.Pool[0].Amount)) + assert.Zero(t, len(vi1.PoolCommission)) + assert.True(sdk.DecEq(t, sdk.NewDec(4), commissionRecv[0].Amount)) }