github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/bank/simulation/operations.go (about)

     1  package simulation
     2  
     3  import (
     4  	"math/rand"
     5  
     6  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto"
     7  
     8  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/baseapp"
     9  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec"
    10  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/simapp/helpers"
    11  	simappparams "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/simapp/params"
    12  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    13  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/bank/internal/keeper"
    14  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/bank/internal/types"
    15  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/simulation"
    16  )
    17  
    18  // Simulation operation weights constants
    19  const (
    20  	OpWeightMsgSend      = "op_weight_msg_send"
    21  	OpWeightMsgMultiSend = "op_weight_msg_multisend"
    22  )
    23  
    24  // WeightedOperations returns all the operations from the module with their respective weights
    25  func WeightedOperations(appParams simulation.AppParams, cdc *codec.Codec, ak types.AccountKeeper,
    26  	bk keeper.Keeper) simulation.WeightedOperations {
    27  
    28  	var weightMsgSend, weightMsgMultiSend int
    29  	appParams.GetOrGenerate(cdc, OpWeightMsgSend, &weightMsgSend, nil,
    30  		func(_ *rand.Rand) {
    31  			weightMsgSend = simappparams.DefaultWeightMsgSend
    32  		},
    33  	)
    34  
    35  	appParams.GetOrGenerate(cdc, OpWeightMsgMultiSend, &weightMsgMultiSend, nil,
    36  		func(_ *rand.Rand) {
    37  			weightMsgMultiSend = simappparams.DefaultWeightMsgMultiSend
    38  		},
    39  	)
    40  
    41  	return simulation.WeightedOperations{
    42  		simulation.NewWeightedOperation(
    43  			weightMsgSend,
    44  			SimulateMsgSend(ak, bk),
    45  		),
    46  		simulation.NewWeightedOperation(
    47  			weightMsgMultiSend,
    48  			SimulateMsgMultiSend(ak, bk),
    49  		),
    50  	}
    51  }
    52  
    53  // SimulateMsgSend tests and runs a single msg send where both
    54  // accounts already exist.
    55  // nolint: funlen
    56  func SimulateMsgSend(ak types.AccountKeeper, bk keeper.Keeper) simulation.Operation {
    57  	return func(
    58  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
    59  		accs []simulation.Account, chainID string,
    60  	) (simulation.OperationMsg, []simulation.FutureOperation, error) {
    61  
    62  		if !bk.GetSendEnabled(ctx) {
    63  			return simulation.NoOpMsg(types.ModuleName), nil, nil
    64  		}
    65  
    66  		simAccount, toSimAcc, coins, skip, err := randomSendFields(r, ctx, accs, ak)
    67  		if err != nil {
    68  			return simulation.NoOpMsg(types.ModuleName), nil, err
    69  		}
    70  
    71  		if skip {
    72  			return simulation.NoOpMsg(types.ModuleName), nil, nil
    73  		}
    74  
    75  		msg := types.NewMsgSend(simAccount.Address, toSimAcc.Address, coins)
    76  
    77  		err = sendMsgSend(r, app, ak, msg, ctx, chainID, []crypto.PrivKey{simAccount.PrivKey})
    78  		if err != nil {
    79  			return simulation.NoOpMsg(types.ModuleName), nil, err
    80  		}
    81  
    82  		return simulation.NewOperationMsg(msg, true, ""), nil, nil
    83  	}
    84  }
    85  
    86  // sendMsgSend sends a transaction with a MsgSend from a provided random account.
    87  func sendMsgSend(
    88  	r *rand.Rand, app *baseapp.BaseApp, ak types.AccountKeeper, // nolint:interfacer
    89  	msg types.MsgSend, ctx sdk.Context, chainID string, privkeys []crypto.PrivKey,
    90  ) error {
    91  
    92  	account := ak.GetAccount(ctx, msg.FromAddress)
    93  	coins := account.SpendableCoins(ctx.BlockTime())
    94  
    95  	var (
    96  		fees sdk.Coins
    97  		err  error
    98  	)
    99  	coins, hasNeg := coins.SafeSub(msg.Amount)
   100  	if !hasNeg {
   101  		fees, err = simulation.RandomFees(r, ctx, coins)
   102  		if err != nil {
   103  			return err
   104  		}
   105  	}
   106  
   107  	tx := helpers.GenTx(
   108  		[]sdk.Msg{msg},
   109  		fees,
   110  		helpers.DefaultGenTxGas,
   111  		chainID,
   112  		[]uint64{account.GetAccountNumber()},
   113  		[]uint64{account.GetSequence()},
   114  		privkeys...,
   115  	)
   116  
   117  	_, _, err = app.Deliver(tx)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // SimulateMsgMultiSend tests and runs a single msg multisend, with randomized, capped number of inputs/outputs.
   126  // all accounts in msg fields exist in state
   127  // nolint: funlen
   128  func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simulation.Operation {
   129  	return func(
   130  		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
   131  		accs []simulation.Account, chainID string,
   132  	) (simulation.OperationMsg, []simulation.FutureOperation, error) {
   133  
   134  		if !bk.GetSendEnabled(ctx) {
   135  			return simulation.NoOpMsg(types.ModuleName), nil, nil
   136  		}
   137  
   138  		// random number of inputs/outputs between [1, 3]
   139  		inputs := make([]types.Input, r.Intn(3)+1)
   140  		outputs := make([]types.Output, r.Intn(3)+1)
   141  
   142  		// collect signer privKeys
   143  		privs := make([]crypto.PrivKey, len(inputs))
   144  
   145  		// use map to check if address already exists as input
   146  		usedAddrs := make(map[string]bool)
   147  
   148  		var totalSentCoins sdk.Coins
   149  		for i := range inputs {
   150  			// generate random input fields, ignore to address
   151  			simAccount, _, coins, skip, err := randomSendFields(r, ctx, accs, ak)
   152  
   153  			// make sure account is fresh and not used in previous input
   154  			for usedAddrs[simAccount.Address.String()] {
   155  				simAccount, _, coins, skip, err = randomSendFields(r, ctx, accs, ak)
   156  			}
   157  
   158  			if err != nil {
   159  				return simulation.NoOpMsg(types.ModuleName), nil, err
   160  			}
   161  			if skip {
   162  				return simulation.NoOpMsg(types.ModuleName), nil, nil
   163  			}
   164  
   165  			// set input address in used address map
   166  			usedAddrs[simAccount.Address.String()] = true
   167  
   168  			// set signer privkey
   169  			privs[i] = simAccount.PrivKey
   170  
   171  			// set next input and accumulate total sent coins
   172  			inputs[i] = types.NewInput(simAccount.Address, coins)
   173  			totalSentCoins = totalSentCoins.Add(coins...)
   174  		}
   175  
   176  		for o := range outputs {
   177  			outAddr, _ := simulation.RandomAcc(r, accs)
   178  
   179  			var outCoins sdk.Coins
   180  			// split total sent coins into random subsets for output
   181  			if o == len(outputs)-1 {
   182  				outCoins = totalSentCoins
   183  			} else {
   184  				// take random subset of remaining coins for output
   185  				// and update remaining coins
   186  				outCoins = simulation.RandSubsetCoins(r, totalSentCoins)
   187  				totalSentCoins = totalSentCoins.Sub(outCoins)
   188  			}
   189  
   190  			outputs[o] = types.NewOutput(outAddr.Address, outCoins)
   191  		}
   192  
   193  		// remove any output that has no coins
   194  		i := 0
   195  		for i < len(outputs) {
   196  			if outputs[i].Coins.Empty() {
   197  				outputs[i] = outputs[len(outputs)-1]
   198  				outputs = outputs[:len(outputs)-1]
   199  			} else {
   200  				// continue onto next coin
   201  				i++
   202  			}
   203  		}
   204  
   205  		msg := types.MsgMultiSend{
   206  			Inputs:  inputs,
   207  			Outputs: outputs,
   208  		}
   209  
   210  		err := sendMsgMultiSend(r, app, ak, msg, ctx, chainID, privs)
   211  		if err != nil {
   212  			return simulation.NoOpMsg(types.ModuleName), nil, err
   213  		}
   214  
   215  		return simulation.NewOperationMsg(msg, true, ""), nil, nil
   216  	}
   217  }
   218  
   219  // sendMsgMultiSend sends a transaction with a MsgMultiSend from a provided random
   220  // account.
   221  func sendMsgMultiSend(
   222  	r *rand.Rand, app *baseapp.BaseApp, ak types.AccountKeeper, // nolint:interfacer
   223  	msg types.MsgMultiSend, ctx sdk.Context, chainID string, privkeys []crypto.PrivKey,
   224  ) error {
   225  
   226  	accountNumbers := make([]uint64, len(msg.Inputs))
   227  	sequenceNumbers := make([]uint64, len(msg.Inputs))
   228  
   229  	for i := 0; i < len(msg.Inputs); i++ {
   230  		acc := ak.GetAccount(ctx, msg.Inputs[i].Address)
   231  		accountNumbers[i] = acc.GetAccountNumber()
   232  		sequenceNumbers[i] = acc.GetSequence()
   233  	}
   234  
   235  	// feePayer is the first signer, i.e. first input address
   236  	feePayer := ak.GetAccount(ctx, msg.Inputs[0].Address)
   237  	coins := feePayer.SpendableCoins(ctx.BlockTime())
   238  
   239  	var (
   240  		fees sdk.Coins
   241  		err  error
   242  	)
   243  	coins, hasNeg := coins.SafeSub(msg.Inputs[0].Coins)
   244  	if !hasNeg {
   245  		fees, err = simulation.RandomFees(r, ctx, coins)
   246  		if err != nil {
   247  			return err
   248  		}
   249  	}
   250  
   251  	tx := helpers.GenTx(
   252  		[]sdk.Msg{msg},
   253  		fees,
   254  		helpers.DefaultGenTxGas,
   255  		chainID,
   256  		accountNumbers,
   257  		sequenceNumbers,
   258  		privkeys...,
   259  	)
   260  
   261  	_, _, err = app.Deliver(tx)
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  // randomSendFields returns the sender and recipient simulation accounts as well
   270  // as the transferred amount.
   271  func randomSendFields(
   272  	r *rand.Rand, ctx sdk.Context, accs []simulation.Account, ak types.AccountKeeper, // nolint:interfacer
   273  ) (simulation.Account, simulation.Account, sdk.Coins, bool, error) {
   274  
   275  	simAccount, _ := simulation.RandomAcc(r, accs)
   276  	toSimAcc, _ := simulation.RandomAcc(r, accs)
   277  
   278  	// disallow sending money to yourself
   279  	for simAccount.PubKey.Equals(toSimAcc.PubKey) {
   280  		toSimAcc, _ = simulation.RandomAcc(r, accs)
   281  	}
   282  
   283  	acc := ak.GetAccount(ctx, simAccount.Address)
   284  	if acc == nil {
   285  		return simAccount, toSimAcc, nil, true, nil // skip error
   286  	}
   287  
   288  	coins := acc.SpendableCoins(ctx.BlockHeader().Time)
   289  
   290  	sendCoins := simulation.RandSubsetCoins(r, coins)
   291  	if sendCoins.Empty() {
   292  		return simAccount, toSimAcc, nil, true, nil // skip error
   293  	}
   294  
   295  	return simAccount, toSimAcc, sendCoins, false, nil
   296  }