From b64363fcbed898a2532f003930e7ca51e6ec6996 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 12 May 2018 18:22:59 -0400 Subject: [PATCH] split types into multiple files, fix delegation share exrate --- CHANGELOG.md | 6 + cmd/gaia/app/genesis.go | 4 +- x/stake/delegation.go | 33 ++++ x/stake/genesis.go | 54 ++++++ x/stake/handler.go | 32 ---- x/stake/keeper.go | 7 +- x/stake/keeper_keys.go | 20 ++- x/stake/keeper_test.go | 268 ++++++++++++++++++----------- x/stake/params.go | 36 ++++ x/stake/pool.go | 202 ++++++++++++++++------ x/stake/tick.go | 2 +- x/stake/types_test.go | 3 - x/stake/{types.go => validator.go} | 157 +---------------- 13 files changed, 473 insertions(+), 351 deletions(-) create mode 100644 x/stake/delegation.go create mode 100644 x/stake/genesis.go create mode 100644 x/stake/params.go delete mode 100644 x/stake/types_test.go rename x/stake/{types.go => validator.go} (50%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee9da0498..c686912a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ FEATURES * Seperation of fee distribution to a new module * Creation of a validator/delegation generics in `/types` +BUG FIXES + +* Auto-sequencing now works correctly +* staking delegator shares exchange rate now relative to equivalent-bonded-tokens the validator has instead of bonded tokens + + ## 0.17.0 (May 15, 2018) BREAKING CHANGES diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index e96fe2fba9..7ca2412000 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -166,8 +166,8 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso // pool logic stakeData.Pool.TotalSupply += freeFermionVal - stakeData.Pool.BondedPool += freeFermionVal - stakeData.Pool.BondedShares = sdk.NewRat(stakeData.Pool.BondedPool) + stakeData.Pool.BondedTokens += freeFermionVal + stakeData.Pool.BondedShares = sdk.NewRat(stakeData.Pool.BondedTokens) } } diff --git a/x/stake/delegation.go b/x/stake/delegation.go new file mode 100644 index 0000000000..89afe8e907 --- /dev/null +++ b/x/stake/delegation.go @@ -0,0 +1,33 @@ +package stake + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Delegation represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +// TODO better way of managing space +type Delegation struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` + Shares sdk.Rat `json:"shares"` + Height int64 `json:"height"` // Last height bond updated +} + +func (b Delegation) equal(b2 Delegation) bool { + return bytes.Equal(b.DelegatorAddr, b2.DelegatorAddr) && + bytes.Equal(b.ValidatorAddr, b2.ValidatorAddr) && + b.Height == b2.Height && + b.Shares.Equal(b2.Shares) +} + +// ensure fulfills the sdk validator types +var _ sdk.Delegation = Delegation{} + +// nolint - for sdk.Delegation +func (b Delegation) GetDelegator() sdk.Address { return b.DelegatorAddr } +func (b Delegation) GetValidator() sdk.Address { return b.ValidatorAddr } +func (b Delegation) GetBondShares() sdk.Rat { return b.Shares } diff --git a/x/stake/genesis.go b/x/stake/genesis.go new file mode 100644 index 0000000000..62fdeeeaa9 --- /dev/null +++ b/x/stake/genesis.go @@ -0,0 +1,54 @@ +package stake + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// GenesisState - all staking state that must be provided at genesis +type GenesisState struct { + Pool Pool `json:"pool"` + Params Params `json:"params"` + Validators []Validator `json:"validators"` + Bonds []Delegation `json:"bonds"` +} + +func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { + return GenesisState{ + Pool: pool, + Params: params, + Validators: validators, + Bonds: bonds, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Pool: initialPool(), + Params: defaultParams(), + } +} + +// InitGenesis - store genesis parameters +func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + k.setPool(ctx, data.Pool) + k.setParams(ctx, data.Params) + for _, validator := range data.Validators { + k.setValidator(ctx, validator) + } + for _, bond := range data.Bonds { + k.setDelegation(ctx, bond) + } +} + +// WriteGenesis - output genesis parameters +func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { + pool := k.GetPool(ctx) + params := k.GetParams(ctx) + validators := k.GetValidators(ctx, 32767) + bonds := k.getBonds(ctx, 32767) + return GenesisState{ + pool, + params, + validators, + bonds, + } +} diff --git a/x/stake/handler.go b/x/stake/handler.go index b140696d0f..3d0b23359c 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -15,8 +15,6 @@ const ( GasUnbond int64 = 20 ) -//_______________________________________________________________________ - func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { // NOTE msg already has validate basic run @@ -35,8 +33,6 @@ func NewHandler(k Keeper) sdk.Handler { } } -//_____________________________________________________________________ - // NewEndBlocker generates sdk.EndBlocker // Performs tick functionality func NewEndBlocker(k Keeper) sdk.EndBlocker { @@ -48,34 +44,6 @@ func NewEndBlocker(k Keeper) sdk.EndBlocker { //_____________________________________________________________________ -// InitGenesis - store genesis parameters -func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - k.setPool(ctx, data.Pool) - k.setParams(ctx, data.Params) - for _, validator := range data.Validators { - k.setValidator(ctx, validator) - } - for _, bond := range data.Bonds { - k.setDelegation(ctx, bond) - } -} - -// WriteGenesis - output genesis parameters -func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { - pool := k.GetPool(ctx) - params := k.GetParams(ctx) - validators := k.GetValidators(ctx, 32767) - bonds := k.getBonds(ctx, 32767) - return GenesisState{ - pool, - params, - validators, - bonds, - } -} - -//_____________________________________________________________________ - // These functions assume everything has been authenticated, // now we just perform action and save diff --git a/x/stake/keeper.go b/x/stake/keeper.go index b95247ade1..5e178be30e 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -91,7 +91,7 @@ func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { powerIncreasing = true } // delete the old record in the power ordered list - store.Delete(GetValidatorsBondedByPowerKey(oldValidator)) + store.Delete(GetValidatorsBondedByPowerKey(oldValidator, pool)) } // if already a validator, copy the old block height and counter, else set them @@ -107,7 +107,7 @@ func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { // update the list ordered by voting power bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorsBondedByPowerKey(validator), bz) + store.Set(GetValidatorsBondedByPowerKey(validator, pool), bz) // add to the validators and return to update list if is already a validator and power is increasing if powerIncreasing && oldValidator.Status == sdk.Bonded { @@ -143,8 +143,9 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { // delete the old validator record store := ctx.KVStore(k.storeKey) + pool := k.getPool(store) store.Delete(GetValidatorKey(address)) - store.Delete(GetValidatorsBondedByPowerKey(validator)) + store.Delete(GetValidatorsBondedByPowerKey(validator, pool)) // delete from current and power weighted validator groups if the validator // exists and add validator with zero power to the validator updates diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index f449b6f022..eef0ce98a3 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -13,14 +13,14 @@ import ( //nolint var ( // Keys for store prefixes - ParamKey = []byte{0x00} // key for global parameters relating to staking - PoolKey = []byte{0x01} // key for global parameters relating to staking - ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByPowerKey = []byte{0x03} // prefix for each key to a validator + ParamKey = []byte{0x00} // key for global parameters relating to staking + PoolKey = []byte{0x01} // key for global parameters relating to staking + ValidatorsKey = []byte{0x02} // prefix for each key to a validator + ValidatorsByPowerKey = []byte{0x03} // prefix for each key to a validator TendermintUpdatesKey = []byte{0x04} // prefix for each key to a validator which is being updated - ValidatorsBondedKey = []byte{0x05} // prefix for each key to bonded/actively validating validators - DelegationKey = []byte{0x06} // prefix for each key to a delegator's bond - IntraTxCounterKey = []byte{0x07} // key for block-local tx index + ValidatorsBondedKey = []byte{0x05} // prefix for each key to bonded/actively validating validators + DelegationKey = []byte{0x06} // prefix for each key to a delegator's bond + IntraTxCounterKey = []byte{0x07} // key for block-local tx index ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -31,8 +31,10 @@ func GetValidatorKey(addr sdk.Address) []byte { } // get the key for the validator used in the power-store -func GetValidatorsBondedByPowerKey(validator Validator) []byte { - powerBytes := []byte(validator.BondedShares.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) +func GetValidatorsBondedByPowerKey(validator Validator, pool Pool) []byte { + + power := pool.EquivalentBondedShares(validator) + powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) // TODO ensure that the key will be a readable string.. probably should add seperators and have // heightBytes and counterBytes represent strings like powerBytes does diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 59f2d06385..526fd22786 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -39,7 +39,7 @@ func TestValidatorBasics(t *testing.T) { // check the empty keeper first _, found := keeper.GetValidator(ctx, addrVals[0]) - assert.False(t, found) + UnbondedSharesassert.False(t, found) resVals := keeper.GetValidatorsBonded(ctx) assert.Zero(t, len(resVals)) @@ -88,7 +88,7 @@ func TestValidatorBasics(t *testing.T) { } // test how the validators are sorted, tests GetValidatorsBondedByPower -func GetValidatorSorting(t *testing.T) { +func GetValidatorSortingUnmixed(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) // initialize some validators into the state @@ -121,16 +121,15 @@ func GetValidatorSorting(t *testing.T) { keeper.setValidator(ctx, validators[3]) gotValidators = keeper.GetValidatorsBondedByPower(ctx) require.Equal(t, len(gotValidators), n) - assert.Equal(ValEq(t, validators[3], gotValidators[0])) + assert.True(ValEq(t, validators[3], gotValidators[0])) // test a decrease in voting power validators[3].BondedShares = sdk.NewRat(300) keeper.setValidator(ctx, validators[3]) gotValidators = keeper.GetValidatorsBondedByPower(ctx) require.Equal(t, len(gotValidators), n) - assert.Equal(t, sdk.NewRat(300), gotValidators[0].BondedShares, "%v", gotValidators) - assert.Equal(t, validators[3].Address, gotValidators[0].Address, "%v", gotValidators) - assert.Equal(t, validators[4].Address, gotValidators[1].Address, "%v", gotValidators) + assert.True(ValEq(t, validators[3], gotValidators[0])) + assert.True(ValEq(t, validators[4], gotValidators[1])) // test equal voting power, different age validators[3].BondedShares = sdk.NewRat(200) @@ -138,10 +137,8 @@ func GetValidatorSorting(t *testing.T) { keeper.setValidator(ctx, validators[3]) gotValidators = keeper.GetValidatorsBondedByPower(ctx) require.Equal(t, len(gotValidators), n) - assert.Equal(t, sdk.NewRat(200), gotValidators[0].BondedShares, "%v", gotValidators) - assert.Equal(t, sdk.NewRat(200), gotValidators[1].BondedShares, "%v", gotValidators) - assert.Equal(t, validators[3].Address, gotValidators[0].Address, "%v", gotValidators) - assert.Equal(t, validators[4].Address, gotValidators[1].Address, "%v", gotValidators) + assert.True(ValEq(t, validators[3], gotValidators[0])) + assert.True(ValEq(t, validators[4], gotValidators[1])) assert.Equal(t, int64(0), gotValidators[0].BondHeight, "%v", gotValidators) assert.Equal(t, int64(0), gotValidators[1].BondHeight, "%v", gotValidators) @@ -150,8 +147,8 @@ func GetValidatorSorting(t *testing.T) { keeper.setValidator(ctx, validators[4]) gotValidators = keeper.GetValidatorsBondedByPower(ctx) require.Equal(t, len(gotValidators), n) - assert.Equal(t, validators[3].Address, gotValidators[0].Address, "%v", gotValidators) - assert.Equal(t, validators[4].Address, gotValidators[1].Address, "%v", gotValidators) + assert.True(ValEq(t, validators[3], gotValidators[0])) + assert.True(ValEq(t, validators[4], gotValidators[1])) // change in voting power of both validators, both still in v-set, no age change validators[3].BondedShares = sdk.NewRat(300) @@ -163,14 +160,169 @@ func GetValidatorSorting(t *testing.T) { keeper.setValidator(ctx, validators[4]) gotValidators = keeper.GetValidatorsBondedByPower(ctx) require.Equal(t, len(gotValidators), n, "%v", gotValidators) + assert.True(ValEq(t, validators[3], gotValidators[0])) + assert.True(ValEq(t, validators[4], gotValidators[1])) +} + +func GetValidatorSortingMixed(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + + // now 2 max gotValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.setParams(ctx, params) + + pool := keeper.GetPool(ctx) + + // initialize some validators into the state + amts := []int64{0, 100, 1, 400, 200} + + n := len(amts) + var validators [5]Validator + for i, amt := range amts { + validators[i] = NewValidator(addrs[i], pks[i], Description{}) + validators[i].DelegatorShares = sdk.NewRat(amt) + } + validators[0].UnbondedShares = sdk.NewRat(amts[0]) + validators[1].UnbondedShares = sdk.NewRat(amts[1]) + validators[2].UnbondedShares = sdk.NewRat(amts[2]) + validators[3].BondedShares = sdk.NewRat(amts[3]) + validators[4].BondedShares = sdk.NewRat(amts[4]) + for i := range amts { + keeper.setValidator(ctx, validators[i]) + } + assert.Equal(t, sdk.Unbonded, keeper.GetValidator(ctx, addr[0]).Status) + assert.Equal(t, sdk.Unbonded, keeper.GetValidator(ctx, addr[1]).Status) + assert.Equal(t, sdk.Unbonded, keeper.GetValidator(ctx, addr[2]).Status) + assert.Equal(t, sdk.Bonded, keeper.GetValidator(ctx, addr[3]).Status) + assert.Equal(t, sdk.Bonded, keeper.GetValidator(ctx, addr[4]).Status) + + // first make sure everything made it in to the gotValidator group + gotValidators := keeper.GetValidatorsBondedByPower(ctx) + require.Equal(t, n, len(gotValidators)) + assert.Equal(t, sdk.NewRat(400), gotValidators[0].BondedShares, "%v", gotValidators) + assert.Equal(t, sdk.NewRat(200), gotValidators[1].BondedShares, "%v", gotValidators) + assert.Equal(t, sdk.NewRat(100), gotValidators[2].BondedShares, "%v", gotValidators) + assert.Equal(t, sdk.NewRat(1), gotValidators[3].BondedShares, "%v", gotValidators) + assert.Equal(t, sdk.NewRat(0), gotValidators[4].BondedShares, "%v", gotValidators) assert.Equal(t, validators[3].Address, gotValidators[0].Address, "%v", gotValidators) assert.Equal(t, validators[4].Address, gotValidators[1].Address, "%v", gotValidators) + assert.Equal(t, validators[1].Address, gotValidators[2].Address, "%v", gotValidators) + assert.Equal(t, validators[2].Address, gotValidators[3].Address, "%v", gotValidators) + assert.Equal(t, validators[0].Address, gotValidators[4].Address, "%v", gotValidators) } // TODO seperate out into multiple tests func TestGetValidatorsEdgeCases(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) + // now 2 max gotValidators + params := keeper.GetParams(ctx) + nMax := uint16(2) + params.MaxValidators = nMax + keeper.setParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 400, 400} + var validators [5]Validator + for i, amt := range amts { + validators[i] = NewValidator(addrs[i], pks[i], Description{}) + validators[i].DelegatorShares = sdk.NewRat(amt) + } + validators[0].UnbondedShares = sdk.NewRat(amts[0]) + validators[1].UnbondedShares = sdk.NewRat(amts[1]) + validators[2].BondedShares = sdk.NewRat(amts[2]) + validators[3].BondedShares = sdk.NewRat(amts[3]) + for i := range amts { + keeper.setValidator(ctx, validators[i]) + } + + validators[0].UnbondedShares = sdk.NewRat(500) + keeper.setValidator(ctx, validators[0]) + gotValidators := keeper.GetValidatorsBondedByPower(ctx) + require.Equal(t, nMax, uint16(len(gotValidators))) + assert.True(ValEq(t, validators[0], gotValidators[0])) + + // validator 3 was set before validator 4 + assert.True(ValEq(t, validators[2], gotValidators[1])) + + // A validator which leaves the gotValidator set due to a decrease in voting power, + // then increases to the original voting power, does not get its spot back in the + // case of a tie. + // ref https://github.com/cosmos/cosmos-sdk/issues/582#issuecomment-380757108 + validators[3].BondedShares = sdk.NewRat(401) + keeper.setValidator(ctx, validators[3]) + gotValidators = keeper.GetValidatorsBondedByPower(ctx) + require.Equal(t, nMax, uint16(len(gotValidators))) + assert.True(ValEq(t, validators[0], gotValidators[0])) + assert.True(ValEq(t, validators[3], gotValidators[1])) + ctx = ctx.WithBlockHeight(40) + + // validator 3 kicked out temporarily + validators[3].BondedShares = sdk.NewRat(200) + keeper.setValidator(ctx, validators[3]) + gotValidators = keeper.GetValidatorsBondedByPower(ctx) + require.Equal(t, nMax, uint16(len(gotValidators))) + assert.True(ValEq(t, validators[0], gotValidators[0])) + assert.True(ValEq(t, validators[2], gotValidators[1])) + + // validator 4 does not get spot back + validators[3].BondedShares = sdk.NewRat(400) + keeper.setValidator(ctx, validators[3]) + gotValidators = keeper.GetValidatorsBondedByPower(ctx) + require.Equal(t, nMax, uint16(len(gotValidators))) + assert.True(ValEq(t, validators[0], gotValidators[0])) + assert.True(ValEq(t, validators[2], gotValidators[1])) + validator, exists := keeper.GetValidator(ctx, validators[3].Address) + require.Equal(t, exists, true) + require.Equal(t, validator.BondHeight, int64(40)) +} + +// TODO seperate out into multiple tests +func TestValidatorBondHeight(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + + // now 2 max gotValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.setParams(ctx, params) + + // initialize some validators into the state + var validators [3]Validator + validators[0] = NewValidator(addrs[0], pks[0], Description{}) + validators[0].BondedShares = sdk.NewRat(200) + validators[0].DelegatorShares = sdk.NewRat(200) + keeper.setValidator(ctx, validators[0]) + validators[1] = NewValidator(addrs[1], pks[1], Description{}) + validators[1].BondedShares = sdk.NewRat(100) + validators[1].DelegatorShares = sdk.NewRat(100) + validators[2] = NewValidator(addrs[2], pks[2], Description{}) + validators[2].UnbondedShares = sdk.NewRat(100) + validators[2].DelegatorShares = sdk.NewRat(100) + + //////////////////////////////////////// + // If two validators both increase to the same voting power in the same block, + // the one with the first transaction should become bonded + keeper.setValidator(ctx, validators[1]) + keeper.setValidator(ctx, validators[2]) + gotValidators := keeper.GetValidatorsBondedByPower(ctx) + require.Equal(t, uint16(len(gotValidators)), params.MaxValidators) + assert.True(ValEq(t, validators[0], gotValidators[0])) + assert.True(ValEq(t, validators[1], gotValidators[1])) + validators[1].BondedShares = sdk.NewRat(1100) + validators[2].BondedShares = sdk.NewRat(1100) + keeper.setValidator(ctx, validators[2]) + keeper.setValidator(ctx, validators[1]) + gotValidators = keeper.GetValidatorsBondedByPower(ctx) + require.Equal(t, params.MaxValidators, uint16(len(gotValidators))) + assert.True(ValEq(t, validators[0], gotValidators[0])) + assert.True(ValEq(t, validators[2], gotValidators[1])) +} + +// XXX rename test +func TestGetValidatorsEdgeCases2(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + // now 2 max gotValidators params := keeper.GetParams(ctx) params.MaxValidators = 2 @@ -187,89 +339,13 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.setValidator(ctx, validators[i]) } - validators[0].BondedShares = sdk.NewRat(500) - keeper.setValidator(ctx, validators[0]) - gotValidators := keeper.GetValidatorsBondedByPower(ctx) - require.Equal(t, uint16(len(gotValidators)), params.MaxValidators) - require.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - // validator 3 was set before validator 4 - require.Equal(t, validators[2].Address, gotValidators[1].Address, "%v", gotValidators) - - // A validator which leaves the gotValidator set due to a decrease in voting power, - // then increases to the original voting power, does not get its spot back in the - // case of a tie. - // ref https://github.com/cosmos/cosmos-sdk/issues/582#issuecomment-380757108 - validators[3].BondedShares = sdk.NewRat(401) - keeper.setValidator(ctx, validators[3]) - gotValidators = keeper.GetValidatorsBondedByPower(ctx) - require.Equal(t, uint16(len(gotValidators)), params.MaxValidators) - require.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - require.Equal(t, validators[3].Address, gotValidators[1].Address, "%v", gotValidators) - ctx = ctx.WithBlockHeight(40) - // validator 3 kicked out temporarily - validators[3].BondedShares = sdk.NewRat(200) - keeper.setValidator(ctx, validators[3]) - gotValidators = keeper.GetValidatorsBondedByPower(ctx) - require.Equal(t, uint16(len(gotValidators)), params.MaxValidators) - require.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - require.Equal(t, validators[2].Address, gotValidators[1].Address, "%v", gotValidators) - // validator 4 does not get spot back - validators[3].BondedShares = sdk.NewRat(400) - keeper.setValidator(ctx, validators[3]) - gotValidators = keeper.GetValidatorsBondedByPower(ctx) - require.Equal(t, uint16(len(gotValidators)), params.MaxValidators) - require.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - require.Equal(t, validators[2].Address, gotValidators[1].Address, "%v", gotValidators) - validator, exists := keeper.GetValidator(ctx, validators[3].Address) - require.Equal(t, exists, true) - require.Equal(t, validator.BondHeight, int64(40)) - - // If two validators both increase to the same voting power in the same block, - // the one with the first transaction should take precedence (become a gotValidator). - // ref https://github.com/cosmos/cosmos-sdk/issues/582#issuecomment-381250392 - validators[0].BondedShares = sdk.NewRat(2000) - keeper.setValidator(ctx, validators[0]) - validators[1].BondedShares = sdk.NewRat(1000) - validators[2].BondedShares = sdk.NewRat(1000) - keeper.setValidator(ctx, validators[1]) - keeper.setValidator(ctx, validators[2]) - gotValidators = keeper.GetValidatorsBondedByPower(ctx) - require.Equal(t, uint16(len(gotValidators)), params.MaxValidators) - require.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - require.Equal(t, validators[1].Address, gotValidators[1].Address, "%v", gotValidators) - validators[1].BondedShares = sdk.NewRat(1100) - validators[2].BondedShares = sdk.NewRat(1100) - keeper.setValidator(ctx, validators[2]) - keeper.setValidator(ctx, validators[1]) - gotValidators = keeper.GetValidatorsBondedByPower(ctx) - require.Equal(t, uint16(len(gotValidators)), params.MaxValidators) - require.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - require.Equal(t, validators[2].Address, gotValidators[1].Address, "%v", gotValidators) - - // reset assets / heights - params.MaxValidators = 100 - keeper.setParams(ctx, params) - validators[0].BondedShares = sdk.NewRat(0) - validators[1].BondedShares = sdk.NewRat(100) - validators[2].BondedShares = sdk.NewRat(1) - validators[3].BondedShares = sdk.NewRat(300) - validators[4].BondedShares = sdk.NewRat(200) - ctx = ctx.WithBlockHeight(0) - keeper.setValidator(ctx, validators[0]) - keeper.setValidator(ctx, validators[1]) - keeper.setValidator(ctx, validators[2]) - keeper.setValidator(ctx, validators[3]) - keeper.setValidator(ctx, validators[4]) - // test a swap in voting power validators[0].BondedShares = sdk.NewRat(600) keeper.setValidator(ctx, validators[0]) - gotValidators = keeper.GetValidatorsBondedByPower(ctx) + gotValidators := keeper.GetValidatorsBondedByPower(ctx) require.Equal(t, len(gotValidators), n) - assert.Equal(t, sdk.NewRat(600), gotValidators[0].BondedShares, "%v", gotValidators) - assert.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - assert.Equal(t, sdk.NewRat(300), gotValidators[1].BondedShares, "%v", gotValidators) - assert.Equal(t, validators[3].Address, gotValidators[1].Address, "%v", gotValidators) + assert.True(ValEq(t, validators[0], gotValidators[0])) + assert.True(ValEq(t, validators[3], gotValidators[1])) // test the max gotValidators term params = keeper.GetParams(ctx) @@ -278,10 +354,8 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.setParams(ctx, params) gotValidators = keeper.GetValidatorsBondedByPower(ctx) require.Equal(t, len(gotValidators), n) - assert.Equal(t, sdk.NewRat(600), gotValidators[0].BondedShares, "%v", gotValidators) - assert.Equal(t, validators[0].Address, gotValidators[0].Address, "%v", gotValidators) - assert.Equal(t, sdk.NewRat(300), gotValidators[1].BondedShares, "%v", gotValidators) - assert.Equal(t, validators[3].Address, gotValidators[1].Address, "%v", gotValidators) + assert.True(ValEq(t, validators[0], gotValidators[0])) + assert.True(ValEq(t, validators[3], gotValidators[1])) } // clear the tracked changes to the gotValidator set @@ -343,8 +417,8 @@ func TestGetTendermintUpdates(t *testing.T) { require.Equal(t, 2, len(validators)) assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) assert.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1]) - assert.True(t, validators[0].equal(vals[1])) - assert.True(t, validators[1].equal(vals[0])) + assert.True(ValEq(t, validators[0], vals[1])) + assert.True(ValEq(t, validators[1], vals[0])) // test identical, // validator set: {c1, c3} -> {c1, c3} diff --git a/x/stake/params.go b/x/stake/params.go new file mode 100644 index 0000000000..9fcae9f828 --- /dev/null +++ b/x/stake/params.go @@ -0,0 +1,36 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Params defines the high level settings for staking +type Params struct { + InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax sdk.Rat `json:"inflation_max"` // maximum inflation rate + InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate + GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms + + MaxValidators uint16 `json:"max_validators"` // maximum number of validators + BondDenom string `json:"bond_denom"` // bondable coin denomination +} + +func (p Params) equal(p2 Params) bool { + return p.InflationRateChange.Equal(p2.InflationRateChange) && + p.InflationMax.Equal(p2.InflationMax) && + p.InflationMin.Equal(p2.InflationMin) && + p.GoalBonded.Equal(p2.GoalBonded) && + p.MaxValidators == p2.MaxValidators && + p.BondDenom == p2.BondDenom +} + +func defaultParams() Params { + return Params{ + InflationRateChange: sdk.NewRat(13, 100), + InflationMax: sdk.NewRat(20, 100), + InflationMin: sdk.NewRat(7, 100), + GoalBonded: sdk.NewRat(67, 100), + MaxValidators: 100, + BondDenom: "steak", + } +} diff --git a/x/stake/pool.go b/x/stake/pool.go index b406e2f248..7abaa7ae83 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -4,10 +4,59 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// Pool - dynamic parameters of the current state +type Pool struct { + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool + UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedTokens int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondingTokens int64 `json:"unbonding_pool"` // tokens moving from bonded to unbonded pool + UnbondedTokens int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with validators + 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 + + DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) + + // Fee Related + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calcualtions +} + +func (p Pool) equal(p2 Pool) bool { + return p.TotalSupply == p2.TotalSupply && + p.BondedShares.Equal(p2.BondedShares) && + p.UnbondedShares.Equal(p2.UnbondedShares) && + p.BondedTokens == p2.BondedTokens && + p.UnbondedTokens == p2.UnbondedTokens && + p.InflationLastTime == p2.InflationLastTime && + p.Inflation.Equal(p2.Inflation) && + p.DateLastCommissionReset == p2.DateLastCommissionReset && + p.PrevBondedShares.Equal(p2.PrevBondedShares) +} + +// initial pool for testing +func initialPool() Pool { + return Pool{ + TotalSupply: 0, + BondedShares: sdk.ZeroRat(), + UnbondingShares: sdk.ZeroRat(), + UnbondedShares: sdk.ZeroRat(), + BondedTokens: 0, + UnbondingTokens: 0, + UnbondedTokens: 0, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + DateLastCommissionReset: 0, + PrevBondedShares: sdk.ZeroRat(), + } +} + +//____________________________________________________________________ + // get the bond ratio of the global state func (p Pool) bondedRatio() sdk.Rat { if p.TotalSupply > 0 { - return sdk.NewRat(p.BondedPool, p.TotalSupply) + return sdk.NewRat(p.BondedTokens, p.TotalSupply) } return sdk.ZeroRat() } @@ -17,7 +66,7 @@ func (p Pool) bondedShareExRate() sdk.Rat { if p.BondedShares.IsZero() { return sdk.OneRat() } - return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) + return sdk.NewRat(p.BondedTokens).Quo(p.BondedShares) } // get the exchange rate of unbonding tokens held in validators per issued share @@ -25,7 +74,7 @@ func (p Pool) unbondingShareExRate() sdk.Rat { if p.UnbondingShares.IsZero() { return sdk.OneRat() } - return sdk.NewRat(p.UnbondingPool).Quo(p.UnbondingShares) + return sdk.NewRat(p.UnbondingTokens).Quo(p.UnbondingShares) } // get the exchange rate of unbonded tokens held in validators per issued share @@ -33,9 +82,56 @@ func (p Pool) unbondedShareExRate() sdk.Rat { if p.UnbondedShares.IsZero() { return sdk.OneRat() } - return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) + return sdk.NewRat(p.UnbondedTokens).Quo(p.UnbondedShares) } +//_______________________________________________________________________ + +func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { + issuedShares = sdk.NewRat(amount).Quo(p.bondedShareExRate()) // tokens * (shares/tokens) + p.BondedTokens += amount + p.BondedShares = p.BondedShares.Add(issuedShares) + return p, issuedShares +} + +func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.BondedShares = p.BondedShares.Sub(shares) + p.BondedTokens = p.BondedTokens - removedTokens + return p, removedTokens +} + +func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares sdk.Rat) { + issuedShares = sdk.NewRat(amount).Quo(p.unbondingShareExRate()) // tokens * (shares/tokens) + p.UnbondingShares = p.UnbondingShares.Add(issuedShares) + p.UnbondingTokens += amount + return p, issuedShares +} + +func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.unbondingShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondingShares = p.UnbondingShares.Sub(shares) + p.UnbondingTokens -= removedTokens + return p, removedTokens +} + +func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { + issuedShares = sdk.NewRat(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens) + p.UnbondedShares = p.UnbondedShares.Add(issuedShares) + p.UnbondedTokens += amount + return p, issuedShares +} + +func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondedShares = p.UnbondedShares.Sub(shares) + p.UnbondedTokens -= removedTokens + return p, removedTokens +} + +//_______________________________________________________________________ +// Validator Properties TODO move back to types.go under validator (maybe split types.go) + // XXX write test // update the location of the shares within a validator if its bond status has changed func (p Pool) UpdateSharesLocation(validator Validator) (Pool, Validator) { @@ -71,67 +167,61 @@ func (p Pool) UpdateSharesLocation(validator Validator) (Pool, Validator) { return p, validator } -//_______________________________________________________________________ - -func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { - issuedShares = sdk.NewRat(amount).Quo(p.bondedShareExRate()) // tokens * (shares/tokens) - p.BondedPool += amount - p.BondedShares = p.BondedShares.Add(issuedShares) - return p, issuedShares +// XXX TEST +// 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 (p Pool) EquivalentBondedShares(validator Validator) (power sdk.Rat) { + switch validator.Status { + case sdk.Bonded: + power = validator.BondedShares + case sdk.Unbonding: + shares := validator.UnbondingShares // ubShr + exRate := p.unbondingShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + power = shares.Mul(exRate) // ubshr*bshr/ubshr = bshr + case sdk.Unbonded, sdk.Revoked: + shares := validator.UnbondedShares // ubShr + exRate := p.unbondedShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + power = shares.Mul(exRate) // ubshr*bshr/ubshr = bshr + } + return } -func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { - removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - p.BondedShares = p.BondedShares.Sub(shares) - p.BondedPool = p.BondedPool - removedTokens - return p, removedTokens +// get the equivalent amount of tokens contained by a validator +func (p Pool) validatorTokens(v Validator) sdk.Rat { + switch v.Status { + case sdk.Bonded: + return p.unbondedShareExRate().Mul(v.BondedShares) // (tokens/shares) * shares + case sdk.Unbonding: + return p.unbondedShareExRate().Mul(v.UnbondingShares) + case sdk.Unbonded, sdk.Revoked: + return p.unbondedShareExRate().Mul(v.UnbondedShares) + } + return sdk.ZeroRat() } -func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares sdk.Rat) { - issuedShares = sdk.NewRat(amount).Quo(p.unbondingShareExRate()) // tokens * (shares/tokens) - p.UnbondingShares = p.UnbondingShares.Add(issuedShares) - p.UnbondingPool += amount - return p, issuedShares -} - -func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) { - removedTokens = p.unbondingShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - p.UnbondingShares = p.UnbondingShares.Sub(shares) - p.UnbondingPool -= removedTokens - return p, removedTokens -} - -func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { - issuedShares = sdk.NewRat(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens) - p.UnbondedShares = p.UnbondedShares.Add(issuedShares) - p.UnbondedPool += amount - return p, issuedShares -} - -func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { - removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - p.UnbondedShares = p.UnbondedShares.Sub(shares) - p.UnbondedPool -= removedTokens - return p, removedTokens -} - -//_______________________________________________________________________ - +// XXX Audit this function further to make sure it's correct // add tokens to a validator func (p Pool) validatorAddTokens(validator Validator, amount int64) (p2 Pool, validator2 Validator, issuedDelegatorShares sdk.Rat) { - exRate := validator.DelegatorShareExRate() - - var receivedGlobalShares sdk.Rat - if validator.Status == sdk.Bonded { - p, receivedGlobalShares = p.addTokensBonded(amount) - } else { - p, receivedGlobalShares = p.addTokensUnbonded(amount) + var poolShares sdk.Rat + switch validator.Status { + case sdk.Bonded: + p, poolShares = p.addTokensBonded(amount) + validator.BondedShares = validator.BondedShares.Add(poolShares) + case sdk.Unbonding: + p, poolShares = p.addTokensUnbonding(amount) + validator.UnbondingShares = validator.UnbondingShares.Add(poolShares) + case sdk.Unbonded, sdk.Revoked: + p, poolShares = p.addTokensUnbonded(amount) + validator.UnbondedShares = validator.UnbondedShares.Add(poolShares) } - validator.BondedShares = validator.BondedShares.Add(receivedGlobalShares) - issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + equivalentBondedShares := p.EquivalentBondedShares(validator) + exRate := validator.DelegatorShareExRate(p) // eq-val-bonded-shares/delegator-shares + issuedDelegatorShares = equivalentBondedShares.Quo(exRate) validator.DelegatorShares = validator.DelegatorShares.Add(issuedDelegatorShares) return p, validator, issuedDelegatorShares @@ -143,7 +233,7 @@ func (p Pool) validatorRemoveShares(validator Validator, //exRate := validator.DelegatorShareExRate() //XXX make sure not used - globalPoolSharesToRemove := validator.DelegatorShareExRate().Mul(shares) + globalPoolSharesToRemove := validator.DelegatorShareExRate(p).Mul(shares) if validator.Status == sdk.Bonded { p, createdCoins = p.removeSharesBonded(globalPoolSharesToRemove) } else { diff --git a/x/stake/tick.go b/x/stake/tick.go index b712ff9c92..b63bf22b5a 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -52,7 +52,7 @@ func (k Keeper) processProvisions(ctx sdk.Context) Pool { // which needs to be updated is the `BondedPool`. So for each previsions cycle: provisions := pool.Inflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat).Evaluate() - pool.BondedPool += provisions + pool.BondedTokens += provisions pool.TotalSupply += provisions return pool } diff --git a/x/stake/types_test.go b/x/stake/types_test.go deleted file mode 100644 index c6bc2a9d2b..0000000000 --- a/x/stake/types_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package stake - -// XXX test global state functions, validator exchange rate functions etc. diff --git a/x/stake/types.go b/x/stake/validator.go similarity index 50% rename from x/stake/types.go rename to x/stake/validator.go index 55de038c45..46c5e9a6e4 100644 --- a/x/stake/types.go +++ b/x/stake/validator.go @@ -10,115 +10,6 @@ import ( crypto "github.com/tendermint/go-crypto" ) -// GenesisState - all staking state that must be provided at genesis -type GenesisState struct { - Pool Pool `json:"pool"` - Params Params `json:"params"` - Validators []Validator `json:"validators"` - Bonds []Delegation `json:"bonds"` -} - -func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { - return GenesisState{ - Pool: pool, - Params: params, - Validators: validators, - Bonds: bonds, - } -} - -// get raw genesis raw message for testing -func DefaultGenesisState() GenesisState { - return GenesisState{ - Pool: initialPool(), - Params: defaultParams(), - } -} - -//_________________________________________________________________________ - -// Params defines the high level settings for staking -type Params struct { - InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax sdk.Rat `json:"inflation_max"` // maximum inflation rate - InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate - GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms - - MaxValidators uint16 `json:"max_validators"` // maximum number of validators - BondDenom string `json:"bond_denom"` // bondable coin denomination -} - -func (p Params) equal(p2 Params) bool { - return p.InflationRateChange.Equal(p2.InflationRateChange) && - p.InflationMax.Equal(p2.InflationMax) && - p.InflationMin.Equal(p2.InflationMin) && - p.GoalBonded.Equal(p2.GoalBonded) && - p.MaxValidators == p2.MaxValidators && - p.BondDenom == p2.BondDenom -} - -func defaultParams() Params { - return Params{ - InflationRateChange: sdk.NewRat(13, 100), - InflationMax: sdk.NewRat(20, 100), - InflationMin: sdk.NewRat(7, 100), - GoalBonded: sdk.NewRat(67, 100), - MaxValidators: 100, - BondDenom: "steak", - } -} - -//_________________________________________________________________________ - -// Pool - dynamic parameters of the current state -type Pool struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool - UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondingPool int64 `json:"unbonding_pool"` // tokens moving from bonded to unbonded pool - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with validators - 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 - - DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) - - // Fee Related - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calcualtions -} - -func (p Pool) equal(p2 Pool) bool { - return p.TotalSupply == p2.TotalSupply && - p.BondedShares.Equal(p2.BondedShares) && - p.UnbondedShares.Equal(p2.UnbondedShares) && - p.BondedPool == p2.BondedPool && - p.UnbondedPool == p2.UnbondedPool && - p.InflationLastTime == p2.InflationLastTime && - p.Inflation.Equal(p2.Inflation) && - p.DateLastCommissionReset == p2.DateLastCommissionReset && - p.PrevBondedShares.Equal(p2.PrevBondedShares) -} - -// initial pool for testing -func initialPool() Pool { - return Pool{ - TotalSupply: 0, - BondedShares: sdk.ZeroRat(), - UnbondingShares: sdk.ZeroRat(), - UnbondedShares: sdk.ZeroRat(), - BondedPool: 0, - UnbondingPool: 0, - UnbondedPool: 0, - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - DateLastCommissionReset: 0, - PrevBondedShares: sdk.ZeroRat(), - } -} - -//_________________________________________________________________________ - // Validator defines the total amount of bond shares and their exchange rate to // coins. Accumulation of interest is modelled as an in increase in the // exchange rate, and slashing as a decrease. When coins are delegated to this @@ -163,6 +54,8 @@ func NewValidator(address sdk.Address, pubKey crypto.PubKey, description Descrip Address: address, PubKey: pubKey, BondedShares: sdk.ZeroRat(), + UnbondingShares: sdk.ZeroRat(), + UnbondedShares: sdk.ZeroRat(), DelegatorShares: sdk.ZeroRat(), Description: description, BondHeight: int64(0), @@ -176,6 +69,7 @@ func NewValidator(address sdk.Address, pubKey crypto.PubKey, description Descrip } } +// only the vitals - does not check bond height of IntraTxCounter func (v Validator) equal(c2 Validator) bool { return v.Status == c2.Status && v.PubKey.Equals(c2.PubKey) && @@ -183,7 +77,7 @@ func (v Validator) equal(c2 Validator) bool { v.BondedShares.Equal(c2.BondedShares) && v.DelegatorShares.Equal(c2.DelegatorShares) && v.Description == c2.Description && - v.BondHeight == c2.BondHeight && + //v.BondHeight == c2.BondHeight && //v.BondIntraTxCounter == c2.BondIntraTxCounter && // counter is always changing v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && v.Commission.Equal(c2.Commission) && @@ -215,19 +109,14 @@ func NewDescription(moniker, identity, website, details string) Description { } } -// get the exchange rate of global pool shares over delegator shares -func (v Validator) DelegatorShareExRate() sdk.Rat { +// get the exchange rate of tokens over delegator shares +// UNITS: eq-val-bonded-shares/delegator-shares +func (v Validator) DelegatorShareExRate(p Pool) sdk.Rat { if v.DelegatorShares.IsZero() { return sdk.OneRat() } - switch v.Status { - case sdk.Bonded: - return v.BondedShares.Quo(v.DelegatorShares) - case sdk.Unbonding: - return v.UnbondingShares.Quo(v.DelegatorShares) - default: //sdk.Unbonded, sdk.Revoked: - return v.UnbondedShares.Quo(v.DelegatorShares) - } + tokens := p.EquivalentBondedShares(v) + return tokens.Quo(v.DelegatorShares) } // abci validator from stake validator type @@ -261,31 +150,3 @@ func (v Validator) GetAddress() sdk.Address { return v.Address } func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } func (v Validator) GetPower() sdk.Rat { return v.BondedShares } func (v Validator) GetBondHeight() int64 { return v.BondHeight } - -//_________________________________________________________________________ - -// Delegation represents the bond with tokens held by an account. It is -// owned by one delegator, and is associated with the voting power of one -// pubKey. -// TODO better way of managing space -type Delegation struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Shares sdk.Rat `json:"shares"` - Height int64 `json:"height"` // Last height bond updated -} - -func (b Delegation) equal(b2 Delegation) bool { - return bytes.Equal(b.DelegatorAddr, b2.DelegatorAddr) && - bytes.Equal(b.ValidatorAddr, b2.ValidatorAddr) && - b.Height == b2.Height && - b.Shares.Equal(b2.Shares) -} - -// ensure fulfills the sdk validator types -var _ sdk.Delegation = Delegation{} - -// nolint - for sdk.Delegation -func (b Delegation) GetDelegator() sdk.Address { return b.DelegatorAddr } -func (b Delegation) GetValidator() sdk.Address { return b.ValidatorAddr } -func (b Delegation) GetBondShares() sdk.Rat { return b.Shares }