update simulation operations to use BaseApp (#4946)

* update operations to use baseapp

* updates and cleanup operations

* update operations

* restructure sim ops params

* rename sim /operations/msg.go to /operations.go

* move GenTx to a helper pkg to avoid circle deps

* rm msg.ValidateBasic

* changelog

* random fees; delete auth's DeductFees sim operation

* add chain-id for sig verification

* Update x/simulation/account.go

Co-Authored-By: colin axner <colinaxner@berkeley.edu>

* fix bank, gov and distr errors

* fix staking and slashing errors; increase prob for send enabled

* increase gas x10

* make format

* fix some distr and staking edge cases

* fix all edge cases

* golang ci

* rename acc vars; default no fees to 0stake

* cleanup; check for exchange rate and skip invalid ops

* fixes

* check for max entries

* add pubkey to genaccounts

* fix gov bug

* update staking sim ops

* fix small redelegation error

* fix small self delegation on unjail

* rm inf loop on random val/accs

* copy array

* add ok boolean to RandomValidator return values

* format

* Update x/bank/simulation/operations.go

Co-Authored-By: colin axner <colinaxner@berkeley.edu>

* Update simapp/helpers/test_helpers.go

Co-Authored-By: colin axner <colinaxner@berkeley.edu>

* address @colin-axner comments

* add genaccount pubkey validation

* fix test

* update operations and move RandomFees to x/simulation

* update gov ops

* address @alexanderbez comments

* avoid modifications to config

* reorder params

* changelog

* Update x/distribution/simulation/genesis.go

Co-Authored-By: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>

* remove named return values

* ensure all operations are simulated

* golangci

* add nolint

* disable whitespace and funlen linter

* disable godox

* add TODO on unjail

* update ops weights

* remove dup

* update godoc

* x/slashing/simulation/operations.go linting

* x/staking/simulation/operations.go linting

* update operations format

* x/bank/simulation/operations.go linting

* x/distribution/simulation/operations.go linting

* x/staking/simulation/operations.go linting

* start changes: make bank simulate send multiple coins, code cleanup

* fix nondeterminism bug

* fix txsiglimit err

* fix multisend bug

* simplify simulation, cleanup opt privkey args

* make slashing test invalid unjail msgs

* Update simapp/state.go

* golangCI changes
This commit is contained in:
Federico Kunze 2019-10-23 11:14:45 +02:00 committed by GitHub
parent 7581871b9b
commit 8344c0aef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1573 additions and 1035 deletions

View File

@ -167,6 +167,7 @@ generalized genesis accounts through the `GenesisAccount` interface.
`ParamChangeProposal`s
* (simulation) [\#4893](https://github.com/cosmos/cosmos-sdk/issues/4893) Change SimApp keepers to be public and add getter functions for keys and codec
* (simulation) [\#4906](https://github.com/cosmos/cosmos-sdk/issues/4906) Add simulation `Config` struct that wraps simulation flags
* (simulation) [\#4935](https://github.com/cosmos/cosmos-sdk/issues/4935) Update simulation to reflect a proper `ABCI` application without bypassing `BaseApp` semantics
* (store) [\#4792](https://github.com/cosmos/cosmos-sdk/issues/4792) panic on non-registered store
* (types) [\#4821](https://github.com/cosmos/cosmos-sdk/issues/4821) types/errors package added with support for stacktraces. It is meant as a more feature-rich replacement for sdk.Errors in the mid-term.
* (store) [\#1947](https://github.com/cosmos/cosmos-sdk/issues/1947) Implement inter-block (persistent)

View File

@ -15,6 +15,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
@ -229,7 +230,7 @@ func NewSimApp(
// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetAnteHandler(auth.NewAnteHandler(app.AccountKeeper, app.SupplyKeeper, auth.DefaultSigVerificationGasConsumer))
app.SetAnteHandler(ante.NewAnteHandler(app.AccountKeeper, app.SupplyKeeper, auth.DefaultSigVerificationGasConsumer))
app.SetEndBlocker(app.EndBlocker)
if loadLatest {

View File

@ -0,0 +1,45 @@
package helpers
import (
"math/rand"
"time"
"github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// SimAppChainID hardcoded chainID for simulation
const SimAppChainID = "simulation-app"
// GenTx generates a signed mock transaction.
func GenTx(msgs []sdk.Msg, feeAmt sdk.Coins, chainID string, accnums []uint64, seq []uint64, priv ...crypto.PrivKey) auth.StdTx {
fee := auth.StdFee{
Amount: feeAmt,
Gas: 1000000, // TODO: this should be a param
}
sigs := make([]auth.StdSignature, len(priv))
// create a random length memo
r := rand.New(rand.NewSource(time.Now().UnixNano()))
memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100))
for i, p := range priv {
// use a empty chainID for ease of testing
sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, memo))
if err != nil {
panic(err)
}
sigs[i] = auth.StdSignature{
PubKey: p.PubKey(),
Signature: sig,
}
}
return auth.NewStdTx(msgs, fee, sigs, memo)
}

View File

@ -2,22 +2,23 @@ package simapp
// Simulation parameter constants
const (
StakePerAccount = "stake_per_account"
InitiallyBondedValidators = "initially_bonded_validators"
OpWeightDeductFee = "op_weight_deduct_fee"
OpWeightMsgSend = "op_weight_msg_send"
OpWeightSingleInputMsgMultiSend = "op_weight_single_input_msg_multisend"
OpWeightMsgSetWithdrawAddress = "op_weight_msg_set_withdraw_address"
OpWeightMsgWithdrawDelegationReward = "op_weight_msg_withdraw_delegation_reward"
OpWeightMsgWithdrawValidatorCommission = "op_weight_msg_withdraw_validator_commission"
OpWeightSubmitVotingSlashingTextProposal = "op_weight_submit_voting_slashing_text_proposal"
OpWeightSubmitVotingSlashingCommunitySpendProposal = "op_weight_submit_voting_slashing_community_spend_proposal"
OpWeightSubmitVotingSlashingParamChangeProposal = "op_weight_submit_voting_slashing_param_change_proposal"
OpWeightMsgDeposit = "op_weight_msg_deposit"
OpWeightMsgCreateValidator = "op_weight_msg_create_validator"
OpWeightMsgEditValidator = "op_weight_msg_edit_validator"
OpWeightMsgDelegate = "op_weight_msg_delegate"
OpWeightMsgUndelegate = "op_weight_msg_undelegate"
OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate"
OpWeightMsgUnjail = "op_weight_msg_unjail"
StakePerAccount = "stake_per_account"
InitiallyBondedValidators = "initially_bonded_validators"
OpWeightDeductFee = "op_weight_deduct_fee"
OpWeightMsgSend = "op_weight_msg_send"
OpWeightMsgMultiSend = "op_weight_msg_multisend"
OpWeightMsgSetWithdrawAddress = "op_weight_msg_set_withdraw_address"
OpWeightMsgWithdrawDelegationReward = "op_weight_msg_withdraw_delegation_reward"
OpWeightMsgWithdrawValidatorCommission = "op_weight_msg_withdraw_validator_commission"
OpWeightSubmitTextProposal = "op_weight_submit_text_proposal"
OpWeightSubmitCommunitySpendProposal = "op_weight_submit_community_spend_proposal"
OpWeightSubmitParamChangeProposal = "op_weight_submit_param_change_proposal"
OpWeightMsgDeposit = "op_weight_msg_deposit"
OpWeightMsgVote = "op_weight_msg_vote"
OpWeightMsgCreateValidator = "op_weight_msg_create_validator"
OpWeightMsgEditValidator = "op_weight_msg_edit_validator"
OpWeightMsgDelegate = "op_weight_msg_delegate"
OpWeightMsgUndelegate = "op_weight_msg_undelegate"
OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate"
OpWeightMsgUnjail = "op_weight_msg_unjail"
)

View File

@ -10,6 +10,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
@ -19,6 +20,7 @@ import (
func BenchmarkFullAppSimulation(b *testing.B) {
logger := log.NewNopLogger()
config := NewConfigFromFlags()
config.ChainID = helpers.SimAppChainID
var db dbm.DB
dir, _ := ioutil.TempDir("", "goleveldb-app-sim")
@ -69,6 +71,7 @@ func BenchmarkInvariants(b *testing.B) {
config := NewConfigFromFlags()
config.AllInvariants = false
config.ChainID = helpers.SimAppChainID
dir, _ := ioutil.TempDir("", "goleveldb-app-invariant-bench")
db, _ := sdk.NewLevelDB("simulation", dir)

View File

@ -14,23 +14,23 @@ import (
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authsimops "github.com/cosmos/cosmos-sdk/x/auth/simulation/operations"
banksimops "github.com/cosmos/cosmos-sdk/x/bank/simulation/operations"
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
distrsimops "github.com/cosmos/cosmos-sdk/x/distribution/simulation/operations"
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
"github.com/cosmos/cosmos-sdk/x/gov"
govsimops "github.com/cosmos/cosmos-sdk/x/gov/simulation/operations"
govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation"
"github.com/cosmos/cosmos-sdk/x/mint"
"github.com/cosmos/cosmos-sdk/x/params"
paramsimops "github.com/cosmos/cosmos-sdk/x/params/simulation/operations"
paramsim "github.com/cosmos/cosmos-sdk/x/params/simulation"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingsimops "github.com/cosmos/cosmos-sdk/x/slashing/simulation/operations"
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations"
stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation"
"github.com/cosmos/cosmos-sdk/x/supply"
)
@ -39,7 +39,6 @@ func init() {
GetSimulatorFlags()
}
// TODO: add description
func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedOperation {
ap := make(simulation.AppParams)
@ -56,17 +55,6 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
// nolint: govet
return []simulation.WeightedOperation{
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightDeductFee, &v, nil,
func(_ *rand.Rand) {
v = 5
})
return v
}(nil),
authsimops.SimulateDeductFee(app.AccountKeeper, app.SupplyKeeper),
},
{
func(_ *rand.Rand) int {
var v int
@ -76,18 +64,18 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
banksimops.SimulateMsgSend(app.AccountKeeper, app.BankKeeper),
banksim.SimulateMsgSend(app.AccountKeeper, app.BankKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSingleInputMsgMultiSend, &v, nil,
ap.GetOrGenerate(app.cdc, OpWeightMsgMultiSend, &v, nil,
func(_ *rand.Rand) {
v = 10
v = 40
})
return v
}(nil),
banksimops.SimulateSingleInputMsgMultiSend(app.AccountKeeper, app.BankKeeper),
banksim.SimulateMsgMultiSend(app.AccountKeeper, app.BankKeeper),
},
{
func(_ *rand.Rand) int {
@ -98,7 +86,7 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
distrsimops.SimulateMsgSetWithdrawAddress(app.DistrKeeper),
distrsim.SimulateMsgSetWithdrawAddress(app.AccountKeeper, app.DistrKeeper),
},
{
func(_ *rand.Rand) int {
@ -109,7 +97,7 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
distrsimops.SimulateMsgWithdrawDelegatorReward(app.DistrKeeper),
distrsim.SimulateMsgWithdrawDelegatorReward(app.AccountKeeper, app.DistrKeeper, app.StakingKeeper),
},
{
func(_ *rand.Rand) int {
@ -120,40 +108,40 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
distrsimops.SimulateMsgWithdrawValidatorCommission(app.DistrKeeper),
distrsim.SimulateMsgWithdrawValidatorCommission(app.AccountKeeper, app.DistrKeeper, app.StakingKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingTextProposal, &v, nil,
ap.GetOrGenerate(app.cdc, OpWeightSubmitTextProposal, &v, nil,
func(_ *rand.Rand) {
v = 5
v = 20
})
return v
}(nil),
govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.GovKeeper, govsimops.SimulateTextProposalContent),
govsim.SimulateSubmitProposal(app.AccountKeeper, app.GovKeeper, govsim.SimulateTextProposalContent),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingCommunitySpendProposal, &v, nil,
ap.GetOrGenerate(app.cdc, OpWeightSubmitCommunitySpendProposal, &v, nil,
func(_ *rand.Rand) {
v = 5
v = 20
})
return v
}(nil),
govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.GovKeeper, distrsimops.SimulateCommunityPoolSpendProposalContent(app.DistrKeeper)),
govsim.SimulateSubmitProposal(app.AccountKeeper, app.GovKeeper, distrsim.SimulateCommunityPoolSpendProposalContent(app.DistrKeeper)),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingParamChangeProposal, &v, nil,
ap.GetOrGenerate(app.cdc, OpWeightSubmitParamChangeProposal, &v, nil,
func(_ *rand.Rand) {
v = 5
v = 20
})
return v
}(nil),
govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.GovKeeper, paramsimops.SimulateParamChangeProposalContent(paramChanges)),
govsim.SimulateSubmitProposal(app.AccountKeeper, app.GovKeeper, paramsim.SimulateParamChangeProposalContent(paramChanges)),
},
{
func(_ *rand.Rand) int {
@ -164,7 +152,18 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
govsimops.SimulateMsgDeposit(app.GovKeeper),
govsim.SimulateMsgDeposit(app.AccountKeeper, app.GovKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgVote, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
govsim.SimulateMsgVote(app.AccountKeeper, app.GovKeeper),
},
{
func(_ *rand.Rand) int {
@ -175,18 +174,18 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
stakingsimops.SimulateMsgCreateValidator(app.AccountKeeper, app.StakingKeeper),
stakingsim.SimulateMsgCreateValidator(app.AccountKeeper, app.StakingKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgEditValidator, &v, nil,
func(_ *rand.Rand) {
v = 5
v = 20
})
return v
}(nil),
stakingsimops.SimulateMsgEditValidator(app.StakingKeeper),
stakingsim.SimulateMsgEditValidator(app.AccountKeeper, app.StakingKeeper),
},
{
func(_ *rand.Rand) int {
@ -197,7 +196,7 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
stakingsimops.SimulateMsgDelegate(app.AccountKeeper, app.StakingKeeper),
stakingsim.SimulateMsgDelegate(app.AccountKeeper, app.StakingKeeper),
},
{
func(_ *rand.Rand) int {
@ -208,7 +207,7 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
stakingsimops.SimulateMsgUndelegate(app.AccountKeeper, app.StakingKeeper),
stakingsim.SimulateMsgUndelegate(app.AccountKeeper, app.StakingKeeper),
},
{
func(_ *rand.Rand) int {
@ -219,7 +218,7 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
stakingsimops.SimulateMsgBeginRedelegate(app.AccountKeeper, app.StakingKeeper),
stakingsim.SimulateMsgBeginRedelegate(app.AccountKeeper, app.StakingKeeper),
},
{
func(_ *rand.Rand) int {
@ -230,7 +229,7 @@ func testAndRunTxs(app *SimApp, config simulation.Config) []simulation.WeightedO
})
return v
}(nil),
slashingsimops.SimulateMsgUnjail(app.SlashingKeeper),
slashingsim.SimulateMsgUnjail(app.AccountKeeper, app.SlashingKeeper, app.StakingKeeper),
},
}
}
@ -254,6 +253,7 @@ func TestFullAppSimulation(t *testing.T) {
var logger log.Logger
config := NewConfigFromFlags()
config.ChainID = helpers.SimAppChainID
if FlagVerboseValue {
logger = log.TestingLogger()
@ -308,6 +308,7 @@ func TestAppImportExport(t *testing.T) {
var logger log.Logger
config := NewConfigFromFlags()
config.ChainID = helpers.SimAppChainID
if FlagVerboseValue {
logger = log.TestingLogger()
@ -414,7 +415,7 @@ func TestAppImportExport(t *testing.T) {
require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare")
fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), storeKeyA, storeKeyB)
require.Len(t, failedKVAs, 0, GetSimulationLog(storeKeyA.Name(), app.sm.StoreDecoders, app.cdc, failedKVAs, failedKVBs))
require.Equal(t, len(failedKVAs), 0, GetSimulationLog(storeKeyA.Name(), app.sm.StoreDecoders, app.cdc, failedKVAs, failedKVBs))
}
}
@ -426,6 +427,7 @@ func TestAppSimulationAfterImport(t *testing.T) {
var logger log.Logger
config := NewConfigFromFlags()
config.ChainID = helpers.SimAppChainID
if FlagVerboseValue {
logger = log.TestingLogger()
@ -520,6 +522,7 @@ func TestAppStateDeterminism(t *testing.T) {
config.ExportParamsPath = ""
config.OnOperation = false
config.AllInvariants = false
config.ChainID = helpers.SimAppChainID
numSeeds := 3
numTimesToRunPerSeed := 5
@ -529,7 +532,13 @@ func TestAppStateDeterminism(t *testing.T) {
config.Seed = rand.Int63()
for j := 0; j < numTimesToRunPerSeed; j++ {
logger := log.NewNopLogger()
var logger log.Logger
if FlagVerboseValue {
logger = log.TestingLogger()
} else {
logger = log.NewNopLogger()
}
db := dbm.NewMemDB()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())

View File

@ -35,6 +35,7 @@ func AppStateFn(cdc *codec.Codec, simManager *module.SimulationManager) simulati
panic("cannot provide both a genesis file and a params file")
case config.GenesisFile != "":
// override the default chain-id from simapp to set it later to the config
genesisDoc, accounts := AppStateFromGenesisFileFn(r, cdc, config.GenesisFile)
if FlagGenesisTimeValue == 0 {
@ -54,11 +55,11 @@ func AppStateFn(cdc *codec.Codec, simManager *module.SimulationManager) simulati
}
cdc.MustUnmarshalJSON(bz, &appParams)
appState, simAccs, chainID = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams)
appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams)
default:
appParams := make(simulation.AppParams)
appState, simAccs, chainID = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams)
appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams)
}
return appState, simAccs, chainID, genesisTimestamp
@ -70,7 +71,7 @@ func AppStateFn(cdc *codec.Codec, simManager *module.SimulationManager) simulati
func AppStateRandomizedFn(
simManager *module.SimulationManager, r *rand.Rand, cdc *codec.Codec,
accs []simulation.Account, genesisTimestamp time.Time, appParams simulation.AppParams,
) (json.RawMessage, []simulation.Account, string) {
) (json.RawMessage, []simulation.Account) {
numAccs := int64(len(accs))
genesisState := NewDefaultGenesisState()
@ -83,7 +84,7 @@ func AppStateRandomizedFn(
)
appParams.GetOrGenerate(
cdc, InitiallyBondedValidators, &numInitiallyBonded, r,
func(r *rand.Rand) { numInitiallyBonded = int64(r.Intn(250)) },
func(r *rand.Rand) { numInitiallyBonded = int64(r.Intn(300)) },
)
if numInitiallyBonded > numAccs {
@ -117,7 +118,7 @@ func AppStateRandomizedFn(
panic(err)
}
return appState, accs, "simulation"
return appState, accs
}
// AppStateFromGenesisFileFn util function to generate the genesis AppState

View File

@ -13,6 +13,7 @@ import (
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
@ -107,33 +108,6 @@ func CheckBalance(t *testing.T, app *SimApp, addr sdk.AccAddress, exp sdk.Coins)
require.Equal(t, exp, res.GetCoins())
}
// GenTx generates a signed mock transaction.
func GenTx(msgs []sdk.Msg, accnums []uint64, seq []uint64, priv ...crypto.PrivKey) auth.StdTx {
// Make the transaction free
fee := auth.StdFee{
Amount: sdk.NewCoins(sdk.NewInt64Coin("foocoin", 0)),
Gas: 100000,
}
sigs := make([]auth.StdSignature, len(priv))
memo := "testmemotestmemo"
for i, p := range priv {
// use a empty chainID for ease of testing
sig, err := p.Sign(auth.StdSignBytes("", accnums[i], seq[i], fee, msgs, memo))
if err != nil {
panic(err)
}
sigs[i] = auth.StdSignature{
PubKey: p.PubKey(),
Signature: sig,
}
}
return auth.NewStdTx(msgs, fee, sigs, memo)
}
// SignCheckDeliver checks a generated signed transaction and simulates a
// block commitment with the given transaction. A test assertion is made using
// the parameter 'expPass' against the result. A corresponding result is
@ -143,7 +117,14 @@ func SignCheckDeliver(
accNums, seq []uint64, expSimPass, expPass bool, priv ...crypto.PrivKey,
) sdk.Result {
tx := GenTx(msgs, accNums, seq, priv...)
tx := helpers.GenTx(
msgs,
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)},
"",
accNums,
seq,
priv...,
)
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
require.Nil(t, err)
@ -176,10 +157,17 @@ func SignCheckDeliver(
// GenSequenceOfTxs generates a set of signed transactions of messages, such
// that they differ only by having the sequence numbers incremented between
// every transaction.
func GenSequenceOfTxs(msgs []sdk.Msg, accnums []uint64, initSeqNums []uint64, numToGenerate int, priv ...crypto.PrivKey) []auth.StdTx {
func GenSequenceOfTxs(msgs []sdk.Msg, accNums []uint64, initSeqNums []uint64, numToGenerate int, priv ...crypto.PrivKey) []auth.StdTx {
txs := make([]auth.StdTx, numToGenerate)
for i := 0; i < numToGenerate; i++ {
txs[i] = GenTx(msgs, accnums, initSeqNums, priv...)
txs[i] = helpers.GenTx(
msgs,
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)},
"",
accNums,
initSeqNums,
priv...,
)
incrementAllSequenceNumbers(initSeqNums)
}

View File

@ -30,8 +30,11 @@ func GenMaxMemoChars(r *rand.Rand) uint64 {
}
// GenTxSigLimit randomized TxSigLimit
// make sure that sigLimit is always high
// so that arbitrarily simulated messages from other
// modules can still create valid transactions
func GenTxSigLimit(r *rand.Rand) uint64 {
return uint64(r.Intn(7) + 1)
return uint64(r.Intn(7) + 5)
}
// GenTxSizeCostPerByte randomized TxSizeCostPerByte

View File

@ -1,74 +0,0 @@
package operations
import (
"errors"
"fmt"
"math/big"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// SimulateDeductFee
func SimulateDeductFee(ak auth.AccountKeeper, supplyKeeper types.SupplyKeeper) simulation.Operation {
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
account := simulation.RandomAcc(r, accs)
stored := ak.GetAccount(ctx, account.Address)
initCoins := stored.GetCoins()
opMsg = simulation.NewOperationMsgBasic(types.ModuleName, "deduct_fee", "", false, nil)
feeCollector := ak.GetAccount(ctx, supplyKeeper.GetModuleAddress(types.FeeCollectorName))
if feeCollector == nil {
panic(fmt.Errorf("fee collector account hasn't been set"))
}
if len(initCoins) == 0 {
return opMsg, nil, nil
}
denomIndex := r.Intn(len(initCoins))
randCoin := initCoins[denomIndex]
amt, err := randPositiveInt(r, randCoin.Amount)
if err != nil {
return opMsg, nil, nil
}
// Create a random fee and verify the fees are within the account's spendable
// balance.
fees := sdk.NewCoins(sdk.NewCoin(randCoin.Denom, amt))
spendableCoins := stored.SpendableCoins(ctx.BlockHeader().Time)
if _, hasNeg := spendableCoins.SafeSub(fees); hasNeg {
return opMsg, nil, nil
}
// get the new account balance
_, hasNeg := initCoins.SafeSub(fees)
if hasNeg {
return opMsg, nil, nil
}
err = supplyKeeper.SendCoinsFromAccountToModule(ctx, stored.GetAddress(), types.FeeCollectorName, fees)
if err != nil {
panic(err)
}
opMsg.OK = true
return opMsg, nil, nil
}
}
func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
if !max.GT(sdk.OneInt()) {
return sdk.Int{}, errors.New("max too small")
}
max = max.Sub(sdk.OneInt())
return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil
}

View File

@ -18,7 +18,7 @@ const (
// GenSendEnabled randomized SendEnabled
func GenSendEnabled(r *rand.Rand) bool {
return r.Int63n(2) == 0
return r.Int63n(101) <= 95 // 95% chance of transfers being enabled
}
// RandomizedGenState generates a random GenesisState for bank

View File

@ -0,0 +1,252 @@
package simulation
import (
"errors"
"math/rand"
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/internal/keeper"
"github.com/cosmos/cosmos-sdk/x/bank/internal/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// SimulateMsgSend tests and runs a single msg send where both
// accounts already exist.
// nolint: funlen
func SimulateMsgSend(ak types.AccountKeeper, bk keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
if !bk.GetSendEnabled(ctx) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, toSimAcc, coins, skip, err := randomSendFields(r, ctx, accs, ak)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if skip {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
msg := types.NewMsgSend(simAccount.Address, toSimAcc.Address, coins)
err = sendMsgSend(r, app, ak, msg, ctx, chainID, []crypto.PrivKey{simAccount.PrivKey})
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// sendMsgSend sends a transaction with a MsgSend from a provided random account.
func sendMsgSend(
r *rand.Rand, app *baseapp.BaseApp, ak types.AccountKeeper,
msg types.MsgSend, ctx sdk.Context, chainID string, privkeys []crypto.PrivKey,
) (err error) {
account := ak.GetAccount(ctx, msg.FromAddress)
coins := account.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(msg.Amount)
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return err
}
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
privkeys...,
)
res := app.Deliver(tx)
if !res.IsOK() {
return errors.New(res.Log)
}
return nil
}
// SimulateMsgMultiSend tests and runs a single msg multisend, with randomized, capped number of inputs/outputs.
// all accounts in msg fields exist in state
// nolint: funlen
func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
if !bk.GetSendEnabled(ctx) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
// random number of inputs/outputs between [1, 3]
inputs := make([]types.Input, r.Intn(3)+1)
outputs := make([]types.Output, r.Intn(3)+1)
// collect signer privKeys
privs := make([]crypto.PrivKey, len(inputs))
// use map to check if address already exists as input
usedAddrs := make(map[string]bool)
var totalSentCoins sdk.Coins
for i := range inputs {
// generate random input fields, ignore to address
simAccount, _, coins, skip, err := randomSendFields(r, ctx, accs, ak)
// make sure account is fresh and not used in previous input
for usedAddrs[simAccount.Address.String()] {
simAccount, _, coins, skip, err = randomSendFields(r, ctx, accs, ak)
}
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if skip {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
// set input address in used address map
usedAddrs[simAccount.Address.String()] = true
// set signer privkey
privs[i] = simAccount.PrivKey
// set next input and accumulate total sent coins
inputs[i] = types.NewInput(simAccount.Address, coins)
totalSentCoins = totalSentCoins.Add(coins)
}
for o := range outputs {
outAddr, _ := simulation.RandomAcc(r, accs)
var outCoins sdk.Coins
// split total sent coins into random subsets for output
if o == len(outputs)-1 {
outCoins = totalSentCoins
} else {
// take random subset of remaining coins for output
// and update remaining coins
outCoins = simulation.RandSubsetCoins(r, totalSentCoins)
totalSentCoins = totalSentCoins.Sub(outCoins)
}
outputs[o] = types.NewOutput(outAddr.Address, outCoins)
}
// remove any output that has no coins
i := 0
for i < len(outputs) {
if outputs[i].Coins.Empty() {
outputs[i] = outputs[len(outputs)-1]
outputs = outputs[:len(outputs)-1]
} else {
// continue onto next coin
i++
}
}
msg := types.MsgMultiSend{
Inputs: inputs,
Outputs: outputs,
}
err := sendMsgMultiSend(r, app, ak, msg, ctx, chainID, privs)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// sendMsgMultiSend sends a transaction with a MsgMultiSend from a provided random
// account.
func sendMsgMultiSend(
r *rand.Rand, app *baseapp.BaseApp, ak types.AccountKeeper,
msg types.MsgMultiSend, ctx sdk.Context, chainID string, privkeys []crypto.PrivKey,
) (err error) {
accountNumbers := make([]uint64, len(msg.Inputs))
sequenceNumbers := make([]uint64, len(msg.Inputs))
for i := 0; i < len(msg.Inputs); i++ {
acc := ak.GetAccount(ctx, msg.Inputs[i].Address)
accountNumbers[i] = acc.GetAccountNumber()
sequenceNumbers[i] = acc.GetSequence()
}
// feePayer is the first signer, i.e. first input address
feePayer := ak.GetAccount(ctx, msg.Inputs[0].Address)
coins := feePayer.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(msg.Inputs[0].Coins)
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return err
}
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
accountNumbers,
sequenceNumbers,
privkeys...,
)
res := app.Deliver(tx)
if !res.IsOK() {
return errors.New(res.Log)
}
return nil
}
// randomSendFields returns the sender and recipient simulation accounts as well
// as the transferred amount.
func randomSendFields(
r *rand.Rand, ctx sdk.Context, accs []simulation.Account, ak types.AccountKeeper,
) (simulation.Account, simulation.Account, sdk.Coins, bool, error) {
simAccount, _ := simulation.RandomAcc(r, accs)
toSimAcc, _ := simulation.RandomAcc(r, accs)
// disallow sending money to yourself
for simAccount.PubKey.Equals(toSimAcc.PubKey) {
toSimAcc, _ = simulation.RandomAcc(r, accs)
}
acc := ak.GetAccount(ctx, simAccount.Address)
if acc == nil {
return simAccount, toSimAcc, nil, true, nil // skip error
}
coins := acc.SpendableCoins(ctx.BlockHeader().Time)
sendCoins := simulation.RandSubsetCoins(r, coins)
if sendCoins.Empty() {
return simAccount, toSimAcc, nil, true, nil // skip error
}
return simAccount, toSimAcc, sendCoins, false, nil
}

View File

@ -1,218 +0,0 @@
package operations
import (
"fmt"
"math/rand"
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/bank/internal/types"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// SimulateMsgSend tests and runs a single msg send where both
// accounts already exist.
func SimulateMsgSend(mapper types.AccountKeeper, bk bank.Keeper) simulation.Operation {
handler := bank.NewHandler(bk)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
fromAcc, comment, msg, ok := createMsgSend(r, ctx, accs, mapper)
opMsg = simulation.NewOperationMsg(msg, ok, comment)
if !ok {
return opMsg, nil, nil
}
err = sendAndVerifyMsgSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}, handler)
if err != nil {
return opMsg, nil, err
}
return opMsg, nil, nil
}
}
func createMsgSend(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper types.AccountKeeper) (
fromAcc simulation.Account, comment string, msg types.MsgSend, ok bool) {
fromAcc = simulation.RandomAcc(r, accs)
toAcc := simulation.RandomAcc(r, accs)
// Disallow sending money to yourself
for {
if !fromAcc.PubKey.Equals(toAcc.PubKey) {
break
}
toAcc = simulation.RandomAcc(r, accs)
}
initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).SpendableCoins(ctx.BlockHeader().Time)
if len(initFromCoins) == 0 {
return fromAcc, "skipping, no coins at all", msg, false
}
denomIndex := r.Intn(len(initFromCoins))
amt, goErr := simulation.RandPositiveInt(r, initFromCoins[denomIndex].Amount)
if goErr != nil {
return fromAcc, "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, msg, false
}
coins := sdk.Coins{sdk.NewCoin(initFromCoins[denomIndex].Denom, amt)}
msg = types.NewMsgSend(fromAcc.Address, toAcc.Address, coins)
return fromAcc, "", msg, true
}
// Sends and verifies the transition of a msg send.
func sendAndVerifyMsgSend(app *baseapp.BaseApp, mapper types.AccountKeeper, msg types.MsgSend, ctx sdk.Context, privkeys []crypto.PrivKey, handler sdk.Handler) error {
fromAcc := mapper.GetAccount(ctx, msg.FromAddress)
AccountNumbers := []uint64{fromAcc.GetAccountNumber()}
SequenceNumbers := []uint64{fromAcc.GetSequence()}
initialFromAddrCoins := fromAcc.GetCoins()
toAcc := mapper.GetAccount(ctx, msg.ToAddress)
initialToAddrCoins := toAcc.GetCoins()
if handler != nil {
res := handler(ctx, msg)
if !res.IsOK() {
if res.Code == types.CodeSendDisabled {
return nil
}
// TODO: Do this in a more 'canonical' way
return fmt.Errorf("handling msg failed %v", res)
}
} else {
tx := mock.GenTx([]sdk.Msg{msg},
AccountNumbers,
SequenceNumbers,
privkeys...)
res := app.Deliver(tx)
if !res.IsOK() {
// TODO: Do this in a more 'canonical' way
return fmt.Errorf("deliver failed %v", res)
}
}
fromAcc = mapper.GetAccount(ctx, msg.FromAddress)
toAcc = mapper.GetAccount(ctx, msg.ToAddress)
if !initialFromAddrCoins.Sub(msg.Amount).IsEqual(fromAcc.GetCoins()) {
return fmt.Errorf("fromAddress %s had an incorrect amount of coins", fromAcc.GetAddress())
}
if !initialToAddrCoins.Add(msg.Amount).IsEqual(toAcc.GetCoins()) {
return fmt.Errorf("toAddress %s had an incorrect amount of coins", toAcc.GetAddress())
}
return nil
}
// SimulateSingleInputMsgMultiSend tests and runs a single msg multisend, with one input and one output, where both
// accounts already exist.
func SimulateSingleInputMsgMultiSend(mapper types.AccountKeeper, bk bank.Keeper) simulation.Operation {
handler := bank.NewHandler(bk)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
fromAcc, comment, msg, ok := createSingleInputMsgMultiSend(r, ctx, accs, mapper)
opMsg = simulation.NewOperationMsg(msg, ok, comment)
if !ok {
return opMsg, nil, nil
}
err = sendAndVerifyMsgMultiSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}, handler)
if err != nil {
return opMsg, nil, err
}
return opMsg, nil, nil
}
}
func createSingleInputMsgMultiSend(r *rand.Rand, ctx sdk.Context, accs []simulation.Account, mapper types.AccountKeeper) (
fromAcc simulation.Account, comment string, msg types.MsgMultiSend, ok bool) {
fromAcc = simulation.RandomAcc(r, accs)
toAcc := simulation.RandomAcc(r, accs)
// Disallow sending money to yourself
for {
if !fromAcc.PubKey.Equals(toAcc.PubKey) {
break
}
toAcc = simulation.RandomAcc(r, accs)
}
toAddr := toAcc.Address
initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).SpendableCoins(ctx.BlockHeader().Time)
if len(initFromCoins) == 0 {
return fromAcc, "skipping, no coins at all", msg, false
}
denomIndex := r.Intn(len(initFromCoins))
amt, goErr := simulation.RandPositiveInt(r, initFromCoins[denomIndex].Amount)
if goErr != nil {
return fromAcc, "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, msg, false
}
coins := sdk.Coins{sdk.NewCoin(initFromCoins[denomIndex].Denom, amt)}
msg = types.MsgMultiSend{
Inputs: []types.Input{types.NewInput(fromAcc.Address, coins)},
Outputs: []types.Output{types.NewOutput(toAddr, coins)},
}
return fromAcc, "", msg, true
}
// Sends and verifies the transition of a msg multisend. This fails if there are repeated inputs or outputs
// pass in handler as nil to handle txs, otherwise handle msgs
func sendAndVerifyMsgMultiSend(app *baseapp.BaseApp, mapper types.AccountKeeper, msg types.MsgMultiSend,
ctx sdk.Context, privkeys []crypto.PrivKey, handler sdk.Handler) error {
initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs))
initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs))
AccountNumbers := make([]uint64, len(msg.Inputs))
SequenceNumbers := make([]uint64, len(msg.Inputs))
for i := 0; i < len(msg.Inputs); i++ {
acc := mapper.GetAccount(ctx, msg.Inputs[i].Address)
AccountNumbers[i] = acc.GetAccountNumber()
SequenceNumbers[i] = acc.GetSequence()
initialInputAddrCoins[i] = acc.GetCoins()
}
for i := 0; i < len(msg.Outputs); i++ {
acc := mapper.GetAccount(ctx, msg.Outputs[i].Address)
initialOutputAddrCoins[i] = acc.GetCoins()
}
if handler != nil {
res := handler(ctx, msg)
if !res.IsOK() {
if res.Code == types.CodeSendDisabled {
return nil
}
// TODO: Do this in a more 'canonical' way
return fmt.Errorf("handling msg failed %v", res)
}
} else {
tx := mock.GenTx([]sdk.Msg{msg},
AccountNumbers,
SequenceNumbers,
privkeys...)
res := app.Deliver(tx)
if !res.IsOK() {
// TODO: Do this in a more 'canonical' way
return fmt.Errorf("deliver failed %v", res)
}
}
for i := 0; i < len(msg.Inputs); i++ {
terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins()
if !initialInputAddrCoins[i].Sub(msg.Inputs[i].Coins).IsEqual(terminalInputCoins) {
return fmt.Errorf("input #%d had an incorrect amount of coins", i)
}
}
for i := 0; i < len(msg.Outputs); i++ {
terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins()
if !terminalOutputCoins.IsEqual(initialOutputAddrCoins[i].Add(msg.Outputs[i].Coins)) {
return fmt.Errorf("output #%d had an incorrect amount of coins", i)
}
}
return nil
}

