remove global shares (#1644)

* wip removing pool shares

* remove PoolShares/Tokens entirely

* worked through stake/type compile error

* work through a bunch of keeper errors

* worked through compile errors

* debugging tests

* resolve compilation error

* resolved types errors

* ...

* move inflation to pool type

* ...

* stumped problem

* Calculate newly issued shares, remove unnecessary pool arg from exchange rate calculation

* Rounding changed

* Update x/slashing tests for sdk.Rat BondedTokens

* testing fixes

* resolved test fixes

* cwgoes comments, changelog, lint

* cli bugfixes

* ..

* cli fixed

* spec update

* 'make format'

* cwgoes comments

* Increase test_cover parallelism
This commit is contained in:
Rigel 2018-07-13 16:46:14 -04:00 committed by Ethan Buchman
parent faa88d83c7
commit 3231daa4d8
41 changed files with 783 additions and 1418 deletions

View File

@ -87,7 +87,7 @@ jobs:
test_cover:
<<: *defaults
parallelism: 2
parallelism: 4
steps:
- attach_workspace:
at: /tmp/workspace
@ -103,7 +103,6 @@ jobs:
make install
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | circleci tests split --split-by=timings); do
id=$(basename "$pkg")
GOCACHE=off go test -v -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
done
- persist_to_workspace:

View File

@ -6,6 +6,8 @@
BREAKING CHANGES
* [x/stake] Specify DelegatorAddress in MsgCreateValidator
* [x/stake] Remove the use of global shares in the pool
* Remove the use of `PoolShares` type in `x/stake/validator` type - replace with `Status` `Tokens` fields
* [x/auth] NewAccountMapper takes a constructor instead of a prototype
* [keys] Keybase.Update function now takes in a function to get the newpass, rather than the password itself

View File

@ -27,7 +27,6 @@ import (
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/stake"
stakerest "github.com/cosmos/cosmos-sdk/x/stake/client/rest"
)
func init() {
@ -834,11 +833,11 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string,
return results[0]
}
func getValidators(t *testing.T, port string) []stakerest.StakeValidatorOutput {
func getValidators(t *testing.T, port string) []stake.BechValidator {
// get the account to get the sequence
res, body := Request(t, port, "GET", "/stake/validators", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var validators []stakerest.StakeValidatorOutput
var validators []stake.BechValidator
err := cdc.UnmarshalJSON([]byte(body), &validators)
require.Nil(t, err)
return validators

View File

@ -146,7 +146,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
accAuth.Coins = sdk.Coins{sdk.NewCoin("steak", 100)}
acc := gapp.NewGenesisAccount(&accAuth)
genesisState.Accounts = append(genesisState.Accounts, acc)
genesisState.StakeData.Pool.LooseTokens += 100
genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewRat(100))
}
appState, err := wire.MarshalJSONIndent(cdc, genesisState)

View File

@ -160,7 +160,7 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
}
acc := NewGenesisAccount(&accAuth)
genaccs[i] = acc
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionsAcc // increase the supply
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewRat(freeFermionsAcc)) // increase the supply
// add the validator
if len(genTx.Name) > 0 {
@ -168,7 +168,7 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
validator := stake.NewValidator(genTx.Address,
sdk.MustGetAccPubKeyBech32(genTx.PubKey), desc)
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionVal // increase the supply
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewRat(freeFermionVal)) // increase the supply
// add some new shares to the validator
var issuedDelShares sdk.Rat

View File

