split types into multiple files, fix delegation share exrate

This commit is contained in:
rigelrozanski 2018-05-12 18:22:59 -04:00
parent 0c98cc6895
commit b64363fcbe
13 changed files with 473 additions and 351 deletions

View File

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

View File

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

33
x/stake/delegation.go Normal file
View File

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

54
x/stake/genesis.go Normal file
View File

@ -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,
}
}

View File

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

View File

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

View File

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

View File

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

36
x/stake/params.go Normal file
View File

@ -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",
}
}

View File

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

View File

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

View File

@ -1,3 +0,0 @@
package stake
// XXX test global state functions, validator exchange rate functions etc.

View File

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