2018-02-23 15:57:31 -08:00
package stake
2018-03-29 05:27:35 -07:00
import (
2018-06-06 06:55:34 -07:00
"math/rand"
2018-06-05 20:12:28 -07:00
"strconv"
2018-03-29 05:27:35 -07:00
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
2018-06-06 06:55:34 -07:00
//change the int in NewSource to generate random input for tests that use r for randomization
var r = rand . New ( rand . NewSource ( 505 ) )
2018-03-29 05:27:35 -07:00
func TestGetInflation ( t * testing . T ) {
2018-04-01 09:05:58 -07:00
ctx , _ , keeper := createTestInput ( t , false , 0 )
2018-03-29 05:27:35 -07:00
pool := keeper . GetPool ( ctx )
params := keeper . GetParams ( ctx )
hrsPerYrRat := sdk . NewRat ( hrsPerYr )
// Governing Mechanism:
2018-05-12 17:21:02 -07:00
// bondedRatio = BondedTokens / TotalSupply
2018-03-29 05:27:35 -07:00
// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange
tests := [ ] struct {
2018-05-22 15:50:59 -07:00
name string
setBondedTokens , setLooseTokens int64
setInflation , expectedChange sdk . Rat
2018-03-29 05:27:35 -07:00
} {
// with 0% bonded atom supply the inflation should increase by InflationRateChange
2018-03-30 13:19:21 -07:00
{ "test 1" , 0 , 0 , sdk . NewRat ( 7 , 100 ) , params . InflationRateChange . Quo ( hrsPerYrRat ) . Round ( precision ) } ,
2018-03-29 05:27:35 -07:00
// 100% bonded, starting at 20% inflation and being reduced
// (1 - (1/0.67))*(0.13/8667)
2018-05-22 15:50:59 -07:00
{ "test 2" , 1 , 0 , sdk . NewRat ( 20 , 100 ) ,
2018-04-30 14:21:14 -07:00
sdk . OneRat ( ) . Sub ( sdk . OneRat ( ) . Quo ( params . GoalBonded ) ) . Mul ( params . InflationRateChange ) . Quo ( hrsPerYrRat ) . Round ( precision ) } ,
2018-03-29 05:27:35 -07:00
// 50% bonded, starting at 10% inflation and being increased
2018-05-22 15:50:59 -07:00
{ "test 3" , 1 , 1 , sdk . NewRat ( 10 , 100 ) ,
2018-04-30 14:21:14 -07:00
sdk . OneRat ( ) . Sub ( sdk . NewRat ( 1 , 2 ) . Quo ( params . GoalBonded ) ) . Mul ( params . InflationRateChange ) . Quo ( hrsPerYrRat ) . Round ( precision ) } ,
2018-03-29 05:27:35 -07:00
// test 7% minimum stop (testing with 100% bonded)
2018-05-22 15:50:59 -07:00
{ "test 4" , 1 , 0 , sdk . NewRat ( 7 , 100 ) , sdk . ZeroRat ( ) } ,
{ "test 5" , 1 , 0 , sdk . NewRat ( 70001 , 1000000 ) , sdk . NewRat ( - 1 , 1000000 ) . Round ( precision ) } ,
2018-03-29 05:27:35 -07:00
// test 20% maximum stop (testing with 0% bonded)
2018-04-30 14:21:14 -07:00
{ "test 6" , 0 , 0 , sdk . NewRat ( 20 , 100 ) , sdk . ZeroRat ( ) } ,
2018-03-30 13:19:21 -07:00
{ "test 7" , 0 , 0 , sdk . NewRat ( 199999 , 1000000 ) , sdk . NewRat ( 1 , 1000000 ) . Round ( precision ) } ,
2018-03-29 05:27:35 -07:00
// perfect balance shouldn't change inflation
2018-05-22 15:50:59 -07:00
{ "test 8" , 67 , 33 , sdk . NewRat ( 15 , 100 ) , sdk . ZeroRat ( ) } ,
2018-03-29 05:27:35 -07:00
}
for _ , tc := range tests {
2018-05-22 15:50:59 -07:00
pool . BondedTokens , pool . LooseUnbondedTokens = tc . setBondedTokens , tc . setLooseTokens
2018-03-29 05:27:35 -07:00
pool . Inflation = tc . setInflation
keeper . setPool ( ctx , pool )
inflation := keeper . nextInflation ( ctx )
diffInflation := inflation . Sub ( tc . setInflation )
assert . True ( t , diffInflation . Equal ( tc . expectedChange ) ,
"Name: %v\nDiff: %v\nExpected: %v\n" , tc . name , diffInflation , tc . expectedChange )
}
}
func TestProcessProvisions ( t * testing . T ) {
2018-04-01 09:05:58 -07:00
ctx , _ , keeper := createTestInput ( t , false , 0 )
2018-03-29 05:27:35 -07:00
pool := keeper . GetPool ( ctx )
2018-06-05 20:02:31 -07:00
var (
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 250000000
initialUnbondedTokens int64 = 300000000
cumulativeExpProvs int64
initialBondedShares = sdk . NewRat ( 250000000 , 1 )
initialUnbondedShares = sdk . NewRat ( 300000000 , 1 )
tokensForValidators = [ ] int64 { 150000000 , 100000000 , 100000000 , 100000000 , 100000000 }
bondedValidators uint16 = 2
)
2018-05-18 15:57:47 -07:00
// create some validators some bonded, some unbonded
2018-06-05 20:02:31 -07:00
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , tokensForValidators , bondedValidators )
checkValidatorSetup ( t , pool , initialTotalTokens , initialBondedTokens , initialUnbondedTokens )
// process the provisions for a year
for hr := 0 ; hr < 8766 ; hr ++ {
pool := keeper . GetPool ( ctx )
_ , expProvisions , _ := checkAndProcessProvisions ( t , keeper , pool , ctx , hr )
cumulativeExpProvs = cumulativeExpProvs + expProvisions
}
//get the pool and do the final value checks from checkFinalPoolValues
2018-05-22 15:50:59 -07:00
pool = keeper . GetPool ( ctx )
2018-06-05 20:02:31 -07:00
checkFinalPoolValues ( t , pool , initialTotalTokens ,
initialUnbondedTokens , cumulativeExpProvs ,
0 , 0 , initialBondedShares , initialUnbondedShares )
}
2018-05-22 15:50:59 -07:00
2018-06-05 20:12:28 -07:00
// Tests that the hourly rate of change will be positive, negative, or zero, depending on bonded ratio and inflation rate
// Cycles through the whole gambit of starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years)
func TestHourlyInflationRateOfChange ( t * testing . T ) {
ctx , _ , keeper := createTestInput ( t , false , 0 )
pool := keeper . GetPool ( ctx )
var (
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 150000000
initialUnbondedTokens int64 = 400000000
cumulativeExpProvs int64
bondedShares = sdk . NewRat ( 150000000 , 1 )
unbondedShares = sdk . NewRat ( 400000000 , 1 )
tokensForValidators = [ ] int64 { 150000000 , 100000000 , 100000000 , 100000000 , 100000000 }
bondedValidators uint16 = 1
)
2018-06-06 06:55:34 -07:00
// create some validators some bonded, some unbonded
2018-06-05 20:12:28 -07:00
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , tokensForValidators , bondedValidators )
checkValidatorSetup ( t , pool , initialTotalTokens , initialBondedTokens , initialUnbondedTokens )
// ~11.4 years to go from 7%, up to 20%, back down to 7%
for hr := 0 ; hr < 100000 ; hr ++ {
pool := keeper . GetPool ( ctx )
previousInflation := pool . Inflation
updatedInflation , expProvisions , pool := checkAndProcessProvisions ( t , keeper , pool , ctx , hr )
cumulativeExpProvs = cumulativeExpProvs + expProvisions
msg := strconv . Itoa ( hr )
checkInflation ( t , pool , previousInflation , updatedInflation , msg )
}
// Final check that the pool equals initial values + cumulative provisions and adjustments we recorded
pool = keeper . GetPool ( ctx )
checkFinalPoolValues ( t , pool , initialTotalTokens ,
initialUnbondedTokens , cumulativeExpProvs ,
0 , 0 , bondedShares , unbondedShares )
}
2018-06-06 06:55:34 -07:00
//Test that a large unbonding will significantly lower the bonded ratio
func TestLargeUnbond ( t * testing . T ) {
ctx , _ , keeper := createTestInput ( t , false , 0 )
pool := keeper . GetPool ( ctx )
var (
initialTotalTokens int64 = 1200000000
initialBondedTokens int64 = 900000000
initialUnbondedTokens int64 = 300000000
val0UnbondedTokens int64
bondedShares = sdk . NewRat ( 900000000 , 1 )
unbondedShares = sdk . NewRat ( 300000000 , 1 )
bondSharesVal0 = sdk . NewRat ( 300000000 , 1 )
tokensForValidators = [ ] int64 { 300000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 }
bondedValidators uint16 = 7
)
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , tokensForValidators , bondedValidators )
checkValidatorSetup ( t , pool , initialTotalTokens , initialBondedTokens , initialUnbondedTokens )
pool = keeper . GetPool ( ctx )
validator , found := keeper . GetValidator ( ctx , addrs [ 0 ] )
assert . True ( t , found )
// initialBondedRatio that we can use to compare to the new values after the unbond
initialBondedRatio := pool . bondedRatio ( )
// validator[0] will be unbonded, bringing us from 75% to ~50%
// This func will unbond 300,000,000 tokens that were previously bonded
pool , validator , _ , _ = OpBondOrUnbond ( r , pool , validator )
keeper . setPool ( ctx , pool )
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
_ , expProvisionsAfter , pool := checkAndProcessProvisions ( t , keeper , pool , ctx , 0 )
bondedShares = bondedShares . Sub ( bondSharesVal0 )
val0UnbondedTokens = pool . unbondedShareExRate ( ) . Mul ( validator . PoolShares . Unbonded ( ) ) . Evaluate ( )
unbondedShares = unbondedShares . Add ( sdk . NewRat ( val0UnbondedTokens , 1 ) . Mul ( pool . unbondedShareExRate ( ) ) )
// unbonded shares should increase
2018-06-06 10:11:36 -07:00
assert . True ( t , unbondedShares . GT ( sdk . NewRat ( 300000000 , 1 ) ) )
2018-06-06 06:55:34 -07:00
// Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 55 < 72)
assert . True ( t , ( pool . bondedRatio ( ) . LT ( initialBondedRatio ) ) )
// Final check that the pool equals initial values + provisions and adjustments we recorded
pool = keeper . GetPool ( ctx )
checkFinalPoolValues ( t , pool , initialTotalTokens ,
initialUnbondedTokens , expProvisionsAfter ,
- val0UnbondedTokens , val0UnbondedTokens , bondedShares , unbondedShares )
}
2018-06-06 10:11:36 -07:00
//Test that a large bonding will significantly increase the bonded ratio
func TestLargeBond ( t * testing . T ) {
ctx , _ , keeper := createTestInput ( t , false , 0 )
pool := keeper . GetPool ( ctx )
var (
initialTotalTokens int64 = 1600000000
initialBondedTokens int64 = 400000000
initialUnbondedTokens int64 = 1200000000
val9UnbondedTokens int64 = 400000000
val9BondedTokens int64
bondedShares = sdk . NewRat ( 400000000 , 1 )
unbondedShares = sdk . NewRat ( 1200000000 , 1 )
unbondedSharesVal9 = sdk . NewRat ( 400000000 , 1 )
tokensForValidators = [ ] int64 { 400000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 400000000 }
bondedValidators uint16 = 1
)
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , tokensForValidators , bondedValidators )
checkValidatorSetup ( t , pool , initialTotalTokens , initialBondedTokens , initialUnbondedTokens )
pool = keeper . GetPool ( ctx )
validator , found := keeper . GetValidator ( ctx , addrs [ 9 ] )
assert . True ( t , found )
// initialBondedRatio that we can use to compare to the new values after the unbond
initialBondedRatio := pool . bondedRatio ( )
params := defaultParams ( )
params . MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond
keeper . setParams ( ctx , params )
// validator[9] will be bonded, bringing us from 25% to ~50%
// This func will bond 400,000,000 tokens that were previously unbonded
pool , validator , _ , _ = OpBondOrUnbond ( r , pool , validator )
keeper . setPool ( ctx , pool )
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
_ , expProvisionsAfter , pool := checkAndProcessProvisions ( t , keeper , pool , ctx , 0 )
unbondedShares = unbondedShares . Sub ( unbondedSharesVal9 )
val9BondedTokens = val9UnbondedTokens
val9UnbondedTokens = 0
bondedTokens := initialBondedTokens + val9BondedTokens + expProvisionsAfter
bondedShares = sdk . NewRat ( bondedTokens , 1 ) . Quo ( pool . bondedShareExRate ( ) )
// unbonded shares should decrease
assert . True ( t , unbondedShares . LT ( sdk . NewRat ( 1200000000 , 1 ) ) )
// Ensure that new bonded ratio is greater than old bonded ratio (i.e. 50% > 25%)
assert . True ( t , ( pool . bondedRatio ( ) . GT ( initialBondedRatio ) ) )
// Final check that the pool equals initial values + provisions and adjustments we recorded
pool = keeper . GetPool ( ctx )
checkFinalPoolValues ( t , pool , initialTotalTokens ,
initialUnbondedTokens , expProvisionsAfter ,
val9BondedTokens , - val9BondedTokens , bondedShares , unbondedShares )
}
2018-06-05 20:02:31 -07:00
////////////////////////////////HELPER FUNCTIONS BELOW/////////////////////////////////////
2018-05-18 15:57:47 -07:00
2018-06-05 20:02:31 -07:00
// Final check on the global pool values for what the total tokens accumulated from each hour of provisions and other functions
// bondedAdjustment and unbondedAdjustment are the accumulated changes for the operations of the test (i.e. if three unbonds happened, their total value would be passed as unbondedAdjustment)
func checkFinalPoolValues ( t * testing . T , pool Pool , initialTotalTokens , initialUnbondedTokens ,
cumulativeExpProvs , bondedAdjustment , unbondedAdjustment int64 , bondedShares , unbondedShares sdk . Rat ) {
2018-03-29 05:27:35 -07:00
2018-06-05 20:02:31 -07:00
initialBonded := initialTotalTokens - initialUnbondedTokens
calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs
calculatedBondedTokens := initialBonded + cumulativeExpProvs + bondedAdjustment
calculatedUnbondedTokens := initialUnbondedTokens + unbondedAdjustment
2018-03-29 05:27:35 -07:00
2018-06-05 20:02:31 -07:00
// test that the bonded ratio the pool has is equal to what we calculated for tokens
assert . True ( t , pool . bondedRatio ( ) . Equal ( sdk . NewRat ( calculatedBondedTokens , calculatedTotalTokens ) ) , "%v" , pool . bondedRatio ( ) )
2018-03-29 05:27:35 -07:00
2018-06-05 20:02:31 -07:00
// test global supply
assert . Equal ( t , calculatedTotalTokens , pool . TokenSupply ( ) )
assert . Equal ( t , calculatedBondedTokens , pool . BondedTokens )
assert . Equal ( t , calculatedUnbondedTokens , pool . UnbondedTokens )
2018-03-29 05:27:35 -07:00
2018-06-06 06:55:34 -07:00
// test the value of validator shares
2018-06-05 20:02:31 -07:00
assert . True ( t , pool . bondedShareExRate ( ) . Mul ( bondedShares ) . Equal ( sdk . NewRat ( calculatedBondedTokens ) ) , "%v" , pool . bondedShareExRate ( ) )
assert . True ( t , pool . unbondedShareExRate ( ) . Mul ( unbondedShares ) . Equal ( sdk . NewRat ( calculatedUnbondedTokens ) ) , "%v" , pool . unbondedShareExRate ( ) )
}
// Checks provisions are added to the pool correctly every hour
// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests
func checkAndProcessProvisions ( t * testing . T , keeper Keeper , pool Pool , ctx sdk . Context , hr int ) ( sdk . Rat , int64 , Pool ) {
//If we are not doing a random operation, just check that normal provisions are working for each hour
expInflation := keeper . nextInflation ( ctx )
expProvisions := ( expInflation . Mul ( sdk . NewRat ( pool . TokenSupply ( ) ) ) . Quo ( hrsPerYrRat ) ) . Evaluate ( )
startBondedPool := pool . BondedTokens
startTotalSupply := pool . TokenSupply ( )
pool = keeper . processProvisions ( ctx )
keeper . setPool ( ctx , pool )
//check provisions were added to pool
require . Equal ( t , startBondedPool + expProvisions , pool . BondedTokens , "hr %v" , hr )
require . Equal ( t , startTotalSupply + expProvisions , pool . TokenSupply ( ) )
return expInflation , expProvisions , pool
}
// Deterministic setup of validators, which updates the pool and choose maxValidators to be bonded
// Allows you to decide how many validators to setup, and which ones you want bonded
// You choose bonded validators by setting params.MaxValidators. If you choose 2, the first 2 Validators in the arrray will be bonded, the rest unbonded
func setupTestValidators ( pool Pool , keeper Keeper , ctx sdk . Context , validatorTokens [ ] int64 , maxValidators uint16 ) ( [ ] Validator , Keeper , Pool ) {
params := defaultParams ( )
params . MaxValidators = maxValidators //set to limit the amount of validators we want bonded
keeper . setParams ( ctx , params )
numValidators := len ( validatorTokens )
validators := make ( [ ] Validator , numValidators )
for i := 0 ; i < numValidators ; i ++ {
validators [ i ] = NewValidator ( addrs [ i ] , pks [ i ] , Description { } )
validators [ i ] , pool , _ = validators [ i ] . addTokensFromDel ( pool , validatorTokens [ i ] )
2018-03-29 05:27:35 -07:00
keeper . setPool ( ctx , pool )
2018-06-06 10:11:36 -07:00
validators [ i ] = keeper . updateValidator ( ctx , validators [ i ] ) //will kick out lower power validators. must keep in mind when setting up the test validators order
2018-06-05 20:02:31 -07:00
pool = keeper . GetPool ( ctx )
2018-03-29 05:27:35 -07:00
}
2018-06-05 20:02:31 -07:00
return validators , keeper , pool
}
2018-06-06 06:55:34 -07:00
// Checks that the deterministic validator setup you wanted matches the values in the pool
2018-06-05 20:02:31 -07:00
func checkValidatorSetup ( t * testing . T , pool Pool , initialTotalTokens , initialBondedTokens , initialUnbondedTokens int64 ) {
2018-03-29 05:27:35 -07:00
2018-06-05 20:02:31 -07:00
assert . Equal ( t , initialTotalTokens , pool . TokenSupply ( ) )
assert . Equal ( t , initialBondedTokens , pool . BondedTokens )
assert . Equal ( t , initialUnbondedTokens , pool . UnbondedTokens )
2018-03-29 05:27:35 -07:00
2018-06-05 20:02:31 -07:00
// test initial bonded ratio
assert . True ( t , pool . bondedRatio ( ) . Equal ( sdk . NewRat ( initialBondedTokens , initialTotalTokens ) ) , "%v" , pool . bondedRatio ( ) )
2018-06-06 06:55:34 -07:00
// test the value of validator shares
2018-06-05 20:02:31 -07:00
assert . True ( t , pool . bondedShareExRate ( ) . Equal ( sdk . OneRat ( ) ) , "%v" , pool . bondedShareExRate ( ) )
2018-03-29 05:27:35 -07:00
}
2018-06-05 20:12:28 -07:00
// Checks that The inflation will correctly increase or decrease after an update to the pool
func checkInflation ( t * testing . T , pool Pool , previousInflation , updatedInflation sdk . Rat , msg string ) {
inflationChange := updatedInflation . Sub ( previousInflation )
switch {
//BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation
case pool . bondedRatio ( ) . LT ( sdk . NewRat ( 67 , 100 ) ) && updatedInflation . LT ( sdk . NewRat ( 20 , 100 ) ) :
assert . Equal ( t , true , inflationChange . GT ( sdk . ZeroRat ( ) ) , msg )
//BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio
case pool . bondedRatio ( ) . LT ( sdk . NewRat ( 67 , 100 ) ) && updatedInflation . Equal ( sdk . NewRat ( 20 , 100 ) ) :
if previousInflation . Equal ( sdk . NewRat ( 20 , 100 ) ) {
assert . Equal ( t , true , inflationChange . IsZero ( ) , msg )
//This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%)
} else {
assert . Equal ( t , true , inflationChange . GT ( sdk . ZeroRat ( ) ) , msg )
}
//ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7%
case pool . bondedRatio ( ) . GT ( sdk . NewRat ( 67 , 100 ) ) && updatedInflation . LT ( sdk . NewRat ( 20 , 100 ) ) && updatedInflation . GT ( sdk . NewRat ( 7 , 100 ) ) :
assert . Equal ( t , true , inflationChange . LT ( sdk . ZeroRat ( ) ) , msg )
//ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%.
case pool . bondedRatio ( ) . GT ( sdk . NewRat ( 67 , 100 ) ) && updatedInflation . Equal ( sdk . NewRat ( 7 , 100 ) ) :
if previousInflation . Equal ( sdk . NewRat ( 7 , 100 ) ) {
assert . Equal ( t , true , inflationChange . IsZero ( ) , msg )
//This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%)
} else {
assert . Equal ( t , true , inflationChange . LT ( sdk . ZeroRat ( ) ) , msg )
}
}
}