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