View File

@ -7,7 +7,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
)
// set the proposer for determining distribution during endblock
// BeginBlocker sets the proposer for determining distribution during endblock
// and distribute rewards for the previous block
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
// determine the total power signing the block

View File

@ -18,6 +18,7 @@ const (
CommunityTax = "community_tax"
BaseProposerReward = "base_proposer_reward"
BonusProposerReward = "bonus_proposer_reward"
WithdrawEnabled = "withdraw_enabled"
)
// GenCommunityTax randomized CommunityTax
@ -35,6 +36,11 @@ func GenBonusProposerReward(r *rand.Rand) sdk.Dec {
return sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2))
}
// GenWithdrawEnabled returns a randomized WithdrawEnabled parameter.
func GenWithdrawEnabled(r *rand.Rand) bool {
return r.Int63n(101) <= 95 // 95% chance of withdraws being enabled
}
// RandomizedGenState generates a random GenesisState for distribution
func RandomizedGenState(simState *module.SimulationState) {
var communityTax sdk.Dec
@ -55,11 +61,18 @@ func RandomizedGenState(simState *module.SimulationState) {
func(r *rand.Rand) { bonusProposerReward = GenBonusProposerReward(r) },
)
var withdrawEnabled bool
simState.AppParams.GetOrGenerate(
simState.Cdc, WithdrawEnabled, &withdrawEnabled, simState.Rand,
func(r *rand.Rand) { withdrawEnabled = GenWithdrawEnabled(r) },
)
distrGenesis := types.GenesisState{
FeePool: types.InitialFeePool(),
CommunityTax: communityTax,
BaseProposerReward: baseProposerReward,
BonusProposerReward: bonusProposerReward,
WithdrawAddrEnabled: withdrawEnabled,
}
fmt.Printf("Selected randomly generated distribution parameters:\n%s\n", codec.MustMarshalJSONIndent(simState.Cdc, distrGenesis))

View File

@ -0,0 +1,176 @@
package simulation
import (
"errors"
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
)
// SimulateMsgSetWithdrawAddress generates a MsgSetWithdrawAddress with random values.
// nolint: funlen
func SimulateMsgSetWithdrawAddress(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
if !k.GetWithdrawAddrEnabled(ctx) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, _ := simulation.RandomAcc(r, accs)
simToAccount, _ := simulation.RandomAcc(r, accs)
account := ak.GetAccount(ctx, simAccount.Address)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgSetWithdrawAddress(simAccount.Address, simToAccount.Address)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgWithdrawDelegatorReward generates a MsgWithdrawDelegatorReward with random values.
// nolint: funlen
func SimulateMsgWithdrawDelegatorReward(ak types.AccountKeeper, k keeper.Keeper, sk stakingkeeper.Keeper) simulation.Operation {
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
simAccount, _ := simulation.RandomAcc(r, accs)
delegations := sk.GetAllDelegatorDelegations(ctx, simAccount.Address)
if len(delegations) == 0 {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
delegation := delegations[r.Intn(len(delegations))]
validator := sk.Validator(ctx, delegation.GetValidatorAddr())
if validator == nil {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("validator %s not found", delegation.GetValidatorAddr())
}
account := ak.GetAccount(ctx, simAccount.Address)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgWithdrawDelegatorReward(simAccount.Address, validator.GetOperator())
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgWithdrawValidatorCommission generates a MsgWithdrawValidatorCommission with random values.
// nolint: funlen
func SimulateMsgWithdrawValidatorCommission(ak types.AccountKeeper, k keeper.Keeper, sk stakingkeeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
validator, ok := stakingkeeper.RandomValidator(r, sk, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
commission := k.GetValidatorAccumulatedCommission(ctx, validator.GetOperator())
if commission.IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, found := simulation.FindAccount(accs, sdk.AccAddress(validator.GetOperator()))
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("validator %s not found", validator.GetOperator())
}
account := ak.GetAccount(ctx, simAccount.Address)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgWithdrawValidatorCommission(validator.GetOperator())
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateCommunityPoolSpendProposalContent generates random community-pool-spend proposal content
// nolint: funlen
func SimulateCommunityPoolSpendProposalContent(k keeper.Keeper) govsim.ContentSimulator {
return func(r *rand.Rand, ctx sdk.Context, accs []simulation.Account) govtypes.Content {
simAccount, _ := simulation.RandomAcc(r, accs)
balance := k.GetFeePool(ctx).CommunityPool
if balance.Empty() {
return nil
}
denomIndex := r.Intn(len(balance))
amount, err := simulation.RandPositiveInt(r, balance[denomIndex].Amount.TruncateInt())
if err != nil {
return nil
}
return types.NewCommunityPoolSpendProposal(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 100),
simAccount.Address,
sdk.NewCoins(sdk.NewCoin(balance[denomIndex].Denom, amount)),
)
}
}

View File

@ -1,114 +0,0 @@
package operations
import (
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution"
govsimops "github.com/cosmos/cosmos-sdk/x/gov/simulation/operations"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// SimulateMsgSetWithdrawAddress generates a MsgSetWithdrawAddress with random values.
func SimulateMsgSetWithdrawAddress(k distribution.Keeper) simulation.Operation {
handler := distribution.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
accountOrigin := simulation.RandomAcc(r, accs)
accountDestination := simulation.RandomAcc(r, accs)
msg := distribution.NewMsgSetWithdrawAddress(accountOrigin.Address, accountDestination.Address)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(distribution.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgWithdrawDelegatorReward generates a MsgWithdrawDelegatorReward with random values.
func SimulateMsgWithdrawDelegatorReward(k distribution.Keeper) simulation.Operation {
handler := distribution.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
delegatorAccount := simulation.RandomAcc(r, accs)
validatorAccount := simulation.RandomAcc(r, accs)
msg := distribution.NewMsgWithdrawDelegatorReward(delegatorAccount.Address, sdk.ValAddress(validatorAccount.Address))
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(distribution.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgWithdrawValidatorCommission generates a MsgWithdrawValidatorCommission with random values.
func SimulateMsgWithdrawValidatorCommission(k distribution.Keeper) simulation.Operation {
handler := distribution.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
account := simulation.RandomAcc(r, accs)
msg := distribution.NewMsgWithdrawValidatorCommission(sdk.ValAddress(account.Address))
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(distribution.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateCommunityPoolSpendProposalContent generates random community-pool-spend proposal content
func SimulateCommunityPoolSpendProposalContent(k distribution.Keeper) govsimops.ContentSimulator {
return func(r *rand.Rand, ctx sdk.Context, accs []simulation.Account) govtypes.Content {
var coins sdk.Coins
recipientAcc := simulation.RandomAcc(r, accs)
balance := k.GetFeePool(ctx).CommunityPool
if len(balance) > 0 {
denomIndex := r.Intn(len(balance))
amount, err := simulation.RandPositiveInt(r, balance[denomIndex].Amount.TruncateInt())
if err == nil {
denom := balance[denomIndex].Denom
coins = sdk.NewCoins(sdk.NewCoin(denom, amount.Mul(sdk.NewInt(2))))
}
}
return distribution.NewCommunityPoolSpendProposal(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 100),
recipientAcc.Address,
coins,
)
}
}

View File

@ -2,11 +2,17 @@ package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
// AccountKeeper defines the expected account keeper used for simulations (noalias)
type AccountKeeper interface {
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
}
// StakingKeeper expected staking keeper (noalias)
type StakingKeeper interface {
// iterate through validators by operator address, execute func for each validator

View File

@ -0,0 +1,324 @@
package simulation
import (
"errors"
"math"
"math/rand"
"time"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov/keeper"
"github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
var initialProposalID = uint64(100000000000000)
// ContentSimulator defines a function type alias for generating random proposal
// content.
type ContentSimulator func(r *rand.Rand, ctx sdk.Context, accs []simulation.Account) types.Content
// SimulateSubmitProposal simulates creating a msg Submit Proposal
// voting on the proposal, and subsequently slashing the proposal. It is implemented using
// future operations.
// nolint: funlen
func SimulateSubmitProposal(ak types.AccountKeeper, k keeper.Keeper,
contentSim ContentSimulator) simulation.Operation {
// The states are:
// column 1: All validators vote
// column 2: 90% vote
// column 3: 75% vote
// column 4: 40% vote
// column 5: 15% vote
// column 6: noone votes
// All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily,
// feel free to change.
numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{
{20, 10, 0, 0, 0, 0},
{55, 50, 20, 10, 0, 0},
{25, 25, 30, 25, 30, 15},
{0, 15, 30, 25, 30, 30},
{0, 0, 20, 30, 30, 30},
{0, 0, 0, 10, 10, 25},
})
statePercentageArray := []float64{1, .9, .75, .4, .15, 0}
curNumVotesState := 1
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// 1) submit proposal now
content := contentSim(r, ctx, accs)
if content == nil {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, _ := simulation.RandomAcc(r, accs)
deposit, skip, err := randomDeposit(r, ctx, ak, k, simAccount.Address)
switch {
case skip:
return simulation.NoOpMsg(types.ModuleName), nil, nil
case err != nil:
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgSubmitProposal(content, deposit, simAccount.Address)
account := ak.GetAccount(ctx, simAccount.Address)
coins := account.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(deposit)
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
opMsg := simulation.NewOperationMsg(msg, true, "")
// get the submitted proposal ID
proposalID, err := k.GetProposalID(ctx)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
// 2) Schedule operations for votes
// 2.1) first pick a number of people to vote.
curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState)
numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState]))
// 2.2) select who votes and when
whoVotes := r.Perm(len(accs))
// didntVote := whoVotes[numVotes:]
whoVotes = whoVotes[:numVotes]
votingPeriod := k.GetVotingParams(ctx).VotingPeriod
fops := make([]simulation.FutureOperation, numVotes+1)
for i := 0; i < numVotes; i++ {
whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second)
fops[i] = simulation.FutureOperation{
BlockTime: whenVote,
Op: operationSimulateMsgVote(ak, k, accs[whoVotes[i]], int64(proposalID)),
}
}
return opMsg, fops, nil
}
}
// SimulateTextProposalContent returns random text proposal content.
func SimulateTextProposalContent(r *rand.Rand, _ sdk.Context, _ []simulation.Account) types.Content {
return types.NewTextProposal(
simulation.RandStringOfLength(r, 140),
simulation.RandStringOfLength(r, 5000),
)
}
// SimulateMsgDeposit generates a MsgDeposit with random values.
// nolint: funlen
func SimulateMsgDeposit(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
simAccount, _ := simulation.RandomAcc(r, accs)
proposalID, ok := randomProposalID(r, k, ctx, types.StatusDepositPeriod)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
deposit, skip, err := randomDeposit(r, ctx, ak, k, simAccount.Address)
switch {
case skip:
return simulation.NoOpMsg(types.ModuleName), nil, nil
case err != nil:
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgDeposit(simAccount.Address, proposalID, deposit)
account := ak.GetAccount(ctx, simAccount.Address)
coins := account.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(deposit)
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgVote generates a MsgVote with random values.
// nolint: funlen
func SimulateMsgVote(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return operationSimulateMsgVote(ak, k, simulation.Account{}, -1)
}
func operationSimulateMsgVote(ak types.AccountKeeper, k keeper.Keeper,
simAccount simulation.Account, proposalIDInt int64) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
if simAccount.Equals(simulation.Account{}) {
simAccount, _ = simulation.RandomAcc(r, accs)
}
var proposalID uint64
switch {
case proposalIDInt < 0:
var ok bool
proposalID, ok = randomProposalID(r, k, ctx, types.StatusVotingPeriod)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
default:
proposalID = uint64(proposalIDInt)
}
option := randomVotingOption(r)
msg := types.NewMsgVote(simAccount.Address, proposalID, option)
account := ak.GetAccount(ctx, simAccount.Address)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// Pick a random deposit with a random denomination with a
// deposit amount between (0, min(balance, minDepositAmount))
// This is to simulate multiple users depositing to get the
// proposal above the minimum deposit amount
func randomDeposit(r *rand.Rand, ctx sdk.Context,
ak types.AccountKeeper, k keeper.Keeper, addr sdk.AccAddress,
) (deposit sdk.Coins, skip bool, err error) {
account := ak.GetAccount(ctx, addr)
coins := account.SpendableCoins(ctx.BlockHeader().Time)
if coins.Empty() {
return nil, true, nil // skip
}
minDeposit := k.GetDepositParams(ctx).MinDeposit
denomIndex := r.Intn(len(minDeposit))
denom := minDeposit[denomIndex].Denom
depositCoins := coins.AmountOf(denom)
if depositCoins.IsZero() {
return nil, true, nil
}
maxAmt := depositCoins
if maxAmt.GT(minDeposit[denomIndex].Amount) {
maxAmt = minDeposit[denomIndex].Amount
}
amount, err := simulation.RandPositiveInt(r, maxAmt)
if err != nil {
return nil, false, err
}
return sdk.Coins{sdk.NewCoin(denom, amount)}, false, nil
}
// Pick a random proposal ID between the initial proposal ID
// (defined in gov GenesisState) and the latest proposal ID
// that matches a given Status.
// It does not provide a default ID.
func randomProposalID(r *rand.Rand, k keeper.Keeper,
ctx sdk.Context, status types.ProposalStatus) (proposalID uint64, found bool) {
proposalID, _ = k.GetProposalID(ctx)
switch {
case proposalID > initialProposalID:
// select a random ID between [initialProposalID, proposalID]
proposalID = uint64(simulation.RandIntBetween(r, int(initialProposalID), int(proposalID)))
default:
// This is called on the first call to this funcion
// in order to update the global variable
initialProposalID = proposalID
}
proposal, ok := k.GetProposal(ctx, proposalID)
if !ok || proposal.Status != status {
return proposalID, false
}
return proposalID, true
}
// Pick a random voting option
func randomVotingOption(r *rand.Rand) types.VoteOption {
switch r.Intn(4) {
case 0:
return types.OptionYes
case 1:
return types.OptionAbstain
case 2:
return types.OptionNo
case 3:
return types.OptionNoWithVeto
default:
panic("invalid vote option")
}
}

View File

@ -1,222 +0,0 @@
package operations
import (
"fmt"
"math"
"math/rand"
"time"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// ContentSimulator defines a function type alias for generating random proposal
// content.
type ContentSimulator func(r *rand.Rand, ctx sdk.Context, accs []simulation.Account) gov.Content
// SimulateSubmittingVotingAndSlashingForProposal simulates creating a msg Submit Proposal
// voting on the proposal, and subsequently slashing the proposal. It is implemented using
// future operations.
// TODO: Vote more intelligently, so we can actually do some checks regarding votes passing or failing
// TODO: Actually check that validator slashings happened
func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, contentSim ContentSimulator) simulation.Operation {
handler := gov.NewHandler(k)
// The states are:
// column 1: All validators vote
// column 2: 90% vote
// column 3: 75% vote
// column 4: 40% vote
// column 5: 15% vote
// column 6: noone votes
// All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily,
// feel free to change.
numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{
{20, 10, 0, 0, 0, 0},
{55, 50, 20, 10, 0, 0},
{25, 25, 30, 25, 30, 15},
{0, 15, 30, 25, 30, 30},
{0, 0, 20, 30, 30, 30},
{0, 0, 0, 10, 10, 25},
})
statePercentageArray := []float64{1, .9, .75, .4, .15, 0}
curNumVotesState := 1
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account,
) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
// 1) submit proposal now
sender := simulation.RandomAcc(r, accs)
content := contentSim(r, ctx, accs)
msg, err := simulationCreateMsgSubmitProposal(r, content, sender)
if err != nil {
return simulation.NoOpMsg(gov.ModuleName), nil, err
}
ok := simulateHandleMsgSubmitProposal(msg, handler, ctx)
opMsg = simulation.NewOperationMsg(msg, ok, content.ProposalType())
// don't schedule votes if proposal failed
if !ok {
return opMsg, nil, nil
}
proposalID, err := k.GetProposalID(ctx)
if err != nil {
return simulation.NoOpMsg(gov.ModuleName), nil, err
}
proposalID = uint64(math.Max(float64(proposalID)-1, 0))
// 2) Schedule operations for votes
// 2.1) first pick a number of people to vote.
curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState)
numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState]))
// 2.2) select who votes and when
whoVotes := r.Perm(len(accs))
// didntVote := whoVotes[numVotes:]
whoVotes = whoVotes[:numVotes]
votingPeriod := k.GetVotingParams(ctx).VotingPeriod
fops := make([]simulation.FutureOperation, numVotes+1)
for i := 0; i < numVotes; i++ {
whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second)
fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, accs[whoVotes[i]], proposalID)}
}
// 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant)
// TODO: Find a way to check if a validator was slashed other than just checking their balance a block
// before and after.
return opMsg, fops, nil
}
}
func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, handler sdk.Handler, ctx sdk.Context) (ok bool) {
ctx, write := ctx.CacheContext()
ok = handler(ctx, msg).IsOK()
if ok {
write()
}
return ok
}
// SimulateTextProposalContent returns random text proposal content.
func SimulateTextProposalContent(r *rand.Rand, _ sdk.Context, _ []simulation.Account) gov.Content {
return gov.NewTextProposal(
simulation.RandStringOfLength(r, 140),
simulation.RandStringOfLength(r, 5000),
)
}
func simulationCreateMsgSubmitProposal(r *rand.Rand, c gov.Content, s simulation.Account) (msg gov.MsgSubmitProposal, err error) {
msg = gov.NewMsgSubmitProposal(c, randomDeposit(r), s.Address)
if msg.ValidateBasic() != nil {
err = fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
return
}
// SimulateMsgDeposit generates a MsgDeposit with random values.
func SimulateMsgDeposit(k gov.Keeper) simulation.Operation {
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
acc := simulation.RandomAcc(r, accs)
proposalID, ok := randomProposalID(r, k, ctx)
if !ok {
return simulation.NoOpMsg(gov.ModuleName), nil, nil
}
deposit := randomDeposit(r)
msg := gov.NewMsgDeposit(acc.Address, proposalID, deposit)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(gov.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok = gov.NewHandler(k)(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgVote generates a MsgVote with random values.
func SimulateMsgVote(k gov.Keeper) simulation.Operation {
return operationSimulateMsgVote(k, simulation.Account{}, 0)
}
// nolint: unparam
func operationSimulateMsgVote(k gov.Keeper, acc simulation.Account, proposalID uint64) simulation.Operation {
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
if acc.Equals(simulation.Account{}) {
acc = simulation.RandomAcc(r, accs)
}
if proposalID < uint64(0) {
var ok bool
proposalID, ok = randomProposalID(r, k, ctx)
if !ok {
return simulation.NoOpMsg(gov.ModuleName), nil, nil
}
}
option := randomVotingOption(r)
msg := gov.NewMsgVote(acc.Address, proposalID, option)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(gov.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := gov.NewHandler(k)(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// Pick a random deposit
func randomDeposit(r *rand.Rand) sdk.Coins {
// TODO Choose based on account balance and min deposit
amount := int64(r.Intn(20)) + 1
return sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, amount)}
}
// Pick a random proposal ID
func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID uint64, ok bool) {
lastProposalID, _ := k.GetProposalID(ctx)
lastProposalID = uint64(math.Max(float64(lastProposalID)-1, 0))
if lastProposalID < 1 || lastProposalID == (2<<63-1) {
return 0, false
}
proposalID = uint64(r.Intn(1+int(lastProposalID)) - 1)
return proposalID, true
}
// Pick a random voting option
func randomVotingOption(r *rand.Rand) gov.VoteOption {
switch r.Intn(4) {
case 0:
return gov.OptionYes
case 1:
return gov.OptionAbstain
case 2:
return gov.OptionNo
case 3:
return gov.OptionNoWithVeto
}
panic("should not happen")
}

View File

@ -2,6 +2,7 @@ package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
@ -36,3 +37,8 @@ type StakingKeeper interface {
IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress,
fn func(index int64, delegation stakingexported.DelegationI) (stop bool))
}
// AccountKeeper defines the expected account keeper (noalias)
type AccountKeeper interface {
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
}

View File

@ -1,10 +1,10 @@
package operations
package simulation
import (
"math/rand"
sdk "github.com/cosmos/cosmos-sdk/types"
govsimops "github.com/cosmos/cosmos-sdk/x/gov/simulation/operations"
govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/simulation"
@ -13,7 +13,7 @@ import (
// SimulateParamChangeProposalContent returns random parameter change content.
// It will generate a ParameterChangeProposal object with anywhere between 1 and
// the total amount of defined parameters changes, all of which have random valid values.
func SimulateParamChangeProposalContent(paramChangePool []simulation.ParamChange) govsimops.ContentSimulator {
func SimulateParamChangeProposalContent(paramChangePool []simulation.ParamChange) govsim.ContentSimulator {
return func(r *rand.Rand, _ sdk.Context, _ []simulation.Account) govtypes.Content {
lenParamChange := len(paramChangePool)

View File

@ -18,16 +18,16 @@ type Account struct {
Address sdk.AccAddress
}
// are two accounts equal
// Equals returns true if two accounts are equal
func (acc Account) Equals(acc2 Account) bool {
return acc.Address.Equals(acc2.Address)
}
// RandomAcc pick a random account from an array
func RandomAcc(r *rand.Rand, accs []Account) Account {
return accs[r.Intn(
len(accs),
)]
// RandomAcc picks and returns a random account from an array and returs its
// position in the array.
func RandomAcc(r *rand.Rand, accs []Account) (Account, int) {
idx := r.Intn(len(accs))
return accs[idx], idx
}
// RandomAccounts generates n random accounts
@ -45,3 +45,41 @@ func RandomAccounts(r *rand.Rand, n int) []Account {
return accs
}
// FindAccount iterates over all the simulation accounts to find the one that matches
// the given address
func FindAccount(accs []Account, address sdk.Address) (Account, bool) {
for _, acc := range accs {
if acc.Address.Equals(address) {
return acc, true
}
}
return Account{}, false
}
// RandomFees returns a random fee by selecting a random coin denomination and
// amount from the account's available balance. If the user doesn't have enough
// funds for paying fees, it returns empty coins.
func RandomFees(r *rand.Rand, ctx sdk.Context, spendableCoins sdk.Coins) (sdk.Coins, error) {
if spendableCoins.Empty() {
return nil, nil
}
denomIndex := r.Intn(len(spendableCoins))
randCoin := spendableCoins[denomIndex]
if randCoin.Amount.IsZero() {
return nil, nil
}
amt, err := RandPositiveInt(r, randCoin.Amount)
if err != nil {
return nil, err
}
// Create a random fee and verify the fees are within the account's spendable
// balance.
fees := sdk.NewCoins(sdk.NewCoin(randCoin.Denom, amt))
return fees, nil
}

View File

@ -10,10 +10,11 @@ type Config struct {
ExportStatePath string //custom file path to save the exported app state JSON
ExportStatsPath string // custom file path to save the exported simulation statistics JSON
Seed int64 // simulation random seed
InitialBlockHeight int // initial block to start the simulation
NumBlocks int // number of new blocks to simulate from the initial block height
BlockSize int // operations per block
Seed int64 // simulation random seed
InitialBlockHeight int // initial block to start the simulation
NumBlocks int // number of new blocks to simulate from the initial block height
BlockSize int // operations per block
ChainID string // chain-id used on the simulation
Lean bool // lean simulation log output
Commit bool // have the simulation commit

View File

@ -11,7 +11,7 @@ import (
)
// Operation runs a state machine transition, and ensures the transition
// happened as exported. The operation could be running and testing a fuzzed
// happened as expected. The operation could be running and testing a fuzzed
// transaction, or doing the same for a message.
//
// For ease of debugging, an operation returns a descriptive message "action",
@ -20,7 +20,7 @@ import (
// Operations can optionally provide a list of "FutureOperations" to run later
// These will be ran at the beginning of the corresponding block.
type Operation func(r *rand.Rand, app *baseapp.BaseApp,
ctx sdk.Context, accounts []Account) (
ctx sdk.Context, accounts []Account, chainID string) (
OperationMsg OperationMsg, futureOps []FutureOperation, err error)
// entry kinds for use within OperationEntry
@ -82,11 +82,11 @@ func (oe OperationEntry) MustMarshal() json.RawMessage {
// OperationMsg - structure for operation output
type OperationMsg struct {
Route string `json:"route" yaml:"route"`
Name string `json:"name" yaml:"name"`
Comment string `json:"comment" yaml:"comment"`
OK bool `json:"ok" yaml:"ok"`
Msg json.RawMessage `json:"msg" yaml:"msg"`
Route string `json:"route" yaml:"route"` // msg route (i.e module name)
Name string `json:"name" yaml:"name"` // operation name (msg Type or "no-operation")
Comment string `json:"comment" yaml:"comment"` // additional comment
OK bool `json:"ok" yaml:"ok"` // success
Msg json.RawMessage `json:"msg" yaml:"msg"` // JSON encoded msg
}
// NewOperationMsgBasic creates a new operation message from raw input.

View File

@ -69,7 +69,7 @@ type Params struct {
func RandomParams(r *rand.Rand) Params {
return Params{
PastEvidenceFraction: r.Float64(),
NumKeys: RandIntBetween(r, 2, 250),
NumKeys: RandIntBetween(r, 2, 2500), // number of accounts created for the simulation
EvidenceFraction: r.Float64(),
InitialLivenessWeightings: []int{RandIntBetween(r, 1, 80), r.Intn(10), r.Intn(10)},
LivenessTransitionMatrix: defaultLivenessTransitionMatrix,

View File

@ -39,7 +39,7 @@ func RandStringOfLength(r *rand.Rand, n int) string {
// RandPositiveInt get a rand positive sdk.Int
func RandPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
if !max.GT(sdk.OneInt()) {
if !max.GTE(sdk.OneInt()) {
return sdk.Int{}, errors.New("max too small")
}
max = max.Sub(sdk.OneInt())
@ -88,6 +88,43 @@ func RandIntBetween(r *rand.Rand, min, max int) int {
return r.Intn(max-min) + min
}
// returns random subset of the provided coins
// will return at least one coin unless coins argument is empty or malformed
// i.e. 0 amt in coins
func RandSubsetCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins {
if len(coins) == 0 {
return sdk.Coins{}
}
// make sure at least one coin added
denomIdx := r.Intn(len(coins))
coin := coins[denomIdx]
amt, err := RandPositiveInt(r, coin.Amount)
// malformed coin. 0 amt in coins
if err != nil {
return sdk.Coins{}
}
subset := sdk.Coins{sdk.NewCoin(coin.Denom, amt)}
for i, c := range coins {
// skip denom that we already chose earlier
if i == denomIdx {
continue
}
// coin flip if multiple coins
// if there is single coin then return random amount of it
if r.Intn(2) == 0 && len(coins) != 1 {
continue
}
amt, err := RandPositiveInt(r, c.Amount)
// ignore errors and try another denom
if err != nil {
continue
}
subset = append(subset, sdk.NewCoin(c.Denom, amt))
}
return subset
}
// DeriveRand derives a new Rand deterministically from another random source.
// Unlike rand.New(rand.NewSource(seed)), the result is "more random"
// depending on the source and state of r.

View File

@ -17,7 +17,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// AppStateFn returns the app state json bytes, the genesis accounts, and the chain identifier
// AppStateFn returns the app state json bytes and the genesis accounts
type AppStateFn func(r *rand.Rand, accs []Account, config Config) (
appState json.RawMessage, accounts []Account, chainId string, genesisTimestamp time.Time,
)
@ -26,7 +26,7 @@ type AppStateFn func(r *rand.Rand, accs []Account, config Config) (
func initChain(
r *rand.Rand, params Params, accounts []Account, app *baseapp.BaseApp,
appStateFn AppStateFn, config Config,
) (mockValidators, time.Time, []Account) {
) (mockValidators, time.Time, []Account, string) {
appState, accounts, chainID, genesisTimestamp := appStateFn(r, accounts, config)
@ -37,7 +37,7 @@ func initChain(
res := app.InitChain(req)
validators := newMockValidators(r, res.Validators, params)
return validators, genesisTimestamp, accounts
return validators, genesisTimestamp, accounts, chainID
}
// SimulateFromSeed tests an application by running the provided
@ -51,7 +51,7 @@ func SimulateFromSeed(
// in case we have to end early, don't os.Exit so that we can run cleanup code.
testingMode, t, b := getTestingMode(tb)
fmt.Fprintf(w, "Starting SimulateFromSeed with randomness created with config.Seed %d\n", int(config.Seed))
fmt.Fprintf(w, "Starting SimulateFromSeed with randomness created with seed %d\n", int(config.Seed))
r := rand.New(rand.NewSource(config.Seed))
params := RandomParams(r)
@ -63,11 +63,13 @@ func SimulateFromSeed(
// Second variable to keep pending validator set (delayed one block since
// TM 0.24) Initially this is the same as the initial validator set
validators, genesisTimestamp, accs := initChain(r, params, accs, app, appStateFn, config)
validators, genesisTimestamp, accs, chainID := initChain(r, params, accs, app, appStateFn, config)
if len(accs) == 0 {
return true, params, fmt.Errorf("must have greater than zero genesis accounts")
}
config.ChainID = chainID
fmt.Printf(
"Starting the simulation from time %v (unixtime %v)\n",
genesisTimestamp.UTC().Format(time.UnixDate), genesisTimestamp.Unix(),
@ -86,6 +88,7 @@ func SimulateFromSeed(
nextValidators := validators
header := abci.Header{
ChainID: config.ChainID,
Height: 1,
Time: genesisTimestamp,
ProposerAddress: validators.randomProposer(r),
@ -124,7 +127,7 @@ func SimulateFromSeed(
// recover logs in case of panic
defer func() {
if r := recover(); r != nil {
_, _ = fmt.Fprintf(w, "simulation halted due to panic on block %d; %v\n", header.Height, r)
_, _ = fmt.Fprintf(w, "simulation halted due to panic on block %d\n", header.Height)
logWriter.PrintLogs()
panic(r)
}
@ -151,12 +154,15 @@ func SimulateFromSeed(
// Run queued operations. Ignores blocksize if blocksize is too small
numQueuedOpsRan := runQueuedOperations(
operationQueue, int(header.Height),
tb, r, app, ctx, accs, logWriter, eventStats.Tally, config.Lean)
operationQueue, int(header.Height), tb, r, app, ctx, accs, logWriter,
eventStats.Tally, config.Lean, config.ChainID,
)
numQueuedTimeOpsRan := runQueuedTimeOperations(
timeOperationQueue, int(header.Height), header.Time,
tb, r, app, ctx, accs, logWriter, eventStats.Tally, config.Lean)
tb, r, app, ctx, accs, logWriter, eventStats.Tally,
config.Lean, config.ChainID,
)
// run standard operations
operations := blockSimulator(r, app, ctx, accs, header)
@ -271,7 +277,7 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, w io.Wr
// NOTE: the Rand 'r' should not be used here.
opAndR := opAndRz[i]
op, r2 := opAndR.op, opAndR.rand
opMsg, futureOps, err := op(r2, app, ctx, accounts)
opMsg, futureOps, err := op(r2, app, ctx, accounts, config.ChainID)
opMsg.LogEvent(event)
if !config.Lean || opMsg.OK {
@ -280,8 +286,10 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, w io.Wr
if err != nil {
logWriter.PrintLogs()
tb.Fatalf("error on operation %d within block %d, %v",
header.Height, opCount, err)
tb.Fatalf(`error on block %d/%d, operation (%d/%d) from x/%s:
%v
Comment: %s`,
header.Height, config.NumBlocks, opCount, blocksize, opMsg.Route, err, opMsg.Comment)
}
queueOperations(operationQueue, timeOperationQueue, futureOps)
@ -300,7 +308,8 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, w io.Wr
// nolint: errcheck
func runQueuedOperations(queueOps map[int][]Operation,
height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp,
ctx sdk.Context, accounts []Account, logWriter LogWriter, event func(route, op, evResult string), lean bool) (numOpsRan int) {
ctx sdk.Context, accounts []Account, logWriter LogWriter,
event func(route, op, evResult string), lean bool, chainID string) (numOpsRan int) {
queuedOp, ok := queueOps[height]
if !ok {
@ -313,7 +322,7 @@ func runQueuedOperations(queueOps map[int][]Operation,
// For now, queued operations cannot queue more operations.
// If a need arises for us to support queued messages to queue more messages, this can
// be changed.
opMsg, _, err := queuedOp[i](r, app, ctx, accounts)
opMsg, _, err := queuedOp[i](r, app, ctx, accounts, chainID)
opMsg.LogEvent(event)
if !lean || opMsg.OK {
logWriter.AddEntry((QueuedMsgEntry(int64(height), opMsg)))
@ -330,7 +339,8 @@ func runQueuedOperations(queueOps map[int][]Operation,
func runQueuedTimeOperations(queueOps []FutureOperation,
height int, currentTime time.Time, tb testing.TB, r *rand.Rand,
app *baseapp.BaseApp, ctx sdk.Context, accounts []Account,
logWriter LogWriter, event func(route, op, evResult string), lean bool) (numOpsRan int) {
logWriter LogWriter, event func(route, op, evResult string),
lean bool, chainID string) (numOpsRan int) {
numOpsRan = 0
for len(queueOps) > 0 && currentTime.After(queueOps[0].BlockTime) {
@ -338,7 +348,7 @@ func runQueuedTimeOperations(queueOps []FutureOperation,
// For now, queued operations cannot queue more operations.
// If a need arises for us to support queued messages to queue more messages, this can
// be changed.
opMsg, _, err := queueOps[0].Op(r, app, ctx, accounts)
opMsg, _, err := queueOps[0].Op(r, app, ctx, accounts, chainID)
opMsg.LogEvent(event)
if !lean || opMsg.OK {
logWriter.AddEntry(QueuedMsgEntry(int64(height), opMsg))

View File

@ -11,6 +11,7 @@ import (
// AccountKeeper expected account keeper
type AccountKeeper interface {
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
IterateAccounts(ctx sdk.Context, process func(authexported.Account) (stop bool))
}

View File

@ -0,0 +1,97 @@
package simulation
import (
"errors"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/slashing/internal/keeper"
"github.com/cosmos/cosmos-sdk/x/slashing/internal/types"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
)
// SimulateMsgUnjail generates a MsgUnjail with random values
// nolint: funlen
func SimulateMsgUnjail(ak types.AccountKeeper, k keeper.Keeper, sk stakingkeeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
validator, ok := stakingkeeper.RandomValidator(r, sk, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
simAccount, found := simulation.FindAccount(accs, sdk.AccAddress(validator.GetOperator()))
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
if !validator.IsJailed() {
// TODO: due to this condition this message is almost, if not always, skipped !
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
consAddr := sdk.ConsAddress(validator.GetConsPubKey().Address())
info, found := k.GetValidatorSigningInfo(ctx, consAddr)
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
selfDel := sk.Delegation(ctx, simAccount.Address, validator.GetOperator())
if selfDel == nil {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
account := ak.GetAccount(ctx, sdk.AccAddress(validator.GetOperator()))
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgUnjail(validator.GetOperator())
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
// result should fail if:
// - validator cannot be unjailed due to tombstone
// - validator is still in jailed period
// - self delegation too low
if info.Tombstoned ||
ctx.BlockHeader().Time.Before(info.JailedUntil) ||
validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) {
if res.IsOK() {
if info.Tombstoned {
return simulation.NewOperationMsg(msg, true, ""), nil, errors.New("validator should not have been unjailed if validator tombstoned")
}
if ctx.BlockHeader().Time.Before(info.JailedUntil) {
return simulation.NewOperationMsg(msg, true, ""), nil, errors.New("validator unjailed while validator still in jail period")
}
if validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) {
return simulation.NewOperationMsg(msg, true, ""), nil, errors.New("validator unjailed even though self-delegation too low")
}
}
// msg failed as expected
return simulation.NewOperationMsg(msg, false, ""), nil, nil
}
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}

View File

@ -1,32 +0,0 @@
package operations
import (
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/slashing"
)
// SimulateMsgUnjail generates a MsgUnjail with random values
func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation {
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
acc := simulation.RandomAcc(r, accs)
address := sdk.ValAddress(acc.Address)
msg := slashing.NewMsgUnjail(address)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(slashing.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := slashing.NewHandler(k)(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}

View File

@ -289,15 +289,12 @@ func validatorByPowerIndexExists(k Keeper, ctx sdk.Context, power []byte) bool {
}
// RandomValidator returns a random validator given access to the keeper and ctx
func RandomValidator(r *rand.Rand, keeper Keeper, ctx sdk.Context) types.Validator {
func RandomValidator(r *rand.Rand, keeper Keeper, ctx sdk.Context) (val types.Validator, ok bool) {
vals := keeper.GetAllValidators(ctx)
i := r.Intn(len(vals))
return vals[i]
}
if len(vals) == 0 {
return types.Validator{}, false
}
// RandomBondedValidator returns a random bonded validator given access to the keeper and ctx
func RandomBondedValidator(r *rand.Rand, keeper Keeper, ctx sdk.Context) types.Validator {
vals := keeper.GetBondedValidatorsByPower(ctx)
i := r.Intn(len(vals))
return vals[i]
return vals[i], true
}

View File

@ -63,9 +63,18 @@ func RandomizedGenState(simState *module.SimulationState) {
valAddr := sdk.ValAddress(simState.Accounts[i].Address)
valAddrs[i] = valAddr
maxCommission := sdk.NewDecWithPrec(int64(simulation.RandIntBetween(simState.Rand, 1, 100)), 2)
commission := types.NewCommission(
simulation.RandomDecAmount(simState.Rand, maxCommission),
maxCommission,
simulation.RandomDecAmount(simState.Rand, maxCommission),
)
validator := types.NewValidator(valAddr, simState.Accounts[i].PubKey, types.Description{})
validator.Tokens = sdk.NewInt(simState.InitialStake)
validator.DelegatorShares = sdk.NewDec(simState.InitialStake)
validator.Commission = commission
delegation := types.NewDelegation(simState.Accounts[i].Address, valAddr, sdk.NewDec(simState.InitialStake))
validators = append(validators, validator)
delegations = append(delegations, delegation)

View File

@ -0,0 +1,405 @@
package simulation
import (
"errors"
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
// SimulateMsgCreateValidator generates a MsgCreateValidator with random values
// nolint: funlen
func SimulateMsgCreateValidator(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
simAccount, _ := simulation.RandomAcc(r, accs)
address := sdk.ValAddress(simAccount.Address)
// ensure the validator doesn't exist already
_, found := k.GetValidator(ctx, address)
if found {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
denom := k.GetParams(ctx).BondDenom
amount := ak.GetAccount(ctx, simAccount.Address).GetCoins().AmountOf(denom)
if !amount.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
amount, err := simulation.RandPositiveInt(r, amount)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
selfDelegation := sdk.NewCoin(denom, amount)
account := ak.GetAccount(ctx, simAccount.Address)
coins := account.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(sdk.Coins{selfDelegation})
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
}
description := types.NewDescription(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
)
maxCommission := sdk.NewDecWithPrec(int64(simulation.RandIntBetween(r, 0, 100)), 2)
commission := types.NewCommissionRates(
simulation.RandomDecAmount(r, maxCommission),
maxCommission,
simulation.RandomDecAmount(r, maxCommission),
)
msg := types.NewMsgCreateValidator(address, simAccount.PubKey,
selfDelegation, description, commission, sdk.OneInt())
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgEditValidator generates a MsgEditValidator with random values
// nolint: funlen
func SimulateMsgEditValidator(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
if len(k.GetAllValidators(ctx)) == 0 {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
val, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
address := val.GetOperator()
newCommissionRate := simulation.RandomDecAmount(r, val.Commission.MaxRate)
if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.BlockHeader().Time); err != nil {
// skip as the commission is invalid
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, found := simulation.FindAccount(accs, sdk.AccAddress(val.GetOperator()))
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("validator %s not found", val.GetOperator())
}
account := ak.GetAccount(ctx, simAccount.Address)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
description := types.NewDescription(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
)
msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgDelegate generates a MsgDelegate with random values
// nolint: funlen
func SimulateMsgDelegate(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
denom := k.GetParams(ctx).BondDenom
if len(k.GetAllValidators(ctx)) == 0 {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
simAccount, _ := simulation.RandomAcc(r, accs)
val, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
if val.InvalidExRate() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
amount := ak.GetAccount(ctx, simAccount.Address).GetCoins().AmountOf(denom)
if !amount.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
amount, err := simulation.RandPositiveInt(r, amount)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
bondAmt := sdk.NewCoin(denom, amount)
account := ak.GetAccount(ctx, simAccount.Address)
coins := account.SpendableCoins(ctx.BlockTime())
var fees sdk.Coins
coins, hasNeg := coins.SafeSub(sdk.Coins{bondAmt})
if !hasNeg {
fees, err = simulation.RandomFees(r, ctx, coins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
}
msg := types.NewMsgDelegate(simAccount.Address, val.GetOperator(), bondAmt)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgUndelegate generates a MsgUndelegate with random values
// nolint: funlen
func SimulateMsgUndelegate(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// get random validator
validator, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
valAddr := validator.GetOperator()
delegations := k.GetValidatorDelegations(ctx, validator.OperatorAddress)
// get random delegator from validator
delegation := delegations[r.Intn(len(delegations))]
delAddr := delegation.GetDelegatorAddr()
if k.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
totalBond := validator.TokensFromShares(delegation.GetShares()).TruncateInt()
if !totalBond.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
unbondAmt, err := simulation.RandPositiveInt(r, totalBond)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if unbondAmt.IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
msg := types.NewMsgUndelegate(
delAddr, valAddr, sdk.NewCoin(k.BondDenom(ctx), unbondAmt),
)
// need to retrieve the simulation account associated with delegation to retrieve PrivKey
var simAccount simulation.Account
for _, simAcc := range accs {
if simAcc.Address.Equals(delAddr) {
simAccount = simAcc
break
}
}
// if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error
if simAccount.PrivKey == nil {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("Delegation addr: %s does not exist in simulation accounts", delAddr)
}
account := ak.GetAccount(ctx, delAddr)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values
// nolint: funlen
func SimulateMsgBeginRedelegate(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// get random source validator
srcVal, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
srcAddr := srcVal.GetOperator()
delegations := k.GetValidatorDelegations(ctx, srcAddr)
// get random delegator from src validator
delegation := delegations[r.Intn(len(delegations))]
delAddr := delegation.GetDelegatorAddr()
if k.HasReceivingRedelegation(ctx, delAddr, srcAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
// get random destination validator
destVal, ok := keeper.RandomValidator(r, k, ctx)
if !ok {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
destAddr := destVal.GetOperator()
if srcAddr.Equals(destAddr) ||
destVal.InvalidExRate() ||
k.HasMaxRedelegationEntries(ctx, delAddr, srcAddr, destAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt()
if !totalBond.IsPositive() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
redAmt, err := simulation.RandPositiveInt(r, totalBond)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if redAmt.IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
// check if the shares truncate to zero
shares, err := srcVal.SharesFromTokens(redAmt)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
if srcVal.TokensFromShares(shares).TruncateInt().IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil // skip
}
// need to retrieve the simulation account associated with delegation to retrieve PrivKey
var simAccount simulation.Account
for _, simAcc := range accs {
if simAcc.Address.Equals(delAddr) {
simAccount = simAcc
break
}
}
// if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error
if simAccount.PrivKey == nil {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("Delegation addr: %s does not exist in simulation accounts", delAddr)
}
// get tx fees
account := ak.GetAccount(ctx, delAddr)
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
msg := types.NewMsgBeginRedelegate(
delAddr, srcAddr, destAddr,
sdk.NewCoin(k.BondDenom(ctx), redAmt),
)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
simAccount.PrivKey,
)
res := app.Deliver(tx)
if !res.IsOK() {
return simulation.NoOpMsg(types.ModuleName), nil, errors.New(res.Log)
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}

View File

@ -1,227 +0,0 @@
package operations
import (
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
)
// SimulateMsgCreateValidator generates a MsgCreateValidator with random values
func SimulateMsgCreateValidator(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
handler := staking.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
denom := k.GetParams(ctx).BondDenom
description := staking.NewDescription(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
)
maxCommission := sdk.NewDecWithPrec(r.Int63n(1000), 3)
commission := staking.NewCommissionRates(
simulation.RandomDecAmount(r, maxCommission),
maxCommission,
simulation.RandomDecAmount(r, maxCommission),
)
acc := simulation.RandomAcc(r, accs)
address := sdk.ValAddress(acc.Address)
amount := m.GetAccount(ctx, acc.Address).GetCoins().AmountOf(denom)
if amount.IsPositive() {
amount = simulation.RandomAmount(r, amount)
}
if amount.Equal(sdk.ZeroInt()) {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
selfDelegation := sdk.NewCoin(denom, amount)
msg := staking.NewMsgCreateValidator(address, acc.PubKey,
selfDelegation, description, commission, sdk.OneInt())
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(staking.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgEditValidator generates a MsgEditValidator with random values
func SimulateMsgEditValidator(k staking.Keeper) simulation.Operation {
handler := staking.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
description := staking.NewDescription(
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
simulation.RandStringOfLength(r, 10),
)
if len(k.GetAllValidators(ctx)) == 0 {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
val := keeper.RandomValidator(r, k, ctx)
address := val.GetOperator()
newCommissionRate := simulation.RandomDecAmount(r, val.Commission.MaxRate)
msg := staking.NewMsgEditValidator(address, description, &newCommissionRate, nil)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(staking.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgDelegate generates a MsgDelegate with random values
func SimulateMsgDelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
handler := staking.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
denom := k.GetParams(ctx).BondDenom
if len(k.GetAllValidators(ctx)) == 0 {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
val := keeper.RandomValidator(r, k, ctx)
validatorAddress := val.GetOperator()
delegatorAcc := simulation.RandomAcc(r, accs)
delegatorAddress := delegatorAcc.Address
amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom)
if amount.IsPositive() {
amount = simulation.RandomAmount(r, amount)
}
if amount.Equal(sdk.ZeroInt()) {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
msg := staking.NewMsgDelegate(
delegatorAddress, validatorAddress, sdk.NewCoin(denom, amount))
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(staking.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgUndelegate generates a MsgUndelegate with random values
func SimulateMsgUndelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
handler := staking.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
delegatorAcc := simulation.RandomAcc(r, accs)
delegatorAddress := delegatorAcc.Address
delegations := k.GetAllDelegatorDelegations(ctx, delegatorAddress)
if len(delegations) == 0 {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
delegation := delegations[r.Intn(len(delegations))]
validator, found := k.GetValidator(ctx, delegation.GetValidatorAddr())
if !found {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
totalBond := validator.TokensFromShares(delegation.GetShares()).TruncateInt()
unbondAmt := simulation.RandomAmount(r, totalBond)
if unbondAmt.Equal(sdk.ZeroInt()) {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
msg := staking.NewMsgUndelegate(
delegatorAddress, delegation.ValidatorAddress, sdk.NewCoin(k.GetParams(ctx).BondDenom, unbondAmt),
)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(staking.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s, got error %v",
msg.GetSignBytes(), msg.ValidateBasic())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}
// SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values
func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Operation {
handler := staking.NewHandler(k)
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
denom := k.GetParams(ctx).BondDenom
if len(k.GetAllValidators(ctx)) == 0 {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
srcVal := keeper.RandomValidator(r, k, ctx)
srcValidatorAddress := srcVal.GetOperator()
destVal := keeper.RandomValidator(r, k, ctx)
destValidatorAddress := destVal.GetOperator()
delegatorAcc := simulation.RandomAcc(r, accs)
delegatorAddress := delegatorAcc.Address
// TODO
amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom)
if amount.IsPositive() {
amount = simulation.RandomAmount(r, amount)
}
if amount.Equal(sdk.ZeroInt()) {
return simulation.NoOpMsg(staking.ModuleName), nil, nil
}
msg := staking.NewMsgBeginRedelegate(
delegatorAddress, srcValidatorAddress, destValidatorAddress, sdk.NewCoin(denom, amount),
)
if msg.ValidateBasic() != nil {
return simulation.NoOpMsg(staking.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
}
ctx, write := ctx.CacheContext()
ok := handler(ctx, msg).IsOK()
if ok {
write()
}
opMsg = simulation.NewOperationMsg(msg, ok, "")
return opMsg, nil, nil
}
}

View File

@ -16,6 +16,7 @@ type DistributionKeeper interface {
// AccountKeeper defines the expected account keeper (noalias)
type AccountKeeper interface {
IterateAccounts(ctx sdk.Context, process func(authexported.Account) (stop bool))
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account // only used for simulation
}
// SupplyKeeper defines the expected supply Keeper (noalias)