move validator property fn and tests from pool.go to validator.go
This commit is contained in:
parent
b64363fcbe
commit
67123a3a46
|
@ -162,7 +162,7 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pool, validator, newShares := pool.validatorAddTokens(validator, bondAmt.Amount)
|
validator, pool, newShares := validator.addTokens(pool, bondAmt.Amount)
|
||||||
bond.Shares = bond.Shares.Add(newShares)
|
bond.Shares = bond.Shares.Add(newShares)
|
||||||
|
|
||||||
// Update bond height
|
// Update bond height
|
||||||
|
@ -244,7 +244,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
|
||||||
|
|
||||||
// Add the coins
|
// Add the coins
|
||||||
p := k.GetPool(ctx)
|
p := k.GetPool(ctx)
|
||||||
p, validator, returnAmount := p.validatorRemoveShares(validator, shares)
|
validator, p, returnAmount := validator.removeShares(p, shares)
|
||||||
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
|
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
|
||||||
k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins)
|
k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins)
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
|
||||||
// change the share types to unbonded if they were not already
|
// change the share types to unbonded if they were not already
|
||||||
if validator.Status == sdk.Bonded {
|
if validator.Status == sdk.Bonded {
|
||||||
validator.Status = sdk.Unbonded
|
validator.Status = sdk.Unbonded
|
||||||
p, validator = p.UpdateSharesLocation(validator)
|
validator, p = validator.UpdateSharesLocation(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastly update the status
|
// lastly update the status
|
||||||
|
|
|
@ -126,7 +126,7 @@ func (k Keeper) setValidator(ctx sdk.Context, validator Validator) {
|
||||||
|
|
||||||
if oldValidator.Status != sdk.Bonded && valIsNowBonded {
|
if oldValidator.Status != sdk.Bonded && valIsNowBonded {
|
||||||
validator.Status = sdk.Bonded
|
validator.Status = sdk.Bonded
|
||||||
pool, validator = pool.UpdateSharesLocation(validator)
|
validator, pool = validator.UpdateSharesLocation(pool)
|
||||||
k.setPool(ctx, pool)
|
k.setPool(ctx, pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ func GetValidatorKey(addr sdk.Address) []byte {
|
||||||
// get the key for the validator used in the power-store
|
// get the key for the validator used in the power-store
|
||||||
func GetValidatorsBondedByPowerKey(validator Validator, pool Pool) []byte {
|
func GetValidatorsBondedByPowerKey(validator Validator, pool Pool) []byte {
|
||||||
|
|
||||||
power := pool.EquivalentBondedShares(validator)
|
power := validator.EquivalentBondedShares(pool)
|
||||||
powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first)
|
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
|
// TODO ensure that the key will be a readable string.. probably should add seperators and have
|
||||||
|
|
115
x/stake/pool.go
115
x/stake/pool.go
|
@ -128,118 +128,3 @@ func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64
|
||||||
p.UnbondedTokens -= removedTokens
|
p.UnbondedTokens -= removedTokens
|
||||||
return p, 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) {
|
|
||||||
var tokens int64
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case !validator.BondedShares.IsZero():
|
|
||||||
if validator.Status == sdk.Bonded { // return if nothing needs switching
|
|
||||||
return p, validator
|
|
||||||
}
|
|
||||||
p, tokens = p.removeSharesBonded(validator.BondedShares)
|
|
||||||
case !validator.UnbondingShares.IsZero():
|
|
||||||
if validator.Status == sdk.Unbonding {
|
|
||||||
return p, validator
|
|
||||||
}
|
|
||||||
p, tokens = p.removeSharesUnbonding(validator.BondedShares)
|
|
||||||
case !validator.UnbondedShares.IsZero():
|
|
||||||
if validator.Status == sdk.Unbonding {
|
|
||||||
return p, validator
|
|
||||||
}
|
|
||||||
p, tokens = p.removeSharesUnbonded(validator.BondedShares)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch validator.Status {
|
|
||||||
case sdk.Bonded:
|
|
||||||
p, validator.BondedShares = p.addTokensBonded(tokens)
|
|
||||||
case sdk.Unbonding:
|
|
||||||
p, validator.UnbondingShares = p.addTokensUnbonding(tokens)
|
|
||||||
case sdk.Unbonded, sdk.Revoked:
|
|
||||||
p, validator.UnbondedShares = p.addTokensUnbonded(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, validator
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove shares from a validator
|
|
||||||
func (p Pool) validatorRemoveShares(validator Validator,
|
|
||||||
shares sdk.Rat) (p2 Pool, validator2 Validator, createdCoins int64) {
|
|
||||||
|
|
||||||
//exRate := validator.DelegatorShareExRate() //XXX make sure not used
|
|
||||||
|
|
||||||
globalPoolSharesToRemove := validator.DelegatorShareExRate(p).Mul(shares)
|
|
||||||
if validator.Status == sdk.Bonded {
|
|
||||||
p, createdCoins = p.removeSharesBonded(globalPoolSharesToRemove)
|
|
||||||
} else {
|
|
||||||
p, createdCoins = p.removeSharesUnbonded(globalPoolSharesToRemove)
|
|
||||||
}
|
|
||||||
validator.BondedShares = validator.BondedShares.Sub(globalPoolSharesToRemove)
|
|
||||||
validator.DelegatorShares = validator.DelegatorShares.Sub(shares)
|
|
||||||
return p, validator, createdCoins
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package stake
|
package stake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -39,6 +37,20 @@ func TestBondedShareExRate(t *testing.T) {
|
||||||
require.Equal(t, pool.bondedShareExRate(), sdk.OneRat())
|
require.Equal(t, pool.bondedShareExRate(), sdk.OneRat())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnbondingShareExRate(t *testing.T) {
|
||||||
|
ctx, _, keeper := createTestInput(t, false, 0)
|
||||||
|
pool := keeper.GetPool(ctx)
|
||||||
|
pool.UnbondingPool = 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) {
|
func TestUnbondedShareExRate(t *testing.T) {
|
||||||
ctx, _, keeper := createTestInput(t, false, 0)
|
ctx, _, keeper := createTestInput(t, false, 0)
|
||||||
pool := keeper.GetPool(ctx)
|
pool := keeper.GetPool(ctx)
|
||||||
|
@ -53,62 +65,6 @@ func TestUnbondedShareExRate(t *testing.T) {
|
||||||
require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat())
|
require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO convert these commend out tests to test UpdateSharesLocation
|
|
||||||
//func TestBondedToUnbondedPool(t *testing.T) {
|
|
||||||
//ctx, _, keeper := createTestInput(t, false, 0)
|
|
||||||
|
|
||||||
//poolA := keeper.GetPool(ctx)
|
|
||||||
//assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
|
||||||
//assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
|
||||||
//valA := Validator{
|
|
||||||
//Status: sdk.Bonded,
|
|
||||||
//Address: addrs[0],
|
|
||||||
//PubKey: pks[0],
|
|
||||||
//BondedShares: sdk.OneRat(),
|
|
||||||
//DelegatorShares: sdk.OneRat(),
|
|
||||||
//}
|
|
||||||
//poolB, valB := poolA.bondedToUnbondedPool(valA)
|
|
||||||
|
|
||||||
//// status unbonded
|
|
||||||
//assert.Equal(t, valB.Status, sdk.Unbonded)
|
|
||||||
//// same exchange rate, assets unchanged
|
|
||||||
//assert.Equal(t, valB.BondedShares, valA.BondedShares)
|
|
||||||
//// bonded pool decreased
|
|
||||||
//assert.Equal(t, poolB.BondedPool, poolA.BondedPool-valA.BondedShares.Evaluate())
|
|
||||||
//// unbonded pool increased
|
|
||||||
//assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+valA.BondedShares.Evaluate())
|
|
||||||
//// conservation of tokens
|
|
||||||
//assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func TestUnbonbedtoBondedPool(t *testing.T) {
|
|
||||||
//ctx, _, keeper := createTestInput(t, false, 0)
|
|
||||||
|
|
||||||
//poolA := keeper.GetPool(ctx)
|
|
||||||
//assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
|
||||||
//assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
|
||||||
//valA := Validator{
|
|
||||||
//Status: sdk.Bonded,
|
|
||||||
//Address: addrs[0],
|
|
||||||
//PubKey: pks[0],
|
|
||||||
//BondedShares: sdk.OneRat(),
|
|
||||||
//DelegatorShares: sdk.OneRat(),
|
|
||||||
//}
|
|
||||||
//valA.Status = sdk.Unbonded
|
|
||||||
//poolB, valB := poolA.unbondedToBondedPool(valA)
|
|
||||||
|
|
||||||
//// status bonded
|
|
||||||
//assert.Equal(t, valB.Status, sdk.Bonded)
|
|
||||||
//// same exchange rate, assets unchanged
|
|
||||||
//assert.Equal(t, valB.BondedShares, valA.BondedShares)
|
|
||||||
//// bonded pool increased
|
|
||||||
//assert.Equal(t, poolB.BondedPool, poolA.BondedPool+valA.BondedShares.Evaluate())
|
|
||||||
//// unbonded pool decreased
|
|
||||||
//assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-valA.BondedShares.Evaluate())
|
|
||||||
//// conservation of tokens
|
|
||||||
//assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func TestAddTokensBonded(t *testing.T) {
|
func TestAddTokensBonded(t *testing.T) {
|
||||||
ctx, _, keeper := createTestInput(t, false, 0)
|
ctx, _, keeper := createTestInput(t, false, 0)
|
||||||
|
|
||||||
|
@ -172,354 +128,3 @@ func TestRemoveSharesUnbonded(t *testing.T) {
|
||||||
// same number of unbonded shares / tokens when exchange rate is one
|
// same number of unbonded shares / tokens when exchange rate is one
|
||||||
assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedPool)))
|
assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedPool)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatorAddTokens(t *testing.T) {
|
|
||||||
ctx, _, keeper := createTestInput(t, false, 0)
|
|
||||||
|
|
||||||
poolA := keeper.GetPool(ctx)
|
|
||||||
valA := Validator{
|
|
||||||
Status: sdk.Bonded,
|
|
||||||
Address: addrs[0],
|
|
||||||
PubKey: pks[0],
|
|
||||||
BondedShares: sdk.NewRat(9),
|
|
||||||
DelegatorShares: sdk.NewRat(9),
|
|
||||||
}
|
|
||||||
poolA.BondedPool = valA.BondedShares.Evaluate()
|
|
||||||
poolA.BondedShares = valA.BondedShares
|
|
||||||
assert.Equal(t, valA.DelegatorShareExRate(), sdk.OneRat())
|
|
||||||
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
|
||||||
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
|
||||||
poolB, valB, sharesB := poolA.validatorAddTokens(valA, 10)
|
|
||||||
|
|
||||||
// shares were issued
|
|
||||||
assert.Equal(t, sdk.NewRat(10).Mul(valA.DelegatorShareExRate()), sharesB)
|
|
||||||
// pool shares were added
|
|
||||||
assert.Equal(t, valB.BondedShares, valA.BondedShares.Add(sdk.NewRat(10)))
|
|
||||||
// conservation of tokens
|
|
||||||
assert.Equal(t, poolB.BondedPool, 10+poolA.BondedPool)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidatorRemoveShares(t *testing.T) {
|
|
||||||
ctx, _, keeper := createTestInput(t, false, 0)
|
|
||||||
|
|
||||||
poolA := keeper.GetPool(ctx)
|
|
||||||
valA := Validator{
|
|
||||||
Status: sdk.Bonded,
|
|
||||||
Address: addrs[0],
|
|
||||||
PubKey: pks[0],
|
|
||||||
BondedShares: sdk.NewRat(9),
|
|
||||||
DelegatorShares: sdk.NewRat(9),
|
|
||||||
}
|
|
||||||
poolA.BondedPool = valA.BondedShares.Evaluate()
|
|
||||||
poolA.BondedShares = valA.BondedShares
|
|
||||||
assert.Equal(t, valA.DelegatorShareExRate(), sdk.OneRat())
|
|
||||||
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
|
||||||
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
|
||||||
poolB, valB, coinsB := poolA.validatorRemoveShares(valA, sdk.NewRat(10))
|
|
||||||
|
|
||||||
// coins were created
|
|
||||||
assert.Equal(t, coinsB, int64(10))
|
|
||||||
// pool shares were removed
|
|
||||||
assert.Equal(t, valB.BondedShares, valA.BondedShares.Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate())))
|
|
||||||
// conservation of tokens
|
|
||||||
assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool+coinsB, poolA.UnbondedPool+poolA.BondedPool)
|
|
||||||
|
|
||||||
// specific case from random tests
|
|
||||||
assets := sdk.NewRat(5102)
|
|
||||||
liabilities := sdk.NewRat(115)
|
|
||||||
val := Validator{
|
|
||||||
Status: sdk.Bonded,
|
|
||||||
Address: addrs[0],
|
|
||||||
PubKey: pks[0],
|
|
||||||
BondedShares: assets,
|
|
||||||
DelegatorShares: liabilities,
|
|
||||||
}
|
|
||||||
pool := Pool{
|
|
||||||
TotalSupply: 0,
|
|
||||||
BondedShares: sdk.NewRat(248305),
|
|
||||||
UnbondedShares: sdk.NewRat(232147),
|
|
||||||
BondedPool: 248305,
|
|
||||||
UnbondedPool: 232147,
|
|
||||||
InflationLastTime: 0,
|
|
||||||
Inflation: sdk.NewRat(7, 100),
|
|
||||||
}
|
|
||||||
shares := sdk.NewRat(29)
|
|
||||||
msg := fmt.Sprintf("validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
|
||||||
val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
|
||||||
msg = fmt.Sprintf("Removed %v shares from %s", shares, msg)
|
|
||||||
newPool, _, tokens := pool.validatorRemoveShares(val, shares)
|
|
||||||
require.Equal(t,
|
|
||||||
tokens+newPool.UnbondedPool+newPool.BondedPool,
|
|
||||||
pool.BondedPool+pool.UnbondedPool,
|
|
||||||
"Tokens were not conserved: %s", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
//________________________________________________________________________________
|
|
||||||
// TODO refactor this random setup
|
|
||||||
|
|
||||||
// generate a random validator
|
|
||||||
func randomValidator(r *rand.Rand) Validator {
|
|
||||||
var status sdk.BondStatus
|
|
||||||
if r.Float64() < float64(0.5) {
|
|
||||||
status = sdk.Bonded
|
|
||||||
} else {
|
|
||||||
status = sdk.Unbonded
|
|
||||||
}
|
|
||||||
assets := sdk.NewRat(int64(r.Int31n(10000)))
|
|
||||||
liabilities := sdk.NewRat(int64(r.Int31n(10000)))
|
|
||||||
return Validator{
|
|
||||||
Status: status,
|
|
||||||
Address: addrs[0],
|
|
||||||
PubKey: pks[0],
|
|
||||||
BondedShares: assets,
|
|
||||||
DelegatorShares: liabilities,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate a random staking state
|
|
||||||
func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) {
|
|
||||||
pool := Pool{
|
|
||||||
TotalSupply: 0,
|
|
||||||
BondedShares: sdk.ZeroRat(),
|
|
||||||
UnbondedShares: sdk.ZeroRat(),
|
|
||||||
BondedPool: 0,
|
|
||||||
UnbondedPool: 0,
|
|
||||||
InflationLastTime: 0,
|
|
||||||
Inflation: sdk.NewRat(7, 100),
|
|
||||||
}
|
|
||||||
|
|
||||||
validators := make([]Validator, numValidators)
|
|
||||||
for i := 0; i < numValidators; i++ {
|
|
||||||
validator := randomValidator(r)
|
|
||||||
if validator.Status == sdk.Bonded {
|
|
||||||
pool.BondedShares = pool.BondedShares.Add(validator.BondedShares)
|
|
||||||
pool.BondedPool += validator.BondedShares.Evaluate()
|
|
||||||
} else if validator.Status == sdk.Unbonded {
|
|
||||||
pool.UnbondedShares = pool.UnbondedShares.Add(validator.BondedShares)
|
|
||||||
pool.UnbondedPool += validator.BondedShares.Evaluate()
|
|
||||||
}
|
|
||||||
validators[i] = validator
|
|
||||||
}
|
|
||||||
return pool, validators
|
|
||||||
}
|
|
||||||
|
|
||||||
// any operation that transforms staking state
|
|
||||||
// takes in RNG instance, pool, validator
|
|
||||||
// returns updated pool, updated validator, delta tokens, descriptive message
|
|
||||||
type Operation func(r *rand.Rand, p Pool, c Validator) (Pool, Validator, int64, string)
|
|
||||||
|
|
||||||
// operation: bond or unbond a validator depending on current status
|
|
||||||
func OpBondOrUnbond(r *rand.Rand, p Pool, val Validator) (Pool, Validator, int64, string) {
|
|
||||||
var msg string
|
|
||||||
if val.Status == sdk.Bonded {
|
|
||||||
msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
|
||||||
val.Address, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
|
||||||
val.Status = sdk.Unbonded
|
|
||||||
} else if val.Status == sdk.Unbonded {
|
|
||||||
msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
|
||||||
val.Address, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
|
||||||
val.Status = sdk.Bonded
|
|
||||||
}
|
|
||||||
p, val = p.UpdateSharesLocation(val)
|
|
||||||
return p, val, 0, msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// operation: add a random number of tokens to a validator
|
|
||||||
func OpAddTokens(r *rand.Rand, p Pool, val Validator) (Pool, Validator, int64, string) {
|
|
||||||
tokens := int64(r.Int31n(1000))
|
|
||||||
msg := fmt.Sprintf("validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
|
||||||
val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
|
||||||
p, val, _ = p.validatorAddTokens(val, tokens)
|
|
||||||
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
|
|
||||||
return p, val, -1 * tokens, msg // tokens are removed so for accounting must be negative
|
|
||||||
}
|
|
||||||
|
|
||||||
// operation: remove a random number of shares from a validator
|
|
||||||
func OpRemoveShares(r *rand.Rand, p Pool, val Validator) (Pool, Validator, int64, string) {
|
|
||||||
var shares sdk.Rat
|
|
||||||
for {
|
|
||||||
shares = sdk.NewRat(int64(r.Int31n(1000)))
|
|
||||||
if shares.LT(val.DelegatorShares) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
|
||||||
shares, val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
|
||||||
|
|
||||||
p, val, tokens := p.validatorRemoveShares(val, shares)
|
|
||||||
return p, val, tokens, msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// pick a random staking operation
|
|
||||||
func randomOperation(r *rand.Rand) Operation {
|
|
||||||
operations := []Operation{
|
|
||||||
OpBondOrUnbond,
|
|
||||||
OpAddTokens,
|
|
||||||
OpRemoveShares,
|
|
||||||
}
|
|
||||||
r.Shuffle(len(operations), func(i, j int) {
|
|
||||||
operations[i], operations[j] = operations[j], operations[i]
|
|
||||||
})
|
|
||||||
return operations[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure invariants that should always be true are true
|
|
||||||
func assertInvariants(t *testing.T, msg string,
|
|
||||||
pOrig Pool, cOrig Validators, pMod Pool, cMods Validators, tokens int64) {
|
|
||||||
|
|
||||||
// total tokens conserved
|
|
||||||
require.Equal(t,
|
|
||||||
pOrig.UnbondedPool+pOrig.BondedPool,
|
|
||||||
pMod.UnbondedPool+pMod.BondedPool+tokens,
|
|
||||||
"Tokens not conserved - msg: %v\n, pOrig.BondedShares: %v, pOrig.UnbondedShares: %v, pMod.BondedShares: %v, pMod.UnbondedShares: %v, pOrig.UnbondedPool: %v, pOrig.BondedPool: %v, pMod.UnbondedPool: %v, pMod.BondedPool: %v, tokens: %v\n",
|
|
||||||
msg,
|
|
||||||
pOrig.BondedShares, pOrig.UnbondedShares,
|
|
||||||
pMod.BondedShares, pMod.UnbondedShares,
|
|
||||||
pOrig.UnbondedPool, pOrig.BondedPool,
|
|
||||||
pMod.UnbondedPool, pMod.BondedPool, tokens)
|
|
||||||
|
|
||||||
// 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 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().Evaluate())
|
|
||||||
|
|
||||||
// nonnegative unbonded ex rate
|
|
||||||
require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat()),
|
|
||||||
"Applying operation \"%s\" resulted in negative unbondedShareExRate: %d",
|
|
||||||
msg, pMod.unbondedShareExRate().Evaluate())
|
|
||||||
|
|
||||||
for _, cMod := range cMods {
|
|
||||||
|
|
||||||
// nonnegative ex rate
|
|
||||||
require.False(t, cMod.DelegatorShareExRate().LT(sdk.ZeroRat()),
|
|
||||||
"Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Address: %s)",
|
|
||||||
msg,
|
|
||||||
cMod.DelegatorShareExRate(),
|
|
||||||
cMod.Address,
|
|
||||||
)
|
|
||||||
|
|
||||||
// nonnegative assets
|
|
||||||
require.False(t, cMod.BondedShares.LT(sdk.ZeroRat()),
|
|
||||||
"Applying operation \"%s\" resulted in negative validator.BondedShares: %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Address: %s)",
|
|
||||||
msg,
|
|
||||||
cMod.BondedShares,
|
|
||||||
cMod.DelegatorShares,
|
|
||||||
cMod.DelegatorShareExRate(),
|
|
||||||
cMod.Address,
|
|
||||||
)
|
|
||||||
|
|
||||||
// nonnegative liabilities
|
|
||||||
require.False(t, cMod.DelegatorShares.LT(sdk.ZeroRat()),
|
|
||||||
"Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.BondedShares: %v, validator.DelegatorShareExRate: %v, validator.Address: %s)",
|
|
||||||
msg,
|
|
||||||
cMod.DelegatorShares,
|
|
||||||
cMod.BondedShares,
|
|
||||||
cMod.DelegatorShareExRate(),
|
|
||||||
cMod.Address,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPossibleOverflow(t *testing.T) {
|
|
||||||
assets := sdk.NewRat(2159)
|
|
||||||
liabilities := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664))
|
|
||||||
val := Validator{
|
|
||||||
Status: sdk.Bonded,
|
|
||||||
Address: addrs[0],
|
|
||||||
PubKey: pks[0],
|
|
||||||
BondedShares: assets,
|
|
||||||
DelegatorShares: liabilities,
|
|
||||||
}
|
|
||||||
pool := Pool{
|
|
||||||
TotalSupply: 0,
|
|
||||||
BondedShares: assets,
|
|
||||||
UnbondedShares: sdk.ZeroRat(),
|
|
||||||
BondedPool: assets.Evaluate(),
|
|
||||||
UnbondedPool: 0,
|
|
||||||
InflationLastTime: 0,
|
|
||||||
Inflation: sdk.NewRat(7, 100),
|
|
||||||
}
|
|
||||||
tokens := int64(71)
|
|
||||||
msg := fmt.Sprintf("validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
|
||||||
val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
|
||||||
_, newValidator, _ := pool.validatorAddTokens(val, tokens)
|
|
||||||
|
|
||||||
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
|
|
||||||
require.False(t, newValidator.DelegatorShareExRate().LT(sdk.ZeroRat()),
|
|
||||||
"Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v",
|
|
||||||
msg, newValidator.DelegatorShareExRate())
|
|
||||||
}
|
|
||||||
|
|
||||||
// run random operations in a random order on a random single-validator state, assert invariants hold
|
|
||||||
func TestSingleValidatorIntegrationInvariants(t *testing.T) {
|
|
||||||
r := rand.New(rand.NewSource(41))
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
poolOrig, validatorsOrig := randomSetup(r, 1)
|
|
||||||
require.Equal(t, 1, len(validatorsOrig))
|
|
||||||
|
|
||||||
// sanity check
|
|
||||||
assertInvariants(t, "no operation",
|
|
||||||
poolOrig, validatorsOrig,
|
|
||||||
poolOrig, validatorsOrig, 0)
|
|
||||||
|
|
||||||
for j := 0; j < 5; j++ {
|
|
||||||
poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[0])
|
|
||||||
|
|
||||||
validatorsMod := make([]Validator, len(validatorsOrig))
|
|
||||||
copy(validatorsMod[:], validatorsOrig[:])
|
|
||||||
require.Equal(t, 1, len(validatorsOrig), "j %v", j)
|
|
||||||
require.Equal(t, 1, len(validatorsMod), "j %v", j)
|
|
||||||
validatorsMod[0] = validatorMod
|
|
||||||
|
|
||||||
assertInvariants(t, msg,
|
|
||||||
poolOrig, validatorsOrig,
|
|
||||||
poolMod, validatorsMod, tokens)
|
|
||||||
|
|
||||||
poolOrig = poolMod
|
|
||||||
validatorsOrig = validatorsMod
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run random operations in a random order on a random multi-validator state, assert invariants hold
|
|
||||||
func TestMultiValidatorIntegrationInvariants(t *testing.T) {
|
|
||||||
r := rand.New(rand.NewSource(42))
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
poolOrig, validatorsOrig := randomSetup(r, 100)
|
|
||||||
|
|
||||||
assertInvariants(t, "no operation",
|
|
||||||
poolOrig, validatorsOrig,
|
|
||||||
poolOrig, validatorsOrig, 0)
|
|
||||||
|
|
||||||
for j := 0; j < 5; j++ {
|
|
||||||
index := int(r.Int31n(int32(len(validatorsOrig))))
|
|
||||||
poolMod, validatorMod, tokens, 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)
|
|
||||||
|
|
||||||
poolOrig = poolMod
|
|
||||||
validatorsOrig = validatorsMod
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ func TestProcessProvisions(t *testing.T) {
|
||||||
// create some validators some bonded, some unbonded
|
// create some validators some bonded, some unbonded
|
||||||
validators := make([]Validator, 10)
|
validators := make([]Validator, 10)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
c := Validator{
|
v := Validator{
|
||||||
Status: sdk.Unbonded,
|
Status: sdk.Unbonded,
|
||||||
PubKey: pks[i],
|
PubKey: pks[i],
|
||||||
Address: addrs[i],
|
Address: addrs[i],
|
||||||
|
@ -76,14 +76,14 @@ func TestProcessProvisions(t *testing.T) {
|
||||||
DelegatorShares: sdk.NewRat(0),
|
DelegatorShares: sdk.NewRat(0),
|
||||||
}
|
}
|
||||||
if i < 5 {
|
if i < 5 {
|
||||||
c.Status = sdk.Bonded
|
v.Status = sdk.Bonded
|
||||||
}
|
}
|
||||||
mintedTokens := int64((i + 1) * 10000000)
|
mintedTokens := int64((i + 1) * 10000000)
|
||||||
pool.TotalSupply += mintedTokens
|
pool.TotalSupply += mintedTokens
|
||||||
pool, c, _ = pool.validatorAddTokens(c, mintedTokens)
|
v, pool, _ = v.addTokens(pool, mintedTokens)
|
||||||
|
|
||||||
keeper.setValidator(ctx, c)
|
keeper.setValidator(ctx, v)
|
||||||
validators[i] = c
|
validators[i] = v
|
||||||
}
|
}
|
||||||
keeper.setPool(ctx, pool)
|
keeper.setPool(ctx, pool)
|
||||||
var totalSupply int64 = 550000000
|
var totalSupply int64 = 550000000
|
||||||
|
|
|
@ -109,13 +109,16 @@ func NewDescription(moniker, identity, website, details string) Description {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX updateDescription function
|
||||||
|
//XXX enforce limit to number of description characters
|
||||||
|
|
||||||
// get the exchange rate of tokens over delegator shares
|
// get the exchange rate of tokens over delegator shares
|
||||||
// UNITS: eq-val-bonded-shares/delegator-shares
|
// UNITS: eq-val-bonded-shares/delegator-shares
|
||||||
func (v Validator) DelegatorShareExRate(p Pool) sdk.Rat {
|
func (v Validator) DelegatorShareExRate(p Pool) sdk.Rat {
|
||||||
if v.DelegatorShares.IsZero() {
|
if v.DelegatorShares.IsZero() {
|
||||||
return sdk.OneRat()
|
return sdk.OneRat()
|
||||||
}
|
}
|
||||||
tokens := p.EquivalentBondedShares(v)
|
tokens := v.EquivalentBondedShares(p)
|
||||||
return tokens.Quo(v.DelegatorShares)
|
return tokens.Quo(v.DelegatorShares)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,8 +139,116 @@ func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//XXX updateDescription function
|
// XXX write test
|
||||||
//XXX enforce limit to number of description characters
|
// update the location of the shares within a validator if its bond status has changed
|
||||||
|
func (v Validator) UpdateSharesLocation(p Pool) (Validator, Pool) {
|
||||||
|
var tokens int64
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !v.BondedShares.IsZero():
|
||||||
|
if v.Status == sdk.Bonded { // return if nothing needs switching
|
||||||
|
return v, p
|
||||||
|
}
|
||||||
|
p, tokens = p.removeSharesBonded(v.BondedShares)
|
||||||
|
case !v.UnbondingShares.IsZero():
|
||||||
|
if v.Status == sdk.Unbonding {
|
||||||
|
return v, p
|
||||||
|
}
|
||||||
|
p, tokens = p.removeSharesUnbonding(v.BondedShares)
|
||||||
|
case !v.UnbondedShares.IsZero():
|
||||||
|
if v.Status == sdk.Unbonding {
|
||||||
|
return v, p
|
||||||
|
}
|
||||||
|
p, tokens = p.removeSharesUnbonded(v.BondedShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Status {
|
||||||
|
case sdk.Bonded:
|
||||||
|
p, v.BondedShares = p.addTokensBonded(tokens)
|
||||||
|
case sdk.Unbonding:
|
||||||
|
p, v.UnbondingShares = p.addTokensUnbonding(tokens)
|
||||||
|
case sdk.Unbonded, sdk.Revoked:
|
||||||
|
p, v.UnbondedShares = p.addTokensUnbonded(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, p
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (v Validator) EquivalentBondedShares(p Pool) (power sdk.Rat) {
|
||||||
|
switch v.Status {
|
||||||
|
case sdk.Bonded:
|
||||||
|
power = v.BondedShares
|
||||||
|
case sdk.Unbonding:
|
||||||
|
shares := v.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 := v.UnbondedShares // ubShr
|
||||||
|
exRate := p.unbondedShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr
|
||||||
|
power = shares.Mul(exRate) // ubshr*bshr/ubshr = bshr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Implement Use in query functionality
|
||||||
|
// get the equivalent amount of tokens contained by a validator
|
||||||
|
func (v Validator) Tokens(p Pool) 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX Audit this function further to make sure it's correct
|
||||||
|
// add tokens to a validator
|
||||||
|
func (v Validator) addTokens(p Pool,
|
||||||
|
amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) {
|
||||||
|
|
||||||
|
var poolShares sdk.Rat
|
||||||
|
switch v.Status {
|
||||||
|
case sdk.Bonded:
|
||||||
|
p, poolShares = p.addTokensBonded(amount)
|
||||||
|
v.BondedShares = v.BondedShares.Add(poolShares)
|
||||||
|
case sdk.Unbonding:
|
||||||
|
p, poolShares = p.addTokensUnbonding(amount)
|
||||||
|
v.UnbondingShares = v.UnbondingShares.Add(poolShares)
|
||||||
|
case sdk.Unbonded, sdk.Revoked:
|
||||||
|
p, poolShares = p.addTokensUnbonded(amount)
|
||||||
|
v.UnbondedShares = v.UnbondedShares.Add(poolShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
equivalentBondedShares := v.EquivalentBondedShares(p)
|
||||||
|
exRate := v.DelegatorShareExRate(p) // eq-val-bonded-shares/delegator-shares
|
||||||
|
issuedDelegatorShares = equivalentBondedShares.Quo(exRate)
|
||||||
|
v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares)
|
||||||
|
|
||||||
|
return v, p, issuedDelegatorShares
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove shares from a validator
|
||||||
|
func (v Validator) removeShares(p Pool,
|
||||||
|
delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) {
|
||||||
|
|
||||||
|
globalPoolSharesToRemove := v.DelegatorShareExRate(p).Mul(delShares)
|
||||||
|
if v.Status == sdk.Bonded {
|
||||||
|
p, createdCoins = p.removeSharesBonded(globalPoolSharesToRemove)
|
||||||
|
} else {
|
||||||
|
p, createdCoins = p.removeSharesUnbonded(globalPoolSharesToRemove)
|
||||||
|
}
|
||||||
|
v.BondedShares = v.BondedShares.Sub(globalPoolSharesToRemove)
|
||||||
|
v.DelegatorShares = v.DelegatorShares.Sub(delShares)
|
||||||
|
return v, p, createdCoins
|
||||||
|
}
|
||||||
|
|
||||||
//______________________________________________________________________
|
//______________________________________________________________________
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,420 @@
|
||||||
|
package stake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddTokens(t *testing.T) {
|
||||||
|
ctx, _, keeper := createTestInput(t, false, 0)
|
||||||
|
|
||||||
|
poolA := keeper.GetPool(ctx)
|
||||||
|
valA := Validator{
|
||||||
|
Status: sdk.Bonded,
|
||||||
|
Address: addrs[0],
|
||||||
|
PubKey: pks[0],
|
||||||
|
BondedShares: sdk.NewRat(9),
|
||||||
|
DelegatorShares: sdk.NewRat(9),
|
||||||
|
}
|
||||||
|
poolA.BondedPool = valA.BondedShares.Evaluate()
|
||||||
|
poolA.BondedShares = valA.BondedShares
|
||||||
|
assert.Equal(t, valA.DelegatorShareExRate(), sdk.OneRat())
|
||||||
|
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
||||||
|
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
||||||
|
valB, poolB, sharesB := valA.addTokens(poolA, 10)
|
||||||
|
|
||||||
|
// shares were issued
|
||||||
|
assert.Equal(t, sdk.NewRat(10).Mul(valA.DelegatorShareExRate()), sharesB)
|
||||||
|
// pool shares were added
|
||||||
|
assert.Equal(t, valB.BondedShares, valA.BondedShares.Add(sdk.NewRat(10)))
|
||||||
|
// conservation of tokens
|
||||||
|
assert.Equal(t, poolB.BondedPool, 10+poolA.BondedPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveShares(t *testing.T) {
|
||||||
|
ctx, _, keeper := createTestInput(t, false, 0)
|
||||||
|
|
||||||
|
poolA := keeper.GetPool(ctx)
|
||||||
|
valA := Validator{
|
||||||
|
Status: sdk.Bonded,
|
||||||
|
Address: addrs[0],
|
||||||
|
PubKey: pks[0],
|
||||||
|
BondedShares: sdk.NewRat(9),
|
||||||
|
DelegatorShares: sdk.NewRat(9),
|
||||||
|
}
|
||||||
|
poolA.BondedPool = valA.BondedShares.Evaluate()
|
||||||
|
poolA.BondedShares = valA.BondedShares
|
||||||
|
assert.Equal(t, valA.DelegatorShareExRate(), sdk.OneRat())
|
||||||
|
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
||||||
|
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
||||||
|
valB, poolB, coinsB := valA.removeShares(poolA, sdk.NewRat(10))
|
||||||
|
|
||||||
|
// coins were created
|
||||||
|
assert.Equal(t, coinsB, int64(10))
|
||||||
|
// pool shares were removed
|
||||||
|
assert.Equal(t, valB.BondedShares, valA.BondedShares.Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate())))
|
||||||
|
// conservation of tokens
|
||||||
|
assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool+coinsB, poolA.UnbondedPool+poolA.BondedPool)
|
||||||
|
|
||||||
|
// specific case from random tests
|
||||||
|
assets := sdk.NewRat(5102)
|
||||||
|
liabilities := sdk.NewRat(115)
|
||||||
|
val := Validator{
|
||||||
|
Status: sdk.Bonded,
|
||||||
|
Address: addrs[0],
|
||||||
|
PubKey: pks[0],
|
||||||
|
BondedShares: assets,
|
||||||
|
DelegatorShares: liabilities,
|
||||||
|
}
|
||||||
|
pool := Pool{
|
||||||
|
TotalSupply: 0,
|
||||||
|
BondedShares: sdk.NewRat(248305),
|
||||||
|
UnbondedShares: sdk.NewRat(232147),
|
||||||
|
BondedPool: 248305,
|
||||||
|
UnbondedPool: 232147,
|
||||||
|
InflationLastTime: 0,
|
||||||
|
Inflation: sdk.NewRat(7, 100),
|
||||||
|
}
|
||||||
|
shares := sdk.NewRat(29)
|
||||||
|
msg := fmt.Sprintf("validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
||||||
|
val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
||||||
|
msg = fmt.Sprintf("Removed %v shares from %s", shares, msg)
|
||||||
|
_, newPool, tokens := val.removeShares(pool, shares)
|
||||||
|
require.Equal(t,
|
||||||
|
tokens+newPool.UnbondedPool+newPool.BondedPool,
|
||||||
|
pool.BondedPool+pool.UnbondedPool,
|
||||||
|
"Tokens were not conserved: %s", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO convert these commend out tests to test UpdateSharesLocation
|
||||||
|
//func TestUpdateSharesLocation(t *testing.T) {
|
||||||
|
//}
|
||||||
|
//func TestBondedToUnbondedPool(t *testing.T) {
|
||||||
|
//ctx, _, keeper := createTestInput(t, false, 0)
|
||||||
|
|
||||||
|
//poolA := keeper.GetPool(ctx)
|
||||||
|
//assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
||||||
|
//assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
||||||
|
//valA := Validator{
|
||||||
|
//Status: sdk.Bonded,
|
||||||
|
//Address: addrs[0],
|
||||||
|
//PubKey: pks[0],
|
||||||
|
//BondedShares: sdk.OneRat(),
|
||||||
|
//DelegatorShares: sdk.OneRat(),
|
||||||
|
//}
|
||||||
|
//poolB, valB := poolA.bondedToUnbondedPool(valA)
|
||||||
|
|
||||||
|
//// status unbonded
|
||||||
|
//assert.Equal(t, valB.Status, sdk.Unbonded)
|
||||||
|
//// same exchange rate, assets unchanged
|
||||||
|
//assert.Equal(t, valB.BondedShares, valA.BondedShares)
|
||||||
|
//// bonded pool decreased
|
||||||
|
//assert.Equal(t, poolB.BondedPool, poolA.BondedPool-valA.BondedShares.Evaluate())
|
||||||
|
//// unbonded pool increased
|
||||||
|
//assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+valA.BondedShares.Evaluate())
|
||||||
|
//// conservation of tokens
|
||||||
|
//assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//func TestUnbonbedtoBondedPool(t *testing.T) {
|
||||||
|
//ctx, _, keeper := createTestInput(t, false, 0)
|
||||||
|
|
||||||
|
//poolA := keeper.GetPool(ctx)
|
||||||
|
//assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
|
||||||
|
//assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
|
||||||
|
//valA := Validator{
|
||||||
|
//Status: sdk.Bonded,
|
||||||
|
//Address: addrs[0],
|
||||||
|
//PubKey: pks[0],
|
||||||
|
//BondedShares: sdk.OneRat(),
|
||||||
|
//DelegatorShares: sdk.OneRat(),
|
||||||
|
//}
|
||||||
|
//valA.Status = sdk.Unbonded
|
||||||
|
//poolB, valB := poolA.unbondedToBondedPool(valA)
|
||||||
|
|
||||||
|
//// status bonded
|
||||||
|
//assert.Equal(t, valB.Status, sdk.Bonded)
|
||||||
|
//// same exchange rate, assets unchanged
|
||||||
|
//assert.Equal(t, valB.BondedShares, valA.BondedShares)
|
||||||
|
//// bonded pool increased
|
||||||
|
//assert.Equal(t, poolB.BondedPool, poolA.BondedPool+valA.BondedShares.Evaluate())
|
||||||
|
//// unbonded pool decreased
|
||||||
|
//assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-valA.BondedShares.Evaluate())
|
||||||
|
//// conservation of tokens
|
||||||
|
//assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//________________________________________________________________________________
|
||||||
|
// TODO refactor this random setup
|
||||||
|
|
||||||
|
// generate a random validator
|
||||||
|
func randomValidator(r *rand.Rand) Validator {
|
||||||
|
var status sdk.BondStatus
|
||||||
|
if r.Float64() < float64(0.5) {
|
||||||
|
status = sdk.Bonded
|
||||||
|
} else {
|
||||||
|
status = sdk.Unbonded
|
||||||
|
}
|
||||||
|
assets := sdk.NewRat(int64(r.Int31n(10000)))
|
||||||
|
liabilities := sdk.NewRat(int64(r.Int31n(10000)))
|
||||||
|
return Validator{
|
||||||
|
Status: status,
|
||||||
|
Address: addrs[0],
|
||||||
|
PubKey: pks[0],
|
||||||
|
BondedShares: assets,
|
||||||
|
DelegatorShares: liabilities,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a random staking state
|
||||||
|
func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) {
|
||||||
|
pool := Pool{
|
||||||
|
TotalSupply: 0,
|
||||||
|
BondedShares: sdk.ZeroRat(),
|
||||||
|
UnbondedShares: sdk.ZeroRat(),
|
||||||
|
BondedPool: 0,
|
||||||
|
UnbondedPool: 0,
|
||||||
|
InflationLastTime: 0,
|
||||||
|
Inflation: sdk.NewRat(7, 100),
|
||||||
|
}
|
||||||
|
|
||||||
|
validators := make([]Validator, numValidators)
|
||||||
|
for i := 0; i < numValidators; i++ {
|
||||||
|
validator := randomValidator(r)
|
||||||
|
if validator.Status == sdk.Bonded {
|
||||||
|
pool.BondedShares = pool.BondedShares.Add(validator.BondedShares)
|
||||||
|
pool.BondedPool += validator.BondedShares.Evaluate()
|
||||||
|
} else if validator.Status == sdk.Unbonded {
|
||||||
|
pool.UnbondedShares = pool.UnbondedShares.Add(validator.BondedShares)
|
||||||
|
pool.UnbondedPool += validator.BondedShares.Evaluate()
|
||||||
|
}
|
||||||
|
validators[i] = validator
|
||||||
|
}
|
||||||
|
return pool, validators
|
||||||
|
}
|
||||||
|
|
||||||
|
// any operation that transforms staking state
|
||||||
|
// takes in RNG instance, pool, validator
|
||||||
|
// returns updated pool, updated validator, delta tokens, descriptive message
|
||||||
|
type Operation func(r *rand.Rand, p Pool, c Validator) (Pool, Validator, int64, string)
|
||||||
|
|
||||||
|
// operation: bond or unbond a validator depending on current status
|
||||||
|
func OpBondOrUnbond(r *rand.Rand, p Pool, val Validator) (Pool, Validator, int64, string) {
|
||||||
|
var msg string
|
||||||
|
if val.Status == sdk.Bonded {
|
||||||
|
msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
||||||
|
val.Address, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
||||||
|
val.Status = sdk.Unbonded
|
||||||
|
} else if val.Status == sdk.Unbonded {
|
||||||
|
msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
||||||
|
val.Address, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
||||||
|
val.Status = sdk.Bonded
|
||||||
|
}
|
||||||
|
val, p = val.UpdateSharesLocation(p)
|
||||||
|
return p, val, 0, msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// operation: add a random number of tokens to a validator
|
||||||
|
func OpAddTokens(r *rand.Rand, p Pool, val Validator) (Pool, Validator, int64, string) {
|
||||||
|
tokens := int64(r.Int31n(1000))
|
||||||
|
msg := fmt.Sprintf("validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
||||||
|
val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
||||||
|
val, p, _ = val.addTokens(p, tokens)
|
||||||
|
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
|
||||||
|
return p, val, -1 * tokens, msg // tokens are removed so for accounting must be negative
|
||||||
|
}
|
||||||
|
|
||||||
|
// operation: remove a random number of shares from a validator
|
||||||
|
func OpRemoveShares(r *rand.Rand, p Pool, val Validator) (Pool, Validator, int64, string) {
|
||||||
|
var shares sdk.Rat
|
||||||
|
for {
|
||||||
|
shares = sdk.NewRat(int64(r.Int31n(1000)))
|
||||||
|
if shares.LT(val.DelegatorShares) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
||||||
|
shares, val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
||||||
|
|
||||||
|
val, p, tokens := val.removeShares(p, shares)
|
||||||
|
return p, val, tokens, msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// pick a random staking operation
|
||||||
|
func randomOperation(r *rand.Rand) Operation {
|
||||||
|
operations := []Operation{
|
||||||
|
OpBondOrUnbond,
|
||||||
|
OpAddTokens,
|
||||||
|
OpRemoveShares,
|
||||||
|
}
|
||||||
|
r.Shuffle(len(operations), func(i, j int) {
|
||||||
|
operations[i], operations[j] = operations[j], operations[i]
|
||||||
|
})
|
||||||
|
return operations[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure invariants that should always be true are true
|
||||||
|
func assertInvariants(t *testing.T, msg string,
|
||||||
|
pOrig Pool, cOrig Validators, pMod Pool, cMods Validators, tokens int64) {
|
||||||
|
|
||||||
|
// total tokens conserved
|
||||||
|
require.Equal(t,
|
||||||
|
pOrig.UnbondedPool+pOrig.BondedPool,
|
||||||
|
pMod.UnbondedPool+pMod.BondedPool+tokens,
|
||||||
|
"Tokens not conserved - msg: %v\n, pOrig.BondedShares: %v, pOrig.UnbondedShares: %v, pMod.BondedShares: %v, pMod.UnbondedShares: %v, pOrig.UnbondedPool: %v, pOrig.BondedPool: %v, pMod.UnbondedPool: %v, pMod.BondedPool: %v, tokens: %v\n",
|
||||||
|
msg,
|
||||||
|
pOrig.BondedShares, pOrig.UnbondedShares,
|
||||||
|
pMod.BondedShares, pMod.UnbondedShares,
|
||||||
|
pOrig.UnbondedPool, pOrig.BondedPool,
|
||||||
|
pMod.UnbondedPool, pMod.BondedPool, tokens)
|
||||||
|
|
||||||
|
// 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 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().Evaluate())
|
||||||
|
|
||||||
|
// nonnegative unbonded ex rate
|
||||||
|
require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat()),
|
||||||
|
"Applying operation \"%s\" resulted in negative unbondedShareExRate: %d",
|
||||||
|
msg, pMod.unbondedShareExRate().Evaluate())
|
||||||
|
|
||||||
|
for _, cMod := range cMods {
|
||||||
|
|
||||||
|
// nonnegative ex rate
|
||||||
|
require.False(t, cMod.DelegatorShareExRate().LT(sdk.ZeroRat()),
|
||||||
|
"Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Address: %s)",
|
||||||
|
msg,
|
||||||
|
cMod.DelegatorShareExRate(),
|
||||||
|
cMod.Address,
|
||||||
|
)
|
||||||
|
|
||||||
|
// nonnegative assets
|
||||||
|
require.False(t, cMod.BondedShares.LT(sdk.ZeroRat()),
|
||||||
|
"Applying operation \"%s\" resulted in negative validator.BondedShares: %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Address: %s)",
|
||||||
|
msg,
|
||||||
|
cMod.BondedShares,
|
||||||
|
cMod.DelegatorShares,
|
||||||
|
cMod.DelegatorShareExRate(),
|
||||||
|
cMod.Address,
|
||||||
|
)
|
||||||
|
|
||||||
|
// nonnegative liabilities
|
||||||
|
require.False(t, cMod.DelegatorShares.LT(sdk.ZeroRat()),
|
||||||
|
"Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.BondedShares: %v, validator.DelegatorShareExRate: %v, validator.Address: %s)",
|
||||||
|
msg,
|
||||||
|
cMod.DelegatorShares,
|
||||||
|
cMod.BondedShares,
|
||||||
|
cMod.DelegatorShareExRate(),
|
||||||
|
cMod.Address,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPossibleOverflow(t *testing.T) {
|
||||||
|
assets := sdk.NewRat(2159)
|
||||||
|
liabilities := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664))
|
||||||
|
val := Validator{
|
||||||
|
Status: sdk.Bonded,
|
||||||
|
Address: addrs[0],
|
||||||
|
PubKey: pks[0],
|
||||||
|
BondedShares: assets,
|
||||||
|
DelegatorShares: liabilities,
|
||||||
|
}
|
||||||
|
pool := Pool{
|
||||||
|
TotalSupply: 0,
|
||||||
|
BondedShares: assets,
|
||||||
|
UnbondedShares: sdk.ZeroRat(),
|
||||||
|
BondedPool: assets.Evaluate(),
|
||||||
|
UnbondedPool: 0,
|
||||||
|
InflationLastTime: 0,
|
||||||
|
Inflation: sdk.NewRat(7, 100),
|
||||||
|
}
|
||||||
|
tokens := int64(71)
|
||||||
|
msg := fmt.Sprintf("validator %s (status: %d, assets: %v, liabilities: %v, DelegatorShareExRate: %v)",
|
||||||
|
val.Address, val.Status, val.BondedShares, val.DelegatorShares, val.DelegatorShareExRate())
|
||||||
|
newValidator, _, _ := val.addTokens(pool, tokens)
|
||||||
|
|
||||||
|
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
|
||||||
|
require.False(t, newValidator.DelegatorShareExRate().LT(sdk.ZeroRat()),
|
||||||
|
"Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v",
|
||||||
|
msg, newValidator.DelegatorShareExRate())
|
||||||
|
}
|
||||||
|
|
||||||
|
// run random operations in a random order on a random single-validator state, assert invariants hold
|
||||||
|
func TestSingleValidatorIntegrationInvariants(t *testing.T) {
|
||||||
|
r := rand.New(rand.NewSource(41))
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
poolOrig, validatorsOrig := randomSetup(r, 1)
|
||||||
|
require.Equal(t, 1, len(validatorsOrig))
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
assertInvariants(t, "no operation",
|
||||||
|
poolOrig, validatorsOrig,
|
||||||
|
poolOrig, validatorsOrig, 0)
|
||||||
|
|
||||||
|
for j := 0; j < 5; j++ {
|
||||||
|
poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[0])
|
||||||
|
|
||||||
|
validatorsMod := make([]Validator, len(validatorsOrig))
|
||||||
|
copy(validatorsMod[:], validatorsOrig[:])
|
||||||
|
require.Equal(t, 1, len(validatorsOrig), "j %v", j)
|
||||||
|
require.Equal(t, 1, len(validatorsMod), "j %v", j)
|
||||||
|
validatorsMod[0] = validatorMod
|
||||||
|
|
||||||
|
assertInvariants(t, msg,
|
||||||
|
poolOrig, validatorsOrig,
|
||||||
|
poolMod, validatorsMod, tokens)
|
||||||
|
|
||||||
|
poolOrig = poolMod
|
||||||
|
validatorsOrig = validatorsMod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run random operations in a random order on a random multi-validator state, assert invariants hold
|
||||||
|
func TestMultiValidatorIntegrationInvariants(t *testing.T) {
|
||||||
|
r := rand.New(rand.NewSource(42))
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
poolOrig, validatorsOrig := randomSetup(r, 100)
|
||||||
|
|
||||||
|
assertInvariants(t, "no operation",
|
||||||
|
poolOrig, validatorsOrig,
|
||||||
|
poolOrig, validatorsOrig, 0)
|
||||||
|
|
||||||
|
for j := 0; j < 5; j++ {
|
||||||
|
index := int(r.Int31n(int32(len(validatorsOrig))))
|
||||||
|
poolMod, validatorMod, tokens, 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)
|
||||||
|
|
||||||
|
poolOrig = poolMod
|
||||||
|
validatorsOrig = validatorsMod
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue