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 11:05:51 -07:00
//changing the int in NewSource will allow you to test different, deterministic, sets of operations
var r = rand . New ( rand . NewSource ( 6595 ) )
2018-06-06 06:55:34 -07:00
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 )
}
}
2018-06-06 11:05:51 -07:00
// Test that provisions are correctly added to the pool and validators each hour for 1 year
2018-03-29 05:27:35 -07:00
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
2018-06-06 11:05:51 -07:00
validatorTokens = [ ] int64 { 150000000 , 100000000 , 100000000 , 100000000 , 100000000 }
2018-06-05 20:02:31 -07:00
bondedValidators uint16 = 2
)
2018-05-18 15:57:47 -07:00
// create some validators some bonded, some unbonded
2018-06-06 11:05:51 -07:00
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , validatorTokens , bondedValidators )
2018-06-05 20:02:31 -07:00
checkValidatorSetup ( t , pool , initialTotalTokens , initialBondedTokens , initialUnbondedTokens )
// process the provisions for a year
for hr := 0 ; hr < 8766 ; hr ++ {
pool := keeper . GetPool ( ctx )
2018-06-06 11:05:51 -07:00
_ , expProvisions , _ := updateProvisions ( t , keeper , pool , ctx , hr )
2018-06-05 20:02:31 -07:00
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-13 00:12:57 -07:00
checkFinalPoolValues ( t , pool , initialTotalTokens , cumulativeExpProvs )
2018-06-05 20:02:31 -07:00
}
2018-05-22 15:50:59 -07:00
2018-06-06 11:05:51 -07:00
// Tests that the hourly rate of change of inflation will be positive, negative, or zero, depending on bonded ratio and inflation rate
// Cycles through the whole gambit of inflation possibilities, starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years)
2018-06-05 20:12:28 -07:00
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
2018-06-06 11:05:51 -07:00
validatorTokens = [ ] int64 { 150000000 , 100000000 , 100000000 , 100000000 , 100000000 }
2018-06-05 20:12:28 -07:00
bondedValidators uint16 = 1
)
2018-06-06 06:55:34 -07:00
// create some validators some bonded, some unbonded
2018-06-06 11:05:51 -07:00
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , validatorTokens , bondedValidators )
2018-06-05 20:12:28 -07:00
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
2018-06-06 11:05:51 -07:00
updatedInflation , expProvisions , pool := updateProvisions ( t , keeper , pool , ctx , hr )
2018-06-05 20:12:28 -07:00
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 )
2018-06-13 00:12:57 -07:00
checkFinalPoolValues ( t , pool , initialTotalTokens , cumulativeExpProvs )
2018-06-05 20:12:28 -07:00
}
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 )
2018-06-06 11:05:51 -07:00
validatorTokens = [ ] int64 { 300000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 }
2018-06-06 06:55:34 -07:00
bondedValidators uint16 = 7
)
2018-06-06 11:05:51 -07:00
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , validatorTokens , bondedValidators )
2018-06-06 06:55:34 -07:00
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 ( )
2018-06-06 11:05:51 -07:00
// validator[0] will be unbonded, bringing us from 75% bonded ratio to ~50% (unbonding 300,000,000)
2018-06-06 06:55:34 -07:00
pool , validator , _ , _ = OpBondOrUnbond ( r , pool , validator )
keeper . setPool ( ctx , pool )
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
2018-06-06 11:05:51 -07:00
_ , expProvisionsAfter , pool := updateProvisions ( t , keeper , pool , ctx , 0 )
2018-06-06 06:55:34 -07:00
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 11:05:51 -07:00
// Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 50% < 75)
2018-06-06 06:55:34 -07:00
assert . True ( t , ( pool . bondedRatio ( ) . LT ( initialBondedRatio ) ) )
// Final check that the pool equals initial values + provisions and adjustments we recorded
pool = keeper . GetPool ( ctx )
2018-06-13 00:12:57 -07:00
checkFinalPoolValues ( t , pool , initialTotalTokens , expProvisionsAfter )
2018-06-06 06:55:34 -07:00
}
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 (
2018-06-13 00:12:57 -07:00
initialTotalTokens int64 = 1600000000
initialBondedTokens int64 = 400000000
initialUnbondedTokens int64 = 1200000000
2018-06-06 10:11:36 -07:00
unbondedShares = sdk . NewRat ( 1200000000 , 1 )
unbondedSharesVal9 = sdk . NewRat ( 400000000 , 1 )
2018-06-06 11:05:51 -07:00
validatorTokens = [ ] int64 { 400000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 100000000 , 400000000 }
2018-06-06 10:11:36 -07:00
bondedValidators uint16 = 1
)
2018-06-06 11:05:51 -07:00
_ , keeper , pool = setupTestValidators ( pool , keeper , ctx , validatorTokens , bondedValidators )
2018-06-06 10:11:36 -07:00
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 ( )
2018-06-06 12:49:22 -07:00
params := DefaultParams ( )
2018-06-06 10:11:36 -07:00
params . MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond
keeper . setParams ( ctx , params )
2018-06-06 11:05:51 -07:00
// validator[9] will be bonded, bringing us from 25% to ~50% (bonding 400,000,000 tokens)
2018-06-06 10:11:36 -07:00
pool , validator , _ , _ = OpBondOrUnbond ( r , pool , validator )
keeper . setPool ( ctx , pool )
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
2018-06-06 11:05:51 -07:00
_ , expProvisionsAfter , pool := updateProvisions ( t , keeper , pool , ctx , 0 )
2018-06-06 10:11:36 -07:00
unbondedShares = unbondedShares . Sub ( unbondedSharesVal9 )
// 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 )
2018-06-13 00:12:57 -07:00
checkFinalPoolValues ( t , pool , initialTotalTokens , expProvisionsAfter )
2018-06-06 10:11:36 -07:00
}
2018-06-06 11:05:51 -07:00
// Tests that inflation increases or decreases as expected when we do a random operation on 20 different validators
func TestInflationWithRandomOperations ( t * testing . T ) {
ctx , _ , keeper := createTestInput ( t , false , 0 )
2018-06-06 12:49:22 -07:00
params := DefaultParams ( )
2018-06-06 11:05:51 -07:00
keeper . setParams ( ctx , params )
numValidators := 20
// start off by randomly setting up 20 validators
pool , validators := randomSetup ( r , numValidators )
require . Equal ( t , numValidators , len ( validators ) )
for i := 0 ; i < len ( validators ) ; i ++ {
keeper . setValidator ( ctx , validators [ i ] )
}
keeper . setPool ( ctx , pool )
// Used to rotate validators so each random operation is applied to a different validator
validatorCounter := 0
// Loop through 20 random operations, and check the inflation after each operation
for i := 0 ; i < numValidators ; i ++ {
pool := keeper . GetPool ( ctx )
// Get inflation before randomOperation, for comparison later
previousInflation := pool . Inflation
// Perform the random operation, and record how validators are modified
poolMod , validatorMod , tokens , msg := randomOperation ( r ) ( r , pool , validators [ validatorCounter ] )
validatorsMod := make ( [ ] Validator , len ( validators ) )
copy ( validatorsMod [ : ] , validators [ : ] )
require . Equal ( t , numValidators , len ( validators ) , "i %v" , validatorCounter )
require . Equal ( t , numValidators , len ( validatorsMod ) , "i %v" , validatorCounter )
validatorsMod [ validatorCounter ] = validatorMod
assertInvariants ( t , msg ,
pool , validators ,
poolMod , validatorsMod , tokens )
// set pool and validators after the random operation
pool = poolMod
keeper . setPool ( ctx , pool )
validators = validatorsMod
// Must set inflation here manually, as opposed to most other tests in this suite, where we call keeper.processProvisions(), which updates pool.Inflation
updatedInflation := keeper . nextInflation ( ctx )
pool . Inflation = updatedInflation
keeper . setPool ( ctx , pool )
// Ensure inflation changes as expected when random operations are applied.
checkInflation ( t , pool , previousInflation , updatedInflation , msg )
validatorCounter ++
}
}
2018-06-13 00:12:57 -07:00
//_________________________________________________________________________________________
2018-06-05 20:02:31 -07:00
////////////////////////////////HELPER FUNCTIONS BELOW/////////////////////////////////////
2018-05-18 15:57:47 -07:00
2018-06-13 00:12:57 -07:00
// Final check on the global pool values for what the total tokens accumulated from each hour of provisions
func checkFinalPoolValues ( t * testing . T , pool Pool , initialTotalTokens , cumulativeExpProvs int64 ) {
2018-06-05 20:02:31 -07:00
calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs
assert . Equal ( t , calculatedTotalTokens , pool . TokenSupply ( ) )
}
2018-06-06 11:05:51 -07:00
// Processes provisions are added to the pool correctly every hour
2018-06-05 20:02:31 -07:00
// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests
2018-06-06 11:05:51 -07:00
func updateProvisions ( t * testing . T , keeper Keeper , pool Pool , ctx sdk . Context , hr int ) ( sdk . Rat , int64 , Pool ) {
2018-06-05 20:02:31 -07:00
expInflation := keeper . nextInflation ( ctx )
expProvisions := ( expInflation . Mul ( sdk . NewRat ( pool . TokenSupply ( ) ) ) . Quo ( hrsPerYrRat ) ) . Evaluate ( )
startTotalSupply := pool . TokenSupply ( )
pool = keeper . processProvisions ( ctx )
keeper . setPool ( ctx , pool )
//check provisions were added to pool
require . Equal ( t , startTotalSupply + expProvisions , pool . TokenSupply ( ) )
return expInflation , expProvisions , pool
}
2018-06-06 11:05:51 -07:00
// Deterministic setup of validators and pool
// Allows you to decide how many validators to setup
// Allows you to pick which validators are bonded by adjusting the MaxValidators of params
2018-06-05 20:02:31 -07:00
func setupTestValidators ( pool Pool , keeper Keeper , ctx sdk . Context , validatorTokens [ ] int64 , maxValidators uint16 ) ( [ ] Validator , Keeper , Pool ) {
2018-06-06 12:49:22 -07:00
params := DefaultParams ( )
2018-06-06 11:05:51 -07:00
params . MaxValidators = maxValidators
2018-06-05 20:02:31 -07:00
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 11:05:51 -07:00
validators [ i ] = keeper . updateValidator ( ctx , validators [ i ] ) //will kick out lower power validators. Keep this 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 ) {
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 )
2018-06-06 11:05:51 -07:00
2018-06-05 20:12:28 -07:00
//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 )
2018-06-06 11:05:51 -07:00
2018-06-05 20:12:28 -07:00
//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 )
}
}
}