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 }