Merge branch 'master' into cwgoes/a-random-walk-down-proof-of-stake

This commit is contained in:
Christopher Goes
2018-07-14 02:09:41 +02:00
119 changed files with 8111 additions and 5205 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk
mapp.InitChainer(ctx, req)
stakeGenesis := stake.DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stakeGenesis.Pool.LooseTokens = sdk.NewRat(100000)
err := stake.InitGenesis(ctx, stakeKeeper, stakeGenesis)
if err != nil {
+3 -3
View File
@@ -54,7 +54,7 @@ func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
stakeGenesis := stake.DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stakeGenesis.Pool.LooseTokens = sdk.NewRat(100000)
err := stake.InitGenesis(ctx, keeper, stakeGenesis)
if err != nil {
panic(err)
@@ -101,8 +101,8 @@ func TestSlashingMsgs(t *testing.T) {
validator := checkValidator(t, mapp, stakeKeeper, addr1, true)
require.Equal(t, addr1, validator.Owner)
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
require.Equal(t, sdk.Bonded, validator.Status)
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.BondedTokens()))
unrevokeMsg := MsgUnrevoke{ValidatorAddr: sdk.AccAddress(validator.PubKey.Address())}
// no signing info yet
+3 -3
View File
@@ -100,7 +100,7 @@ func TestHandleAbsentValidator(t *testing.T) {
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(amtInt), pool.BondedTokens)
require.Equal(t, int64(amtInt), pool.BondedTokens.RoundInt64())
// 501st block missed
ctx = ctx.WithBlockHeight(height)
@@ -129,7 +129,7 @@ func TestHandleAbsentValidator(t *testing.T) {
// validator should have been slashed
pool = sk.GetPool(ctx)
require.Equal(t, int64(amtInt-1), pool.BondedTokens)
require.Equal(t, int64(amtInt-1), pool.BondedTokens.RoundInt64())
// validator start height should have been changed
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address()))
@@ -194,5 +194,5 @@ func TestHandleNewValidator(t *testing.T) {
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(100), pool.BondedTokens)
require.Equal(t, int64(100), pool.BondedTokens.RoundInt64())
}
+3 -1
View File
@@ -63,7 +63,9 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep
ck := bank.NewKeeper(accountMapper)
sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace)
genesis := stake.DefaultGenesisState()
genesis.Pool.LooseTokens = initCoins.MulRaw(int64(len(addrs))).Int64()
genesis.Pool.LooseTokens = sdk.NewRat(initCoins.MulRaw(int64(len(addrs))).Int64())
err = stake.InitGenesis(ctx, sk, genesis)
require.Nil(t, err)
+5 -5
View File
@@ -63,7 +63,7 @@ func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer {
mapp.InitChainer(ctx, req)
stakeGenesis := DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stakeGenesis.Pool.LooseTokens = sdk.NewRat(100000)
err := InitGenesis(ctx, keeper, stakeGenesis)
if err != nil {
@@ -135,8 +135,8 @@ func TestStakeMsgs(t *testing.T) {
validator := checkValidator(t, mApp, keeper, addr1, true)
require.Equal(t, addr1, validator.Owner)
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
require.Equal(t, sdk.Bonded, validator.Status)
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.BondedTokens()))
// addr1 create validator on behalf of addr2
createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf(addr1, addr2, priv2.PubKey(), bondCoin, description)
@@ -147,8 +147,8 @@ func TestStakeMsgs(t *testing.T) {
validator = checkValidator(t, mApp, keeper, addr2, true)
require.Equal(t, addr2, validator.Owner)
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
require.Equal(t, sdk.Bonded, validator.Status)
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
// check the bond that should have been created as well
checkDelegation(t, mApp, keeper, addr1, addr1, true, sdk.NewRat(10))
+2 -53
View File
@@ -215,57 +215,6 @@ func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc {
}
}
// TODO move exist next to validator struct for maintainability
type StakeValidatorOutput struct {
Owner sdk.AccAddress `json:"owner"` // in bech32
PubKey string `json:"pub_key"` // in bech32
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
PoolShares stake.PoolShares `json:"pool_shares"` // total shares for tokens held in the pool
DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators
Description stake.Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change
ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer
Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators
CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission
CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools
}
func bech32StakeValidatorOutput(validator stake.Validator) (StakeValidatorOutput, error) {
bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey)
if err != nil {
return StakeValidatorOutput{}, err
}
return StakeValidatorOutput{
Owner: validator.Owner,
PubKey: bechValPubkey,
Revoked: validator.Revoked,
PoolShares: validator.PoolShares,
DelegatorShares: validator.DelegatorShares,
Description: validator.Description,
BondHeight: validator.BondHeight,
BondIntraTxCounter: validator.BondIntraTxCounter,
ProposerRewardPool: validator.ProposerRewardPool,
Commission: validator.Commission,
CommissionMax: validator.CommissionMax,
CommissionChangeRate: validator.CommissionChangeRate,
CommissionChangeToday: validator.CommissionChangeToday,
PrevBondedShares: validator.PrevBondedShares,
}, nil
}
// TODO bech32
// http request handler to query list of validators
func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc {
@@ -284,7 +233,7 @@ func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF
}
// parse out the validators
validators := make([]StakeValidatorOutput, len(kvs))
validators := make([]types.BechValidator, len(kvs))
for i, kv := range kvs {
addr := kv.Key[1:]
@@ -295,7 +244,7 @@ func validatorsHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF
return
}
bech32Validator, err := bech32StakeValidatorOutput(validator)
bech32Validator, err := validator.Bech32Validator()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
+2 -2
View File
@@ -20,7 +20,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error
for _, validator := range data.Validators {
keeper.SetValidator(ctx, validator)
if validator.PoolShares.Amount.IsZero() {
if validator.Tokens.IsZero() {
return errors.Errorf("genesis validator cannot have zero pool shares, validator: %v", validator)
}
if validator.DelegatorShares.IsZero() {
@@ -31,7 +31,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) error
keeper.SetValidatorByPubKeyIndex(ctx, validator)
keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool)
if validator.Status() == sdk.Bonded {
if validator.Status == sdk.Bonded {
keeper.SetValidatorBondedIndex(ctx, validator)
}
}
+2 -3
View File
@@ -14,8 +14,7 @@ func TestInitGenesis(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
pool := keeper.GetPool(ctx)
pool.UnbondedTokens = 1
pool.UnbondedShares = sdk.OneRat()
pool.LooseTokens = sdk.OneRat()
params := keeper.GetParams(ctx)
var delegations []Delegation
@@ -28,7 +27,7 @@ func TestInitGenesis(t *testing.T) {
err := InitGenesis(ctx, keeper, genesisState)
require.Error(t, err)
validators[0].PoolShares.Amount = sdk.OneRat()
validators[0].Tokens = sdk.OneRat()
validators[0].DelegatorShares = sdk.OneRat()
genesisState = types.NewGenesisState(pool, params, validators, delegations)
+2 -1
View File
@@ -35,12 +35,13 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
// Called every block, process inflation, update validator set
func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Validator) {
pool := k.GetPool(ctx)
params := k.GetParams(ctx)
// Process types.Validator Provisions
blockTime := ctx.BlockHeader().Time
if pool.InflationLastTime+blockTime >= 3600 {
pool.InflationLastTime = blockTime
pool = k.ProcessProvisions(ctx)
pool = pool.ProcessProvisions(params)
}
// save the params
+23 -24
View File
@@ -84,8 +84,8 @@ func TestValidatorByPowerIndex(t *testing.T) {
keeper.Revoke(ctx, keep.PKs[0])
validator, found = keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded
require.Equal(t, int64(500000), validator.PoolShares.Amount.RoundInt64()) // ensure is unbonded
require.Equal(t, sdk.Unbonded, validator.Status) // ensure is unbonded
require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure is unbonded
// the old power record should have been deleted as the power changed
require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power))
@@ -98,8 +98,9 @@ func TestValidatorByPowerIndex(t *testing.T) {
require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2))
// inflate a bunch
for i := 0; i < 20000; i++ {
pool = keeper.ProcessProvisions(ctx)
params := keeper.GetParams(ctx)
for i := 0; i < 200; i++ {
pool = pool.ProcessProvisions(params)
keeper.SetPool(ctx, pool)
}
@@ -133,10 +134,10 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) {
validator, found := keeper.GetValidator(ctx, addr1)
require.True(t, found)
assert.Equal(t, sdk.Bonded, validator.Status())
assert.Equal(t, sdk.Bonded, validator.Status)
assert.Equal(t, addr1, validator.Owner)
assert.Equal(t, pk1, validator.PubKey)
assert.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded())
assert.Equal(t, sdk.NewRat(10), validator.BondedTokens())
assert.Equal(t, sdk.NewRat(10), validator.DelegatorShares)
assert.Equal(t, Description{}, validator.Description)
@@ -157,11 +158,11 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) {
validator, found = keeper.GetValidator(ctx, addr2)
require.True(t, found)
assert.Equal(t, sdk.Bonded, validator.Status())
assert.Equal(t, sdk.Bonded, validator.Status)
assert.Equal(t, addr2, validator.Owner)
assert.Equal(t, pk2, validator.PubKey)
assert.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded())
assert.Equal(t, sdk.NewRat(10), validator.DelegatorShares)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
assert.Equal(t, Description{}, validator.Description)
}
@@ -177,12 +178,12 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
require.Equal(t, validatorAddr, validator.Owner)
require.Equal(t, pk, validator.PubKey)
require.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded())
require.Equal(t, sdk.NewRat(10), validator.DelegatorShares)
require.Equal(t, Description{}, validator.Description)
assert.Equal(t, sdk.Bonded, validator.Status)
assert.Equal(t, validatorAddr, validator.Owner)
assert.Equal(t, pk, validator.PubKey)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
assert.Equal(t, Description{}, validator.Description)
// one validator cannot be created twice even from different delegator
msgCreateValidatorOnBehalfOf.DelegatorAddr = keep.Addrs[2]
@@ -206,9 +207,9 @@ func TestIncrementsMsgDelegate(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
require.Equal(t, sdk.Bonded, validator.Status)
require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64())
require.Equal(t, bondAmount, validator.PoolShares.Bonded().RoundInt64(), "validator: %v", validator)
require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64(), "validator: %v", validator)
_, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
require.False(t, found)
@@ -218,10 +219,9 @@ func TestIncrementsMsgDelegate(t *testing.T) {
require.Equal(t, bondAmount, bond.Shares.RoundInt64())
pool := keeper.GetPool(ctx)
exRate := validator.DelegatorShareExRate(pool)
exRate := validator.DelegatorShareExRate()
require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v", exRate)
require.Equal(t, bondAmount, pool.BondedShares.RoundInt64())
require.Equal(t, bondAmount, pool.BondedTokens)
require.Equal(t, bondAmount, pool.BondedTokens.RoundInt64())
// just send the same msgbond multiple times
msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount)
@@ -238,8 +238,7 @@ func TestIncrementsMsgDelegate(t *testing.T) {
bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
require.True(t, found)
pool := keeper.GetPool(ctx)
exRate := validator.DelegatorShareExRate(pool)
exRate := validator.DelegatorShareExRate()
require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v, i = %v", exRate, i)
expBond := int64(i+1) * bondAmount
@@ -291,7 +290,7 @@ func TestIncrementsMsgUnbond(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, initBond*2, validator.DelegatorShares.RoundInt64())
require.Equal(t, initBond*2, validator.PoolShares.Bonded().RoundInt64())
require.Equal(t, initBond*2, validator.BondedTokens().RoundInt64())
// just send the same msgUnbond multiple times
// TODO use decimals here
@@ -674,7 +673,7 @@ func TestUnbondingWhenExcessValidators(t *testing.T) {
require.Equal(t, 2, len(vals), "vals %v", vals)
val1, found := keeper.GetValidator(ctx, validatorAddr1)
require.True(t, found)
require.Equal(t, sdk.Bonded, val1.Status(), "%v", val1)
require.Equal(t, sdk.Bonded, val1.Status, "%v", val1)
}
func TestJoiningAsCliffValidator(t *testing.T) {
+3 -3
View File
@@ -238,7 +238,7 @@ func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.AccAddress, bondAmt
// unbond the the delegation return
func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress,
shares sdk.Rat) (amount int64, err sdk.Error) {
shares sdk.Rat) (amount sdk.Rat, err sdk.Error) {
// check if delegation has any shares in it unbond
delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr)
@@ -306,7 +306,7 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk
// create the unbonding delegation
params := k.GetParams(ctx)
minTime := ctx.BlockHeader().Time + params.UnbondingTime
balance := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)}
balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()}
ubd := types.UnbondingDelegation{
DelegatorAddr: delegatorAddr,
@@ -356,7 +356,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd
}
params := k.GetParams(ctx)
returnCoin := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)}
returnCoin := sdk.Coin{params.BondDenom, returnAmount.RoundInt()}
dstValidator, found := k.GetValidator(ctx, validatorDstAddr)
if !found {
return types.ErrBadRedelegationDst(k.Codespace())
+8 -8
View File
@@ -141,7 +141,7 @@ func TestUnbondingDelegation(t *testing.T) {
func TestUnbondDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = 10
pool.LooseTokens = sdk.NewRat(10)
//create a validator and a delegator to that validator
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
@@ -151,8 +151,8 @@ func TestUnbondDelegation(t *testing.T) {
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
require.Equal(t, int64(10), pool.BondedTokens)
require.Equal(t, int64(10), validator.PoolShares.Bonded().RoundInt64())
require.Equal(t, int64(10), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(10), validator.BondedTokens().RoundInt64())
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
@@ -162,10 +162,10 @@ func TestUnbondDelegation(t *testing.T) {
keeper.SetDelegation(ctx, delegation)
var err error
var amount int64
var amount sdk.Rat
amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewRat(6))
require.NoError(t, err)
require.Equal(t, int64(6), amount) // shares to be added to an unbonding delegation / redelegation
require.Equal(t, int64(6), amount.RoundInt64()) // shares to be added to an unbonding delegation / redelegation
delegation, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
@@ -174,9 +174,9 @@ func TestUnbondDelegation(t *testing.T) {
pool = keeper.GetPool(ctx)
require.Equal(t, int64(4), delegation.Shares.RoundInt64())
require.Equal(t, int64(4), validator.PoolShares.Bonded().RoundInt64())
require.Equal(t, int64(6), pool.LooseTokens, "%v", pool)
require.Equal(t, int64(4), pool.BondedTokens)
require.Equal(t, int64(4), validator.BondedTokens().RoundInt64())
require.Equal(t, int64(6), pool.LooseTokens.RoundInt64(), "%v", pool)
require.Equal(t, int64(4), pool.BondedTokens.RoundInt64())
}
// Make sure that that the retrieving the delegations doesn't affect the state
-53
View File
@@ -1,53 +0,0 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
const (
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
precision = 100000000000 // increased to this precision for accuracy
)
var hrsPerYrRat = sdk.NewRat(hrsPerYr)
// process provisions for an hour period
func (k Keeper) ProcessProvisions(ctx sdk.Context) types.Pool {
pool := k.GetPool(ctx)
pool.Inflation = k.NextInflation(ctx)
provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).RoundInt64()
// TODO add to the fees provisions
pool.LooseTokens += provisions
return pool
}
// get the next inflation rate for the hour
func (k Keeper) NextInflation(ctx sdk.Context) (inflation sdk.Rat) {
params := k.GetParams(ctx)
pool := k.GetPool(ctx)
// The target annual inflation rate is recalculated for each previsions cycle. The
// inflation is also subject to a rate change (positive or negative) depending on
// the distance from the desired ratio (67%). The maximum rate change possible is
// defined to be 13% per year, however the annual inflation is capped as between
// 7% and 20%.
// (1 - bondedRatio/GoalBonded) * InflationRateChange
inflationRateChangePerYear := sdk.OneRat().Sub(pool.BondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange)
inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat)
// increase the new annual inflation for this next cycle
inflation = pool.Inflation.Add(inflationRateChange)
if inflation.GT(params.InflationMax) {
inflation = params.InflationMax
}
if inflation.LT(params.InflationMin) {
inflation = params.InflationMin
}
return inflation.Round(precision)
}
-378
View File
@@ -1,378 +0,0 @@
package keeper
import (
"math/rand"
"strconv"
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
//changing the int in NewSource will allow you to test different, deterministic, sets of operations
var r = rand.New(rand.NewSource(6595))
func TestGetInflation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
params := keeper.GetParams(ctx)
hrsPerYrRat := sdk.NewRat(hrsPerYr)
// Governing Mechanism:
// BondedRatio = BondedTokens / TotalSupply
// inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange
tests := []struct {
name string
setBondedTokens, setLooseTokens int64
setInflation, expectedChange sdk.Rat
}{
// with 0% bonded atom supply the inflation should increase by InflationRateChange
{"test 1", 0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)},
// 100% bonded, starting at 20% inflation and being reduced
// (1 - (1/0.67))*(0.13/8667)
{"test 2", 1, 0, sdk.NewRat(20, 100),
sdk.OneRat().Sub(sdk.OneRat().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
// 50% bonded, starting at 10% inflation and being increased
{"test 3", 1, 1, sdk.NewRat(10, 100),
sdk.OneRat().Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
// test 7% minimum stop (testing with 100% bonded)
{"test 4", 1, 0, sdk.NewRat(7, 100), sdk.ZeroRat()},
{"test 5", 1, 0, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)},
// test 20% maximum stop (testing with 0% bonded)
{"test 6", 0, 0, sdk.NewRat(20, 100), sdk.ZeroRat()},
{"test 7", 0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)},
// perfect balance shouldn't change inflation
{"test 8", 67, 33, sdk.NewRat(15, 100), sdk.ZeroRat()},
}
for _, tc := range tests {
pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens
pool.Inflation = tc.setInflation
keeper.SetPool(ctx, pool)
inflation := keeper.NextInflation(ctx)
diffInflation := inflation.Sub(tc.setInflation)
require.True(t, diffInflation.Equal(tc.expectedChange),
"Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange)
}
}
// Test that provisions are correctly added to the pool and validators each hour for 1 year
func TestProcessProvisions(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 250000000
initialUnbondedTokens int64 = 300000000
cumulativeExpProvs int64
validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 2
)
pool.LooseTokens = initialTotalTokens
// create some validators some bonded, some unbonded
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens)
// process the provisions for a year
for hr := 0; hr < 8766; hr++ {
pool := keeper.GetPool(ctx)
_, expProvisions, _ := updateProvisions(t, keeper, pool, ctx, hr)
cumulativeExpProvs = cumulativeExpProvs + expProvisions
}
//get the pool and do the final value checks from checkFinalPoolValues
pool = keeper.GetPool(ctx)
checkFinalPoolValues(t, pool, initialTotalTokens, cumulativeExpProvs)
}
// Tests that the hourly rate of change of inflation will be positive, negative, or zero, depending on bonded ratio and inflation rate
// Cycles through the whole gambit of inflation possibilities, starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years)
func TestHourlyInflationRateOfChange(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 150000000
initialUnbondedTokens int64 = 400000000
cumulativeExpProvs int64
validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 1
)
pool.LooseTokens = initialTotalTokens
// create some validators some bonded, some unbonded
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens)
// ~11.4 years to go from 7%, up to 20%, back down to 7%
for hr := 0; hr < 100000; hr++ {
pool := keeper.GetPool(ctx)
previousInflation := pool.Inflation
updatedInflation, expProvisions, pool := updateProvisions(t, keeper, pool, ctx, hr)
cumulativeExpProvs = cumulativeExpProvs + expProvisions
msg := strconv.Itoa(hr)
checkInflation(t, pool, previousInflation, updatedInflation, msg)
}
// Final check that the pool equals initial values + cumulative provisions and adjustments we recorded
pool = keeper.GetPool(ctx)
checkFinalPoolValues(t, pool, initialTotalTokens, cumulativeExpProvs)
}
//Test that a large unbonding will significantly lower the bonded ratio
func TestLargeUnbond(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
initialTotalTokens int64 = 1200000000
initialBondedTokens int64 = 900000000
initialUnbondedTokens int64 = 300000000
val0UnbondedTokens int64
bondedShares = sdk.NewRat(900000000, 1)
unbondedShares = sdk.NewRat(300000000, 1)
bondSharesVal0 = sdk.NewRat(300000000, 1)
validatorTokens = []int64{300000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 7
)
pool.LooseTokens = initialTotalTokens
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens)
pool = keeper.GetPool(ctx)
validator, found := keeper.GetValidator(ctx, Addrs[0])
require.True(t, found)
// initialBondedRatio that we can use to compare to the new values after the unbond
initialBondedRatio := pool.BondedRatio()
// validator[0] will be unbonded, bringing us from 75% bonded ratio to ~50% (unbonding 300,000,000)
pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator)
keeper.SetPool(ctx, pool)
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
_, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0)
bondedShares = bondedShares.Sub(bondSharesVal0)
val0UnbondedTokens = pool.UnbondedShareExRate().Mul(validator.PoolShares.Unbonded()).RoundInt64()
unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.UnbondedShareExRate()))
// unbonded shares should increase
require.True(t, unbondedShares.GT(sdk.NewRat(300000000, 1)))
// Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 50% < 75)
require.True(t, (pool.BondedRatio().LT(initialBondedRatio)))
// Final check that the pool equals initial values + provisions and adjustments we recorded
pool = keeper.GetPool(ctx)
checkFinalPoolValues(t, pool, initialTotalTokens, expProvisionsAfter)
}
//Test that a large bonding will significantly increase the bonded ratio
func TestLargeBond(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
initialTotalTokens int64 = 1600000000
initialBondedTokens int64 = 400000000
initialUnbondedTokens int64 = 1200000000
unbondedShares = sdk.NewRat(1200000000, 1)
unbondedSharesVal9 = sdk.NewRat(400000000, 1)
validatorTokens = []int64{400000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 400000000}
bondedValidators uint16 = 1
)
pool.LooseTokens = initialTotalTokens
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens)
pool = keeper.GetPool(ctx)
validator, found := keeper.GetValidator(ctx, Addrs[9])
require.True(t, found)
// initialBondedRatio that we can use to compare to the new values after the unbond
initialBondedRatio := pool.BondedRatio()
params := types.DefaultParams()
params.MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond
keeper.SetParams(ctx, params)
// validator[9] will be bonded, bringing us from 25% to ~50% (bonding 400,000,000 tokens)
pool, _, _, _ = types.OpBondOrUnbond(r, pool, validator)
keeper.SetPool(ctx, pool)
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
_, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0)
unbondedShares = unbondedShares.Sub(unbondedSharesVal9)
// unbonded shares should decrease
require.True(t, unbondedShares.LT(sdk.NewRat(1200000000, 1)))
// Ensure that new bonded ratio is greater than old bonded ratio (i.e. 50% > 25%)
require.True(t, (pool.BondedRatio().GT(initialBondedRatio)))
// Final check that the pool equals initial values + provisions and adjustments we recorded
pool = keeper.GetPool(ctx)
checkFinalPoolValues(t, pool, initialTotalTokens, expProvisionsAfter)
}
// Tests that inflation increases or decreases as expected when we do a random operation on 20 different validators
func TestInflationWithRandomOperations(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
params := types.DefaultParams()
keeper.SetParams(ctx, params)
numValidators := 20
// start off by randomly setting up 20 validators
pool, validators := types.RandomSetup(r, numValidators)
require.Equal(t, numValidators, len(validators))
for i := 0; i < len(validators); i++ {
keeper.SetValidator(ctx, validators[i])
}
keeper.SetPool(ctx, pool)
// Used to rotate validators so each random operation is applied to a different validator
validatorCounter := 0
// Loop through 20 random operations, and check the inflation after each operation
for i := 0; i < numValidators; i++ {
pool := keeper.GetPool(ctx)
// Get inflation before RandomOperation, for comparison later
previousInflation := pool.Inflation
// Perform the random operation, and record how validators are modified
poolMod, validatorMod, tokens, msg := types.RandomOperation(r)(r, pool, validators[validatorCounter])
validatorsMod := make([]types.Validator, len(validators))
copy(validatorsMod[:], validators[:])
require.Equal(t, numValidators, len(validators), "i %v", validatorCounter)
require.Equal(t, numValidators, len(validatorsMod), "i %v", validatorCounter)
validatorsMod[validatorCounter] = validatorMod
types.AssertInvariants(t, msg,
pool, validators,
poolMod, validatorsMod, tokens)
// set pool and validators after the random operation
pool = poolMod
keeper.SetPool(ctx, pool)
validators = validatorsMod
// Must set inflation here manually, as opposed to most other tests in this suite, where we call keeper.processProvisions(), which updates pool.Inflation
updatedInflation := keeper.NextInflation(ctx)
pool.Inflation = updatedInflation
keeper.SetPool(ctx, pool)
// Ensure inflation changes as expected when random operations are applied.
checkInflation(t, pool, previousInflation, updatedInflation, msg)
validatorCounter++
}
}
//_________________________________________________________________________________________
////////////////////////////////HELPER FUNCTIONS BELOW/////////////////////////////////////
// Final check on the global pool values for what the total tokens accumulated from each hour of provisions
func checkFinalPoolValues(t *testing.T, pool types.Pool, initialTotalTokens, cumulativeExpProvs int64) {
calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs
require.Equal(t, calculatedTotalTokens, pool.TokenSupply())
}
// Processes provisions are added to the pool correctly every hour
// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests
func updateProvisions(t *testing.T, keeper Keeper, pool types.Pool, ctx sdk.Context, hr int) (sdk.Rat, int64, types.Pool) {
expInflation := keeper.NextInflation(ctx)
expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).RoundInt64()
startTotalSupply := pool.TokenSupply()
pool = keeper.ProcessProvisions(ctx)
keeper.SetPool(ctx, pool)
//check provisions were added to pool
require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply())
return expInflation, expProvisions, pool
}
// Deterministic setup of validators and pool
// Allows you to decide how many validators to setup
// Allows you to pick which validators are bonded by adjusting the MaxValidators of params
func setupTestValidators(pool types.Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64,
maxValidators uint16) ([]types.Validator, Keeper, types.Pool) {
params := types.DefaultParams()
params.MaxValidators = maxValidators
keeper.SetParams(ctx, params)
numValidators := len(validatorTokens)
validators := make([]types.Validator, numValidators)
for i := 0; i < numValidators; i++ {
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, validatorTokens[i])
keeper.SetPool(ctx, pool)
validators[i] = keeper.UpdateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order
pool = keeper.GetPool(ctx)
}
return validators, keeper, pool
}
// Checks that the deterministic validator setup you wanted matches the values in the pool
func checkValidatorSetup(t *testing.T, pool types.Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) {
require.Equal(t, initialTotalTokens, pool.TokenSupply(), "%v", pool)
require.Equal(t, initialBondedTokens, pool.BondedTokens, "%v", pool)
require.Equal(t, initialUnbondedTokens, pool.UnbondedTokens, "%v", pool)
// test initial bonded ratio
require.True(t, pool.BondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.BondedRatio())
// test the value of validator shares
require.True(t, pool.BondedShareExRate().Equal(sdk.OneRat()), "%v", pool.BondedShareExRate())
}
// Checks that The inflation will correctly increase or decrease after an update to the pool
// nolint: gocyclo
func checkInflation(t *testing.T, pool types.Pool, previousInflation, updatedInflation sdk.Rat, msg string) {
inflationChange := updatedInflation.Sub(previousInflation)
switch {
//BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation
case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)):
require.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg)
//BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio
case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)):
if previousInflation.Equal(sdk.NewRat(20, 100)) {
require.Equal(t, true, inflationChange.IsZero(), msg)
//This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%)
} else {
require.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg)
}
//ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7%
case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)):
require.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg)
//ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%.
case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)):
if previousInflation.Equal(sdk.NewRat(7, 100)) {
require.Equal(t, true, inflationChange.IsZero(), msg)
//This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%)
} else {
require.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg)
}
}
}
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
@@ -32,7 +33,7 @@ func TestPool(t *testing.T) {
require.True(t, expPool.Equal(resPool))
//modify a params, save, and retrieve
expPool.BondedTokens = 777
expPool.BondedTokens = sdk.NewRat(777)
keeper.SetPool(ctx, expPool)
resPool = keeper.GetPool(ctx)
require.True(t, expPool.Equal(resPool))
+2 -2
View File
@@ -69,8 +69,8 @@ func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []
// NOTE the larger values are of higher value
func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte {
power := validator.EquivalentBondedShares(pool)
powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first)
potentialPower := validator.Tokens
powerBytes := []byte(potentialPower.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first)
revokedBytes := make([]byte, 1)
if validator.Revoked {
+1 -1
View File
@@ -60,7 +60,7 @@ func (k Keeper) Validator(ctx sdk.Context, address sdk.AccAddress) sdk.Validator
// total power from the bond
func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat {
pool := k.GetPool(ctx)
return pool.BondedShares
return pool.BondedTokens
}
//__________________________________________________________________________
+37 -23
View File
@@ -28,7 +28,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
}
// Amount of slashing = slash slashFactor * power at time of infraction
slashAmount := sdk.NewRat(power).Mul(slashFactor).RoundInt()
slashAmount := sdk.NewRat(power).Mul(slashFactor)
// ref https://github.com/cosmos/cosmos-sdk/issues/1348
// ref https://github.com/cosmos/cosmos-sdk/issues/1471
@@ -38,7 +38,9 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
// NOTE: Correctness dependent on invariant that unbonding delegations / redelegations must also have been completely
// slashed in this case - which we don't explicitly check, but should be true.
// Log the slash attempt for future reference (maybe we should tag it too)
logger.Error(fmt.Sprintf("WARNING: Ignored attempt to slash a nonexistent validator with address %s, we recommend you investigate immediately", pubkey.Address()))
logger.Error(fmt.Sprintf(
"WARNING: Ignored attempt to slash a nonexistent validator with address %s, we recommend you investigate immediately",
pubkey.Address()))
return
}
ownerAddress := validator.GetOwner()
@@ -50,14 +52,21 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
switch {
case infractionHeight > ctx.BlockHeight():
// Can't slash infractions in the future
panic(fmt.Sprintf("impossible attempt to slash future infraction at height %d but we are at height %d", infractionHeight, ctx.BlockHeight()))
panic(fmt.Sprintf(
"impossible attempt to slash future infraction at height %d but we are at height %d",
infractionHeight, ctx.BlockHeight()))
case infractionHeight == ctx.BlockHeight():
// Special-case slash at current height for efficiency - we don't need to look through unbonding delegations or redelegations
logger.Info(fmt.Sprintf("Slashing at current height %d, not scanning unbonding delegations & redelegations", infractionHeight))
logger.Info(fmt.Sprintf(
"Slashing at current height %d, not scanning unbonding delegations & redelegations",
infractionHeight))
case infractionHeight < ctx.BlockHeight():
// Iterate through unbonding delegations from slashed validator
unbondingDelegations := k.GetUnbondingDelegationsFromValidator(ctx, ownerAddress)
for _, unbondingDelegation := range unbondingDelegations {
@@ -77,29 +86,30 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
}
remainingSlashAmount = remainingSlashAmount.Sub(amountSlashed)
}
}
// Cannot decrease balance below zero
sharesToRemove := sdk.MinInt(remainingSlashAmount, validator.PoolShares.Amount.RoundInt())
tokensToBurn := sdk.MinRat(remainingSlashAmount, validator.Tokens)
// Get the current pool
pool := k.GetPool(ctx)
// remove shares from the validator
validator, pool, burned := validator.RemovePoolShares(pool, sdk.NewRatFromInt(sharesToRemove))
// remove tokens from the validator
validator, pool = validator.RemoveTokens(pool, tokensToBurn)
// burn tokens
pool.LooseTokens -= burned
pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn)
// update the pool
k.SetPool(ctx, pool)
// update the validator, possibly kicking it out
validator = k.UpdateValidator(ctx, validator)
// remove validator if it has been reduced to zero shares
if validator.PoolShares.Amount.IsZero() {
if validator.Tokens.IsZero() {
k.RemoveValidator(ctx, validator.Owner)
}
// Log that a slash occurred!
logger.Info(fmt.Sprintf("Validator %s slashed by slashFactor %v, removed %v shares and burned %d tokens", pubkey.Address(), slashFactor, sharesToRemove, burned))
logger.Info(fmt.Sprintf(
"Validator %s slashed by slashFactor %v, burned %v tokens",
pubkey.Address(), slashFactor, tokensToBurn))
// TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803
return
@@ -139,28 +149,30 @@ func (k Keeper) setRevoked(ctx sdk.Context, pubkey crypto.PubKey, revoked bool)
// the unbonding delegation had enough stake to slash
// (the amount actually slashed may be less if there's
// insufficient stake remaining)
func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation, infractionHeight int64, slashFactor sdk.Rat) (slashAmount sdk.Int) {
func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation,
infractionHeight int64, slashFactor sdk.Rat) (slashAmount sdk.Rat) {
now := ctx.BlockHeader().Time
// If unbonding started before this height, stake didn't contribute to infraction
if unbondingDelegation.CreationHeight < infractionHeight {
return sdk.ZeroInt()
return sdk.ZeroRat()
}
if unbondingDelegation.MinTime < now {
// Unbonding delegation no longer eligible for slashing, skip it
// TODO Settle and delete it automatically?
return sdk.ZeroInt()
return sdk.ZeroRat()
}
// Calculate slash amount proportional to stake contributing to infraction
slashAmount = sdk.NewRatFromInt(unbondingDelegation.InitialBalance.Amount, sdk.OneInt()).Mul(slashFactor).RoundInt()
slashAmount = sdk.NewRatFromInt(unbondingDelegation.InitialBalance.Amount, sdk.OneInt()).Mul(slashFactor)
// Don't slash more tokens than held
// Possible since the unbonding delegation may already
// have been slashed, and slash amounts are calculated
// according to stake held at time of infraction
unbondingSlashAmount := sdk.MinInt(slashAmount, unbondingDelegation.Balance.Amount)
unbondingSlashAmount := sdk.MinInt(slashAmount.RoundInt(), unbondingDelegation.Balance.Amount)
// Update unbonding delegation if necessary
if !unbondingSlashAmount.IsZero() {
@@ -169,7 +181,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty
pool := k.GetPool(ctx)
// Burn loose tokens
// Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760
pool.LooseTokens -= slashAmount.Int64()
pool.LooseTokens = pool.LooseTokens.Sub(slashAmount)
k.SetPool(ctx, pool)
}
@@ -181,28 +193,30 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty
// the unbonding delegation had enough stake to slash
// (the amount actually slashed may be less if there's
// insufficient stake remaining)
func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, redelegation types.Redelegation, infractionHeight int64, slashFactor sdk.Rat) (slashAmount sdk.Int) {
func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, redelegation types.Redelegation,
infractionHeight int64, slashFactor sdk.Rat) (slashAmount sdk.Rat) {
now := ctx.BlockHeader().Time
// If redelegation started before this height, stake didn't contribute to infraction
if redelegation.CreationHeight < infractionHeight {
return sdk.ZeroInt()
return sdk.ZeroRat()
}
if redelegation.MinTime < now {
// Redelegation no longer eligible for slashing, skip it
// TODO Delete it automatically?
return sdk.ZeroInt()
return sdk.ZeroRat()
}
// Calculate slash amount proportional to stake contributing to infraction
slashAmount = sdk.NewRatFromInt(redelegation.InitialBalance.Amount, sdk.OneInt()).Mul(slashFactor).RoundInt()
slashAmount = sdk.NewRatFromInt(redelegation.InitialBalance.Amount, sdk.OneInt()).Mul(slashFactor)
// Don't slash more tokens than held
// Possible since the redelegation may already
// have been slashed, and slash amounts are calculated
// according to stake held at time of infraction
redelegationSlashAmount := sdk.MinInt(slashAmount, redelegation.Balance.Amount)
redelegationSlashAmount := sdk.MinInt(slashAmount.RoundInt(), redelegation.Balance.Amount)
// Update redelegation if necessary
if !redelegationSlashAmount.IsZero() {
@@ -227,7 +241,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re
}
// Burn loose tokens
pool := k.GetPool(ctx)
pool.LooseTokens -= tokensToBurn
pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn)
k.SetPool(ctx, pool)
}
+29 -23
View File
@@ -18,16 +18,17 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) {
params := keeper.GetParams(ctx)
pool := keeper.GetPool(ctx)
numVals := 3
pool.LooseTokens = amt * int64(numVals)
pool.LooseTokens = sdk.NewRat(amt * int64(numVals))
// add numVals validators
for i := 0; i < numVals; i++ {
validator := types.NewValidator(addrVals[i], PKs[i], types.Description{})
validator, pool, _ = validator.AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validator)
validator = keeper.UpdateValidator(ctx, validator)
keeper.SetValidatorByPubKeyIndex(ctx, validator)
}
pool = keeper.GetPool(ctx)
return ctx, keeper, params
}
@@ -77,20 +78,20 @@ func TestSlashUnbondingDelegation(t *testing.T) {
// unbonding started prior to the infraction height, stake didn't contribute
slashAmount := keeper.slashUnbondingDelegation(ctx, ubd, 1, fraction)
require.Equal(t, int64(0), slashAmount.Int64())
require.Equal(t, int64(0), slashAmount.RoundInt64())
// after the expiration time, no longer eligible for slashing
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(10)})
keeper.SetUnbondingDelegation(ctx, ubd)
slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction)
require.Equal(t, int64(0), slashAmount.Int64())
require.Equal(t, int64(0), slashAmount.RoundInt64())
// test valid slash, before expiration timestamp and to which stake contributed
oldPool := keeper.GetPool(ctx)
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(0)})
keeper.SetUnbondingDelegation(ctx, ubd)
slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction)
require.Equal(t, int64(5), slashAmount.Int64())
require.Equal(t, int64(5), slashAmount.RoundInt64())
ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
// initialbalance unchanged
@@ -98,7 +99,7 @@ func TestSlashUnbondingDelegation(t *testing.T) {
// balance decreased
require.Equal(t, sdk.NewCoin(params.BondDenom, 5), ubd.Balance)
newPool := keeper.GetPool(ctx)
require.Equal(t, int64(5), oldPool.LooseTokens-newPool.LooseTokens)
require.Equal(t, int64(5), oldPool.LooseTokens.Sub(newPool.LooseTokens).RoundInt64())
}
// tests slashRedelegation
@@ -133,7 +134,7 @@ func TestSlashRedelegation(t *testing.T) {
validator, found := keeper.GetValidator(ctx, addrVals[1])
require.True(t, found)
slashAmount := keeper.slashRedelegation(ctx, validator, rd, 1, fraction)
require.Equal(t, int64(0), slashAmount.Int64())
require.Equal(t, int64(0), slashAmount.RoundInt64())
// after the expiration time, no longer eligible for slashing
ctx = ctx.WithBlockHeader(abci.Header{Time: int64(10)})
@@ -141,7 +142,7 @@ func TestSlashRedelegation(t *testing.T) {
validator, found = keeper.GetValidator(ctx, addrVals[1])
require.True(t, found)
slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction)
require.Equal(t, int64(0), slashAmount.Int64())
require.Equal(t, int64(0), slashAmount.RoundInt64())
// test valid slash, before expiration timestamp and to which stake contributed
oldPool := keeper.GetPool(ctx)
@@ -150,7 +151,7 @@ func TestSlashRedelegation(t *testing.T) {
validator, found = keeper.GetValidator(ctx, addrVals[1])
require.True(t, found)
slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction)
require.Equal(t, int64(5), slashAmount.Int64())
require.Equal(t, int64(5), slashAmount.RoundInt64())
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
// initialbalance unchanged
@@ -163,7 +164,7 @@ func TestSlashRedelegation(t *testing.T) {
require.Equal(t, int64(5), del.Shares.RoundInt64())
// pool bonded tokens decreased
newPool := keeper.GetPool(ctx)
require.Equal(t, int64(5), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
}
// tests Slash at a future height (must panic)
@@ -193,7 +194,7 @@ func TestSlashAtCurrentHeight(t *testing.T) {
// power decreased
require.Equal(t, sdk.NewRat(5), validator.GetPower())
// pool bonded shares decreased
require.Equal(t, sdk.NewRat(5).RoundInt64(), oldPool.BondedShares.Sub(newPool.BondedShares).RoundInt64())
require.Equal(t, sdk.NewRat(5).RoundInt64(), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
}
// tests Slash at a previous height with an unbonding delegation
@@ -229,7 +230,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
// read updated pool
newPool := keeper.GetPool(ctx)
// bonded tokens burned
require.Equal(t, int64(3), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
@@ -249,7 +250,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
// read updated pool
newPool = keeper.GetPool(ctx)
// bonded tokens burned again
require.Equal(t, int64(6), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(6), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
@@ -269,7 +270,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
// read updated pool
newPool = keeper.GetPool(ctx)
// bonded tokens burned again
require.Equal(t, int64(9), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(9), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
@@ -289,7 +290,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
// read updated pool
newPool = keeper.GetPool(ctx)
// just 1 bonded token burned again since that's all the validator now has
require.Equal(t, int64(10), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(10), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
// power decreased by 1 again, validator is out of stake
// ergo validator should have been removed from the store
@@ -325,6 +326,11 @@ func TestSlashWithRedelegation(t *testing.T) {
}
keeper.SetDelegation(ctx, del)
// update bonded tokens
pool := keeper.GetPool(ctx)
pool.BondedTokens = pool.BondedTokens.Add(sdk.NewRat(6))
keeper.SetPool(ctx, pool)
// slash validator
ctx = ctx.WithBlockHeight(12)
oldPool := keeper.GetPool(ctx)
@@ -340,7 +346,7 @@ func TestSlashWithRedelegation(t *testing.T) {
// read updated pool
newPool := keeper.GetPool(ctx)
// bonded tokens burned
require.Equal(t, int64(5), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
@@ -354,7 +360,7 @@ func TestSlashWithRedelegation(t *testing.T) {
ctx = ctx.WithBlockHeight(12)
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
keeper.Slash(ctx, pk, 10, 10, sdk.NewRat(3, 4))
require.NotPanics(t, func() { keeper.Slash(ctx, pk, 10, 10, sdk.OneRat()) })
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
@@ -363,8 +369,8 @@ func TestSlashWithRedelegation(t *testing.T) {
require.Equal(t, sdk.NewInt(0), rd.Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// 7 bonded tokens burned
require.Equal(t, int64(12), oldPool.BondedTokens-newPool.BondedTokens)
// seven bonded tokens burned
require.Equal(t, int64(12), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
@@ -385,7 +391,7 @@ func TestSlashWithRedelegation(t *testing.T) {
// read updated pool
newPool = keeper.GetPool(ctx)
// four more bonded tokens burned
require.Equal(t, int64(16), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
// validator decreased to zero power, should have been removed from the store
_, found = keeper.GetValidatorByPubKey(ctx, pk)
@@ -407,7 +413,7 @@ func TestSlashWithRedelegation(t *testing.T) {
// read updated pool
newPool = keeper.GetPool(ctx)
// no more bonded tokens burned
require.Equal(t, int64(16), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
// power still zero, still not in the store
_, found = keeper.GetValidatorByPubKey(ctx, pk)
@@ -469,9 +475,9 @@ func TestSlashBoth(t *testing.T) {
// read updated pool
newPool := keeper.GetPool(ctx)
// loose tokens burned
require.Equal(t, int64(2), oldPool.LooseTokens-newPool.LooseTokens)
require.Equal(t, int64(2), oldPool.LooseTokens.Sub(newPool.LooseTokens).RoundInt64())
// bonded tokens burned
require.Equal(t, int64(3), oldPool.BondedTokens-newPool.BondedTokens)
require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
// read updated validator
validator, found = keeper.GetValidatorByPubKey(ctx, PKs[0])
require.True(t, found)
+1 -1
View File
@@ -118,7 +118,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context
{keeper.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)},
})
require.Nil(t, err)
pool.LooseTokens += initCoins
pool.LooseTokens = pool.LooseTokens.Add(sdk.NewRat(initCoins))
keeper.SetPool(ctx, pool)
}
+20 -20
View File
@@ -149,7 +149,7 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator {
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", address))
}
if validator.Status() == sdk.Bonded {
if validator.Status == sdk.Bonded {
validators[i] = validator
i++
}
@@ -212,7 +212,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
switch {
// if already bonded and power increasing only need to update tendermint
case powerIncreasing && !validator.Revoked &&
(oldFound && oldValidator.Status() == sdk.Bonded):
(oldFound && oldValidator.Status == sdk.Bonded):
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator())
store.Set(GetTendermintUpdatesKey(validator.Owner), bz)
@@ -224,7 +224,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
// if was unbonded and the new power is less than the cliff validator
case cliffPower != nil &&
(oldFound && oldValidator.Status() == sdk.Unbonded) &&
(oldFound && oldValidator.Status == sdk.Unbonded) &&
bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower
// skip to completion
@@ -234,19 +234,19 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
default:
// update the validator set for this validator
// if updated, the validator has changed bonding status
updatedVal, updated := k.UpdateBondedValidators(ctx, validator)
if updated { // updates to validator occurred to be updated
validator = updatedVal
} else {
// if decreased in power but still bonded, update Tendermint validator
// (if updatedVal is set, the validator has changed bonding status)
stillBonded := oldFound && oldValidator.Status() == sdk.Bonded
if stillBonded && oldValidator.PoolShares.Bonded().GT(validator.PoolShares.Bonded()) {
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator())
store.Set(GetTendermintUpdatesKey(validator.Owner), bz)
}
break
}
// if decreased in power but still bonded, update Tendermint validator
if oldFound && oldValidator.BondedTokens().GT(validator.BondedTokens()) {
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator())
store.Set(GetTendermintUpdatesKey(validator.Owner), bz)
}
}
k.SetValidator(ctx, validator)
@@ -254,7 +254,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
}
func (k Keeper) updateForRevoking(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator {
if newValidator.Revoked && oldFound && oldValidator.Status() == sdk.Bonded {
if newValidator.Revoked && oldFound && oldValidator.Status == sdk.Bonded {
newValidator = k.unbondValidator(ctx, newValidator)
// need to also clear the cliff validator spot because the revoke has
@@ -266,7 +266,7 @@ func (k Keeper) updateForRevoking(ctx sdk.Context, oldFound bool, oldValidator,
}
func (k Keeper) getPowerIncreasing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) bool {
if oldFound && oldValidator.PoolShares.Bonded().LT(newValidator.PoolShares.Bonded()) {
if oldFound && oldValidator.BondedTokens().LT(newValidator.BondedTokens()) {
return true
}
return false
@@ -277,7 +277,7 @@ func (k Keeper) bondIncrement(ctx sdk.Context, oldFound bool, oldValidator,
newValidator types.Validator) (height int64, intraTxCounter int16) {
// if already a validator, copy the old block height and counter, else set them
if oldFound && oldValidator.Status() == sdk.Bonded {
if oldFound && oldValidator.Status == sdk.Bonded {
height = oldValidator.BondHeight
intraTxCounter = oldValidator.BondIntraTxCounter
return
@@ -350,14 +350,14 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
// increment bondedValidatorsCount / get the validator to bond
if !validator.Revoked {
if validator.Status() != sdk.Bonded {
if validator.Status != sdk.Bonded {
validatorToBond = validator
newValidatorBonded = true
}
bondedValidatorsCount++
// sanity check
} else if validator.Status() == sdk.Bonded {
} else if validator.Status == sdk.Bonded {
panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr))
}
@@ -444,7 +444,7 @@ func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) {
if !validator.Revoked {
bondedValidatorsCount++
} else {
if validator.Status() == sdk.Bonded {
if validator.Status == sdk.Bonded {
panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr))
}
}
@@ -483,7 +483,7 @@ func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) type
pool := k.GetPool(ctx)
// sanity check
if validator.Status() == sdk.Unbonded {
if validator.Status == sdk.Unbonded {
panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator))
}
@@ -510,7 +510,7 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.
pool := k.GetPool(ctx)
// sanity check
if validator.Status() == sdk.Bonded {
if validator.Status == sdk.Bonded {
panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator))
}
+78 -55
View File
@@ -18,8 +18,8 @@ func TestSetValidator(t *testing.T) {
// test how the validator is set from a purely unbonbed pool
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, _ = validator.AddTokensFromDel(pool, 10)
require.Equal(t, sdk.Unbonded, validator.Status())
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded()))
require.Equal(t, sdk.Unbonded, validator.Status)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validator)
@@ -27,8 +27,8 @@ func TestSetValidator(t *testing.T) {
// after the save the validator should be bonded
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
require.Equal(t, sdk.Bonded, validator.Status)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
// Check each store for being saved
@@ -55,25 +55,20 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) {
pool := keeper.GetPool(ctx)
// create a random pool
pool.LooseTokens = 10000
pool.BondedTokens = 1234
pool.BondedShares = sdk.NewRat(124)
pool.UnbondingTokens = 13934
pool.UnbondingShares = sdk.NewRat(145)
pool.UnbondedTokens = 154
pool.UnbondedShares = sdk.NewRat(1333)
pool.LooseTokens = sdk.NewRat(10000)
pool.BondedTokens = sdk.NewRat(1234)
keeper.SetPool(ctx, pool)
// add a validator
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, delSharesCreated := validator.AddTokensFromDel(pool, 100)
require.Equal(t, sdk.Unbonded, validator.Status())
require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64())
require.Equal(t, sdk.Unbonded, validator.Status)
require.Equal(t, int64(100), validator.Tokens.RoundInt64())
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validator)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64(), "\nvalidator %v\npool %v", validator, pool)
require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool)
pool = keeper.GetPool(ctx)
power := GetValidatorsByPowerIndexKey(validator, pool)
@@ -81,7 +76,7 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) {
// burn half the delegator shares
validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2)))
require.Equal(t, int64(50), burned)
require.Equal(t, int64(50), burned.RoundInt64())
keeper.SetPool(ctx, pool) // update the pool
keeper.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out
require.False(t, keeper.validatorByPowerIndexExists(ctx, power))
@@ -101,12 +96,12 @@ func TestSlashToZeroPowerRemoved(t *testing.T) {
// add a validator
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, _ = validator.AddTokensFromDel(pool, 100)
require.Equal(t, sdk.Unbonded, validator.Status())
require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64())
require.Equal(t, sdk.Unbonded, validator.Status)
require.Equal(t, int64(100), validator.Tokens.RoundInt64())
keeper.SetPool(ctx, pool)
keeper.SetValidatorByPubKeyIndex(ctx, validator)
validator = keeper.UpdateValidator(ctx, validator)
require.Equal(t, int64(100), validator.PoolShares.Tokens(pool).RoundInt64(), "\nvalidator %v\npool %v", validator, pool)
require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool)
// slash the validator by 100%
keeper.Slash(ctx, PKs[0], 0, 100, sdk.OneRat())
@@ -125,9 +120,14 @@ func TestValidatorBasics(t *testing.T) {
amts := []int64{9, 8, 7}
for i, amt := range amts {
validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{})
validators[i].PoolShares = types.NewUnbondedShares(sdk.ZeroRat())
validators[i].AddTokensFromDel(pool, amt)
validators[i].Status = sdk.Unbonded
validators[i].Tokens = sdk.ZeroRat()
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
}
assert.True(sdk.RatEq(t, sdk.NewRat(9), validators[0].Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(8), validators[1].Tokens))
assert.True(sdk.RatEq(t, sdk.NewRat(7), validators[2].Tokens))
// check the empty keeper first
_, found := keeper.GetValidator(ctx, addrVals[0])
@@ -135,6 +135,9 @@ func TestValidatorBasics(t *testing.T) {
resVals := keeper.GetValidatorsBonded(ctx)
assert.Zero(t, len(resVals))
pool = keeper.GetPool(ctx)
assert.True(sdk.RatEq(t, sdk.ZeroRat(), pool.BondedTokens))
// set and retrieve a record
validators[0] = keeper.UpdateValidator(ctx, validators[0])
resVal, found := keeper.GetValidator(ctx, addrVals[0])
@@ -144,9 +147,15 @@ func TestValidatorBasics(t *testing.T) {
resVals = keeper.GetValidatorsBonded(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validators[0], resVals[0]))
assert.Equal(t, sdk.Bonded, validators[0].Status)
assert.True(sdk.RatEq(t, sdk.NewRat(9), validators[0].BondedTokens()))
pool = keeper.GetPool(ctx)
assert.True(sdk.RatEq(t, pool.BondedTokens, validators[0].BondedTokens()))
// modify a records, save, and retrieve
validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(10))
validators[0].Status = sdk.Bonded
validators[0].Tokens = sdk.NewRat(10)
validators[0].DelegatorShares = sdk.NewRat(10)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
resVal, found = keeper.GetValidator(ctx, addrVals[0])
@@ -189,7 +198,8 @@ func GetValidatorSortingUnmixed(t *testing.T) {
var validators [5]types.Validator
for i, amt := range amts {
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i].PoolShares = types.NewBondedShares(sdk.NewRat(amt))
validators[i].Status = sdk.Bonded
validators[i].Tokens = sdk.NewRat(amt)
validators[i].DelegatorShares = sdk.NewRat(amt)
keeper.UpdateValidator(ctx, validators[i])
}
@@ -197,11 +207,11 @@ func GetValidatorSortingUnmixed(t *testing.T) {
// first make sure everything made it in to the gotValidator group
resValidators := keeper.GetValidatorsByPower(ctx)
assert.Equal(t, n, len(resValidators))
assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(400), resValidators[0].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].BondedTokens(), "%v", resValidators)
assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators)
assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators)
assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators)
@@ -209,14 +219,14 @@ func GetValidatorSortingUnmixed(t *testing.T) {
assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators)
// test a basic increase in voting power
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(500))
validators[3].Tokens = sdk.NewRat(500)
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
// test a decrease in voting power
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300))
validators[3].Tokens = sdk.NewRat(300)
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
@@ -224,7 +234,7 @@ func GetValidatorSortingUnmixed(t *testing.T) {
assert.True(ValEq(t, validators[4], resValidators[1]))
// test equal voting power, different age
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(200))
validators[3].Tokens = sdk.NewRat(200)
ctx = ctx.WithBlockHeight(10)
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
@@ -243,8 +253,8 @@ func GetValidatorSortingUnmixed(t *testing.T) {
assert.True(ValEq(t, validators[4], resValidators[1]))
// change in voting power of both validators, both still in v-set, no age change
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300))
validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(300))
validators[3].Tokens = sdk.NewRat(300)
validators[4].Tokens = sdk.NewRat(300)
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
@@ -273,11 +283,19 @@ func GetValidatorSortingMixed(t *testing.T) {
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[0]))
validators[1].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[1]))
validators[2].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[2]))
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(amts[3]))
validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(amts[4]))
validators[0].Status = sdk.Bonded
validators[1].Status = sdk.Bonded
validators[2].Status = sdk.Bonded
validators[0].Tokens = sdk.NewRat(amts[0])
validators[1].Tokens = sdk.NewRat(amts[1])
validators[2].Tokens = sdk.NewRat(amts[2])
validators[3].Status = sdk.Bonded
validators[4].Status = sdk.Bonded
validators[3].Tokens = sdk.NewRat(amts[3])
validators[4].Tokens = sdk.NewRat(amts[4])
for i := range amts {
keeper.UpdateValidator(ctx, validators[i])
}
@@ -291,20 +309,20 @@ func GetValidatorSortingMixed(t *testing.T) {
require.True(t, found)
val4, found := keeper.GetValidator(ctx, Addrs[4])
require.True(t, found)
require.Equal(t, sdk.Unbonded, val0.Status())
require.Equal(t, sdk.Unbonded, val1.Status())
require.Equal(t, sdk.Unbonded, val2.Status())
require.Equal(t, sdk.Bonded, val3.Status())
require.Equal(t, sdk.Bonded, val4.Status())
require.Equal(t, sdk.Unbonded, val0.Status)
require.Equal(t, sdk.Unbonded, val1.Status)
require.Equal(t, sdk.Unbonded, val2.Status)
require.Equal(t, sdk.Bonded, val3.Status)
require.Equal(t, sdk.Bonded, val4.Status)
// first make sure everything made it in to the gotValidator group
resValidators := keeper.GetValidatorsByPower(ctx)
assert.Equal(t, n, len(resValidators))
assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(400), resValidators[0].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].BondedTokens(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].BondedTokens(), "%v", resValidators)
assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators)
assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators)
assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators)
@@ -392,6 +410,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) {
func TestValidatorBondHeight(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
pool := keeper.GetPool(ctx)
// now 2 max resValidators
params := keeper.GetParams(ctx)
@@ -399,7 +418,6 @@ func TestValidatorBondHeight(t *testing.T) {
keeper.SetParams(ctx, params)
// initialize some validators into the state
pool := keeper.GetPool(ctx)
var validators [3]types.Validator
validators[0] = types.NewValidator(Addrs[0], PKs[0], types.Description{})
validators[1] = types.NewValidator(Addrs[1], PKs[1], types.Description{})
@@ -408,14 +426,18 @@ func TestValidatorBondHeight(t *testing.T) {
validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 200)
validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 100)
validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 100)
keeper.SetPool(ctx, pool)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
////////////////////////////////////////
// If two validators both increase to the same voting power in the same block,
// the one with the first transaction should become bonded
validators[1] = keeper.UpdateValidator(ctx, validators[1])
validators[2] = keeper.UpdateValidator(ctx, validators[2])
pool = keeper.GetPool(ctx)
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, uint16(len(resValidators)), params.MaxValidators)
@@ -454,11 +476,11 @@ func TestFullValidatorSetPowerChange(t *testing.T) {
validators[i], found = keeper.GetValidator(ctx, validators[i].Owner)
require.True(t, found)
}
assert.Equal(t, sdk.Unbonded, validators[0].Status())
assert.Equal(t, sdk.Unbonded, validators[1].Status())
assert.Equal(t, sdk.Bonded, validators[2].Status())
assert.Equal(t, sdk.Bonded, validators[3].Status())
assert.Equal(t, sdk.Unbonded, validators[4].Status())
assert.Equal(t, sdk.Unbonded, validators[0].Status)
assert.Equal(t, sdk.Unbonded, validators[1].Status)
assert.Equal(t, sdk.Bonded, validators[2].Status)
assert.Equal(t, sdk.Bonded, validators[3].Status)
assert.Equal(t, sdk.Unbonded, validators[4].Status)
resValidators := keeper.GetValidatorsByPower(ctx)
assert.Equal(t, max, len(resValidators))
assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs
@@ -576,7 +598,8 @@ func TestGetTendermintUpdatesSingleValueChange(t *testing.T) {
// test single value change
// tendermintUpdate set: {} -> {c1'}
validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(600))
validators[0].Status = sdk.Bonded
validators[0].Tokens = sdk.NewRat(600)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
updates := keeper.GetTendermintUpdates(ctx)
+1 -4
View File
@@ -10,13 +10,13 @@ import (
type (
Keeper = keeper.Keeper
Validator = types.Validator
BechValidator = types.BechValidator
Description = types.Description
Delegation = types.Delegation
UnbondingDelegation = types.UnbondingDelegation
Redelegation = types.Redelegation
Params = types.Params
Pool = types.Pool
PoolShares = types.PoolShares
MsgCreateValidator = types.MsgCreateValidator
MsgEditValidator = types.MsgEditValidator
MsgDelegate = types.MsgDelegate
@@ -62,9 +62,6 @@ var (
DefaultParams = types.DefaultParams
InitialPool = types.InitialPool
NewUnbondedShares = types.NewUnbondedShares
NewUnbondingShares = types.NewUnbondingShares
NewBondedShares = types.NewBondedShares
NewValidator = types.NewValidator
NewDescription = types.NewDescription
NewGenesisState = types.NewGenesisState
+142
View File
@@ -0,0 +1,142 @@
package types
import (
"math/rand"
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
//changing the int in NewSource will allow you to test different, deterministic, sets of operations
var r = rand.New(rand.NewSource(6595))
func TestGetInflation(t *testing.T) {
pool := InitialPool()
params := DefaultParams()
// Governing Mechanism:
// BondedRatio = BondedTokens / TotalSupply
// inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange
tests := []struct {
name string
setBondedTokens, setLooseTokens,
setInflation, expectedChange sdk.Rat
}{
// with 0% bonded atom supply the inflation should increase by InflationRateChange
{"test 1", sdk.ZeroRat(), sdk.ZeroRat(), sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)},
// 100% bonded, starting at 20% inflation and being reduced
// (1 - (1/0.67))*(0.13/8667)
{"test 2", sdk.OneRat(), sdk.ZeroRat(), sdk.NewRat(20, 100),
sdk.OneRat().Sub(sdk.OneRat().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
// 50% bonded, starting at 10% inflation and being increased
{"test 3", sdk.OneRat(), sdk.OneRat(), sdk.NewRat(10, 100),
sdk.OneRat().Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
// test 7% minimum stop (testing with 100% bonded)
{"test 4", sdk.OneRat(), sdk.ZeroRat(), sdk.NewRat(7, 100), sdk.ZeroRat()},
{"test 5", sdk.OneRat(), sdk.ZeroRat(), sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)},
// test 20% maximum stop (testing with 0% bonded)
{"test 6", sdk.ZeroRat(), sdk.ZeroRat(), sdk.NewRat(20, 100), sdk.ZeroRat()},
{"test 7", sdk.ZeroRat(), sdk.ZeroRat(), sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)},
// perfect balance shouldn't change inflation
{"test 8", sdk.NewRat(67), sdk.NewRat(33), sdk.NewRat(15, 100), sdk.ZeroRat()},
}
for _, tc := range tests {
pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens
pool.Inflation = tc.setInflation
inflation := pool.NextInflation(params)
diffInflation := inflation.Sub(tc.setInflation)
require.True(t, diffInflation.Equal(tc.expectedChange),
"Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange)
}
}
// Test that provisions are correctly added to the pool and validators each hour for 1 year
func TestProcessProvisions(t *testing.T) {
pool := InitialPool()
params := DefaultParams()
var (
initialTotalTokens int64 = 550000000
cumulativeExpProvs = sdk.ZeroRat()
)
pool.LooseTokens = sdk.NewRat(initialTotalTokens)
// process the provisions for a year
for hr := 0; hr < 100; hr++ {
var expProvisions sdk.Rat
_, expProvisions, pool = updateProvisions(t, pool, params, hr)
cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions)
}
//get the pool and do the final value checks from checkFinalPoolValues
checkFinalPoolValues(t, pool, sdk.NewRat(initialTotalTokens), cumulativeExpProvs)
}
//_________________________________________________________________________________________
////////////////////////////////HELPER FUNCTIONS BELOW/////////////////////////////////////
// Final check on the global pool values for what the total tokens accumulated from each hour of provisions
func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens, cumulativeExpProvs sdk.Rat) {
calculatedTotalTokens := initialTotalTokens.Add(cumulativeExpProvs)
require.True(sdk.RatEq(t, calculatedTotalTokens, pool.TokenSupply()))
}
// Processes provisions are added to the pool correctly every hour
// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests
func updateProvisions(t *testing.T, pool Pool, params Params, hr int) (sdk.Rat, sdk.Rat, Pool) {
expInflation := pool.NextInflation(params)
expProvisions := expInflation.Mul(pool.TokenSupply()).Quo(hrsPerYrRat)
startTotalSupply := pool.TokenSupply()
pool = pool.ProcessProvisions(params)
//check provisions were added to pool
require.True(sdk.RatEq(t, startTotalSupply.Add(expProvisions), pool.TokenSupply()))
return expInflation, expProvisions, pool
}
// Checks that The inflation will correctly increase or decrease after an update to the pool
// nolint: gocyclo
func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Rat, msg string) {
inflationChange := updatedInflation.Sub(previousInflation)
switch {
//BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation
case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)):
require.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg)
//BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio
case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)):
if previousInflation.Equal(sdk.NewRat(20, 100)) {
require.Equal(t, true, inflationChange.IsZero(), msg)
//This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%)
} else {
require.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg)
}
//ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7%
case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)):
require.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg)
//ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%.
case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)):
if previousInflation.Equal(sdk.NewRat(7, 100)) {
require.Equal(t, true, inflationChange.IsZero(), msg)
//This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%)
} else {
require.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg)
}
}
}
+53 -93
View File
@@ -9,13 +9,8 @@ import (
// Pool - dynamic parameters of the current state
type Pool struct {
LooseTokens int64 `json:"loose_tokens"` // tokens not associated with any validator
UnbondedTokens int64 `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators
UnbondingTokens int64 `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool
BondedTokens int64 `json:"bonded_tokens"` // reserve of bonded tokens
UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool
UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool
BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool
LooseTokens sdk.Rat `json:"loose_tokens"` // tokens which are not bonded in a validator
BondedTokens sdk.Rat `json:"bonded_tokens"` // reserve of bonded tokens
InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time
Inflation sdk.Rat `json:"inflation"` // current annual inflation rate
@@ -35,13 +30,8 @@ func (p Pool) Equal(p2 Pool) bool {
// initial pool for testing
func InitialPool() Pool {
return Pool{
LooseTokens: 0,
BondedTokens: 0,
UnbondingTokens: 0,
UnbondedTokens: 0,
BondedShares: sdk.ZeroRat(),
UnbondingShares: sdk.ZeroRat(),
UnbondedShares: sdk.ZeroRat(),
LooseTokens: sdk.ZeroRat(),
BondedTokens: sdk.ZeroRat(),
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
DateLastCommissionReset: 0,
@@ -52,108 +42,78 @@ func InitialPool() Pool {
//____________________________________________________________________
// Sum total of all staking tokens in the pool
func (p Pool) TokenSupply() int64 {
return p.LooseTokens + p.UnbondedTokens + p.UnbondingTokens + p.BondedTokens
func (p Pool) TokenSupply() sdk.Rat {
return p.LooseTokens.Add(p.BondedTokens)
}
//____________________________________________________________________
// get the bond ratio of the global state
func (p Pool) BondedRatio() sdk.Rat {
if p.TokenSupply() > 0 {
return sdk.NewRat(p.BondedTokens, p.TokenSupply())
supply := p.TokenSupply()
if supply.GT(sdk.ZeroRat()) {
return p.BondedTokens.Quo(supply)
}
return sdk.ZeroRat()
}
// get the exchange rate of bonded token per issued share
func (p Pool) BondedShareExRate() sdk.Rat {
if p.BondedShares.IsZero() {
return sdk.OneRat()
//_______________________________________________________________________
func (p Pool) looseTokensToBonded(bondedTokens sdk.Rat) Pool {
p.BondedTokens = p.BondedTokens.Add(bondedTokens)
p.LooseTokens = p.LooseTokens.Sub(bondedTokens)
if p.LooseTokens.LT(sdk.ZeroRat()) {
panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p))
}
return sdk.NewRat(p.BondedTokens).Quo(p.BondedShares)
return p
}
// get the exchange rate of unbonding tokens held in validators per issued share
func (p Pool) UnbondingShareExRate() sdk.Rat {
if p.UnbondingShares.IsZero() {
return sdk.OneRat()
func (p Pool) bondedTokensToLoose(bondedTokens sdk.Rat) Pool {
p.BondedTokens = p.BondedTokens.Sub(bondedTokens)
p.LooseTokens = p.LooseTokens.Add(bondedTokens)
if p.BondedTokens.LT(sdk.ZeroRat()) {
panic(fmt.Sprintf("sanity check: bonded tokens negative, pool: %v", p))
}
return sdk.NewRat(p.UnbondingTokens).Quo(p.UnbondingShares)
}
// get the exchange rate of unbonded tokens held in validators per issued share
func (p Pool) UnbondedShareExRate() sdk.Rat {
if p.UnbondedShares.IsZero() {
return sdk.OneRat()
}
return sdk.NewRat(p.UnbondedTokens).Quo(p.UnbondedShares)
return p
}
//_______________________________________________________________________
// Inflation
func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondedShareExRate()) // tokens * (shares/tokens)
p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount)
p.UnbondedTokens += amount
p.LooseTokens -= amount
if p.LooseTokens < 0 {
panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p))
}
return p, NewUnbondedShares(issuedSharesAmount)
const precision = 100000000000 // increased to this precision for accuracy
var hrsPerYrRat = sdk.NewRat(8766) // as defined by a julian year of 365.25 days
// process provisions for an hour period
func (p Pool) ProcessProvisions(params Params) Pool {
p.Inflation = p.NextInflation(params)
provisions := p.Inflation.Mul(p.TokenSupply()).Quo(hrsPerYrRat)
// TODO add to the fees provisions
p.LooseTokens = p.LooseTokens.Add(provisions)
return p
}
func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.UnbondedShareExRate().Mul(shares).RoundInt64() // (tokens/shares) * shares
p.UnbondedShares = p.UnbondedShares.Sub(shares)
p.UnbondedTokens -= removedTokens
p.LooseTokens += removedTokens
if p.UnbondedTokens < 0 {
panic(fmt.Sprintf("sanity check: unbonded tokens negative, pool: %v", p))
}
return p, removedTokens
}
// get the next inflation rate for the hour
func (p Pool) NextInflation(params Params) (inflation sdk.Rat) {
func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondingShareExRate()) // tokens * (shares/tokens)
p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount)
p.UnbondingTokens += amount
p.LooseTokens -= amount
if p.LooseTokens < 0 {
panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p))
}
return p, NewUnbondingShares(issuedSharesAmount)
}
// The target annual inflation rate is recalculated for each previsions cycle. The
// inflation is also subject to a rate change (positive or negative) depending on
// the distance from the desired ratio (67%). The maximum rate change possible is
// defined to be 13% per year, however the annual inflation is capped as between
// 7% and 20%.
func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.UnbondingShareExRate().Mul(shares).RoundInt64() // (tokens/shares) * shares
p.UnbondingShares = p.UnbondingShares.Sub(shares)
p.UnbondingTokens -= removedTokens
p.LooseTokens += removedTokens
if p.UnbondedTokens < 0 {
panic(fmt.Sprintf("sanity check: unbonding tokens negative, pool: %v", p))
}
return p, removedTokens
}
// (1 - bondedRatio/GoalBonded) * InflationRateChange
inflationRateChangePerYear := sdk.OneRat().Sub(p.BondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange)
inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat)
func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRat(amount).Quo(p.BondedShareExRate()) // tokens * (shares/tokens)
p.BondedShares = p.BondedShares.Add(issuedSharesAmount)
p.BondedTokens += amount
p.LooseTokens -= amount
if p.LooseTokens < 0 {
panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p))
// increase the new annual inflation for this next cycle
inflation = p.Inflation.Add(inflationRateChange)
if inflation.GT(params.InflationMax) {
inflation = params.InflationMax
}
if inflation.LT(params.InflationMin) {
inflation = params.InflationMin
}
return p, NewBondedShares(issuedSharesAmount)
}
func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.BondedShareExRate().Mul(shares).RoundInt64() // (tokens/shares) * shares
p.BondedShares = p.BondedShares.Sub(shares)
p.BondedTokens -= removedTokens
p.LooseTokens += removedTokens
if p.UnbondedTokens < 0 {
panic(fmt.Sprintf("sanity check: bonded tokens negative, pool: %v", p))
}
return p, removedTokens
return inflation.Round(precision)
}
+15 -117
View File
@@ -10,131 +10,29 @@ import (
func TestPoolEqual(t *testing.T) {
p1 := InitialPool()
p2 := InitialPool()
ok := p1.Equal(p2)
require.True(t, ok)
p2.BondedTokens = 3
p2.BondedShares = sdk.NewRat(10)
ok = p1.Equal(p2)
require.False(t, ok)
require.True(t, p1.Equal(p2))
p2.BondedTokens = sdk.NewRat(3)
require.False(t, p1.Equal(p2))
}
func TestBondedRatio(t *testing.T) {
func TestAddBondedTokens(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 1
pool.BondedTokens = 2
pool.LooseTokens = sdk.NewRat(10)
pool.BondedTokens = sdk.NewRat(10)
// bonded pool / total supply
require.Equal(t, pool.BondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3)))
pool = pool.looseTokensToBonded(sdk.NewRat(10))
// avoids divide-by-zero
pool.LooseTokens = 0
pool.BondedTokens = 0
require.Equal(t, pool.BondedRatio(), sdk.ZeroRat())
require.True(sdk.RatEq(t, sdk.NewRat(20), pool.BondedTokens))
require.True(sdk.RatEq(t, sdk.NewRat(0), pool.LooseTokens))
}
func TestBondedShareExRate(t *testing.T) {
func TestRemoveBondedTokens(t *testing.T) {
pool := InitialPool()
pool.BondedTokens = 3
pool.BondedShares = sdk.NewRat(10)
pool.LooseTokens = sdk.NewRat(10)
pool.BondedTokens = sdk.NewRat(10)
// bonded pool / bonded shares
require.Equal(t, pool.BondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.BondedShares = sdk.ZeroRat()
pool = pool.bondedTokensToLoose(sdk.NewRat(5))
// avoids divide-by-zero
require.Equal(t, pool.BondedShareExRate(), sdk.OneRat())
}
func TestUnbondingShareExRate(t *testing.T) {
pool := InitialPool()
pool.UnbondingTokens = 3
pool.UnbondingShares = sdk.NewRat(10)
// unbonding pool / unbonding shares
require.Equal(t, pool.UnbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.UnbondingShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.UnbondingShareExRate(), sdk.OneRat())
}
func TestUnbondedShareExRate(t *testing.T) {
pool := InitialPool()
pool.UnbondedTokens = 3
pool.UnbondedShares = sdk.NewRat(10)
// unbonded pool / unbonded shares
require.Equal(t, pool.UnbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.UnbondedShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.UnbondedShareExRate(), sdk.OneRat())
}
func TestAddTokensBonded(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat())
poolB, sharesB := poolA.addTokensBonded(10)
require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat())
// correct changes to bonded shares and bonded pool
require.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount))
require.Equal(t, poolB.BondedTokens, poolA.BondedTokens+10)
// same number of bonded shares / tokens when exchange rate is one
require.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens)))
}
func TestRemoveSharesBonded(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat())
poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10))
require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat())
// correct changes to bonded shares and bonded pool
require.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10)))
require.Equal(t, poolB.BondedTokens, poolA.BondedTokens-tokensB)
// same number of bonded shares / tokens when exchange rate is one
require.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens)))
}
func TestAddTokensUnbonded(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat())
poolB, sharesB := poolA.addTokensUnbonded(10)
require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat())
// correct changes to unbonded shares and unbonded pool
require.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount))
require.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens+10)
// same number of unbonded shares / tokens when exchange rate is one
require.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens)))
}
func TestRemoveSharesUnbonded(t *testing.T) {
poolA := InitialPool()
poolA.UnbondedTokens = 10
poolA.UnbondedShares = sdk.NewRat(10)
require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat())
poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10))
require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat())
// correct changes to unbonded shares and bonded pool
require.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10)))
require.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens-tokensB)
// same number of unbonded shares / tokens when exchange rate is one
require.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens)))
require.True(sdk.RatEq(t, sdk.NewRat(5), pool.BondedTokens))
require.True(sdk.RatEq(t, sdk.NewRat(15), pool.LooseTokens))
}
-149
View File
@@ -1,149 +0,0 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// PoolShares reflects the shares of a validator in a pool.
type PoolShares struct {
Status sdk.BondStatus `json:"status"`
Amount sdk.Rat `json:"amount"`
}
// Equal returns a boolean determining of two PoolShares are identical.
func (s PoolShares) Equal(s2 PoolShares) bool {
return s.Status == s2.Status &&
s.Amount.Equal(s2.Amount)
}
// NewUnbondedShares returns a new PoolShares with a specified unbonded amount.
func NewUnbondedShares(amount sdk.Rat) PoolShares {
return PoolShares{
Status: sdk.Unbonded,
Amount: amount,
}
}
// NewUnbondingShares returns a new PoolShares with a specified unbonding
// amount.
func NewUnbondingShares(amount sdk.Rat) PoolShares {
return PoolShares{
Status: sdk.Unbonding,
Amount: amount,
}
}
// NewBondedShares returns a new PoolSahres with a specified bonding amount.
func NewBondedShares(amount sdk.Rat) PoolShares {
return PoolShares{
Status: sdk.Bonded,
Amount: amount,
}
}
// Unbonded returns the amount of unbonded shares.
func (s PoolShares) Unbonded() sdk.Rat {
if s.Status == sdk.Unbonded {
return s.Amount
}
return sdk.ZeroRat()
}
// Unbonding returns the amount of unbonding shares.
func (s PoolShares) Unbonding() sdk.Rat {
if s.Status == sdk.Unbonding {
return s.Amount
}
return sdk.ZeroRat()
}
// Bonded returns amount of bonded shares.
func (s PoolShares) Bonded() sdk.Rat {
if s.Status == sdk.Bonded {
return s.Amount
}
return sdk.ZeroRat()
}
// ToUnbonded returns the equivalent amount of pool shares if the shares were
// unbonded.
func (s PoolShares) ToUnbonded(p Pool) PoolShares {
var amount sdk.Rat
switch s.Status {
case sdk.Bonded:
// (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr
exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate())
// bondedshr*unbondedshr/bondedshr = unbondedshr
amount = s.Amount.Mul(exRate)
case sdk.Unbonding:
// (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr
exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate())
// unbondingshr*unbondedshr/unbondingshr = unbondedshr
amount = s.Amount.Mul(exRate)
case sdk.Unbonded:
amount = s.Amount
}
return NewUnbondedShares(amount)
}
// ToUnbonding returns the equivalent amount of pool shares if the shares were
// unbonding.
func (s PoolShares) ToUnbonding(p Pool) PoolShares {
var amount sdk.Rat
switch s.Status {
case sdk.Bonded:
// (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr
exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate())
// bondedshr*unbondingshr/bondedshr = unbondingshr
amount = s.Amount.Mul(exRate)
case sdk.Unbonding:
amount = s.Amount
case sdk.Unbonded:
// (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr
exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate())
// unbondedshr*unbondingshr/unbondedshr = unbondingshr
amount = s.Amount.Mul(exRate)
}
return NewUnbondingShares(amount)
}
// ToBonded the equivalent amount of pool shares if the shares were bonded.
func (s PoolShares) ToBonded(p Pool) PoolShares {
var amount sdk.Rat
switch s.Status {
case sdk.Bonded:
amount = s.Amount
case sdk.Unbonding:
// (tok/ubshr)/(tok/bshr) = bshr/ubshr
exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate())
// ubshr*bshr/ubshr = bshr
amount = s.Amount.Mul(exRate)
case sdk.Unbonded:
// (tok/ubshr)/(tok/bshr) = bshr/ubshr
exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate())
// ubshr*bshr/ubshr = bshr
amount = s.Amount.Mul(exRate)
}
return NewUnbondedShares(amount)
}
// Tokens returns the equivalent amount of tokens contained by the pool shares
// for a given pool.
func (s PoolShares) Tokens(p Pool) sdk.Rat {
switch s.Status {
case sdk.Bonded:
return p.BondedShareExRate().Mul(s.Amount)
case sdk.Unbonding:
return p.UnbondingShareExRate().Mul(s.Amount)
case sdk.Unbonded:
return p.UnbondedShareExRate().Mul(s.Amount)
default:
panic("unknown share kind")
}
}
-35
View File
@@ -1,35 +0,0 @@
package types
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
func TestPoolSharesTokens(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
val := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(sdk.NewRat(100)),
DelegatorShares: sdk.NewRat(100),
}
pool.BondedTokens = val.PoolShares.Bonded().RoundInt64()
pool.BondedShares = val.PoolShares.Bonded()
poolShares := NewBondedShares(sdk.NewRat(50))
tokens := poolShares.Tokens(pool)
require.Equal(t, int64(50), tokens.RoundInt64())
poolShares = NewUnbondingShares(sdk.NewRat(50))
tokens = poolShares.Tokens(pool)
require.Equal(t, int64(50), tokens.RoundInt64())
poolShares = NewUnbondedShares(sdk.NewRat(50))
tokens = poolShares.Tokens(pool)
require.Equal(t, int64(50), tokens.RoundInt64())
}
+61 -82
View File
@@ -25,62 +25,59 @@ var (
// Operation reflects any operation that transforms staking state. It takes in
// a RNG instance, pool, validator and returns an updated pool, updated
// validator, delta tokens, and descriptive message.
type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string)
type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, sdk.Rat, string)
// OpBondOrUnbond implements an operation that bonds or unbonds a validator
// depending on current status.
// nolint: unparam
func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) {
// TODO split up into multiple operations
func OpBondOrUnbond(r *rand.Rand, pool Pool, validator Validator) (Pool, Validator, sdk.Rat, string) {
var (
msg string
newStatus sdk.BondStatus
)
if val.Status() == sdk.Bonded {
msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
if validator.Status == sdk.Bonded {
msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %#v", validator)
newStatus = sdk.Unbonded
} else if val.Status() == sdk.Unbonded {
msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
} else if validator.Status == sdk.Unbonded {
msg = fmt.Sprintf("sdk.Bonded previously bonded validator %#v", validator)
newStatus = sdk.Bonded
}
val, pool = val.UpdateStatus(pool, newStatus)
return pool, val, 0, msg
validator, pool = validator.UpdateStatus(pool, newStatus)
return pool, validator, sdk.ZeroRat(), msg
}
// OpAddTokens implements an operation that adds a random number of tokens to a
// validator.
func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) {
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
func OpAddTokens(r *rand.Rand, pool Pool, validator Validator) (Pool, Validator, sdk.Rat, string) {
msg := fmt.Sprintf("validator %#v", validator)
tokens := int64(r.Int31n(1000))
val, pool, _ = val.AddTokensFromDel(pool, tokens)
validator, pool, _ = validator.AddTokensFromDel(pool, tokens)
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
// Tokens are removed so for accounting must be negative
return pool, val, -1 * tokens, msg
return pool, validator, sdk.NewRat(-1 * tokens), msg
}
// OpRemoveShares implements an operation that removes a random number of
// shares from a validator.
func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) {
// delegatorshares from a validator.
func OpRemoveShares(r *rand.Rand, pool Pool, validator Validator) (Pool, Validator, sdk.Rat, string) {
var shares sdk.Rat
for {
shares = sdk.NewRat(int64(r.Int31n(1000)))
if shares.LT(val.DelegatorShares) {
if shares.LT(validator.DelegatorShares) {
break
}
}
msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool))
msg := fmt.Sprintf("Removed %v shares from validator %#v", shares, validator)
val, pool, tokens := val.RemoveDelShares(pool, shares)
return pool, val, tokens, msg
validator, pool, tokens := validator.RemoveDelShares(pool, shares)
return pool, validator, tokens, msg
}
// RandomOperation returns a random staking operation.
@@ -100,66 +97,48 @@ func RandomOperation(r *rand.Rand) Operation {
// AssertInvariants ensures invariants that should always be true are true.
// nolint: unparam
func AssertInvariants(t *testing.T, msg string,
pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator, tokens int64) {
pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator) {
// total tokens conserved
require.Equal(t,
pOrig.UnbondedTokens+pOrig.BondedTokens,
pMod.UnbondedTokens+pMod.BondedTokens+tokens,
"Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n",
require.True(t,
pOrig.LooseTokens.Add(pOrig.BondedTokens).Equal(
pMod.LooseTokens.Add(pMod.BondedTokens)),
"Tokens not conserved - msg: %v\n, pOrig.BondedTokens: %v, pOrig.LooseTokens: %v, pMod.BondedTokens: %v, pMod.LooseTokens: %v",
msg,
pOrig.BondedShares, pOrig.UnbondedShares,
pMod.BondedShares, pMod.UnbondedShares,
pOrig.UnbondedTokens, pOrig.BondedTokens,
pMod.UnbondedTokens, pMod.BondedTokens, tokens)
pOrig.BondedTokens, pOrig.LooseTokens,
pMod.BondedTokens, pMod.LooseTokens)
// Nonnegative bonded shares
require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()),
"Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// Nonnegative bonded tokens
require.False(t, pMod.BondedTokens.LT(sdk.ZeroRat()),
"Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\n",
msg, pOrig, pMod)
// Nonnegative unbonded shares
require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()),
"Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// Nonnegative bonded ex rate
require.False(t, pMod.BondedShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative BondedShareExRate: %d",
msg, pMod.BondedShareExRate().RoundInt64())
// Nonnegative unbonded ex rate
require.False(t, pMod.UnbondedShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative UnbondedShareExRate: %d",
msg, pMod.UnbondedShareExRate().RoundInt64())
// Nonnegative loose tokens
require.False(t, pMod.LooseTokens.LT(sdk.ZeroRat()),
"Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\n",
msg, pOrig, pMod)
for _, vMod := range vMods {
// Nonnegative ex rate
require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()),
require.False(t, vMod.DelegatorShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)",
msg,
vMod.DelegatorShareExRate(pMod),
vMod.DelegatorShareExRate(),
vMod.Owner,
)
// Nonnegative poolShares
require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)",
require.False(t, vMod.BondedTokens().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.BondedTokens(): %#v",
msg,
vMod.PoolShares.Bonded(),
vMod.DelegatorShares,
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
vMod,
)
// Nonnegative delShares
require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)",
"Applying operation \"%s\" resulted in negative validator.DelegatorShares: %#v",
msg,
vMod.DelegatorShares,
vMod.PoolShares.Bonded(),
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
vMod,
)
}
}
@@ -169,40 +148,40 @@ func AssertInvariants(t *testing.T, msg string,
// randomValidator generates a random validator.
// nolint: unparam
func randomValidator(r *rand.Rand, i int) Validator {
poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000)))
tokens := sdk.NewRat(int64(r.Int31n(10000)))
delShares := sdk.NewRat(int64(r.Int31n(10000)))
var pShares PoolShares
if r.Float64() < float64(0.5) {
pShares = NewBondedShares(poolSharesAmt)
} else {
pShares = NewUnbondedShares(poolSharesAmt)
// TODO add more options here
status := sdk.Bonded
if r.Float64() > float64(0.5) {
status = sdk.Unbonded
}
return Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: pShares,
DelegatorShares: delShares,
}
validator := NewValidator(addr1, pk1, Description{})
validator.Status = status
validator.Tokens = tokens
validator.DelegatorShares = delShares
return validator
}
// RandomSetup generates a random staking state.
func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) {
pool := InitialPool()
pool.LooseTokens = 100000
pool.LooseTokens = sdk.NewRat(100000)
validators := make([]Validator, numValidators)
for i := 0; i < numValidators; i++ {
validator := randomValidator(r, i)
if validator.Status() == sdk.Bonded {
pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded())
pool.BondedTokens += validator.PoolShares.Bonded().RoundInt64()
} else if validator.Status() == sdk.Unbonded {
pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded())
pool.UnbondedTokens += validator.PoolShares.Unbonded().RoundInt64()
switch validator.Status {
case sdk.Bonded:
pool.BondedTokens = pool.BondedTokens.Add(validator.Tokens)
case sdk.Unbonded, sdk.Unbonding:
pool.LooseTokens = pool.LooseTokens.Add(validator.Tokens)
default:
panic("improper use of RandomSetup")
}
validators[i] = validator
+135 -115
View File
@@ -27,8 +27,9 @@ type Validator struct {
PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool
DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators
Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded)
Tokens sdk.Rat `json:"tokens"` // delegated tokens (incl. self-delegation)
DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators
Description Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
@@ -41,7 +42,7 @@ type Validator struct {
CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools
LastBondedTokens sdk.Rat `json:"prev_bonded_tokens"` // Previous bonded tokens held
}
// NewValidator - initialize a new validator
@@ -50,7 +51,8 @@ func NewValidator(owner sdk.AccAddress, pubKey crypto.PubKey, description Descri
Owner: owner,
PubKey: pubKey,
Revoked: false,
PoolShares: NewUnbondedShares(sdk.ZeroRat()),
Status: sdk.Unbonded,
Tokens: sdk.ZeroRat(),
DelegatorShares: sdk.ZeroRat(),
Description: description,
BondHeight: int64(0),
@@ -60,7 +62,7 @@ func NewValidator(owner sdk.AccAddress, pubKey crypto.PubKey, description Descri
CommissionMax: sdk.ZeroRat(),
CommissionChangeRate: sdk.ZeroRat(),
CommissionChangeToday: sdk.ZeroRat(),
PrevBondedShares: sdk.ZeroRat(),
LastBondedTokens: sdk.ZeroRat(),
}
}
@@ -68,7 +70,8 @@ func NewValidator(owner sdk.AccAddress, pubKey crypto.PubKey, description Descri
type validatorValue struct {
PubKey crypto.PubKey
Revoked bool
PoolShares PoolShares
Status sdk.BondStatus
Tokens sdk.Rat
DelegatorShares sdk.Rat
Description Description
BondHeight int64
@@ -78,7 +81,7 @@ type validatorValue struct {
CommissionMax sdk.Rat
CommissionChangeRate sdk.Rat
CommissionChangeToday sdk.Rat
PrevBondedShares sdk.Rat
LastBondedTokens sdk.Rat
}
// return the redelegation without fields contained within the key for the store
@@ -86,7 +89,8 @@ func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte {
val := validatorValue{
PubKey: validator.PubKey,
Revoked: validator.Revoked,
PoolShares: validator.PoolShares,
Status: validator.Status,
Tokens: validator.Tokens,
DelegatorShares: validator.DelegatorShares,
Description: validator.Description,
BondHeight: validator.BondHeight,
@@ -96,7 +100,7 @@ func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte {
CommissionMax: validator.CommissionMax,
CommissionChangeRate: validator.CommissionChangeRate,
CommissionChangeToday: validator.CommissionChangeToday,
PrevBondedShares: validator.PrevBondedShares,
LastBondedTokens: validator.LastBondedTokens,
}
return cdc.MustMarshalBinary(val)
}
@@ -128,7 +132,8 @@ func UnmarshalValidator(cdc *wire.Codec, ownerAddr, value []byte) (validator Val
Owner: ownerAddr,
PubKey: storeValue.PubKey,
Revoked: storeValue.Revoked,
PoolShares: storeValue.PoolShares,
Tokens: storeValue.Tokens,
Status: storeValue.Status,
DelegatorShares: storeValue.DelegatorShares,
Description: storeValue.Description,
BondHeight: storeValue.BondHeight,
@@ -138,15 +143,75 @@ func UnmarshalValidator(cdc *wire.Codec, ownerAddr, value []byte) (validator Val
CommissionMax: storeValue.CommissionMax,
CommissionChangeRate: storeValue.CommissionChangeRate,
CommissionChangeToday: storeValue.CommissionChangeToday,
PrevBondedShares: storeValue.PrevBondedShares,
LastBondedTokens: storeValue.LastBondedTokens,
}, nil
}
//___________________________________________________________________
// validator struct for bech output
type BechValidator struct {
Owner sdk.AccAddress `json:"owner"` // in bech32
PubKey string `json:"pub_key"` // in bech32
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded)
Tokens sdk.Rat `json:"tokens"` // delegated tokens (incl. self-delegation)
DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators
Description Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change
ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer
Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators
CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission
CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
LastBondedTokens sdk.Rat `json:"prev_bonded_shares"` // last bonded token amount
}
// get the bech validator from the the regular validator
func (v Validator) Bech32Validator() (BechValidator, error) {
bechValPubkey, err := sdk.Bech32ifyValPub(v.PubKey)
if err != nil {
return BechValidator{}, err
}
return BechValidator{
Owner: v.Owner,
PubKey: bechValPubkey,
Revoked: v.Revoked,
Status: v.Status,
Tokens: v.Tokens,
DelegatorShares: v.DelegatorShares,
Description: v.Description,
BondHeight: v.BondHeight,
BondIntraTxCounter: v.BondIntraTxCounter,
ProposerRewardPool: v.ProposerRewardPool,
Commission: v.Commission,
CommissionMax: v.CommissionMax,
CommissionChangeRate: v.CommissionChangeRate,
CommissionChangeToday: v.CommissionChangeToday,
LastBondedTokens: v.LastBondedTokens,
}, nil
}
//___________________________________________________________________
// only the vitals - does not check bond height of IntraTxCounter
// nolint gocyclo - why dis fail?
func (v Validator) Equal(c2 Validator) bool {
return v.PubKey.Equals(c2.PubKey) &&
bytes.Equal(v.Owner, c2.Owner) &&
v.PoolShares.Equal(c2.PoolShares) &&
v.Status.Equal(c2.Status) &&
v.Tokens.Equal(c2.Tokens) &&
v.DelegatorShares.Equal(c2.DelegatorShares) &&
v.Description == c2.Description &&
v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) &&
@@ -154,7 +219,7 @@ func (v Validator) Equal(c2 Validator) bool {
v.CommissionMax.Equal(c2.CommissionMax) &&
v.CommissionChangeRate.Equal(c2.CommissionChangeRate) &&
v.CommissionChangeToday.Equal(c2.CommissionChangeToday) &&
v.PrevBondedShares.Equal(c2.PrevBondedShares)
v.LastBondedTokens.Equal(c2.LastBondedTokens)
}
// Description - description fields for a validator
@@ -221,7 +286,7 @@ func (d Description) EnsureLength() (Description, sdk.Error) {
func (v Validator) ABCIValidator() abci.Validator {
return abci.Validator{
PubKey: tmtypes.TM2PB.PubKey(v.PubKey),
Power: v.PoolShares.Bonded().RoundInt64(),
Power: v.BondedTokens().RoundInt64(),
}
}
@@ -234,145 +299,99 @@ func (v Validator) ABCIValidatorZero() abci.Validator {
}
}
// Status returns the validator's bond status inferred from the pool shares.
func (v Validator) Status() sdk.BondStatus {
return v.PoolShares.Status
}
// UpdateStatus updates the location of the shares within a validator if it's
// bond status has changed.
// UpdateStatus updates the location of the shares within a validator
// to reflect the new status
func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) {
var tokens int64
switch v.Status() {
switch v.Status {
case sdk.Unbonded:
if NewStatus == sdk.Unbonded {
return v, pool
}
pool, tokens = pool.removeSharesUnbonded(v.PoolShares.Amount)
switch NewStatus {
case sdk.Unbonded:
return v, pool
case sdk.Bonded:
pool = pool.looseTokensToBonded(v.Tokens)
}
case sdk.Unbonding:
if NewStatus == sdk.Unbonding {
return v, pool
}
pool, tokens = pool.removeSharesUnbonding(v.PoolShares.Amount)
case sdk.Bonded:
if NewStatus == sdk.Bonded {
// Return if nothing needs switching
switch NewStatus {
case sdk.Unbonding:
return v, pool
case sdk.Bonded:
pool = pool.looseTokensToBonded(v.Tokens)
}
pool, tokens = pool.removeSharesBonded(v.PoolShares.Amount)
}
switch NewStatus {
case sdk.Unbonded:
pool, v.PoolShares = pool.addTokensUnbonded(tokens)
case sdk.Unbonding:
pool, v.PoolShares = pool.addTokensUnbonding(tokens)
case sdk.Bonded:
pool, v.PoolShares = pool.addTokensBonded(tokens)
switch NewStatus {
case sdk.Bonded:
return v, pool
default:
pool = pool.bondedTokensToLoose(v.Tokens)
}
}
v.Status = NewStatus
return v, pool
}
// RemovePoolShares removes pool shares from a validator. It returns
// corresponding tokens, which could be burned (e.g. when slashing a validator)
// or redistributed elsewhere.
func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) {
var tokens int64
switch v.Status() {
case sdk.Unbonded:
pool, tokens = pool.removeSharesUnbonded(poolShares)
case sdk.Unbonding:
pool, tokens = pool.removeSharesUnbonding(poolShares)
case sdk.Bonded:
pool, tokens = pool.removeSharesBonded(poolShares)
// removes tokens from a validator
func (v Validator) RemoveTokens(pool Pool, tokens sdk.Rat) (Validator, Pool) {
if v.Status == sdk.Bonded {
pool = pool.bondedTokensToLoose(tokens)
}
v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares)
return v, pool, tokens
}
// EquivalentBondedShares ...
//
// TODO: Remove should only be tokens get the power or potential power for a
// validator if bonded, the power is the BondedShares if not bonded, the power
// is the amount of bonded shares which the the validator would have it was
// bonded.
func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) {
return v.PoolShares.ToBonded(pool).Amount
v.Tokens = v.Tokens.Sub(tokens)
return v, pool
}
//_________________________________________________________________________________________________________
// AddTokensFromDel adds tokens to a validator
func (v Validator) AddTokensFromDel(pool Pool, amount int64) (Validator, Pool, sdk.Rat) {
var (
poolShares PoolShares
equivalentBondedShares sdk.Rat
)
// bondedShare/delegatedShare
exRate := v.DelegatorShareExRate(pool)
exRate := v.DelegatorShareExRate()
amountRat := sdk.NewRat(amount)
switch v.Status() {
case sdk.Unbonded:
pool, poolShares = pool.addTokensUnbonded(amount)
case sdk.Unbonding:
pool, poolShares = pool.addTokensUnbonding(amount)
case sdk.Bonded:
pool, poolShares = pool.addTokensBonded(amount)
if v.Status == sdk.Bonded {
pool = pool.looseTokensToBonded(amountRat)
}
v.PoolShares.Amount = v.PoolShares.Amount.Add(poolShares.Amount)
equivalentBondedShares = poolShares.ToBonded(pool).Amount
// bondedShare/(bondedShare/delegatedShare) = delegatedShare
issuedDelegatorShares := equivalentBondedShares.Quo(exRate)
v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares)
v.Tokens = v.Tokens.Add(amountRat)
issuedShares := amountRat.Quo(exRate)
v.DelegatorShares = v.DelegatorShares.Add(issuedShares)
return v, pool, issuedDelegatorShares
return v, pool, issuedShares
}
// RemoveDelShares removes delegator shares from a validator.
//
// NOTE: This function assumes the shares have already been updated for the
// validator status.
func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Rat) (Validator, Pool, int64) {
amount := v.DelegatorShareExRate(pool).Mul(delShares)
eqBondedSharesToRemove := NewBondedShares(amount)
func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Rat) (Validator, Pool, sdk.Rat) {
issuedTokens := v.DelegatorShareExRate().Mul(delShares)
v.Tokens = v.Tokens.Sub(issuedTokens)
v.DelegatorShares = v.DelegatorShares.Sub(delShares)
var createdCoins int64
switch v.Status() {
case sdk.Unbonded:
unbondedShares := eqBondedSharesToRemove.ToUnbonded(pool).Amount
pool, createdCoins = pool.removeSharesUnbonded(unbondedShares)
v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondedShares)
case sdk.Unbonding:
unbondingShares := eqBondedSharesToRemove.ToUnbonding(pool).Amount
pool, createdCoins = pool.removeSharesUnbonding(unbondingShares)
v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondingShares)
case sdk.Bonded:
pool, createdCoins = pool.removeSharesBonded(eqBondedSharesToRemove.Amount)
v.PoolShares.Amount = v.PoolShares.Amount.Sub(eqBondedSharesToRemove.Amount)
if v.Status == sdk.Bonded {
pool = pool.bondedTokensToLoose(issuedTokens)
}
return v, pool, createdCoins
return v, pool, issuedTokens
}
// DelegatorShareExRate gets the exchange rate of tokens over delegator shares.
// UNITS: eq-val-bonded-shares/delegator-shares
func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat {
// UNITS: tokens/delegator-shares
func (v Validator) DelegatorShareExRate() sdk.Rat {
if v.DelegatorShares.IsZero() {
return sdk.OneRat()
}
return v.Tokens.Quo(v.DelegatorShares)
}
eqBondedShares := v.PoolShares.ToBonded(pool).Amount
return eqBondedShares.Quo(v.DelegatorShares)
// Get the bonded tokens which the validator holds
func (v Validator) BondedTokens() sdk.Rat {
if v.Status == sdk.Bonded {
return v.Tokens
}
return sdk.ZeroRat()
}
//______________________________________________________________________
@@ -383,10 +402,10 @@ var _ sdk.Validator = Validator{}
// nolint - for sdk.Validator
func (v Validator) GetRevoked() bool { return v.Revoked }
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) GetOwner() sdk.AccAddress { return v.Owner }
func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey }
func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() }
func (v Validator) GetPower() sdk.Rat { return v.BondedTokens() }
func (v Validator) GetDelegatorShares() sdk.Rat { return v.DelegatorShares }
func (v Validator) GetBondHeight() int64 { return v.BondHeight }
@@ -402,7 +421,8 @@ func (v Validator) HumanReadableString() (string, error) {
resp := "Validator \n"
resp += fmt.Sprintf("Owner: %s\n", v.Owner)
resp += fmt.Sprintf("Validator: %s\n", bechVal)
resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.FloatString())
resp += fmt.Sprintf("Status: %s\n", sdk.BondStatusToString(v.Status))
resp += fmt.Sprintf("Tokens: %s\n", v.Tokens.FloatString())
resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.FloatString())
resp += fmt.Sprintf("Description: %s\n", v.Description)
resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight)
@@ -411,7 +431,7 @@ func (v Validator) HumanReadableString() (string, error) {
resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String())
resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String())
resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String())
resp += fmt.Sprintf("Previously Bonded Stares: %s\n", v.PrevBondedShares.String())
resp += fmt.Sprintf("Previous Bonded Tokens: %s\n", v.LastBondedTokens.String())
return resp, nil
}
+114 -127
View File
@@ -42,209 +42,196 @@ func TestUpdateDescription(t *testing.T) {
}
func TestABCIValidator(t *testing.T) {
val := NewValidator(addr1, pk1, Description{})
validator := NewValidator(addr1, pk1, Description{})
abciVal := val.ABCIValidator()
require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey)
require.Equal(t, val.PoolShares.Bonded().RoundInt64(), abciVal.Power)
abciVal := validator.ABCIValidator()
require.Equal(t, tmtypes.TM2PB.PubKey(validator.PubKey), abciVal.PubKey)
require.Equal(t, validator.BondedTokens().RoundInt64(), abciVal.Power)
}
func TestABCIValidatorZero(t *testing.T) {
val := NewValidator(addr1, pk1, Description{})
validator := NewValidator(addr1, pk1, Description{})
abciVal := val.ABCIValidatorZero()
require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey)
abciVal := validator.ABCIValidatorZero()
require.Equal(t, tmtypes.TM2PB.PubKey(validator.PubKey), abciVal.PubKey)
require.Equal(t, int64(0), abciVal.Power)
}
func TestRemovePoolShares(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
func TestRemoveTokens(t *testing.T) {
val := Validator{
validator := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(sdk.NewRat(100)),
Status: sdk.Bonded,
Tokens: sdk.NewRat(100),
DelegatorShares: sdk.NewRat(100),
}
pool.BondedTokens = val.PoolShares.Bonded().RoundInt64()
pool.BondedShares = val.PoolShares.Bonded()
pool := InitialPool()
pool.LooseTokens = sdk.NewRat(10)
pool.BondedTokens = validator.BondedTokens()
val, pool = val.UpdateStatus(pool, sdk.Bonded)
val, pool, tk := val.RemovePoolShares(pool, sdk.NewRat(10))
require.Equal(t, int64(90), val.PoolShares.Amount.RoundInt64())
require.Equal(t, int64(90), pool.BondedTokens)
require.Equal(t, int64(90), pool.BondedShares.RoundInt64())
require.Equal(t, int64(20), pool.LooseTokens)
require.Equal(t, int64(10), tk)
validator, pool = validator.UpdateStatus(pool, sdk.Bonded)
require.Equal(t, sdk.Bonded, validator.Status)
val, pool = val.UpdateStatus(pool, sdk.Unbonded)
val, pool, tk = val.RemovePoolShares(pool, sdk.NewRat(10))
require.Equal(t, int64(80), val.PoolShares.Amount.RoundInt64())
require.Equal(t, int64(0), pool.BondedTokens)
require.Equal(t, int64(0), pool.BondedShares.RoundInt64())
require.Equal(t, int64(30), pool.LooseTokens)
require.Equal(t, int64(10), tk)
// remove tokens and test check everything
validator, pool = validator.RemoveTokens(pool, sdk.NewRat(10))
require.Equal(t, int64(90), validator.Tokens.RoundInt64())
require.Equal(t, int64(90), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(20), pool.LooseTokens.RoundInt64())
// update validator to unbonded and remove some more tokens
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
require.Equal(t, sdk.Unbonded, validator.Status)
require.Equal(t, int64(0), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(110), pool.LooseTokens.RoundInt64())
validator, pool = validator.RemoveTokens(pool, sdk.NewRat(10))
require.Equal(t, int64(80), validator.Tokens.RoundInt64())
require.Equal(t, int64(0), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(110), pool.LooseTokens.RoundInt64())
}
func TestAddTokensValidatorBonded(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
val := NewValidator(addr1, pk1, Description{})
val, pool = val.UpdateStatus(pool, sdk.Bonded)
val, pool, delShares := val.AddTokensFromDel(pool, 10)
pool.LooseTokens = sdk.NewRat(10)
validator := NewValidator(addr1, pk1, Description{})
validator, pool = validator.UpdateStatus(pool, sdk.Bonded)
validator, pool, delShares := validator.AddTokensFromDel(pool, 10)
require.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
require.Equal(t, sdk.OneRat(), pool.BondedShareExRate())
require.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate())
require.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate())
require.Equal(t, sdk.OneRat(), validator.DelegatorShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded()))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.BondedTokens()))
}
func TestAddTokensValidatorUnbonding(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
val := NewValidator(addr1, pk1, Description{})
val, pool = val.UpdateStatus(pool, sdk.Unbonding)
val, pool, delShares := val.AddTokensFromDel(pool, 10)
pool.LooseTokens = sdk.NewRat(10)
validator := NewValidator(addr1, pk1, Description{})
validator, pool = validator.UpdateStatus(pool, sdk.Unbonding)
validator, pool, delShares := validator.AddTokensFromDel(pool, 10)
require.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
require.Equal(t, sdk.OneRat(), pool.BondedShareExRate())
require.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate())
require.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate())
require.Equal(t, sdk.OneRat(), validator.DelegatorShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding()))
assert.Equal(t, sdk.Unbonding, validator.Status)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
}
func TestAddTokensValidatorUnbonded(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
val := NewValidator(addr1, pk1, Description{})
val, pool = val.UpdateStatus(pool, sdk.Unbonded)
val, pool, delShares := val.AddTokensFromDel(pool, 10)
pool.LooseTokens = sdk.NewRat(10)
validator := NewValidator(addr1, pk1, Description{})
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
validator, pool, delShares := validator.AddTokensFromDel(pool, 10)
require.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
require.Equal(t, sdk.OneRat(), pool.BondedShareExRate())
require.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate())
require.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate())
require.Equal(t, sdk.OneRat(), validator.DelegatorShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded()))
assert.Equal(t, sdk.Unbonded, validator.Status)
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.Tokens))
}
// TODO refactor to make simpler like the AddToken tests above
func TestRemoveDelShares(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
valA := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(sdk.NewRat(100)),
Status: sdk.Bonded,
Tokens: sdk.NewRat(100),
DelegatorShares: sdk.NewRat(100),
}
poolA.BondedTokens = valA.PoolShares.Bonded().RoundInt64()
poolA.BondedShares = valA.PoolShares.Bonded()
require.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat())
require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat())
require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat())
poolA := InitialPool()
poolA.LooseTokens = sdk.NewRat(10)
poolA.BondedTokens = valA.BondedTokens()
require.Equal(t, valA.DelegatorShareExRate(), sdk.OneRat())
// Remove delegator shares
valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewRat(10))
assert.Equal(t, int64(10), coinsB.RoundInt64())
assert.Equal(t, int64(90), valB.DelegatorShares.RoundInt64())
assert.Equal(t, int64(90), valB.BondedTokens().RoundInt64())
assert.Equal(t, int64(90), poolB.BondedTokens.RoundInt64())
assert.Equal(t, int64(20), poolB.LooseTokens.RoundInt64())
// coins were created
require.Equal(t, coinsB, int64(10))
// pool shares were removed
require.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA))))
// conservation of tokens
require.Equal(t, poolB.UnbondedTokens+poolB.BondedTokens+coinsB, poolA.UnbondedTokens+poolA.BondedTokens)
require.True(sdk.RatEq(t,
poolB.LooseTokens.Add(poolB.BondedTokens),
poolA.LooseTokens.Add(poolA.BondedTokens)))
// specific case from random tests
poolShares := sdk.NewRat(5102)
poolTokens := sdk.NewRat(5102)
delShares := sdk.NewRat(115)
val := Validator{
validator := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(poolShares),
Status: sdk.Bonded,
Tokens: poolTokens,
DelegatorShares: delShares,
}
pool := Pool{
BondedShares: sdk.NewRat(248305),
UnbondedShares: sdk.NewRat(232147),
BondedTokens: 248305,
UnbondedTokens: 232147,
BondedTokens: sdk.NewRat(248305),
LooseTokens: sdk.NewRat(232147),
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
shares := sdk.NewRat(29)
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
msg = fmt.Sprintf("Removed %v shares from %s", shares, msg)
_, newPool, tokens := val.RemoveDelShares(pool, shares)
require.Equal(t,
tokens+newPool.UnbondedTokens+newPool.BondedTokens,
pool.BondedTokens+pool.UnbondedTokens,
"Tokens were not conserved: %s", msg)
_, newPool, tokens := validator.RemoveDelShares(pool, shares)
require.True(sdk.RatEq(t, sdk.NewRat(147958, 115), tokens))
require.True(sdk.RatEq(t,
newPool.LooseTokens.Add(newPool.BondedTokens),
pool.LooseTokens.Add(pool.BondedTokens)))
}
func TestUpdateStatus(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 100
pool.LooseTokens = sdk.NewRat(100)
val := NewValidator(addr1, pk1, Description{})
val, pool, _ = val.AddTokensFromDel(pool, 100)
require.Equal(t, int64(0), val.PoolShares.Bonded().RoundInt64())
require.Equal(t, int64(0), val.PoolShares.Unbonding().RoundInt64())
require.Equal(t, int64(100), val.PoolShares.Unbonded().RoundInt64())
require.Equal(t, int64(0), pool.BondedTokens)
require.Equal(t, int64(0), pool.UnbondingTokens)
require.Equal(t, int64(100), pool.UnbondedTokens)
validator := NewValidator(addr1, pk1, Description{})
validator, pool, _ = validator.AddTokensFromDel(pool, 100)
require.Equal(t, sdk.Unbonded, validator.Status)
require.Equal(t, int64(100), validator.Tokens.RoundInt64())
require.Equal(t, int64(0), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(100), pool.LooseTokens.RoundInt64())
val, pool = val.UpdateStatus(pool, sdk.Unbonding)
require.Equal(t, int64(0), val.PoolShares.Bonded().RoundInt64())
require.Equal(t, int64(100), val.PoolShares.Unbonding().RoundInt64())
require.Equal(t, int64(0), val.PoolShares.Unbonded().RoundInt64())
require.Equal(t, int64(0), pool.BondedTokens)
require.Equal(t, int64(100), pool.UnbondingTokens)
require.Equal(t, int64(0), pool.UnbondedTokens)
validator, pool = validator.UpdateStatus(pool, sdk.Bonded)
require.Equal(t, sdk.Bonded, validator.Status)
require.Equal(t, int64(100), validator.Tokens.RoundInt64())
require.Equal(t, int64(100), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(0), pool.LooseTokens.RoundInt64())
val, pool = val.UpdateStatus(pool, sdk.Bonded)
require.Equal(t, int64(100), val.PoolShares.Bonded().RoundInt64())
require.Equal(t, int64(0), val.PoolShares.Unbonding().RoundInt64())
require.Equal(t, int64(0), val.PoolShares.Unbonded().RoundInt64())
require.Equal(t, int64(100), pool.BondedTokens)
require.Equal(t, int64(0), pool.UnbondingTokens)
require.Equal(t, int64(0), pool.UnbondedTokens)
validator, pool = validator.UpdateStatus(pool, sdk.Unbonding)
require.Equal(t, sdk.Unbonding, validator.Status)
require.Equal(t, int64(100), validator.Tokens.RoundInt64())
require.Equal(t, int64(0), pool.BondedTokens.RoundInt64())
require.Equal(t, int64(100), pool.LooseTokens.RoundInt64())
}
func TestPossibleOverflow(t *testing.T) {
poolShares := sdk.NewRat(2159)
poolTokens := sdk.NewRat(2159)
delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664))
val := Validator{
validator := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(poolShares),
Status: sdk.Bonded,
Tokens: poolTokens,
DelegatorShares: delShares,
}
pool := Pool{
LooseTokens: 100,
BondedShares: poolShares,
UnbondedShares: sdk.ZeroRat(),
BondedTokens: poolShares.RoundInt64(),
UnbondedTokens: 0,
LooseTokens: sdk.NewRat(100),
BondedTokens: poolTokens,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
tokens := int64(71)
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
newValidator, _, _ := val.AddTokensFromDel(pool, tokens)
msg := fmt.Sprintf("validator %#v", validator)
newValidator, _, _ := validator.AddTokensFromDel(pool, tokens)
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()),
require.False(t, newValidator.DelegatorShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v",
msg, newValidator.DelegatorShareExRate(pool))
msg, newValidator.DelegatorShareExRate())
}
// run random operations in a random order on a random single-validator state, assert invariants hold
@@ -258,10 +245,10 @@ func TestSingleValidatorIntegrationInvariants(t *testing.T) {
// sanity check
AssertInvariants(t, "no operation",
poolOrig, validatorsOrig,
poolOrig, validatorsOrig, 0)
poolOrig, validatorsOrig)
for j := 0; j < 5; j++ {
poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[0])
poolMod, validatorMod, _, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[0])
validatorsMod := make([]Validator, len(validatorsOrig))
copy(validatorsMod[:], validatorsOrig[:])
@@ -271,7 +258,7 @@ func TestSingleValidatorIntegrationInvariants(t *testing.T) {
AssertInvariants(t, msg,
poolOrig, validatorsOrig,
poolMod, validatorsMod, tokens)
poolMod, validatorsMod)
poolOrig = poolMod
validatorsOrig = validatorsMod
@@ -288,18 +275,18 @@ func TestMultiValidatorIntegrationInvariants(t *testing.T) {
AssertInvariants(t, "no operation",
poolOrig, validatorsOrig,
poolOrig, validatorsOrig, 0)
poolOrig, validatorsOrig)
for j := 0; j < 5; j++ {
index := int(r.Int31n(int32(len(validatorsOrig))))
poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[index])
poolMod, validatorMod, _, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[index])
validatorsMod := make([]Validator, len(validatorsOrig))
copy(validatorsMod[:], validatorsOrig[:])
validatorsMod[index] = validatorMod
AssertInvariants(t, msg,
poolOrig, validatorsOrig,
poolMod, validatorsMod, tokens)
poolMod, validatorsMod)
poolOrig = poolMod
validatorsOrig = validatorsMod
@@ -309,11 +296,11 @@ func TestMultiValidatorIntegrationInvariants(t *testing.T) {
}
func TestHumanReadableString(t *testing.T) {
val := NewValidator(addr1, pk1, Description{})
validator := NewValidator(addr1, pk1, Description{})
// NOTE: Being that the validator's keypair is random, we cannot test the
// actual contents of the string.
valStr, err := val.HumanReadableString()
valStr, err := validator.HumanReadableString()
require.Nil(t, err)
require.NotEmpty(t, valStr)
}