github.com/Finschia/finschia-sdk@v0.48.1/x/bank/simulation/operations.go (about)

     1  package simulation
     2  
     3  import (
     4  	"math/rand"
     5  
     6  	"github.com/Finschia/finschia-sdk/baseapp"
     7  	"github.com/Finschia/finschia-sdk/codec"
     8  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
     9  	"github.com/Finschia/finschia-sdk/simapp/helpers"
    10  	simappparams "github.com/Finschia/finschia-sdk/simapp/params"
    11  	sdk "github.com/Finschia/finschia-sdk/types"
    12  	simtypes "github.com/Finschia/finschia-sdk/types/simulation"
    13  	"github.com/Finschia/finschia-sdk/x/bank/keeper"
    14  	"github.com/Finschia/finschia-sdk/x/bank/types"
    15  	distributiontypes "github.com/Finschia/finschia-sdk/x/distribution/types"
    16  	"github.com/Finschia/finschia-sdk/x/simulation"
    17  )
    18  
    19  // nolint:gosec
    20  // Simulation operation weights constants
    21  const (
    22  	OpWeightMsgSend      = "op_weight_msg_send"      //nolint:gosec
    23  	OpWeightMsgMultiSend = "op_weight_msg_multisend" //nolint:gosec
    24  )
    25  
    26  // WeightedOperations returns all the operations from the module with their respective weights
    27  func WeightedOperations(
    28  	appParams simtypes.AppParams, cdc codec.JSONCodec, ak types.AccountKeeper, bk keeper.Keeper,
    29  ) simulation.WeightedOperations {
    30  	var weightMsgSend, weightMsgMultiSend int
    31  	appParams.GetOrGenerate(cdc, OpWeightMsgSend, &weightMsgSend, nil,
    32  		func(_ *rand.Rand) {
    33  			weightMsgSend = simappparams.DefaultWeightMsgSend
    34  		},
    35  	)
    36  
    37  	appParams.GetOrGenerate(cdc, OpWeightMsgMultiSend, &weightMsgMultiSend, nil,
    38  		func(_ *rand.Rand) {
    39  			weightMsgMultiSend = simappparams.DefaultWeightMsgMultiSend
    40  		},
    41  	)
    42  
    43  	return simulation.WeightedOperations{
    44  		simulation.NewWeightedOperation(
    45  			weightMsgSend,
    46  			SimulateMsgSend(ak, bk),
    47  		),
    48  		simulation.NewWeightedOperation(
    49  			weightMsgMultiSend,
    50  			SimulateMsgMultiSend(ak, bk),
    51  		),
    52  	}
    53  }
    54  
    55  // SimulateMsgSend tests and runs a single msg send where both
    56  // accounts already exist.
    57  func SimulateMsgSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Operation {
    58  	return func(
    59  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
    60  		accs []simtypes.Account, chainID string,
    61  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
    62  		from, to, coins, skip := randomSendFields(r, ctx, accs, bk, ak)
    63  		// if coins slice is empty, we can not create valid types.MsgSend
    64  		if len(coins) == 0 {
    65  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSend, "empty coins slice"), nil, nil
    66  		}
    67  
    68  		// Check send_enabled status of each coin denom
    69  		if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil {
    70  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSend, err.Error()), nil, nil
    71  		}
    72  
    73  		if skip {
    74  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSend, "skip all transfers"), nil, nil
    75  		}
    76  
    77  		msg := types.NewMsgSend(from.Address, to.Address, coins)
    78  
    79  		err := sendMsgSend(r, app, bk, ak, msg, ctx, chainID, []cryptotypes.PrivKey{from.PrivKey})
    80  		if err != nil {
    81  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err
    82  		}
    83  
    84  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
    85  	}
    86  }
    87  
    88  // SimulateMsgSendToModuleAccount tests and runs a single msg send where both
    89  // accounts already exist.
    90  func SimulateMsgSendToModuleAccount(ak types.AccountKeeper, bk keeper.Keeper, moduleAccCount int) simtypes.Operation {
    91  	return func(
    92  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
    93  		accs []simtypes.Account, chainID string,
    94  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
    95  		from := accs[0]
    96  
    97  		to := getModuleAccounts(ak, ctx, moduleAccCount)[0]
    98  
    99  		spendable := bk.SpendableCoins(ctx, from.Address)
   100  		coins := simtypes.RandSubsetCoins(r, spendable)
   101  		// if coins slice is empty, we can not create valid types.MsgSend
   102  		if len(coins) == 0 {
   103  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSend, "empty coins slice"), nil, nil
   104  		}
   105  
   106  		// Check send_enabled status of each coin denom
   107  		if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil {
   108  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSend, err.Error()), nil, nil
   109  		}
   110  
   111  		msg := types.NewMsgSend(from.Address, to.Address, coins)
   112  
   113  		err := sendMsgSend(r, app, bk, ak, msg, ctx, chainID, []cryptotypes.PrivKey{from.PrivKey})
   114  		if err != nil {
   115  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err
   116  		}
   117  
   118  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
   119  	}
   120  }
   121  
   122  // sendMsgSend sends a transaction with a MsgSend from a provided random account.
   123  func sendMsgSend(
   124  	r *rand.Rand, app *baseapp.BaseApp, bk keeper.Keeper, ak types.AccountKeeper,
   125  	msg *types.MsgSend, ctx sdk.Context, chainID string, privkeys []cryptotypes.PrivKey,
   126  ) error {
   127  	var (
   128  		fees sdk.Coins
   129  		err  error
   130  	)
   131  
   132  	from, err := sdk.AccAddressFromBech32(msg.FromAddress)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	account := ak.GetAccount(ctx, from)
   138  	spendable := bk.SpendableCoins(ctx, account.GetAddress())
   139  
   140  	coins, hasNeg := spendable.SafeSub(msg.Amount)
   141  	if !hasNeg {
   142  		fees, err = simtypes.RandomFees(r, ctx, coins)
   143  		if err != nil {
   144  			return err
   145  		}
   146  	}
   147  	txGen := simappparams.MakeTestEncodingConfig().TxConfig
   148  	tx, err := helpers.GenSignedMockTx(
   149  		r,
   150  		txGen,
   151  		[]sdk.Msg{msg},
   152  		fees,
   153  		helpers.DefaultGenTxGas,
   154  		chainID,
   155  		[]uint64{account.GetAccountNumber()},
   156  		[]uint64{account.GetSequence()},
   157  		privkeys...,
   158  	)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	_, _, err = app.Deliver(txGen.TxEncoder(), tx)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  // SimulateMsgMultiSend tests and runs a single msg multisend, with randomized, capped number of inputs/outputs.
   172  // all accounts in msg fields exist in state
   173  func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Operation {
   174  	return func(
   175  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
   176  		accs []simtypes.Account, chainID string,
   177  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   178  		// random number of inputs/outputs between [1, 3]
   179  		inputs := make([]types.Input, r.Intn(3)+1)
   180  		outputs := make([]types.Output, r.Intn(3)+1)
   181  
   182  		// collect signer privKeys
   183  		privs := make([]cryptotypes.PrivKey, len(inputs))
   184  
   185  		// use map to check if address already exists as input
   186  		usedAddrs := make(map[string]bool)
   187  
   188  		var totalSentCoins sdk.Coins
   189  		for i := range inputs {
   190  			// generate random input fields, ignore to address
   191  			from, _, coins, skip := randomSendFields(r, ctx, accs, bk, ak)
   192  
   193  			// make sure account is fresh and not used in previous input
   194  			for usedAddrs[from.Address.String()] {
   195  				from, _, coins, skip = randomSendFields(r, ctx, accs, bk, ak)
   196  			}
   197  
   198  			if skip {
   199  				return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgMultiSend, "skip all transfers"), nil, nil
   200  			}
   201  
   202  			// set input address in used address map
   203  			usedAddrs[from.Address.String()] = true
   204  
   205  			// set signer privkey
   206  			privs[i] = from.PrivKey
   207  
   208  			// set next input and accumulate total sent coins
   209  			inputs[i] = types.NewInput(from.Address, coins)
   210  			totalSentCoins = totalSentCoins.Add(coins...)
   211  		}
   212  
   213  		// Check send_enabled status of each sent coin denom
   214  		if err := bk.IsSendEnabledCoins(ctx, totalSentCoins...); err != nil {
   215  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgMultiSend, err.Error()), nil, nil
   216  		}
   217  
   218  		for o := range outputs {
   219  			outAddr, _ := simtypes.RandomAcc(r, accs)
   220  
   221  			var outCoins sdk.Coins
   222  			// split total sent coins into random subsets for output
   223  			if o == len(outputs)-1 {
   224  				outCoins = totalSentCoins
   225  			} else {
   226  				// take random subset of remaining coins for output
   227  				// and update remaining coins
   228  				outCoins = simtypes.RandSubsetCoins(r, totalSentCoins)
   229  				totalSentCoins = totalSentCoins.Sub(outCoins)
   230  			}
   231  
   232  			outputs[o] = types.NewOutput(outAddr.Address, outCoins)
   233  		}
   234  
   235  		// remove any output that has no coins
   236  
   237  		for i := 0; i < len(outputs); {
   238  			if outputs[i].Coins.Empty() {
   239  				outputs[i] = outputs[len(outputs)-1]
   240  				outputs = outputs[:len(outputs)-1]
   241  			} else {
   242  				// continue onto next coin
   243  				i++
   244  			}
   245  		}
   246  
   247  		msg := &types.MsgMultiSend{
   248  			Inputs:  inputs,
   249  			Outputs: outputs,
   250  		}
   251  		err := sendMsgMultiSend(r, app, bk, ak, msg, ctx, chainID, privs)
   252  		if err != nil {
   253  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err
   254  		}
   255  
   256  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
   257  	}
   258  }
   259  
   260  // SimulateMsgMultiSendToModuleAccount sends coins to Module Accounts
   261  func SimulateMsgMultiSendToModuleAccount(ak types.AccountKeeper, bk keeper.Keeper, moduleAccCount int) simtypes.Operation {
   262  	return func(
   263  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
   264  		accs []simtypes.Account, chainID string,
   265  	) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
   266  		inputs := make([]types.Input, 2)
   267  		outputs := make([]types.Output, moduleAccCount)
   268  		// collect signer privKeys
   269  		privs := make([]cryptotypes.PrivKey, len(inputs))
   270  
   271  		var totalSentCoins sdk.Coins
   272  		for i := range inputs {
   273  			sender := accs[i]
   274  			privs[i] = sender.PrivKey
   275  			spendable := bk.SpendableCoins(ctx, sender.Address)
   276  			coins := simtypes.RandSubsetCoins(r, spendable)
   277  			inputs[i] = types.NewInput(sender.Address, coins)
   278  			totalSentCoins = totalSentCoins.Add(coins...)
   279  		}
   280  
   281  		if err := bk.IsSendEnabledCoins(ctx, totalSentCoins...); err != nil {
   282  			return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgMultiSend, err.Error()), nil, nil
   283  		}
   284  
   285  		moduleAccounts := getModuleAccounts(ak, ctx, moduleAccCount)
   286  		for i := range outputs {
   287  			var outCoins sdk.Coins
   288  			// split total sent coins into random subsets for output
   289  			if i == len(outputs)-1 {
   290  				outCoins = totalSentCoins
   291  			} else {
   292  				// take random subset of remaining coins for output
   293  				// and update remaining coins
   294  				outCoins = simtypes.RandSubsetCoins(r, totalSentCoins)
   295  				totalSentCoins = totalSentCoins.Sub(outCoins)
   296  			}
   297  
   298  			outputs[i] = types.NewOutput(moduleAccounts[i].Address, outCoins)
   299  		}
   300  
   301  		// remove any output that has no coins
   302  
   303  		for i := 0; i < len(outputs); {
   304  			if outputs[i].Coins.Empty() {
   305  				outputs[i] = outputs[len(outputs)-1]
   306  				outputs = outputs[:len(outputs)-1]
   307  			} else {
   308  				// continue onto next coin
   309  				i++
   310  			}
   311  		}
   312  
   313  		msg := &types.MsgMultiSend{
   314  			Inputs:  inputs,
   315  			Outputs: outputs,
   316  		}
   317  		err := sendMsgMultiSend(r, app, bk, ak, msg, ctx, chainID, privs)
   318  		if err != nil {
   319  			return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err
   320  		}
   321  
   322  		return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil
   323  	}
   324  }
   325  
   326  // sendMsgMultiSend sends a transaction with a MsgMultiSend from a provided random
   327  // account.
   328  func sendMsgMultiSend(
   329  	r *rand.Rand, app *baseapp.BaseApp, bk keeper.Keeper, ak types.AccountKeeper,
   330  	msg *types.MsgMultiSend, ctx sdk.Context, chainID string, privkeys []cryptotypes.PrivKey,
   331  ) error {
   332  	accountNumbers := make([]uint64, len(msg.Inputs))
   333  	sequenceNumbers := make([]uint64, len(msg.Inputs))
   334  
   335  	for i := 0; i < len(msg.Inputs); i++ {
   336  		addr := sdk.MustAccAddressFromBech32(msg.Inputs[i].Address)
   337  		acc := ak.GetAccount(ctx, addr)
   338  		accountNumbers[i] = acc.GetAccountNumber()
   339  		sequenceNumbers[i] = acc.GetSequence()
   340  	}
   341  
   342  	var (
   343  		fees sdk.Coins
   344  		err  error
   345  	)
   346  
   347  	addr := sdk.MustAccAddressFromBech32(msg.Inputs[0].Address)
   348  
   349  	// feePayer is the first signer, i.e. first input address
   350  	feePayer := ak.GetAccount(ctx, addr)
   351  	spendable := bk.SpendableCoins(ctx, feePayer.GetAddress())
   352  
   353  	coins, hasNeg := spendable.SafeSub(msg.Inputs[0].Coins)
   354  	if !hasNeg {
   355  		fees, err = simtypes.RandomFees(r, ctx, coins)
   356  		if err != nil {
   357  			return err
   358  		}
   359  	}
   360  
   361  	txGen := simappparams.MakeTestEncodingConfig().TxConfig
   362  	tx, err := helpers.GenSignedMockTx(
   363  		r,
   364  		txGen,
   365  		[]sdk.Msg{msg},
   366  		fees,
   367  		helpers.DefaultGenTxGas,
   368  		chainID,
   369  		accountNumbers,
   370  		sequenceNumbers,
   371  		privkeys...,
   372  	)
   373  	if err != nil {
   374  		return err
   375  	}
   376  
   377  	_, _, err = app.Deliver(txGen.TxEncoder(), tx)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	return nil
   383  }
   384  
   385  // randomSendFields returns the sender and recipient simulation accounts as well
   386  // as the transferred amount.
   387  func randomSendFields(
   388  	r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, bk keeper.Keeper, ak types.AccountKeeper,
   389  ) (simtypes.Account, simtypes.Account, sdk.Coins, bool) {
   390  	from, _ := simtypes.RandomAcc(r, accs)
   391  	to, _ := simtypes.RandomAcc(r, accs)
   392  
   393  	// disallow sending money to yourself
   394  	for from.PubKey.Equals(to.PubKey) {
   395  		to, _ = simtypes.RandomAcc(r, accs)
   396  	}
   397  
   398  	acc := ak.GetAccount(ctx, from.Address)
   399  	if acc == nil {
   400  		return from, to, nil, true
   401  	}
   402  
   403  	spendable := bk.SpendableCoins(ctx, acc.GetAddress())
   404  
   405  	sendCoins := simtypes.RandSubsetCoins(r, spendable)
   406  	if sendCoins.Empty() {
   407  		return from, to, nil, true
   408  	}
   409  
   410  	return from, to, sendCoins, false
   411  }
   412  
   413  func getModuleAccounts(ak types.AccountKeeper, ctx sdk.Context, moduleAccCount int) []simtypes.Account {
   414  	moduleAccounts := make([]simtypes.Account, moduleAccCount)
   415  
   416  	for i := 0; i < moduleAccCount; i++ {
   417  		addr := ak.GetModuleAddress(distributiontypes.ModuleName)
   418  		acc := ak.GetAccount(ctx, addr)
   419  		mAcc := simtypes.Account{
   420  			Address: acc.GetAddress(),
   421  			PrivKey: nil,
   422  			ConsKey: nil,
   423  			PubKey:  acc.GetPubKey(),
   424  		}
   425  		moduleAccounts[i] = mAcc
   426  	}
   427  
   428  	return moduleAccounts
   429  }