diff --git a/CHANGELOG.md b/CHANGELOG.md index 626e8ec8d..882ee49e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/simapp/app.go b/simapp/app.go index 32501aceb..53e671a12 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -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 { diff --git a/simapp/helpers/test_helpers.go b/simapp/helpers/test_helpers.go new file mode 100644 index 000000000..a9127a6fb --- /dev/null +++ b/simapp/helpers/test_helpers.go @@ -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) +} diff --git a/simapp/params.go b/simapp/params.go index b081715f3..e0416761a 100644 --- a/simapp/params.go +++ b/simapp/params.go @@ -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" ) diff --git a/simapp/sim_bench_test.go b/simapp/sim_bench_test.go index 430388f05..e9d5d7d87 100644 --- a/simapp/sim_bench_test.go +++ b/simapp/sim_bench_test.go @@ -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) diff --git a/simapp/sim_test.go b/simapp/sim_test.go index f42c78e4b..17bcf9d57 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -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()) diff --git a/simapp/state.go b/simapp/state.go index a9e815d22..679bce803 100644 --- a/simapp/state.go +++ b/simapp/state.go @@ -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 diff --git a/simapp/test_helpers.go b/simapp/test_helpers.go index c108d9f9f..7d91a6108 100644 --- a/simapp/test_helpers.go +++ b/simapp/test_helpers.go @@ -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) } diff --git a/x/auth/simulation/genesis.go b/x/auth/simulation/genesis.go index 1dc41a578..61b248472 100644 --- a/x/auth/simulation/genesis.go +++ b/x/auth/simulation/genesis.go @@ -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 diff --git a/x/auth/simulation/operations/msgs.go b/x/auth/simulation/operations/msgs.go deleted file mode 100644 index 9a516785c..000000000 --- a/x/auth/simulation/operations/msgs.go +++ /dev/null @@ -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 -} diff --git a/x/bank/simulation/genesis.go b/x/bank/simulation/genesis.go index 5e676e80c..2dae431c9 100644 --- a/x/bank/simulation/genesis.go +++ b/x/bank/simulation/genesis.go @@ -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 diff --git a/x/bank/simulation/operations.go b/x/bank/simulation/operations.go new file mode 100644 index 000000000..9d46d1fff --- /dev/null +++ b/x/bank/simulation/operations.go @@ -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 +} diff --git a/x/bank/simulation/operations/msgs.go b/x/bank/simulation/operations/msgs.go deleted file mode 100644 index 070581a62..000000000 --- a/x/bank/simulation/operations/msgs.go +++ /dev/null @@ -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 -} diff --git a/x/distribution/abci.go b/x/distribution/abci.go index de7df339e..44a2286ca 100644 --- a/x/distribution/abci.go +++ b/x/distribution/abci.go @@ -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 diff --git a/x/distribution/simulation/genesis.go b/x/distribution/simulation/genesis.go index e11213dad..207a14fe2 100644 --- a/x/distribution/simulation/genesis.go +++ b/x/distribution/simulation/genesis.go @@ -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)) diff --git a/x/distribution/simulation/operations.go b/x/distribution/simulation/operations.go new file mode 100644 index 000000000..c8bab36d4 --- /dev/null +++ b/x/distribution/simulation/operations.go @@ -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)), + ) + } +} diff --git a/x/distribution/simulation/operations/msgs.go b/x/distribution/simulation/operations/msgs.go deleted file mode 100644 index 10bc0418f..000000000 --- a/x/distribution/simulation/operations/msgs.go +++ /dev/null @@ -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, - ) - } -} diff --git a/x/distribution/types/expected_keepers.go b/x/distribution/types/expected_keepers.go index 7afb5aa59..a51bc9d04 100644 --- a/x/distribution/types/expected_keepers.go +++ b/x/distribution/types/expected_keepers.go @@ -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 diff --git a/x/gov/simulation/operations.go b/x/gov/simulation/operations.go new file mode 100644 index 000000000..ddabe9c23 --- /dev/null +++ b/x/gov/simulation/operations.go @@ -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") + } +} diff --git a/x/gov/simulation/operations/msgs.go b/x/gov/simulation/operations/msgs.go deleted file mode 100644 index 35010fef9..000000000 --- a/x/gov/simulation/operations/msgs.go +++ /dev/null @@ -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") -} diff --git a/x/gov/types/expected_keepers.go b/x/gov/types/expected_keepers.go index 8b28faea8..c0fda62d6 100644 --- a/x/gov/types/expected_keepers.go +++ b/x/gov/types/expected_keepers.go @@ -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 +} diff --git a/x/params/simulation/operations/msgs.go b/x/params/simulation/operations.go similarity index 93% rename from x/params/simulation/operations/msgs.go rename to x/params/simulation/operations.go index bdcfa3f75..0e2516c5f 100644 --- a/x/params/simulation/operations/msgs.go +++ b/x/params/simulation/operations.go @@ -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) diff --git a/x/simulation/account.go b/x/simulation/account.go index 386c55acd..4abcd8df5 100644 --- a/x/simulation/account.go +++ b/x/simulation/account.go @@ -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 +} diff --git a/x/simulation/config.go b/x/simulation/config.go index b15a6f2f3..58a39bb17 100644 --- a/x/simulation/config.go +++ b/x/simulation/config.go @@ -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 diff --git a/x/simulation/operation.go b/x/simulation/operation.go index eaf0c8d14..d7eaf9cfe 100644 --- a/x/simulation/operation.go +++ b/x/simulation/operation.go @@ -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. diff --git a/x/simulation/params.go b/x/simulation/params.go index 63aab6c40..e2bbcfcca 100644 --- a/x/simulation/params.go +++ b/x/simulation/params.go @@ -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, diff --git a/x/simulation/rand_util.go b/x/simulation/rand_util.go index fc73619c1..b0c403cf7 100644 --- a/x/simulation/rand_util.go +++ b/x/simulation/rand_util.go @@ -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. diff --git a/x/simulation/simulate.go b/x/simulation/simulate.go index 3f8cf01cb..800432c74 100644 --- a/x/simulation/simulate.go +++ b/x/simulation/simulate.go @@ -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)) diff --git a/x/slashing/internal/types/expected_keepers.go b/x/slashing/internal/types/expected_keepers.go index 776817416..58e1f30d6 100644 --- a/x/slashing/internal/types/expected_keepers.go +++ b/x/slashing/internal/types/expected_keepers.go @@ -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)) } diff --git a/x/slashing/simulation/operations.go b/x/slashing/simulation/operations.go new file mode 100644 index 000000000..e7b4c6870 --- /dev/null +++ b/x/slashing/simulation/operations.go @@ -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 + } +} diff --git a/x/slashing/simulation/operations/msgs.go b/x/slashing/simulation/operations/msgs.go deleted file mode 100644 index 5772101b1..000000000 --- a/x/slashing/simulation/operations/msgs.go +++ /dev/null @@ -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 - } -} diff --git a/x/staking/keeper/test_common.go b/x/staking/keeper/test_common.go index b00bdc00e..51d7f71ae 100644 --- a/x/staking/keeper/test_common.go +++ b/x/staking/keeper/test_common.go @@ -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 } diff --git a/x/staking/simulation/genesis.go b/x/staking/simulation/genesis.go index a73ac7eb7..d04553862 100644 --- a/x/staking/simulation/genesis.go +++ b/x/staking/simulation/genesis.go @@ -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) diff --git a/x/staking/simulation/operations.go b/x/staking/simulation/operations.go new file mode 100644 index 000000000..c4ee64ef5 --- /dev/null +++ b/x/staking/simulation/operations.go @@ -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 + } +} diff --git a/x/staking/simulation/operations/msgs.go b/x/staking/simulation/operations/msgs.go deleted file mode 100644 index d70ddd9ed..000000000 --- a/x/staking/simulation/operations/msgs.go +++ /dev/null @@ -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 - } -} diff --git a/x/staking/types/expected_keepers.go b/x/staking/types/expected_keepers.go index d3faecf82..8f346a71c 100644 --- a/x/staking/types/expected_keepers.go +++ b/x/staking/types/expected_keepers.go @@ -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)