@ -131,7 +131,7 @@ func TestGaiaCLICreateValidator(t *testing.T) {
validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags))
require.Equal(t, validator.Owner, barAddr)
require.Equal(t, "2/1", validator.PoolShares.Amount.String())
require.True(sdk.RatEq(t, sdk.NewRat(2), validator.Tokens))
// unbond a single share
unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags)
@ -149,7 +149,7 @@ func TestGaiaCLICreateValidator(t *testing.T) {
require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc)
*/
validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", barAddr, flags))
require.Equal(t, "1/1", validator.PoolShares.Amount.String())
require.Equal(t, "1/1", validator.Tokens.String())
}
func TestGaiaCLISubmitProposal(t *testing.T) {

View File

@ -6,29 +6,18 @@
- value: `amino(pool)`
The pool is a space for all dynamic global state of the Cosmos Hub. It tracks
information about the total amounts of Atoms in all states, representative
validator shares for stake in the global pools, moving Atom inflation
information, etc.
information about the total amounts of Atoms in all states, moving Atom
inflation information, etc.
```golang
type Pool struct {
LooseTokens int64 // tokens not associated with any validator
UnbondedTokens int64 // reserve of unbonded tokens held with validators
UnbondingTokens int64 // tokens moving from bonded to unbonded pool
LooseTokens int64 // tokens not associated with any bonded validator
BondedTokens int64 // reserve of bonded tokens
UnbondedShares sdk.Rat // sum of all shares distributed for the Unbonded Pool
UnbondingShares sdk.Rat // shares moving from Bonded to Unbonded Pool
BondedShares sdk.Rat // sum of all shares distributed for the Bonded Pool
InflationLastTime int64 // block which the last inflation was processed // TODO make time
Inflation sdk.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily)
}
type PoolShares struct {
Status sdk.BondStatus // either: unbonded, unbonding, or bonded
Amount sdk.Rat // total shares of type ShareKind
}
```
### Params
@ -85,7 +74,8 @@ type Validator struct {
ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator
Revoked bool // has the validator been revoked?
PoolShares PoolShares // total shares for tokens held in the pool
Status sdk.BondStatus // validator status (bonded/unbonding/unbonded)
Tokens sdk.Rat // delegated tokens (incl. self-delegation)
DelegatorShares sdk.Rat // total shares issued to a validator's delegators
SlashRatio sdk.Rat // increases each time the validator is slashed
@ -100,7 +90,7 @@ type Validator struct {
ProposerRewardPool sdk.Coins // reward pool collected from being the proposer
// TODO: maybe this belongs in distribution module ?
PrevPoolShares PoolShares // total shares of a global hold pools
LastBondedTokens sdk.Rat // last bonded token amount
}
type CommissionInfo struct {

View File

@ -11,9 +11,9 @@ import (
)
var kvPairs = []KVPair{
KVPair{Key: keyFmt(1), Value: valFmt(1)},
KVPair{Key: keyFmt(2), Value: valFmt(2)},
KVPair{Key: keyFmt(3), Value: valFmt(3)},
{Key: keyFmt(1), Value: valFmt(1)},
{Key: keyFmt(2), Value: valFmt(2)},
{Key: keyFmt(3), Value: valFmt(3)},
}
func newTraceKVStore(w io.Writer) *TraceKVStore {

View File

@ -252,3 +252,11 @@ func RatsEqual(r1s, r2s []Rat) bool {
func RatEq(t *testing.T, exp, got Rat) (*testing.T, bool, string, Rat, Rat) {
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
}
// minimum rational between two
func MinRat(r1, r2 Rat) Rat {
if r1.LT(r2) {
return r1
}
return r2
}

View File

@ -26,10 +26,15 @@ func BondStatusToString(b BondStatus) string {
case 0x02:
return "Bonded"
default:
return ""
panic("improper use of BondStatusToString")
}
}
// nolint
func (b BondStatus) Equal(b2 BondStatus) bool {
return byte(b) == byte(b2)
}
// validator for a delegated proof of stake system
type Validator interface {
GetRevoked() bool // whether the validator is revoked

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 {

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

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())
}

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)

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))

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()))

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)
}
}

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)

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

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) {

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())

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

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)
}

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)
}
}
}

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))

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 {

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
}
//__________________________________________________________________________

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)
}

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)

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)
}

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))
}

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)

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

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)
}
}
}

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)
}

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))
}

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")
}
}

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())
}

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

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)
}
case sdk.Bonded:
switch NewStatus {
case sdk.Bonded:
return v, pool
default:
pool = pool.bondedTokensToLoose(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)
}
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
}

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())
valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewRat(10))
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)
}