2018-07-11 10:43:25 -07:00
package simulation
2018-07-10 11:46:28 -07:00
import (
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/mock"
2018-07-11 10:43:25 -07:00
"github.com/cosmos/cosmos-sdk/x/stake"
2018-07-10 11:46:28 -07:00
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
)
2018-07-10 19:36:12 -07:00
var (
stats = make ( map [ string ] int )
)
2018-07-10 11:46:28 -07:00
// ModuleInvariants runs all invariants of the stake module.
// Currently: total supply, positive power
2018-07-11 10:43:25 -07:00
func ModuleInvariants ( ck bank . Keeper , k stake . Keeper ) mock . Invariant {
2018-07-10 11:46:28 -07:00
return func ( t * testing . T , app * mock . App , log string ) {
2018-07-10 17:36:50 -07:00
SupplyInvariants ( ck , k ) ( t , app , log )
2018-07-10 11:46:28 -07:00
PositivePowerInvariant ( k ) ( t , app , log )
2018-07-10 17:36:50 -07:00
ValidatorSetInvariant ( k ) ( t , app , log )
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations
2018-07-11 10:43:25 -07:00
func SupplyInvariants ( ck bank . Keeper , k stake . Keeper ) mock . Invariant {
2018-07-10 11:46:28 -07:00
return func ( t * testing . T , app * mock . App , log string ) {
ctx := app . NewContext ( false , abci . Header { } )
pool := k . GetPool ( ctx )
// Loose tokens should equal coin supply
loose := sdk . ZeroInt ( )
app . AccountMapper . IterateAccounts ( ctx , func ( acc auth . Account ) bool {
loose = loose . Add ( acc . GetCoins ( ) . AmountOf ( "steak" ) )
return false
} )
2018-07-16 17:41:36 -07:00
require . True ( t , pool . LooseTokens . RoundInt64 ( ) == loose . Int64 ( ) , "expected loose tokens to equal total steak held by accounts - pool.LooseTokens: %v, sum of account tokens: %v\nlog: %s" ,
2018-07-10 19:36:12 -07:00
pool . LooseTokens , loose , log )
stats [ "stake/invariant/looseTokens" ] += 1
2018-07-10 11:46:28 -07:00
// Bonded tokens should equal sum of tokens with bonded validators
2018-07-10 17:36:50 -07:00
bonded := sdk . ZeroRat ( )
k . IterateValidators ( ctx , func ( _ int64 , validator sdk . Validator ) bool {
switch validator . GetStatus ( ) {
case sdk . Bonded :
bonded = bonded . Add ( validator . GetPower ( ) )
}
return false
} )
2018-07-16 17:41:36 -07:00
require . True ( t , pool . BondedTokens . Equal ( bonded ) , "expected bonded tokens to equal total steak held by bonded validators\nlog: %s" , log )
2018-07-10 19:36:12 -07:00
stats [ "stake/invariant/bondedTokens" ] += 1
2018-07-10 17:36:50 -07:00
// TODO Inflation check on total supply
2018-07-10 11:46:28 -07:00
}
}
// PositivePowerInvariant checks that all stored validators have > 0 power
2018-07-11 10:43:25 -07:00
func PositivePowerInvariant ( k stake . Keeper ) mock . Invariant {
2018-07-10 11:46:28 -07:00
return func ( t * testing . T , app * mock . App , log string ) {
ctx := app . NewContext ( false , abci . Header { } )
k . IterateValidatorsBonded ( ctx , func ( _ int64 , validator sdk . Validator ) bool {
require . True ( t , validator . GetPower ( ) . GT ( sdk . ZeroRat ( ) ) , "validator with non-positive power stored" )
return false
} )
2018-07-10 19:36:12 -07:00
stats [ "stake/invariant/positivePower" ] += 1
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set
2018-07-11 10:43:25 -07:00
func ValidatorSetInvariant ( k stake . Keeper ) mock . Invariant {
2018-07-10 17:36:50 -07:00
return func ( t * testing . T , app * mock . App , log string ) {
// TODO
}
}
// SimulateMsgCreateValidator
2018-07-11 10:43:25 -07:00
func SimulateMsgCreateValidator ( m auth . AccountMapper , k stake . Keeper ) mock . TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func ( t * testing . T , r * rand . Rand , ctx sdk . Context , keys [ ] crypto . PrivKey , log string ) ( action string , err sdk . Error ) {
denom := k . GetParams ( ctx ) . BondDenom
2018-07-11 10:43:25 -07:00
description := stake . Description {
2018-07-10 17:36:50 -07:00
Moniker : mock . RandStringOfLength ( r , 10 ) ,
}
key := keys [ r . Intn ( len ( keys ) ) ]
pubkey := key . PubKey ( )
address := sdk . AccAddress ( pubkey . Address ( ) )
amount := m . GetAccount ( ctx , address ) . GetCoins ( ) . AmountOf ( denom )
if amount . GT ( sdk . ZeroInt ( ) ) {
amount = sdk . NewInt ( int64 ( r . Intn ( int ( amount . Int64 ( ) ) ) ) )
}
2018-07-10 19:36:12 -07:00
if amount . Equal ( sdk . ZeroInt ( ) ) {
return "nop" , nil
}
2018-07-11 10:43:25 -07:00
msg := stake . MsgCreateValidator {
2018-07-11 10:17:09 -07:00
Description : description ,
ValidatorAddr : address ,
DelegatorAddr : address ,
PubKey : pubkey ,
Delegation : sdk . NewIntCoin ( denom , amount ) ,
2018-07-10 17:36:50 -07:00
}
2018-07-10 19:36:12 -07:00
require . Nil ( t , msg . ValidateBasic ( ) , "expected msg to pass ValidateBasic: %s" , msg . GetSignBytes ( ) )
2018-07-10 19:55:57 -07:00
ctx , write := ctx . CacheContext ( )
2018-07-11 10:43:25 -07:00
result := stake . NewHandler ( k ) ( ctx , msg )
2018-07-10 19:55:57 -07:00
if result . IsOK ( ) {
write ( )
}
2018-07-10 19:36:12 -07:00
stats [ fmt . Sprintf ( "stake/createvalidator/%v" , result . IsOK ( ) ) ] += 1
// require.True(t, result.IsOK(), "expected OK result but instead got %v", result)
2018-07-10 17:36:50 -07:00
action = fmt . Sprintf ( "TestMsgCreateValidator: %s" , msg . GetSignBytes ( ) )
return action , nil
}
}
// SimulateMsgEditValidator
2018-07-11 10:43:25 -07:00
func SimulateMsgEditValidator ( k stake . Keeper ) mock . TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func ( t * testing . T , r * rand . Rand , ctx sdk . Context , keys [ ] crypto . PrivKey , log string ) ( action string , err sdk . Error ) {
2018-07-11 10:43:25 -07:00
description := stake . Description {
2018-07-10 17:36:50 -07:00
Moniker : mock . RandStringOfLength ( r , 10 ) ,
Identity : mock . RandStringOfLength ( r , 10 ) ,
Website : mock . RandStringOfLength ( r , 10 ) ,
Details : mock . RandStringOfLength ( r , 10 ) ,
}
key := keys [ r . Intn ( len ( keys ) ) ]
pubkey := key . PubKey ( )
address := sdk . AccAddress ( pubkey . Address ( ) )
2018-07-11 10:43:25 -07:00
msg := stake . MsgEditValidator {
2018-07-10 17:36:50 -07:00
Description : description ,
ValidatorAddr : address ,
}
2018-07-10 19:36:12 -07:00
require . Nil ( t , msg . ValidateBasic ( ) , "expected msg to pass ValidateBasic: %s" , msg . GetSignBytes ( ) )
2018-07-10 19:55:57 -07:00
ctx , write := ctx . CacheContext ( )
2018-07-11 10:43:25 -07:00
result := stake . NewHandler ( k ) ( ctx , msg )
2018-07-10 19:55:57 -07:00
if result . IsOK ( ) {
write ( )
}
2018-07-10 19:36:12 -07:00
stats [ fmt . Sprintf ( "stake/editvalidator/%v" , result . IsOK ( ) ) ] += 1
2018-07-10 17:36:50 -07:00
action = fmt . Sprintf ( "TestMsgEditValidator: %s" , msg . GetSignBytes ( ) )
return action , nil
}
}
2018-07-10 11:46:28 -07:00
// SimulateMsgDelegate
2018-07-11 10:43:25 -07:00
func SimulateMsgDelegate ( m auth . AccountMapper , k stake . Keeper ) mock . TestAndRunMsg {
2018-07-10 11:46:28 -07:00
return func ( t * testing . T , r * rand . Rand , ctx sdk . Context , keys [ ] crypto . PrivKey , log string ) ( action string , err sdk . Error ) {
2018-07-10 19:36:12 -07:00
denom := k . GetParams ( ctx ) . BondDenom
validatorKey := keys [ r . Intn ( len ( keys ) ) ]
validatorAddress := sdk . AccAddress ( validatorKey . PubKey ( ) . Address ( ) )
delegatorKey := keys [ r . Intn ( len ( keys ) ) ]
delegatorAddress := sdk . AccAddress ( delegatorKey . PubKey ( ) . Address ( ) )
amount := m . GetAccount ( ctx , delegatorAddress ) . GetCoins ( ) . AmountOf ( denom )
if amount . GT ( sdk . ZeroInt ( ) ) {
amount = sdk . NewInt ( int64 ( r . Intn ( int ( amount . Int64 ( ) ) ) ) )
}
if amount . Equal ( sdk . ZeroInt ( ) ) {
return "nop" , nil
}
2018-07-11 10:43:25 -07:00
msg := stake . MsgDelegate {
2018-07-10 19:36:12 -07:00
DelegatorAddr : delegatorAddress ,
ValidatorAddr : validatorAddress ,
2018-07-11 10:17:09 -07:00
Delegation : sdk . NewIntCoin ( denom , amount ) ,
2018-07-10 19:36:12 -07:00
}
require . Nil ( t , msg . ValidateBasic ( ) , "expected msg to pass ValidateBasic: %s" , msg . GetSignBytes ( ) )
2018-07-10 19:55:57 -07:00
ctx , write := ctx . CacheContext ( )
2018-07-11 10:43:25 -07:00
result := stake . NewHandler ( k ) ( ctx , msg )
2018-07-10 19:55:57 -07:00
if result . IsOK ( ) {
write ( )
}
2018-07-10 19:36:12 -07:00
stats [ fmt . Sprintf ( "stake/delegate/%v" , result . IsOK ( ) ) ] += 1
action = fmt . Sprintf ( "TestMsgDelegate: %s" , msg . GetSignBytes ( ) )
return action , nil
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// SimulateMsgBeginUnbonding
2018-07-11 10:43:25 -07:00
func SimulateMsgBeginUnbonding ( m auth . AccountMapper , k stake . Keeper ) mock . TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func ( t * testing . T , r * rand . Rand , ctx sdk . Context , keys [ ] crypto . PrivKey , log string ) ( action string , err sdk . Error ) {
2018-07-11 10:51:04 -07:00
denom := k . GetParams ( ctx ) . BondDenom
validatorKey := keys [ r . Intn ( len ( keys ) ) ]
validatorAddress := sdk . AccAddress ( validatorKey . PubKey ( ) . Address ( ) )
delegatorKey := keys [ r . Intn ( len ( keys ) ) ]
delegatorAddress := sdk . AccAddress ( delegatorKey . PubKey ( ) . Address ( ) )
amount := m . GetAccount ( ctx , delegatorAddress ) . GetCoins ( ) . AmountOf ( denom )
if amount . GT ( sdk . ZeroInt ( ) ) {
amount = sdk . NewInt ( int64 ( r . Intn ( int ( amount . Int64 ( ) ) ) ) )
}
if amount . Equal ( sdk . ZeroInt ( ) ) {
return "nop" , nil
}
msg := stake . MsgBeginUnbonding {
DelegatorAddr : delegatorAddress ,
ValidatorAddr : validatorAddress ,
SharesAmount : sdk . NewRatFromInt ( amount ) ,
}
require . Nil ( t , msg . ValidateBasic ( ) , "expected msg to pass ValidateBasic: %s" , msg . GetSignBytes ( ) )
ctx , write := ctx . CacheContext ( )
result := stake . NewHandler ( k ) ( ctx , msg )
if result . IsOK ( ) {
write ( )
}
stats [ fmt . Sprintf ( "stake/beginunbonding/%v" , result . IsOK ( ) ) ] += 1
action = fmt . Sprintf ( "TestMsgBeginUnbonding: %s" , msg . GetSignBytes ( ) )
return action , nil
2018-07-10 17:36:50 -07:00
}
}
// SimulateMsgCompleteUnbonding
2018-07-11 10:43:25 -07:00
func SimulateMsgCompleteUnbonding ( k stake . Keeper ) mock . TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func ( t * testing . T , r * rand . Rand , ctx sdk . Context , keys [ ] crypto . PrivKey , log string ) ( action string , err sdk . Error ) {
2018-07-11 13:44:21 -07:00
validatorKey := keys [ r . Intn ( len ( keys ) ) ]
validatorAddress := sdk . AccAddress ( validatorKey . PubKey ( ) . Address ( ) )
delegatorKey := keys [ r . Intn ( len ( keys ) ) ]
delegatorAddress := sdk . AccAddress ( delegatorKey . PubKey ( ) . Address ( ) )
msg := stake . MsgCompleteUnbonding {
DelegatorAddr : delegatorAddress ,
ValidatorAddr : validatorAddress ,
}
require . Nil ( t , msg . ValidateBasic ( ) , "expected msg to pass ValidateBasic: %s" , msg . GetSignBytes ( ) )
ctx , write := ctx . CacheContext ( )
result := stake . NewHandler ( k ) ( ctx , msg )
if result . IsOK ( ) {
write ( )
}
stats [ fmt . Sprintf ( "stake/completeunbonding/%v" , result . IsOK ( ) ) ] += 1
action = fmt . Sprintf ( "TestMsgCompleteUnbonding with %s" , msg . GetSignBytes ( ) )
return action , nil
2018-07-10 17:36:50 -07:00
}
}
// SimulateMsgBeginRedelegate
2018-07-11 10:43:25 -07:00
func SimulateMsgBeginRedelegate ( m auth . AccountMapper , k stake . Keeper ) mock . TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func ( t * testing . T , r * rand . Rand , ctx sdk . Context , keys [ ] crypto . PrivKey , log string ) ( action string , err sdk . Error ) {
2018-07-10 19:36:12 -07:00
denom := k . GetParams ( ctx ) . BondDenom
sourceValidatorKey := keys [ r . Intn ( len ( keys ) ) ]
sourceValidatorAddress := sdk . AccAddress ( sourceValidatorKey . PubKey ( ) . Address ( ) )
destValidatorKey := keys [ r . Intn ( len ( keys ) ) ]
destValidatorAddress := sdk . AccAddress ( destValidatorKey . PubKey ( ) . Address ( ) )
delegatorKey := keys [ r . Intn ( len ( keys ) ) ]
delegatorAddress := sdk . AccAddress ( delegatorKey . PubKey ( ) . Address ( ) )
// TODO
amount := m . GetAccount ( ctx , delegatorAddress ) . GetCoins ( ) . AmountOf ( denom )
if amount . GT ( sdk . ZeroInt ( ) ) {
amount = sdk . NewInt ( int64 ( r . Intn ( int ( amount . Int64 ( ) ) ) ) )
}
if amount . Equal ( sdk . ZeroInt ( ) ) {
return "nop" , nil
}
2018-07-11 10:43:25 -07:00
msg := stake . MsgBeginRedelegate {
2018-07-10 19:36:12 -07:00
DelegatorAddr : delegatorAddress ,
ValidatorSrcAddr : sourceValidatorAddress ,
ValidatorDstAddr : destValidatorAddress ,
SharesAmount : sdk . NewRatFromInt ( amount ) ,
}
require . Nil ( t , msg . ValidateBasic ( ) , "expected msg to pass ValidateBasic: %s" , msg . GetSignBytes ( ) )
2018-07-10 19:55:57 -07:00
ctx , write := ctx . CacheContext ( )
2018-07-11 10:43:25 -07:00
result := stake . NewHandler ( k ) ( ctx , msg )
2018-07-10 19:55:57 -07:00
if result . IsOK ( ) {
write ( )
}
2018-07-10 19:36:12 -07:00
stats [ fmt . Sprintf ( "stake/beginredelegate/%v" , result . IsOK ( ) ) ] += 1
action = fmt . Sprintf ( "TestMsgBeginRedelegate: %s" , msg . GetSignBytes ( ) )
return action , nil
2018-07-10 17:36:50 -07:00
}
}
// SimulateMsgCompleteRedelegate
2018-07-11 10:43:25 -07:00
func SimulateMsgCompleteRedelegate ( k stake . Keeper ) mock . TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func ( t * testing . T , r * rand . Rand , ctx sdk . Context , keys [ ] crypto . PrivKey , log string ) ( action string , err sdk . Error ) {
2018-07-11 13:44:21 -07:00
validatorSrcKey := keys [ r . Intn ( len ( keys ) ) ]
validatorSrcAddress := sdk . AccAddress ( validatorSrcKey . PubKey ( ) . Address ( ) )
validatorDstKey := keys [ r . Intn ( len ( keys ) ) ]
validatorDstAddress := sdk . AccAddress ( validatorDstKey . PubKey ( ) . Address ( ) )
delegatorKey := keys [ r . Intn ( len ( keys ) ) ]
delegatorAddress := sdk . AccAddress ( delegatorKey . PubKey ( ) . Address ( ) )
msg := stake . MsgCompleteRedelegate {
DelegatorAddr : delegatorAddress ,
ValidatorSrcAddr : validatorSrcAddress ,
ValidatorDstAddr : validatorDstAddress ,
}
require . Nil ( t , msg . ValidateBasic ( ) , "expected msg to pass ValidateBasic: %s" , msg . GetSignBytes ( ) )
ctx , write := ctx . CacheContext ( )
result := stake . NewHandler ( k ) ( ctx , msg )
if result . IsOK ( ) {
write ( )
}
stats [ fmt . Sprintf ( "stake/completeredelegate/%v" , result . IsOK ( ) ) ] += 1
action = fmt . Sprintf ( "TestMsgCompleteRedelegate with %s" , msg . GetSignBytes ( ) )
return action , nil
2018-07-10 17:36:50 -07:00
}
}
2018-07-10 11:46:28 -07:00
// SimulationSetup
2018-07-11 10:43:25 -07:00
func SimulationSetup ( mapp * mock . App , k stake . Keeper ) mock . RandSetup {
2018-07-10 11:46:28 -07:00
return func ( r * rand . Rand , privKeys [ ] crypto . PrivKey ) {
ctx := mapp . NewContext ( false , abci . Header { } )
2018-07-11 10:43:25 -07:00
stake . InitGenesis ( ctx , k , stake . DefaultGenesisState ( ) )
2018-07-10 19:36:12 -07:00
params := k . GetParams ( ctx )
denom := params . BondDenom
loose := sdk . ZeroInt ( )
mapp . AccountMapper . IterateAccounts ( ctx , func ( acc auth . Account ) bool {
balance := sdk . NewInt ( int64 ( r . Intn ( 1000000 ) ) )
acc . SetCoins ( acc . GetCoins ( ) . Plus ( sdk . Coins { sdk . NewIntCoin ( denom , balance ) } ) )
mapp . AccountMapper . SetAccount ( ctx , acc )
loose = loose . Add ( balance )
return false
} )
pool := k . GetPool ( ctx )
2018-07-16 17:41:36 -07:00
pool . LooseTokens = pool . LooseTokens . Add ( sdk . NewRat ( loose . Int64 ( ) , 1 ) )
2018-07-10 19:36:12 -07:00
k . SetPool ( ctx , pool )
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// TestStakeWithRandomMessages
2018-07-10 11:46:28 -07:00
func TestStakeWithRandomMessages ( t * testing . T ) {
mapp := mock . NewApp ( )
bank . RegisterWire ( mapp . Cdc )
2018-07-10 17:36:50 -07:00
mapper := mapp . AccountMapper
coinKeeper := bank . NewKeeper ( mapper )
2018-07-10 11:46:28 -07:00
stakeKey := sdk . NewKVStoreKey ( "stake" )
2018-07-11 10:43:25 -07:00
stakeKeeper := stake . NewKeeper ( mapp . Cdc , stakeKey , coinKeeper , stake . DefaultCodespace )
mapp . Router ( ) . AddRoute ( "stake" , stake . NewHandler ( stakeKeeper ) )
2018-07-10 19:36:12 -07:00
mapp . SetEndBlocker ( func ( ctx sdk . Context , req abci . RequestEndBlock ) abci . ResponseEndBlock {
2018-07-11 10:43:25 -07:00
validatorUpdates := stake . EndBlocker ( ctx , stakeKeeper )
2018-07-10 19:36:12 -07:00
return abci . ResponseEndBlock {
ValidatorUpdates : validatorUpdates ,
}
} )
2018-07-10 11:46:28 -07:00
err := mapp . CompleteSetup ( [ ] * sdk . KVStoreKey { stakeKey } )
if err != nil {
panic ( err )
}
mapp . SimpleRandomizedTestingFromSeed (
t , 20 , [ ] mock . TestAndRunMsg {
2018-07-10 17:36:50 -07:00
SimulateMsgCreateValidator ( mapper , stakeKeeper ) ,
SimulateMsgEditValidator ( stakeKeeper ) ,
2018-07-10 19:36:12 -07:00
SimulateMsgDelegate ( mapper , stakeKeeper ) ,
2018-07-11 13:44:21 -07:00
// XXX TODO
// SimulateMsgBeginUnbonding(mapper, stakeKeeper),
2018-07-10 17:36:50 -07:00
SimulateMsgCompleteUnbonding ( stakeKeeper ) ,
2018-07-12 16:54:07 -07:00
SimulateMsgBeginRedelegate ( mapper , stakeKeeper ) ,
2018-07-10 17:36:50 -07:00
SimulateMsgCompleteRedelegate ( stakeKeeper ) ,
2018-07-10 11:46:28 -07:00
} , [ ] mock . RandSetup {
SimulationSetup ( mapp , stakeKeeper ) ,
} , [ ] mock . Invariant {
ModuleInvariants ( coinKeeper , stakeKeeper ) ,
} , 10 , 100 , 500 ,
)
2018-07-10 19:36:12 -07:00
fmt . Printf ( "Stats: %v\n" , stats )
2018-07-10 11:46:28 -07:00
}