From 89970740287b7b2115b0bb1a69ea426d90a4d05e Mon Sep 17 00:00:00 2001 From: Andrei Ivasko Date: Thu, 13 May 2021 09:51:21 -0400 Subject: [PATCH] Use module accounts in MsgSend and MsgMultiSend tests in bank module (#9075) * first draft * unable to cast to simtypes.Account * fix test * add for loop in TestSimulateModuleAccountMsgSend * TestSimulateModuleAccountMsgMultiSend * refactoring, r4r * change fromSimAcc, toSimAcc to from,to respectively * Update x/bank/simulation/operations.go Co-authored-by: Robert Zaremba * Update x/bank/simulation/operations.go Co-authored-by: Robert Zaremba * getModuleAccounts * fix for loop * applied reviewers suggestions, r4r * Update x/bank/simulation/operations.go Co-authored-by: Robert Zaremba * minor changes * fix typo * all simulation package tests pass, r4r Co-authored-by: anilCSE Co-authored-by: Robert Zaremba Co-authored-by: Cory Co-authored-by: Aaron Craelius Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- x/bank/simulation/operations.go | 154 +++++++++++++++++++++++---- x/bank/simulation/operations_test.go | 78 ++++++++++++-- 2 files changed, 205 insertions(+), 27 deletions(-) diff --git a/x/bank/simulation/operations.go b/x/bank/simulation/operations.go index 8c3aef80f8..3b30157b85 100644 --- a/x/bank/simulation/operations.go +++ b/x/bank/simulation/operations.go @@ -12,6 +12,7 @@ import ( simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/bank/keeper" "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/simulation" ) @@ -58,7 +59,7 @@ func SimulateMsgSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Operatio r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - simAccount, toSimAcc, coins, skip := randomSendFields(r, ctx, accs, bk, ak) + from, to, coins, skip := randomSendFields(r, ctx, accs, bk, ak) // Check send_enabled status of each coin denom if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil { @@ -69,9 +70,39 @@ func SimulateMsgSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Operatio return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSend, "skip all transfers"), nil, nil } - msg := types.NewMsgSend(simAccount.Address, toSimAcc.Address, coins) + msg := types.NewMsgSend(from.Address, to.Address, coins) - err := sendMsgSend(r, app, bk, ak, msg, ctx, chainID, []cryptotypes.PrivKey{simAccount.PrivKey}) + err := sendMsgSend(r, app, bk, ak, msg, ctx, chainID, []cryptotypes.PrivKey{from.PrivKey}) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil + } +} + +// SimulateMsgSendToModuleAccount tests and runs a single msg send where both +// accounts already exist. +func SimulateMsgSendToModuleAccount(ak types.AccountKeeper, bk keeper.Keeper, moduleAccCount int) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + from := accs[0] + + to := getModuleAccounts(ak, ctx, moduleAccCount)[0] + + spendable := bk.SpendableCoins(ctx, from.Address) + coins := simtypes.RandSubsetCoins(r, spendable) + + // Check send_enabled status of each coin denom + if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSend, err.Error()), nil, nil + } + + msg := types.NewMsgSend(from.Address, to.Address, coins) + + err := sendMsgSend(r, app, bk, ak, msg, ctx, chainID, []cryptotypes.PrivKey{from.PrivKey}) if err != nil { return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err } @@ -150,11 +181,11 @@ func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Ope var totalSentCoins sdk.Coins for i := range inputs { // generate random input fields, ignore to address - simAccount, _, coins, skip := randomSendFields(r, ctx, accs, bk, ak) + from, _, coins, skip := randomSendFields(r, ctx, accs, bk, ak) // make sure account is fresh and not used in previous input - for usedAddrs[simAccount.Address.String()] { - simAccount, _, coins, skip = randomSendFields(r, ctx, accs, bk, ak) + for usedAddrs[from.Address.String()] { + from, _, coins, skip = randomSendFields(r, ctx, accs, bk, ak) } if skip { @@ -162,13 +193,13 @@ func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Ope } // set input address in used address map - usedAddrs[simAccount.Address.String()] = true + usedAddrs[from.Address.String()] = true // set signer privkey - privs[i] = simAccount.PrivKey + privs[i] = from.PrivKey // set next input and accumulate total sent coins - inputs[i] = types.NewInput(simAccount.Address, coins) + inputs[i] = types.NewInput(from.Address, coins) totalSentCoins = totalSentCoins.Add(coins...) } @@ -195,8 +226,8 @@ func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Ope } // remove any output that has no coins - i := 0 - for i < len(outputs) { + + for i := 0; i < len(outputs); { if outputs[i].Coins.Empty() { outputs[i] = outputs[len(outputs)-1] outputs = outputs[:len(outputs)-1] @@ -210,7 +241,73 @@ func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Ope Inputs: inputs, Outputs: outputs, } + err := sendMsgMultiSend(r, app, bk, ak, msg, ctx, chainID, privs) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err + } + return simtypes.NewOperationMsg(msg, true, "", nil), nil, nil + } +} + +// SimulateMsgMultiSendToModuleAccount sends coins to Module Accounts +func SimulateMsgMultiSendToModuleAccount(ak types.AccountKeeper, bk keeper.Keeper, moduleAccCount int) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + inputs := make([]types.Input, 2) + outputs := make([]types.Output, moduleAccCount) + // collect signer privKeys + privs := make([]cryptotypes.PrivKey, len(inputs)) + + var totalSentCoins sdk.Coins + for i := range inputs { + sender := accs[i] + privs[i] = sender.PrivKey + spendable := bk.SpendableCoins(ctx, sender.Address) + coins := simtypes.RandSubsetCoins(r, spendable) + inputs[i] = types.NewInput(sender.Address, coins) + totalSentCoins = totalSentCoins.Add(coins...) + } + + if err := bk.IsSendEnabledCoins(ctx, totalSentCoins...); err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgMultiSend, err.Error()), nil, nil + } + + moduleAccounts := getModuleAccounts(ak, ctx, moduleAccCount) + for i := range outputs { + var outCoins sdk.Coins + // split total sent coins into random subsets for output + if i == len(outputs)-1 { + outCoins = totalSentCoins + } else { + // take random subset of remaining coins for output + // and update remaining coins + outCoins = simtypes.RandSubsetCoins(r, totalSentCoins) + totalSentCoins = totalSentCoins.Sub(outCoins) + } + + outputs[i] = types.NewOutput(moduleAccounts[i].Address, outCoins) + } + + // remove any output that has no coins + + for i := 0; i < len(outputs); { + if outputs[i].Coins.Empty() { + outputs[i] = outputs[len(outputs)-1] + outputs = outputs[:len(outputs)-1] + } else { + // continue onto next coin + i++ + } + } + + msg := &types.MsgMultiSend{ + Inputs: inputs, + Outputs: outputs, + } err := sendMsgMultiSend(r, app, bk, ak, msg, ctx, chainID, privs) if err != nil { return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err @@ -291,25 +388,44 @@ func randomSendFields( r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, bk keeper.Keeper, ak types.AccountKeeper, ) (simtypes.Account, simtypes.Account, sdk.Coins, bool) { - simAccount, _ := simtypes.RandomAcc(r, accs) - toSimAcc, _ := simtypes.RandomAcc(r, accs) + from, _ := simtypes.RandomAcc(r, accs) + to, _ := simtypes.RandomAcc(r, accs) // disallow sending money to yourself - for simAccount.PubKey.Equals(toSimAcc.PubKey) { - toSimAcc, _ = simtypes.RandomAcc(r, accs) + for from.PubKey.Equals(to.PubKey) { + to, _ = simtypes.RandomAcc(r, accs) } - acc := ak.GetAccount(ctx, simAccount.Address) + acc := ak.GetAccount(ctx, from.Address) if acc == nil { - return simAccount, toSimAcc, nil, true + return from, to, nil, true } spendable := bk.SpendableCoins(ctx, acc.GetAddress()) sendCoins := simtypes.RandSubsetCoins(r, spendable) if sendCoins.Empty() { - return simAccount, toSimAcc, nil, true + return from, to, nil, true } - return simAccount, toSimAcc, sendCoins, false + return from, to, sendCoins, false +} + +func getModuleAccounts(ak types.AccountKeeper, ctx sdk.Context, moduleAccCount int) []simtypes.Account { + + moduleAccounts := make([]simtypes.Account, moduleAccCount) + + for i := 0; i < moduleAccCount; i++ { + addr := ak.GetModuleAddress(distributiontypes.ModuleName) + acc := ak.GetAccount(ctx, addr) + mAcc := simtypes.Account{ + Address: acc.GetAddress(), + PrivKey: nil, + ConsKey: nil, + PubKey: acc.GetPubKey(), + } + moduleAccounts[i] = mAcc + } + + return moduleAccounts } diff --git a/x/bank/simulation/operations_test.go b/x/bank/simulation/operations_test.go index 496a5f816b..ac654d608d 100644 --- a/x/bank/simulation/operations_test.go +++ b/x/bank/simulation/operations_test.go @@ -104,18 +104,80 @@ func (suite *SimTestSuite) TestSimulateMsgMultiSend() { // execute operation op := simulation.SimulateMsgMultiSend(suite.app.AccountKeeper, suite.app.BankKeeper) operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") - suite.Require().NoError(err) + require := suite.Require() + require.NoError(err) var msg types.MsgMultiSend types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg) - suite.Require().True(operationMsg.OK) - suite.Require().Len(msg.Inputs, 3) - suite.Require().Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Inputs[1].Address) - suite.Require().Equal("185121068stake", msg.Inputs[1].Coins.String()) - suite.Require().Len(msg.Outputs, 2) - suite.Require().Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Outputs[1].Address) - suite.Require().Equal("260469617stake", msg.Outputs[1].Coins.String()) + require.True(operationMsg.OK) + require.Len(msg.Inputs, 3) + require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Inputs[1].Address) + require.Equal("185121068stake", msg.Inputs[1].Coins.String()) + require.Len(msg.Outputs, 2) + require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Outputs[1].Address) + require.Equal("260469617stake", msg.Outputs[1].Coins.String()) + require.Equal(types.TypeMsgMultiSend, msg.Type()) + require.Equal(types.ModuleName, msg.Route()) + require.Len(futureOperations, 0) +} + +func (suite *SimTestSuite) TestSimulateModuleAccountMsgSend() { + const ( + accCount = 1 + moduleAccCount = 1 + ) + + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, accCount) + + // begin a new block + suite.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgSendToModuleAccount(suite.app.AccountKeeper, suite.app.BankKeeper, moduleAccCount) + + s = rand.NewSource(1) + r = rand.New(s) + + operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") + suite.Require().Error(err) + + var msg types.MsgSend + types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg) + + suite.Require().False(operationMsg.OK) + suite.Require().Equal(operationMsg.Comment, "invalid transfers") + suite.Require().Equal(types.TypeMsgSend, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +func (suite *SimTestSuite) TestSimulateMsgMultiSendToModuleAccount() { + const ( + accCount = 2 + mAccCount = 2 + ) + + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, accCount) + + // begin a new block + suite.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgMultiSendToModuleAccount(suite.app.AccountKeeper, suite.app.BankKeeper, mAccCount) + + operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") + suite.Require().Error(err) + + var msg types.MsgMultiSend + types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg) + + suite.Require().False(operationMsg.OK) // sending tokens to a module account should fail + suite.Require().Equal(operationMsg.Comment, "invalid transfers") suite.Require().Equal(types.TypeMsgMultiSend, msg.Type()) suite.Require().Equal(types.ModuleName, msg.Route()) suite.Require().Len(futureOperations, 0)