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 }