Work-in-progress staking invariants
This commit is contained in:
parent
37640d6c00
commit
6a119f6934
@ -202,8 +202,7 @@ func RandomSetGenesis(r *rand.Rand, app *App, addrs []sdk.Address, denoms []stri
|
||||
(&baseAcc).SetCoins(coins)
|
||||
accts[i] = &baseAcc
|
||||
}
|
||||
|
||||
SetGenesis(app, accts)
|
||||
app.GenesisAccounts = accts
|
||||
}
|
||||
|
||||
// GetAllAccounts returns all accounts in the accountMapper.
|
||||
|
||||
@ -31,11 +31,13 @@ func (app *App) RandomizedTestingFromSeed(
|
||||
keys, addrs := GeneratePrivKeyAddressPairs(numKeys)
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
|
||||
RandomSetGenesis(r, app, addrs, []string{"foocoin"})
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
for i := 0; i < len(setups); i++ {
|
||||
setups[i](r, keys)
|
||||
}
|
||||
app.Commit()
|
||||
|
||||
RandomSetGenesis(r, app, addrs, []string{"foocoin"})
|
||||
header := abci.Header{Height: 0}
|
||||
|
||||
for i := 0; i < numBlocks; i++ {
|
||||
@ -62,6 +64,45 @@ func (app *App) RandomizedTestingFromSeed(
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) SimpleRandomizedTestingFromSeed(
|
||||
t *testing.T, seed int64, ops []TestAndRunMsg, setups []RandSetup,
|
||||
invariants []Invariant, numKeys int, numBlocks int, blockSize int,
|
||||
) {
|
||||
log := fmt.Sprintf("Starting SimpleSingleModuleTest with randomness created with seed %d", int(seed))
|
||||
keys, addrs := GeneratePrivKeyAddressPairs(numKeys)
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
|
||||
RandomSetGenesis(r, app, addrs, []string{"foocoin"})
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
for i := 0; i < len(setups); i++ {
|
||||
setups[i](r, keys)
|
||||
}
|
||||
app.Commit()
|
||||
|
||||
header := abci.Header{Height: 0}
|
||||
|
||||
for i := 0; i < numBlocks; i++ {
|
||||
app.BeginBlock(abci.RequestBeginBlock{})
|
||||
|
||||
app.assertAllInvariants(t, invariants, log)
|
||||
|
||||
ctx := app.NewContext(false, header)
|
||||
|
||||
// TODO: Add modes to simulate "no load", "medium load", and
|
||||
// "high load" blocks.
|
||||
for j := 0; j < blockSize; j++ {
|
||||
logUpdate, err := ops[r.Intn(len(ops))](t, r, ctx, keys, log)
|
||||
log += "\n" + logUpdate
|
||||
|
||||
require.Nil(t, err, log)
|
||||
app.assertAllInvariants(t, invariants, log)
|
||||
}
|
||||
|
||||
app.EndBlock(abci.RequestEndBlock{})
|
||||
header.Height++
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) assertAllInvariants(t *testing.T, tests []Invariant, log string) {
|
||||
for i := 0; i < len(tests); i++ {
|
||||
tests[i](t, app, log)
|
||||
|
||||
@ -17,6 +17,15 @@ type (
|
||||
privKeys []crypto.PrivKey, log string,
|
||||
) (action string, err sdk.Error)
|
||||
|
||||
// TestAndRunMsg produces a fuzzed message, calls the appropriate handler
|
||||
// (bypassing all ante handler checks), and ensures that the state
|
||||
// transition was as expected. It returns a descriptive message "action"
|
||||
// about what this fuzzed msg actually did for ease of debugging.
|
||||
TestAndRunMsg func(
|
||||
t *testing.T, r *rand.Rand, ctx sdk.Context,
|
||||
privKey []crypto.PrivKey, log string,
|
||||
) (action string, err sdk.Error)
|
||||
|
||||
// RandSetup performs the random setup the mock module needs.
|
||||
RandSetup func(r *rand.Rand, privKeys []crypto.PrivKey)
|
||||
|
||||
|
||||
99
x/stake/simulation_test.go
Normal file
99
x/stake/simulation_test.go
Normal file
@ -0,0 +1,99 @@
|
||||
package stake
|
||||
|
||||
import (
|
||||
// "errors"
|
||||
"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"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
// ModuleInvariants runs all invariants of the stake module.
|
||||
// Currently: total supply, positive power
|
||||
func ModuleInvariants(ck bank.Keeper, k Keeper) mock.Invariant {
|
||||
return func(t *testing.T, app *mock.App, log string) {
|
||||
TotalSupplyInvariant(ck, k)(t, app, log)
|
||||
PositivePowerInvariant(k)(t, app, log)
|
||||
}
|
||||
}
|
||||
|
||||
// TotalSupplyInvariant checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations
|
||||
func TotalSupplyInvariant(ck bank.Keeper, k Keeper) mock.Invariant {
|
||||
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
|
||||
})
|
||||
require.True(t, sdk.NewInt(pool.LooseTokens).Equal(loose), "expected loose tokens to equal total steak held by accounts")
|
||||
|
||||
// Bonded tokens should equal sum of tokens with bonded validators
|
||||
|
||||
// Unbonded tokens should equal sum of tokens with unbonded validators
|
||||
}
|
||||
}
|
||||
|
||||
// PositivePowerInvariant checks that all stored validators have > 0 power
|
||||
func PositivePowerInvariant(k Keeper) mock.Invariant {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SimulateMsgDelegate
|
||||
func SimulateMsgDelegate(k Keeper) mock.TestAndRunMsg {
|
||||
return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
|
||||
msg := fmt.Sprintf("TestMsgDelegate with %s", "ok")
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SimulationSetup
|
||||
func SimulationSetup(mapp *mock.App, k Keeper) mock.RandSetup {
|
||||
return func(r *rand.Rand, privKeys []crypto.PrivKey) {
|
||||
ctx := mapp.NewContext(false, abci.Header{})
|
||||
InitGenesis(ctx, k, DefaultGenesisState())
|
||||
}
|
||||
}
|
||||
|
||||
// Test random messages
|
||||
func TestStakeWithRandomMessages(t *testing.T) {
|
||||
mapp := mock.NewApp()
|
||||
|
||||
bank.RegisterWire(mapp.Cdc)
|
||||
coinKeeper := bank.NewKeeper(mapp.AccountMapper)
|
||||
stakeKey := sdk.NewKVStoreKey("stake")
|
||||
stakeKeeper := NewKeeper(mapp.Cdc, stakeKey, coinKeeper, DefaultCodespace)
|
||||
mapp.Router().AddRoute("stake", NewHandler(stakeKeeper))
|
||||
|
||||
err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mapp.SimpleRandomizedTestingFromSeed(
|
||||
t, 20, []mock.TestAndRunMsg{
|
||||
SimulateMsgDelegate(stakeKeeper),
|
||||
}, []mock.RandSetup{
|
||||
SimulationSetup(mapp, stakeKeeper),
|
||||
}, []mock.Invariant{
|
||||
ModuleInvariants(coinKeeper, stakeKeeper),
|
||||
}, 10, 100, 500,
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user