github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/simulation/operations.go (about)

     1  package simulation
     2  
     3  import (
     4  	"math"
     5  	"math/rand"
     6  	"time"
     7  
     8  	sdkmath "cosmossdk.io/math"
     9  
    10  	"github.com/cosmos/cosmos-sdk/baseapp"
    11  	"github.com/cosmos/cosmos-sdk/client"
    12  	simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
    13  	sdk "github.com/cosmos/cosmos-sdk/types"
    14  	simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
    15  	"github.com/cosmos/cosmos-sdk/x/gov/keeper"
    16  	"github.com/cosmos/cosmos-sdk/x/gov/types"
    17  	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
    18  	"github.com/cosmos/cosmos-sdk/x/simulation"
    19  )
    20  
    21  var initialProposalID = uint64(100000000000000)
    22  
    23  // Governance message types and routes
    24  var (
    25  	TypeMsgDeposit        = sdk.MsgTypeURL(&v1.MsgDeposit{})
    26  	TypeMsgVote           = sdk.MsgTypeURL(&v1.MsgVote{})
    27  	TypeMsgVoteWeighted   = sdk.MsgTypeURL(&v1.MsgVoteWeighted{})
    28  	TypeMsgSubmitProposal = sdk.MsgTypeURL(&v1.MsgSubmitProposal{})
    29  	TypeMsgCancelProposal = sdk.MsgTypeURL(&v1.MsgCancelProposal{})
    30  )
    31  
    32  // Simulation operation weights constants
    33  const (
    34  	OpWeightMsgDeposit        = "op_weight_msg_deposit"
    35  	OpWeightMsgVote           = "op_weight_msg_vote"
    36  	OpWeightMsgVoteWeighted   = "op_weight_msg_weighted_vote"
    37  	OpWeightMsgCancelProposal = "op_weight_msg_cancel_proposal"
    38  
    39  	DefaultWeightMsgDeposit        = 100
    40  	DefaultWeightMsgVote           = 67
    41  	DefaultWeightMsgVoteWeighted   = 33
    42  	DefaultWeightTextProposal      = 5
    43  	DefaultWeightMsgCancelProposal = 5
    44  )
    45  
    46  // WeightedOperations returns all the operations from the module with their respective weights
    47  func WeightedOperations(
    48  	appParams simtypes.AppParams,
    49  	txGen client.TxConfig,
    50  	ak types.AccountKeeper,
    51  	bk types.BankKeeper,
    52  	k *keeper.Keeper,
    53  	wMsgs []simtypes.WeightedProposalMsg,
    54  	wContents []simtypes.WeightedProposalContent, //nolint:staticcheck // used for legacy testing
    55  ) simulation.WeightedOperations {
    56  	var (
    57  		weightMsgDeposit        int
    58  		weightMsgVote           int
    59  		weightMsgVoteWeighted   int
    60  		weightMsgCancelProposal int
    61  	)
    62  
    63  	appParams.GetOrGenerate(OpWeightMsgDeposit, &weightMsgDeposit, nil,
    64  		func(_ *rand.Rand) {
    65  			weightMsgDeposit = DefaultWeightMsgDeposit
    66  		},
    67  	)
    68  
    69  	appParams.GetOrGenerate(OpWeightMsgVote, &weightMsgVote, nil,
    70  		func(_ *rand.Rand) {
    71  			weightMsgVote = DefaultWeightMsgVote
    72  		},
    73  	)
    74  
    75  	appParams.GetOrGenerate(OpWeightMsgVoteWeighted, &weightMsgVoteWeighted, nil,
    76  		func(_ *rand.Rand) {
    77  			weightMsgVoteWeighted = DefaultWeightMsgVoteWeighted
    78  		},
    79  	)
    80  
    81  	appParams.GetOrGenerate(OpWeightMsgCancelProposal, &weightMsgCancelProposal, nil,
    82  		func(_ *rand.Rand) {
    83  			weightMsgCancelProposal = DefaultWeightMsgCancelProposal
    84  		},
    85  	)
    86  
    87  	// generate the weighted operations for the proposal msgs
    88  	var wProposalOps simulation.WeightedOperations
    89  	for _, wMsg := range wMsgs {
    90  		wMsg := wMsg // pin variable
    91  		var weight int
    92  		appParams.GetOrGenerate(wMsg.AppParamsKey(), &weight, nil,
    93  			func(_ *rand.Rand) { weight = wMsg.DefaultWeight() },
    94  		)
    95  
    96  		wProposalOps = append(
    97  			wProposalOps,
    98  			simulation.NewWeightedOperation(
    99  				weight,
   100  				SimulateMsgSubmitProposal(txGen, ak, bk, k, wMsg.MsgSimulatorFn()),
   101  			),
   102  		)
   103  	}
   104  
   105  	// generate the weighted operations for the proposal contents
   106  	var wLegacyProposalOps simulation.WeightedOperations
   107  	for _, wContent := range wContents {
   108  		wContent := wContent // pin variable
   109  		var weight int
   110  		appParams.GetOrGenerate(wContent.AppParamsKey(), &weight, nil,
   111  			func(_ *rand.Rand) { weight = wContent.DefaultWeight() },
   112  		)
   113  
   114  		wLegacyProposalOps = append(
   115  			wLegacyProposalOps,
   116  			simulation.NewWeightedOperation(
   117  				weight,
   118  				SimulateMsgSubmitLegacyProposal(txGen, ak, bk, k, wContent.ContentSimulatorFn()),
   119  			),
   120  		)
   121  	}
   122  
   123  	wGovOps := simulation.WeightedOperations{
   124  		simulation.NewWeightedOperation(
   125  			weightMsgDeposit,
   126  			SimulateMsgDeposit(txGen, ak, bk, k),
   127  		),
   128  		simulation.NewWeightedOperation(
   129  			weightMsgVote,
   130  			SimulateMsgVote(txGen, ak, bk, k),
   131  		),
   132  		simulation.NewWeightedOperation(
   133  			weightMsgVoteWeighted,
   134  			SimulateMsgVoteWeighted(txGen, ak, bk, k),
   135  		),
   136  		simulation.NewWeightedOperation(
   137  			weightMsgCancelProposal,
   138  			SimulateMsgCancelProposal(txGen, ak, bk, k),
   139  		),
   140  	}
   141  
   142  	return append(wGovOps, append(wProposalOps, wLegacyProposalOps...)...)
   143  }
   144  
   145  // SimulateMsgSubmitProposal simulates creating a msg Submit Proposal
   146  // voting on the proposal, and subsequently slashing the proposal. It is implemented using
   147  // future operations.
   148  func SimulateMsgSubmitProposal(
   149  	txGen client.TxConfig,
   150  	ak types.AccountKeeper,
   151  	bk types.BankKeeper,
   152  	k *keeper.Keeper,
   153  	msgSim simtypes.MsgSimulatorFn,
   154  ) simtypes.Operation {
   155  	return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   156  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   157  		msgs := []sdk.Msg{}
   158  		proposalMsg := msgSim(r, ctx, accs)
   159  		if proposalMsg != nil {
   160  			msgs = append(msgs, proposalMsg)
   161  		}
   162  
   163  		return simulateMsgSubmitProposal(txGen, ak, bk, k, msgs)(r, app, ctx, accs, chainID)
   164  	}
   165  }
   166  
   167  // SimulateMsgSubmitLegacyProposal simulates creating a msg Submit Proposal
   168  // voting on the proposal, and subsequently slashing the proposal. It is implemented using
   169  // future operations.
   170  func SimulateMsgSubmitLegacyProposal(
   171  	txGen client.TxConfig,
   172  	ak types.AccountKeeper,
   173  	bk types.BankKeeper,
   174  	k *keeper.Keeper,
   175  	contentSim simtypes.ContentSimulatorFn, //nolint:staticcheck // used for legacy testing
   176  ) simtypes.Operation {
   177  	return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
   178  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   179  		// 1) submit proposal now
   180  		content := contentSim(r, ctx, accs)
   181  		if content == nil {
   182  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "content is nil"), nil, nil
   183  		}
   184  
   185  		govacc := k.GetGovernanceAccount(ctx)
   186  		contentMsg, err := v1.NewLegacyContent(content, govacc.GetAddress().String())
   187  		if err != nil {
   188  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "error converting legacy content into proposal message"), nil, err
   189  		}
   190  
   191  		return simulateMsgSubmitProposal(txGen, ak, bk, k, []sdk.Msg{contentMsg})(r, app, ctx, accs, chainID)
   192  	}
   193  }
   194  
   195  func simulateMsgSubmitProposal(
   196  	txGen client.TxConfig,
   197  	ak types.AccountKeeper,
   198  	bk types.BankKeeper,
   199  	k *keeper.Keeper,
   200  	proposalMsgs []sdk.Msg,
   201  ) simtypes.Operation {
   202  	// The states are:
   203  	// column 1: All validators vote
   204  	// column 2: 90% vote
   205  	// column 3: 75% vote
   206  	// column 4: 40% vote
   207  	// column 5: 15% vote
   208  	// column 6: noone votes
   209  	// All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily,
   210  	// feel free to change.
   211  	numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{
   212  		{20, 10, 0, 0, 0, 0},
   213  		{55, 50, 20, 10, 0, 0},
   214  		{25, 25, 30, 25, 30, 15},
   215  		{0, 15, 30, 25, 30, 30},
   216  		{0, 0, 20, 30, 30, 30},
   217  		{0, 0, 0, 10, 10, 25},
   218  	})
   219  
   220  	statePercentageArray := []float64{1, .9, .75, .4, .15, 0}
   221  	curNumVotesState := 1
   222  
   223  	return func(
   224  		r *rand.Rand,
   225  		app *baseapp.BaseApp,
   226  		ctx sdk.Context,
   227  		accs []simtypes.Account,
   228  		chainID string,
   229  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   230  		simAccount, _ := simtypes.RandomAcc(r, accs)
   231  		expedited := r.Intn(2) == 0
   232  		deposit, skip, err := randomDeposit(r, ctx, ak, bk, k, simAccount.Address, true, expedited)
   233  		switch {
   234  		case skip:
   235  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "skip deposit"), nil, nil
   236  		case err != nil:
   237  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "unable to generate deposit"), nil, err
   238  		}
   239  
   240  		msg, err := v1.NewMsgSubmitProposal(
   241  			proposalMsgs,
   242  			deposit,
   243  			simAccount.Address.String(),
   244  			simtypes.RandStringOfLength(r, 100),
   245  			simtypes.RandStringOfLength(r, 100),
   246  			simtypes.RandStringOfLength(r, 100),
   247  			expedited,
   248  		)
   249  		if err != nil {
   250  			return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate a submit proposal msg"), nil, err
   251  		}
   252  
   253  		account := ak.GetAccount(ctx, simAccount.Address)
   254  		tx, err := simtestutil.GenSignedMockTx(
   255  			r,
   256  			txGen,
   257  			[]sdk.Msg{msg},
   258  			sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)},
   259  			simtestutil.DefaultGenTxGas,
   260  			chainID,
   261  			[]uint64{account.GetAccountNumber()},
   262  			[]uint64{account.GetSequence()},
   263  			simAccount.PrivKey,
   264  		)
   265  		if err != nil {
   266  			return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate mock tx"), nil, err
   267  		}
   268  
   269  		_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
   270  		if err != nil {
   271  			return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err
   272  		}
   273  
   274  		opMsg := simtypes.NewOperationMsg(msg, true, "")
   275  
   276  		// get the submitted proposal ID
   277  		proposalID, err := k.ProposalID.Peek(ctx)
   278  		if err != nil {
   279  			return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate proposalID"), nil, err
   280  		}
   281  
   282  		// 2) Schedule operations for votes
   283  		// 2.1) first pick a number of people to vote.
   284  		curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState)
   285  		numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState]))
   286  
   287  		// 2.2) select who votes and when
   288  		whoVotes := r.Perm(len(accs))
   289  
   290  		// didntVote := whoVotes[numVotes:]
   291  		whoVotes = whoVotes[:numVotes]
   292  		params, _ := k.Params.Get(ctx)
   293  		votingPeriod := params.VotingPeriod
   294  
   295  		fops := make([]simtypes.FutureOperation, numVotes+1)
   296  		for i := 0; i < numVotes; i++ {
   297  			whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second)
   298  			fops[i] = simtypes.FutureOperation{
   299  				BlockTime: whenVote,
   300  				Op:        operationSimulateMsgVote(txGen, ak, bk, k, accs[whoVotes[i]], int64(proposalID)),
   301  			}
   302  		}
   303  
   304  		return opMsg, fops, nil
   305  	}
   306  }
   307  
   308  // SimulateMsgDeposit generates a MsgDeposit with random values.
   309  func SimulateMsgDeposit(
   310  	txGen client.TxConfig,
   311  	ak types.AccountKeeper,
   312  	bk types.BankKeeper,
   313  	k *keeper.Keeper,
   314  ) simtypes.Operation {
   315  	return func(
   316  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
   317  		accs []simtypes.Account, chainID string,
   318  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   319  		simAccount, _ := simtypes.RandomAcc(r, accs)
   320  		proposalID, ok := randomProposalID(r, k, ctx, v1.StatusDepositPeriod)
   321  		if !ok {
   322  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to generate proposalID"), nil, nil
   323  		}
   324  
   325  		p, err := k.Proposals.Get(ctx, proposalID)
   326  		if err != nil {
   327  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to get proposal"), nil, err
   328  		}
   329  
   330  		deposit, skip, err := randomDeposit(r, ctx, ak, bk, k, simAccount.Address, false, p.Expedited)
   331  		switch {
   332  		case skip:
   333  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "skip deposit"), nil, nil
   334  		case err != nil:
   335  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to generate deposit"), nil, err
   336  		}
   337  
   338  		msg := v1.NewMsgDeposit(simAccount.Address, proposalID, deposit)
   339  
   340  		account := ak.GetAccount(ctx, simAccount.Address)
   341  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   342  
   343  		var fees sdk.Coins
   344  		coins, hasNeg := spendable.SafeSub(deposit...)
   345  		if !hasNeg {
   346  			fees, err = simtypes.RandomFees(r, ctx, coins)
   347  			if err != nil {
   348  				return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate fees"), nil, err
   349  			}
   350  		}
   351  
   352  		txCtx := simulation.OperationInput{
   353  			R:             r,
   354  			App:           app,
   355  			TxGen:         txGen,
   356  			Cdc:           nil,
   357  			Msg:           msg,
   358  			Context:       ctx,
   359  			SimAccount:    simAccount,
   360  			AccountKeeper: ak,
   361  			ModuleName:    types.ModuleName,
   362  		}
   363  
   364  		return simulation.GenAndDeliverTx(txCtx, fees)
   365  	}
   366  }
   367  
   368  // SimulateMsgVote generates a MsgVote with random values.
   369  func SimulateMsgVote(
   370  	txGen client.TxConfig,
   371  	ak types.AccountKeeper,
   372  	bk types.BankKeeper,
   373  	k *keeper.Keeper,
   374  ) simtypes.Operation {
   375  	return operationSimulateMsgVote(txGen, ak, bk, k, simtypes.Account{}, -1)
   376  }
   377  
   378  func operationSimulateMsgVote(
   379  	txGen client.TxConfig,
   380  	ak types.AccountKeeper,
   381  	bk types.BankKeeper,
   382  	k *keeper.Keeper,
   383  	simAccount simtypes.Account,
   384  	proposalIDInt int64,
   385  ) simtypes.Operation {
   386  	return func(
   387  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
   388  		accs []simtypes.Account, chainID string,
   389  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   390  		if simAccount.Equals(simtypes.Account{}) {
   391  			simAccount, _ = simtypes.RandomAcc(r, accs)
   392  		}
   393  
   394  		var proposalID uint64
   395  
   396  		switch {
   397  		case proposalIDInt < 0:
   398  			var ok bool
   399  			proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod)
   400  			if !ok {
   401  				return simtypes.NoOpMsg(types.ModuleName, TypeMsgVote, "unable to generate proposalID"), nil, nil
   402  			}
   403  		default:
   404  			proposalID = uint64(proposalIDInt)
   405  		}
   406  
   407  		option := randomVotingOption(r)
   408  		msg := v1.NewMsgVote(simAccount.Address, proposalID, option, "")
   409  
   410  		account := ak.GetAccount(ctx, simAccount.Address)
   411  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   412  
   413  		txCtx := simulation.OperationInput{
   414  			R:               r,
   415  			App:             app,
   416  			TxGen:           txGen,
   417  			Cdc:             nil,
   418  			Msg:             msg,
   419  			Context:         ctx,
   420  			SimAccount:      simAccount,
   421  			AccountKeeper:   ak,
   422  			Bankkeeper:      bk,
   423  			ModuleName:      types.ModuleName,
   424  			CoinsSpentInMsg: spendable,
   425  		}
   426  
   427  		return simulation.GenAndDeliverTxWithRandFees(txCtx)
   428  	}
   429  }
   430  
   431  // SimulateMsgVoteWeighted generates a MsgVoteWeighted with random values.
   432  func SimulateMsgVoteWeighted(
   433  	txGen client.TxConfig,
   434  	ak types.AccountKeeper,
   435  	bk types.BankKeeper,
   436  	k *keeper.Keeper,
   437  ) simtypes.Operation {
   438  	return operationSimulateMsgVoteWeighted(txGen, ak, bk, k, simtypes.Account{}, -1)
   439  }
   440  
   441  func operationSimulateMsgVoteWeighted(
   442  	txGen client.TxConfig,
   443  	ak types.AccountKeeper,
   444  	bk types.BankKeeper,
   445  	k *keeper.Keeper,
   446  	simAccount simtypes.Account,
   447  	proposalIDInt int64,
   448  ) simtypes.Operation {
   449  	return func(
   450  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
   451  		accs []simtypes.Account, chainID string,
   452  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   453  		if simAccount.Equals(simtypes.Account{}) {
   454  			simAccount, _ = simtypes.RandomAcc(r, accs)
   455  		}
   456  
   457  		var proposalID uint64
   458  
   459  		switch {
   460  		case proposalIDInt < 0:
   461  			var ok bool
   462  			proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod)
   463  			if !ok {
   464  				return simtypes.NoOpMsg(types.ModuleName, TypeMsgVoteWeighted, "unable to generate proposalID"), nil, nil
   465  			}
   466  		default:
   467  			proposalID = uint64(proposalIDInt)
   468  		}
   469  
   470  		options := randomWeightedVotingOptions(r)
   471  		msg := v1.NewMsgVoteWeighted(simAccount.Address, proposalID, options, "")
   472  
   473  		account := ak.GetAccount(ctx, simAccount.Address)
   474  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   475  
   476  		txCtx := simulation.OperationInput{
   477  			R:               r,
   478  			App:             app,
   479  			TxGen:           txGen,
   480  			Cdc:             nil,
   481  			Msg:             msg,
   482  			Context:         ctx,
   483  			SimAccount:      simAccount,
   484  			AccountKeeper:   ak,
   485  			Bankkeeper:      bk,
   486  			ModuleName:      types.ModuleName,
   487  			CoinsSpentInMsg: spendable,
   488  		}
   489  
   490  		return simulation.GenAndDeliverTxWithRandFees(txCtx)
   491  	}
   492  }
   493  
   494  // SimulateMsgCancelProposal generates a MsgCancelProposal.
   495  func SimulateMsgCancelProposal(
   496  	txGen client.TxConfig,
   497  	ak types.AccountKeeper,
   498  	bk types.BankKeeper,
   499  	k *keeper.Keeper,
   500  ) simtypes.Operation {
   501  	return func(
   502  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
   503  		accs []simtypes.Account, chainID string,
   504  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   505  		simAccount := accs[0]
   506  		proposal := randomProposal(r, k, ctx)
   507  		if proposal == nil {
   508  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "no proposals found"), nil, nil
   509  		}
   510  
   511  		if proposal.Proposer != simAccount.Address.String() {
   512  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposer"), nil, nil
   513  		}
   514  
   515  		if (proposal.Status != v1.StatusDepositPeriod) && (proposal.Status != v1.StatusVotingPeriod) {
   516  			return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposal status"), nil, nil
   517  		}
   518  
   519  		account := ak.GetAccount(ctx, simAccount.Address)
   520  		spendable := bk.SpendableCoins(ctx, account.GetAddress())
   521  
   522  		msg := v1.NewMsgCancelProposal(proposal.Id, account.GetAddress().String())
   523  
   524  		txCtx := simulation.OperationInput{
   525  			R:               r,
   526  			App:             app,
   527  			TxGen:           txGen,
   528  			Cdc:             nil,
   529  			Msg:             msg,
   530  			Context:         ctx,
   531  			SimAccount:      simAccount,
   532  			AccountKeeper:   ak,
   533  			Bankkeeper:      bk,
   534  			ModuleName:      types.ModuleName,
   535  			CoinsSpentInMsg: spendable,
   536  		}
   537  
   538  		return simulation.GenAndDeliverTxWithRandFees(txCtx)
   539  	}
   540  }
   541  
   542  // Pick a random deposit with a random denomination with a
   543  // deposit amount between (0, min(balance, minDepositAmount))
   544  // This is to simulate multiple users depositing to get the
   545  // proposal above the minimum deposit amount
   546  func randomDeposit(
   547  	r *rand.Rand,
   548  	ctx sdk.Context,
   549  	ak types.AccountKeeper,
   550  	bk types.BankKeeper,
   551  	k *keeper.Keeper,
   552  	addr sdk.AccAddress,
   553  	useMinAmount bool,
   554  	expedited bool,
   555  ) (deposit sdk.Coins, skip bool, err error) {
   556  	account := ak.GetAccount(ctx, addr)
   557  	spendable := bk.SpendableCoins(ctx, account.GetAddress())
   558  
   559  	if spendable.Empty() {
   560  		return nil, true, nil // skip
   561  	}
   562  
   563  	params, _ := k.Params.Get(ctx)
   564  	minDeposit := params.MinDeposit
   565  	if expedited {
   566  		minDeposit = params.ExpeditedMinDeposit
   567  	}
   568  	denomIndex := r.Intn(len(minDeposit))
   569  	denom := minDeposit[denomIndex].Denom
   570  
   571  	spendableBalance := spendable.AmountOf(denom)
   572  	if spendableBalance.IsZero() {
   573  		return nil, true, nil
   574  	}
   575  
   576  	minDepositAmount := minDeposit[denomIndex].Amount
   577  
   578  	minDepositRatio, err := sdkmath.LegacyNewDecFromStr(params.GetMinDepositRatio())
   579  	if err != nil {
   580  		return nil, false, err
   581  	}
   582  
   583  	threshold := minDepositAmount.ToLegacyDec().Mul(minDepositRatio).TruncateInt()
   584  
   585  	minAmount := sdkmath.ZeroInt()
   586  	if useMinAmount {
   587  		minDepositPercent, err := sdkmath.LegacyNewDecFromStr(params.MinInitialDepositRatio)
   588  		if err != nil {
   589  			return nil, false, err
   590  		}
   591  
   592  		minAmount = sdkmath.LegacyNewDecFromInt(minDepositAmount).Mul(minDepositPercent).TruncateInt()
   593  	}
   594  
   595  	amount, err := simtypes.RandPositiveInt(r, minDepositAmount.Sub(minAmount))
   596  	if err != nil {
   597  		return nil, false, err
   598  	}
   599  	amount = amount.Add(minAmount)
   600  
   601  	if amount.GT(spendableBalance) || amount.LT(threshold) {
   602  		return nil, true, nil
   603  	}
   604  
   605  	return sdk.Coins{sdk.NewCoin(denom, amount)}, false, nil
   606  }
   607  
   608  // randomProposal returns a random proposal stored in state
   609  func randomProposal(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context) *v1.Proposal {
   610  	var proposals []*v1.Proposal
   611  	err := k.Proposals.Walk(ctx, nil, func(key uint64, value v1.Proposal) (stop bool, err error) {
   612  		proposals = append(proposals, &value)
   613  		return false, nil
   614  	})
   615  	if err != nil {
   616  		panic(err)
   617  	}
   618  	if len(proposals) == 0 {
   619  		return nil
   620  	}
   621  	randomIndex := r.Intn(len(proposals))
   622  	return proposals[randomIndex]
   623  }
   624  
   625  // Pick a random proposal ID between the initial proposal ID
   626  // (defined in gov GenesisState) and the latest proposal ID
   627  // that matches a given Status.
   628  // It does not provide a default ID.
   629  func randomProposalID(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context, status v1.ProposalStatus) (proposalID uint64, found bool) {
   630  	proposalID, _ = k.ProposalID.Peek(ctx)
   631  
   632  	switch {
   633  	case proposalID > initialProposalID:
   634  		// select a random ID between [initialProposalID, proposalID]
   635  		proposalID = uint64(simtypes.RandIntBetween(r, int(initialProposalID), int(proposalID)))
   636  
   637  	default:
   638  		// This is called on the first call to this funcion
   639  		// in order to update the global variable
   640  		initialProposalID = proposalID
   641  	}
   642  
   643  	proposal, err := k.Proposals.Get(ctx, proposalID)
   644  	if err != nil || proposal.Status != status {
   645  		return proposalID, false
   646  	}
   647  
   648  	return proposalID, true
   649  }
   650  
   651  // Pick a random voting option
   652  func randomVotingOption(r *rand.Rand) v1.VoteOption {
   653  	switch r.Intn(4) {
   654  	case 0:
   655  		return v1.OptionYes
   656  	case 1:
   657  		return v1.OptionAbstain
   658  	case 2:
   659  		return v1.OptionNo
   660  	case 3:
   661  		return v1.OptionNoWithVeto
   662  	default:
   663  		panic("invalid vote option")
   664  	}
   665  }
   666  
   667  // Pick a random weighted voting options
   668  func randomWeightedVotingOptions(r *rand.Rand) v1.WeightedVoteOptions {
   669  	w1 := r.Intn(100 + 1)
   670  	w2 := r.Intn(100 - w1 + 1)
   671  	w3 := r.Intn(100 - w1 - w2 + 1)
   672  	w4 := 100 - w1 - w2 - w3
   673  	weightedVoteOptions := v1.WeightedVoteOptions{}
   674  	if w1 > 0 {
   675  		weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
   676  			Option: v1.OptionYes,
   677  			Weight: sdkmath.LegacyNewDecWithPrec(int64(w1), 2).String(),
   678  		})
   679  	}
   680  	if w2 > 0 {
   681  		weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
   682  			Option: v1.OptionAbstain,
   683  			Weight: sdkmath.LegacyNewDecWithPrec(int64(w2), 2).String(),
   684  		})
   685  	}
   686  	if w3 > 0 {
   687  		weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
   688  			Option: v1.OptionNo,
   689  			Weight: sdkmath.LegacyNewDecWithPrec(int64(w3), 2).String(),
   690  		})
   691  	}
   692  	if w4 > 0 {
   693  		weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
   694  			Option: v1.OptionNoWithVeto,
   695  			Weight: sdkmath.LegacyNewDecWithPrec(int64(w4), 2).String(),
   696  		})
   697  	}
   698  	return weightedVoteOptions
   699  }